Infer function expressions in matching contexts (#514)

* legalizes omitting types on function expressions within function type contexts
* legalizes omitting any number of arguments
This commit is contained in:
Daniel Wirtz
2019-02-27 21:45:36 +01:00
committed by GitHub
parent 2945af6557
commit e8b0767143
29 changed files with 1349 additions and 701 deletions

View File

@ -108,7 +108,9 @@ import {
EnumDeclaration,
ExportStatement,
ExpressionStatement,
FieldDeclaration,
ForStatement,
FunctionDeclaration,
IfStatement,
ImportStatement,
InstanceOfExpression,
@ -146,8 +148,7 @@ import {
nodeIsConstantValue,
findDecorator,
FieldDeclaration,
FunctionDeclaration
isTypeOmitted
} from "./ast";
import {
@ -1075,7 +1076,7 @@ export class Compiler extends DiagnosticEmitter {
assert(bodyNode.kind == NodeKind.EXPRESSION);
// must be an arrow function
assert(instance.is(CommonFlags.ARROW));
assert(instance.prototype.arrowKind);
// none of the following can be an arrow function
assert(!instance.isAny(CommonFlags.CONSTRUCTOR | CommonFlags.GET | CommonFlags.SET | CommonFlags.MAIN));
@ -2362,7 +2363,7 @@ export class Compiler extends DiagnosticEmitter {
break;
}
case NodeKind.FUNCTION: {
expr = this.compileFunctionExpression(<FunctionExpression>expression, contextualType);
expr = this.compileFunctionExpression(<FunctionExpression>expression, contextualType.signatureReference);
break;
}
case NodeKind.IDENTIFIER:
@ -2711,7 +2712,7 @@ export class Compiler extends DiagnosticEmitter {
rightExpr = this.compileExpressionRetainType(right, leftType, WrapMode.NONE);
rightType = this.currentType;
if (commonType = Type.commonCompatible(leftType, rightType, true)) {
if (commonType = Type.commonDenominator(leftType, rightType, true)) {
leftExpr = this.convertExpression(
leftExpr,
leftType,
@ -2817,7 +2818,7 @@ export class Compiler extends DiagnosticEmitter {
rightExpr = this.compileExpressionRetainType(right, leftType, WrapMode.NONE);
rightType = this.currentType;
if (commonType = Type.commonCompatible(leftType, rightType, true)) {
if (commonType = Type.commonDenominator(leftType, rightType, true)) {
leftExpr = this.convertExpression(
leftExpr,
leftType,
@ -2923,7 +2924,7 @@ export class Compiler extends DiagnosticEmitter {
rightExpr = this.compileExpressionRetainType(right, leftType, WrapMode.NONE);
rightType = this.currentType;
if (commonType = Type.commonCompatible(leftType, rightType, true)) {
if (commonType = Type.commonDenominator(leftType, rightType, true)) {
leftExpr = this.convertExpression(
leftExpr,
leftType,
@ -3029,7 +3030,7 @@ export class Compiler extends DiagnosticEmitter {
rightExpr = this.compileExpressionRetainType(right, leftType, WrapMode.NONE);
rightType = this.currentType;
if (commonType = Type.commonCompatible(leftType, rightType, true)) {
if (commonType = Type.commonDenominator(leftType, rightType, true)) {
leftExpr = this.convertExpression(
leftExpr,
leftType,
@ -3138,7 +3139,7 @@ export class Compiler extends DiagnosticEmitter {
rightExpr = this.compileExpressionRetainType(right, leftType, WrapMode.NONE);
rightType = this.currentType;
if (commonType = Type.commonCompatible(leftType, rightType, false)) {
if (commonType = Type.commonDenominator(leftType, rightType, false)) {
leftExpr = this.convertExpression(
leftExpr,
leftType,
@ -3226,7 +3227,7 @@ export class Compiler extends DiagnosticEmitter {
rightExpr = this.compileExpressionRetainType(right, leftType, WrapMode.NONE);
rightType = this.currentType;
if (commonType = Type.commonCompatible(leftType, rightType, false)) {
if (commonType = Type.commonDenominator(leftType, rightType, false)) {
leftExpr = this.convertExpression(
leftExpr,
leftType,
@ -3324,7 +3325,7 @@ export class Compiler extends DiagnosticEmitter {
} else {
rightExpr = this.compileExpressionRetainType(right, leftType, WrapMode.NONE);
rightType = this.currentType;
if (commonType = Type.commonCompatible(leftType, rightType, false)) {
if (commonType = Type.commonDenominator(leftType, rightType, false)) {
leftExpr = this.convertExpression(
leftExpr,
leftType,
@ -3420,7 +3421,7 @@ export class Compiler extends DiagnosticEmitter {
} else {
rightExpr = this.compileExpressionRetainType(right, leftType, WrapMode.NONE);
rightType = this.currentType;
if (commonType = Type.commonCompatible(leftType, rightType, false)) {
if (commonType = Type.commonDenominator(leftType, rightType, false)) {
leftExpr = this.convertExpression(
leftExpr,
leftType,
@ -3516,7 +3517,7 @@ export class Compiler extends DiagnosticEmitter {
} else {
rightExpr = this.compileExpressionRetainType(right, leftType, WrapMode.NONE);
rightType = this.currentType;
if (commonType = Type.commonCompatible(leftType, rightType, false)) {
if (commonType = Type.commonDenominator(leftType, rightType, false)) {
leftExpr = this.convertExpression(
leftExpr,
leftType,
@ -3713,7 +3714,7 @@ export class Compiler extends DiagnosticEmitter {
} else {
rightExpr = this.compileExpressionRetainType(right, leftType, WrapMode.NONE);
rightType = this.currentType;
if (commonType = Type.commonCompatible(leftType, rightType, false)) {
if (commonType = Type.commonDenominator(leftType, rightType, false)) {
leftExpr = this.convertExpression(
leftExpr,
leftType,
@ -3828,7 +3829,7 @@ export class Compiler extends DiagnosticEmitter {
} else {
rightExpr = this.compileExpressionRetainType(right, leftType, WrapMode.NONE);
rightType = this.currentType;
if (commonType = Type.commonCompatible(leftType, rightType, false)) {
if (commonType = Type.commonDenominator(leftType, rightType, false)) {
leftExpr = this.convertExpression(
leftExpr,
leftType,
@ -4222,7 +4223,7 @@ export class Compiler extends DiagnosticEmitter {
} else {
rightExpr = this.compileExpressionRetainType(right, leftType, WrapMode.NONE);
rightType = this.currentType;
if (commonType = Type.commonCompatible(leftType, rightType, false)) {
if (commonType = Type.commonDenominator(leftType, rightType, false)) {
leftExpr = this.convertExpression(
leftExpr,
leftType,
@ -4318,7 +4319,7 @@ export class Compiler extends DiagnosticEmitter {
} else {
rightExpr = this.compileExpressionRetainType(right, leftType, WrapMode.NONE);
rightType = this.currentType;
if (commonType = Type.commonCompatible(leftType, rightType, false)) {
if (commonType = Type.commonDenominator(leftType, rightType, false)) {
leftExpr = this.convertExpression(
leftExpr,
leftType,
@ -4417,7 +4418,7 @@ export class Compiler extends DiagnosticEmitter {
} else {
rightExpr = this.compileExpressionRetainType(right, leftType, WrapMode.NONE);
rightType = this.currentType;
if (commonType = Type.commonCompatible(leftType, rightType, false)) {
if (commonType = Type.commonDenominator(leftType, rightType, false)) {
leftExpr = this.convertExpression(
leftExpr,
leftType,
@ -5085,8 +5086,8 @@ export class Compiler extends DiagnosticEmitter {
if (inferredType) {
argumentExprs[i] = this.compileExpressionRetainType(argumentExpression, inferredType, WrapMode.NONE);
let commonType: Type | null;
if (!(commonType = Type.commonCompatible(inferredType, this.currentType, true))) {
if (!(commonType = Type.commonCompatible(inferredType, this.currentType, false))) {
if (!(commonType = Type.commonDenominator(inferredType, this.currentType, true))) {
if (!(commonType = Type.commonDenominator(inferredType, this.currentType, false))) {
this.error(
DiagnosticCode.Type_0_is_not_assignable_to_type_1,
parameterNodes[i].type.range, this.currentType.toString(), inferredType.toString()
@ -5901,30 +5902,135 @@ export class Compiler extends DiagnosticEmitter {
compileFunctionExpression(
expression: FunctionExpression,
contextualType: Type
contextualSignature: Signature | null
): ExpressionRef {
var declaration = expression.declaration;
var name = declaration.name;
var simpleName = (name.text.length
? name.text
: "anonymous") + "|" + this.functionTable.length.toString(10);
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 prototype = new FunctionPrototype(
simpleName,
flow.actualFunction,
declaration.clone(), // same function can be compiled multiple times if generic
declaration.name.text.length
? declaration.name.text
: "anonymous|" + (actualFunction.nextAnonymousId++).toString(10),
actualFunction,
declaration,
DecoratorFlags.NONE
);
var instance = this.compileFunctionUsingTypeArguments(
prototype,
[],
makeMap<string,Type>(flow.contextualTypeArguments),
declaration
);
if (!instance) return this.module.createUnreachable();
this.currentType = instance.signature.type; // TODO: get cached type?
// NOTE that, in order to make this work in every case, the function must be represented by a
// value, so we add it and rely on the optimizer to figure out where it can be called directly.
var instance: Function | null;
var contextualTypeArguments = makeMap(flow.contextualTypeArguments);
// 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.
if (contextualSignature) {
let signatureNode = prototype.signatureNode;
let parameterNodes = signatureNode.parameters;
let numPresentParameters = parameterNodes.length;
// must not require more than the maximum number of parameters
let parameterTypes = contextualSignature.parameterTypes;
let numParameters = parameterTypes.length;
if (numPresentParameters > numParameters) {
this.error(
DiagnosticCode.Expected_0_arguments_but_got_1,
expression.range, numParameters.toString(), numPresentParameters.toString()
);
return this.module.createUnreachable();
}
// check non-omitted parameter types
let parameterNames = new Array<string>(numPresentParameters);
for (let i = 0; i < numPresentParameters; ++i) {
let parameterNode = parameterNodes[i];
parameterNames[i] = parameterNode.name.text; // use actual name
if (!isTypeOmitted(parameterNode.type)) {
let resolvedType = this.resolver.resolveType(
parameterNode.type,
actualFunction.parent,
contextualTypeArguments
);
if (!resolvedType) return this.module.createUnreachable();
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.createUnreachable();
}
}
// any unused parameters are inherited but ignored
}
// check non-omitted return type
let returnType = contextualSignature.returnType;
if (!isTypeOmitted(signatureNode.returnType)) {
let resolvedType = this.resolver.resolveType(
signatureNode.returnType,
actualFunction.parent,
contextualTypeArguments
);
if (!resolvedType) return this.module.createUnreachable();
if (
returnType == Type.void
? resolvedType != Type.void
: !resolvedType.isStrictlyAssignableTo(returnType)
) {
this.error(
DiagnosticCode.Type_0_is_not_assignable_to_type_1,
signatureNode.returnType.range, resolvedType.toString(), returnType.toString()
);
return this.module.createUnreachable();
}
}
// check explicit this type
let thisType = contextualSignature.thisType;
let thisTypeNode = signatureNode.explicitThisType;
if (thisTypeNode) {
if (!thisType) {
this.error(
DiagnosticCode._this_cannot_be_referenced_in_current_location,
thisTypeNode.range
);
return this.module.createUnreachable();
}
let resolvedType = this.resolver.resolveType(
thisTypeNode,
actualFunction.parent,
contextualTypeArguments
);
if (!resolvedType) return this.module.createUnreachable();
if (!thisType.isStrictlyAssignableTo(resolvedType)) {
this.error(
DiagnosticCode.Type_0_is_not_assignable_to_type_1,
thisTypeNode.range, thisType.toString(), resolvedType.toString()
);
return this.module.createUnreachable();
}
}
let signature = new Signature(parameterTypes, returnType, thisType);
signature.requiredParameters = numParameters; // !
signature.parameterNames = parameterNames;
instance = new Function(
prototype.name,
prototype,
signature,
contextualTypeArguments
);
if (!this.compileFunction(instance)) return this.module.createUnreachable();
this.currentType = contextualSignature.type;
// otherwise compile like a normal function
} else {
instance = this.compileFunctionUsingTypeArguments(
prototype,
[],
contextualTypeArguments
);
if (!instance) return this.module.createUnreachable();
this.currentType = instance.signature.type;
}
var index = this.ensureFunctionTableEntry(instance); // reports
return index < 0
? this.module.createUnreachable()
@ -6955,7 +7061,7 @@ export class Compiler extends DiagnosticEmitter {
outerFlow.inheritMutual(ifThenFlow, ifElseFlow);
var commonType = Type.commonCompatible(ifThenType, ifElseType, false);
var commonType = Type.commonDenominator(ifThenType, ifElseType, false);
if (!commonType) {
this.error(
DiagnosticCode.Type_0_is_not_assignable_to_type_1,