greatly simplify and improve type inferencing - suggestions broken

This commit is contained in:
Joeri Exelmans 2025-05-23 19:48:24 +02:00
parent c3f7cea310
commit c5c5def598
12 changed files with 1022 additions and 694 deletions

View file

@ -4,7 +4,7 @@ import { ExprBlock, type ExprBlockState } from './ExprBlock';
import { GlobalContext } from './GlobalContext';
import { biggerExample, emptySet, factorial, higherOrder, higherOrder2Params, inc, initialEditorState, lambda2Params, nonEmptyEditorState, pushBool, tripleFunctionCallEditorState } from "./configurations";
import { actionShortcuts } from './actions';
import { scoreResolved, type ResolvedType } from './eval';
// import { scoreResolved, type ResolvedType } from './eval';
const examples: [string, ExprBlockState][] = [
@ -62,6 +62,8 @@ export function App() {
setAppState(_ => defaultState);
}
// factoryReset();
const pushHistory = (callback: (p: ExprBlockState) => ExprBlockState) => {
setAppState(({history}) => {
const newState = callback(history.at(-1)!);
@ -166,9 +168,7 @@ export function App() {
state={appState.history.at(-1)!}
setState={pushHistory}
onCancel={() => {}}
score={(resolved: ResolvedType) => {
return scoreResolved(resolved, () => 0);
}}
score={() => 0}
/>
</GlobalContext>
</main>

View file

@ -1,7 +1,7 @@
import { useContext } from "react";
import { EnvContext } from "./EnvContext";
import { addFocusRightMost, evalCallBlock2, evalExprBlock, recomputeTypeVarsForEnv, scoreResolved, type Environment, type ResolvedType } from "./eval";
// import { addFocusRightMost, evalCallBlock2, evalExprBlock, recomputeTypeVarsForEnv, scoreResolved, type Environment, type ResolvedType } from "./eval";
import { ExprBlock, type ExprBlockState, type SetStateFn, type State2Props } from "./ExprBlock";
import { GlobalContext } from "./GlobalContext";
import { Value } from "./Value";
@ -9,6 +9,8 @@ import { Value } from "./Value";
import { getActions } from "./actions";
import "./CallBlock.css";
import { CallContext } from "./CallContext";
import { inferType, inferTypeCall, type Environment } from "./infer_type";
import { Type } from "./Type";
export interface CallBlockState {
kind: "call";
@ -21,20 +23,19 @@ export interface CallBlockProps<
InputState=ExprBlockState,
> extends State2Props<CallBlockState,ExprBlockState> {}
function nestedFnProperties({state, setState, score}: CallBlockProps, env) {
function nestedFnProperties({state, setState, score}: CallBlockProps, env: Environment) {
const setFn = (callback: SetStateFn) => {
setState(state => ({...state, fn: callback(state.fn)}));
}
};
const onFnCancel = () => {
setState(state => state.input); // we become our input
}
const scoreFn = (fnSuggestion: ResolvedType) => {
return computePriority(
fnSuggestion,
evalExprBlock(state.input, env)[0],
score,
env,
);
};
const scoreFn = (fnSuggestion: ExprBlockState) => {
return score({
kind: "call",
fn: fnSuggestion,
input: state.input,
});
};
return {state: state.fn, setState: setFn, onCancel: onFnCancel, score: scoreFn};
}
@ -42,36 +43,37 @@ function nestedFnProperties({state, setState, score}: CallBlockProps, env) {
function nestedInputProperties({state, setState, score}: CallBlockProps, env: Environment) {
const setInput = (callback: SetStateFn) => {
setState(state => ({...state, input: callback(state.input)}));
}
};
const onInputCancel = () => {
setState(state => addFocusRightMost(state.fn)); // we become our function
}
const scoreInput = (inputSuggestion: ResolvedType) => {
return computePriority(
evalExprBlock(state.fn, env)[0], // fn *may* be set
inputSuggestion, // suggestions will be for input
score, // priority function we get from parent block
env,
);
}
setState(state => /*addFocusRightMost*/(state.fn)); // we become our function
};
const scoreInput = (inputSuggestion: ExprBlockState) => {
return score({
kind: "call",
fn: state.fn,
input: inputSuggestion,
});
};
return {state: state.input, setState: setInput, onCancel: onInputCancel, score: scoreInput};
}
function computePriority(fn: ResolvedType, input: ResolvedType, outPriority: (s: ResolvedType) => number, env) {
// dirty, but works:
const [fnR, env2] = recomputeTypeVarsForEnv('<fn>', fn, env);
const [inR, env3] = recomputeTypeVarsForEnv('<in>', input, env2);
const [resolved] = evalCallBlock2(fnR, inR, env3);
const score = scoreResolved(resolved, outPriority);
return score;
}
// function computePriority(fn: ResolvedType, input: ResolvedType, outPriority: (s: ResolvedType) => number, env) {
// // dirty, but works:
// const [fnR, env2] = recomputeTypeVarsForEnv('<fn>', fn, env);
// const [inR, env3] = recomputeTypeVarsForEnv('<in>', input, env2);
// const [resolved] = evalCallBlock2(fnR, inR, env3);
// const score = scoreResolved(resolved, outPriority);
// return score;
// }
export function CallBlock(props: CallBlockProps) {
const env = useContext(EnvContext);
const globalContext = useContext(GlobalContext);
const addParam = getActions(globalContext, props.setState).c;
const [resolved] = evalExprBlock(props.state, env);
return <span className={"functionBlock" + ((resolved.kind === "error") ? " unifyError" : "")}>
// const [resolved] = evalExprBlock(props.state, env);
// return <span className={"functionBlock" + ((resolved.kind === "error") ? " unifyError" : "")}>
const typeInfo = inferTypeCall(props.state, env);
return <span className={"functionBlock"}>
<CallContext value={{addParam}}>
<FunctionHeader {...props} addParam={addParam} />
<div className="functionParams">
@ -80,12 +82,14 @@ export function CallBlock(props: CallBlockProps) {
<InputParams
{...props}
depth={0}
errorDepth={(resolved.kind === "error") ? (resolved.depth) : -1}
// errorDepth={(resolved.kind === "error") ? (resolved.depth) : -1}
errorDepth={-1}
addParam={addParam}
/>
{ (resolved.kind === "error") && resolved.e.toString()
{/* { (resolved.kind === "error") && resolved.e.toString()
|| (resolved.kind === "value") && <Value dynamic={resolved} />
|| "unknown" }
|| "unknown" } */}
:: <Type type={typeInfo.type} />
</div>
</div>
</CallContext>

View file

@ -1,6 +1,7 @@
import { createContext } from "react";
import { getDefaultTypeParser, module2Env, ModuleStd } from "dope2";
import type { Dynamic, Environment } from "./eval";
// import type { Dynamic, Environment } from "./eval";
import type { Environment } from "./infer_type";
export const functionWith3Params = i => j => k => i+j+k;
export const functionWith4Params = i => j => k => l => i+j+k+l;
@ -14,10 +15,10 @@ export const extendedEnv: Environment = {
["leqZero", {i: n => leq => otherwise => (n<=0)?leq({}):otherwise({}), t: mkType("Int -> (Unit -> a) -> (Unit -> a) -> a")}],
["functionWith3Params", { i: functionWith3Params, t: mkType("Int->Int->Int->Int") }],
["functionWith4Params", { i: functionWith4Params, t: mkType("Int->Int->Int->Int->Int")}]
]).map(([name, {i,t}]) => [name, { kind: "value", i, t, unification: new Map() }] as [string, Dynamic])
]).map(([name, {i,t}]) => [name, { kind: "value", i, t, unification: new Map() }] as [string, any])
).name2dyn,
nextFreeTypeVar: 0,
typeVars: new Set(),
// nextFreeTypeVar: 0,
typevars: new Set(),
};
export const EnvContext = createContext(extendedEnv);

View file

@ -9,11 +9,12 @@ import { InputBlock, type InputBlockProps, type InputBlockState } from "./InputB
import { LambdaBlock, type LambdaBlockProps, type LambdaBlockState } from "./LambdaBlock";
import { LetInBlock, type LetInBlockProps, type LetInBlockState } from "./LetInBlock";
import { Type } from "./Type";
import { evalExprBlock, type ResolvedType } from "./eval";
// import { evalExprBlock, type ResolvedType } from "./eval";
import "./ExprBlock.css";
import { Input } from "./Input";
import { getActions } from "./actions";
import { inferType } from "./infer_type";
export type ExprBlockState =
InputBlockState
@ -26,7 +27,7 @@ export type SetStateFn<InType = ExprBlockState, OutType = InType> = (state: InTy
export interface State2Props<InType, OutType = InType> {
state: InType;
setState: (callback: SetStateFn<InType, OutType>) => void;
score: (suggestion: ResolvedType) => number;
score: (suggestion: ExprBlockState) => number;
}
interface ExprBlockProps extends State2Props<ExprBlockState> {
@ -44,19 +45,22 @@ export function ExprBlock(props: ExprBlockProps) {
lambda: () => <LambdaBlock {...props as LambdaBlockProps} />,
};
const [resolved] = evalExprBlock(props.state, env);
// const [resolved] = evalExprBlock(props.state, env);
// const typeInfo = inferType(props.state, env);
const actions = getActions(globalContext, props.setState);
const extraHandlers = Object.fromEntries(Object.entries(actions).map(([shortcut, action]) =>
[shortcut, (e) => { e.preventDefault(); action(); }]))
return <span className={"editor" + ((resolved.kind!=="value") ? " "+resolved.kind : "")}>
// return <span className={"editor" + ((resolved.kind!=="value") ? " "+resolved.kind : "")}>
return <span className={"editor"}>
{renderBlock[props.state.kind]()}
{/* @ts-ignore */}
<div className={"typeSignature" + (resolved.__debug ? ' gotDebug' : '')}>
&nbsp;::&nbsp;<Type type={getType(resolved)} />
{/* <div className={"typeSignature" + (resolved.__debug ? ' gotDebug' : '')}> */}
{/* &nbsp;::&nbsp;<Type type={typeInfo.type} /> */}
{/* @ts-ignore */}
{resolved.__debug && <div className="typeDebug">{resolved.__debug}</div>}
</div>
{/* {resolved.__debug && <div className="typeDebug">{resolved.__debug}</div>} */}
{/* </div> */}
<Input
placeholder="<c>"
text=""

View file

@ -3,15 +3,16 @@ import { memo, useContext, useEffect, useMemo, useRef, useState } from "react";
import { getType, prettyT, trie } from "dope2";
import { EnvContext } from "./EnvContext";
import type { Environment, ResolvedType } from "./eval";
// import type { Environment, ResolvedType } from "./eval";
import "./InputBlock.css";
import { Type } from "./Type";
import type { ExprBlockState, State2Props } from "./ExprBlock";
import { attemptParseLiteral } from "./eval";
// import { attemptParseLiteral } from "./eval";
import { Input } from "./Input";
import { CallContext } from "./CallContext";
import { getActions } from "./actions";
import { GlobalContext } from "./GlobalContext";
import { inferTypeInput } from "./infer_type";
interface Literal {
kind: "literal";
@ -32,35 +33,35 @@ export interface InputBlockState {
focus: boolean
}
export type SuggestionType = ["literal"|"name", string, ResolvedType];
export type PrioritizedSuggestionType = [number, ...SuggestionType];
// export type SuggestionType = ["literal"|"name", string, ResolvedType];
// export type PrioritizedSuggestionType = [number, ...SuggestionType];
export interface InputBlockProps extends State2Props<InputBlockState,ExprBlockState> {
onCancel: () => void;
}
const computeSuggestions = (
text: string,
env: Environment,
score: InputBlockProps['score'],
): PrioritizedSuggestionType[] => {
const literals = attemptParseLiteral(text, env);
const ls: SuggestionType[] = [
// literals
... literals.map((resolved) => ["literal", text, resolved]),
// const computeSuggestions = (
// text: string,
// env: Environment,
// score: InputBlockProps['score'],
// ): PrioritizedSuggestionType[] => {
// const literals = attemptParseLiteral(text, env);
// const ls: SuggestionType[] = [
// // literals
// ... literals.map((resolved) => ["literal", text, resolved]),
// names
... trie.suggest(env.names)(text)(Infinity)
.map(([name, resolved]) => ["name", name, resolved]),
]
// return []; // <-- uncomment to disable suggestions (useful for debugging)
return ls
.map((suggestion: SuggestionType) =>
[score(suggestion[2]), ...suggestion] as PrioritizedSuggestionType)
.sort(([priorityA], [priorityB]) => priorityB - priorityA)
}
// // names
// ... trie.suggest(env.names)(text)(Infinity)
// .map(([name, resolved]) => ["name", name, resolved]),
// ]
// // return []; // <-- uncomment to disable suggestions (useful for debugging)
// return ls
// .map((suggestion: SuggestionType) =>
// [score(suggestion[2]), ...suggestion] as PrioritizedSuggestionType)
// .sort(([priorityA], [priorityB]) => priorityB - priorityA)
// }
export function InputBlock({ state, setState, score, onCancel }: InputBlockProps) {
export function InputBlock({ state, setState, /*score,*/ onCancel }: InputBlockProps) {
const {text, focus} = state;
const globalContext = useContext(GlobalContext);
const env = useContext(EnvContext);
@ -69,7 +70,8 @@ export function InputBlock({ state, setState, score, onCancel }: InputBlockProps
const [i, setI] = useState(0); // selected suggestion idx
const singleSuggestion = trie.growPrefix(env.names)(text);
const suggestions = useMemo(() => computeSuggestions(text, env, score), [text, score, env]);
// const suggestions = useMemo(() => computeSuggestions(text, env, score), [text, score, env]);
const suggestions = useMemo(() => [], []);
useEffect(() => {
@ -94,21 +96,21 @@ export function InputBlock({ state, setState, score, onCancel }: InputBlockProps
}
const onSelectSuggestion = () => {
const [_priority, kind, name, dynamic] = suggestions[i];
if (kind === "literal") {
setState(state => ({
...state,
text: name,
value: {kind, type: prettyT(getType(dynamic))},
}));
}
else {
setState(state => ({
...state,
text: name,
value: {kind},
}))
}
// const [_priority, kind, name, dynamic] = suggestions[i];
// if (kind === "literal") {
// setState(state => ({
// ...state,
// text: name,
// value: {kind, type: prettyT(getType(dynamic))},
// }));
// }
// else {
// setState(state => ({
// ...state,
// text: name,
// value: {kind},
// }))
// }
};
const extraHandlers = {
@ -134,7 +136,9 @@ export function InputBlock({ state, setState, score, onCancel }: InputBlockProps
},
};
return <Input
const typeInfo = inferTypeInput(state, env);
return <><Input
placeholder="<name or literal>"
onCancel={onCancel}
onEnter={onSelectSuggestion}
@ -150,6 +154,8 @@ export function InputBlock({ state, setState, score, onCancel }: InputBlockProps
i={i} setI={setI} />
</span>
</Input>
::<Type type={typeInfo.type} />
</>
}
function Suggestions({ suggestions, onSelect, i, setI }) {
@ -170,10 +176,10 @@ interface SuggestionProps {
j: number;
onSelect: any;
highlighted: boolean;
suggestion: PrioritizedSuggestionType;
// suggestion: PrioritizedSuggestionType;
}
function Suggestion({ setI, j, onSelect, highlighted, suggestion: [priority, kind, text, resolved] }: SuggestionProps) {
function Suggestion({ setI, j, onSelect, highlighted, /*suggestion: [priority, kind, text, resolved]*/ }: SuggestionProps) {
const onMouseEnter = j => () => {
setI(j);
};
@ -186,7 +192,7 @@ function Suggestion({ setI, j, onSelect, highlighted, suggestion: [priority, kin
className={(highlighted ? " selected" : "")}
onMouseEnter={onMouseEnter(j)}
onMouseDown={onMouseDown(j)}>
({priority}) ({kind}) {text} :: <Type type={resolved.t} />
{/* ({priority}) ({kind}) {text} :: <Type type={resolved.t} /> */}
</div>
}

View file

@ -1,14 +1,15 @@
import { useContext } from "react";
import { eqType, getSymbol, reduceUnification } from "dope2";
// import { eqType, getSymbol, reduceUnification } from "dope2";
import { ExprBlock, type ExprBlockState, type State2Props } from "./ExprBlock";
import { EnvContext } from "./EnvContext";
import { evalExprBlock, evalLambdaBlock, makeInnerEnv, makeTypeVar } from "./eval";
// import { evalExprBlock, evalLambdaBlock, makeInnerEnv, makeTypeVar } from "./eval";
import "./LambdaBlock.css";
import { Type } from "./Type";
import { Input } from "./Input";
import { inferTypeLambda } from "./infer_type";
export interface LambdaBlockState {
kind: "lambda";
@ -36,9 +37,13 @@ export function LambdaBlock({state, setState, score}: LambdaBlockProps) {
expr: callback(state.expr),
}));
const [lambdaResolved, _, innerEnv] = evalLambdaBlock(state.paramName, state.expr, env);
const {paramType, innerEnv} = inferTypeLambda(state, env);
const inferredParamType = lambdaResolved.t.params[0](lambdaResolved.t);
// const [lambdaResolved, _, innerEnv] = evalLambdaBlock(state.paramName, state.expr, env);
// const inferredParamType = lambdaResolved.t.params[0](lambdaResolved.t);
// const innerEnv = env; // todo: change this
return <span className="lambdaBlock">
<span className="keyword">&#955;</span>
@ -55,7 +60,7 @@ export function LambdaBlock({state, setState, score}: LambdaBlockProps) {
/>
</span>
<div className="typeSignature">
&nbsp;::&nbsp;<Type type={inferredParamType} />
&nbsp;::&nbsp;<Type type={paramType} />
</div>
&nbsp;
<span className="keyword">:</span>

View file

@ -2,12 +2,14 @@ import { useContext } from "react";
import { ExprBlock, type ExprBlockState } from "./ExprBlock";
import { EnvContext } from "./EnvContext";
import { evalExprBlock, makeInnerEnv, scoreResolved, type ResolvedType } from "./eval";
// import { evalExprBlock, makeInnerEnv, scoreResolved, type ResolvedType } from "./eval";
import { type State2Props } from "./ExprBlock";
import { GlobalContext } from "./GlobalContext";
import "./LetInBlock.css";
import { Input } from "./Input";
import { inferTypeLet } from "./infer_type";
import { Type } from "./Type";
export interface LetInBlockState {
kind: "let";
@ -31,49 +33,54 @@ export function LetInBlock(props: LetInBlockProps) {
</span>
}
function DeclColumns({state: {name, value, inner, focus}, setState, score}) {
function DeclColumns({state, setState, score}) {
const env = useContext(EnvContext);
const globalContext = useContext(GlobalContext);
const setInner = callback => setState(state => ({...state, inner: callback(state.inner)}));
const setValue = callback => setState(state => ({...state, value: callback(state.value)}));
const valueSuggestionPriority = (suggestion: ResolvedType) => {
const innerEnv = makeInnerEnv(env, name, suggestion);
const [resolved] = evalExprBlock(inner, innerEnv);
return scoreResolved(resolved, score);
};
// const valueSuggestionPriority = (suggestion: ResolvedType) => {
// const innerEnv = makeInnerEnv(env, name, suggestion);
// const [resolved] = evalExprBlock(inner, innerEnv);
// return scoreResolved(resolved, score);
// };
const [valueResolved] = evalExprBlock(value, env);
const innerEnv = makeInnerEnv(env, name, valueResolved);
// const [valueResolved] = evalExprBlock(value, env);
// const innerEnv = makeInnerEnv(env, name, valueResolved);
const {paramType, innerEnv} = inferTypeLet(state, env);
// const innerEnv = env; // todo: change this
return <>
<span className="keyword column">let&nbsp;</span>
<span className="column rightAlign">
<Input
placeholder="<name>"
text={name}
suggestion=""
onEnter={() => {}}
onCancel={() => {}}
onTextChange={name => setState(state => ({...state, name}))}
extraHandlers={{}}
/>
<Input
placeholder="<name>"
text={state.name}
suggestion=""
onEnter={() => {}}
onCancel={() => {}}
onTextChange={name => setState(state => ({...state, name}))}
extraHandlers={{}}
/>
:: <Type type={paramType} />
</span>
<span className="keyword column">&nbsp;=&nbsp;</span>
<span className="column">
<ExprBlock
state={value}
state={state.value}
setState={setValue}
score={valueSuggestionPriority}
score={() => 0}
onCancel={() => setState(state => state.inner)} // keep inner
/>
</span>
{inner.kind === "let" &&
{state.inner.kind === "let" &&
globalContext?.syntacticSugar &&
<EnvContext value={innerEnv}>
<DeclColumns
state={inner}
state={state.inner}
setState={setInner}
score={score}
/>
@ -86,8 +93,9 @@ function InnerMost({state, setState, score}) {
const env = useContext(EnvContext);
const globalContext = useContext(GlobalContext);
const setInner = callback => setState(state => ({...state, inner: callback(state.inner)}));
const [valueResolved] = evalExprBlock(state.value, env);
const innerEnv = makeInnerEnv(env, state.name, valueResolved);
// const [valueResolved] = evalExprBlock(state.value, env);
// const innerEnv = makeInnerEnv(env, state.name, valueResolved);
const innerEnv = env; // todo: change this
const onCancel = () => setState(state => state.value);
if (state.inner.kind === "let" && globalContext?.syntacticSugar) {
return <EnvContext value={innerEnv}>

View file

@ -1,5 +1,7 @@
import { initialEditorState } from "./configurations";
import { removeFocus } from "./eval";
// import { removeFocus } from "./eval";
const removeFocus = state => state;
export const actionShortcuts: [string, string[], string][] = [
["call" , ['c'], "expr ⌴" ],

File diff suppressed because it is too large Load diff

216
src/infer_type.ts Normal file
View file

@ -0,0 +1,216 @@
import { Double, eqType, fnType, IncompatibleTypesError, Int, mergeSubstitutionsN, occurring, prettySS, recomputeTypeVars, substitute, trie, TYPE_VARS, UNBOUND_SYMBOLS, unify } from "dope2";
import type { CallBlockState } from "./CallBlock";
import type { ExprBlockState } from "./ExprBlock";
import type { InputBlockState } from "./InputBlock";
import type { LambdaBlockState } from "./LambdaBlock";
import type { LetInBlockState } from "./LetInBlock";
export interface Environment {
names: any;
typevars: Set<string>;
}
export interface Type {
symbol: string;
params: ((t: Type) => Type)[];
};
export type Substitutions = Map<string, Type>;
export interface TypeInfo {
type: Type;
subs: Substitutions;
newEnv: Environment;
err?: IncompatibleTypesError;
}
export interface LambdaTypeInfo extends TypeInfo {
paramType: Type;
innerEnv: Environment;
}
export function inferType(s: ExprBlockState, env: Environment): TypeInfo {
if (s.kind === "input") {
return inferTypeInput(s, env);
}
else if (s.kind === "call") {
return inferTypeCall(s, env);
}
else if (s.kind === "let") {
return inferTypeLet(s, env);
}
else { // (s.kind === "lambda")
return inferTypeLambda(s, env);
}
}
export function inferTypeInput(s: InputBlockState, env: Environment): TypeInfo {
if (s.value.kind === "literal") {
const type = {
Int: Int,
Double: Double,
}[s.value.type] as Type;
return {
type,
subs: new Map(),
newEnv: env,
}
}
else if (s.value.kind === "name") {
const found = trie.get(env.names)(s.text);
if (found) {
let type = found.t;
let newEnv = env;
if (found.kind !== "unknown") {
[type, newEnv] = rewriteType(found.t, env);
}
return {
type,
subs: new Map(),
newEnv,
};
}
}
// kind === "text", or name not found
const [type, newEnv] = typeUnknown(env);
return {
type,
subs: new Map(),
newEnv,
}
}
// @ts-ignore
export function inferTypeCall(s: CallBlockState, env: Environment): TypeInfo {
const fnTypeInfo = inferType(s.fn, env);
const inputTypeInfo = inferType(s.input, fnTypeInfo.newEnv);
const [returnType, envWithReturn] = typeUnknown(inputTypeInfo.newEnv);
const fakeFnType = fnType(_ => inputTypeInfo.type)(_ => returnType);
try {
const subs = unify(
fnTypeInfo.type,
fakeFnType);
console.log("subs:", prettySS(subs));
let type, newEnv;
if (subs.has(returnType!.symbol)) {
type = subs.get(returnType!.symbol);
subs.delete(returnType!.symbol);
newEnv = inputTypeInfo.newEnv
}
else {
type = returnType;
newEnv = envWithReturn;
}
const mergedSubs = mergeSubstitutionsN([
fnTypeInfo.subs,
inputTypeInfo.subs,
subs,
]);
return {
type,
subs: mergedSubs,
newEnv,
};
}
catch (e) {
if (e instanceof IncompatibleTypesError) {
const [type, newEnv] = typeUnknown(env);
return {
type,
subs: new Map(),
newEnv,
err: e,
}
}
}
}
export function inferTypeLet(s: LetInBlockState, env: Environment): LambdaTypeInfo {
const valTypeInfo = inferType(s.value, env);
const innerEnv = {
names: trie.insert(env.names)(s.name)({kind: "value", t: valTypeInfo.type}),
typevars: env.typevars,
};
return {
...inferType(s.inner, innerEnv),
paramType: valTypeInfo.type,
innerEnv,
};
}
export function inferTypeLambda(s: LambdaBlockState, env: Environment): LambdaTypeInfo {
let [paramType] = typeUnknown(env);
const paramTypeVar = paramType.symbol;
let iterations = 1;
while (true) {
const innerEnv = {
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);
subsWithoutPType.delete(paramTypeVar);
const inferredPType = substitute(paramType, typeInfo.subs, []);
const inferredPType2 = rewriteInferredType(inferredPType, env);
if (eqType(inferredPType2)(paramType)) {
return {
type: fnType(_ => paramType)(_ => typeInfo.type),
subs: subsWithoutPType,
newEnv: env, // no change
paramType,
innerEnv,
};
}
if ((iterations++) == 4) {
throw new Error("too many iterations!");
}
// 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;
}
}
// Helpers
const highestTypeVar2 = (typevars: Iterable<string>) => {
let highest = -1;
for (const typeVar of typevars) {
highest = Math.max(highest, UNBOUND_SYMBOLS.indexOf(typeVar));
}
return highest;
}
function rewriteType(type: Type, env: Environment): [Type, Environment] {
const [recomputed] = recomputeTypeVars([type], highestTypeVar2(env.typevars)+1);
return [type, {
names: env.names,
typevars: env.typevars.union(occurring(recomputed)),
}];
}
function typeUnknown(env: Environment): [Type, Environment] {
const type = TYPE_VARS[highestTypeVar2(env.typevars)+1];
const newEnv = {
names: env.names,
typevars: new Set([...env.typevars, type.symbol]),
};
return [type, newEnv];
}
function rewriteInferredType(type: Type, env: Environment) {
const substitutions = new Map();
let i = 0;
for (const o of occurring(type)) {
while (env.typevars.has(UNBOUND_SYMBOLS[i])) {
i++;
}
if (!env.typevars.has(o)) {
substitutions.set(o, TYPE_VARS[i]);
}
}
return substitute(type, substitutions, []);
}