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 {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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
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 { 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) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Since we are assured by now that `type.argumentTypes` and `elementType.paramTypes` have equal length,
|
if (!hasDiagnostic(type.node!, E_TOO_MANY_ARGUMENTS_FOR_FUNCTION_CALL)) {
|
||||||
// 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;
|
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,
|
// 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue