Make type checker produce first sane results
This commit is contained in:
parent
c2d101c25c
commit
4251c6eeaf
4 changed files with 191 additions and 255 deletions
|
@ -20,6 +20,7 @@ import {TextSpan, TextPos, TextFile} from "./text";
|
|||
import {Scanner} from "./scanner";
|
||||
import * as path from "path"
|
||||
import { convertNodeToSymbolPath } from "./resolver";
|
||||
import { TYPE_ERROR_MESSAGES } from "./diagnostics";
|
||||
|
||||
export function getSourceFile(node: Syntax) {
|
||||
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) {
|
||||
const symbolPath = convertNodeToSymbolPath(node);
|
||||
while (true) {
|
||||
|
|
|
@ -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_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_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_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}'."
|
||||
|
@ -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_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']
|
||||
|
||||
export interface Diagnostic {
|
||||
|
@ -52,6 +59,8 @@ export class DiagnosticPrinter {
|
|||
public hasErrors = false
|
||||
public hasFatal = false;
|
||||
|
||||
private indent = 0;
|
||||
|
||||
public add(diagnostic: Diagnostic): void {
|
||||
|
||||
if (BOLT_HARD_ERRORS && (diagnostic.severity === 'error' || diagnostic.severity === 'fatal')) {
|
||||
|
@ -64,7 +73,9 @@ export class DiagnosticPrinter {
|
|||
throw new Error(out);
|
||||
}
|
||||
|
||||
let out = ''
|
||||
const indentation = ' '.repeat(this.indent);
|
||||
|
||||
let out = indentation;
|
||||
|
||||
switch (diagnostic.severity) {
|
||||
case 'error':
|
||||
|
@ -78,6 +89,9 @@ export class DiagnosticPrinter {
|
|||
this.hasErrors = true;
|
||||
out += chalk.bold.red('warning: ');
|
||||
break;
|
||||
case 'info':
|
||||
out += chalk.bold.yellow('info: ')
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unkown severity for diagnostic message.`);
|
||||
}
|
||||
|
@ -103,8 +117,8 @@ export class DiagnosticPrinter {
|
|||
for (let i = startLine; i < endLine; i++) {
|
||||
const line = lines[i];
|
||||
let j = firstIndexOfNonEmpty(line);
|
||||
out += ' '+chalk.bgWhite.black(' '.repeat(gutterWidth-countDigits(i+1))+(i+1).toString())+' '+line+'\n'
|
||||
const gutter = ' '+chalk.bgWhite.black(' '.repeat(gutterWidth))+' '
|
||||
out += indentation + ' '+chalk.bgWhite.black(' '.repeat(gutterWidth-countDigits(i+1))+(i+1).toString())+' '+line+'\n'
|
||||
const gutter = indentation + ' '+chalk.bgWhite.black(' '.repeat(gutterWidth))+' '
|
||||
let mark: number;
|
||||
let skip: number;
|
||||
if (i === span.start.line-1 && i === span.end.line-1) {
|
||||
|
@ -131,6 +145,15 @@ export class DiagnosticPrinter {
|
|||
}
|
||||
|
||||
process.stderr.write(out);
|
||||
|
||||
if (diagnostic.nested !== undefined) {
|
||||
this.indent += 2;
|
||||
for (const nested of diagnostic.nested) {
|
||||
this.add(nested);
|
||||
}
|
||||
this.indent -= 2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ describe('a function that merges two equivalent types', () => {
|
|||
type1.node = createBoltIdentifier('a');
|
||||
const type2 = new AnyType;
|
||||
type2.node = createBoltIdentifier('b');
|
||||
const types = new UnionType([type1 type2]);
|
||||
const types = new UnionType([type1, type2]);
|
||||
mergeTypes(types);
|
||||
})
|
||||
|
||||
|
|
395
src/types.ts
395
src/types.ts
|
@ -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 { convertNodeToSymbolPath, ScopeType, SymbolResolver, SymbolInfo, SymbolPath } from "./resolver";
|
||||
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 { BOLT_MAX_FIELDS_TO_PRINT } from "./constants";
|
||||
import { emitNode } from "./emitter";
|
||||
|
@ -147,7 +147,7 @@ export class OpaqueType extends TypeBase {
|
|||
|
||||
public kind: TypeKind.OpaqueType = TypeKind.OpaqueType;
|
||||
|
||||
constructor(public name: string) {
|
||||
constructor(public name: string, public source?: Syntax) {
|
||||
super();
|
||||
}
|
||||
|
||||
|
@ -335,6 +335,7 @@ export function prettyPrintType(type: Type): string {
|
|||
out += '{ ' + elementType.name + ' } ';
|
||||
break;
|
||||
case TypeKind.ReturnType:
|
||||
out += 'return type of '
|
||||
out += prettyPrintType(elementType.fnType);
|
||||
out += '(';
|
||||
out += elementType.argumentTypes.map(prettyPrintType).join(', ')
|
||||
|
@ -433,10 +434,8 @@ function introducesType(node: Syntax) {
|
|||
|| isBoltRecordField(node)
|
||||
|| isBoltRecordFieldPattern(node)
|
||||
|| isBoltPattern(node)
|
||||
|| isBoltStatement(node)
|
||||
|| isBoltTypeExpression(node)
|
||||
|| isJSExpression(node)
|
||||
|| isJSStatement(node)
|
||||
|| isJSPattern(node)
|
||||
|| isJSParameter(node)
|
||||
}
|
||||
|
@ -487,78 +486,8 @@ export class TypeChecker {
|
|||
}
|
||||
}
|
||||
|
||||
private diagnoseTypeMismatch(node: Syntax, a: Type, b: Type): void {
|
||||
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 },
|
||||
})
|
||||
}
|
||||
private *diagnoseTypeMismatch(a: Type, b: Type): IterableIterator<Diagnostic> {
|
||||
|
||||
}
|
||||
|
||||
public registerSourceFile(sourceFile: SourceFile): void {
|
||||
|
@ -607,6 +536,7 @@ export class TypeChecker {
|
|||
const node = queued.shift()!;
|
||||
|
||||
const derivedType = this.deriveTypeUsingChildren(node);
|
||||
derivedType.node = node;
|
||||
const newType = this.simplifyType(derivedType);
|
||||
|
||||
//if (newType !== derivedType) {
|
||||
|
@ -616,13 +546,14 @@ export class TypeChecker {
|
|||
const narrowedType = this.narrowTypeDownTo(node.type!.solved, newType);
|
||||
|
||||
if (narrowedType === null) {
|
||||
this.diagnoseTypeMismatch(node, node.type!, derivedType);
|
||||
//node.errors.push({
|
||||
// message: E_TYPE_MISMATCH,
|
||||
// severity: 'error',
|
||||
// args: { left: node.type!, right: newType },
|
||||
// nested: [...this.diagnoseTypeMismatch(node.type!.solved, newType)],
|
||||
//});
|
||||
if (!hasTypeError(node)) {
|
||||
node.errors.push({
|
||||
message: E_TYPE_MISMATCH,
|
||||
severity: 'error',
|
||||
args: { left: node.type!, right: newType },
|
||||
nested: [...this.diagnoseTypeMismatch(node.type!.solved, newType)],
|
||||
});
|
||||
}
|
||||
} else {
|
||||
narrowedType.node = node;
|
||||
if (node.type.solved !== narrowedType) {
|
||||
|
@ -672,87 +603,6 @@ export class TypeChecker {
|
|||
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.
|
||||
*
|
||||
|
@ -836,36 +686,38 @@ export class TypeChecker {
|
|||
return new AnyType;
|
||||
}
|
||||
|
||||
this.mergeDuplicateTypes(elementTypes);
|
||||
|
||||
// This is a small optimisation to prevent the complexity of processing a
|
||||
// union type everwhere where it just contains one element.
|
||||
if (elementTypes.length === 1) {
|
||||
return elementTypes[0];
|
||||
}
|
||||
|
||||
return this.mergeDuplicateTypes(new UnionType(elementTypes));
|
||||
return new UnionType(elementTypes);
|
||||
}
|
||||
|
||||
private deriveTypeUsingChildren(node: Syntax): Type {
|
||||
|
||||
switch (node.kind) {
|
||||
|
||||
case SyntaxKind.JSReturnStatement:
|
||||
{
|
||||
if (node.value === null) {
|
||||
return this.getOpaqueType('undefined');
|
||||
}
|
||||
this.markNodeAsRequiringUpdate(node.value, node);
|
||||
return node.value.type!.solved;
|
||||
}
|
||||
//case SyntaxKind.JSReturnStatement:
|
||||
//{
|
||||
// if (node.value === null) {
|
||||
// return this.getOpaqueType('undefined');
|
||||
// }
|
||||
// this.markNodeAsRequiringUpdate(node.value, node);
|
||||
// return node.value.type!.solved;
|
||||
//}
|
||||
|
||||
case SyntaxKind.JSExpressionStatement:
|
||||
{
|
||||
if (node.expression === null) {
|
||||
return new TupleType;
|
||||
}
|
||||
this.markNodeAsRequiringUpdate(node.expression, node);
|
||||
return node.expression.type!.solved;
|
||||
}
|
||||
//case SyntaxKind.JSExpressionStatement:
|
||||
//{
|
||||
// if (node.expression === null) {
|
||||
// return new TupleType;
|
||||
// }
|
||||
// this.markNodeAsRequiringUpdate(node.expression, node);
|
||||
// return node.expression.type!.solved;
|
||||
//}
|
||||
|
||||
case SyntaxKind.JSMemberExpression:
|
||||
{
|
||||
|
@ -947,7 +799,7 @@ export class TypeChecker {
|
|||
{
|
||||
if (node.members === null) {
|
||||
const symbolPath = getFullyQualifiedPathToNode(node);
|
||||
return new OpaqueType(symbolPath.encode());
|
||||
return new OpaqueType(symbolPath.encode(), node);
|
||||
} else {
|
||||
let memberTypes: RecordFieldType[] = []
|
||||
for (const member of node.members) {
|
||||
|
@ -1069,31 +921,33 @@ export class TypeChecker {
|
|||
return this.getOpaqueType('Lang::Bolt::Node');
|
||||
}
|
||||
|
||||
case SyntaxKind.BoltExpressionStatement:
|
||||
{
|
||||
return this.deriveTypeUsingChildren(node.expression);
|
||||
}
|
||||
//case SyntaxKind.BoltExpressionStatement:
|
||||
//{
|
||||
// return this.deriveTypeUsingChildren(node.expression);
|
||||
//}
|
||||
|
||||
case SyntaxKind.BoltReturnStatement:
|
||||
{
|
||||
if (node.value === null) {
|
||||
const tupleType = new TupleType();
|
||||
return tupleType
|
||||
}
|
||||
return node.value.type!.solved;
|
||||
}
|
||||
//case SyntaxKind.BoltReturnStatement:
|
||||
//{
|
||||
// if (node.value === null) {
|
||||
// const tupleType = new TupleType();
|
||||
// return tupleType
|
||||
// }
|
||||
// return node.value.type!.solved;
|
||||
//}
|
||||
|
||||
case SyntaxKind.BoltBlockExpression:
|
||||
{
|
||||
let elementTypes = [];
|
||||
if (node.elements !== null) {
|
||||
for (const returnStmt of getReturnStatementsInFunctionBody(node.elements)) {
|
||||
elementTypes.push(returnStmt.type!.solved)
|
||||
this.markNodeAsRequiringUpdate(returnStmt, node);
|
||||
}
|
||||
}
|
||||
return new UnionType(elementTypes);
|
||||
}
|
||||
//case SyntaxKind.BoltBlockExpression:
|
||||
//{
|
||||
// let elementTypes = [];
|
||||
// if (node.elements !== null) {
|
||||
// for (const returnStmt of getReturnStatementsInFunctionBody(node.elements)) {
|
||||
// if (returnStmt.value !== null) {
|
||||
// elementTypes.push(returnStmt.value.type!.solved)
|
||||
// this.markNodeAsRequiringUpdate(returnStmt.value, node);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return new UnionType(elementTypes);
|
||||
//}
|
||||
|
||||
case SyntaxKind.BoltMemberExpression:
|
||||
{
|
||||
|
@ -1145,8 +999,12 @@ export class TypeChecker {
|
|||
}
|
||||
if (node.body !== null) {
|
||||
for (const returnStmt of getAllReturnStatementsInFunctionBody(node.body)) {
|
||||
returnTypes.push(returnStmt.type!.solved);
|
||||
this.markNodeAsRequiringUpdate(returnStmt, node);
|
||||
if (returnStmt.value !== null) {
|
||||
returnTypes.push(returnStmt.value.type!.solved);
|
||||
this.markNodeAsRequiringUpdate(returnStmt.value, node);
|
||||
} else {
|
||||
returnTypes.push(new TupleType);
|
||||
}
|
||||
}
|
||||
}
|
||||
let paramTypes = [];
|
||||
|
@ -1261,7 +1119,7 @@ export class TypeChecker {
|
|||
return this.areTypesSemanticallyEquivalent(resolvedType, b);
|
||||
}
|
||||
if (b.kind === TypeKind.ReturnType) {
|
||||
const resolvedType = this.resolveReturnType(a);
|
||||
const resolvedType = this.resolveReturnType(b);
|
||||
return this.areTypesSemanticallyEquivalent(a, resolvedType);
|
||||
}
|
||||
|
||||
|
@ -1306,38 +1164,91 @@ export class TypeChecker {
|
|||
|
||||
case TypeKind.FunctionType:
|
||||
|
||||
// Early check to see if the signature of the function call matches that of the function declaration.
|
||||
if (type.argumentTypes.length !== elementType.paramTypes.length) {
|
||||
continue;
|
||||
}
|
||||
if (elementType.paramTypes.length < type.argumentTypes.length) {
|
||||
|
||||
// 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;
|
||||
if (!hasDiagnostic(type.node!, E_TOO_MANY_ARGUMENTS_FOR_FUNCTION_CALL)) {
|
||||
|
||||
let matchFailed = false;
|
||||
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,
|
||||
});
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// If the argument types and parameter types didn't fail to match,
|
||||
// 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;
|
||||
|
||||
default:
|
||||
|
@ -1363,9 +1274,7 @@ export class TypeChecker {
|
|||
return new UnionType(resultTypes);
|
||||
}
|
||||
|
||||
private mergeDuplicateTypes(type: Type): Type {
|
||||
|
||||
const resultTypes = [...getAllPossibleElementTypes(type)];
|
||||
private mergeDuplicateTypes(resultTypes: Type[]): void {
|
||||
|
||||
resultTypes.sort(comparator(areTypesLexicallyLessThan));
|
||||
|
||||
|
@ -1377,19 +1286,9 @@ export class TypeChecker {
|
|||
break;
|
||||
}
|
||||
}
|
||||
resultTypes.splice(i, 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 {
|
||||
|
@ -1403,8 +1302,6 @@ export class TypeChecker {
|
|||
// iteration.
|
||||
const stack = [ type ];
|
||||
|
||||
//this.removeDuplicateTypes(elementTypes);
|
||||
|
||||
while (stack.length > 0) {
|
||||
|
||||
const elementType = stack.pop()!;
|
||||
|
@ -1454,12 +1351,14 @@ export class TypeChecker {
|
|||
}
|
||||
}
|
||||
|
||||
this.mergeDuplicateTypes(resultTypes);
|
||||
|
||||
// Small optimisation to make debugging easier.
|
||||
if (resultTypes.length === 1) {
|
||||
return resultTypes[0]
|
||||
}
|
||||
|
||||
return this.mergeDuplicateTypes(new UnionType(resultTypes));
|
||||
return new UnionType(resultTypes);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue