1
0
mirror of https://github.com/fluencelabs/assemblyscript synced 2025-06-19 18:01:31 +00:00

Make 'instanceof' behave like TS if the lhs is nullable

This commit is contained in:
dcodeIO
2018-06-09 02:01:45 +02:00
parent 7478c8a0d3
commit 47f2e0950a
8 changed files with 220 additions and 54 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -2377,18 +2377,17 @@ export class Compiler extends DiagnosticEmitter {
} }
// any to void // any to void
if (toType.kind == TypeKind.VOID) { if (toType.kind == TypeKind.VOID) return module.createDrop(expr);
return module.createDrop(expr);
}
if (conversionKind == ConversionKind.IMPLICIT && !fromType.isAssignableTo(toType)) { if (!fromType.isAssignableTo(toType)) {
if (conversionKind == ConversionKind.IMPLICIT) {
this.error( this.error(
DiagnosticCode.Conversion_from_type_0_to_1_requires_an_explicit_cast, DiagnosticCode.Conversion_from_type_0_to_1_requires_an_explicit_cast,
reportNode.range, fromType.toString(), toType.toString() reportNode.range, fromType.toString(), toType.toString()
); // recoverable ); // recoverable
} }
}
// TODO: make this a proper switch?
if (fromType.is(TypeFlags.FLOAT)) { if (fromType.is(TypeFlags.FLOAT)) {
// float to float // float to float
@ -4502,7 +4501,7 @@ export class Compiler extends DiagnosticEmitter {
if (!target) return this.module.createUnreachable(); if (!target) return this.module.createUnreachable();
// to compile just the value, we need to know the target's type // to compile just the value, we need to know the target's type
var elementType: Type; var targetType: Type;
switch (target.kind) { switch (target.kind) {
case ElementKind.GLOBAL: { case ElementKind.GLOBAL: {
if (!this.compileGlobal(<Global>target)) { // reports; not yet compiled if a static field compiled as a global if (!this.compileGlobal(<Global>target)) { // reports; not yet compiled if a static field compiled as a global
@ -4513,7 +4512,7 @@ export class Compiler extends DiagnosticEmitter {
} }
case ElementKind.LOCAL: case ElementKind.LOCAL:
case ElementKind.FIELD: { case ElementKind.FIELD: {
elementType = (<VariableLikeElement>target).type; targetType = (<VariableLikeElement>target).type;
break; break;
} }
case ElementKind.PROPERTY: { case ElementKind.PROPERTY: {
@ -4522,7 +4521,7 @@ export class Compiler extends DiagnosticEmitter {
let instance = prototype.resolve(); // reports let instance = prototype.resolve(); // reports
if (!instance) return this.module.createUnreachable(); if (!instance) return this.module.createUnreachable();
assert(instance.signature.parameterTypes.length == 1); // parser must guarantee this assert(instance.signature.parameterTypes.length == 1); // parser must guarantee this
elementType = instance.signature.parameterTypes[0]; targetType = instance.signature.parameterTypes[0];
break; break;
} }
this.error( this.error(
@ -4551,7 +4550,7 @@ export class Compiler extends DiagnosticEmitter {
return this.module.createUnreachable(); return this.module.createUnreachable();
} }
assert(indexedSet.signature.parameterTypes.length == 2); // parser must guarantee this assert(indexedSet.signature.parameterTypes.length == 2); // parser must guarantee this
elementType = indexedSet.signature.parameterTypes[1]; // 2nd parameter is the element targetType = indexedSet.signature.parameterTypes[1]; // 2nd parameter is the element
break; break;
} }
// fall-through // fall-through
@ -4566,7 +4565,7 @@ export class Compiler extends DiagnosticEmitter {
} }
// compile the value and do the assignment // compile the value and do the assignment
var valueExpr = this.compileExpression(valueExpression, elementType, ConversionKind.IMPLICIT, WrapMode.NONE); var valueExpr = this.compileExpression(valueExpression, targetType, ConversionKind.IMPLICIT, WrapMode.NONE);
return this.compileAssignmentWithValue( return this.compileAssignmentWithValue(
expression, expression,
valueExpr, valueExpr,
@ -5882,12 +5881,26 @@ export class Compiler extends DiagnosticEmitter {
expression: InstanceOfExpression, expression: InstanceOfExpression,
contextualType: Type contextualType: Type
): ExpressionRef { ): ExpressionRef {
this.compileExpressionRetainType(expression.expression, this.options.usizeType, WrapMode.NONE); var module = this.module;
// NOTE that this differs from TypeScript in that the rhs is a type, not an expression. at the
// time of implementation, this seemed more useful because dynamic rhs expressions are not
// possible in AS anyway.
var expr = this.compileExpressionRetainType(expression.expression, this.options.usizeType, WrapMode.NONE);
var type = this.currentType; var type = this.currentType;
var isType = this.program.resolveType(expression.isType); var isType = this.program.resolveType(expression.isType);
this.currentType = Type.bool; this.currentType = Type.bool;
if (!isType) return this.module.createUnreachable(); if (!isType) return module.createUnreachable();
return this.module.createI32(type.isAssignableTo(isType, false) ? 1 : 0); return type.is(TypeFlags.NULLABLE) && !isType.is(TypeFlags.NULLABLE)
? type.nonNullableType.isAssignableTo(isType)
? module.createBinary( // not precomputeable
type.is(TypeFlags.LONG)
? BinaryOp.NeI64
: BinaryOp.NeI32,
expr,
type.toNativeZero(module)
)
: module.createI32(0)
: module.createI32(type.isAssignableTo(isType, true) ? 1 : 0);
} }
compileLiteralExpression( compileLiteralExpression(

@ -1847,7 +1847,9 @@ export class Program extends DiagnosticEmitter {
if (node.kind == NodeKind.SIGNATURE) { if (node.kind == NodeKind.SIGNATURE) {
let signature = this.resolveSignature(<SignatureNode>node, contextualTypeArguments, reportNotFound); let signature = this.resolveSignature(<SignatureNode>node, contextualTypeArguments, reportNotFound);
if (!signature) return null; if (!signature) return null;
return Type.u32.asFunction(signature); return node.isNullable
? signature.type.asNullable()
: signature.type;
} }
var typeNode = <TypeNode>node; var typeNode = <TypeNode>node;
var simpleName = typeNode.name.text; var simpleName = typeNode.name.text;
@ -1867,7 +1869,10 @@ export class Program extends DiagnosticEmitter {
contextualTypeArguments, contextualTypeArguments,
null null
); // reports ); // reports
return instance ? instance.type : null; if (!instance) return null;
return node.isNullable
? instance.type.asNullable()
: instance.type;
} }
} }
} }

@ -170,6 +170,7 @@ export class Type {
if (!this.cachedNullableType) { if (!this.cachedNullableType) {
assert(!this.is(TypeFlags.NULLABLE)); assert(!this.is(TypeFlags.NULLABLE));
this.cachedNullableType = new Type(this.kind, this.flags | TypeFlags.NULLABLE, this.size); this.cachedNullableType = new Type(this.kind, this.flags | TypeFlags.NULLABLE, this.size);
this.cachedNullableType.nonNullableType = this;
this.cachedNullableType.classReference = this.classReference; // either a class reference this.cachedNullableType.classReference = this.classReference; // either a class reference
this.cachedNullableType.signatureReference = this.signatureReference; // or a function reference this.cachedNullableType.signatureReference = this.signatureReference; // or a function reference
} }
@ -177,13 +178,14 @@ export class Type {
} }
/** Tests if a value of this type is assignable to a target of the specified type. */ /** Tests if a value of this type is assignable to a target of the specified type. */
isAssignableTo(target: Type, signednessIsImportant: bool = false): bool { isAssignableTo(target: Type, signednessIsRelevant: bool = false): bool {
var currentClass: Class | null; var currentClass: Class | null;
var targetClass: Class | null; var targetClass: Class | null;
var currentFunction: Signature | null; var currentFunction: Signature | null;
var targetFunction: Signature | null; var targetFunction: Signature | null;
if (this.is(TypeFlags.REFERENCE)) { if (this.is(TypeFlags.REFERENCE)) {
if (target.is(TypeFlags.REFERENCE)) { if (target.is(TypeFlags.REFERENCE)) {
if (!this.is(TypeFlags.NULLABLE) || target.is(TypeFlags.NULLABLE)) {
if (currentClass = this.classReference) { if (currentClass = this.classReference) {
if (targetClass = target.classReference) { if (targetClass = target.classReference) {
return currentClass.isAssignableTo(targetClass); return currentClass.isAssignableTo(targetClass);
@ -194,10 +196,11 @@ export class Type {
} }
} }
} }
}
} else if (!target.is(TypeFlags.REFERENCE)) { } else if (!target.is(TypeFlags.REFERENCE)) {
if (this.is(TypeFlags.INTEGER)) { if (this.is(TypeFlags.INTEGER)) {
if (target.is(TypeFlags.INTEGER)) { if (target.is(TypeFlags.INTEGER)) {
if (!signednessIsImportant || this.is(TypeFlags.SIGNED) == target.is(TypeFlags.SIGNED)) { if (!signednessIsRelevant || this.is(TypeFlags.SIGNED) == target.is(TypeFlags.SIGNED)) {
return this.size <= target.size; return this.size <= target.size;
} }
} else if (target.kind == TypeKind.F32) { } else if (target.kind == TypeKind.F32) {
@ -223,6 +226,21 @@ export class Type {
/** Converts this type to its TypeScript representation. */ /** Converts this type to its TypeScript representation. */
toString(kindOnly: bool = false): string { toString(kindOnly: bool = false): string {
if (!kindOnly && this.is(TypeFlags.REFERENCE)) {
let classReference = this.classReference;
if (classReference) {
return this.is(TypeFlags.NULLABLE)
? classReference.toString() + " | null"
: classReference.toString();
}
let signatureReference = this.signatureReference;
if (signatureReference) {
return this.is(TypeFlags.NULLABLE)
? "(" + signatureReference.toString(true) + ") | null"
: signatureReference.toString(true);
}
assert(false);
}
switch (this.kind) { switch (this.kind) {
case TypeKind.I8: return "i8"; case TypeKind.I8: return "i8";
case TypeKind.I16: return "i16"; case TypeKind.I16: return "i16";
@ -231,23 +249,14 @@ export class Type {
case TypeKind.ISIZE: return "isize"; case TypeKind.ISIZE: return "isize";
case TypeKind.U8: return "u8"; case TypeKind.U8: return "u8";
case TypeKind.U16: return "u16"; case TypeKind.U16: return "u16";
case TypeKind.U32: { case TypeKind.U32: return "u32";
let functionType = this.signatureReference;
return kindOnly || !functionType ? "u32" : functionType.toString(true);
}
case TypeKind.U64: return "u64"; case TypeKind.U64: return "u64";
case TypeKind.USIZE: { case TypeKind.USIZE: return "usize";
let classType = this.classReference;
return kindOnly || !classType ? "usize" : classType.toString();
}
case TypeKind.BOOL: return "bool"; case TypeKind.BOOL: return "bool";
case TypeKind.F32: return "f32"; case TypeKind.F32: return "f32";
case TypeKind.F64: return "f64"; case TypeKind.F64: return "f64";
default: assert(false);
case TypeKind.VOID: return "void"; case TypeKind.VOID: return "void";
default: {
assert(false);
return "";
}
} }
} }

@ -4,6 +4,7 @@
(type $Fi (func (param f64) (result i32))) (type $Fi (func (param f64) (result i32)))
(type $v (func)) (type $v (func))
(import "env" "abort" (func $~lib/env/abort (param i32 i32 i32 i32))) (import "env" "abort" (func $~lib/env/abort (param i32 i32 i32 i32)))
(global $instanceof/an (mut i32) (i32.const 0))
(memory $0 1) (memory $0 1)
(data (i32.const 8) "\0d\00\00\00i\00n\00s\00t\00a\00n\00c\00e\00o\00f\00.\00t\00s") (data (i32.const 8) "\0d\00\00\00i\00n\00s\00t\00a\00n\00c\00e\00o\00f\00.\00t\00s")
(export "memory" (memory $0)) (export "memory" (memory $0))
@ -14,7 +15,10 @@
(func $instanceof/isI32<f64> (; 2 ;) (type $Fi) (param $0 f64) (result i32) (func $instanceof/isI32<f64> (; 2 ;) (type $Fi) (param $0 f64) (result i32)
(i32.const 0) (i32.const 0)
) )
(func $start (; 3 ;) (type $v) (func $instanceof/isI32<u32> (; 3 ;) (type $ii) (param $0 i32) (result i32)
(i32.const 0)
)
(func $start (; 4 ;) (type $v)
(if (if
(i32.eqz (i32.eqz
(call $instanceof/isI32<i32> (call $instanceof/isI32<i32>
@ -45,5 +49,48 @@
(unreachable) (unreachable)
) )
) )
(if
(call $instanceof/isI32<u32>
(i32.const 0)
)
(block
(call $~lib/env/abort
(i32.const 0)
(i32.const 8)
(i32.const 40)
(i32.const 0)
)
(unreachable)
)
)
(if
(get_global $instanceof/an)
(block
(call $~lib/env/abort
(i32.const 0)
(i32.const 8)
(i32.const 43)
(i32.const 0)
)
(unreachable)
)
)
(set_global $instanceof/an
(i32.const 1)
)
(if
(i32.eqz
(get_global $instanceof/an)
)
(block
(call $~lib/env/abort
(i32.const 0)
(i32.const 8)
(i32.const 46)
(i32.const 0)
)
(unreachable)
)
)
) )
) )

@ -37,13 +37,14 @@ function isI32<T>(v: T): bool {
assert( isI32(0)); assert( isI32(0));
assert(!isI32(0.0)); assert(!isI32(0.0));
assert(!isI32(<u32>0)); // signedness is relevant
// TODO: what about nullables? var an: A | null = null;
// var an: A | null; assert(!(an instanceof A)); // TS: null is not an instance of A
// var bn: B | null; assert( an instanceof A | null); // AS: null is an instance of A | null
// an = changetype<A | null>(1);
// assert(an instanceof A); assert( an instanceof A); // TS: non-null is an instance of A
// assert(bn instanceof A); assert( an instanceof A | null); // AS: non-null is an instance of A | null
//
// assert(!(an instanceof B)); // TODO: keep track of nullability during flows, so this becomes precomputable:
// assert(bn instanceof B); // assert(an !== null && an instanceof A);

@ -8,6 +8,7 @@
(global $instanceof/b (mut i32) (i32.const 0)) (global $instanceof/b (mut i32) (i32.const 0))
(global $instanceof/i (mut i32) (i32.const 0)) (global $instanceof/i (mut i32) (i32.const 0))
(global $instanceof/f (mut f32) (f32.const 0)) (global $instanceof/f (mut f32) (f32.const 0))
(global $instanceof/an (mut i32) (i32.const 0))
(global $HEAP_BASE i32 (i32.const 40)) (global $HEAP_BASE i32 (i32.const 40))
(memory $0 1) (memory $0 1)
(data (i32.const 8) "\0d\00\00\00i\00n\00s\00t\00a\00n\00c\00e\00o\00f\00.\00t\00s\00") (data (i32.const 8) "\0d\00\00\00i\00n\00s\00t\00a\00n\00c\00e\00o\00f\00.\00t\00s\00")
@ -23,7 +24,12 @@
(i32.const 0) (i32.const 0)
) )
) )
(func $start (; 3 ;) (type $v) (func $instanceof/isI32<u32> (; 3 ;) (type $ii) (param $0 i32) (result i32)
(return
(i32.const 0)
)
)
(func $start (; 4 ;) (type $v)
(if (if
(i32.eqz (i32.eqz
(i32.const 1) (i32.const 1)
@ -304,5 +310,90 @@
(unreachable) (unreachable)
) )
) )
(if
(i32.eqz
(i32.eqz
(call $instanceof/isI32<u32>
(i32.const 0)
)
)
)
(block
(call $~lib/env/abort
(i32.const 0)
(i32.const 8)
(i32.const 40)
(i32.const 0)
)
(unreachable)
)
)
(if
(i32.eqz
(i32.eqz
(i32.ne
(get_global $instanceof/an)
(i32.const 0)
)
)
)
(block
(call $~lib/env/abort
(i32.const 0)
(i32.const 8)
(i32.const 43)
(i32.const 0)
)
(unreachable)
)
)
(if
(i32.eqz
(i32.const 1)
)
(block
(call $~lib/env/abort
(i32.const 0)
(i32.const 8)
(i32.const 44)
(i32.const 0)
)
(unreachable)
)
)
(set_global $instanceof/an
(i32.const 1)
)
(if
(i32.eqz
(i32.ne
(get_global $instanceof/an)
(i32.const 0)
)
)
(block
(call $~lib/env/abort
(i32.const 0)
(i32.const 8)
(i32.const 46)
(i32.const 0)
)
(unreachable)
)
)
(if
(i32.eqz
(i32.const 1)
)
(block
(call $~lib/env/abort
(i32.const 0)
(i32.const 8)
(i32.const 47)
(i32.const 0)
)
(unreachable)
)
)
) )
) )