Compare commits
1 Commits
pr35044
...
mmv-tcf-01
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c01775222 |
@@ -104,6 +104,7 @@ import {validateNoImpureFunctionsInRender} from '../Validation/ValidateNoImpureF
|
||||
import {CompilerError} from '..';
|
||||
import {validateStaticComponents} from '../Validation/ValidateStaticComponents';
|
||||
import {validateNoFreezingKnownMutableFunctions} from '../Validation/ValidateNoFreezingKnownMutableFunctions';
|
||||
import { typecheck } from '../TypeInference/Flood/Typecheck';
|
||||
|
||||
export type CompilerPipelineValue =
|
||||
| {kind: 'ast'; name: string; value: CodegenFunction}
|
||||
@@ -200,6 +201,8 @@ function runWithEnvironment(
|
||||
constantPropagation(hir);
|
||||
log({kind: 'hir', name: 'ConstantPropagation', value: hir});
|
||||
|
||||
typecheck(hir);
|
||||
|
||||
inferTypes(hir);
|
||||
log({kind: 'hir', name: 'InferTypes', value: hir});
|
||||
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import {CompilerError, SourceLocation} from '../..';
|
||||
import {ConcreteType, printConcrete, printType, VariableId} from './Types';
|
||||
|
||||
export function unsupportedLanguageFeature(
|
||||
desc: string,
|
||||
loc: SourceLocation,
|
||||
): never {
|
||||
CompilerError.throwInvalidJS({
|
||||
reason: `Typedchecker does not currently support language feature: ${desc}`,
|
||||
loc,
|
||||
});
|
||||
}
|
||||
|
||||
export type UnificationError = {left: ConcreteType; right: ConcreteType};
|
||||
|
||||
export function raiseUnificationErrors(
|
||||
errs: null | Array<UnificationError>,
|
||||
loc: SourceLocation,
|
||||
) {
|
||||
if (errs != null) {
|
||||
if (errs.length === 0) {
|
||||
CompilerError.invariant(false, {
|
||||
reason: 'Should not have array of zero errors',
|
||||
loc,
|
||||
});
|
||||
} else if (errs.length === 1) {
|
||||
CompilerError.throwInvalidJS({
|
||||
reason: `Unable to unify types because ${printConcrete(errs[0].left)} is incompatible with ${printConcrete(errs[0].right)}`,
|
||||
loc,
|
||||
});
|
||||
} else {
|
||||
const messages = errs
|
||||
.map(
|
||||
({left, right}) =>
|
||||
`\t* ${printConcrete(errs[0].left)} is incompatible with ${printConcrete(errs[0].right)}`,
|
||||
)
|
||||
.join('\n');
|
||||
CompilerError.throwInvalidJS({
|
||||
reason: `Unable to unify types because:\n${messages}`,
|
||||
loc,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function unresolvableTypeVariable(
|
||||
id: VariableId,
|
||||
loc: SourceLocation,
|
||||
): never {
|
||||
CompilerError.throwInvalidJS({
|
||||
reason: `Unable to resolve free variable ${id} to a concrete type`,
|
||||
loc,
|
||||
});
|
||||
}
|
||||
|
||||
export function cannotAddVoid(explicit: boolean, loc: SourceLocation): never {
|
||||
if (explicit) {
|
||||
CompilerError.throwInvalidJS({
|
||||
reason: `Undefined is not a valid operand of \`+\``,
|
||||
loc,
|
||||
});
|
||||
} else {
|
||||
CompilerError.throwInvalidJS({
|
||||
reason: `Value may be undefined, which is not a valid operand of \`+\``,
|
||||
loc,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function unsupportedTypeAnnotation(
|
||||
desc: string,
|
||||
loc: SourceLocation,
|
||||
): never {
|
||||
CompilerError.throwInvalidJS({
|
||||
reason: `Typedchecker does not currently support type annotation: ${desc}`,
|
||||
loc,
|
||||
});
|
||||
}
|
||||
|
||||
export function checkTypeArgumentArity(
|
||||
desc: string,
|
||||
expected: number,
|
||||
actual: number,
|
||||
loc: SourceLocation,
|
||||
) {
|
||||
if (expected !== actual) {
|
||||
CompilerError.throwInvalidJS({
|
||||
reason: `Expected ${desc} to have ${expected} type parameters, got ${actual}`,
|
||||
loc,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,611 @@
|
||||
import {
|
||||
BlockId,
|
||||
Environment,
|
||||
GeneratedSource,
|
||||
HIR,
|
||||
HIRFunction,
|
||||
InstructionKind,
|
||||
Place,
|
||||
SourceLocation,
|
||||
SpreadPattern,
|
||||
} from '../../HIR';
|
||||
import {Types, TypeEnv, ConcreteType, Type} from './Types';
|
||||
import * as TypeErrors from './TypeErrors';
|
||||
import * as t from '@babel/types';
|
||||
import {CompilerError} from '../..';
|
||||
import {assertExhaustive} from '../../Utils/utils';
|
||||
|
||||
export function typecheck(fn: HIRFunction): void {
|
||||
const typeEnv = new TypeEnv();
|
||||
const seenBlocks = new Set<BlockId>();
|
||||
|
||||
typeParams(fn.params, typeEnv);
|
||||
|
||||
const returnType = t.isFlowType(fn.returnTypeAnnotation) ? convert(fn.returnTypeAnnotation, typeEnv) : Types.variable(typeEnv);
|
||||
|
||||
for (const [blockId, block] of fn.body.blocks) {
|
||||
for (const phi of block.phis) {
|
||||
const [[block0, operand0], ...operands] = phi.operands;
|
||||
if (seenBlocks.has(block0) && operands.every(([block, op]) => seenBlocks.has(block) && typeEnv.checkEqual(typeEnv.getType(op.identifier), typeEnv.getType(operand0.identifier)))) {
|
||||
typeEnv.setType(phi.place.identifier, typeEnv.getType(operand0.identifier));
|
||||
} else if (phi.place.identifier.name != null) {
|
||||
typeEnv.setType(phi.place.identifier, typeEnv.getDeclaredType(phi.place.identifier));
|
||||
} else {
|
||||
CompilerError.throwTodo({ reason: `Cannot determine phi type for ${phi.place.identifier.id}`, loc: phi.place.loc});
|
||||
}
|
||||
}
|
||||
|
||||
for (const instr of block.instructions) {
|
||||
switch (instr.value.kind) {
|
||||
case 'ArrayExpression': {
|
||||
const eltType = Types.variable(typeEnv);
|
||||
for (const elt of instr.value.elements) {
|
||||
if (elt.kind === 'Spread') {
|
||||
TypeErrors.unsupportedLanguageFeature(
|
||||
'array spreads',
|
||||
elt.place.loc,
|
||||
);
|
||||
} else if (elt.kind === 'Hole') {
|
||||
TypeErrors.unsupportedLanguageFeature(
|
||||
'array holes',
|
||||
instr.value.loc,
|
||||
);
|
||||
} else {
|
||||
typeEnv.unify(
|
||||
eltType,
|
||||
typeEnv.getType(elt.identifier),
|
||||
instr.loc,
|
||||
);
|
||||
}
|
||||
}
|
||||
typeEnv.setType(instr.lvalue.identifier, eltType);
|
||||
break;
|
||||
}
|
||||
case 'Await': {
|
||||
TypeErrors.unsupportedLanguageFeature('await', instr.loc);
|
||||
}
|
||||
case 'BinaryExpression': {
|
||||
const left = typeEnv.getType(instr.value.left.identifier);
|
||||
const right = typeEnv.getType(instr.value.right.identifier);
|
||||
|
||||
switch (instr.value.operator) {
|
||||
case '+': {
|
||||
TypeErrors.unsupportedLanguageFeature(
|
||||
'addition, lol',
|
||||
instr.value.loc,
|
||||
);
|
||||
}
|
||||
case '!=':
|
||||
case '!==':
|
||||
case '==':
|
||||
case '===':
|
||||
case 'instanceof':
|
||||
case 'in': {
|
||||
typeEnv.setType(instr.lvalue.identifier, Types.boolean());
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
typeEnv.unify(left, Types.number(), instr.value.left.loc);
|
||||
typeEnv.unify(right, Types.number(), instr.value.right.loc);
|
||||
typeEnv.setType(instr.lvalue.identifier, Types.number());
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'MethodCall':
|
||||
case 'CallExpression': {
|
||||
const calleePlace =
|
||||
instr.value.kind === 'CallExpression'
|
||||
? instr.value.callee
|
||||
: instr.value.property;
|
||||
const typeArgumentsPlaces =
|
||||
instr.value.kind === 'CallExpression'
|
||||
? instr.value.typeArguments
|
||||
: null;
|
||||
const callee = typeEnv.getType(calleePlace.identifier);
|
||||
const typeArguments =
|
||||
typeArgumentsPlaces?.map(ty => convert(ty, typeEnv)) ?? null;
|
||||
if (typeArguments != null) {
|
||||
TypeErrors.unsupportedLanguageFeature(
|
||||
'explicit function generic instantiation',
|
||||
instr.value.loc,
|
||||
);
|
||||
}
|
||||
const callArguments = instr.value.args.map(arg => {
|
||||
if (arg.kind === 'Spread') {
|
||||
TypeErrors.unsupportedLanguageFeature(
|
||||
'spread arguments',
|
||||
arg.place.loc,
|
||||
);
|
||||
}
|
||||
return typeEnv.getType(arg.identifier);
|
||||
});
|
||||
const returnType = Types.variable(typeEnv);
|
||||
typeEnv.unify(
|
||||
callee,
|
||||
Types.function(null, callArguments, returnType),
|
||||
calleePlace.loc,
|
||||
);
|
||||
typeEnv.setType(instr.lvalue.identifier, returnType);
|
||||
break;
|
||||
}
|
||||
case 'ComputedDelete':
|
||||
case 'PropertyDelete': {
|
||||
TypeErrors.unsupportedLanguageFeature('delete', instr.loc);
|
||||
}
|
||||
case 'ComputedLoad': {
|
||||
const loaded = typeEnv.resolve(
|
||||
typeEnv.getType(instr.value.object.identifier),
|
||||
instr.value.object.loc,
|
||||
);
|
||||
if (loaded.kind === 'Array') {
|
||||
typeEnv.unify(
|
||||
typeEnv.getType(instr.value.property.identifier),
|
||||
Types.number(),
|
||||
instr.value.property.loc,
|
||||
);
|
||||
typeEnv.setType(instr.lvalue.identifier, loaded.element);
|
||||
} else {
|
||||
typeEnv.unify(
|
||||
typeEnv.getType(instr.value.property.identifier),
|
||||
Types.string(),
|
||||
instr.value.property.loc,
|
||||
);
|
||||
TypeErrors.unsupportedLanguageFeature(
|
||||
'computed object load',
|
||||
instr.loc,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'ComputedStore': {
|
||||
const loaded = typeEnv.resolve(
|
||||
typeEnv.getType(instr.value.object.identifier),
|
||||
instr.value.object.loc,
|
||||
);
|
||||
if (loaded.kind === 'Array') {
|
||||
typeEnv.unify(
|
||||
typeEnv.getType(instr.value.property.identifier),
|
||||
Types.number(),
|
||||
instr.value.property.loc,
|
||||
);
|
||||
typeEnv.unify(
|
||||
loaded.element,
|
||||
typeEnv.getType(instr.value.value.identifier),
|
||||
instr.value.value.loc,
|
||||
);
|
||||
} else {
|
||||
typeEnv.unify(
|
||||
typeEnv.getType(instr.value.property.identifier),
|
||||
Types.string(),
|
||||
instr.value.property.loc,
|
||||
);
|
||||
TypeErrors.unsupportedLanguageFeature(
|
||||
'computed object store',
|
||||
instr.loc,
|
||||
);
|
||||
}
|
||||
typeEnv.setType(
|
||||
instr.lvalue.identifier,
|
||||
typeEnv.getType(instr.value.value.identifier),
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'Debugger': {
|
||||
TypeErrors.unsupportedLanguageFeature('debugger', instr.loc);
|
||||
}
|
||||
case 'DeclareContext':
|
||||
case 'DeclareLocal': {
|
||||
if (
|
||||
instr.value.kind === 'DeclareLocal' &&
|
||||
t.isFlowType(instr.value.type)
|
||||
) {
|
||||
typeEnv.declare(
|
||||
instr.value.lvalue.place.identifier,
|
||||
convert(instr.value.type, typeEnv),
|
||||
);
|
||||
} else {
|
||||
typeEnv.declare(
|
||||
instr.value.lvalue.place.identifier,
|
||||
Types.variable(typeEnv),
|
||||
);
|
||||
}
|
||||
typeEnv.setType(instr.lvalue.identifier, Types.void());
|
||||
break;
|
||||
}
|
||||
case 'Destructure': {
|
||||
TypeErrors.unsupportedLanguageFeature('destructuring', instr.loc);
|
||||
}
|
||||
case 'FinishMemoize':
|
||||
case 'StartMemoize':
|
||||
case 'UnsupportedNode': {
|
||||
break;
|
||||
}
|
||||
case 'FunctionExpression':
|
||||
case 'ObjectMethod': {
|
||||
TypeErrors.unsupportedLanguageFeature(
|
||||
'function expressions',
|
||||
instr.loc,
|
||||
);
|
||||
}
|
||||
case 'GetIterator':
|
||||
case 'IteratorNext':
|
||||
case 'NextPropertyOf': {
|
||||
TypeErrors.unsupportedLanguageFeature('iterators', instr.loc);
|
||||
}
|
||||
case 'JSXText': {
|
||||
typeEnv.setType(instr.lvalue.identifier, Types.string());
|
||||
break;
|
||||
}
|
||||
case 'JsxExpression':
|
||||
case 'JsxFragment': {
|
||||
TypeErrors.unsupportedLanguageFeature('JSX', instr.loc);
|
||||
}
|
||||
case 'LoadContext':
|
||||
case 'LoadLocal': {
|
||||
typeEnv.setType(
|
||||
instr.lvalue.identifier,
|
||||
typeEnv.getType(instr.value.place.identifier),
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'LoadGlobal': {
|
||||
TypeErrors.unsupportedLanguageFeature('globals', instr.loc);
|
||||
}
|
||||
case 'MetaProperty': {
|
||||
TypeErrors.unsupportedLanguageFeature('metaproperties', instr.loc);
|
||||
}
|
||||
case 'NewExpression': {
|
||||
TypeErrors.unsupportedLanguageFeature('new', instr.loc);
|
||||
}
|
||||
case 'ObjectExpression': {
|
||||
TypeErrors.unsupportedLanguageFeature('objects', instr.loc);
|
||||
}
|
||||
case 'PostfixUpdate':
|
||||
case 'PrefixUpdate': {
|
||||
typeEnv.unify(
|
||||
typeEnv.getType(instr.value.value.identifier),
|
||||
Types.number(),
|
||||
instr.value.loc,
|
||||
);
|
||||
typeEnv.setType(instr.lvalue.identifier, Types.number());
|
||||
break;
|
||||
}
|
||||
case 'Primitive': {
|
||||
let ty: Type;
|
||||
switch (typeof instr.value.value) {
|
||||
case 'boolean':
|
||||
ty = Types.boolean();
|
||||
break;
|
||||
case 'number':
|
||||
ty = Types.number();
|
||||
break;
|
||||
case 'string':
|
||||
ty = Types.string();
|
||||
break;
|
||||
case 'undefined':
|
||||
ty = Types.void();
|
||||
break;
|
||||
case 'object':
|
||||
if (instr.value.value === null) {
|
||||
TypeErrors.unsupportedLanguageFeature('null', instr.value.loc);
|
||||
}
|
||||
CompilerError.invariant(false, {
|
||||
reason: 'Unexpected primitive value',
|
||||
loc: instr.value.loc,
|
||||
});
|
||||
default:
|
||||
CompilerError.invariant(false, {
|
||||
reason: 'Unexpected primitive value',
|
||||
loc: instr.value.loc,
|
||||
});
|
||||
}
|
||||
typeEnv.setType(instr.lvalue.identifier, ty);
|
||||
break;
|
||||
}
|
||||
case 'PropertyLoad':
|
||||
case 'PropertyStore': {
|
||||
TypeErrors.unsupportedLanguageFeature('object properties', instr.loc);
|
||||
}
|
||||
case 'RegExpLiteral': {
|
||||
TypeErrors.unsupportedLanguageFeature('regexp literals', instr.loc);
|
||||
}
|
||||
case 'StoreContext':
|
||||
case 'StoreLocal': {
|
||||
let ty;
|
||||
if (
|
||||
instr.value.kind === 'StoreLocal' &&
|
||||
t.isFlowType(instr.value.type)
|
||||
) {
|
||||
ty = convert(instr.value.type, typeEnv);
|
||||
} else {
|
||||
ty = Types.variable(typeEnv);
|
||||
}
|
||||
if (instr.value.lvalue.kind === InstructionKind.Reassign) {
|
||||
typeEnv.setType(instr.value.lvalue.place.identifier, ty);
|
||||
typeEnv.setType(instr.lvalue.identifier, ty);
|
||||
} else {
|
||||
typeEnv.declare(instr.value.lvalue.place.identifier, ty);
|
||||
typeEnv.setType(instr.lvalue.identifier, Types.void());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'StoreGlobal': {
|
||||
TypeErrors.unsupportedLanguageFeature('global assignment', instr.loc);
|
||||
}
|
||||
case 'TaggedTemplateExpression': {
|
||||
TypeErrors.unsupportedLanguageFeature('tagged templates', instr.loc);
|
||||
}
|
||||
case 'TemplateLiteral': {
|
||||
TypeErrors.unsupportedLanguageFeature('templates', instr.loc);
|
||||
}
|
||||
case 'TypeCastExpression': {
|
||||
if (t.isFlowType(instr.value.typeAnnotation)) {
|
||||
const cast = convert(instr.value.typeAnnotation, typeEnv);
|
||||
typeEnv.unify(
|
||||
typeEnv.getType(instr.value.value.identifier),
|
||||
cast,
|
||||
instr.value.loc,
|
||||
);
|
||||
typeEnv.setType(instr.lvalue.identifier, cast);
|
||||
} else {
|
||||
typeEnv.setType(
|
||||
instr.lvalue.identifier,
|
||||
typeEnv.getType(instr.value.value.identifier),
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'UnaryExpression': {
|
||||
switch (instr.value.operator) {
|
||||
case '+':
|
||||
case '-':
|
||||
case '~': {
|
||||
typeEnv.setType(instr.lvalue.identifier, Types.number());
|
||||
break;
|
||||
}
|
||||
case '!': {
|
||||
typeEnv.setType(instr.lvalue.identifier, Types.boolean());
|
||||
break;
|
||||
}
|
||||
case 'void': {
|
||||
typeEnv.setType(instr.lvalue.identifier, Types.void());
|
||||
break;
|
||||
}
|
||||
case 'typeof': {
|
||||
typeEnv.setType(instr.lvalue.identifier, Types.string());
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(instr.value, 'Unhandled unary operator');
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (block.terminal.kind) {
|
||||
case 'return': {
|
||||
typeEnv.unify(returnType, typeEnv.getType(block.terminal.value.identifier), block.terminal.loc);
|
||||
}
|
||||
}
|
||||
seenBlocks.add(blockId);
|
||||
}
|
||||
}
|
||||
|
||||
function convert(ty: t.FlowType, typeEnv: TypeEnv): Type {
|
||||
switch (ty.type) {
|
||||
case 'AnyTypeAnnotation': {
|
||||
TypeErrors.unsupportedTypeAnnotation('any', ty.loc ?? GeneratedSource);
|
||||
}
|
||||
case 'ArrayTypeAnnotation': {
|
||||
return Types.array(convert(ty.elementType, typeEnv));
|
||||
}
|
||||
case 'BooleanLiteralTypeAnnotation':
|
||||
case 'BooleanTypeAnnotation': {
|
||||
return Types.boolean();
|
||||
}
|
||||
case 'EmptyTypeAnnotation': {
|
||||
TypeErrors.unsupportedTypeAnnotation('empty', ty.loc ?? GeneratedSource);
|
||||
}
|
||||
case 'ExistsTypeAnnotation': {
|
||||
TypeErrors.unsupportedTypeAnnotation(
|
||||
'star type',
|
||||
ty.loc ?? GeneratedSource,
|
||||
);
|
||||
}
|
||||
case 'FunctionTypeAnnotation': {
|
||||
if (ty.rest != null) {
|
||||
TypeErrors.unsupportedTypeAnnotation(
|
||||
'rest parameters',
|
||||
ty.rest.loc ?? GeneratedSource,
|
||||
);
|
||||
}
|
||||
const type = Types.function(
|
||||
ty.typeParameters?.params.map(param => {
|
||||
if (param.default != null) {
|
||||
TypeErrors.unsupportedTypeAnnotation(
|
||||
'type parameter defaults',
|
||||
param.default.loc ?? GeneratedSource,
|
||||
);
|
||||
}
|
||||
const binding = {
|
||||
id: typeEnv.nextTypeParameterId(),
|
||||
bound:
|
||||
param.bound != null
|
||||
? convert(param.bound.typeAnnotation, typeEnv)
|
||||
: Types.mixed(),
|
||||
};
|
||||
typeEnv.pushGeneric(param.name, binding);
|
||||
return binding;
|
||||
}) ?? null,
|
||||
ty.params.map(param => {
|
||||
if (param.optional) {
|
||||
TypeErrors.unsupportedTypeAnnotation(
|
||||
'optional parameters',
|
||||
param.loc ?? GeneratedSource,
|
||||
);
|
||||
}
|
||||
return convert(param.typeAnnotation, typeEnv);
|
||||
}),
|
||||
convert(ty.returnType, typeEnv),
|
||||
);
|
||||
ty.typeParameters?.params.forEach(param => {
|
||||
typeEnv.popGeneric(param.name);
|
||||
});
|
||||
return type;
|
||||
}
|
||||
case 'GenericTypeAnnotation': {
|
||||
if (ty.id.type === 'Identifier') {
|
||||
if (ty.id.name === 'Array') {
|
||||
TypeErrors.checkTypeArgumentArity(
|
||||
'array',
|
||||
1,
|
||||
ty.typeParameters?.params.length ?? 0,
|
||||
ty.loc ?? GeneratedSource,
|
||||
);
|
||||
return Types.array(convert(ty.typeParameters!.params[0], typeEnv));
|
||||
} else if (ty.id.name === 'Set') {
|
||||
TypeErrors.checkTypeArgumentArity(
|
||||
'set',
|
||||
1,
|
||||
ty.typeParameters?.params.length ?? 0,
|
||||
ty.loc ?? GeneratedSource,
|
||||
);
|
||||
return Types.set(convert(ty.typeParameters!.params[0], typeEnv));
|
||||
} else if (ty.id.name === 'Map') {
|
||||
TypeErrors.checkTypeArgumentArity(
|
||||
'map',
|
||||
2,
|
||||
ty.typeParameters?.params.length ?? 0,
|
||||
ty.loc ?? GeneratedSource,
|
||||
);
|
||||
return Types.map(
|
||||
convert(ty.typeParameters!.params[0], typeEnv),
|
||||
convert(ty.typeParameters!.params[1], typeEnv),
|
||||
);
|
||||
} else if (ty.id.name === 'undefined') {
|
||||
TypeErrors.checkTypeArgumentArity(
|
||||
'undefined',
|
||||
0,
|
||||
ty.typeParameters?.params.length ?? 0,
|
||||
ty.loc ?? GeneratedSource,
|
||||
);
|
||||
return Types.void();
|
||||
} else {
|
||||
const generic = typeEnv.getGeneric(ty.id.name);
|
||||
if (generic != null) {
|
||||
return {
|
||||
kind: 'Concrete',
|
||||
type: {kind: 'Generic', id: generic.id, bound: generic.bound},
|
||||
};
|
||||
}
|
||||
TypeErrors.unsupportedTypeAnnotation(
|
||||
'type alias',
|
||||
ty.loc ?? GeneratedSource,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
TypeErrors.unsupportedTypeAnnotation(
|
||||
'qualified type alias',
|
||||
ty.loc ?? GeneratedSource,
|
||||
);
|
||||
}
|
||||
}
|
||||
case 'IndexedAccessType':
|
||||
case 'OptionalIndexedAccessType': {
|
||||
TypeErrors.unsupportedTypeAnnotation(
|
||||
'indexed access type',
|
||||
ty.loc ?? GeneratedSource,
|
||||
);
|
||||
}
|
||||
case 'InterfaceTypeAnnotation': {
|
||||
TypeErrors.unsupportedTypeAnnotation(
|
||||
'interface',
|
||||
ty.loc ?? GeneratedSource,
|
||||
);
|
||||
}
|
||||
case 'IntersectionTypeAnnotation': {
|
||||
TypeErrors.unsupportedTypeAnnotation(
|
||||
'intersection',
|
||||
ty.loc ?? GeneratedSource,
|
||||
);
|
||||
}
|
||||
case 'MixedTypeAnnotation': {
|
||||
return Types.mixed();
|
||||
}
|
||||
case 'NullLiteralTypeAnnotation': {
|
||||
TypeErrors.unsupportedTypeAnnotation(
|
||||
'null type',
|
||||
ty.loc ?? GeneratedSource,
|
||||
);
|
||||
}
|
||||
case 'NullableTypeAnnotation': {
|
||||
return Types.nullable(convert(ty.typeAnnotation, typeEnv));
|
||||
}
|
||||
case 'NumberLiteralTypeAnnotation':
|
||||
case 'NumberTypeAnnotation': {
|
||||
return Types.number();
|
||||
}
|
||||
case 'ObjectTypeAnnotation': {
|
||||
TypeErrors.unsupportedTypeAnnotation(
|
||||
'object type',
|
||||
ty.loc ?? GeneratedSource,
|
||||
);
|
||||
}
|
||||
case 'StringLiteralTypeAnnotation':
|
||||
case 'StringTypeAnnotation': {
|
||||
return Types.string();
|
||||
}
|
||||
case 'SymbolTypeAnnotation': {
|
||||
TypeErrors.unsupportedTypeAnnotation(
|
||||
'symbol type',
|
||||
ty.loc ?? GeneratedSource,
|
||||
);
|
||||
}
|
||||
case 'ThisTypeAnnotation': {
|
||||
TypeErrors.unsupportedTypeAnnotation(
|
||||
'this-type',
|
||||
ty.loc ?? GeneratedSource,
|
||||
);
|
||||
}
|
||||
case 'TupleTypeAnnotation': {
|
||||
TypeErrors.unsupportedTypeAnnotation(
|
||||
'tuple type',
|
||||
ty.loc ?? GeneratedSource,
|
||||
);
|
||||
}
|
||||
case 'TypeofTypeAnnotation': {
|
||||
TypeErrors.unsupportedTypeAnnotation(
|
||||
'typeof type',
|
||||
ty.loc ?? GeneratedSource,
|
||||
);
|
||||
}
|
||||
case 'UnionTypeAnnotation': {
|
||||
TypeErrors.unsupportedTypeAnnotation(
|
||||
'union type',
|
||||
ty.loc ?? GeneratedSource,
|
||||
);
|
||||
}
|
||||
case 'VoidTypeAnnotation': {
|
||||
return Types.void();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function typeParams(
|
||||
params: Array<Place | SpreadPattern>,
|
||||
typeEnv: TypeEnv,
|
||||
): void {
|
||||
for (const param of params) {
|
||||
if (param.kind === 'Spread') {
|
||||
TypeErrors.unsupportedLanguageFeature(
|
||||
'spread arguments',
|
||||
param.place.loc,
|
||||
);
|
||||
}
|
||||
|
||||
typeEnv.declare(param.identifier, Types.todo());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,579 @@
|
||||
import {CompilerError, SourceLocation} from '../..';
|
||||
import {DeclarationId, Identifier, IdentifierId} from '../../HIR';
|
||||
import DisjointSet from '../../Utils/DisjointSet';
|
||||
import {UnificationError} from './TypeErrors';
|
||||
import * as TypeErrors from './TypeErrors';
|
||||
|
||||
export type Type =
|
||||
| {kind: 'Concrete'; type: ConcreteType}
|
||||
| {kind: 'Variable'; id: VariableId};
|
||||
|
||||
export type ConcreteType =
|
||||
| {kind: 'Mixed'}
|
||||
| {kind: 'Number'}
|
||||
| {kind: 'String'}
|
||||
| {kind: 'Boolean'}
|
||||
| {kind: 'Void'}
|
||||
| {kind: 'Nullable'; type: Type}
|
||||
| {kind: 'Array'; element: Type}
|
||||
| {kind: 'Set'; element: Type}
|
||||
| {kind: 'Map'; key: Type; value: Type}
|
||||
| {
|
||||
kind: 'Function';
|
||||
typeParameters: null | Array<TypeParameter>;
|
||||
params: Array<Type>;
|
||||
returnType: Type;
|
||||
}
|
||||
| {kind: 'Generic'; id: TypeParameterId; bound: Type}
|
||||
| {kind: 'Nominal'; id: NominalId; typeArguments: null | Array<Type>};
|
||||
|
||||
type Object = {
|
||||
id: NominalId;
|
||||
typeParameters: null | Array<TypeParameter>;
|
||||
members: Map<string, Type>;
|
||||
};
|
||||
|
||||
type TypeParameter = {
|
||||
id: TypeParameterId;
|
||||
bound: Type;
|
||||
};
|
||||
|
||||
const opaqueTypeParameterId = Symbol();
|
||||
export type TypeParameterId = number & {
|
||||
[opaqueTypeParameterId]: 'TypeParameterId';
|
||||
};
|
||||
|
||||
export function makeTypeParameterId(id: number): TypeParameterId {
|
||||
CompilerError.invariant(id >= 0 && Number.isInteger(id), {
|
||||
reason: 'Expected TypeParameterId to be a non-negative integer',
|
||||
description: null,
|
||||
loc: null,
|
||||
suggestions: null,
|
||||
});
|
||||
return id as TypeParameterId;
|
||||
}
|
||||
|
||||
const opaqueNominalId = Symbol();
|
||||
export type NominalId = number & {
|
||||
[opaqueNominalId]: 'NominalId';
|
||||
};
|
||||
|
||||
export function makeNominalId(id: number): NominalId {
|
||||
CompilerError.invariant(id >= 0 && Number.isInteger(id), {
|
||||
reason: 'Expected NominalId id to be a non-negative integer',
|
||||
description: null,
|
||||
loc: null,
|
||||
suggestions: null,
|
||||
});
|
||||
return id as NominalId;
|
||||
}
|
||||
|
||||
const opaqueVariableId = Symbol();
|
||||
export type VariableId = number & {
|
||||
[opaqueVariableId]: 'VariableId';
|
||||
};
|
||||
|
||||
export function makeVariableId(id: number): VariableId {
|
||||
CompilerError.invariant(id >= 0 && Number.isInteger(id), {
|
||||
reason: 'Expected VariableId id to be a non-negative integer',
|
||||
description: null,
|
||||
loc: null,
|
||||
suggestions: null,
|
||||
});
|
||||
return id as VariableId;
|
||||
}
|
||||
|
||||
export function printConcrete(type: ConcreteType): string {
|
||||
switch (type.kind) {
|
||||
case 'Mixed':
|
||||
return 'mixed';
|
||||
case 'Number':
|
||||
return 'number';
|
||||
case 'String':
|
||||
return 'string';
|
||||
case 'Boolean':
|
||||
return 'boolean';
|
||||
case 'Void':
|
||||
return 'void';
|
||||
case 'Nullable':
|
||||
return `${printType(type.type)} | null`;
|
||||
case 'Array':
|
||||
return `Array<${printType(type.element)}>`;
|
||||
case 'Set':
|
||||
return `Set<${printType(type.element)}>`;
|
||||
case 'Map':
|
||||
return `Map<${printType(type.key)}, ${printType(type.value)}>`;
|
||||
case 'Function': {
|
||||
const typeParams = type.typeParameters
|
||||
? `<${type.typeParameters.map(tp => `T${tp}`).join(', ')}>`
|
||||
: '';
|
||||
const params = type.params.map(printType).join(', ');
|
||||
const returnType = printType(type.returnType);
|
||||
return `${typeParams}(${params}) => ${returnType}`;
|
||||
}
|
||||
case 'Generic':
|
||||
return `T${type.id}`;
|
||||
case 'Nominal': {
|
||||
const name = `Nominal ${type.id}`;
|
||||
if (!type.typeArguments) {
|
||||
return name;
|
||||
}
|
||||
const typeArgs = type.typeArguments.map(printType).join(', ');
|
||||
return `${name}<${typeArgs}>`;
|
||||
}
|
||||
default:
|
||||
// Exhaustiveness check
|
||||
const _exhaustiveCheck: never = type;
|
||||
return `Unknown type: ${JSON.stringify(type)}`;
|
||||
}
|
||||
}
|
||||
|
||||
export function printType(type: Type): string {
|
||||
switch (type.kind) {
|
||||
case 'Concrete':
|
||||
return printConcrete(type.type);
|
||||
case 'Variable':
|
||||
return `$${type.id}`;
|
||||
default:
|
||||
// Exhaustiveness check
|
||||
const _exhaustiveCheck: never = type;
|
||||
return `Unknown type: ${JSON.stringify(type)}`;
|
||||
}
|
||||
}
|
||||
|
||||
export class TypeEnv {
|
||||
#nextTypeParameterId: number = 0;
|
||||
#nextNominalId: number = 0;
|
||||
#nextVariableId: number = 0;
|
||||
#declarations: Map<DeclarationId, Type> = new Map();
|
||||
#types: Map<IdentifierId, Type> = new Map();
|
||||
#objects: Map<NominalId, Object> = new Map();
|
||||
#roots: Map<VariableId, ConcreteType> = new Map();
|
||||
#variables: DisjointSet<VariableId> = new DisjointSet();
|
||||
#generics: Array<[string, TypeParameter]> = [];
|
||||
|
||||
nextTypeParameterId(): TypeParameterId {
|
||||
const id = makeTypeParameterId(this.#nextTypeParameterId++);
|
||||
return id;
|
||||
}
|
||||
|
||||
nextNominalId(): NominalId {
|
||||
const id = makeNominalId(this.#nextNominalId++);
|
||||
return id;
|
||||
}
|
||||
|
||||
nextTypeVariable(): Type {
|
||||
const id = makeVariableId(this.#nextVariableId++);
|
||||
return {kind: 'Variable', id};
|
||||
}
|
||||
|
||||
declare(id: Identifier, type: Type) {
|
||||
CompilerError.invariant(id.name != null, {
|
||||
reason: 'Expected to only call declare on named identifiers',
|
||||
description: `Attempting to declare ${id.id} with name ${id.name?.value}`,
|
||||
loc: null,
|
||||
});
|
||||
this.#declarations.set(id.declarationId, type);
|
||||
this.setType(id, type);
|
||||
}
|
||||
|
||||
setType(id: Identifier, type: Type) {
|
||||
this.#types.set(id.id, type);
|
||||
}
|
||||
|
||||
getType(id: Identifier): Type {
|
||||
const ty = this.#types.get(id.id);
|
||||
CompilerError.invariant(ty != null, {
|
||||
reason: 'Expected all looked-up identifiers to have types in environment',
|
||||
description: `Missing type for ${id.id}`,
|
||||
loc: null,
|
||||
});
|
||||
return ty;
|
||||
}
|
||||
|
||||
getDeclaredType(id: Identifier): Type {
|
||||
CompilerError.invariant(id.name != null, {
|
||||
reason: 'Expected to only call getDeclaredType on named identifiers',
|
||||
description: `Attempting to get declared type of ${id.id} with name ${id.name?.value}`,
|
||||
loc: null,
|
||||
});
|
||||
const ty = this.#declarations.get(id.declarationId);
|
||||
CompilerError.invariant(ty != null, {
|
||||
reason: 'Expected all looked-up named identifiers to have declared types in environment',
|
||||
description: `Missing declared type for ${id.id}`,
|
||||
loc: null,
|
||||
});
|
||||
return ty;
|
||||
}
|
||||
|
||||
pushGeneric(name: string, generic: TypeParameter) {
|
||||
this.#generics.unshift([name, generic]);
|
||||
}
|
||||
|
||||
popGeneric(name: string) {
|
||||
for (let i = 0; i < this.#generics.length; i++) {
|
||||
if (this.#generics[i][0] === name) {
|
||||
this.#generics.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getGeneric(name: string): null | TypeParameter {
|
||||
for (const [eltName, param] of this.#generics) {
|
||||
if (name === eltName) {
|
||||
return param;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
resolve(t: Type, loc: SourceLocation): ConcreteType {
|
||||
if (t.kind === 'Concrete') {
|
||||
return t.type;
|
||||
} else {
|
||||
const root = this.#variables.find(t.id) ?? t.id;
|
||||
const resolved = this.#roots.get(root);
|
||||
if (resolved != null) {
|
||||
return resolved;
|
||||
} else {
|
||||
TypeErrors.unresolvableTypeVariable(t.id, loc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unify(a: Type, b: Type, loc: SourceLocation) {
|
||||
const errs = this.#unify(a, b);
|
||||
TypeErrors.raiseUnificationErrors(errs, loc);
|
||||
}
|
||||
|
||||
#unify(a: Type, b: Type): null | Array<UnificationError> {
|
||||
let errors: null | Array<UnificationError> = null;
|
||||
function addErrors(err: null | Array<UnificationError>) {
|
||||
if (err != null) {
|
||||
if (errors == null) {
|
||||
errors = [];
|
||||
}
|
||||
errors.push(...err);
|
||||
}
|
||||
}
|
||||
|
||||
const getPossiblyMissingRoot = (id: VariableId) => {
|
||||
const root = this.#variables.find(id);
|
||||
if (root == null) {
|
||||
this.#variables.union([id]);
|
||||
return id;
|
||||
}
|
||||
return root;
|
||||
};
|
||||
|
||||
if (a.kind === 'Variable') {
|
||||
const aRoot = getPossiblyMissingRoot(a.id);
|
||||
const aConcrete = this.#roots.get(aRoot);
|
||||
if (b.kind === 'Variable') {
|
||||
const bRoot = getPossiblyMissingRoot(b.id);
|
||||
const bConcrete = this.#roots.get(bRoot);
|
||||
this.#variables.union([aRoot, bRoot]);
|
||||
const unionRoot = this.#variables.find(aRoot);
|
||||
CompilerError.invariant(unionRoot != null, {
|
||||
reason: 'Disjoint set reports no root for variable added to set',
|
||||
loc: null,
|
||||
});
|
||||
if (aConcrete != null) {
|
||||
if (bConcrete != null) {
|
||||
addErrors(this.#unifyConcrete(aConcrete, bConcrete));
|
||||
}
|
||||
this.#roots.set(unionRoot, aConcrete);
|
||||
} else if (bConcrete != null) {
|
||||
this.#roots.set(unionRoot, bConcrete);
|
||||
}
|
||||
} else if (aConcrete != null) {
|
||||
addErrors(this.#unifyConcrete(aConcrete, b.type));
|
||||
} else {
|
||||
this.#roots.set(aRoot, b.type);
|
||||
}
|
||||
} else if (b.kind === 'Variable') {
|
||||
const bRoot = getPossiblyMissingRoot(b.id);
|
||||
const bConcrete = this.#roots.get(bRoot);
|
||||
if (bConcrete != null) {
|
||||
addErrors(this.#unifyConcrete(a.type, bConcrete));
|
||||
} else {
|
||||
this.#roots.set(bRoot, a.type);
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
#unifyConcrete(
|
||||
a: ConcreteType,
|
||||
b: ConcreteType,
|
||||
): null | Array<UnificationError> {
|
||||
|
||||
function addErrors(err: null | Array<UnificationError>, cur: null | Array<UnificationError>): null | Array<UnificationError> {
|
||||
if (err != null) {
|
||||
if (cur == null) {
|
||||
return [...err];
|
||||
}
|
||||
return [...cur, ...err];
|
||||
}
|
||||
return cur;
|
||||
}
|
||||
function addError(a: ConcreteType, b: ConcreteType, cur: null | Array<UnificationError>): null | Array<UnificationError> {
|
||||
if (cur != null) {
|
||||
return [...cur, { left: a, right: b}]
|
||||
}
|
||||
return [{left: a, right: b}];
|
||||
}
|
||||
|
||||
return this.#pairMapConcrete(a, b, (a, b) => this.#unify(a, b), addErrors, addError, null)
|
||||
}
|
||||
|
||||
checkEqual(a: Type, b: Type): boolean {
|
||||
if (a.kind === 'Variable' && b.kind === 'Variable' && this.#variables.find(a.id) === this.#variables.find(b.id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let aConcrete: ConcreteType;
|
||||
if (a.kind === 'Concrete') {
|
||||
aConcrete = a.type;
|
||||
} else {
|
||||
const root = this.#variables.find(a.id) ?? a.id;
|
||||
const concrete = this.#roots.get(root);
|
||||
if (concrete != null) {
|
||||
aConcrete = concrete;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
let bConcrete: ConcreteType;
|
||||
if (b.kind === 'Concrete') {
|
||||
bConcrete = b.type;
|
||||
} else {
|
||||
const root = this.#variables.find(b.id) ?? b.id;
|
||||
const concrete = this.#roots.get(root);
|
||||
if (concrete != null) {
|
||||
bConcrete = concrete;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return this.#pairMapConcrete(aConcrete, bConcrete, (a, b) => this.checkEqual(a, b), (child, cur) => child && cur, (_a, _b, _cur) => false, true )
|
||||
}
|
||||
|
||||
#pairMapConcrete<R>(
|
||||
a: ConcreteType,
|
||||
b: ConcreteType,
|
||||
onChild: (a: Type, b: Type) => R,
|
||||
onChildMismatch: (child: R, cur: R) => R,
|
||||
onMismatch: (
|
||||
a: ConcreteType,
|
||||
b: ConcreteType,
|
||||
cur: R,
|
||||
) => R,
|
||||
init: R,
|
||||
): R {
|
||||
let errors = init;
|
||||
|
||||
// Check if kinds match
|
||||
if (a.kind !== b.kind) {
|
||||
errors = onMismatch(a, b, errors);
|
||||
}
|
||||
|
||||
// Based on kind, check other properties
|
||||
switch (a.kind) {
|
||||
case 'Mixed':
|
||||
case 'Number':
|
||||
case 'String':
|
||||
case 'Boolean':
|
||||
case 'Void':
|
||||
// Simple types, no further checks needed
|
||||
break;
|
||||
|
||||
case 'Nullable':
|
||||
// Check the nested type
|
||||
errors = onChildMismatch(
|
||||
onChild(
|
||||
a.type,
|
||||
(b as typeof a).type,
|
||||
),
|
||||
errors,
|
||||
);
|
||||
break;
|
||||
|
||||
case 'Array':
|
||||
case 'Set':
|
||||
// Check the element type
|
||||
errors = onChildMismatch(
|
||||
onChild(
|
||||
a.element,
|
||||
(b as typeof a).element,
|
||||
),
|
||||
errors,
|
||||
);
|
||||
break;
|
||||
|
||||
case 'Map':
|
||||
// Check both key and value types
|
||||
errors = onChildMismatch(
|
||||
onChild(
|
||||
a.key,
|
||||
(b as typeof a).key,
|
||||
),
|
||||
errors,
|
||||
);
|
||||
errors = onChildMismatch(
|
||||
onChild(
|
||||
a.value,
|
||||
(b as typeof a).value,
|
||||
),
|
||||
errors,
|
||||
);
|
||||
break;
|
||||
|
||||
case 'Function': {
|
||||
const bFunc = b as typeof a;
|
||||
|
||||
// Check type parameters
|
||||
if ((a.typeParameters === null) !== (bFunc.typeParameters === null)) {
|
||||
errors = onMismatch(a, b, errors);
|
||||
}
|
||||
|
||||
if (a.typeParameters !== null && bFunc.typeParameters !== null) {
|
||||
if (a.typeParameters.length !== bFunc.typeParameters.length) {
|
||||
errors = onMismatch(a, b, errors);
|
||||
}
|
||||
|
||||
// Type parameters are just numbers, so we can compare them directly
|
||||
for (let i = 0; i < a.typeParameters.length; i++) {
|
||||
if (a.typeParameters[i] !== bFunc.typeParameters[i]) {
|
||||
errors = onMismatch(a, b, errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check parameters
|
||||
if (a.params.length !== bFunc.params.length) {
|
||||
errors = onMismatch(a, b, errors);
|
||||
}
|
||||
|
||||
for (let i = 0; i < a.params.length; i++) {
|
||||
errors = onChildMismatch(
|
||||
onChild(
|
||||
a.params[i],
|
||||
bFunc.params[i],
|
||||
),
|
||||
errors,
|
||||
);
|
||||
}
|
||||
|
||||
// Check return type
|
||||
errors = onChildMismatch(
|
||||
onChild(
|
||||
a.returnType,
|
||||
bFunc.returnType,
|
||||
),
|
||||
errors,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'Generic':
|
||||
// Check that the type parameter IDs match
|
||||
if (a.id !== (b as typeof a).id) {
|
||||
errors = onMismatch(a, b, errors);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'Nominal': {
|
||||
const bNom = b as typeof a;
|
||||
|
||||
// Check that the nominal IDs match
|
||||
if (a.id !== bNom.id) {
|
||||
errors = onMismatch(a, b, errors);
|
||||
}
|
||||
|
||||
// Check type arguments
|
||||
if ((a.typeArguments === null) !== (bNom.typeArguments === null)) {
|
||||
errors = onMismatch(a, b, errors);
|
||||
}
|
||||
|
||||
if (a.typeArguments !== null && bNom.typeArguments !== null) {
|
||||
if (a.typeArguments.length !== bNom.typeArguments.length) {
|
||||
errors = onMismatch(a, b, errors);
|
||||
}
|
||||
|
||||
for (let i = 0; i < a.typeArguments.length; i++) {
|
||||
errors = onChildMismatch(
|
||||
onChild(
|
||||
a.typeArguments[i],
|
||||
bNom.typeArguments[i],
|
||||
),
|
||||
errors,
|
||||
);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
}
|
||||
export const Types = {
|
||||
variable(env: TypeEnv): Type {
|
||||
return env.nextTypeVariable();
|
||||
},
|
||||
number(): Type {
|
||||
return {kind: 'Concrete', type: {kind: 'Number'}};
|
||||
},
|
||||
string(): Type {
|
||||
return {kind: 'Concrete', type: {kind: 'String'}};
|
||||
},
|
||||
boolean(): Type {
|
||||
return {kind: 'Concrete', type: {kind: 'Boolean'}};
|
||||
},
|
||||
void(): Type {
|
||||
return {kind: 'Concrete', type: {kind: 'Void'}};
|
||||
},
|
||||
mixed(): Type {
|
||||
return {kind: 'Concrete', type: {kind: 'Mixed'}};
|
||||
},
|
||||
todo(): Type {
|
||||
return {kind: 'Concrete', type: {kind: 'Mixed'}};
|
||||
},
|
||||
nullable(type: Type): Type {
|
||||
return {kind: 'Concrete', type: {kind: 'Nullable', type}};
|
||||
},
|
||||
array(element: Type): Type {
|
||||
return {kind: 'Concrete', type: {kind: 'Array', element}};
|
||||
},
|
||||
set(element: Type): Type {
|
||||
return {kind: 'Concrete', type: {kind: 'Set', element}};
|
||||
},
|
||||
map(key: Type, value: Type): Type {
|
||||
return {kind: 'Concrete', type: {kind: 'Map', key, value}};
|
||||
},
|
||||
function(
|
||||
typeParameters: null | Array<TypeParameter>,
|
||||
params: Array<Type>,
|
||||
returnType: Type,
|
||||
): Type {
|
||||
return {
|
||||
kind: 'Concrete',
|
||||
type: {kind: 'Function', typeParameters, params, returnType},
|
||||
};
|
||||
},
|
||||
nominal(id: NominalId, typeArguments: null | Array<Type> = null): Type {
|
||||
return {
|
||||
kind: 'Concrete',
|
||||
type: {
|
||||
kind: 'Nominal',
|
||||
id,
|
||||
typeArguments,
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user