Make type checker produce first sane results

This commit is contained in:
Sam Vervaeck 2020-05-29 20:33:04 +02:00
parent c2d101c25c
commit 4251c6eeaf
4 changed files with 191 additions and 255 deletions

View file

@ -20,6 +20,7 @@ import {TextSpan, TextPos, TextFile} from "./text";
import {Scanner} from "./scanner"; import {Scanner} from "./scanner";
import * as path from "path" import * as path from "path"
import { convertNodeToSymbolPath } from "./resolver"; import { convertNodeToSymbolPath } from "./resolver";
import { TYPE_ERROR_MESSAGES } from "./diagnostics";
export function getSourceFile(node: Syntax) { export function getSourceFile(node: Syntax) {
while (true) { while (true) {
@ -250,6 +251,19 @@ export function isExported(node: Syntax) {
} }
} }
export function hasTypeError(node: Syntax) {
for (const message of TYPE_ERROR_MESSAGES) {
if (hasDiagnostic(node, message)) {
return true;
}
}
return false;
}
export function hasDiagnostic(node: Syntax, message: string): boolean {
return node.errors.some(d => d.message === message);
}
export function getFullyQualifiedPathToNode(node: Syntax) { export function getFullyQualifiedPathToNode(node: Syntax) {
const symbolPath = convertNodeToSymbolPath(node); const symbolPath = convertNodeToSymbolPath(node);
while (true) { while (true) {

View file

@ -17,7 +17,7 @@ export const E_TYPE_MISMATCH = "Types {left} and {right} are not semantically eq
export const E_TOO_FEW_ARGUMENTS_FOR_FUNCTION_CALL = "Too few arguments for function call. Expected {expected} but got {actual}."; export const E_TOO_FEW_ARGUMENTS_FOR_FUNCTION_CALL = "Too few arguments for function call. Expected {expected} but got {actual}.";
export const E_TOO_MANY_ARGUMENTS_FOR_FUNCTION_CALL = "Too many arguments for function call. Expected {expected} but got {actual}."; export const E_TOO_MANY_ARGUMENTS_FOR_FUNCTION_CALL = "Too many arguments for function call. Expected {expected} but got {actual}.";
export const E_CANDIDATE_FUNCTION_REQUIRES_THIS_PARAMETER = "Candidate function requires this parameter." export const E_CANDIDATE_FUNCTION_REQUIRES_THIS_PARAMETER = "Candidate function requires this parameter."
export const E_ARGUMENT_HAS_NO_CORRESPONDING_PARAMETER = "Argument has no corresponding parameter." export const E_ARGUMENT_HAS_NO_CORRESPONDING_PARAMETER = "This argument is missing a corresponding parameter."
export const E_INVALID_ARGUMENTS = "Invalid arguments passed to function '{name}'" export const E_INVALID_ARGUMENTS = "Invalid arguments passed to function '{name}'"
export const E_RECORD_MISSING_MEMBER = "Record {name} does not have a member declaration named {memberName}" export const E_RECORD_MISSING_MEMBER = "Record {name} does not have a member declaration named {memberName}"
export const E_TYPES_MISSING_MEMBER = "Not all types resolve to a record with the a member named '{name}'." export const E_TYPES_MISSING_MEMBER = "Not all types resolve to a record with the a member named '{name}'."
@ -26,6 +26,13 @@ export const E_MAY_NOT_RETURN_BECAUSE_TYPE_RESOLVES_TO_VOID = "May not return a
export const E_MUST_RETURN_BECAUSE_TYPE_DOES_NOT_RESOLVE_TO_VOID = "Must return a value because the function's return type does not resolve to '()'" export const E_MUST_RETURN_BECAUSE_TYPE_DOES_NOT_RESOLVE_TO_VOID = "Must return a value because the function's return type does not resolve to '()'"
export const E_ARGUMENT_TYPE_NOT_ASSIGNABLE = "This argument's type is not assignable to the function's parameter type." export const E_ARGUMENT_TYPE_NOT_ASSIGNABLE = "This argument's type is not assignable to the function's parameter type."
export const TYPE_ERROR_MESSAGES = [
E_TOO_FEW_ARGUMENTS_FOR_FUNCTION_CALL,
E_TOO_MANY_ARGUMENTS_FOR_FUNCTION_CALL,
E_ARGUMENT_TYPE_NOT_ASSIGNABLE,
E_TYPE_MISMATCH,
]
const BOLT_HARD_ERRORS = process.env['BOLT_HARD_ERRORS'] const BOLT_HARD_ERRORS = process.env['BOLT_HARD_ERRORS']
export interface Diagnostic { export interface Diagnostic {
@ -52,6 +59,8 @@ export class DiagnosticPrinter {
public hasErrors = false public hasErrors = false
public hasFatal = false; public hasFatal = false;
private indent = 0;
public add(diagnostic: Diagnostic): void { public add(diagnostic: Diagnostic): void {
if (BOLT_HARD_ERRORS && (diagnostic.severity === 'error' || diagnostic.severity === 'fatal')) { if (BOLT_HARD_ERRORS && (diagnostic.severity === 'error' || diagnostic.severity === 'fatal')) {
@ -64,7 +73,9 @@ export class DiagnosticPrinter {
throw new Error(out); throw new Error(out);
} }
let out = '' const indentation = ' '.repeat(this.indent);
let out = indentation;
switch (diagnostic.severity) { switch (diagnostic.severity) {
case 'error': case 'error':
@ -78,6 +89,9 @@ export class DiagnosticPrinter {
this.hasErrors = true; this.hasErrors = true;
out += chalk.bold.red('warning: '); out += chalk.bold.red('warning: ');
break; break;
case 'info':
out += chalk.bold.yellow('info: ')
break;
default: default:
throw new Error(`Unkown severity for diagnostic message.`); throw new Error(`Unkown severity for diagnostic message.`);
} }
@ -103,8 +117,8 @@ export class DiagnosticPrinter {
for (let i = startLine; i < endLine; i++) { for (let i = startLine; i < endLine; i++) {
const line = lines[i]; const line = lines[i];
let j = firstIndexOfNonEmpty(line); let j = firstIndexOfNonEmpty(line);
out += ' '+chalk.bgWhite.black(' '.repeat(gutterWidth-countDigits(i+1))+(i+1).toString())+' '+line+'\n' out += indentation + ' '+chalk.bgWhite.black(' '.repeat(gutterWidth-countDigits(i+1))+(i+1).toString())+' '+line+'\n'
const gutter = ' '+chalk.bgWhite.black(' '.repeat(gutterWidth))+' ' const gutter = indentation + ' '+chalk.bgWhite.black(' '.repeat(gutterWidth))+' '
let mark: number; let mark: number;
let skip: number; let skip: number;
if (i === span.start.line-1 && i === span.end.line-1) { if (i === span.start.line-1 && i === span.end.line-1) {
@ -131,6 +145,15 @@ export class DiagnosticPrinter {
} }
process.stderr.write(out); process.stderr.write(out);
if (diagnostic.nested !== undefined) {
this.indent += 2;
for (const nested of diagnostic.nested) {
this.add(nested);
}
this.indent -= 2;
}
} }
} }

View file

@ -11,7 +11,7 @@ describe('a function that merges two equivalent types', () => {
type1.node = createBoltIdentifier('a'); type1.node = createBoltIdentifier('a');
const type2 = new AnyType; const type2 = new AnyType;
type2.node = createBoltIdentifier('b'); type2.node = createBoltIdentifier('b');
const types = new UnionType([type1 type2]); const types = new UnionType([type1, type2]);
mergeTypes(types); mergeTypes(types);
}) })

View file

@ -3,7 +3,7 @@ import { FastStringMap, assert, isPlainObject, some, prettyPrintTag, map, flatMa
import { SyntaxKind, Syntax, isBoltTypeExpression, BoltExpression, BoltFunctionDeclaration, BoltFunctionBodyElement, kindToString, SourceFile, isBoltExpression, BoltCallExpression, BoltIdentifier, isBoltDeclarationLike, isBoltPattern, isJSExpression, isBoltStatement, isJSStatement, isJSPattern, isJSParameter, isBoltParameter, isBoltMatchArm, isBoltRecordField, isBoltRecordFieldPattern, isEndOfFile, isSyntax, } from "./ast"; import { SyntaxKind, Syntax, isBoltTypeExpression, BoltExpression, BoltFunctionDeclaration, BoltFunctionBodyElement, kindToString, SourceFile, isBoltExpression, BoltCallExpression, BoltIdentifier, isBoltDeclarationLike, isBoltPattern, isJSExpression, isBoltStatement, isJSStatement, isJSPattern, isJSParameter, isBoltParameter, isBoltMatchArm, isBoltRecordField, isBoltRecordFieldPattern, isEndOfFile, isSyntax, } from "./ast";
import { convertNodeToSymbolPath, ScopeType, SymbolResolver, SymbolInfo, SymbolPath } from "./resolver"; import { convertNodeToSymbolPath, ScopeType, SymbolResolver, SymbolInfo, SymbolPath } from "./resolver";
import { Value, Record } from "./evaluator"; import { Value, Record } from "./evaluator";
import { getReturnStatementsInFunctionBody, getAllReturnStatementsInFunctionBody, getFullyQualifiedPathToNode } from "./common"; import { getReturnStatementsInFunctionBody, getAllReturnStatementsInFunctionBody, getFullyQualifiedPathToNode, hasDiagnostic, hasTypeError } from "./common";
import { E_TOO_MANY_ARGUMENTS_FOR_FUNCTION_CALL, E_TOO_FEW_ARGUMENTS_FOR_FUNCTION_CALL, E_CANDIDATE_FUNCTION_REQUIRES_THIS_PARAMETER, E_ARGUMENT_HAS_NO_CORRESPONDING_PARAMETER, E_TYPE_MISMATCH, Diagnostic, E_ARGUMENT_TYPE_NOT_ASSIGNABLE } from "./diagnostics"; import { E_TOO_MANY_ARGUMENTS_FOR_FUNCTION_CALL, E_TOO_FEW_ARGUMENTS_FOR_FUNCTION_CALL, E_CANDIDATE_FUNCTION_REQUIRES_THIS_PARAMETER, E_ARGUMENT_HAS_NO_CORRESPONDING_PARAMETER, E_TYPE_MISMATCH, Diagnostic, E_ARGUMENT_TYPE_NOT_ASSIGNABLE } from "./diagnostics";
import { BOLT_MAX_FIELDS_TO_PRINT } from "./constants"; import { BOLT_MAX_FIELDS_TO_PRINT } from "./constants";
import { emitNode } from "./emitter"; import { emitNode } from "./emitter";
@ -147,7 +147,7 @@ export class OpaqueType extends TypeBase {
public kind: TypeKind.OpaqueType = TypeKind.OpaqueType; public kind: TypeKind.OpaqueType = TypeKind.OpaqueType;
constructor(public name: string) { constructor(public name: string, public source?: Syntax) {
super(); super();
} }
@ -335,6 +335,7 @@ export function prettyPrintType(type: Type): string {
out += '{ ' + elementType.name + ' } '; out += '{ ' + elementType.name + ' } ';
break; break;
case TypeKind.ReturnType: case TypeKind.ReturnType:
out += 'return type of '
out += prettyPrintType(elementType.fnType); out += prettyPrintType(elementType.fnType);
out += '('; out += '(';
out += elementType.argumentTypes.map(prettyPrintType).join(', ') out += elementType.argumentTypes.map(prettyPrintType).join(', ')
@ -433,10 +434,8 @@ function introducesType(node: Syntax) {
|| isBoltRecordField(node) || isBoltRecordField(node)
|| isBoltRecordFieldPattern(node) || isBoltRecordFieldPattern(node)
|| isBoltPattern(node) || isBoltPattern(node)
|| isBoltStatement(node)
|| isBoltTypeExpression(node) || isBoltTypeExpression(node)
|| isJSExpression(node) || isJSExpression(node)
|| isJSStatement(node)
|| isJSPattern(node) || isJSPattern(node)
|| isJSParameter(node) || isJSParameter(node)
} }
@ -487,78 +486,8 @@ export class TypeChecker {
} }
} }
private diagnoseTypeMismatch(node: Syntax, a: Type, b: Type): void { private *diagnoseTypeMismatch(a: Type, b: Type): IterableIterator<Diagnostic> {
if (a.kind === TypeKind.ReturnType && b.kind === TypeKind.FunctionType) {
if (b.paramTypes.length > a.argumentTypes.length) {
let nested: Diagnostic[] = [];
//for (let i = b.paramTypes.length; i < a.argumentTypes.length; i++) {
// nested.push({
// message: E_CANDIDATE_FUNCTION_REQUIRES_THIS_PARAMETER,
// severity: 'error',
// node: b.getTypeAtParameterIndex(a.argumentTypes.length).node!
// });
//}
node.errors.push({
message: E_TOO_MANY_ARGUMENTS_FOR_FUNCTION_CALL,
severity: 'error',
node: a.node,
args: {
expected: b.paramTypes.length,
actual: a.argumentTypes.length,
},
nested,
});
} else if (b.paramTypes.length < a.argumentTypes.length) {
let nested: Diagnostic[] = [];
//for (let i = b.paramTypes.length; i < a.argumentTypes.length; i++) {
// nested.push({
// message: E_ARGUMENT_HAS_NO_CORRESPONDING_PARAMETER,
// severity: 'error',
// node: (a.node as BoltCallExpression).operands[i]
// });
//}
node.errors.push({
message: E_TOO_FEW_ARGUMENTS_FOR_FUNCTION_CALL,
severity: 'error',
node: a.node,
args: {
expected: b.paramTypes.length,
actual: a.argumentTypes.length,
},
nested,
});
} else {
const paramCount = a.argumentTypes.length;
for (let i = 0; i < paramCount; i++) {
const argType = a.argumentTypes[i];
const paramType = b.paramTypes[i];
if (!this.areTypesSemanticallyEquivalent(argType, paramType)) {
node.errors.push({
message: E_ARGUMENT_TYPE_NOT_ASSIGNABLE,
severity: 'error',
node: argType.node,
})
}
//if (!this.areTypesSemanticallyEquivalent(paramA, paramB)) {
// yield {
// message: E_TYPE_MISMATCH,
// severity: 'error',
// node: a.node,
// args: {
// left: a,
// right: b,
// },
// };
//}
}
}
} else {
node.errors.push({
message: E_TYPE_MISMATCH,
severity: 'error',
args: { left: a, right: b },
})
}
} }
public registerSourceFile(sourceFile: SourceFile): void { public registerSourceFile(sourceFile: SourceFile): void {
@ -607,6 +536,7 @@ export class TypeChecker {
const node = queued.shift()!; const node = queued.shift()!;
const derivedType = this.deriveTypeUsingChildren(node); const derivedType = this.deriveTypeUsingChildren(node);
derivedType.node = node;
const newType = this.simplifyType(derivedType); const newType = this.simplifyType(derivedType);
//if (newType !== derivedType) { //if (newType !== derivedType) {
@ -616,13 +546,14 @@ export class TypeChecker {
const narrowedType = this.narrowTypeDownTo(node.type!.solved, newType); const narrowedType = this.narrowTypeDownTo(node.type!.solved, newType);
if (narrowedType === null) { if (narrowedType === null) {
this.diagnoseTypeMismatch(node, node.type!, derivedType); if (!hasTypeError(node)) {
//node.errors.push({ node.errors.push({
// message: E_TYPE_MISMATCH, message: E_TYPE_MISMATCH,
// severity: 'error', severity: 'error',
// args: { left: node.type!, right: newType }, args: { left: node.type!, right: newType },
// nested: [...this.diagnoseTypeMismatch(node.type!.solved, newType)], nested: [...this.diagnoseTypeMismatch(node.type!.solved, newType)],
//}); });
}
} else { } else {
narrowedType.node = node; narrowedType.node = node;
if (node.type.solved !== narrowedType) { if (node.type.solved !== narrowedType) {
@ -672,87 +603,6 @@ export class TypeChecker {
return this.areTypesSemanticallyEquivalent(new TupleType, type); return this.areTypesSemanticallyEquivalent(new TupleType, type);
} }
//private *getTypesForMember(origNode: Syntax, fieldName: string, type: Type): IterableIterator<Type> {
// switch (type.kind) {
// case TypeKind.UnionType:
// {
// const typesMissingMember = [];
// for (const elementType of getAllPossibleElementTypes(type)) {
// let foundType = false;
// for (const recordType of this.getTypesForMemberNoUnionType(origNode, fieldName, elementType, false)) {
// yield recordType;
// foundType = true;
// }
// if (!foundType) {
// origNode.errors.push({
// message: E_TYPES_MISSING_MEMBER,
// severity: 'error',
// })
// }
// }
// }
// default:
// return this.getTypesForMemberNoUnionType(origNode, fieldName, type, true);
// }
//}
//private *getTypesForMemberNoUnionType(origNode: Syntax, fieldName: string, type: Type, hardError: boolean): IterableIterator<Type> {
// switch (type.kind) {
// case TypeKind.AnyType:
// break;
// case TypeKind.FunctionType:
// if (hardError) {
// origNode.errors.push({
// message: E_TYPES_MISSING_MEMBER,
// severity: 'error',
// args: {
// name: fieldName,
// },
// nested: [{
// message: E_NODE_DOES_NOT_CONTAIN_MEMBER,
// severity: 'error',
// node: type.node!,
// }]
// });
// }
// break;
// case TypeKind.RecordType:
// {
// if (type.isFieldRequired(fieldName)) {
// const fieldType = type.getAllMemberTypesForField(fieldName);
// yield (fieldType as PlainRecordFieldType).type;
// } else {
// if (hardError) {
// origNode.errors.push({
// message: E_TYPES_MISSING_MEMBER,
// severity: 'error',
// args: {
// name: fieldName
// }
// })
// }
// }
// break;
// }
// default:
// throw new Error(`I do not know how to find record member types for ${TypeKind[type.kind]}`)
// }
//}
//private *getAllNodesForType(type: Type): IterableIterator<Syntax> {
// if (type.node !== undefined) {
// yield type.node;
// }
// switch (type.kind) {
// case TypeKind.UnionType:
// for (const elementType of type.getElementTypes()) {
// yield* this.getAllNodesForType(type);
// }
// break;
// default:
// }
//}
/** /**
* Narrows @param outter down to @param inner. If @param outer could not be narrowed, this function return null. * Narrows @param outter down to @param inner. If @param outer could not be narrowed, this function return null.
* *
@ -836,36 +686,38 @@ export class TypeChecker {
return new AnyType; return new AnyType;
} }
this.mergeDuplicateTypes(elementTypes);
// This is a small optimisation to prevent the complexity of processing a // This is a small optimisation to prevent the complexity of processing a
// union type everwhere where it just contains one element. // union type everwhere where it just contains one element.
if (elementTypes.length === 1) { if (elementTypes.length === 1) {
return elementTypes[0]; return elementTypes[0];
} }
return this.mergeDuplicateTypes(new UnionType(elementTypes)); return new UnionType(elementTypes);
} }
private deriveTypeUsingChildren(node: Syntax): Type { private deriveTypeUsingChildren(node: Syntax): Type {
switch (node.kind) { switch (node.kind) {
case SyntaxKind.JSReturnStatement: //case SyntaxKind.JSReturnStatement:
{ //{
if (node.value === null) { // if (node.value === null) {
return this.getOpaqueType('undefined'); // return this.getOpaqueType('undefined');
} // }
this.markNodeAsRequiringUpdate(node.value, node); // this.markNodeAsRequiringUpdate(node.value, node);
return node.value.type!.solved; // return node.value.type!.solved;
} //}
case SyntaxKind.JSExpressionStatement: //case SyntaxKind.JSExpressionStatement:
{ //{
if (node.expression === null) { // if (node.expression === null) {
return new TupleType; // return new TupleType;
} // }
this.markNodeAsRequiringUpdate(node.expression, node); // this.markNodeAsRequiringUpdate(node.expression, node);
return node.expression.type!.solved; // return node.expression.type!.solved;
} //}
case SyntaxKind.JSMemberExpression: case SyntaxKind.JSMemberExpression:
{ {
@ -947,7 +799,7 @@ export class TypeChecker {
{ {
if (node.members === null) { if (node.members === null) {
const symbolPath = getFullyQualifiedPathToNode(node); const symbolPath = getFullyQualifiedPathToNode(node);
return new OpaqueType(symbolPath.encode()); return new OpaqueType(symbolPath.encode(), node);
} else { } else {
let memberTypes: RecordFieldType[] = [] let memberTypes: RecordFieldType[] = []
for (const member of node.members) { for (const member of node.members) {
@ -1069,31 +921,33 @@ export class TypeChecker {
return this.getOpaqueType('Lang::Bolt::Node'); return this.getOpaqueType('Lang::Bolt::Node');
} }
case SyntaxKind.BoltExpressionStatement: //case SyntaxKind.BoltExpressionStatement:
{ //{
return this.deriveTypeUsingChildren(node.expression); // return this.deriveTypeUsingChildren(node.expression);
} //}
case SyntaxKind.BoltReturnStatement: //case SyntaxKind.BoltReturnStatement:
{ //{
if (node.value === null) { // if (node.value === null) {
const tupleType = new TupleType(); // const tupleType = new TupleType();
return tupleType // return tupleType
} // }
return node.value.type!.solved; // return node.value.type!.solved;
} //}
case SyntaxKind.BoltBlockExpression: //case SyntaxKind.BoltBlockExpression:
{ //{
let elementTypes = []; // let elementTypes = [];
if (node.elements !== null) { // if (node.elements !== null) {
for (const returnStmt of getReturnStatementsInFunctionBody(node.elements)) { // for (const returnStmt of getReturnStatementsInFunctionBody(node.elements)) {
elementTypes.push(returnStmt.type!.solved) // if (returnStmt.value !== null) {
this.markNodeAsRequiringUpdate(returnStmt, node); // elementTypes.push(returnStmt.value.type!.solved)
} // this.markNodeAsRequiringUpdate(returnStmt.value, node);
} // }
return new UnionType(elementTypes); // }
} // }
// return new UnionType(elementTypes);
//}
case SyntaxKind.BoltMemberExpression: case SyntaxKind.BoltMemberExpression:
{ {
@ -1145,8 +999,12 @@ export class TypeChecker {
} }
if (node.body !== null) { if (node.body !== null) {
for (const returnStmt of getAllReturnStatementsInFunctionBody(node.body)) { for (const returnStmt of getAllReturnStatementsInFunctionBody(node.body)) {
returnTypes.push(returnStmt.type!.solved); if (returnStmt.value !== null) {
this.markNodeAsRequiringUpdate(returnStmt, node); returnTypes.push(returnStmt.value.type!.solved);
this.markNodeAsRequiringUpdate(returnStmt.value, node);
} else {
returnTypes.push(new TupleType);
}
} }
} }
let paramTypes = []; let paramTypes = [];
@ -1261,7 +1119,7 @@ export class TypeChecker {
return this.areTypesSemanticallyEquivalent(resolvedType, b); return this.areTypesSemanticallyEquivalent(resolvedType, b);
} }
if (b.kind === TypeKind.ReturnType) { if (b.kind === TypeKind.ReturnType) {
const resolvedType = this.resolveReturnType(a); const resolvedType = this.resolveReturnType(b);
return this.areTypesSemanticallyEquivalent(a, resolvedType); return this.areTypesSemanticallyEquivalent(a, resolvedType);
} }
@ -1306,38 +1164,91 @@ export class TypeChecker {
case TypeKind.FunctionType: case TypeKind.FunctionType:
// Early check to see if the signature of the function call matches that of the function declaration. if (elementType.paramTypes.length < type.argumentTypes.length) {
if (type.argumentTypes.length !== elementType.paramTypes.length) {
if (!hasDiagnostic(type.node!, E_TOO_MANY_ARGUMENTS_FOR_FUNCTION_CALL)) {
let nested: Diagnostic[] = [];
for (let i = elementType.paramTypes.length; i < type.argumentTypes.length; i++) {
nested.push({
message: E_CANDIDATE_FUNCTION_REQUIRES_THIS_PARAMETER,
severity: 'error',
node: elementType.getTypeAtParameterIndex(type.argumentTypes.length).node!
});
}
type.node!.errors.push({
message: E_TOO_MANY_ARGUMENTS_FOR_FUNCTION_CALL,
severity: 'error',
args: {
expected: elementType.paramTypes.length,
actual: type.argumentTypes.length,
},
nested,
});
}
// Skip this return type
continue;
} else if (elementType.paramTypes.length > type.argumentTypes.length) {
if (!hasDiagnostic(type.node!, E_TOO_FEW_ARGUMENTS_FOR_FUNCTION_CALL)) {
const nested: Diagnostic[] = [];
for (let i = type.argumentTypes.length; i < elementType.paramTypes.length; i++) {
nested.push({
message: E_ARGUMENT_HAS_NO_CORRESPONDING_PARAMETER,
severity: 'info',
node: elementType.paramTypes[i].node!
});
}
type.node!.errors.push({
message: E_TOO_FEW_ARGUMENTS_FOR_FUNCTION_CALL,
severity: 'error',
args: {
expected: elementType.paramTypes.length,
actual: type.argumentTypes.length,
},
nested,
});
}
// Skip this return type
continue;
} else {
let hasErrors = false;
const paramCount = type.argumentTypes.length;
for (let i = 0; i < paramCount; i++) {
const argType = type.argumentTypes[i];
const paramType = elementType.paramTypes[i];
if (!this.areTypesSemanticallyEquivalent(argType, paramType)) {
if (!hasDiagnostic(type.solved.node!, E_ARGUMENT_TYPE_NOT_ASSIGNABLE)) {
type.node!.errors.push({
message: E_ARGUMENT_TYPE_NOT_ASSIGNABLE,
severity: 'error',
node: argType.node,
args: { argType, paramType }
})
}
hasErrors = true;
}
}
// Skip this return type if we had type errors
if (hasErrors) {
continue; continue;
} }
// Since we are assured by now that `type.argumentTypes` and `elementType.paramTypes` have equal length,
// we may pick the length of any of these two as the index bound in the next loop.
const paramCount = type.argumentTypes.length;
let matchFailed = false;
// Iterate pairwise over the argument types and parameter types, checking whether they are assignable to each other.
// In the current version of the checker, this amounts to checking if they are semantically equal.
for (let i = 0; i < paramCount; i++) {
const argType = type.argumentTypes[i].solved;
const paramType = elementType.paramTypes[i].solved;
if (!this.areTypesSemanticallyEquivalent(argType, paramType)) {
//argType.node!.errors.push({
//message: E_TYPE_MISMATCH,
//severity: 'error',
//args: { left: paramType, right: argType }
//})
matchFailed = true;
break;
}
} }
// If the argument types and parameter types didn't fail to match, // If the argument types and parameter types didn't fail to match,
// the function type is eligable to be 'called' by the given ReturnType. // the function type is eligable to be 'called' by the given ReturnType.
if (!matchFailed) {
resultTypes.push(elementType.returnType.solved); resultTypes.push(elementType.returnType.solved);
}
break; break;
default: default:
@ -1363,9 +1274,7 @@ export class TypeChecker {
return new UnionType(resultTypes); return new UnionType(resultTypes);
} }
private mergeDuplicateTypes(type: Type): Type { private mergeDuplicateTypes(resultTypes: Type[]): void {
const resultTypes = [...getAllPossibleElementTypes(type)];
resultTypes.sort(comparator(areTypesLexicallyLessThan)); resultTypes.sort(comparator(areTypesLexicallyLessThan));
@ -1377,19 +1286,9 @@ export class TypeChecker {
break; break;
} }
} }
resultTypes.splice(i, j-i-1);
resultTypes.splice(i+1, j-i-1); resultTypes.splice(i+1, j-i-1);
} }
if (resultTypes.length === 0) {
return new NeverType;
}
if (resultTypes.length === 1) {
return resultTypes[0];
}
return new UnionType(resultTypes);
} }
private simplifyType(type: Type): Type { private simplifyType(type: Type): Type {
@ -1403,8 +1302,6 @@ export class TypeChecker {
// iteration. // iteration.
const stack = [ type ]; const stack = [ type ];
//this.removeDuplicateTypes(elementTypes);
while (stack.length > 0) { while (stack.length > 0) {
const elementType = stack.pop()!; const elementType = stack.pop()!;
@ -1454,12 +1351,14 @@ export class TypeChecker {
} }
} }
this.mergeDuplicateTypes(resultTypes);
// Small optimisation to make debugging easier. // Small optimisation to make debugging easier.
if (resultTypes.length === 1) { if (resultTypes.length === 1) {
return resultTypes[0] return resultTypes[0]
} }
return this.mergeDuplicateTypes(new UnionType(resultTypes)); return new UnionType(resultTypes);
} }
} }