From 428e8cd29815e6fd9a2beb12e6aa84d69a4a58de Mon Sep 17 00:00:00 2001 From: Joeri Exelmans Date: Tue, 27 May 2025 12:44:57 +0200 Subject: [PATCH] better error handling --- src/component/app/configurations.ts | 2 +- src/component/expr/ExprBlock.css | 18 ++++++++- src/component/expr/ExprBlock.tsx | 9 +++-- src/component/expr/InputBlock.tsx | 3 +- src/component/other/Type.css | 3 +- src/component/other/Type.tsx | 3 +- src/eval/eval.ts | 2 +- src/eval/infer_type.ts | 60 ++++++++++++++++++----------- 8 files changed, 66 insertions(+), 34 deletions(-) diff --git a/src/component/app/configurations.ts b/src/component/app/configurations.ts index 32befcb..4d24127 100644 --- a/src/component/app/configurations.ts +++ b/src/component/app/configurations.ts @@ -75,7 +75,7 @@ export const tripleFunctionCallEditorState: ExprBlockState = { }, }; -export const biggerExample: ExprBlockState = {"kind":"let","focus":false,"inner":{"kind":"let","focus":false,"inner":{"kind":"let","focus":false,"inner":{"kind":"let","focus":false,"inner":{"kind":"input","text":"","value":{"kind":"gibberish"},"focus":false},"name":"myListInc","value":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"list.map","value":{"kind":"name"},"focus":false},"input":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"list.map","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"myList","value":{"kind":"name"},"focus":false}},"input":{"kind":"input","text":"inc","value":{"kind":"name"},"focus":false}}},"input":{"kind":"input","text":"id","value":{"kind":"name"},"focus":true}}},"name":"myList","value":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"list.push","value":{"kind":"name"},"focus":false},"input":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"list.push","value":{"kind":"name"},"focus":false},"input":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"list.push","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"list.emptyList","value":{"kind":"name"},"focus":false}},"input":{"kind":"input","text":"1","value":{"kind":"literal","type":"Int"},"focus":false}}},"input":{"kind":"input","text":"2","value":{"kind":"literal","type":"Int"},"focus":false}}},"input":{"kind":"input","text":"3","value":{"kind":"literal","type":"Int"},"focus":false}}},"name":"id","value":{"kind":"lambda","focus":false,"paramName":"x","expr":{"kind":"input","text":"x","value":{"kind":"name"},"focus":false}}},"name":"inc","value":{"kind":"lambda","focus":false,"paramName":"x","expr":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"addInt","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"x","value":{"kind":"name"},"focus":false}},"input":{"kind":"input","text":"1","value":{"kind":"literal","type":"Int"},"focus":true}}}}; +export const biggerExample: ExprBlockState = {"kind":"let","focus":false,"inner":{"kind":"let","focus":false,"inner":{"kind":"let","focus":false,"inner":{"kind":"let","focus":false,"inner":{"kind":"input","text":"myList","value":{"kind":"name"}},"name":"myListInc","value":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"list.map","value":{"kind":"name"},"focus":false},"input":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"list.map","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"myList","value":{"kind":"name"}}},"input":{"kind":"input","text":"inc","value":{"kind":"name"},"focus":false}}},"input":{"kind":"input","text":"id","value":{"kind":"name"},"focus":true}}},"name":"myList","value":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"list.push","value":{"kind":"name"},"focus":false},"input":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"list.push","value":{"kind":"name"},"focus":false},"input":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"list.push","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"list.emptyList","value":{"kind":"name"},"focus":false}},"input":{"kind":"input","text":"1","value":{"kind":"literal","type":"Int"},"focus":false}}},"input":{"kind":"input","text":"2","value":{"kind":"literal","type":"Int"},"focus":false}}},"input":{"kind":"input","text":"3","value":{"kind":"literal","type":"Int"},"focus":false}}},"name":"id","value":{"kind":"lambda","focus":false,"paramName":"x","expr":{"kind":"input","text":"x","value":{"kind":"name"},"focus":false}}},"name":"inc","value":{"kind":"lambda","focus":false,"paramName":"x","expr":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"addInt","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"x","value":{"kind":"name"},"focus":false}},"input":{"kind":"input","text":"1","value":{"kind":"literal","type":"Int"},"focus":true}}}}; export const lambda2Params: ExprBlockState = { "kind": "let", diff --git a/src/component/expr/ExprBlock.css b/src/component/expr/ExprBlock.css index e991248..81dc5b3 100644 --- a/src/component/expr/ExprBlock.css +++ b/src/component/expr/ExprBlock.css @@ -1,10 +1,26 @@ .editor { - padding: 2px;; + padding: 2px; + position: relative; } .editor.error { border: 1px solid red; display: inline-block; } + +.errorMessage { + display: none; + position: absolute; + color: darkred; + background-color: pink; + margin-top: 4px; + z-index: 10; +} + +.editor:hover > .errorMessage { + display: block; + /* z-index: 9999; */ +} + .editor.unknown { border: 1px dashed dodgerblue; display: inline-block; diff --git a/src/component/expr/ExprBlock.tsx b/src/component/expr/ExprBlock.tsx index 834d06e..f048a65 100644 --- a/src/component/expr/ExprBlock.tsx +++ b/src/component/expr/ExprBlock.tsx @@ -51,12 +51,13 @@ export function ExprBlock(props: ExprBlockProps) { const extraHandlers = Object.fromEntries(Object.entries(actions).map(([shortcut, action]) => [shortcut, (e) => { e.preventDefault(); action(); }])) + console.log(props.typeInfo.err); return {renderBlock[props.state.kind]()} - {props.typeInfo.err && -
- {props.typeInfo.err.message.split('\n')[1]} -
} + {(props.typeInfo.err !== undefined) && + (
+ {props.typeInfo.err.message} +
)}
- {/* :: */} :: } @@ -201,7 +200,7 @@ function Suggestion({ setI, j, onSelect, highlighted, suggestion: [priority, typ onMouseEnter={onMouseEnter(j)} onMouseDown={onMouseDown(j)}> ({priority}) ({kind}) {text} :: - + ; } const SuggestionMemo = memo(Suggestion); \ No newline at end of file diff --git a/src/component/other/Type.css b/src/component/other/Type.css index b2f9874..2bd3da4 100644 --- a/src/component/other/Type.css +++ b/src/component/other/Type.css @@ -74,7 +74,6 @@ .typeSignature { display: inline-block; - /* z-index: 1; */ position: relative; } @@ -91,7 +90,7 @@ color: black; font-family: var(--my-monospace-font); padding: 4px; - z-index: 1000; + z-index: 100; } .editor:hover > .typeSignature { diff --git a/src/component/other/Type.tsx b/src/component/other/Type.tsx index a1e54d8..9c4d01d 100644 --- a/src/component/other/Type.tsx +++ b/src/component/other/Type.tsx @@ -6,7 +6,8 @@ import type { TypeInfo } from "../../eval/infer_type"; export function TypeInfoBlock({typeInfo}: {typeInfo: TypeInfo}) { return -
+ +
{prettySS(typeInfo.subs)}
; } diff --git a/src/eval/eval.ts b/src/eval/eval.ts index 0eb4955..86d9578 100644 --- a/src/eval/eval.ts +++ b/src/eval/eval.ts @@ -60,7 +60,7 @@ export function evalLet(s: LetInBlockState, env: DynamicEnvironment): EvalResult const valueEnv = { names: trie.insert(env.names)(s.name)({ recursive: true, - i: () => value, + i: () => { try { return value; } catch (e) {} }, }), }; const value = evalExpr(s.value, valueEnv); diff --git a/src/eval/infer_type.ts b/src/eval/infer_type.ts index e7d8a8e..dc77b50 100644 --- a/src/eval/infer_type.ts +++ b/src/eval/infer_type.ts @@ -1,4 +1,4 @@ -import { Double, eqType, fnType, IncompatibleTypesError, Int, mergeSubstitutionsN, occurring, recomputeTypeVars, substitute, SubstitutionCycle, trie, TYPE_VARS, UNBOUND_SYMBOLS, unify } from "dope2"; +import { Double, eqType, fnType, getHumanReadableName, IncompatibleTypesError, Int, mergeSubstitutionsN, occurring, prettySS, prettyT, recomputeTypeVars, substitute, SubstitutionCycle, trie, TYPE_VARS, UNBOUND_SYMBOLS, unify } from "dope2"; import type { CallBlockState } from "../component/expr/CallBlock"; import type { ExprBlockState } from "../component/expr/ExprBlock"; @@ -8,8 +8,8 @@ import type { LetInBlockState } from "../component/expr/LetInBlock"; import { memoize } from "../util/memoize"; export interface StaticEnvironment { - names: any; - typevars: Set; + names: any; // mapping from name to type + typevars: Set; // set of type variables currently in use } export interface Type { @@ -98,7 +98,7 @@ export const inferTypeInput = memoize(function inferTypeInput(s: InputBlockState type, subs: new Map(), newEnv, - err: new Error("Gibberish"), + err: new Error(`'${s.text}' not found`), } }); @@ -163,10 +163,15 @@ export const inferTypeCall = memoize(function inferTypeCall(s: CallBlockState, e }); export const inferTypeLet = memoize(function inferTypeLet(s: LetInBlockState, env: StaticEnvironment): TypeInfoLet { - const recursiveTypeInfo = iterateRecursiveType(s.name, s.value, env); + console.log('inferTypeLet..', env.typevars); + const recursiveTypeInfo = iterateRecursiveType(s.name, s.value, env, true); + console.log('end inferTypeLet.'); + console.log({recursiveTypeInfo}); // to eval the 'inner' expr, we only need to add our parameter to the environment: const innerEnv = { - names: trie.insert(env.names)(s.name)({kind: "value", t: recursiveTypeInfo.paramType}), + names: trie.insert(env.names)(s.name)({ + t: recursiveTypeInfo.inner.type, + }), typevars: env.typevars, }; const innerTypeInfo = inferType(s.inner, innerEnv); @@ -182,7 +187,9 @@ export const inferTypeLet = memoize(function inferTypeLet(s: LetInBlockState, en }); export const inferTypeLambda = memoize(function inferTypeLambda(s: LambdaBlockState, env: StaticEnvironment): TypeInfoLambda { - const recursiveTypeInfo = iterateRecursiveType(s.paramName, s.expr, env); + console.log('inferTypeLambda..', env.typevars); + const recursiveTypeInfo = iterateRecursiveType(s.paramName, s.expr, env, false); + console.log('end inferTypeLambda.'); return { kind: "lambda", type: fnType(_ => recursiveTypeInfo.paramType)(_ => recursiveTypeInfo.inner.type), @@ -192,21 +199,36 @@ export const inferTypeLambda = memoize(function inferTypeLambda(s: LambdaBlockSt // Given a named value whose type we know nothing about, and an expression that computes the value (which may recursively contain the value), compute the type of the value. // Why? Both lambda functions and let-expressions can refer to themselves recursively. To infer their type, we need to recompute the type and feed it back to itself until some fixed point is reached. -function iterateRecursiveType(paramName: string, expr: ExprBlockState, env: StaticEnvironment) { +function iterateRecursiveType(paramName: string, expr: ExprBlockState, env: StaticEnvironment, paramIsInner: boolean) { let [paramType] = typeUnknown(env); const paramTypeVar = paramType.symbol; - let iterations = 1; + let iterations = 0; while (true) { const innerEnv = { - names: trie.insert(env.names)(paramName)({kind: "unknown", t: paramType}), + names: trie.insert(env.names)(paramName)({ + kind: "unknown", + t: paramType, + }), typevars: env.typevars.union(occurring(paramType) as Set), }; + const innerTypeInfo = inferType(expr, innerEnv); - const subsWithoutPType = new Map(innerTypeInfo.subs); - subsWithoutPType.delete(paramTypeVar); + const inferredPType = substitute(paramType, innerTypeInfo.subs, []); const [inferredPType2, newEnv] = rewriteInferredType(inferredPType, env); + + const subsWithoutPType = new Map(innerTypeInfo.subs); // copy + subsWithoutPType.delete(paramTypeVar); + + // console.log("-----------------", iterations); + // console.log("paramType:", prettyT(paramType)); + // console.log("inferredPType:", prettyT(inferredPType)); + // console.log("inferredPType2:", prettyT(inferredPType2)); + // console.log("env:", [...env.typevars].map(getHumanReadableName)); + // console.log("innerEnv:", [...innerEnv.typevars].map(getHumanReadableName)); + // console.log("-----------------"); + if (eqType(inferredPType2)(paramType)) { return { subs: subsWithoutPType, @@ -216,17 +238,11 @@ function iterateRecursiveType(paramName: string, expr: ExprBlockState, env: Stat innerEnv, }; } - if ((iterations++) == 100) { + + if ((iterations++) == 10) { throw new Error("too many iterations! something's wrong!"); } - // console.log("-----------------", iterations); - // console.log("paramType:", prettyT(paramType)); - // console.log("inferredPType:", prettyT(inferredPType)); - // console.log("inferredPType2:", prettyT(inferredPType2)); - // console.log("env:", [...env.typevars].map(getHumanReadableName)); - // console.log("innerEnv:", [...innerEnv.typevars].map(getHumanReadableName)); - // console.log("-----------------"); - paramType = inferredPType2; + paramType = inferredPType2; // next iteration } } @@ -259,7 +275,7 @@ function rewriteInferredType(type: Type, env: StaticEnvironment): [Type, StaticE const newTypeVars = new Set(env.typevars); let i = 0; for (const o of occurring(type)) { - while (env.typevars.has(UNBOUND_SYMBOLS[i])) { + while (newTypeVars.has(UNBOUND_SYMBOLS[i])) { i++; } if (!env.typevars.has(o)) {