mirror of
https://github.com/fluencelabs/assemblyscript
synced 2025-04-25 07:02:13 +00:00
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).
This commit is contained in:
parent
748e811137
commit
ee73a4d28f
2
dist/asc.js
vendored
2
dist/asc.js
vendored
File diff suppressed because one or more lines are too long
2
dist/asc.js.map
vendored
2
dist/asc.js.map
vendored
File diff suppressed because one or more lines are too long
2
dist/assemblyscript.js
vendored
2
dist/assemblyscript.js
vendored
File diff suppressed because one or more lines are too long
2
dist/assemblyscript.js.map
vendored
2
dist/assemblyscript.js.map
vendored
File diff suppressed because one or more lines are too long
175
src/compiler.ts
175
src/compiler.ts
@ -1811,7 +1811,6 @@ export class Compiler extends DiagnosticEmitter {
|
||||
}
|
||||
}
|
||||
if (!isInlined) {
|
||||
let flow = currentFunction.flow;
|
||||
if (
|
||||
declaration.isAny(CommonFlags.LET | CommonFlags.CONST) ||
|
||||
flow.is(FlowFlags.INLINE_CONTEXT)
|
||||
@ -4404,55 +4403,131 @@ export class Compiler extends DiagnosticEmitter {
|
||||
// direct call: concrete function
|
||||
case ElementKind.FUNCTION_PROTOTYPE: {
|
||||
let prototype = <FunctionPrototype>target;
|
||||
let typeArguments = expression.typeArguments;
|
||||
|
||||
// builtins are compiled on the fly
|
||||
// builtins handle present respectively omitted type arguments on their own
|
||||
if (prototype.is(CommonFlags.AMBIENT | CommonFlags.BUILTIN)) {
|
||||
let expr = compileBuiltinCall( // reports
|
||||
this,
|
||||
prototype,
|
||||
prototype.resolveBuiltinTypeArguments(
|
||||
expression.typeArguments,
|
||||
currentFunction.flow.contextualTypeArguments
|
||||
),
|
||||
expression.arguments,
|
||||
contextualType,
|
||||
expression
|
||||
);
|
||||
if (!expr) {
|
||||
return this.compileCallExpressionBuiltin(prototype, expression, contextualType);
|
||||
}
|
||||
|
||||
let instance: Function | null = null;
|
||||
|
||||
// resolve generic call if type arguments have been provided
|
||||
if (typeArguments) {
|
||||
if (!prototype.is(CommonFlags.GENERIC)) {
|
||||
this.error(
|
||||
DiagnosticCode.Operation_not_supported,
|
||||
expression.range
|
||||
DiagnosticCode.Type_0_is_not_generic,
|
||||
expression.expression.range, prototype.internalName
|
||||
);
|
||||
return module.createUnreachable();
|
||||
}
|
||||
return expr;
|
||||
|
||||
// otherwise compile to a call (and maybe inline)
|
||||
} else {
|
||||
let instance = prototype.resolveUsingTypeArguments( // reports
|
||||
expression.typeArguments,
|
||||
currentFunction.flow.contextualTypeArguments,
|
||||
instance = prototype.resolveUsingTypeArguments( // reports
|
||||
typeArguments,
|
||||
this.currentFunction.flow.contextualTypeArguments,
|
||||
expression
|
||||
);
|
||||
if (!instance) return module.createUnreachable();
|
||||
let thisExpr: ExpressionRef = 0;
|
||||
if (instance.is(CommonFlags.INSTANCE)) {
|
||||
thisExpr = this.compileExpressionRetainType(
|
||||
assert(this.program.resolvedThisExpression),
|
||||
this.options.usizeType
|
||||
);
|
||||
|
||||
// infer generic call if type arguments have been omitted
|
||||
} else if (prototype.is(CommonFlags.GENERIC)) {
|
||||
let inferredTypes = new Map<string,Type | null>();
|
||||
let typeParameters = assert(prototype.declaration.typeParameters);
|
||||
let numTypeParameters = typeParameters.length;
|
||||
for (let i = 0; i < numTypeParameters; ++i) {
|
||||
inferredTypes.set(typeParameters[i].name.text, null);
|
||||
}
|
||||
return this.compileCallDirect(
|
||||
instance,
|
||||
expression.arguments,
|
||||
expression,
|
||||
thisExpr,
|
||||
instance.hasDecorator(DecoratorFlags.INLINE)
|
||||
// let numInferred = 0;
|
||||
let parameterTypes = prototype.declaration.signature.parameterTypes;
|
||||
let numParameterTypes = parameterTypes.length;
|
||||
let argumentExpressions = expression.arguments;
|
||||
let numArguments = argumentExpressions.length;
|
||||
let argumentExprs = new Array<ExpressionRef>(numArguments);
|
||||
for (let i = 0; i < numParameterTypes; ++i) {
|
||||
let typeNode = parameterTypes[i].type;
|
||||
let name = typeNode.kind == NodeKind.TYPE ? (<TypeNode>typeNode).name.text : null;
|
||||
let argumentExpression = i < numArguments
|
||||
? argumentExpressions[i]
|
||||
: prototype.declaration.signature.parameterTypes[i].initializer;
|
||||
if (!argumentExpression) { // missing initializer -> too few arguments
|
||||
this.error(
|
||||
DiagnosticCode.Expected_0_arguments_but_got_1,
|
||||
expression.range, numParameterTypes.toString(10), numArguments.toString(10)
|
||||
);
|
||||
return module.createUnreachable();
|
||||
}
|
||||
if (name !== null && inferredTypes.has(name)) {
|
||||
let inferredType = inferredTypes.get(name);
|
||||
if (inferredType) {
|
||||
argumentExprs[i] = this.compileExpressionRetainType(argumentExpression, inferredType);
|
||||
let commonType: Type | null;
|
||||
if (!(commonType = Type.commonCompatible(inferredType, this.currentType, true))) {
|
||||
if (!(commonType = Type.commonCompatible(inferredType, this.currentType, false))) {
|
||||
this.error(
|
||||
DiagnosticCode.Type_0_is_not_assignable_to_type_1,
|
||||
parameterTypes[i].type.range, this.currentType.toString(), inferredType.toString()
|
||||
);
|
||||
return module.createUnreachable();
|
||||
}
|
||||
}
|
||||
inferredType = commonType;
|
||||
} else {
|
||||
argumentExprs[i] = this.compileExpressionRetainType(argumentExpression, Type.i32);
|
||||
inferredType = this.currentType;
|
||||
// ++numInferred;
|
||||
}
|
||||
inferredTypes.set(name, inferredType);
|
||||
} else {
|
||||
let concreteType = this.program.resolveType(
|
||||
parameterTypes[i].type,
|
||||
this.currentFunction.flow.contextualTypeArguments,
|
||||
true
|
||||
);
|
||||
if (!concreteType) return module.createUnreachable();
|
||||
argumentExprs[i] = this.compileExpression(argumentExpression, concreteType);
|
||||
}
|
||||
}
|
||||
let resolvedTypeArguments = new Array<Type>(numTypeParameters);
|
||||
for (let i = 0; i < numTypeParameters; ++i) {
|
||||
let inferredType = assert(inferredTypes.get(typeParameters[i].name.text)); // TODO
|
||||
resolvedTypeArguments[i] = inferredType;
|
||||
}
|
||||
instance = prototype.resolve(
|
||||
resolvedTypeArguments,
|
||||
this.currentFunction.flow.contextualTypeArguments
|
||||
);
|
||||
if (!instance) return this.module.createUnreachable();
|
||||
return this.makeCallDirect(instance, argumentExprs);
|
||||
// TODO: this skips inlining because inlining requires compiling its temporary locals in
|
||||
// the scope of the inlined flow. might need another mechanism to lock temp. locals early,
|
||||
// so inlining can be performed in `makeCallDirect` instead?
|
||||
|
||||
// otherwise resolve the non-generic call as usual
|
||||
} else {
|
||||
instance = prototype.resolve(
|
||||
null,
|
||||
this.currentFunction.flow.contextualTypeArguments
|
||||
);
|
||||
}
|
||||
if (!instance) return this.module.createUnreachable();
|
||||
|
||||
// compile 'this' expression if an instance method
|
||||
let thisExpr: ExpressionRef = 0;
|
||||
if (instance.is(CommonFlags.INSTANCE)) {
|
||||
thisExpr = this.compileExpressionRetainType(
|
||||
assert(this.program.resolvedThisExpression),
|
||||
this.options.usizeType
|
||||
);
|
||||
}
|
||||
|
||||
return this.compileCallDirect(
|
||||
instance,
|
||||
expression.arguments,
|
||||
expression,
|
||||
thisExpr,
|
||||
instance.hasDecorator(DecoratorFlags.INLINE)
|
||||
);
|
||||
}
|
||||
|
||||
// indirect call: index argument with signature
|
||||
// indirect call: index argument with signature (non-generic, can't be inlined)
|
||||
case ElementKind.LOCAL: {
|
||||
if (signature = (<Local>target).type.signatureReference) {
|
||||
indexArg = module.createGetLocal((<Local>target).index, NativeType.I32);
|
||||
@ -4525,6 +4600,32 @@ export class Compiler extends DiagnosticEmitter {
|
||||
);
|
||||
}
|
||||
|
||||
private compileCallExpressionBuiltin(
|
||||
prototype: FunctionPrototype,
|
||||
expression: CallExpression,
|
||||
contextualType: Type
|
||||
): ExpressionRef {
|
||||
var expr = compileBuiltinCall( // reports
|
||||
this,
|
||||
prototype,
|
||||
prototype.resolveBuiltinTypeArguments(
|
||||
expression.typeArguments,
|
||||
this.currentFunction.flow.contextualTypeArguments
|
||||
),
|
||||
expression.arguments,
|
||||
contextualType,
|
||||
expression
|
||||
);
|
||||
if (!expr) {
|
||||
this.error(
|
||||
DiagnosticCode.Operation_not_supported,
|
||||
expression.range
|
||||
);
|
||||
return this.module.createUnreachable();
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that a call with the given number as arguments can be performed according to the
|
||||
* specified signature.
|
||||
|
@ -470,7 +470,7 @@ export function typesToString(types: Type[]): string {
|
||||
for (let i = 0; i < numTypes; ++i) {
|
||||
sb[i] = types[i].toString();
|
||||
}
|
||||
return sb.join(", ");
|
||||
return sb.join(",");
|
||||
}
|
||||
|
||||
/** Represents a fully resolved function signature. */
|
||||
|
@ -13,8 +13,22 @@ export declare const NaN: f64; // | f32
|
||||
export declare const Infinity: f64; // | f32
|
||||
|
||||
export declare function isNaN<T>(value: T): bool;
|
||||
// export function isNaN<T>(value: T): bool {
|
||||
// return isFloat(value)
|
||||
// ? sizeof<T>() == 32
|
||||
// ? (reinterpret<u32>(value) & -1 >>> 1) > 0xFF << 23
|
||||
// : (reinterpret<u64>(value) & -1 >>> 1) > 0x7FF << 52
|
||||
// : false;
|
||||
// }
|
||||
|
||||
export declare function isFinite<T>(value: T): bool;
|
||||
// export function isFinite<T>(value: T): bool {
|
||||
// return isFloat(value)
|
||||
// ? sizeof<T>() == 32
|
||||
// ? (reinterpret<u32>(value) & -1 >>> 1) < 0xFF << 23
|
||||
// : (reinterpret<u64>(value) & -1 >>> 1) < 0x7FF << 52
|
||||
// : true;
|
||||
// }
|
||||
|
||||
export declare function clz<T>(value: T): T;
|
||||
|
||||
|
@ -311,4 +311,4 @@ assert(f64.EPSILON == 2.2204460492503131e-16);
|
||||
|
||||
// should be importable
|
||||
import { isNaN as isItNaN } from "builtins";
|
||||
isItNaN(1);
|
||||
isItNaN<f64>(1);
|
||||
|
91
tests/compiler/call-inferred.optimized.wat
Normal file
91
tests/compiler/call-inferred.optimized.wat
Normal file
@ -0,0 +1,91 @@
|
||||
(module
|
||||
(type $ii (func (param i32) (result i32)))
|
||||
(type $iiiiv (func (param i32 i32 i32 i32)))
|
||||
(type $FF (func (param f64) (result f64)))
|
||||
(type $ff (func (param f32) (result f32)))
|
||||
(type $v (func))
|
||||
(import "env" "abort" (func $abort (param i32 i32 i32 i32)))
|
||||
(memory $0 1)
|
||||
(data (i32.const 4) "\10\00\00\00c\00a\00l\00l\00-\00i\00n\00f\00e\00r\00r\00e\00d\00.\00t\00s")
|
||||
(export "memory" (memory $0))
|
||||
(start $start)
|
||||
(func $call-inferred/foo<i32> (; 1 ;) (type $ii) (param $0 i32) (result i32)
|
||||
(get_local $0)
|
||||
)
|
||||
(func $call-inferred/foo<f64> (; 2 ;) (type $FF) (param $0 f64) (result f64)
|
||||
(get_local $0)
|
||||
)
|
||||
(func $call-inferred/foo<f32> (; 3 ;) (type $ff) (param $0 f32) (result f32)
|
||||
(get_local $0)
|
||||
)
|
||||
(func $start (; 4 ;) (type $v)
|
||||
(if
|
||||
(i32.ne
|
||||
(call $call-inferred/foo<i32>
|
||||
(i32.const 42)
|
||||
)
|
||||
(i32.const 42)
|
||||
)
|
||||
(block
|
||||
(call $abort
|
||||
(i32.const 0)
|
||||
(i32.const 4)
|
||||
(i32.const 5)
|
||||
(i32.const 0)
|
||||
)
|
||||
(unreachable)
|
||||
)
|
||||
)
|
||||
(if
|
||||
(f64.ne
|
||||
(call $call-inferred/foo<f64>
|
||||
(f64.const 42)
|
||||
)
|
||||
(f64.const 42)
|
||||
)
|
||||
(block
|
||||
(call $abort
|
||||
(i32.const 0)
|
||||
(i32.const 4)
|
||||
(i32.const 6)
|
||||
(i32.const 0)
|
||||
)
|
||||
(unreachable)
|
||||
)
|
||||
)
|
||||
(if
|
||||
(f32.ne
|
||||
(call $call-inferred/foo<f32>
|
||||
(f32.const 42)
|
||||
)
|
||||
(f32.const 42)
|
||||
)
|
||||
(block
|
||||
(call $abort
|
||||
(i32.const 0)
|
||||
(i32.const 4)
|
||||
(i32.const 7)
|
||||
(i32.const 0)
|
||||
)
|
||||
(unreachable)
|
||||
)
|
||||
)
|
||||
(if
|
||||
(f32.ne
|
||||
(call $call-inferred/foo<f32>
|
||||
(f32.const 42)
|
||||
)
|
||||
(f32.const 42)
|
||||
)
|
||||
(block
|
||||
(call $abort
|
||||
(i32.const 0)
|
||||
(i32.const 4)
|
||||
(i32.const 13)
|
||||
(i32.const 0)
|
||||
)
|
||||
(unreachable)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
25
tests/compiler/call-inferred.ts
Normal file
25
tests/compiler/call-inferred.ts
Normal file
@ -0,0 +1,25 @@
|
||||
function foo<T>(a: T): T {
|
||||
return a;
|
||||
}
|
||||
|
||||
assert(foo(42) == 42);
|
||||
assert(foo(42.0) == 42);
|
||||
assert(foo(<f32>42.0) == 42);
|
||||
|
||||
function bar<T>(a: T = <f32>42.0): T {
|
||||
return a;
|
||||
}
|
||||
|
||||
assert(bar() == 42);
|
||||
|
||||
// TODO: this'd require return type inference, i.e., omitted return type
|
||||
// function baz<T>(a: i32): T {
|
||||
// return a;
|
||||
// }
|
||||
// baz(42);
|
||||
|
||||
// TODO: this'd ideally be inferred by matching contextualType, avoiding conversions
|
||||
// function baz<T>(): T {
|
||||
// return 1;
|
||||
// }
|
||||
// baz(42);
|
111
tests/compiler/call-inferred.untouched.wat
Normal file
111
tests/compiler/call-inferred.untouched.wat
Normal file
@ -0,0 +1,111 @@
|
||||
(module
|
||||
(type $ii (func (param i32) (result i32)))
|
||||
(type $iiiiv (func (param i32 i32 i32 i32)))
|
||||
(type $FF (func (param f64) (result f64)))
|
||||
(type $ff (func (param f32) (result f32)))
|
||||
(type $v (func))
|
||||
(import "env" "abort" (func $abort (param i32 i32 i32 i32)))
|
||||
(global $HEAP_BASE i32 (i32.const 40))
|
||||
(memory $0 1)
|
||||
(data (i32.const 4) "\10\00\00\00c\00a\00l\00l\00-\00i\00n\00f\00e\00r\00r\00e\00d\00.\00t\00s\00")
|
||||
(export "memory" (memory $0))
|
||||
(start $start)
|
||||
(func $call-inferred/foo<i32> (; 1 ;) (type $ii) (param $0 i32) (result i32)
|
||||
(return
|
||||
(get_local $0)
|
||||
)
|
||||
)
|
||||
(func $call-inferred/foo<f64> (; 2 ;) (type $FF) (param $0 f64) (result f64)
|
||||
(return
|
||||
(get_local $0)
|
||||
)
|
||||
)
|
||||
(func $call-inferred/foo<f32> (; 3 ;) (type $ff) (param $0 f32) (result f32)
|
||||
(return
|
||||
(get_local $0)
|
||||
)
|
||||
)
|
||||
(func $call-inferred/bar<f32> (; 4 ;) (type $ff) (param $0 f32) (result f32)
|
||||
(return
|
||||
(get_local $0)
|
||||
)
|
||||
)
|
||||
(func $start (; 5 ;) (type $v)
|
||||
(if
|
||||
(i32.eqz
|
||||
(i32.eq
|
||||
(call $call-inferred/foo<i32>
|
||||
(i32.const 42)
|
||||
)
|
||||
(i32.const 42)
|
||||
)
|
||||
)
|
||||
(block
|
||||
(call $abort
|
||||
(i32.const 0)
|
||||
(i32.const 4)
|
||||
(i32.const 5)
|
||||
(i32.const 0)
|
||||
)
|
||||
(unreachable)
|
||||
)
|
||||
)
|
||||
(if
|
||||
(i32.eqz
|
||||
(f64.eq
|
||||
(call $call-inferred/foo<f64>
|
||||
(f64.const 42)
|
||||
)
|
||||
(f64.const 42)
|
||||
)
|
||||
)
|
||||
(block
|
||||
(call $abort
|
||||
(i32.const 0)
|
||||
(i32.const 4)
|
||||
(i32.const 6)
|
||||
(i32.const 0)
|
||||
)
|
||||
(unreachable)
|
||||
)
|
||||
)
|
||||
(if
|
||||
(i32.eqz
|
||||
(f32.eq
|
||||
(call $call-inferred/foo<f32>
|
||||
(f32.const 42)
|
||||
)
|
||||
(f32.const 42)
|
||||
)
|
||||
)
|
||||
(block
|
||||
(call $abort
|
||||
(i32.const 0)
|
||||
(i32.const 4)
|
||||
(i32.const 7)
|
||||
(i32.const 0)
|
||||
)
|
||||
(unreachable)
|
||||
)
|
||||
)
|
||||
(if
|
||||
(i32.eqz
|
||||
(f32.eq
|
||||
(call $call-inferred/bar<f32>
|
||||
(f32.const 42)
|
||||
)
|
||||
(f32.const 42)
|
||||
)
|
||||
)
|
||||
(block
|
||||
(call $abort
|
||||
(i32.const 0)
|
||||
(i32.const 4)
|
||||
(i32.const 13)
|
||||
(i32.const 0)
|
||||
)
|
||||
(unreachable)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
Loading…
x
Reference in New Issue
Block a user