infer all types once at the root level, and pass the result down deeply

This commit is contained in:
Joeri Exelmans 2025-05-25 08:58:21 +02:00
parent b2584a2495
commit d000839878
6 changed files with 75 additions and 80 deletions

View file

@ -1,11 +1,10 @@
import { useEffect, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import { extendedEnv } from '../../context/EnvContext'; import { extendedEnv } from '../../context/EnvContext';
import { GlobalContext } from '../../context/GlobalContext'; import { GlobalContext } from '../../context/GlobalContext';
import { inferType, scoreTypeInfo } from '../../eval/infer_type'; import { inferType, scoreTypeInfo } from '../../eval/infer_type';
import { ExprBlock, type ExprBlockState } from '../expr/ExprBlock'; import { ExprBlock, type ExprBlockState } from '../expr/ExprBlock';
import { actionShortcuts } from './actions'; import { actionShortcuts } from './actions';
import { biggerExample, emptySet, factorial, higherOrder, higherOrder2Params, inc, initialEditorState, lambda2Params, nonEmptyEditorState, pushBool, setOfListOfBool, tripleFunctionCallEditorState } from "./configurations"; import { biggerExample, emptySet, factorial, higherOrder, higherOrder2Params, inc, initialEditorState, lambda2Params, nonEmptyEditorState, pushBool, setOfListOfBool, tripleFunctionCallEditorState } from "./configurations";
// import { scoreResolved, type ResolvedType } from './eval';
import './App.css'; import './App.css';
@ -133,6 +132,11 @@ export function App() {
} }
} }
const currentState = appState.history.at(-1)!;
// infer types for entire app state
const typeInfo = useMemo(() => inferType(currentState, extendedEnv), [currentState]);
return ( return (
<> <>
<header> <header>
@ -168,13 +172,14 @@ export function App() {
<main onKeyDown={onKeyDown}> <main onKeyDown={onKeyDown}>
<GlobalContext value={{undo: onUndo, redo: onRedo, doHighlight, syntacticSugar}}> <GlobalContext value={{undo: onUndo, redo: onRedo, doHighlight, syntacticSugar}}>
<ExprBlock <ExprBlock
state={appState.history.at(-1)!} state={currentState}
setState={pushHistory} setState={pushHistory}
onCancel={() => {}} onCancel={() => {}}
score={(state: ExprBlockState) => { score={(state: ExprBlockState) => {
const typeInfo = inferType(state, extendedEnv); const typeInfo = inferType(state, extendedEnv);
return scoreTypeInfo(typeInfo); return scoreTypeInfo(typeInfo);
}} }}
typeInfo={typeInfo}
/> />
</GlobalContext> </GlobalContext>
</main> </main>

View file

@ -1,14 +1,13 @@
import { useContext } from "react"; import { useContext } from "react";
import { EnvContext } from "../../context/EnvContext";
import { ExprBlock, type ExprBlockState, type SetStateFn, type State2Props } from "./ExprBlock";
import { GlobalContext } from "../../context/GlobalContext";
import { getActions } from "../app/actions";
import "./CallBlock.css";
import { CallContext } from "../../context/CallContext"; import { CallContext } from "../../context/CallContext";
import { inferTypeCall, type Environment } from "../../eval/infer_type"; import { EnvContext } from "../../context/EnvContext";
import { GlobalContext } from "../../context/GlobalContext";
import { type Environment, type TypeInfoCall } from "../../eval/infer_type";
import { getActions } from "../app/actions";
import { Type } from "../other/Type"; import { Type } from "../other/Type";
import "./CallBlock.css";
import { ExprBlock, type ExprBlockState, type SetStateFn, type State2Props } from "./ExprBlock";
export interface CallBlockState { export interface CallBlockState {
kind: "call"; kind: "call";
@ -19,9 +18,11 @@ export interface CallBlockState {
export interface CallBlockProps< export interface CallBlockProps<
FnState=ExprBlockState, FnState=ExprBlockState,
InputState=ExprBlockState, InputState=ExprBlockState,
> extends State2Props<CallBlockState,ExprBlockState> {} > extends State2Props<CallBlockState,ExprBlockState> {
typeInfo: TypeInfoCall;
}
function nestedFnProperties({state, setState, score}: CallBlockProps, env: Environment) { function nestedFnProperties({state, setState, score, typeInfo}: CallBlockProps, env: Environment) {
const setFn = (callback: SetStateFn) => { const setFn = (callback: SetStateFn) => {
setState(state => ({...state, fn: callback(state.fn)})); setState(state => ({...state, fn: callback(state.fn)}));
}; };
@ -31,10 +32,10 @@ function nestedFnProperties({state, setState, score}: CallBlockProps, env: Envir
const scoreFn = (fnSuggestion: ExprBlockState) => { const scoreFn = (fnSuggestion: ExprBlockState) => {
return score({ ...state, fn: fnSuggestion }); return score({ ...state, fn: fnSuggestion });
}; };
return {state: state.fn, setState: setFn, onCancel: onFnCancel, score: scoreFn}; return {state: state.fn, setState: setFn, onCancel: onFnCancel, score: scoreFn, typeInfo: typeInfo.fn};
} }
function nestedInputProperties({state, setState, score}: CallBlockProps, env: Environment) { function nestedInputProperties({state, setState, score, typeInfo}: CallBlockProps, env: Environment) {
const setInput = (callback: SetStateFn) => { const setInput = (callback: SetStateFn) => {
setState(state => ({...state, input: callback(state.input)})); setState(state => ({...state, input: callback(state.input)}));
}; };
@ -44,7 +45,7 @@ function nestedInputProperties({state, setState, score}: CallBlockProps, env: En
const scoreInput = (inputSuggestion: ExprBlockState) => { const scoreInput = (inputSuggestion: ExprBlockState) => {
return score({ ...state, input: inputSuggestion }); return score({ ...state, input: inputSuggestion });
}; };
return {state: state.input, setState: setInput, onCancel: onInputCancel, score: scoreInput}; return {state: state.input, setState: setInput, onCancel: onInputCancel, score: scoreInput, typeInfo: typeInfo.input};
} }
export function CallBlock(props: CallBlockProps) { export function CallBlock(props: CallBlockProps) {
@ -53,7 +54,6 @@ export function CallBlock(props: CallBlockProps) {
const addParam = getActions(globalContext, props.setState).c; const addParam = getActions(globalContext, props.setState).c;
// const [resolved] = evalExprBlock(props.state, env); // const [resolved] = evalExprBlock(props.state, env);
// return <span className={"functionBlock" + ((resolved.kind === "error") ? " unifyError" : "")}> // return <span className={"functionBlock" + ((resolved.kind === "error") ? " unifyError" : "")}>
const typeInfo = inferTypeCall(props.state, env);
return <span className={"functionBlock"}> return <span className={"functionBlock"}>
<CallContext value={{addParam}}> <CallContext value={{addParam}}>
<FunctionHeader {...props} addParam={addParam} /> <FunctionHeader {...props} addParam={addParam} />
@ -67,7 +67,7 @@ export function CallBlock(props: CallBlockProps) {
{/* { (resolved.kind === "error") && resolved.e.toString() {/* { (resolved.kind === "error") && resolved.e.toString()
|| (resolved.kind === "value") && <Value dynamic={resolved} /> || (resolved.kind === "value") && <Value dynamic={resolved} />
|| "unknown" } */} || "unknown" } */}
:: <Type type={typeInfo.type} /> :: <Type type={props.typeInfo.type} />
</div> </div>
</div> </div>
</CallContext> </CallContext>
@ -92,22 +92,21 @@ function FunctionHeader(props) {
} }
} }
function InputParams({ ...rest }) { function InputParams(props) {
const env = useContext(EnvContext); const env = useContext(EnvContext);
const globalContext = useContext(GlobalContext); const globalContext = useContext(GlobalContext);
const typeInfo = inferTypeCall(rest.state, env); const inputEnv = props.typeInfo.fn.newEnv;
const inputEnv = typeInfo.fn.newEnv; const isOffending = props.typeInfo.err;
const isOffending = typeInfo.err;
return <div className={"inputParam" + (isOffending ? " offending" : "")}> return <div className={"inputParam" + (isOffending ? " offending" : "")}>
{rest.state.fn.kind === "call" {props.state.fn.kind === "call"
&& globalContext?.syntacticSugar && globalContext?.syntacticSugar
&& <InputParams && <InputParams
{...nestedFnProperties(rest as CallBlockProps, env)} {...nestedFnProperties(props as CallBlockProps, env)}
/>} />}
{/* Our own input param */} {/* Our own input param */}
<EnvContext value={inputEnv}> <EnvContext value={inputEnv}>
<ExprBlock <ExprBlock
{...nestedInputProperties(rest as CallBlockProps, env)} {...nestedInputProperties(props as CallBlockProps, env)}
/> />
</EnvContext> </EnvContext>
</div>; </div>;

View file

@ -1,17 +1,16 @@
import { useContext } from "react"; import { useContext } from "react";
import { CallBlock, type CallBlockProps, type CallBlockState } from "./CallBlock";
import { EnvContext } from "../../context/EnvContext"; import { EnvContext } from "../../context/EnvContext";
import { GlobalContext } from "../../context/GlobalContext"; import { GlobalContext } from "../../context/GlobalContext";
import { type TypeInfo } from "../../eval/infer_type";
import { getActions } from "../app/actions";
import { Input } from "../other/Input";
import { CallBlock, type CallBlockProps, type CallBlockState } from "./CallBlock";
import { InputBlock, type InputBlockProps, type InputBlockState } from "./InputBlock"; import { InputBlock, type InputBlockProps, type InputBlockState } from "./InputBlock";
import { LambdaBlock, type LambdaBlockProps, type LambdaBlockState } from "./LambdaBlock"; import { LambdaBlock, type LambdaBlockProps, type LambdaBlockState } from "./LambdaBlock";
import { LetInBlock, type LetInBlockProps, type LetInBlockState } from "./LetInBlock"; import { LetInBlock, type LetInBlockProps, type LetInBlockState } from "./LetInBlock";
// import { evalExprBlock, type ResolvedType } from "./eval";
import "./ExprBlock.css"; import "./ExprBlock.css";
import { Input } from "../other/Input";
import { getActions } from "../app/actions";
import { inferType, type Type } from "../../eval/infer_type";
export type ExprBlockState = export type ExprBlockState =
InputBlockState InputBlockState
@ -22,9 +21,15 @@ export type ExprBlockState =
export type SetStateFn<InType = ExprBlockState, OutType = InType> = (state: InType) => OutType; export type SetStateFn<InType = ExprBlockState, OutType = InType> = (state: InType) => OutType;
export interface State2Props<InType, OutType = InType> { export interface State2Props<InType, OutType = InType> {
// Every block gets passed its part of the global app state, and a functino to update that part of the state
state: InType; state: InType;
setState: (callback: SetStateFn<InType, OutType>) => void; setState: (callback: SetStateFn<InType, OutType>) => void;
// To compute the priority of an input suggestion. The root ExprBlock's score function counts the number of errors and subtracts points for every error. Every block passes to its children ExprBlock's a wrapped score-function that creates an app-state with only the child altered, computing a score for that app-state.
score: (suggestion: ExprBlockState) => number; score: (suggestion: ExprBlockState) => number;
// All types are inferred once after every App-state change, and passed deeply to all descendants.
typeInfo: TypeInfo;
} }
interface ExprBlockProps extends State2Props<ExprBlockState> { interface ExprBlockProps extends State2Props<ExprBlockState> {
@ -42,19 +47,15 @@ export function ExprBlock(props: ExprBlockProps) {
lambda: () => <LambdaBlock {...props as LambdaBlockProps} />, lambda: () => <LambdaBlock {...props as LambdaBlockProps} />,
}; };
// const [resolved] = evalExprBlock(props.state, env);
// const typeInfo = inferType(props.state, env);
const actions = getActions(globalContext, props.setState); const actions = getActions(globalContext, props.setState);
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(); }]))
const typeInfo = inferType(props.state, env); return <span className={"editor" + (props.typeInfo.err ? " error" : "")}>
return <span className={"editor" + (typeInfo.err ? " error" : "")}>
{renderBlock[props.state.kind]()} {renderBlock[props.state.kind]()}
{typeInfo.err && {props.typeInfo.err &&
<div> <div>
{typeInfo.err.message.split('\n')[1]} {props.typeInfo.err.message.split('\n')[1]}
</div>} </div>}
<Input <Input
placeholder="<c>" placeholder="<c>"

View file

@ -2,15 +2,16 @@ import { memo, useContext, useEffect, useMemo, useRef, useState } from "react";
import { trie } from "dope2"; import { trie } from "dope2";
import { EnvContext } from "../../context/EnvContext";
import "./InputBlock.css";
import type { ExprBlockState, State2Props } from "./ExprBlock";
import { Input } from "../other/Input";
import { CallContext } from "../../context/CallContext"; import { CallContext } from "../../context/CallContext";
import { getActions } from "../app/actions"; import { EnvContext } from "../../context/EnvContext";
import { GlobalContext } from "../../context/GlobalContext"; import { GlobalContext } from "../../context/GlobalContext";
import { inferType, inferTypeInput, type Environment, type Type } from "../../eval/infer_type"; import { inferTypeInput, type Environment, type Type, type TypeInfoInput } from "../../eval/infer_type";
import { getActions } from "../app/actions";
import { Input } from "../other/Input";
import { Type as TypeBlock } from "../other/Type"; import { Type as TypeBlock } from "../other/Type";
import type { ExprBlockState, State2Props } from "./ExprBlock";
import "./InputBlock.css";
interface Literal { interface Literal {
kind: "literal"; kind: "literal";
@ -35,6 +36,7 @@ export type PrioritizedSuggestionType = [number, Type, InputBlockState];
export interface InputBlockProps extends State2Props<InputBlockState,ExprBlockState> { export interface InputBlockProps extends State2Props<InputBlockState,ExprBlockState> {
onCancel: () => void; onCancel: () => void;
typeInfo: TypeInfoInput;
} }
const attemptLiterals = [ const attemptLiterals = [
@ -68,11 +70,11 @@ const computeSuggestions = (
})), })),
]; ];
// return []; // <-- uncomment to disable suggestions (useful for debugging) // return []; // <-- uncomment to disable suggestions (useful for debugging)
return ls.map((state) => [score(state), inferType(state, env).type, state] as PrioritizedSuggestionType) return ls.map((state) => [score(state), inferTypeInput(state, env).type, state] as PrioritizedSuggestionType)
.sort(([a],[b]) => b-a); .sort(([a],[b]) => b-a);
} }
export function InputBlock({ state, setState, score, onCancel }: InputBlockProps) { export function InputBlock({ state, setState, score, onCancel, typeInfo }: InputBlockProps) {
const {text, focus} = state; const {text, focus} = state;
const globalContext = useContext(GlobalContext); const globalContext = useContext(GlobalContext);
const env = useContext(EnvContext); const env = useContext(EnvContext);
@ -133,8 +135,6 @@ export function InputBlock({ state, setState, score, onCancel }: InputBlockProps
}, },
}; };
const typeInfo = inferTypeInput(state, env);
return <><Input return <><Input
placeholder="<name or literal>" placeholder="<name or literal>"
onCancel={onCancel} onCancel={onCancel}

View file

@ -1,15 +1,12 @@
import { useContext } from "react"; import { useContext } from "react";
// import { eqType, getSymbol, reduceUnification } from "dope2";
import { ExprBlock, type ExprBlockState, type State2Props } from "./ExprBlock";
import { EnvContext } from "../../context/EnvContext"; import { EnvContext } from "../../context/EnvContext";
// import { evalExprBlock, evalLambdaBlock, makeInnerEnv, makeTypeVar } from "./eval"; import { ExprBlock, type ExprBlockState, type State2Props } from "./ExprBlock";
import "./LambdaBlock.css"; import { type TypeInfoLambda } from "../../eval/infer_type";
import { Type } from "../other/Type";
import { Input } from "../other/Input"; import { Input } from "../other/Input";
import { inferTypeLambda } from "../../eval/infer_type"; import { Type } from "../other/Type";
import "./LambdaBlock.css";
export interface LambdaBlockState { export interface LambdaBlockState {
kind: "lambda"; kind: "lambda";
@ -22,10 +19,11 @@ export interface LambdaBlockProps<
FnState=ExprBlockState, FnState=ExprBlockState,
InputState=ExprBlockState, InputState=ExprBlockState,
> extends State2Props<LambdaBlockState,ExprBlockState> { > extends State2Props<LambdaBlockState,ExprBlockState> {
typeInfo: TypeInfoLambda;
} }
export function LambdaBlock({state, setState, score}: LambdaBlockProps) { export function LambdaBlock({state, setState, score, typeInfo}: LambdaBlockProps) {
const env = useContext(EnvContext); const env = useContext(EnvContext);
const setParamName = paramName => setState(state => ({ const setParamName = paramName => setState(state => ({
@ -37,14 +35,6 @@ export function LambdaBlock({state, setState, score}: LambdaBlockProps) {
expr: callback(state.expr), expr: callback(state.expr),
})); }));
const {paramType, innerEnv} = inferTypeLambda(state, env);
// 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"> return <span className="lambdaBlock">
<span className="keyword">&#955;</span> <span className="keyword">&#955;</span>
&nbsp; &nbsp;
@ -60,18 +50,19 @@ export function LambdaBlock({state, setState, score}: LambdaBlockProps) {
/> />
</span> </span>
<div className="typeSignature"> <div className="typeSignature">
&nbsp;::&nbsp;<Type type={paramType} /> &nbsp;::&nbsp;<Type type={typeInfo.paramType} />
</div> </div>
&nbsp; &nbsp;
<span className="keyword">:</span> <span className="keyword">:</span>
&nbsp; &nbsp;
<div className="lambdaInner"> <div className="lambdaInner">
<EnvContext value={innerEnv}> <EnvContext value={typeInfo.innerEnv}>
<ExprBlock <ExprBlock
state={state.expr} state={state.expr}
setState={setExpr} setState={setExpr}
onCancel={() => setState(state => state.expr)} onCancel={() => setState(state => state.expr)}
score={suggestion => score({...state, expr: suggestion})} score={suggestion => score({...state, expr: suggestion})}
typeInfo={typeInfo.inner}
/> />
</EnvContext> </EnvContext>
</div> </div>

View file

@ -1,14 +1,13 @@
import { useContext } from "react"; import { useContext } from "react";
import { ExprBlock, type ExprBlockState } from "./ExprBlock";
import { EnvContext } from "../../context/EnvContext"; import { EnvContext } from "../../context/EnvContext";
import { type State2Props } from "./ExprBlock";
import { GlobalContext } from "../../context/GlobalContext"; import { GlobalContext } from "../../context/GlobalContext";
import { type TypeInfoLet } from "../../eval/infer_type";
import { Input } from "../other/Input";
import { Type } from "../other/Type";
import { ExprBlock, type ExprBlockState, type State2Props } from "./ExprBlock";
import "./LetInBlock.css"; import "./LetInBlock.css";
import { Input } from "../other/Input";
import { inferTypeLet } from "../../eval/infer_type";
import { Type } from "../other/Type";
export interface LetInBlockState { export interface LetInBlockState {
kind: "let"; kind: "let";
@ -19,6 +18,7 @@ export interface LetInBlockState {
} }
export interface LetInBlockProps extends State2Props<LetInBlockState,ExprBlockState> { export interface LetInBlockProps extends State2Props<LetInBlockState,ExprBlockState> {
typeInfo: TypeInfoLet;
} }
export function LetInBlock(props: LetInBlockProps) { export function LetInBlock(props: LetInBlockProps) {
@ -32,15 +32,13 @@ export function LetInBlock(props: LetInBlockProps) {
</span> </span>
} }
function DeclColumns({state, setState, score}) { function DeclColumns({state, setState, score, typeInfo}) {
const env = useContext(EnvContext); const env = useContext(EnvContext);
const globalContext = useContext(GlobalContext); const globalContext = useContext(GlobalContext);
const setInner = callback => setState(state => ({...state, inner: callback(state.inner)})); const setInner = callback => setState(state => ({...state, inner: callback(state.inner)}));
const setValue = callback => setState(state => ({...state, value: callback(state.value)})); const setValue = callback => setState(state => ({...state, value: callback(state.value)}));
const {value: valueTypeInfo, innerEnv} = inferTypeLet(state, env);
return <> return <>
<span className="keyword column">let&nbsp;</span> <span className="keyword column">let&nbsp;</span>
<span className="column rightAlign"> <span className="column rightAlign">
@ -53,7 +51,7 @@ function DeclColumns({state, setState, score}) {
onTextChange={name => setState(state => ({...state, name}))} onTextChange={name => setState(state => ({...state, name}))}
extraHandlers={{}} extraHandlers={{}}
/> />
:: <Type type={valueTypeInfo.type} /> :: <Type type={typeInfo.value.type} />
</span> </span>
<span className="keyword column">&nbsp;=&nbsp;</span> <span className="keyword column">&nbsp;=&nbsp;</span>
<span className="column"> <span className="column">
@ -62,45 +60,46 @@ function DeclColumns({state, setState, score}) {
setState={setValue} setState={setValue}
score={suggestion => score({ ...state, value: suggestion })} score={suggestion => score({ ...state, value: suggestion })}
onCancel={() => setState(state => state.inner)} // keep inner onCancel={() => setState(state => state.inner)} // keep inner
typeInfo={typeInfo.value}
/> />
</span> </span>
{state.inner.kind === "let" && {state.inner.kind === "let" &&
globalContext?.syntacticSugar && globalContext?.syntacticSugar &&
<EnvContext value={innerEnv}> <EnvContext value={typeInfo.innerEnv}>
<DeclColumns <DeclColumns
state={state.inner} state={state.inner}
setState={setInner} setState={setInner}
score={suggestion => score({ ...state, inner: suggestion })} score={suggestion => score({ ...state, inner: suggestion })}
typeInfo={typeInfo.inner}
/> />
</EnvContext> </EnvContext>
} }
</>; </>;
} }
function InnerMost({state, setState, score}) { function InnerMost({state, setState, score, typeInfo}) {
const env = useContext(EnvContext); const env = useContext(EnvContext);
const globalContext = useContext(GlobalContext); const globalContext = useContext(GlobalContext);
const setInner = callback => setState(state => ({...state, inner: callback(state.inner)})); const setInner = callback => setState(state => ({...state, inner: callback(state.inner)}));
// const [valueResolved] = evalExprBlock(state.value, env);
// const innerEnv = makeInnerEnv(env, state.name, valueResolved);
const {innerEnv} = inferTypeLet(state, env);
const onCancel = () => setState(state => state.value); const onCancel = () => setState(state => state.value);
if (state.inner.kind === "let" && globalContext?.syntacticSugar) { if (state.inner.kind === "let" && globalContext?.syntacticSugar) {
return <EnvContext value={innerEnv}> return <EnvContext value={typeInfo.innerEnv}>
<InnerMost <InnerMost
state={state.inner} state={state.inner}
setState={setInner} setState={setInner}
score={suggestion => score({ ...state, inner: suggestion })} score={suggestion => score({ ...state, inner: suggestion })}
typeInfo={typeInfo.inner}
/> />
</EnvContext>; </EnvContext>;
} }
else { else {
return <EnvContext value={innerEnv}> return <EnvContext value={typeInfo.innerEnv}>
<ExprBlock <ExprBlock
state={state.inner} state={state.inner}
setState={setInner} setState={setInner}
score={suggestion => score({ ...state, inner: suggestion })} score={suggestion => score({ ...state, inner: suggestion })}
onCancel={onCancel} // keep value onCancel={onCancel} // keep value
typeInfo={typeInfo.inner}
/> />
</EnvContext> </EnvContext>
} }