Skip to content

Commit 368fda0

Browse files
authored
fix: Properly handle semantically anonymous functions (#1666)
1 parent b24925c commit 368fda0

File tree

6 files changed

+214
-83
lines changed

6 files changed

+214
-83
lines changed

src/compiler.ts

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3429,7 +3429,7 @@ export class Compiler extends DiagnosticEmitter {
34293429
break;
34303430
}
34313431
case NodeKind.FUNCTION: {
3432-
expr = this.compileFunctionExpression(<FunctionExpression>expression, contextualType.signatureReference, constraints);
3432+
expr = this.compileFunctionExpression(<FunctionExpression>expression, contextualType, constraints);
34333433
break;
34343434
}
34353435
case NodeKind.IDENTIFIER:
@@ -7311,26 +7311,30 @@ export class Compiler extends DiagnosticEmitter {
73117311

73127312
private compileFunctionExpression(
73137313
expression: FunctionExpression,
7314-
contextualSignature: Signature | null,
7314+
contextualType: Type,
73157315
constraints: Constraints
73167316
): ExpressionRef {
73177317
var declaration = expression.declaration.clone(); // generic contexts can have multiple
73187318
assert(!declaration.typeParameters); // function expression cannot be generic
73197319
var flow = this.currentFlow;
73207320
var actualFunction = flow.actualFunction;
7321+
var isNamed = declaration.name.text.length > 0;
7322+
var isSemanticallyAnonymous = !isNamed || contextualType != Type.void;
73217323
var prototype = new FunctionPrototype(
7322-
declaration.name.text.length
7323-
? declaration.name.text
7324-
: "anonymous|" + (actualFunction.nextAnonymousId++).toString(),
7324+
isSemanticallyAnonymous
7325+
? (isNamed ? declaration.name.text + "|" : "anonymous|") + (actualFunction.nextAnonymousId++).toString()
7326+
: declaration.name.text,
73257327
actualFunction,
73267328
declaration,
73277329
DecoratorFlags.NONE
73287330
);
73297331
var instance: Function | null;
73307332
var contextualTypeArguments = uniqueMap(flow.contextualTypeArguments);
7333+
var module = this.module;
73317334

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

73507354
// check non-omitted parameter types
@@ -7356,13 +7360,13 @@ export class Compiler extends DiagnosticEmitter {
73567360
actualFunction.parent,
73577361
contextualTypeArguments
73587362
);
7359-
if (!resolvedType) return this.module.unreachable();
7363+
if (!resolvedType) return module.unreachable();
73607364
if (!parameterTypes[i].isStrictlyAssignableTo(resolvedType)) {
73617365
this.error(
73627366
DiagnosticCode.Type_0_is_not_assignable_to_type_1,
73637367
parameterNode.range, parameterTypes[i].toString(), resolvedType.toString()
73647368
);
7365-
return this.module.unreachable();
7369+
return module.unreachable();
73667370
}
73677371
}
73687372
// any unused parameters are inherited but ignored
@@ -7376,7 +7380,7 @@ export class Compiler extends DiagnosticEmitter {
73767380
actualFunction.parent,
73777381
contextualTypeArguments
73787382
);
7379-
if (!resolvedType) return this.module.unreachable();
7383+
if (!resolvedType) return module.unreachable();
73807384
if (
73817385
returnType == Type.void
73827386
? resolvedType != Type.void
@@ -7386,7 +7390,7 @@ export class Compiler extends DiagnosticEmitter {
73867390
DiagnosticCode.Type_0_is_not_assignable_to_type_1,
73877391
signatureNode.returnType.range, resolvedType.toString(), returnType.toString()
73887392
);
7389-
return this.module.unreachable();
7393+
return module.unreachable();
73907394
}
73917395
}
73927396

@@ -7399,20 +7403,20 @@ export class Compiler extends DiagnosticEmitter {
73997403
DiagnosticCode._this_cannot_be_referenced_in_current_location,
74007404
thisTypeNode.range
74017405
);
7402-
return this.module.unreachable();
7406+
return module.unreachable();
74037407
}
74047408
let resolvedType = this.resolver.resolveType(
74057409
thisTypeNode,
74067410
actualFunction.parent,
74077411
contextualTypeArguments
74087412
);
7409-
if (!resolvedType) return this.module.unreachable();
7413+
if (!resolvedType) return module.unreachable();
74107414
if (!thisType.isStrictlyAssignableTo(resolvedType)) {
74117415
this.error(
74127416
DiagnosticCode.Type_0_is_not_assignable_to_type_1,
74137417
thisTypeNode.range, thisType.toString(), resolvedType.toString()
74147418
);
7415-
return this.module.unreachable();
7419+
return module.unreachable();
74167420
}
74177421
}
74187422

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

74337437
// otherwise compile like a normal function
74347438
} else {
@@ -7437,13 +7441,41 @@ export class Compiler extends DiagnosticEmitter {
74377441
instance.flow.outer = flow;
74387442
let worked = this.compileFunction(instance);
74397443
this.currentType = instance.signature.type;
7440-
if (!worked) return this.module.unreachable();
7444+
if (!worked) return module.unreachable();
74417445
}
74427446

74437447
var offset = this.ensureRuntimeFunction(instance); // reports
7444-
return this.options.isWasm64
7445-
? this.module.i64(i64_low(offset), i64_high(offset))
7446-
: this.module.i32(i64_low(offset));
7448+
var expr = this.options.isWasm64
7449+
? module.i64(i64_low(offset), i64_high(offset))
7450+
: module.i32(i64_low(offset));
7451+
7452+
// add a constant local referring to the function if applicable
7453+
if (!isSemanticallyAnonymous) {
7454+
let fname = instance.name;
7455+
let existingLocal = flow.getScopedLocal(fname);
7456+
if (existingLocal) {
7457+
if (!existingLocal.declaration.range.source.isNative) {
7458+
this.errorRelated(
7459+
DiagnosticCode.Duplicate_identifier_0,
7460+
declaration.name.range,
7461+
existingLocal.declaration.name.range,
7462+
fname
7463+
);
7464+
} else { // scoped locals are shared temps that don't track declarations
7465+
this.error(
7466+
DiagnosticCode.Duplicate_identifier_0,
7467+
declaration.name.range, fname
7468+
);
7469+
}
7470+
} else {
7471+
let ftype = instance.type;
7472+
let local = flow.addScopedLocal(instance.name, ftype);
7473+
flow.setLocalFlag(local.index, LocalFlags.CONSTANT);
7474+
expr = module.local_tee(local.index, expr, ftype.isManaged);
7475+
}
7476+
}
7477+
7478+
return expr;
74477479
}
74487480

74497481
/** Makes sure the enclosing source file of the specified expression has been compiled. */

tests/compiler/function-call.optimized.wat

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
(data (i32.const 1760) "\t\00\00\00 \00\00\00\00\00\00\00 ")
4242
(data (i32.const 1812) " ")
4343
(table $0 9 funcref)
44-
(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)
44+
(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)
4545
(global $~lib/rt/itcms/total (mut i32) (i32.const 0))
4646
(global $~lib/rt/itcms/threshold (mut i32) (i32.const 1024))
4747
(global $~lib/rt/itcms/state (mut i32) (i32.const 0))
@@ -64,7 +64,7 @@
6464
local.get $1
6565
i32.add
6666
)
67-
(func $start:function-call~fn2 (param $0 i32) (result i32)
67+
(func $start:function-call~fn2|4 (param $0 i32) (result i32)
6868
local.get $0
6969
)
7070
(func $~lib/rt/itcms/initLazy (param $0 i32) (result i32)

tests/compiler/function-call.untouched.wat

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
(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")
3030
(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")
3131
(table $0 9 funcref)
32-
(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)
32+
(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)
3333
(global $function-call/fnVoid (mut i32) (i32.const 32))
3434
(global $~argumentsLength (mut i32) (i32.const 0))
3535
(global $function-call/faVoid (mut i32) (i32.const 64))
@@ -71,7 +71,7 @@
7171
local.get $1
7272
i32.add
7373
)
74-
(func $start:function-call~fn2 (param $0 i32) (result i32)
74+
(func $start:function-call~fn2|4 (param $0 i32) (result i32)
7575
local.get $0
7676
)
7777
(func $~lib/rt/itcms/Object#set:nextWithColor (param $0 i32) (param $1 i32)

0 commit comments

Comments
 (0)