better error handling

This commit is contained in:
Joeri Exelmans 2025-05-27 12:44:57 +02:00
parent 7edf44f107
commit 428e8cd298
8 changed files with 66 additions and 34 deletions

View file

@ -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 = { export const lambda2Params: ExprBlockState = {
"kind": "let", "kind": "let",

View file

@ -1,10 +1,26 @@
.editor { .editor {
padding: 2px;; padding: 2px;
position: relative;
} }
.editor.error { .editor.error {
border: 1px solid red; border: 1px solid red;
display: inline-block; 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 { .editor.unknown {
border: 1px dashed dodgerblue; border: 1px dashed dodgerblue;
display: inline-block; display: inline-block;

View file

@ -51,12 +51,13 @@ export function ExprBlock(props: ExprBlockProps) {
const extraHandlers = Object.fromEntries(Object.entries(actions).map(([shortcut, action]) => const extraHandlers = Object.fromEntries(Object.entries(actions).map(([shortcut, action]) =>
[shortcut, (e) => { e.preventDefault(); action(); }])) [shortcut, (e) => { e.preventDefault(); action(); }]))
console.log(props.typeInfo.err);
return <span className={"editor" + (props.typeInfo.err ? " error" : "")}> return <span className={"editor" + (props.typeInfo.err ? " error" : "")}>
{renderBlock[props.state.kind]()} {renderBlock[props.state.kind]()}
{props.typeInfo.err && {(props.typeInfo.err !== undefined) &&
<div> (<div className="errorMessage">
{props.typeInfo.err.message.split('\n')[1]} {props.typeInfo.err.message}
</div>} </div>)}
<Input <Input
placeholder="<c>" placeholder="<c>"
text="" text=""

View file

@ -161,7 +161,6 @@ export function InputBlock({ state, setState, score, onCancel, typeInfo }: Input
i={i} setI={setI} /> i={i} setI={setI} />
</span> </span>
</Input> </Input>
{/* ::<TypeBlock type={typeInfo.type} /> */}
::<TypeInfoBlock typeInfo={typeInfo} /> ::<TypeInfoBlock typeInfo={typeInfo} />
</> </>
} }
@ -201,7 +200,7 @@ function Suggestion({ setI, j, onSelect, highlighted, suggestion: [priority, typ
onMouseEnter={onMouseEnter(j)} onMouseEnter={onMouseEnter(j)}
onMouseDown={onMouseDown(j)}> onMouseDown={onMouseDown(j)}>
({priority}) ({kind}) {text} :: <TypeBlock type={type} /> ({priority}) ({kind}) {text} :: <TypeBlock type={type} />
</div> </div>;
} }
const SuggestionMemo = memo<SuggestionProps>(Suggestion); const SuggestionMemo = memo<SuggestionProps>(Suggestion);

View file

@ -74,7 +74,6 @@
.typeSignature { .typeSignature {
display: inline-block; display: inline-block;
/* z-index: 1; */
position: relative; position: relative;
} }
@ -91,7 +90,7 @@
color: black; color: black;
font-family: var(--my-monospace-font); font-family: var(--my-monospace-font);
padding: 4px; padding: 4px;
z-index: 1000; z-index: 100;
} }
.editor:hover > .typeSignature { .editor:hover > .typeSignature {

View file

@ -6,7 +6,8 @@ import type { TypeInfo } from "../../eval/infer_type";
export function TypeInfoBlock({typeInfo}: {typeInfo: TypeInfo}) { export function TypeInfoBlock({typeInfo}: {typeInfo: TypeInfo}) {
return <span className="typeSignature gotDebug"> return <span className="typeSignature gotDebug">
<div><Type type={typeInfo.type}/></div> <Type type={typeInfo.type}/>
<br/>
<span className="typeDebug">{prettySS(typeInfo.subs)}</span> <span className="typeDebug">{prettySS(typeInfo.subs)}</span>
</span>; </span>;
} }

View file

@ -60,7 +60,7 @@ export function evalLet(s: LetInBlockState, env: DynamicEnvironment): EvalResult
const valueEnv = { const valueEnv = {
names: trie.insert(env.names)(s.name)({ names: trie.insert(env.names)(s.name)({
recursive: true, recursive: true,
i: () => value, i: () => { try { return value; } catch (e) {} },
}), }),
}; };
const value = evalExpr(s.value, valueEnv); const value = evalExpr(s.value, valueEnv);

View file

@ -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 { CallBlockState } from "../component/expr/CallBlock";
import type { ExprBlockState } from "../component/expr/ExprBlock"; import type { ExprBlockState } from "../component/expr/ExprBlock";
@ -8,8 +8,8 @@ import type { LetInBlockState } from "../component/expr/LetInBlock";
import { memoize } from "../util/memoize"; import { memoize } from "../util/memoize";
export interface StaticEnvironment { export interface StaticEnvironment {
names: any; names: any; // mapping from name to type
typevars: Set<string>; typevars: Set<string>; // set of type variables currently in use
} }
export interface Type { export interface Type {
@ -98,7 +98,7 @@ export const inferTypeInput = memoize(function inferTypeInput(s: InputBlockState
type, type,
subs: new Map(), subs: new Map(),
newEnv, 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 { 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: // to eval the 'inner' expr, we only need to add our parameter to the environment:
const innerEnv = { 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, typevars: env.typevars,
}; };
const innerTypeInfo = inferType(s.inner, innerEnv); 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 { 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 { return {
kind: "lambda", kind: "lambda",
type: fnType(_ => recursiveTypeInfo.paramType)(_ => recursiveTypeInfo.inner.type), 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. // 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. // 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); let [paramType] = typeUnknown(env);
const paramTypeVar = paramType.symbol; const paramTypeVar = paramType.symbol;
let iterations = 1; let iterations = 0;
while (true) { while (true) {
const innerEnv = { 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<string>), typevars: env.typevars.union(occurring(paramType) as Set<string>),
}; };
const innerTypeInfo = inferType(expr, innerEnv); const innerTypeInfo = inferType(expr, innerEnv);
const subsWithoutPType = new Map(innerTypeInfo.subs);
subsWithoutPType.delete(paramTypeVar);
const inferredPType = substitute(paramType, innerTypeInfo.subs, []); const inferredPType = substitute(paramType, innerTypeInfo.subs, []);
const [inferredPType2, newEnv] = rewriteInferredType(inferredPType, env); 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)) { if (eqType(inferredPType2)(paramType)) {
return { return {
subs: subsWithoutPType, subs: subsWithoutPType,
@ -216,17 +238,11 @@ function iterateRecursiveType(paramName: string, expr: ExprBlockState, env: Stat
innerEnv, innerEnv,
}; };
} }
if ((iterations++) == 100) {
if ((iterations++) == 10) {
throw new Error("too many iterations! something's wrong!"); throw new Error("too many iterations! something's wrong!");
} }
// console.log("-----------------", iterations); paramType = inferredPType2; // next iteration
// 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;
} }
} }
@ -259,7 +275,7 @@ function rewriteInferredType(type: Type, env: StaticEnvironment): [Type, StaticE
const newTypeVars = new Set(env.typevars); const newTypeVars = new Set(env.typevars);
let i = 0; let i = 0;
for (const o of occurring(type)) { for (const o of occurring(type)) {
while (env.typevars.has(UNBOUND_SYMBOLS[i])) { while (newTypeVars.has(UNBOUND_SYMBOLS[i])) {
i++; i++;
} }
if (!env.typevars.has(o)) { if (!env.typevars.has(o)) {