Skip to content

Commit ee73a4d

Browse files
committed
Initial type parameter inference, see #61
This catches the most common cases but doesn't yet implement inference involving the return type because some prequesites are not yet in place (see test case).
1 parent 748e811 commit ee73a4d

File tree

11 files changed

+385
-43
lines changed

11 files changed

+385
-43
lines changed

dist/asc.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/asc.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/assemblyscript.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/assemblyscript.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/compiler.ts

Lines changed: 138 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1811,7 +1811,6 @@ export class Compiler extends DiagnosticEmitter {
18111811
}
18121812
}
18131813
if (!isInlined) {
1814-
let flow = currentFunction.flow;
18151814
if (
18161815
declaration.isAny(CommonFlags.LET | CommonFlags.CONST) ||
18171816
flow.is(FlowFlags.INLINE_CONTEXT)
@@ -4404,55 +4403,131 @@ export class Compiler extends DiagnosticEmitter {
44044403
// direct call: concrete function
44054404
case ElementKind.FUNCTION_PROTOTYPE: {
44064405
let prototype = <FunctionPrototype>target;
4406+
let typeArguments = expression.typeArguments;
44074407

4408-
// builtins are compiled on the fly
4408+
// builtins handle present respectively omitted type arguments on their own
44094409
if (prototype.is(CommonFlags.AMBIENT | CommonFlags.BUILTIN)) {
4410-
let expr = compileBuiltinCall( // reports
4411-
this,
4412-
prototype,
4413-
prototype.resolveBuiltinTypeArguments(
4414-
expression.typeArguments,
4415-
currentFunction.flow.contextualTypeArguments
4416-
),
4417-
expression.arguments,
4418-
contextualType,
4419-
expression
4420-
);
4421-
if (!expr) {
4410+
return this.compileCallExpressionBuiltin(prototype, expression, contextualType);
4411+
}
4412+
4413+
let instance: Function | null = null;
4414+
4415+
// resolve generic call if type arguments have been provided
4416+
if (typeArguments) {
4417+
if (!prototype.is(CommonFlags.GENERIC)) {
44224418
this.error(
4423-
DiagnosticCode.Operation_not_supported,
4424-
expression.range
4419+
DiagnosticCode.Type_0_is_not_generic,
4420+
expression.expression.range, prototype.internalName
44254421
);
44264422
return module.createUnreachable();
44274423
}
4428-
return expr;
4429-
4430-
// otherwise compile to a call (and maybe inline)
4431-
} else {
4432-
let instance = prototype.resolveUsingTypeArguments( // reports
4433-
expression.typeArguments,
4434-
currentFunction.flow.contextualTypeArguments,
4424+
instance = prototype.resolveUsingTypeArguments( // reports
4425+
typeArguments,
4426+
this.currentFunction.flow.contextualTypeArguments,
44354427
expression
44364428
);
4437-
if (!instance) return module.createUnreachable();
4438-
let thisExpr: ExpressionRef = 0;
4439-
if (instance.is(CommonFlags.INSTANCE)) {
4440-
thisExpr = this.compileExpressionRetainType(
4441-
assert(this.program.resolvedThisExpression),
4442-
this.options.usizeType
4443-
);
4429+
4430+
// infer generic call if type arguments have been omitted
4431+
} else if (prototype.is(CommonFlags.GENERIC)) {
4432+
let inferredTypes = new Map<string,Type | null>();
4433+
let typeParameters = assert(prototype.declaration.typeParameters);
4434+
let numTypeParameters = typeParameters.length;
4435+
for (let i = 0; i < numTypeParameters; ++i) {
4436+
inferredTypes.set(typeParameters[i].name.text, null);
4437+
}
4438+
// let numInferred = 0;
4439+
let parameterTypes = prototype.declaration.signature.parameterTypes;
4440+
let numParameterTypes = parameterTypes.length;
4441+
let argumentExpressions = expression.arguments;
4442+
let numArguments = argumentExpressions.length;
4443+
let argumentExprs = new Array<ExpressionRef>(numArguments);
4444+
for (let i = 0; i < numParameterTypes; ++i) {
4445+
let typeNode = parameterTypes[i].type;
4446+
let name = typeNode.kind == NodeKind.TYPE ? (<TypeNode>typeNode).name.text : null;
4447+
let argumentExpression = i < numArguments
4448+
? argumentExpressions[i]
4449+
: prototype.declaration.signature.parameterTypes[i].initializer;
4450+
if (!argumentExpression) { // missing initializer -> too few arguments
4451+
this.error(
4452+
DiagnosticCode.Expected_0_arguments_but_got_1,
4453+
expression.range, numParameterTypes.toString(10), numArguments.toString(10)
4454+
);
4455+
return module.createUnreachable();
4456+
}
4457+
if (name !== null && inferredTypes.has(name)) {
4458+
let inferredType = inferredTypes.get(name);
4459+
if (inferredType) {
4460+
argumentExprs[i] = this.compileExpressionRetainType(argumentExpression, inferredType);
4461+
let commonType: Type | null;
4462+
if (!(commonType = Type.commonCompatible(inferredType, this.currentType, true))) {
4463+
if (!(commonType = Type.commonCompatible(inferredType, this.currentType, false))) {
4464+
this.error(
4465+
DiagnosticCode.Type_0_is_not_assignable_to_type_1,
4466+
parameterTypes[i].type.range, this.currentType.toString(), inferredType.toString()
4467+
);
4468+
return module.createUnreachable();
4469+
}
4470+
}
4471+
inferredType = commonType;
4472+
} else {
4473+
argumentExprs[i] = this.compileExpressionRetainType(argumentExpression, Type.i32);
4474+
inferredType = this.currentType;
4475+
// ++numInferred;
4476+
}
4477+
inferredTypes.set(name, inferredType);
4478+
} else {
4479+
let concreteType = this.program.resolveType(
4480+
parameterTypes[i].type,
4481+
this.currentFunction.flow.contextualTypeArguments,
4482+
true
4483+
);
4484+
if (!concreteType) return module.createUnreachable();
4485+
argumentExprs[i] = this.compileExpression(argumentExpression, concreteType);
4486+
}
44444487
}
4445-
return this.compileCallDirect(
4446-
instance,
4447-
expression.arguments,
4448-
expression,
4449-
thisExpr,
4450-
instance.hasDecorator(DecoratorFlags.INLINE)
4488+
let resolvedTypeArguments = new Array<Type>(numTypeParameters);
4489+
for (let i = 0; i < numTypeParameters; ++i) {
4490+
let inferredType = assert(inferredTypes.get(typeParameters[i].name.text)); // TODO
4491+
resolvedTypeArguments[i] = inferredType;
4492+
}
4493+
instance = prototype.resolve(
4494+
resolvedTypeArguments,
4495+
this.currentFunction.flow.contextualTypeArguments
4496+
);
4497+
if (!instance) return this.module.createUnreachable();
4498+
return this.makeCallDirect(instance, argumentExprs);
4499+
// TODO: this skips inlining because inlining requires compiling its temporary locals in
4500+
// the scope of the inlined flow. might need another mechanism to lock temp. locals early,
4501+
// so inlining can be performed in `makeCallDirect` instead?
4502+
4503+
// otherwise resolve the non-generic call as usual
4504+
} else {
4505+
instance = prototype.resolve(
4506+
null,
4507+
this.currentFunction.flow.contextualTypeArguments
4508+
);
4509+
}
4510+
if (!instance) return this.module.createUnreachable();
4511+
4512+
// compile 'this' expression if an instance method
4513+
let thisExpr: ExpressionRef = 0;
4514+
if (instance.is(CommonFlags.INSTANCE)) {
4515+
thisExpr = this.compileExpressionRetainType(
4516+
assert(this.program.resolvedThisExpression),
4517+
this.options.usizeType
44514518
);
44524519
}
4520+
4521+
return this.compileCallDirect(
4522+
instance,
4523+
expression.arguments,
4524+
expression,
4525+
thisExpr,
4526+
instance.hasDecorator(DecoratorFlags.INLINE)
4527+
);
44534528
}
44544529

4455-
// indirect call: index argument with signature
4530+
// indirect call: index argument with signature (non-generic, can't be inlined)
44564531
case ElementKind.LOCAL: {
44574532
if (signature = (<Local>target).type.signatureReference) {
44584533
indexArg = module.createGetLocal((<Local>target).index, NativeType.I32);
@@ -4525,6 +4600,32 @@ export class Compiler extends DiagnosticEmitter {
45254600
);
45264601
}
45274602

4603+
private compileCallExpressionBuiltin(
4604+
prototype: FunctionPrototype,
4605+
expression: CallExpression,
4606+
contextualType: Type
4607+
): ExpressionRef {
4608+
var expr = compileBuiltinCall( // reports
4609+
this,
4610+
prototype,
4611+
prototype.resolveBuiltinTypeArguments(
4612+
expression.typeArguments,
4613+
this.currentFunction.flow.contextualTypeArguments
4614+
),
4615+
expression.arguments,
4616+
contextualType,
4617+
expression
4618+
);
4619+
if (!expr) {
4620+
this.error(
4621+
DiagnosticCode.Operation_not_supported,
4622+
expression.range
4623+
);
4624+
return this.module.createUnreachable();
4625+
}
4626+
return expr;
4627+
}
4628+
45284629
/**
45294630
* Checks that a call with the given number as arguments can be performed according to the
45304631
* specified signature.

src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,7 @@ export function typesToString(types: Type[]): string {
470470
for (let i = 0; i < numTypes; ++i) {
471471
sb[i] = types[i].toString();
472472
}
473-
return sb.join(", ");
473+
return sb.join(",");
474474
}
475475

476476
/** Represents a fully resolved function signature. */

std/assembly/builtins.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,22 @@ export declare const NaN: f64; // | f32
1313
export declare const Infinity: f64; // | f32
1414

1515
export declare function isNaN<T>(value: T): bool;
16+
// export function isNaN<T>(value: T): bool {
17+
// return isFloat(value)
18+
// ? sizeof<T>() == 32
19+
// ? (reinterpret<u32>(value) & -1 >>> 1) > 0xFF << 23
20+
// : (reinterpret<u64>(value) & -1 >>> 1) > 0x7FF << 52
21+
// : false;
22+
// }
1623

1724
export declare function isFinite<T>(value: T): bool;
25+
// export function isFinite<T>(value: T): bool {
26+
// return isFloat(value)
27+
// ? sizeof<T>() == 32
28+
// ? (reinterpret<u32>(value) & -1 >>> 1) < 0xFF << 23
29+
// : (reinterpret<u64>(value) & -1 >>> 1) < 0x7FF << 52
30+
// : true;
31+
// }
1832

1933
export declare function clz<T>(value: T): T;
2034

tests/compiler/builtins.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,4 +311,4 @@ assert(f64.EPSILON == 2.2204460492503131e-16);
311311

312312
// should be importable
313313
import { isNaN as isItNaN } from "builtins";
314-
isItNaN(1);
314+
isItNaN<f64>(1);
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
(module
2+
(type $ii (func (param i32) (result i32)))
3+
(type $iiiiv (func (param i32 i32 i32 i32)))
4+
(type $FF (func (param f64) (result f64)))
5+
(type $ff (func (param f32) (result f32)))
6+
(type $v (func))
7+
(import "env" "abort" (func $abort (param i32 i32 i32 i32)))
8+
(memory $0 1)
9+
(data (i32.const 4) "\10\00\00\00c\00a\00l\00l\00-\00i\00n\00f\00e\00r\00r\00e\00d\00.\00t\00s")
10+
(export "memory" (memory $0))
11+
(start $start)
12+
(func $call-inferred/foo<i32> (; 1 ;) (type $ii) (param $0 i32) (result i32)
13+
(get_local $0)
14+
)
15+
(func $call-inferred/foo<f64> (; 2 ;) (type $FF) (param $0 f64) (result f64)
16+
(get_local $0)
17+
)
18+
(func $call-inferred/foo<f32> (; 3 ;) (type $ff) (param $0 f32) (result f32)
19+
(get_local $0)
20+
)
21+
(func $start (; 4 ;) (type $v)
22+
(if
23+
(i32.ne
24+
(call $call-inferred/foo<i32>
25+
(i32.const 42)
26+
)
27+
(i32.const 42)
28+
)
29+
(block
30+
(call $abort
31+
(i32.const 0)
32+
(i32.const 4)
33+
(i32.const 5)
34+
(i32.const 0)
35+
)
36+
(unreachable)
37+
)
38+
)
39+
(if
40+
(f64.ne
41+
(call $call-inferred/foo<f64>
42+
(f64.const 42)
43+
)
44+
(f64.const 42)
45+
)
46+
(block
47+
(call $abort
48+
(i32.const 0)
49+
(i32.const 4)
50+
(i32.const 6)
51+
(i32.const 0)
52+
)
53+
(unreachable)
54+
)
55+
)
56+
(if
57+
(f32.ne
58+
(call $call-inferred/foo<f32>
59+
(f32.const 42)
60+
)
61+
(f32.const 42)
62+
)
63+
(block
64+
(call $abort
65+
(i32.const 0)
66+
(i32.const 4)
67+
(i32.const 7)
68+
(i32.const 0)
69+
)
70+
(unreachable)
71+
)
72+
)
73+
(if
74+
(f32.ne
75+
(call $call-inferred/foo<f32>
76+
(f32.const 42)
77+
)
78+
(f32.const 42)
79+
)
80+
(block
81+
(call $abort
82+
(i32.const 0)
83+
(i32.const 4)
84+
(i32.const 13)
85+
(i32.const 0)
86+
)
87+
(unreachable)
88+
)
89+
)
90+
)
91+
)

tests/compiler/call-inferred.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
function foo<T>(a: T): T {
2+
return a;
3+
}
4+
5+
assert(foo(42) == 42);
6+
assert(foo(42.0) == 42);
7+
assert(foo(<f32>42.0) == 42);
8+
9+
function bar<T>(a: T = <f32>42.0): T {
10+
return a;
11+
}
12+
13+
assert(bar() == 42);
14+
15+
// TODO: this'd require return type inference, i.e., omitted return type
16+
// function baz<T>(a: i32): T {
17+
// return a;
18+
// }
19+
// baz(42);
20+
21+
// TODO: this'd ideally be inferred by matching contextualType, avoiding conversions
22+
// function baz<T>(): T {
23+
// return 1;
24+
// }
25+
// baz(42);

0 commit comments

Comments
 (0)