diff --git a/CHANGELOG.md b/CHANGELOG.md index 91bcef821..a46c2a919 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Dates are in `YYYY-MM-DD` format and versions are in [semantic versioning](http: - Better unused bind conflict message. - Narrowed parsing of structure refinements to avoid conflicting with spreads in lists. - Account for documented expressions in bind recurrence relations. +- More consistently concretize name types in binds. ## 0.9.34 2024-02-24 diff --git a/src/nodes/Bind.ts b/src/nodes/Bind.ts index 4027d22a2..2aa771e3d 100644 --- a/src/nodes/Bind.ts +++ b/src/nodes/Bind.ts @@ -6,8 +6,6 @@ import type Conflict from '@conflicts/Conflict'; import UnusedBind from '@conflicts/UnusedBind'; import IncompatibleType from '@conflicts/IncompatibleType'; import UnexpectedEtc from '@conflicts/UnexpectedEtc'; -import NameType from './NameType'; -import StructureType from './StructureType'; import StructureDefinition from './StructureDefinition'; import type Evaluator from '@runtime/Evaluator'; import type Step from '@runtime/Step'; @@ -50,6 +48,7 @@ import Refer from '../edit/Refer'; import UnknownType from './UnknownType'; import type Locales from '../locale/Locales'; import DocumentedExpression from './DocumentedExpression'; +import NameType from './NameType'; export default class Bind extends Expression { readonly docs?: Docs; @@ -482,10 +481,9 @@ export default class Bind extends Expression { // If the type is a name, and it refers to a structure, resolve it. // Leave any other names (namely those that refer to type variables) to be concretized by others. - if (type instanceof NameType) { - const nameType = type.getType(context); - if (nameType instanceof StructureType) return nameType; - } + type = type.nodes().some((t) => t instanceof NameType) + ? type.concretize(context) + : type; return type; } diff --git a/src/nodes/ConversionType.ts b/src/nodes/ConversionType.ts index d0a9427ec..3d512bc4d 100644 --- a/src/nodes/ConversionType.ts +++ b/src/nodes/ConversionType.ts @@ -28,7 +28,7 @@ export default class ConversionType extends Type { return new ConversionType( input, new Token(CONVERT_SYMBOL, Sym.Convert), - output + output, ); } @@ -48,7 +48,7 @@ export default class ConversionType extends Type { return new ConversionType( this.replaceChild('input', this.input, replace), this.replaceChild('convert', this.convert, replace), - this.replaceChild('output', this.output, replace) + this.replaceChild('output', this.output, replace), ) as this; } @@ -65,10 +65,17 @@ export default class ConversionType extends Type { this.input.accepts(type.input, context) && this.output instanceof Type && type.output instanceof Type && - this.output.accepts(type.output, context) + this.output.accepts(type.output, context), ); } + concretize(context: Context) { + return ConversionType.make( + this.input.concretize(context), + this.output.concretize(context), + ); + } + getBasisTypeName(): BasisTypeName { return 'conversion'; } diff --git a/src/nodes/FunctionType.ts b/src/nodes/FunctionType.ts index 07823a02f..1990cbca0 100644 --- a/src/nodes/FunctionType.ts +++ b/src/nodes/FunctionType.ts @@ -44,7 +44,7 @@ export default class FunctionType extends Type { inputs: Bind[], close: Token | undefined, output: Type, - definition?: FunctionDefinition + definition?: FunctionDefinition, ) { super(); @@ -63,7 +63,7 @@ export default class FunctionType extends Type { typeVars: TypeVariables | undefined, inputs: Bind[], output: Type, - definition?: FunctionDefinition + definition?: FunctionDefinition, ) { return new FunctionType( new Token(FUNCTION_SYMBOL, Sym.Function), @@ -72,7 +72,7 @@ export default class FunctionType extends Type { inputs, new EvalCloseToken(), output, - definition + definition, ); } @@ -92,7 +92,7 @@ export default class FunctionType extends Type { this.inputs .filter((input) => input.isRequired()) .map((input) => input.simplify(context).withoutType()), - ExpressionPlaceholder.make() + ExpressionPlaceholder.make(), ); } @@ -123,7 +123,7 @@ export default class FunctionType extends Type { this.replaceChild('open', this.open, replace), this.replaceChild('inputs', this.inputs, replace), this.replaceChild('close', this.close, replace), - this.replaceChild('output', this.output, replace) + this.replaceChild('output', this.output, replace), ) as this; } @@ -162,6 +162,16 @@ export default class FunctionType extends Type { }); } + concretize(context: Context) { + return FunctionType.make( + this.types, + this.inputs.map((i) => + i.type ? i.withType(i.type.concretize(context)) : i, + ), + this.output.concretize(context), + ); + } + simplify(context: Context) { // Simplify all of the binds return new FunctionType( @@ -171,7 +181,7 @@ export default class FunctionType extends Type { this.inputs.map((i) => i.simplify(context)), this.close, this.output.simplify(context), - this.definition + this.definition, ); } diff --git a/src/nodes/ListType.ts b/src/nodes/ListType.ts index 302a0b01f..3d38bf833 100644 --- a/src/nodes/ListType.ts +++ b/src/nodes/ListType.ts @@ -23,7 +23,7 @@ export default class ListType extends BasisType { open: Token, type: Type | undefined, close: Token | undefined, - length?: number + length?: number, ) { super(); @@ -40,14 +40,14 @@ export default class ListType extends BasisType { new Token(LIST_OPEN_SYMBOL, Sym.ListOpen), type, new Token(LIST_CLOSE_SYMBOL, Sym.ListClose), - length + length, ); } static getPossibleNodes( type: Type | undefined, node: Node, - selected: boolean + selected: boolean, ) { return [ ListType.make(), @@ -71,7 +71,7 @@ export default class ListType extends BasisType { return new ListType( this.replaceChild('open', this.open, replace), this.replaceChild('type', this.type, replace), - this.replaceChild('close', this.close, replace) + this.replaceChild('close', this.close, replace), ) as this; } @@ -87,10 +87,14 @@ export default class ListType extends BasisType { (this.type === undefined || // If the given type has no type specified, any will do type.type === undefined || - this.type.accepts(type.type, context)) + this.type.accepts(type.type, context)), ); } + concretize(context: Context): Type { + return ListType.make(this.type?.concretize(context)); + } + generalize(context: Context) { return ListType.make(this.type?.generalize(context)); } diff --git a/src/nodes/MapType.ts b/src/nodes/MapType.ts index 749a8f050..7cfc8f5a9 100644 --- a/src/nodes/MapType.ts +++ b/src/nodes/MapType.ts @@ -29,7 +29,7 @@ export default class MapType extends BasisType { key: Type | undefined, bind: Token, value: Type | undefined, - close?: Token + close?: Token, ) { super(); @@ -48,14 +48,14 @@ export default class MapType extends BasisType { key, new BindToken(), value, - new SetCloseToken() + new SetCloseToken(), ); } static getPossibleNodes( type: Type | undefined, node: Node, - selected: boolean + selected: boolean, ) { return [ MapType.make(), @@ -85,7 +85,7 @@ export default class MapType extends BasisType { this.replaceChild('key', this.key, replace), this.replaceChild('bind', this.bind, replace), this.replaceChild('value', this.value, replace), - this.replaceChild('close', this.close, replace) + this.replaceChild('close', this.close, replace), ) as this; } @@ -111,14 +111,21 @@ export default class MapType extends BasisType { (this.value === undefined || type.key === undefined || (type.value instanceof Type && - this.value.accepts(type.value, context))) + this.value.accepts(type.value, context))), + ); + } + + concretize(context: Context) { + return MapType.make( + this.key?.concretize(context), + this.value?.concretize(context), ); } generalize(context: Context) { return MapType.make( this.key?.generalize(context), - this.value?.generalize(context) + this.value?.generalize(context), ); } @@ -133,10 +140,10 @@ export default class MapType extends BasisType { this.key instanceof Type ? this.key : mapDef.types !== undefined && - mapDef.types.variables[1].hasName(name) && - this.value instanceof Type - ? this.value - : undefined; + mapDef.types.variables[1].hasName(name) && + this.value instanceof Type + ? this.value + : undefined; } getNodeLocale(locales: Locales) { diff --git a/src/nodes/NameType.ts b/src/nodes/NameType.ts index 4527e651a..4799d97c5 100644 --- a/src/nodes/NameType.ts +++ b/src/nodes/NameType.ts @@ -19,6 +19,7 @@ import { UnknownName } from '@conflicts/UnknownName'; import Emotion from '../lore/Emotion'; import Sym from './Sym'; import type Locales from '../locale/Locales'; +import StructureType from './StructureType'; export default class NameType extends Type { readonly name: Token; @@ -28,7 +29,7 @@ export default class NameType extends Type { constructor( type: Token | string, types?: TypeInputs, - definition?: Definition + definition?: Definition, ) { super(); @@ -57,7 +58,7 @@ export default class NameType extends Type { clone(replace?: Replacement) { return new NameType( this.replaceChild('name', this.name, replace), - this.replaceChild('types', this.types, replace) + this.replaceChild('types', this.types, replace), ) as this; } @@ -105,8 +106,8 @@ export default class NameType extends Type { new UnexpectedTypeInput( this, this.types.types[index], - def - ) + def, + ), ); break; } @@ -127,6 +128,12 @@ export default class NameType extends Type { return [this.getType(context)]; } + concretize(context: Context): Type { + const concrete = this.getType(context); + // If it's a structure type, return it, otherwise leave it as a type variable. + return concrete instanceof StructureType ? concrete : this; + } + resolve(context?: Context): Definition | undefined { // Find the name in the binding scope. return ( diff --git a/src/nodes/SetType.ts b/src/nodes/SetType.ts index 2c41d4cce..621628dcc 100644 --- a/src/nodes/SetType.ts +++ b/src/nodes/SetType.ts @@ -37,7 +37,7 @@ export default class SetType extends BasisType { static getPossibleNodes( type: Type | undefined, node: Node, - selected: boolean + selected: boolean, ) { return [ SetType.make(), @@ -61,7 +61,7 @@ export default class SetType extends BasisType { return new SetType( this.replaceChild('open', this.open, replace), this.replaceChild('key', this.key, replace), - this.replaceChild('close', this.close, replace) + this.replaceChild('close', this.close, replace), ) as this; } @@ -83,10 +83,14 @@ export default class SetType extends BasisType { // If it is a specific type, see if the other set's type is unspecified or compatible type.key === undefined || (type.key instanceof Type && - this.key.accepts(type.key, context))) + this.key.accepts(type.key, context))), ); } + concretize(context: Context): Type { + return SetType.make(this.key?.concretize(context)); + } + generalize(context: Context) { return SetType.make(this.key?.generalize(context)); } diff --git a/src/nodes/StreamType.ts b/src/nodes/StreamType.ts index def417410..f149976c0 100644 --- a/src/nodes/StreamType.ts +++ b/src/nodes/StreamType.ts @@ -28,7 +28,7 @@ export default class StreamType extends Type { static make(type?: Type) { return new StreamType( new Token(STREAM_SYMBOL, Sym.Stream), - type ?? new AnyType() + type ?? new AnyType(), ); } @@ -53,7 +53,7 @@ export default class StreamType extends Type { .every( (type) => type instanceof StreamType && - this.type.accepts(type.type, context) + this.type.accepts(type.type, context), ); } @@ -61,10 +61,14 @@ export default class StreamType extends Type { return 'stream'; } + concretize(context: Context) { + return StreamType.make(this.type.concretize(context)); + } + clone(replace?: Replacement) { return new StreamType( this.replaceChild('stream', this.stream, replace), - this.replaceChild('type', this.type, replace) + this.replaceChild('type', this.type, replace), ) as this; } diff --git a/src/nodes/Type.ts b/src/nodes/Type.ts index 7c40453da..b9d9b7774 100644 --- a/src/nodes/Type.ts +++ b/src/nodes/Type.ts @@ -25,18 +25,23 @@ export default abstract class Type extends Node { return this.acceptsAll( new TypeSet(type.getPossibleTypes(context), context), context, - expression + expression, ); } abstract acceptsAll( types: TypeSet, context: Context, - expression?: Expression + expression?: Expression, ): boolean; abstract getBasisTypeName(): BasisTypeName; + /** Subclasses can optionally resolve names. Mainly used to resolve name types into concrete structure types. */ + concretize(_: Context): Type { + return this; + } + /** Subclasses override to abstract away from any literal types specified inside the type. */ generalize(_: Context): Type { return this; @@ -72,7 +77,7 @@ export default abstract class Type extends Node { getConversion( context: Context, input: Type, - output: Type + output: Type, ): ConversionDefinition | undefined { return context .getBasis() @@ -87,7 +92,7 @@ export default abstract class Type extends Node { getFunction( context: Context, - name: string + name: string, ): FunctionDefinition | undefined { return context.getBasis().getFunction(this.getBasisTypeName(), name); } diff --git a/src/nodes/UnionType.ts b/src/nodes/UnionType.ts index 8c62cf0c2..2b24262ce 100644 --- a/src/nodes/UnionType.ts +++ b/src/nodes/UnionType.ts @@ -39,14 +39,14 @@ export default class UnionType extends Type { static getPossibleNodes( type: Type | undefined, node: Node, - selected: boolean + selected: boolean, ) { return [ node instanceof Type && selected ? UnionType.make(node, TypePlaceholder.make()) : UnionType.make( TypePlaceholder.make(), - TypePlaceholder.make() + TypePlaceholder.make(), ), ]; } @@ -71,7 +71,7 @@ export default class UnionType extends Type { return new UnionType( this.replaceChild('left', this.left, replace), this.replaceChild('or', this.or, replace), - this.replaceChild('right', this.right, replace) + this.replaceChild('right', this.right, replace), ) as this; } @@ -120,7 +120,7 @@ export default class UnionType extends Type { getConversion( context: Context, input: Type, - output: Type + output: Type, ): ConversionDefinition | undefined { const left = context .getBasis() @@ -128,7 +128,7 @@ export default class UnionType extends Type { this.left.getBasisTypeName(), context, input, - output + output, ); if (left !== undefined) return left; return this.right instanceof Type @@ -138,14 +138,14 @@ export default class UnionType extends Type { this.right.getBasisTypeName(), context, input, - output + output, ) : undefined; } getFunction( context: Context, - name: string + name: string, ): FunctionDefinition | undefined { const left = context .getBasis() @@ -189,8 +189,8 @@ export default class UnionType extends Type { ? first : first.filter((def1) => rest.every((definitions) => - definitions.some((def2) => def1.isEquivalentTo(def2)) - ) + definitions.some((def2) => def1.isEquivalentTo(def2)), + ), ); } @@ -244,10 +244,17 @@ export default class UnionType extends Type { ]; } + concretize(context: Context) { + return UnionType.make( + this.left.concretize(context), + this.right.concretize(context), + ); + } + generalize(context: Context): Type { // First, generalize all of the types in the union. const generalized = this.getPossibleTypes(context).map((type) => - type.generalize(context) + type.generalize(context), ); // Next, find the smallest subset of types to represent the set.