Skip to content

Commit

Permalink
fix: Properly handle semantically anonymous functions (#1666)
Browse files Browse the repository at this point in the history
  • Loading branch information
dcodeIO committed Feb 3, 2021
1 parent b24925c commit 368fda0
Show file tree
Hide file tree
Showing 6 changed files with 214 additions and 83 deletions.
68 changes: 50 additions & 18 deletions src/compiler.ts
Expand Up @@ -3429,7 +3429,7 @@ export class Compiler extends DiagnosticEmitter {
break;
}
case NodeKind.FUNCTION: {
expr = this.compileFunctionExpression(<FunctionExpression>expression, contextualType.signatureReference, constraints);
expr = this.compileFunctionExpression(<FunctionExpression>expression, contextualType, constraints);
break;
}
case NodeKind.IDENTIFIER:
Expand Down Expand Up @@ -7311,26 +7311,30 @@ export class Compiler extends DiagnosticEmitter {

private compileFunctionExpression(
expression: FunctionExpression,
contextualSignature: Signature | null,
contextualType: Type,
constraints: Constraints
): ExpressionRef {
var declaration = expression.declaration.clone(); // generic contexts can have multiple
assert(!declaration.typeParameters); // function expression cannot be generic
var flow = this.currentFlow;
var actualFunction = flow.actualFunction;
var isNamed = declaration.name.text.length > 0;
var isSemanticallyAnonymous = !isNamed || contextualType != Type.void;
var prototype = new FunctionPrototype(
declaration.name.text.length
? declaration.name.text
: "anonymous|" + (actualFunction.nextAnonymousId++).toString(),
isSemanticallyAnonymous
? (isNamed ? declaration.name.text + "|" : "anonymous|") + (actualFunction.nextAnonymousId++).toString()
: declaration.name.text,
actualFunction,
declaration,
DecoratorFlags.NONE
);
var instance: Function | null;
var contextualTypeArguments = uniqueMap(flow.contextualTypeArguments);
var module = this.module;

// compile according to context. this differs from a normal function in that omitted parameter
// and return types can be inferred and omitted arguments can be replaced with dummies.
var contextualSignature = contextualType.signatureReference;
if (contextualSignature) {
let signatureNode = prototype.functionTypeNode;
let parameterNodes = signatureNode.parameters;
Expand All @@ -7344,7 +7348,7 @@ export class Compiler extends DiagnosticEmitter {
DiagnosticCode.Expected_0_arguments_but_got_1,
expression.range, numParameters.toString(), numPresentParameters.toString()
);
return this.module.unreachable();
return module.unreachable();
}

// check non-omitted parameter types
Expand All @@ -7356,13 +7360,13 @@ export class Compiler extends DiagnosticEmitter {
actualFunction.parent,
contextualTypeArguments
);
if (!resolvedType) return this.module.unreachable();
if (!resolvedType) return module.unreachable();
if (!parameterTypes[i].isStrictlyAssignableTo(resolvedType)) {
this.error(
DiagnosticCode.Type_0_is_not_assignable_to_type_1,
parameterNode.range, parameterTypes[i].toString(), resolvedType.toString()
);
return this.module.unreachable();
return module.unreachable();
}
}
// any unused parameters are inherited but ignored
Expand All @@ -7376,7 +7380,7 @@ export class Compiler extends DiagnosticEmitter {
actualFunction.parent,
contextualTypeArguments
);
if (!resolvedType) return this.module.unreachable();
if (!resolvedType) return module.unreachable();
if (
returnType == Type.void
? resolvedType != Type.void
Expand All @@ -7386,7 +7390,7 @@ export class Compiler extends DiagnosticEmitter {
DiagnosticCode.Type_0_is_not_assignable_to_type_1,
signatureNode.returnType.range, resolvedType.toString(), returnType.toString()
);
return this.module.unreachable();
return module.unreachable();
}
}

Expand All @@ -7399,20 +7403,20 @@ export class Compiler extends DiagnosticEmitter {
DiagnosticCode._this_cannot_be_referenced_in_current_location,
thisTypeNode.range
);
return this.module.unreachable();
return module.unreachable();
}
let resolvedType = this.resolver.resolveType(
thisTypeNode,
actualFunction.parent,
contextualTypeArguments
);
if (!resolvedType) return this.module.unreachable();
if (!resolvedType) return module.unreachable();
if (!thisType.isStrictlyAssignableTo(resolvedType)) {
this.error(
DiagnosticCode.Type_0_is_not_assignable_to_type_1,
thisTypeNode.range, thisType.toString(), resolvedType.toString()
);
return this.module.unreachable();
return module.unreachable();
}
}

Expand All @@ -7428,7 +7432,7 @@ export class Compiler extends DiagnosticEmitter {
instance.flow.outer = flow;
let worked = this.compileFunction(instance);
this.currentType = contextualSignature.type;
if (!worked) return this.module.unreachable();
if (!worked) return module.unreachable();

// otherwise compile like a normal function
} else {
Expand All @@ -7437,13 +7441,41 @@ export class Compiler extends DiagnosticEmitter {
instance.flow.outer = flow;
let worked = this.compileFunction(instance);
this.currentType = instance.signature.type;
if (!worked) return this.module.unreachable();
if (!worked) return module.unreachable();
}

var offset = this.ensureRuntimeFunction(instance); // reports
return this.options.isWasm64
? this.module.i64(i64_low(offset), i64_high(offset))
: this.module.i32(i64_low(offset));
var expr = this.options.isWasm64
? module.i64(i64_low(offset), i64_high(offset))
: module.i32(i64_low(offset));

// add a constant local referring to the function if applicable
if (!isSemanticallyAnonymous) {
let fname = instance.name;
let existingLocal = flow.getScopedLocal(fname);
if (existingLocal) {
if (!existingLocal.declaration.range.source.isNative) {
this.errorRelated(
DiagnosticCode.Duplicate_identifier_0,
declaration.name.range,
existingLocal.declaration.name.range,
fname
);
} else { // scoped locals are shared temps that don't track declarations
this.error(
DiagnosticCode.Duplicate_identifier_0,
declaration.name.range, fname
);
}
} else {
let ftype = instance.type;
let local = flow.addScopedLocal(instance.name, ftype);
flow.setLocalFlag(local.index, LocalFlags.CONSTANT);
expr = module.local_tee(local.index, expr, ftype.isManaged);
}
}

return expr;
}

/** Makes sure the enclosing source file of the specified expression has been compiled. */
Expand Down
4 changes: 2 additions & 2 deletions tests/compiler/function-call.optimized.wat
Expand Up @@ -41,7 +41,7 @@
(data (i32.const 1760) "\t\00\00\00 \00\00\00\00\00\00\00 ")
(data (i32.const 1812) " ")
(table $0 9 funcref)
(elem (i32.const 1) $start:function-call~anonymous|0 $start:function-call~anonymous|0 $start:function-call~anonymous|2 $start:function-call~anonymous|2 $start:function-call~fn2 $function-call/Foo#fnVoid $start:function-call~fn2 $function-call/Foo#fnRet)
(elem (i32.const 1) $start:function-call~anonymous|0 $start:function-call~anonymous|0 $start:function-call~anonymous|2 $start:function-call~anonymous|2 $start:function-call~fn2|4 $function-call/Foo#fnVoid $start:function-call~fn2|4 $function-call/Foo#fnRet)
(global $~lib/rt/itcms/total (mut i32) (i32.const 0))
(global $~lib/rt/itcms/threshold (mut i32) (i32.const 1024))
(global $~lib/rt/itcms/state (mut i32) (i32.const 0))
Expand All @@ -64,7 +64,7 @@
local.get $1
i32.add
)
(func $start:function-call~fn2 (param $0 i32) (result i32)
(func $start:function-call~fn2|4 (param $0 i32) (result i32)
local.get $0
)
(func $~lib/rt/itcms/initLazy (param $0 i32) (result i32)
Expand Down
4 changes: 2 additions & 2 deletions tests/compiler/function-call.untouched.wat
Expand Up @@ -29,7 +29,7 @@
(data (i32.const 700) "\1c\00\00\00\00\00\00\00\00\00\00\00\08\00\00\00\08\00\00\00\08\00\00\00\00\00\00\00\00\00\00\00")
(data (i32.const 736) "\t\00\00\00 \00\00\00\00\00\00\00 \00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00 \00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00")
(table $0 9 funcref)
(elem (i32.const 1) $start:function-call~anonymous|0 $start:function-call~anonymous|1 $start:function-call~anonymous|2 $start:function-call~anonymous|3 $start:function-call~fn2 $function-call/Foo#fnVoid $function-call/Foo#fnThis $function-call/Foo#fnRet)
(elem (i32.const 1) $start:function-call~anonymous|0 $start:function-call~anonymous|1 $start:function-call~anonymous|2 $start:function-call~anonymous|3 $start:function-call~fn2|4 $function-call/Foo#fnVoid $function-call/Foo#fnThis $function-call/Foo#fnRet)
(global $function-call/fnVoid (mut i32) (i32.const 32))
(global $~argumentsLength (mut i32) (i32.const 0))
(global $function-call/faVoid (mut i32) (i32.const 64))
Expand Down Expand Up @@ -71,7 +71,7 @@
local.get $1
i32.add
)
(func $start:function-call~fn2 (param $0 i32) (result i32)
(func $start:function-call~fn2|4 (param $0 i32) (result i32)
local.get $0
)
(func $~lib/rt/itcms/Object#set:nextWithColor (param $0 i32) (param $1 i32)
Expand Down

0 comments on commit 368fda0

Please sign in to comment.