From 570b16a22a82068c271e28f3b93534b725ebe0d6 Mon Sep 17 00:00:00 2001 From: Matt Kantor Date: Tue, 24 Dec 2024 09:02:53 -0500 Subject: [PATCH] Separate `TypeKeyPath` from `KeyPath` --- .../keyword-handlers/lookup-handler.ts | 22 +++----- src/language/parsing/syntax-tree.ts | 3 -- src/language/semantics/key-path.ts | 20 +------ src/language/semantics/prelude.ts | 6 +-- src/language/semantics/semantic-graph.ts | 52 ++++++++----------- .../semantics/type-system/type-utilities.ts | 36 ++++++++----- src/language/unparsing/pretty-plz.ts | 4 +- 7 files changed, 57 insertions(+), 86 deletions(-) diff --git a/src/language/compiling/semantics/keyword-handlers/lookup-handler.ts b/src/language/compiling/semantics/keyword-handlers/lookup-handler.ts index 6cb9366..b62e3ab 100644 --- a/src/language/compiling/semantics/keyword-handlers/lookup-handler.ts +++ b/src/language/compiling/semantics/keyword-handlers/lookup-handler.ts @@ -143,22 +143,14 @@ const lookup = ({ ), right: functionExpression => { if (functionExpression.parameter === firstPathComponent) { - if (!relativePath.every(key => typeof key === 'string')) { - return either.makeLeft({ - kind: 'invalidExpression', - message: - 'dynamically-resolved lookup query contains symbolic key', - }) - } else { - // Keep an unelaborated `@lookup` around for resolution when the `@function` is called. - return either.makeRight( - option.makeSome( - makeLookupExpression( - makeObjectNode(keyPathToMolecule(relativePath)), - ), + // Keep an unelaborated `@lookup` around for resolution when the `@function` is called. + return either.makeRight( + option.makeSome( + makeLookupExpression( + makeObjectNode(keyPathToMolecule(relativePath)), ), - ) - } + ), + ) } else { return either.makeRight(option.none) } diff --git a/src/language/parsing/syntax-tree.ts b/src/language/parsing/syntax-tree.ts index de65e5f..33a58f7 100644 --- a/src/language/parsing/syntax-tree.ts +++ b/src/language/parsing/syntax-tree.ts @@ -26,9 +26,6 @@ export const applyKeyPathToSyntaxTree = ( } else { if (typeof syntaxTree === 'string') { return option.none - } else if (typeof firstKey === 'symbol') { - // TODO: Treat this as an error? Or change the type of `keyPath`? - return option.none } else { const next = withPhantomData()(syntaxTree[firstKey]) if (next === undefined) { diff --git a/src/language/semantics/key-path.ts b/src/language/semantics/key-path.ts index 3ed7e33..f2d9cb9 100644 --- a/src/language/semantics/key-path.ts +++ b/src/language/semantics/key-path.ts @@ -3,19 +3,7 @@ import type { Atom, Molecule } from '../parsing.js' import { unparse } from '../unparsing.js' import { prettyPlz } from '../unparsing/pretty-plz.js' -export const functionParameter = Symbol('functionParameter') -export const functionReturn = Symbol('functionReturn') -export const typeParameterAssignableToConstraint = Symbol( - 'typeParameterAssignableToConstraint', -) -export type KeyPath = readonly ( - | Atom - // These symbol keys are somewhat "internal" at the moment. If they end up not being expressible - // in the surface syntax then `KeyPath` should be split into two separate types. - | typeof functionParameter - | typeof functionReturn - | typeof typeParameterAssignableToConstraint -)[] +export type KeyPath = readonly Atom[] export const stringifyKeyPathForEndUser = (keyPath: KeyPath): string => either.match( @@ -28,8 +16,4 @@ export const stringifyKeyPathForEndUser = (keyPath: KeyPath): string => ) export const keyPathToMolecule = (keyPath: KeyPath): Molecule => - Object.fromEntries( - keyPath.flatMap((key, index) => - typeof key === 'symbol' ? [] : [[index, key]], - ), - ) + Object.fromEntries(keyPath.flatMap((key, index) => [[index, key]])) diff --git a/src/language/semantics/prelude.ts b/src/language/semantics/prelude.ts index 13e1f60..ba016ea 100644 --- a/src/language/semantics/prelude.ts +++ b/src/language/semantics/prelude.ts @@ -2,7 +2,7 @@ import { either, option, type Either } from '../../adts.js' import type { DependencyUnavailable, Panic } from '../errors.js' import type { Atom } from '../parsing.js' import { isFunctionNode, makeFunctionNode } from './function-node.js' -import { keyPathToMolecule } from './key-path.js' +import { keyPathToMolecule, type KeyPath } from './key-path.js' import { isObjectNode, lookupPropertyOfObjectNode, @@ -44,7 +44,7 @@ const handleUnavailableDependencies = } const serializePartiallyAppliedFunction = - (keyPath: readonly string[], argument: SemanticGraph) => () => + (keyPath: KeyPath, argument: SemanticGraph) => () => either.makeRight( makeUnelaboratedObjectNode({ 0: '@apply', @@ -54,7 +54,7 @@ const serializePartiallyAppliedFunction = ) const preludeFunction = ( - keyPath: readonly string[], + keyPath: KeyPath, signature: FunctionType['signature'], f: ( value: SemanticGraph, diff --git a/src/language/semantics/semantic-graph.ts b/src/language/semantics/semantic-graph.ts index 665fccb..91ed07b 100644 --- a/src/language/semantics/semantic-graph.ts +++ b/src/language/semantics/semantic-graph.ts @@ -35,18 +35,14 @@ export const applyKeyPathToSemanticGraph = ( atom: _ => option.none, function: _ => option.none, object: graph => { - if (typeof firstKey === 'symbol') { + const next = graph[firstKey] + if (next === undefined) { return option.none } else { - const next = graph[firstKey] - if (next === undefined) { - return option.none - } else { - return applyKeyPathToSemanticGraph( - isSemanticGraph(next) ? next : syntaxTreeToSemanticGraph(next), - remainingKeyPath, - ) - } + return applyKeyPathToSemanticGraph( + isSemanticGraph(next) ? next : syntaxTreeToSemanticGraph(next), + remainingKeyPath, + ) } }, }) @@ -106,28 +102,24 @@ export const updateValueAtKeyPathInSemanticGraph = ( atom: _ => either.makeLeft(makePropertyNotFoundError(keyPath)), function: _ => either.makeLeft(makePropertyNotFoundError(keyPath)), object: node => { - if (typeof firstKey === 'symbol') { + const next = node[firstKey] + if (next === undefined) { return either.makeLeft(makePropertyNotFoundError(keyPath)) } else { - const next = node[firstKey] - if (next === undefined) { - return either.makeLeft(makePropertyNotFoundError(keyPath)) - } else { - return either.map( - updateValueAtKeyPathInSemanticGraph( - isSemanticGraph(next) ? next : syntaxTreeToSemanticGraph(next), - remainingKeyPath, - operation, - ), - updatedNode => - (isUnelaborated(node) - ? makeUnelaboratedObjectNode - : makeObjectNode)({ - ...node, - [firstKey]: updatedNode, - }), - ) - } + return either.map( + updateValueAtKeyPathInSemanticGraph( + isSemanticGraph(next) ? next : syntaxTreeToSemanticGraph(next), + remainingKeyPath, + operation, + ), + updatedNode => + (isUnelaborated(node) + ? makeUnelaboratedObjectNode + : makeObjectNode)({ + ...node, + [firstKey]: updatedNode, + }), + ) } }, }) diff --git a/src/language/semantics/type-system/type-utilities.ts b/src/language/semantics/type-system/type-utilities.ts index c9f8d0d..026098b 100644 --- a/src/language/semantics/type-system/type-utilities.ts +++ b/src/language/semantics/type-system/type-utilities.ts @@ -1,10 +1,5 @@ import type { Writable } from '../../../utility-types.js' -import { - functionParameter, - functionReturn, - typeParameterAssignableToConstraint, - type KeyPath, -} from '../key-path.js' +import type { Atom } from '../../parsing.js' import { types } from '../type-system.js' import { simplifyUnionType } from './subtyping.js' import { @@ -19,6 +14,19 @@ import { type UnionType, } from './type-formats.js' +const functionParameter = Symbol('functionParameter') +const functionReturn = Symbol('functionReturn') +const typeParameterAssignableToConstraint = Symbol( + 'typeParameterAssignableToConstraint', +) + +export type TypeKeyPath = readonly ( + | Atom + | typeof functionParameter + | typeof functionReturn + | typeof typeParameterAssignableToConstraint +)[] + type StringifiedKeyPath = string // this could be branded if that seems useful type UnionOfTypeParameters = Omit & { readonly members: ReadonlySet @@ -26,7 +34,7 @@ type UnionOfTypeParameters = Omit & { export type TypeParametersByKeyPath = Map< StringifiedKeyPath, { - readonly keyPath: KeyPath + readonly keyPath: TypeKeyPath readonly typeParameters: UnionOfTypeParameters } > @@ -36,7 +44,7 @@ export const containedTypeParameters = (type: Type): TypeParametersByKeyPath => const containedTypeParametersImplementation = ( type: Type, - root: KeyPath, + root: TypeKeyPath, ): TypeParametersByKeyPath => { // Avoid infinite recursion when we hit the top type. if (type === types.something) { @@ -96,14 +104,14 @@ const containedTypeParametersImplementation = ( export const findKeyPathsToTypeParameter = ( type: Type, typeParameterToFind: TypeParameter, -): Set => +): Set => findKeyPathsToTypeParameterImplementation(type, typeParameterToFind, []) const findKeyPathsToTypeParameterImplementation = ( type: Type, typeParameterToFind: TypeParameter, - root: KeyPath, -): Set => { + root: TypeKeyPath, +): Set => { // Avoid infinite recursion when we hit the top type. if (type === types.something) { return new Set() @@ -148,7 +156,7 @@ const findKeyPathsToTypeParameterImplementation = ( union: ({ members }) => [...members] .map( - (member): Set => + (member): Set => typeof member === 'string' ? new Set() : findKeyPathsToTypeParameterImplementation( @@ -253,7 +261,7 @@ export const supplyTypeArgument = ( */ export const updateTypeAtKeyPathIfValid = ( type: Type, - keyPath: KeyPath, + keyPath: TypeKeyPath, // TODO: `operation` should be able to update `Atom`s operation: (typeAtKeyPath: Exclude) => Type, ): Type => { @@ -400,7 +408,7 @@ const mergeTypeParametersByKeyPath = ( // The string format is not meant for human consumption. The only guarantee is that every distinct // key path produces a unique string. -const stringifyKeyPath = (keyPath: KeyPath): string => +const stringifyKeyPath = (keyPath: TypeKeyPath): string => keyPath.reduce((stringifiedKeyPath: string, key) => { const stringifiedKey = typeof key === 'symbol' ? key.description : JSON.stringify(key) diff --git a/src/language/unparsing/pretty-plz.ts b/src/language/unparsing/pretty-plz.ts index efadb8c..99de60b 100644 --- a/src/language/unparsing/pretty-plz.ts +++ b/src/language/unparsing/pretty-plz.ts @@ -85,9 +85,7 @@ const sugaredLookup = (keyPathAsNode: Molecule | SemanticGraph) => { if ( keyPath !== 'invalid' && Object.keys(keyPathAsNode).length === keyPath.length && - keyPath.every( - key => typeof key === 'string' && !either.isLeft(unquotedAtomParser(key)), - ) + keyPath.every(key => !either.isLeft(unquotedAtomParser(key))) ) { return either.makeRight(kleur.cyan(colon.concat(keyPath.join(dot)))) } else {