Skip to content

Commit e0ccf0a

Browse files
authored
Handle instanceof of entire classes (#1108)
1 parent 0710219 commit e0ccf0a

File tree

7 files changed

+629
-9
lines changed

7 files changed

+629
-9
lines changed

src/ast.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1094,6 +1094,11 @@ export class NamedTypeNode extends TypeNode {
10941094
name: TypeName;
10951095
/** Type argument references. */
10961096
typeArguments: TypeNode[] | null;
1097+
1098+
get hasTypeArguments(): bool {
1099+
var typeArguments = this.typeArguments;
1100+
return typeArguments !== null && typeArguments.length > 0;
1101+
}
10971102
}
10981103

10991104
/** Represents a function type. */

src/builtins.ts

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ import {
6464
Global,
6565
DecoratorFlags,
6666
Element,
67-
Class
67+
ClassPrototype
6868
} from "./program";
6969

7070
import {
@@ -4971,6 +4971,58 @@ export function compileRTTI(compiler: Compiler): void {
49714971
}
49724972
}
49734973

4974+
/** Compiles a class-specific instanceof helper, checking a ref against all concrete instances. */
4975+
export function compileClassInstanceOf(compiler: Compiler, prototype: ClassPrototype): void {
4976+
var module = compiler.module;
4977+
var nativeSizeType = compiler.options.nativeSizeType;
4978+
var instanceofInstance = assert(prototype.program.instanceofInstance);
4979+
compiler.compileFunction(instanceofInstance);
4980+
4981+
var stmts = new Array<ExpressionRef>();
4982+
4983+
// if (!ref) return false
4984+
stmts.push(
4985+
module.if(
4986+
module.unary(
4987+
nativeSizeType == NativeType.I64
4988+
? UnaryOp.EqzI64
4989+
: UnaryOp.EqzI32,
4990+
module.local_get(0, nativeSizeType)
4991+
),
4992+
module.return(
4993+
module.i32(0)
4994+
)
4995+
)
4996+
);
4997+
4998+
// if (__instanceof(ref, ID[i])) return true
4999+
var instances = prototype.instances;
5000+
if (instances !== null && instances.size) {
5001+
for (let instance of instances.values()) {
5002+
stmts.push(
5003+
module.if(
5004+
module.call(instanceofInstance.internalName, [
5005+
module.local_get(0, nativeSizeType),
5006+
module.i32(instance.id)
5007+
], NativeType.I32),
5008+
module.return(
5009+
module.i32(1)
5010+
)
5011+
)
5012+
);
5013+
}
5014+
}
5015+
5016+
// return false
5017+
stmts.push(
5018+
module.return(
5019+
module.i32(0)
5020+
)
5021+
);
5022+
5023+
module.addFunction(prototype.internalName + "~instanceof", nativeSizeType, NativeType.I32, null, module.flatten(stmts));
5024+
}
5025+
49745026
// Helpers
49755027

49765028
/** Evaluates the constant type of a type argument *or* expression. */

src/compiler.ts

Lines changed: 86 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
compileVisitGlobals,
1010
compileVisitMembers,
1111
compileRTTI,
12+
compileClassInstanceOf,
1213
} from "./builtins";
1314

1415
import {
@@ -160,6 +161,8 @@ import {
160161
UnaryPostfixExpression,
161162
UnaryPrefixExpression,
162163

164+
NamedTypeNode,
165+
163166
nodeIsConstantValue,
164167
findDecorator,
165168
isTypeOmitted
@@ -335,6 +338,8 @@ export class Compiler extends DiagnosticEmitter {
335338
inlineStack: Function[] = [];
336339
/** Lazily compiled library functions. */
337340
lazyLibraryFunctions: Set<Function> = new Set();
341+
/** Pending class-specific instanceof helpers. */
342+
pendingClassInstanceOf: Set<ClassPrototype> = new Set();
338343

339344
/** Compiles a {@link Program} to a {@link Module} using the specified options. */
340345
static compile(program: Program): Module {
@@ -455,6 +460,11 @@ export class Compiler extends DiagnosticEmitter {
455460
}
456461
} while (lazyLibraryFunctions.size);
457462

463+
// compile pending class-specific instanceof helpers
464+
for (let prototype of this.pendingClassInstanceOf.values()) {
465+
compileClassInstanceOf(this, prototype);
466+
}
467+
458468
// finalize runtime features
459469
module.removeGlobal(BuiltinNames.rtti_base);
460470
if (this.runtimeFeatures & RuntimeFeatures.RTTI) compileRTTI(this);
@@ -7666,21 +7676,42 @@ export class Compiler extends DiagnosticEmitter {
76667676
contextualType: Type,
76677677
constraints: Constraints
76687678
): ExpressionRef {
7669-
var module = this.module;
7670-
// NOTE that this differs from TypeScript in that the rhs is a type, not an expression. at the
7671-
// time of implementation, this seemed more useful because dynamic rhs expressions are not
7672-
// possible in AS anyway. also note that the code generated below must preserve side-effects of
7673-
// the LHS expression even when the result is a constant, i.e. return a block dropping `expr`.
76747679
var flow = this.currentFlow;
7675-
var expr = this.compileExpression(expression.expression, this.options.usizeType);
7676-
var actualType = this.currentType;
7680+
var isType = expression.isType;
7681+
7682+
// Mimic `instanceof CLASS`
7683+
if (isType.kind == NodeKind.NAMEDTYPE) {
7684+
let namedType = <NamedTypeNode>isType;
7685+
if (!(namedType.isNullable || namedType.hasTypeArguments)) {
7686+
let element = this.resolver.resolveTypeName(namedType.name, flow.actualFunction, ReportMode.SWALLOW);
7687+
if (element !== null && element.kind == ElementKind.CLASS_PROTOTYPE) {
7688+
let prototype = <ClassPrototype>element;
7689+
if (prototype.is(CommonFlags.GENERIC)) {
7690+
return this.makeInstanceofClass(expression, prototype);
7691+
}
7692+
}
7693+
}
7694+
}
7695+
7696+
// Fall back to `instanceof TYPE`
76777697
var expectedType = this.resolver.resolveType(
76787698
expression.isType,
76797699
flow.actualFunction,
76807700
makeMap(flow.contextualTypeArguments)
76817701
);
7702+
if (!expectedType) {
7703+
this.currentType = Type.bool;
7704+
return this.module.unreachable();
7705+
}
7706+
return this.makeInstanceofType(expression, expectedType);
7707+
}
7708+
7709+
private makeInstanceofType(expression: InstanceOfExpression, expectedType: Type): ExpressionRef {
7710+
var module = this.module;
7711+
var flow = this.currentFlow;
7712+
var expr = this.compileExpression(expression.expression, expectedType);
7713+
var actualType = this.currentType;
76827714
this.currentType = Type.bool;
7683-
if (!expectedType) return module.unreachable();
76847715

76857716
// instanceof <basic> - must be exact
76867717
if (!expectedType.is(TypeFlags.REFERENCE)) {
@@ -7802,6 +7833,53 @@ export class Compiler extends DiagnosticEmitter {
78027833
], NativeType.I32);
78037834
}
78047835

7836+
private makeInstanceofClass(expression: InstanceOfExpression, prototype: ClassPrototype): ExpressionRef {
7837+
var module = this.module;
7838+
var expr = this.compileExpression(expression.expression, Type.auto);
7839+
var actualType = this.currentType;
7840+
var nativeSizeType = actualType.toNativeType();
7841+
7842+
this.currentType = Type.bool;
7843+
7844+
// exclusively interested in class references here
7845+
var classReference = actualType.classReference;
7846+
if (actualType.is(TypeFlags.REFERENCE) && classReference !== null) {
7847+
7848+
// static check
7849+
if (classReference.extends(prototype)) {
7850+
7851+
// <nullable> instanceof <PROTOTYPE> - LHS must be != 0
7852+
if (actualType.is(TypeFlags.NULLABLE)) {
7853+
return module.binary(
7854+
nativeSizeType == NativeType.I64
7855+
? BinaryOp.NeI64
7856+
: BinaryOp.NeI32,
7857+
expr,
7858+
this.makeZero(actualType)
7859+
);
7860+
7861+
// <nonNullable> is just `true`
7862+
} else {
7863+
return module.block(null, [
7864+
module.drop(expr),
7865+
module.i32(1)
7866+
], NativeType.I32);
7867+
}
7868+
7869+
// dynamic check against all possible concrete ids
7870+
} else if (prototype.extends(classReference.prototype)) {
7871+
this.pendingClassInstanceOf.add(prototype);
7872+
return module.call(prototype.internalName + "~instanceof", [ expr ], NativeType.I32);
7873+
}
7874+
}
7875+
7876+
// false
7877+
return module.block(null, [
7878+
module.drop(expr),
7879+
module.i32(0)
7880+
], NativeType.I32);
7881+
}
7882+
78057883
private compileLiteralExpression(
78067884
expression: LiteralExpression,
78077885
contextualType: Type,

tests/compiler/instanceof-class.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"asc_flags": [
3+
"--runtime none"
4+
]
5+
}

0 commit comments

Comments
 (0)