use unused typevar when encountering unknown value or lambda parameter - type inferencing still not entirely correct
This commit is contained in:
parent
d7a4e210a2
commit
496463bbac
8 changed files with 94 additions and 66 deletions
99
src/eval.ts
99
src/eval.ts
|
|
@ -1,4 +1,4 @@
|
|||
import { assignFnSubstitutions, Double, fnType, getSymbol, growEnv, Int, makeGeneric, NotAFunctionError, prettyT, substitute, symbolFunction, trie, TYPE_VARS, UnifyError } from "dope2";
|
||||
import { assignFnSubstitutions, dict, Double, fnType, getSymbol, growEnv, Int, makeGeneric, NotAFunctionError, prettyT, set, substitute, symbolFunction, trie, TYPE_VARS, UnifyError } from "dope2";
|
||||
|
||||
import type { EditorState } from "./Editor";
|
||||
import type { InputValueType, SuggestionType } from "./InputBlock";
|
||||
|
|
@ -30,7 +30,11 @@ export interface Unknown {
|
|||
substitutions: Map<Type,Type>;
|
||||
}
|
||||
|
||||
export const entirelyUnknown: Unknown = { kind: "unknown", t: makeGeneric(a => a), substitutions: new Map() };
|
||||
export const entirelyUnknown = env => ({
|
||||
kind: "unknown",
|
||||
t: getUnusedTypeVar(env),
|
||||
substitutions: new Map(),
|
||||
} as Unknown);
|
||||
|
||||
// the value of every block is either known (Dynamic), an error, or unknown
|
||||
export type ResolvedType = Dynamic | DeepError | Unknown;
|
||||
|
|
@ -40,9 +44,7 @@ export const evalEditorBlock = (s: EditorState, env): ResolvedType => {
|
|||
return evalInputBlock(s.text, s.value, env);
|
||||
}
|
||||
if (s.kind === "call") {
|
||||
const fn = evalEditorBlock(s.fn, env);
|
||||
const input = evalEditorBlock(s.input, env);
|
||||
return evalCallBlock(fn, input);
|
||||
return evalCallBlock(s.fn, s.input, env);
|
||||
}
|
||||
if (s.kind === "let") {
|
||||
return evalLetInBlock(s.value, s.name, s.inner, env);
|
||||
|
|
@ -51,12 +53,12 @@ export const evalEditorBlock = (s: EditorState, env): ResolvedType => {
|
|||
return evalLambdaBlock(s.paramName, s.expr, env);
|
||||
|
||||
}
|
||||
return entirelyUnknown; // todo
|
||||
return entirelyUnknown(env); // todo
|
||||
};
|
||||
|
||||
export function evalInputBlock(text: string, value: InputValueType, env): ResolvedType {
|
||||
if (value.kind === "literal") {
|
||||
return parseLiteral(text, value.type);
|
||||
return parseLiteral(text, value.type, env);
|
||||
}
|
||||
else if (value.kind === "name") {
|
||||
const found = trie.get(env.name2dyn)(text);
|
||||
|
|
@ -67,11 +69,11 @@ export function evalInputBlock(text: string, value: InputValueType, env): Resolv
|
|||
substitutions: new Map(),
|
||||
};
|
||||
} else {
|
||||
return entirelyUnknown;
|
||||
return entirelyUnknown(env);
|
||||
}
|
||||
}
|
||||
else { // kind === "text" -> unresolved
|
||||
return entirelyUnknown;
|
||||
return entirelyUnknown(env);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -79,48 +81,48 @@ const mergeMaps = (...maps: Map<Type,Type>[]) => {
|
|||
return new Map(maps.flatMap(m => [...m]));
|
||||
}
|
||||
|
||||
export function evalCallBlock(fn: ResolvedType, input: ResolvedType): ResolvedType {
|
||||
if (getSymbol(fn.t) !== symbolFunction) {
|
||||
if (fn.kind === "unknown") {
|
||||
return entirelyUnknown; // don't flash everything red, giving the user a heart attack
|
||||
export function evalCallBlock2(fnResolved: ResolvedType, inputResolved: ResolvedType, env): ResolvedType {
|
||||
if (getSymbol(fnResolved.t) !== symbolFunction) {
|
||||
if (fnResolved.kind === "unknown") {
|
||||
return entirelyUnknown(env); // don't flash everything red, giving the user a heart attack
|
||||
}
|
||||
// worst outcome: we know nothing about the result!
|
||||
// worst outcome: we know nothing about the result!
|
||||
return {
|
||||
kind: "error",
|
||||
e: new NotAFunctionError(`${prettyT(fn.t)} is not a function type!`),
|
||||
t: entirelyUnknown.t,
|
||||
substitutions: mergeMaps(fn.substitutions, input.substitutions),
|
||||
e: new NotAFunctionError(`${prettyT(fnResolved.t)} is not a function type!`),
|
||||
t: getUnusedTypeVar(env),
|
||||
substitutions: mergeMaps(fnResolved.substitutions, inputResolved.substitutions),
|
||||
depth: 0,
|
||||
};
|
||||
}
|
||||
try {
|
||||
// fn is a function...
|
||||
const [outType, substitutions] = assignFnSubstitutions(fn.t, input.t); // may throw
|
||||
const [outType, substitutions] = assignFnSubstitutions(fnResolved.t, inputResolved.t); // may throw
|
||||
|
||||
const mergedSubstitutions = mergeMaps(substitutions, fn.substitutions, input.substitutions);
|
||||
const mergedSubstitutions = mergeMaps(substitutions, fnResolved.substitutions, inputResolved.substitutions);
|
||||
|
||||
if (input.kind === "error") {
|
||||
if (inputResolved.kind === "error") {
|
||||
return {
|
||||
kind: "error",
|
||||
e: input.e, // bubble up the error
|
||||
e: inputResolved.e, // bubble up the error
|
||||
depth: 0,
|
||||
t: outType,
|
||||
substitutions: mergedSubstitutions,
|
||||
};
|
||||
}
|
||||
if (fn.kind === "error") {
|
||||
if (fnResolved.kind === "error") {
|
||||
// also bubble up
|
||||
return {
|
||||
kind: "error",
|
||||
e: fn.e,
|
||||
depth: fn.depth+1,
|
||||
e: fnResolved.e,
|
||||
depth: fnResolved.depth+1,
|
||||
t: outType,
|
||||
substitutions: mergedSubstitutions,
|
||||
};
|
||||
}
|
||||
// if the above statement did not throw => types are compatible...
|
||||
if (input.kind === "value" && fn.kind === "value") {
|
||||
const outValue = fn.i(input.i);
|
||||
if (inputResolved.kind === "value" && fnResolved.kind === "value") {
|
||||
const outValue = fnResolved.i(inputResolved.i);
|
||||
return {
|
||||
kind: "value",
|
||||
i: outValue,
|
||||
|
|
@ -140,31 +142,46 @@ export function evalCallBlock(fn: ResolvedType, input: ResolvedType): ResolvedTy
|
|||
catch (e) {
|
||||
if ((e instanceof UnifyError)) {
|
||||
// even though fn was incompatible with the given parameter, we can still suppose that our output-type will be that of fn...?
|
||||
const outType = fn.t.params[1](fn.t);
|
||||
const outType = fnResolved.t.params[1](fnResolved.t);
|
||||
return {
|
||||
kind: "error",
|
||||
e,
|
||||
depth: 0,
|
||||
t: outType,
|
||||
substitutions: mergeMaps(fn.substitutions, input.substitutions),
|
||||
substitutions: mergeMaps(fnResolved.substitutions, inputResolved.substitutions),
|
||||
};
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
export function evalCallBlock(fn: EditorState, input: EditorState, env): ResolvedType {
|
||||
const fnResolved = evalEditorBlock(fn, env);
|
||||
const inputResolved = evalEditorBlock(input, env);
|
||||
return evalCallBlock2(fnResolved, inputResolved, env);
|
||||
}
|
||||
|
||||
export function evalLetInBlock(value: EditorState, name: string, inner: EditorState, env): ResolvedType {
|
||||
const valueResolved = evalEditorBlock(value, env);
|
||||
const innerEnv = makeInnerEnv(env, name, valueResolved)
|
||||
return evalEditorBlock(inner, innerEnv);
|
||||
}
|
||||
|
||||
export function getUnusedTypeVar(env) {
|
||||
for (let i=0; ; i++) {
|
||||
if (!dict.has(env.typeDict)(TYPE_VARS[i])) {
|
||||
return TYPE_VARS[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function evalLambdaBlock(paramName: string, expr: EditorState, env): ResolvedType {
|
||||
const paramType = getUnusedTypeVar(env);
|
||||
const fn = (x: any) => {
|
||||
const innerEnv = makeInnerEnv(env, paramName, {
|
||||
kind: "value",
|
||||
i: x,
|
||||
t: TYPE_VARS[0],
|
||||
t: paramType,
|
||||
substitutions: new Map(),
|
||||
});
|
||||
const result = evalEditorBlock(expr, innerEnv);
|
||||
|
|
@ -175,11 +192,11 @@ export function evalLambdaBlock(paramName: string, expr: EditorState, env): Reso
|
|||
// static env: we only know the name and the type
|
||||
const staticInnerEnv = makeInnerEnv(env, paramName, {
|
||||
kind: "unknown", // parameter value is not statically known
|
||||
t: TYPE_VARS[0],
|
||||
t: paramType,
|
||||
substitutions: new Map(),
|
||||
});
|
||||
const abstractOutput = evalEditorBlock(expr, staticInnerEnv);
|
||||
const t = fnType(_ => entirelyUnknown.t)(_ => abstractOutput.t);
|
||||
const t = fnType(_ => paramType)(_ => abstractOutput.t);
|
||||
const T = substitute(t, abstractOutput.substitutions, [])
|
||||
return {
|
||||
kind: "value",
|
||||
|
|
@ -194,18 +211,18 @@ export function haveValue(resolved: ResolvedType) {
|
|||
return resolved.kind === "value";
|
||||
}
|
||||
|
||||
function parseLiteral(text: string, type: string): ResolvedType {
|
||||
function parseLiteral(text: string, type: string, env): ResolvedType {
|
||||
// dirty
|
||||
if (type === "Int") {
|
||||
return parseAsInt(text);
|
||||
return parseAsInt(text, env);
|
||||
}
|
||||
if (type === "Double") {
|
||||
return parseAsDouble(text);
|
||||
return parseAsDouble(text, env);
|
||||
}
|
||||
return entirelyUnknown;
|
||||
return entirelyUnknown(env);
|
||||
}
|
||||
|
||||
function parseAsDouble(text: string): ResolvedType {
|
||||
function parseAsDouble(text: string, env): ResolvedType {
|
||||
if (text !== '') {
|
||||
const num = Number(text);
|
||||
if (!Number.isNaN(num)) {
|
||||
|
|
@ -217,9 +234,9 @@ function parseAsDouble(text: string): ResolvedType {
|
|||
};
|
||||
}
|
||||
}
|
||||
return entirelyUnknown;
|
||||
return entirelyUnknown(env);
|
||||
}
|
||||
function parseAsInt(text: string): ResolvedType {
|
||||
function parseAsInt(text: string, env): ResolvedType {
|
||||
if (text !== '') {
|
||||
try {
|
||||
return {
|
||||
|
|
@ -231,13 +248,13 @@ function parseAsInt(text: string): ResolvedType {
|
|||
}
|
||||
catch {}
|
||||
}
|
||||
return entirelyUnknown;
|
||||
return entirelyUnknown(env);
|
||||
}
|
||||
|
||||
const literalParsers = [parseAsDouble, parseAsInt];
|
||||
|
||||
export function attemptParseLiteral(text: string): Dynamic[] {
|
||||
return literalParsers.map(parseFn => parseFn(text))
|
||||
export function attemptParseLiteral(text: string, env): Dynamic[] {
|
||||
return literalParsers.map(parseFn => parseFn(text, env))
|
||||
.filter(resolved => (resolved.kind !== "unknown" && resolved.kind !== "error")) as unknown as Dynamic[];
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue