UNPKG

@helios-lang/compiler

Version:

Helios is a Domain Specific Language that compiles to Plutus-Core (i.e. Cardano on-chain validator scripts). Helios is a non-Haskell alternative to Plutus. With this library you can compile Helios scripts and build Cardano transactions, all you need to bu

2,236 lines (1,948 loc) 44.6 kB
import { makeTypeError } from "@helios-lang/compiler-utils" import { expectDefined, isDefined } from "@helios-lang/type-utils" /** * @import { Site, Word } from "@helios-lang/compiler-utils" * @import { TypeSchema } from "@helios-lang/type-utils" * @import { TypeCheckContext } from "../index.js" */ /** * @typedef {(argType: Type, targetType: Type) => (Type | undefined)} ViableCasts */ /** * @typedef {(type: Type) => (Type[] | undefined)} ExpandTupleCallback */ /** * @type {ExpandTupleCallback | undefined} */ var expandTupleType /** * @param {ExpandTupleCallback} callback */ export function registerExpandTupleType(callback) { expandTupleType = callback } /** * @typedef {(type: Type) => DataType} MakeListCallback */ /** * @type {MakeListCallback | undefined} */ export var makeListType /** * @param {MakeListCallback} callback */ export function registerMakeListType(callback) { makeListType = callback } /** * @typedef {(keyType: Type, valueType: Type) => DataType} MakeMapCallback */ /** * @type {MakeMapCallback | undefined} */ export var makeMapType /** * @param {MakeMapCallback} callback */ export function registerMakeMapType(callback) { makeMapType = callback } /** * @typedef {{ * name: string * typeClass: TypeClass * }} ParameterI */ /** * @typedef {Map<ParameterI, Type>} InferenceMap */ /** * @typedef {Named & Type & { * asDataType: DataType * fieldNames: string[] * toSchema(parents?: Set<string>): TypeSchema * ready: boolean * }} DataType */ /** * @typedef {DataType & { * asEnumMemberType: EnumMemberType * constrIndex: number * parentType: DataType * }} EnumMemberType */ /** * EvalEntities assert themselves * @typedef {{ * asDataType: (null | DataType) * asEnumMemberType: (null | EnumMemberType) * asFunc: (null | Func) * asInstance: (null | Instance) * asNamed: (null | Named) * asNamespace: (null | Namespace) * asParametric: (null | Parametric) * asType: (null | Type) * asTyped: (null | Typed) * asTypeClass: (null | TypeClass) * toString(): string * }} EvalEntity */ /** * @typedef {Typed & { * asFunc: Func * funcType: FuncType * call(ctx: TypeCheckContext, site: Site, args: Typed[], namedArgs?: {[name: string]: Typed}, viableCasts?: ViableCasts): Typed * }} Func */ /** * @typedef {Typed & { * asInstance: Instance * fieldNames: string[] * instanceMembers: InstanceMembers * }} Instance */ /** * @typedef {EvalEntity & { * asNamed: Named * name: string * path: string * }} Named */ /** * @typedef {EvalEntity & { * asNamespace: Namespace * namespaceMembers: NamespaceMembers * }} Namespace */ /** * @typedef {EvalEntity & { * asParametric: Parametric * typeClasses: TypeClass[] * apply(ctx: TypeCheckContext, types: Type[], site?: Site): EvalEntity | undefined * inferCall(ctx: TypeCheckContext, site: Site, args: Typed[], namedArgs?: {[name: string]: Typed}, paramTypes?: Type[]): Func | undefined * infer(ctx: TypeCheckContext, site: Site, map: InferenceMap): Parametric | undefined * }} Parametric */ /** * @typedef {EvalEntity & { * asType: Type * instanceMembers: InstanceMembers * typeMembers: TypeMembers * isBaseOf(type: Type): boolean * infer(ctx: TypeCheckContext, site: Site, map: InferenceMap, type: null | Type): Type * toTyped(): Typed * isParametric(): boolean * }} Type */ /** * @typedef {EvalEntity & { * asTyped: Typed * type: Type * }} Typed */ /** * @typedef {EvalEntity & { * asTypeClass: TypeClass * genInstanceMembers(impl: Type): TypeClassMembers * genTypeMembers(impl: Type): TypeClassMembers * isImplementedBy(type: Type): boolean * toType(name: string, path: string, parameter?: null | ParameterI): Type * }} TypeClass */ /** * @typedef {{[name: string]: (Parametric | Type)}} InstanceMembers */ /** * @typedef {{[name: string]: EvalEntity}} NamespaceMembers */ /** * @typedef {{[name: string]: (Parametric | Type | Typed)}} TypeMembers */ /** * @typedef {{[name: string]: Type}} TypeClassMembers */ /** * @param {TypeCheckContext} ctx * @param {Parametric} parametric * @param {Type[]} types * @returns {DataType} */ export function applyTypes(ctx, parametric, ...types) { return expectDefined( (parametric.apply(ctx, types) ?? new AllType()).asDataType ) } export class Common { constructor() {} /** * @returns {boolean} */ isParametric() { return false } /** * @param {Typed} i * @param {Type} t * @returns {boolean} */ static instanceOf(i, t) { return t.isBaseOf(i.type) } /** * Compares two types. Throws an error if neither is a Type. * @example * Common.typesEq(IntType, IntType) == true * @param {Type} a * @param {Type} b * @returns {boolean} */ static typesEq(a, b) { return a.isBaseOf(b) && b.isBaseOf(a) } /** * @param {Type} type */ static isEnum(type) { return Object.values(type.typeMembers).some((v) => v.asEnumMemberType) } /** * @param {Type} type */ static countEnumMembers(type) { return Object.values(type.typeMembers).reduce( (prev, v) => (v.asEnumMemberType ? prev + 1 : prev), 0 ) } /** * @param {TypeClass} tc * @returns {string[]} */ static typeClassMembers(tc) { const dummy = tc.toType("", "") const typeMemberNames = Object.keys(tc.genTypeMembers(dummy)).sort() const instanceMemberNames = Object.keys( tc.genInstanceMembers(dummy) ).sort() return typeMemberNames.concat(instanceMemberNames) } /** * @param {Type} type * @param {TypeClass} tc * @returns {boolean} */ static typeImplements(type, tc) { if (type instanceof AllType || type.asDataType?.ready === false) { return true } const typeMembers = tc.genTypeMembers(type) for (let k in typeMembers) { const check = type.typeMembers[k]?.asType if ((check && !typeMembers[k].asType?.isBaseOf(check)) || !check) { return false } } const instanceMembers = tc.genInstanceMembers(type) for (let k in instanceMembers) { const check = type.instanceMembers[k]?.asType if ( (check && !instanceMembers[k].asType?.isBaseOf(check)) || !check ) { return false } } return true } /** * @type {null | DataType} */ get asDataType() { return null } /** * @type {null | EnumMemberType} */ get asEnumMemberType() { return null } /** * @type {null | Func} */ get asFunc() { return null } /** * @type {null | Instance} */ get asInstance() { return null } /** * @type {null | Named} */ get asNamed() { return null } /** * @type {null | Namespace} */ get asNamespace() { return null } /** * @type {null | Parametric} */ get asParametric() { return null } /** * @type {null | Type} */ get asType() { return null } /** * @type {null | Typed} */ get asTyped() { return this.asInstance ?? this.asFunc } /** * @type {null | TypeClass} */ get asTypeClass() { return null } /** * @type {boolean} */ get ready() { return true } /** * @returns {string} */ toString() { throw new Error("not yet implemented") } } /** * Used to represent all possible types whenever a TypeExpr throws an error (so type evaluation can continue in order to collect all type errors at once) * @implements {DataType} */ export class AllType extends Common { constructor() { super() } /** * @type {DataType} */ get asDataType() { return this } /** * @type {Named} */ get asNamed() { return this } /** * @type {Type} */ get asType() { return this } /** * @type {string[]} */ get fieldNames() { return [] } /** * @type {InstanceMembers} */ get instanceMembers() { return {} } /** * @type {string} */ get name() { return "" } /** * @type {string} */ get path() { return "" } /** * @type {Type} */ get type() { return this } /** * @returns {Typed} */ get asTyped() { return new DataEntity(this) } /** * @type {TypeMembers} */ get typeMembers() { return {} } /** * @returns {TypeSchema} */ toSchema() { return { kind: "internal", name: "Data" } } /** * @param {TypeCheckContext} ctx * @param {Site} site * @param {InferenceMap} map * @param {null | Type} type * @returns {Type} */ infer(ctx, site, map, type) { return this } /** * @param {Type} other * @returns {boolean} */ isBaseOf(other) { return true } /** * @returns {Typed} */ toTyped() { return new DataEntity(this) } /** * @returns {string} */ toString() { return "All" } } /** * Untyped expressions result in AnyType * @implements {DataType} */ export class AnyType extends Common { constructor() { super() } get fieldNames() { return [] } /** * @type {string} */ get name() { return "Any" } /** * @type {string} */ get path() { return "" } /** * @type {Type} */ get asType() { return this } /** * @type {Named} */ get asNamed() { return this } /** * @type {DataType} */ get asDataType() { return this } /** * @type {InstanceMembers} */ get instanceMembers() { return {} } /** * @type {TypeMembers} */ get typeMembers() { return {} } /** * @returns {TypeSchema} */ toSchema() { return { kind: "internal", name: "Any" } } /** * @param {TypeCheckContext} ctx * @param {Site} site * @param {InferenceMap} map * @param {null | Type} type * @returns {Type} */ infer(ctx, site, map, type) { return this } /** * @param {Type} other * @returns {boolean} */ isBaseOf(other) { return true } /** * @returns {Typed} */ toTyped() { throw new Error("can't be turned into a type") } /** * @returns {string} */ toString() { return "Any" } } /** * @internal */ export class ArgType { /** * @private * @readonly * @type {Word | undefined} */ _name /** * @private * @readonly * @type {Type} */ _type /** * @private * @readonly * @type {boolean} */ _optional /** * @param {Word | undefined} name * @param {Type} type * @param {boolean} optional */ constructor(name, type, optional = false) { this._name = name this._type = type this._optional = optional } /** * @type {string} */ get name() { if (!this._name) { return "" } else { return this._name.toString() } } /** * @type {Type} */ get type() { return this._type } /** * @internal * @param {TypeCheckContext} ctx * @param {Site} site * @param {InferenceMap} map * @param {null | Type} type * @returns {ArgType} */ infer(ctx, site, map, type) { return new ArgType( this._name, this._type.infer(ctx, site, map, type), this._optional ) } /** * @param {ArgType} other * @returns {boolean} */ isBaseOf(other) { // if this arg has a default value, the other arg must also have a default value if (this._optional && !other._optional) { return false } // if this is named, the other must be named as well if (this._name != null) { return this._name.toString() == (other._name?.toString() ?? "") } if (!other._type.isBaseOf(this._type)) { // note the reversal of the check return false } return true } /** * @returns {boolean} */ isNamed() { return isDefined(this._name) } /** * @returns {boolean} */ isOptional() { return this._optional } /** * @returns {string} */ toString() { return [ this._name != null ? `${this._name.toString()}: ` : "", this._optional ? "?" : "", this._type.toString() ].join("") } } /** * @typedef {Type & { * origArgTypes: ArgType[] * argTypes: Type[] * instanceMembers: InstanceMembers * nArgs: number * nNonOptArgs: number * }} FuncTypeI */ /** * Function type with arg types and a return type * @implements {FuncTypeI} */ export class FuncType extends Common { /** * @readonly * @type {ArgType[]} */ origArgTypes /** * @private * @readonly * @type {Type} */ _retType /** * @param {Type[] | ArgType[]} argTypes * @param {Type} retType */ constructor(argTypes, retType) { super() this.origArgTypes = argTypes.map((at) => at instanceof ArgType ? at : new ArgType(undefined, at) ) this._retType = retType } /** * @type {Type[]} */ get argTypes() { return this.origArgTypes.slice().map((at) => at.type) } /** * @type {InstanceMembers} */ get instanceMembers() { return {} } /** * @type {number} */ get nArgs() { return this.origArgTypes.length } /** * @type {number} */ get nNonOptArgs() { return this.origArgTypes.filter((at) => !at.isOptional()).length } /** * @type {number} */ get nOptArgs() { return this.origArgTypes.filter((at) => at.isOptional()).length } /** * @type {Type} */ get retType() { return this._retType } /** * @type {TypeMembers} */ get typeMembers() { return {} } /** * @type {Type} */ get asType() { return this } /** * Expand tuples in posArgs, if that matches argTypes better * @param {Typed[]} posArgs * @returns {Typed[]} */ expandTuplesInPosArgs(posArgs) { posArgs = posArgs.slice() let arg = posArgs.shift() /** * @type {Typed[]} */ let result = [] let i = 0 while (arg) { if ( i < this.origArgTypes.length && Common.instanceOf(arg, this.origArgTypes[i].type) ) { result.push(arg) i++ } else { if (!expandTupleType) { throw new Error("unexpected") } const tupleItemTypes = expandTupleType(arg.type) if ( tupleItemTypes && tupleItemTypes.every( (tit, j) => i + j < this.origArgTypes.length && Common.instanceOf( tit.toTyped(), this.origArgTypes[i + j].type ) ) ) { result = result.concat( tupleItemTypes.map((tit) => tit.toTyped()) ) i += tupleItemTypes.length } else { // mismatched type, but don't throw error here because better error will be thrown later result.push(arg) i++ } } arg = posArgs.shift() } return result } /** * Checks if arg types are valid. * Throws errors if not valid. Returns the return type if valid. * posArgs and namedArgs are mutated if implicit casting is viable * @param {TypeCheckContext} ctx * @param {Site} site * @param {Typed[]} origPosArgs - pos args with tuples, expanded internally * @param {{[name: string]: Typed}} namedArgs * @param {((argType: Type, targetType: Type) => (Type | undefined)) | undefined} viableCasts * @returns {Type} */ checkCall(ctx, site, origPosArgs, namedArgs = {}, viableCasts = undefined) { const posArgs = this.expandTuplesInPosArgs(origPosArgs) if (posArgs.length < this.nNonOptArgs) { // check if each nonOptArg is covered by the named args for (let i = 0; i < this.nNonOptArgs; i++) { if (!this.origArgTypes[i].isNamed()) { ctx.errors.type( site, `expected at least ${this.origArgTypes.filter((at) => !at.isNamed()).length} positional arg(s), got ${posArgs.length} positional arg(s)` ) } else if (!(this.origArgTypes[i].name in namedArgs)) { ctx.errors.type( site, `expected at least ${this.nNonOptArgs} arg(s), missing '${this.origArgTypes[i].name}'` ) } } } else if (posArgs.length > this.origArgTypes.length) { ctx.errors.type( site, `expected at most ${this.origArgTypes.length} arg(s), got ${posArgs.length} arg(s)` ) } for (let i = 0; i < posArgs.length; i++) { const posArg = posArgs[i] const origIndex = origPosArgs.findIndex( (origPosArg) => origPosArg == posArg ) const expectedArgType = this.origArgTypes[i].type if (!Common.instanceOf(posArg, expectedArgType)) { // only apply these casts if not in tuples const altType = origIndex != -1 && viableCasts ? viableCasts(posArg.type, expectedArgType) : undefined if (altType) { origPosArgs[origIndex] = altType.toTyped() } else { ctx.errors.type( site, `expected '${expectedArgType.toString()}' for arg ${i + 1}, got '${posArg.type.toString()}'` ) } } } for (let key in namedArgs) { const i = this.origArgTypes.findIndex((at) => at.name == key) if (i == -1) { ctx.errors.type( site, `arg named ${key} not found in function type ${this.toString()}` ) } if (i < posArgs.length) { ctx.errors.type( site, `named arg '${key}' already covered by positional arg ${i + 1}` ) } const thisArg = this.origArgTypes[i] const namedArg = namedArgs[key] if (!Common.instanceOf(namedArg, thisArg.type)) { const altType = viableCasts ? viableCasts(namedArg.type, thisArg.type) : undefined if (altType) { // mutate the namedArgs object namedArgs[key] = altType.toTyped() } else { ctx.errors.type( site, `expected '${thisArg.type.toString()}' for arg '${key}', got '${namedArg.toString()}'` ) } } } return this._retType } /** * @internal * @param {TypeCheckContext} ctx * @param {Site} site * @param {InferenceMap} map * @param {null | Type} type * @returns {Type} */ infer(ctx, site, map, type) { if (!type) { return new FuncType( this.origArgTypes.map((at) => at.infer(ctx, site, map, null)), this._retType.infer(ctx, site, map, null) ) } else if (type instanceof FuncType) { if (type.argTypes.length == this.origArgTypes.length) { return new FuncType( this.origArgTypes.map((at, i) => at.infer(ctx, site, map, type.argTypes[i]) ), this._retType.infer(ctx, site, map, type.retType) ) } } ctx.errors.type(site, `unable to infer type of ${this.toString()}`) return new AllType() } /** * @internal * @param {TypeCheckContext} ctx * @param {Site} site * @param {InferenceMap} map * @param {Type[]} argTypes * @returns {FuncType} */ inferArgs(ctx, site, map, argTypes) { if (argTypes.length != this.argTypes.length) { ctx.errors.type( site, `expected ${this.argTypes.length} arg(s), got ${argTypes.length}` ) } return new FuncType( this.origArgTypes.map((at, i) => at.infer(ctx, site, map, argTypes[i]) ), this._retType.infer(ctx, site, map, null) ) } /** * Checks if any of 'this' argTypes or retType is same as Type. * Only if this checks return true is the association allowed. * @param {Site} site * @param {Type} type * @returns {boolean} */ isAssociated(site, type) { for (let arg of this.origArgTypes) { if (Common.typesEq(arg.type, type)) { return true } } if (Common.typesEq(type, this._retType)) { return true } return false } /** * Checks if 'this' is a base type of another FuncType. * The number of args needs to be the same. * Each argType of the FuncType we are checking against needs to be the same or less specific (i.e. isBaseOf(this._argTypes[i])) * The retType of 'this' needs to be the same or more specific * @param {Type} other * @returns {boolean} */ isBaseOf(other) { if (other instanceof FuncType) { if (this.nNonOptArgs != other.nNonOptArgs) { return false } else { for (let i = 0; i < this.nNonOptArgs; i++) { if (!this.origArgTypes[i].isBaseOf(other.origArgTypes[i])) { return false } } if (!this._retType.isBaseOf(other._retType)) { return false } return true } } else { return false } } /** * Checks if the type of the first arg is the same as 'type' * Also returns false if there are no args. * For a method to be a valid instance member its first argument must also be named 'self', but that is checked elsewhere * @param {Site} _site * @param {Type} type * @returns {boolean} */ isMaybeMethod(_site, type) { if (this.origArgTypes.length > 0) { return Common.typesEq(this.origArgTypes[0].type, type) } else { return false } } /** * @returns {string} */ toString() { return `(${this.origArgTypes.map((a) => a.toString()).join(", ")}) -> ${this._retType.toString()}` } /** * Throws an error if name isn't found * @param {TypeCheckContext} ctx * @param {Site} site * @param {string} name * @returns {number} */ getNamedIndex(ctx, site, name) { const i = this.origArgTypes.findIndex((at) => at.name == name) if (i == -1) { ctx.errors.type(site, `arg name ${name} not found`) return 0 } else { return i } } /** * @returns {Typed} */ toTyped() { return new FuncEntity(this) } } /** * @typedef {{ * name: string * path?: string * fieldNames?: string[] * genInstanceMembers: (self: Type) => InstanceMembers * genTypeMembers: (self: Type) => TypeMembers * genTypeSchema?: (self: Type, parents: Set<string>) => TypeSchema * }} GenericTypeProps */ /** * Created by statements * @implements {DataType} */ export class GenericType extends Common { /** * @private * @readonly * @type {string} */ _name /** * @private * @readonly * @type {string} */ _path /** * @private * @readonly * @type {string[]} */ _fieldNames /** * defer until needed * @private * @readonly * @type {(self: Type) => InstanceMembers} */ _genInstanceMembers /** * defer until needed * @private * @readonly * @type {(self: Type) => TypeMembers} */ _genTypeMembers /** * @private * @type {InstanceMembers | undefined} */ _instanceMembers /** * @private * @type {TypeMembers | undefined} */ _typeMembers /** * @private * @readonly * @type {((self: Type, parents: Set<string>) => TypeSchema) | undefined} */ _genTypeSchema /** * @private * @type {number} */ _genDepth /** * @param {GenericTypeProps} props */ constructor({ name, path, fieldNames, genInstanceMembers, genTypeMembers, genTypeSchema }) { super() this._name = name this._path = path ?? `__helios__${name.toLowerCase()}` this._fieldNames = fieldNames ?? [] this._genInstanceMembers = genInstanceMembers this._genTypeMembers = genTypeMembers this._instanceMembers = undefined this._typeMembers = undefined this._genTypeSchema = genTypeSchema ?? undefined this._genDepth = 0 } /** * @type {DataType} */ get asDataType() { return this } /** * @type {Named} */ get asNamed() { return this } /** * @type {Type} */ get asType() { return this } /** * @type {string[]} */ get fieldNames() { return this._fieldNames } /** * @type {InstanceMembers} */ get instanceMembers() { if (!this._instanceMembers) { this._instanceMembers = this._genInstanceMembers(this) } return this._instanceMembers } /** * @type {string} */ get name() { return this._name } /** * @param {Set<string>} parents * @returns {TypeSchema} */ toSchema(parents = new Set()) { if (this._genTypeSchema) { return this._genTypeSchema(this, parents) } else { throw new Error(`typeSchema not available for ${this.toString()}`) } } /** * @type {string} */ get path() { return this._path } /** * @type {boolean} */ get ready() { return this._genDepth < 2 } /** * @type {TypeMembers} */ get typeMembers() { if (!this._typeMembers) { this._genDepth += 1 this._typeMembers = this._genTypeMembers(this) this._genDepth -= 1 } return this._typeMembers } /** * @param {TypeCheckContext} ctx * @param {Site} site * @param {InferenceMap} map */ applyInternal(ctx, site, map) { return { name: this._name, path: this._path, fieldNames: this._fieldNames, genInstanceMembers: (self) => { /** * @type {InstanceMembers} */ const instanceMembers = {} const oldInstanceMembers = this._genInstanceMembers(self) for (let k in oldInstanceMembers) { const v = oldInstanceMembers[k] if (v.asParametric) { instanceMembers[k] = v.asParametric.infer(ctx, site, map) ?? new AllType() } else if (v.asType) { instanceMembers[k] = v.asType.infer( ctx, site, map, null ) } else { throw new Error("unhandled") } } return instanceMembers }, genTypeMembers: (self) => { /** * @type {TypeMembers} */ const typeMembers = {} const oldTypeMembers = this._genTypeMembers(self) for (let k in oldTypeMembers) { const v = oldTypeMembers[k] if (v.asParametric) { typeMembers[k] = v.asParametric.infer(ctx, site, map) ?? new AllType() } else if (v.asTyped) { typeMembers[k] = v.asTyped.type .infer(ctx, site, map, null) .toTyped() } else if (v.asType) { typeMembers[k] = v.asType.infer(ctx, site, map, null) } else { throw new Error("unhandled") } } return typeMembers } } } /** * @param {TypeCheckContext} ctx * @param {Site} site * @param {InferenceMap} map * @param {null | Type} type * @returns {Type} */ infer(ctx, site, map, type) { return this } /** * @param {string} name * @param {string} path * @returns {GenericType} */ changeNameAndPath(name, path) { return new GenericType({ name: name, path: path, fieldNames: this._fieldNames, genInstanceMembers: this._genInstanceMembers, genTypeMembers: this._genTypeMembers }) } /** * @param {Type} other * @returns {boolean} */ isBaseOf(other) { if (other.asEnumMemberType) { return this.isBaseOf(other.asEnumMemberType.parentType) } else if (other.asNamed) { return other.asNamed.path == this._path } else { return false } } /** * @returns {string} */ toString() { return this.name } /** * @returns {Typed} */ toTyped() { return new DataEntity(this) } } /** * @typedef {{ * name: string, * path?: string, * constrIndex: number, * parentType: DataType, * fieldNames?: string[], * genInstanceMembers: (self: Type) => InstanceMembers, * genTypeMembers?: (self: Type) => TypeMembers * genTypeSchema: (self: Type, parents: Set<string>) => TypeSchema * }} GenericEnumMemberTypeProps */ /** * Created by statements * @implements {EnumMemberType} * @extends {GenericType} */ export class GenericEnumMemberType extends GenericType { /** * @private * @readonly * @type {number} */ _constrIndex /** * @private * @readonly * @type {DataType} */ _parentType /** * @param {GenericEnumMemberTypeProps} props */ constructor({ name, path, constrIndex, parentType, fieldNames, genInstanceMembers, genTypeMembers, genTypeSchema }) { super({ name, path: path ?? `${parentType.path}__${name.toLowerCase()}`, fieldNames, genInstanceMembers, genTypeMembers: genTypeMembers ?? ((self) => ({})), genTypeSchema: genTypeSchema }) this._constrIndex = constrIndex this._parentType = parentType } /** * @type {number} */ get constrIndex() { return this._constrIndex } /** * @type {DataType} */ get parentType() { return this._parentType } /** * @type {EnumMemberType} */ get asEnumMemberType() { return this } /** * @param {TypeCheckContext} ctx * @param {Site} site * @param {InferenceMap} map * @param {null | Type} type * @returns {Type} */ infer(ctx, site, map, type) { return this } /** * @param {Type} other * @returns {boolean} */ isBaseOf(other) { if (other instanceof GenericEnumMemberType) { return other.path == this.path } else { return false } } /** * @returns {string} */ toString() { return `${this._parentType.toString()}::${this.name}` } } /** * Type of return-value of functions that don't return anything (eg. assert, print, error) * @internal * @implements {Type} */ export class VoidType extends Common { constructor() { super() } /** * @type {InstanceMembers} */ get instanceMembers() { return {} } /** * @type {TypeMembers} */ get typeMembers() { return {} } /** * @type {Type} */ get asType() { return this } /** * @param {TypeCheckContext} ctx * @param {Site} site * @param {InferenceMap} map * @param {null | Type} type * @returns {Type} */ infer(ctx, site, map, type) { return this } /** * @param {Type} type * @returns {boolean} */ isBaseOf(type) { return type instanceof VoidType } /** * @returns {string} */ toString() { return "()" } /** * @returns {Typed} */ toTyped() { return new VoidEntity() } } /** * A regular non-Func Instance. DataValues can always be compared, serialized, used in containers. * @internal * @implements {Instance} */ export class DataEntity extends Common { /** * @private * @readonly * @type {DataType} */ _type /** * @param {DataType} type */ constructor(type) { super() if (type instanceof FuncType) { throw new Error("unexpected") } this._type = type } /** * @type {string[]} */ get fieldNames() { return this._type.fieldNames } /** * @type {InstanceMembers} */ get instanceMembers() { return this._type.instanceMembers } /** * @type {Type} */ get type() { return this._type } /** * @type {Instance} */ get asInstance() { return this } /** * @type {Typed} */ get asTyped() { return this } /** * @returns {string} */ toString() { return this._type.toString() } } /** * Type of special case of no-return value where execution can't continue. * @internal * @implements {Type} */ export class ErrorType extends VoidType { /** * @type {Type} */ get asType() { return this } /** * @param {Type} type * @returns {boolean} */ isBaseOf(type) { return type instanceof ErrorType } /** * @returns {Typed} */ toTyped() { return new ErrorEntity() } } /** * Returned by an error() * Special case of no-return-value that indicates that execution can't proceed. * @internal */ export class ErrorEntity extends Common { constructor() { super() } /** * @type {string[]} */ get fieldNames() { return [] } /** * @type {InstanceMembers} */ get instanceMembers() { return {} } /** * @type {Type} */ get type() { return new ErrorType() } /** * @type {Instance} */ get asInstance() { return this } /** * @type {Typed} */ get asTyped() { return this } /** * @returns {string} */ toString() { return "()" } } /** * @internal * @implements {Named} */ export class NamedEntity { /** * @private * @readonly * @type {string} */ _name /** * @private * @readonly * @type {string} */ _path /** * @private * @readonly * @type {EvalEntity} */ _entity /** * @param {string} name * @param {string} path * @param {EvalEntity} entity */ constructor(name, path, entity) { this._name = name this._path = path this._entity = entity } /** * @type {null | DataType} */ get asDataType() { return this._entity.asDataType } /** * @type {null | EnumMemberType} */ get asEnumMemberType() { return this._entity.asEnumMemberType } /** * @type {null | Func} */ get asFunc() { return this._entity.asFunc } /** * @type {null | Instance} */ get asInstance() { return this._entity.asInstance } /** * @type {Named} */ get asNamed() { return this } /** * @type {null | Namespace} */ get asNamespace() { return this._entity.asNamespace } /** * @type {null | Parametric} */ get asParametric() { return this._entity.asParametric } /** * @type {null | Type} */ get asType() { return this._entity.asType } /** * @type {null | Typed} */ get asTyped() { return this._entity.asTyped } /** * @type {null | TypeClass} */ get asTypeClass() { return this._entity.asTypeClass } /** * @type {string} */ get name() { return this._name } /** * @type {string} */ get path() { return this._path } /** * @returns {string} */ toString() { return this._entity.toString() } } /** * A callable Instance. * @internal * @implements {Func} */ export class FuncEntity extends Common { /** * @private * @readonly * @type {FuncType} */ _type /** * @param {FuncType} type */ constructor(type) { super() if (!(type instanceof FuncType)) { throw new Error("unexpected") } this._type = type } /** * @type {Type} */ get type() { return this._type } /** * Returns the underlying FuncType directly. * @type {FuncType} */ get funcType() { return this._type } /** * @type {Func} */ get asFunc() { return this } /** * @type {Typed} */ get asTyped() { return this } /** * @param {TypeCheckContext} ctx * @param {Site} site * @param {Typed[]} args * @param {{[name: string]: Typed}} namedArgs * @param {ViableCasts | undefined} viableCasts * @returns {Typed} */ call(ctx, site, args, namedArgs = {}, viableCasts = undefined) { const type = this._type.checkCall( ctx, site, args, namedArgs, viableCasts ) return type.toTyped() } /** * Returns a string representing the type. * @returns {string} */ toString() { return this._type.toString() } } /** * @internal * @implements {Typed} */ export class TypedEntity extends Common { /** * @private * @readonly * @type {Type} */ _type /** * @param {Type} type */ constructor(type) { super() this._type = type } /** * @returns {Typed} */ get asTyped() { return this } /** * @type {Type} */ get type() { return this._type } } /** * Returned by functions that don't return anything (eg. assert, error, print) * @internal * @implements {Instance} */ export class VoidEntity extends Common { constructor() { super() } /** * @type {string[]} */ get fieldNames() { return [] } /** * @type {InstanceMembers} */ get instanceMembers() { return {} } /** * @type {Type} */ get type() { return new VoidType() } /** * @type {Instance} */ get asInstance() { return this } /** * @type {Typed} */ get asTyped() { return this } /** * @returns {string} */ toString() { return "()" } } /** * @implements {Instance} */ export class AnyEntity extends Common { constructor() { super() } /** * @type {string[]} */ get fieldNames() { return [] } /** * @type {InstanceMembers} */ get instanceMembers() { return {} } /** * @type {Type} */ get type() { return new AnyType() } /** * @type {Instance} */ get asInstance() { return this } /** * @type {Typed} */ get asTyped() { return this } /** * @returns {string} */ toString() { return "Any" } } /** * @implements {Namespace} */ export class ModuleNamespace extends Common { /** * @readonly * @type {string} */ name /** * @private * @readonly * @type {NamespaceMembers} */ _members /** * @param {string} name * @param {NamespaceMembers} members */ constructor(name, members) { super() this.name = name this._members = members } /** * @type {NamespaceMembers} */ get namespaceMembers() { return this._members } /** * @type {Namespace} */ get asNamespace() { return this } /** * @returns {string} */ toString() { return this.name } } /** * @implements {Named} * @implements {Namespace} */ export class NamedNamespace extends ModuleNamespace { /** * @readonly * @type {string} */ path /** * @param {string} name * @param {string} path * @param {NamespaceMembers} members */ constructor(name, path, members) { super(name, members) this.path = path } /** * @type {Named} */ get asNamed() { return this } } /** * @param {DataType} enumType * @returns {[string, EnumMemberType][]} */ export function collectEnumMembers(enumType) { return /** @type {[string, EnumMemberType][]} */ ( Array.from(Object.entries(enumType.typeMembers)).filter( ([key, variantType]) => { if (variantType.asEnumMemberType) { return enumType.isBaseOf(variantType.asEnumMemberType) } else { return false } } ) ) }