suggestions work again, improve error reporting

This commit is contained in:
Joeri Exelmans 2025-05-24 09:42:26 +02:00
parent 9050581a10
commit 69175c8cb1
12 changed files with 259 additions and 282 deletions

View file

@ -460,53 +460,6 @@
// }
// }
// function parseLiteral(text: string, type: string, env: Environment): [ResolvedType,Environment] {
// // dirty
// if (type === "Int") {
// return parseAsInt(text, env);
// }
// if (type === "Double") {
// return parseAsDouble(text, env);
// }
// return makeError(env, new Error("Failed to parse"));
// }
// function parseAsDouble(text: string, env: Environment): [ResolvedType,Environment] {
// if (text !== '') {
// const num = Number(text);
// if (!Number.isNaN(num)) {
// return [{
// kind: "value",
// i: num,
// t: Double,
// unification: new Map(),
// }, env];
// }
// }
// return makeError(env, new Error("Failed to parse as Double"));
// }
// function parseAsInt(text: string, env: Environment): [ResolvedType,Environment] {
// if (text !== '') {
// try {
// return [{
// kind: "value",
// i: BigInt(text),
// t: Int,
// unification: new Map(),
// }, env]; // may throw
// }
// catch {}
// }
// return makeError(env, new Error("Failed to parse as Int"));
// }
// const literalParsers = [parseAsDouble, parseAsInt];
// export function attemptParseLiteral(text: string, env: Environment): Dynamic[] {
// return literalParsers.map(parseFn => parseFn(text, env))
// .map(([resolved]) => resolved)
// .filter((resolved) => (resolved.kind !== "unknown" && resolved.kind !== "error")) as unknown as Dynamic[];
// }
// export function scoreResolved(resolved: ResolvedType, outPriority: (s:ResolvedType) => number) {
// const bias = outPriority(resolved);

View file

@ -1,4 +1,4 @@
import { Double, eqType, fnType, IncompatibleTypesError, Int, mergeSubstitutionsN, occurring, prettySS, recomputeTypeVars, substitute, trie, TYPE_VARS, UNBOUND_SYMBOLS, unify } from "dope2";
import { Double, eqType, fnType, IncompatibleTypesError, Int, mergeSubstitutionsN, occurring, prettyS, 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";
@ -18,20 +18,35 @@ export interface Type {
export type Substitutions = Map<string, Type>;
export interface TypeInfo {
interface TypeInfoCommon {
type: Type;
subs: Substitutions;
newEnv: Environment;
err?: IncompatibleTypesError;
}
export interface TypeInfoLambda extends TypeInfo {
export interface TypeInfoInput extends TypeInfoCommon {
kind: "input";
}
export interface TypeInfoCall extends TypeInfoCommon {
kind: "call";
fn: TypeInfo;
input: TypeInfo;
}
export interface TypeInfoLet extends TypeInfoCommon {
kind: "let";
innerEnv: Environment;
value: TypeInfo;
inner: TypeInfo;
}
export interface TypeInfoLambda extends TypeInfoCommon {
kind: "lambda";
paramType: Type;
inner: TypeInfo;
innerEnv: Environment;
}
export interface TypeInfoCall extends TypeInfo {
inputEnv: Environment;
}
export type TypeInfo = TypeInfoInput | TypeInfoCall | TypeInfoLet | TypeInfoLambda;
export function inferType(s: ExprBlockState, env: Environment): TypeInfo {
if (s.kind === "input") {
@ -48,13 +63,14 @@ export function inferType(s: ExprBlockState, env: Environment): TypeInfo {
}
}
export function inferTypeInput(s: InputBlockState, env: Environment): TypeInfo {
export function inferTypeInput(s: InputBlockState, env: Environment): TypeInfoInput {
if (s.value.kind === "literal") {
const type = {
Int: Int,
Double: Double,
}[s.value.type] as Type;
return {
kind: "input",
type,
subs: new Map(),
newEnv: env,
@ -67,18 +83,21 @@ export function inferTypeInput(s: InputBlockState, env: Environment): TypeInfo {
? [found.t, env]
: rewriteType(found.t, env);
return {
kind: "input",
type,
subs: new Map(),
newEnv,
};
}
}
// kind === "text", or name not found
// kind === "gibberish", or name not found
const [type, newEnv] = typeUnknown(env);
return {
kind: "input",
type,
subs: new Map(),
newEnv,
err: new Error("Gibberish"),
}
}
@ -104,41 +123,59 @@ export function inferTypeCall(s: CallBlockState, env: Environment): TypeInfoCall
type = returnType;
newEnv = envWithReturn;
}
const mergedSubs = mergeSubstitutionsN([
fnTypeInfo.subs,
inputTypeInfo.subs,
subs,
]);
let mergedSubs;
try {
mergedSubs = mergeSubstitutionsN([
fnTypeInfo.subs,
inputTypeInfo.subs,
subs,
]);
} catch (e) {
if (e instanceof SubstitutionCycle) {
// wrap error
throw new IncompatibleTypesError(fnTypeInfo.type, fakeFnType, e);
}
throw e;
}
return {
kind: "call",
type,
subs: mergedSubs,
newEnv,
inputEnv,
fn: fnTypeInfo,
input: inputTypeInfo,
};
}
catch (e) {
if (e instanceof IncompatibleTypesError) {
const [type, newEnv] = typeUnknown(env);
return {
kind: "call",
type,
subs: new Map(),
newEnv,
err: e,
inputEnv,
fn: fnTypeInfo,
input: inputTypeInfo,
}
}
}
}
export function inferTypeLet(s: LetInBlockState, env: Environment): TypeInfoLambda {
export function inferTypeLet(s: LetInBlockState, env: Environment): TypeInfoLet {
const valTypeInfo = inferType(s.value, env);
const innerEnv = {
names: trie.insert(env.names)(s.name)({kind: "value", t: valTypeInfo.type}),
typevars: env.typevars,
};
const innerTypeInfo = inferType(s.inner, innerEnv);
return {
...inferType(s.inner, innerEnv),
paramType: valTypeInfo.type,
kind: "let",
type: innerTypeInfo.type,
subs: innerTypeInfo.subs,
newEnv: env,
value: valTypeInfo,
inner: innerTypeInfo,
innerEnv,
};
}
@ -154,21 +191,23 @@ export function inferTypeLambda(s: LambdaBlockState, env: Environment): TypeInfo
names: trie.insert(env.names)(s.paramName)({kind: "unknown", t: paramType}),
typevars: env.typevars.union(occurring(paramType) as Set<string>),
};
const typeInfo = inferType(s.expr, innerEnv);
const subsWithoutPType = new Map(typeInfo.subs);
const innerTypeInfo = inferType(s.expr, innerEnv);
const subsWithoutPType = new Map(innerTypeInfo.subs);
subsWithoutPType.delete(paramTypeVar);
const inferredPType = substitute(paramType, typeInfo.subs, []);
const inferredPType = substitute(paramType, innerTypeInfo.subs, []);
const inferredPType2 = rewriteInferredType(inferredPType, env);
if (eqType(inferredPType2)(paramType)) {
return {
type: fnType(_ => paramType)(_ => typeInfo.type),
kind: "lambda",
type: fnType(_ => paramType)(_ => innerTypeInfo.type),
subs: subsWithoutPType,
newEnv: env, // no change
paramType,
inner: innerTypeInfo,
innerEnv,
};
}
if ((iterations++) == 4) {
if ((iterations++) == 10) {
throw new Error("too many iterations!");
}
// console.log("-----------------", iterations);
@ -192,9 +231,10 @@ const highestTypeVar2 = (typevars: Iterable<string>) => {
}
function rewriteType(type: Type, env: Environment): [Type, Environment] {
const [recomputed] = recomputeTypeVars([type], highestTypeVar2(env.typevars)+1);
return [type, {
const newTypeVars = occurring(recomputed);
return [recomputed, {
names: env.names,
typevars: env.typevars.union(occurring(recomputed)),
typevars: env.typevars.union(newTypeVars),
}];
}
function typeUnknown(env: Environment): [Type, Environment] {
@ -218,3 +258,19 @@ function rewriteInferredType(type: Type, env: Environment) {
}
return substitute(type, substitutions, []);
}
export function scoreTypeInfo(typeInfo: TypeInfo): number {
const bias = typeInfo.err ? -1 : 0;
if (typeInfo.kind === "input") {
return bias;
}
else if (typeInfo.kind === "call") {
return bias + scoreTypeInfo(typeInfo.fn) + scoreTypeInfo(typeInfo.input);
}
else if (typeInfo.kind === "let") {
return bias + scoreTypeInfo(typeInfo.value) + scoreTypeInfo(typeInfo.inner);
}
else if (typeInfo.kind === "lambda") {
return bias + scoreTypeInfo(typeInfo.inner);
}
return 0; // shut up typescript
}