From a64981523127ed5e69ed7c7304815609f46faf25 Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Sun, 3 Aug 2025 13:16:48 -0400 Subject: [PATCH 01/65] Add DynType projector --- src/haz3lcore/projectors/ProjectorCore.re | 7 +- src/haz3lcore/projectors/ProjectorInit.re | 1 + .../projectors/implementations/DynTypeProj.re | 131 ++++++++++++++++++ src/web/www/style/projectors/proj-base.css | 1 + src/web/www/style/projectors/proj-dyntype.css | 97 +++++++++++++ 5 files changed, 236 insertions(+), 1 deletion(-) create mode 100644 src/haz3lcore/projectors/implementations/DynTypeProj.re create mode 100644 src/web/www/style/projectors/proj-dyntype.css diff --git a/src/haz3lcore/projectors/ProjectorCore.re b/src/haz3lcore/projectors/ProjectorCore.re index d2a45fa4a4..df9313965a 100644 --- a/src/haz3lcore/projectors/ProjectorCore.re +++ b/src/haz3lcore/projectors/ProjectorCore.re @@ -20,6 +20,7 @@ module Kind = { type t = | Fold | Info + | DynType | Probe | Checkbox | Slider @@ -37,7 +38,8 @@ module Kind = { Livelit, ]; - let projectors: list(t) = livelit_projectors @ [Fold, Info, Probe]; + let projectors: list(t) = + livelit_projectors @ [Fold, Info, DynType, Probe]; /* A friendly name for each projector. This is used * both for identifying a projector in the CSS and for @@ -46,6 +48,7 @@ module Kind = { switch (p) { | Fold => "fold" | Info => "type" + | DynType => "dyntype" | Probe => "probe" | Checkbox => "check" | Slider => "slider" @@ -59,9 +62,11 @@ module Kind = { * name function in order to be able to select the * projector in the projector panel menu */ let of_name = (p: string): t => + // TODO Use a map built from above switch (p) { | "fold" => Fold | "type" => Info + | "dyntype" => DynType | "probe" => Probe | "check" => Checkbox | "slider" => Slider diff --git a/src/haz3lcore/projectors/ProjectorInit.re b/src/haz3lcore/projectors/ProjectorInit.re index 6f7696dff1..cb938f0d23 100644 --- a/src/haz3lcore/projectors/ProjectorInit.re +++ b/src/haz3lcore/projectors/ProjectorInit.re @@ -8,6 +8,7 @@ let to_module = (kind: ProjectorCore.Kind.t): (module Cooked) => switch (kind) { | Fold => (module Cook(FoldProj.M)) | Info => (module Cook(TypeProj.M)) + | DynType => (module Cook(DynTypeProj.M)) | Probe => (module Cook(ProbeProj.M)) | Slider => (module Cook(SliderProj.M)) | SliderF => (module Cook(SliderFProj.M)) diff --git a/src/haz3lcore/projectors/implementations/DynTypeProj.re b/src/haz3lcore/projectors/implementations/DynTypeProj.re new file mode 100644 index 0000000000..bb6c2a7286 --- /dev/null +++ b/src/haz3lcore/projectors/implementations/DynTypeProj.re @@ -0,0 +1,131 @@ +open Virtual_dom.Vdom; +open Node; +open ProjectorBase; +open Language; + +let expected_ty = (info: option(Info.t)): option(Typ.t) => + switch (info) { + | Some(InfoExp({ana, _})) + | Some(InfoPat({ana, _})) => Some(ana) + | _ => None + }; + +let self_ty = (info: option(Info.t)): option(Typ.t) => + switch (info) { + | Some(InfoExp({self, _})) => Self.typ_of_exp(self) + | Some(InfoPat({self, _})) => Self.typ_of_pat(self) + | _ => None + }; + +let totalize_ty = (expected_ty: option(Typ.t)): Typ.t => + switch (expected_ty) { + | Some(expected_ty) => expected_ty + | None => Typ.fresh(Unknown(Internal)) + }; + +module M: Projector = { + [@deriving (show({with_path: false}), sexp, yojson)] + type model = + | Expected + | Self; + + [@deriving (show({with_path: false}), sexp, yojson)] + type action = + | ToggleDisplay; + + let init = (any: Term.Any.t): option(model) => { + switch (any) { + | Exp(_) + | Pat(_) => Some(Expected) + | Any () => Some(Expected) /* Grout don't have sorts rn */ + | _ => None + }; + }; + + let dynamics = true; + let focusable = Focusable.non; + + let typ_view = (info: info, utility, view_seg: View.seg) => { + let dynamic_typ = + info.dynamics + |> Option.bind( + _, + (d: Dynamics.Info.t) => { + let statics = + Statics.mk(CoreSettings.on, Builtins.ctx_init(Some(Int))); + let type_of = (c: Dynamics.Probe.Closure.t) => { + IdTagged.rep_id(c.value) + |> Id.Map.find_opt(_, statics(c.value)) + |> Option.bind( + _, + fun + | InfoExp(e) => { + Some(e.ty); + } + | _ => None, + ); + }; + let types = List.map(type_of, d) |> Util.OptUtil.sequence; + + Option.bind( + types, + Typ.join_all(~empty=Typ.fresh(Unknown(Internal)), Ctx.empty), + ); + }, + ) + |> Option.value(~default=Typ.fresh(Unknown(Internal))); + div( + ~attrs=[Attr.classes(["dyntype-cell"])], + [Typ(dynamic_typ) |> utility.term_to_seg |> view_seg(Sort.Typ)], + ); + }; + + let update = (model, _, a: action) => + switch (a, model) { + | (ToggleDisplay, Expected) => Self + | (ToggleDisplay, Self) => Expected + }; + + let syntax_str = (info: info) => { + let max_len = 30; + let seg = Segment.unparenthesize(info.syntax); + let str = info.utility.seg_to_string(seg); + let str = Re.Str.global_replace(Re.Str.regexp("\n"), " ", str); + String.length(str) > max_len + ? String.sub(str, 0, max_len) ++ "..." : str; + }; + + let placeholder = (_m, info) => + ProjectorCore.Shape.inline(3 + String.length(syntax_str(info))); + + let syntax_view = (info: info) => info |> syntax_str |> text; + + let icon = div(~attrs=[Attr.classes(["icon"])], []); + + let view = + ( + _: model, + info: info, + ~local: action => Ui_effect.t(unit), + ~parent as _, + ~view_seg, + ) => + View.{ + inline: + div( + ~attrs=[ + Attr.classes(["main"]), + Attr.on_double_click(_ => local(ToggleDisplay)), + ], + [syntax_view(info), icon], + ), + offside: + Some( + div( + ~attrs=[Attr.classes(["offside"])], + [typ_view(info, info.utility, view_seg)], + ), + ), + overlay: None, + }; +}; diff --git a/src/web/www/style/projectors/proj-base.css b/src/web/www/style/projectors/proj-base.css index 8e3f6e97aa..0111b55355 100644 --- a/src/web/www/style/projectors/proj-base.css +++ b/src/web/www/style/projectors/proj-base.css @@ -3,6 +3,7 @@ @import "panel.css"; @import "proj-probe.css"; @import "proj-type.css"; +@import "proj-dyntype.css"; @import "proj-cards.css"; @import "proj-text.css"; @import "proj-fold.css"; diff --git a/src/web/www/style/projectors/proj-dyntype.css b/src/web/www/style/projectors/proj-dyntype.css new file mode 100644 index 0000000000..79d1824dc6 --- /dev/null +++ b/src/web/www/style/projectors/proj-dyntype.css @@ -0,0 +1,97 @@ +/* PROJECTOR: INFO */ + +:root {} + +/* BACKING */ + +.projector.dyntype>svg { + filter: drop-shadow(0.7px 0.7px 0px var(--BR3)); +} + +.projector.dyntype.indicated>svg { + fill: var(--main-indicated); + filter: drop-shadow(0.7px 0.7px 0px var(--main-shadow)); +} + +.projector.dyntype.selected>svg { + filter: none; +} + +/* INLINE VIEW */ + +.projector.dyntype:hover { + color: var(--BLACK); +} + +.projector.dyntype .main { + font-family: var(--code-font); + font-size: var(--base-font-size); + color: var(--code-text); + cursor: pointer; + display: flex; + align-items: center; + gap: 0.4em; +} + +.projector.dyntype.indicated:not(.selected):not(.error) .main { + color: white; +} + +.projector.dyntype .icon { + width: 16px; + height: 16px; + background-image: url(../../img/noun-microscope-7023456.svg); + background-size: cover; + filter: invert(1) brightness(0.4) sepia(1) saturate(1.8) hue-rotate(245deg); +} + +.projector.dyntype.indicated:not(.selected):not(.error) .main .icon { + filter: invert(1); +} + +/* OFFSIDE VIEW */ + +.projector.dyntype .offside { + display: flex; + align-items: center; + justify-content: center; + gap: 0.6em; +} + +.projector.dyntype .offside .mode { + font-weight: 800; + color: var(--main-base); + filter: brightness(0.9); +} + +.projector.dyntype.indicated .offside .mode { + opacity: 100%; + color: var(--main-indicated); +} + +.projector.dyntype .offside .dyntype-cell { + border-radius: 0.05em 0.05em 0.05em 0.2em; + background-color: var(--main-base); + border-bottom: 1px solid var(--main-indicated); +} + +.projector.dyntype.indicated .offside .dyntype-cell { + outline-width: 1px; + outline-style: solid; + outline-color: var(--main-indicated); + border-color: var(--main-indicated); +} + +.projector.dyntype .offside .code { + position: relative; + line-height: initial; + color: var(--exp-indicated); +} + + +.projector.dyntype .code .token.mono { + color: var(--exp-indicated); +} +.projector.dyntype .code .token.poly { + color: var(--exp-indicated); +} \ No newline at end of file From 1a40403626c0aef8bbb2a246f27392de58caac55 Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Tue, 5 Aug 2025 15:14:00 -0400 Subject: [PATCH 02/65] Add shortcut for Dynamic Type with alt+d hotkey --- src/web/app/input/Shortcut.re | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/web/app/input/Shortcut.re b/src/web/app/input/Shortcut.re index 5ccc83e177..65447775e1 100644 --- a/src/web/app/input/Shortcut.re +++ b/src/web/app/input/Shortcut.re @@ -130,6 +130,13 @@ let shortcuts = (sys: Util.Key.sys): list(t) => "Type", Globals(ActiveEditor(Project(SetIndicated(Specific(Info))))), ), + mk_shortcut( + ~hotkey="alt+d", + ~mdIcon="camera", + ~section="Projection", + "Dynamic Type", + Globals(ActiveEditor(Project(SetIndicated(Specific(DynType))))), + ), mk_shortcut( ~hotkey="alt+l", ~mdIcon="camera", From 3d1547768ed78756953ef355c13e7e2eb3492ba1 Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Tue, 12 Aug 2025 12:13:54 -0400 Subject: [PATCH 03/65] Add Typ.consistent_join for the dyntype projector --- src/haz3lcore/pretty/ExpToSegment.re | 3 + .../projectors/implementations/DynTypeProj.re | 19 +--- src/language/term/Grammar.re | 2 + src/language/term/TermBase.re | 1 + src/language/term/Typ.re | 59 +++++++--- src/web/app/explainthis/ExplainThis.re | 1 + test/Test_Grammar.re | 1 + test/Test_Typ.re | 106 +++++++++++++++++- 8 files changed, 160 insertions(+), 32 deletions(-) diff --git a/src/haz3lcore/pretty/ExpToSegment.re b/src/haz3lcore/pretty/ExpToSegment.re index 9f4eed5435..1b2be1d18b 100644 --- a/src/haz3lcore/pretty/ExpToSegment.re +++ b/src/haz3lcore/pretty/ExpToSegment.re @@ -132,6 +132,7 @@ let external_precedence_typ = (tp: Typ.t) => | Unknown(Internal) | Unknown(SynSwitch) | Unknown(Hole(EmptyHole)) + | Unknown(Inconsistent) | Var(_) | Atom(_) | Label(_) @@ -467,6 +468,7 @@ and parenthesize_typ = | Var(_) | Unknown(Hole(Invalid(_))) | Unknown(Internal) + | Unknown(Inconsistent) | Unknown(SynSwitch) | Unknown(Hole(EmptyHole)) | Atom(_) => typ @@ -1239,6 +1241,7 @@ and typ_to_pretty = (~settings: Settings.t, typ: Typ.t): pretty => { | Unknown(Hole(Invalid(s))) => text_to_pretty(typ |> Typ.rep_id, Sort.Typ, s) | Unknown(Internal) + | Unknown(Inconsistent) | Unknown(SynSwitch) | Unknown(Hole(EmptyHole)) => if (settings.show_unknown_as_hole) { diff --git a/src/haz3lcore/projectors/implementations/DynTypeProj.re b/src/haz3lcore/projectors/implementations/DynTypeProj.re index bb6c2a7286..3d10d4c6b8 100644 --- a/src/haz3lcore/projectors/implementations/DynTypeProj.re +++ b/src/haz3lcore/projectors/implementations/DynTypeProj.re @@ -3,26 +3,12 @@ open Node; open ProjectorBase; open Language; -let expected_ty = (info: option(Info.t)): option(Typ.t) => - switch (info) { - | Some(InfoExp({ana, _})) - | Some(InfoPat({ana, _})) => Some(ana) - | _ => None - }; - let self_ty = (info: option(Info.t)): option(Typ.t) => switch (info) { | Some(InfoExp({self, _})) => Self.typ_of_exp(self) | Some(InfoPat({self, _})) => Self.typ_of_pat(self) | _ => None }; - -let totalize_ty = (expected_ty: option(Typ.t)): Typ.t => - switch (expected_ty) { - | Some(expected_ty) => expected_ty - | None => Typ.fresh(Unknown(Internal)) - }; - module M: Projector = { [@deriving (show({with_path: false}), sexp, yojson)] type model = @@ -67,10 +53,7 @@ module M: Projector = { }; let types = List.map(type_of, d) |> Util.OptUtil.sequence; - Option.bind( - types, - Typ.join_all(~empty=Typ.fresh(Unknown(Internal)), Ctx.empty), - ); + Option.map(Typ.consistent_join(Ctx.empty), types); }, ) |> Option.value(~default=Typ.fresh(Unknown(Internal))); diff --git a/src/language/term/Grammar.re b/src/language/term/Grammar.re index 27bbe28a0a..631067a41d 100644 --- a/src/language/term/Grammar.re +++ b/src/language/term/Grammar.re @@ -154,6 +154,7 @@ and type_provenance('a) = | SynSwitch | Hole(type_hole('a)) | Internal + | Inconsistent and filter('a) = { pat: exp_t('a), act: FilterAction.t, @@ -416,6 +417,7 @@ and map_type_provenance_annotation: | SynSwitch => SynSwitch | Hole(h) => Hole(map_type_hole_annotation(f, h)) | Internal => Internal + | Inconsistent => Inconsistent }; } and map_type_hole_annotation: diff --git a/src/language/term/TermBase.re b/src/language/term/TermBase.re index cd5a2d6892..fa18bbfdf9 100644 --- a/src/language/term/TermBase.re +++ b/src/language/term/TermBase.re @@ -590,6 +590,7 @@ and Typ: { | Unknown(Hole(Invalid(_))) | Unknown(SynSwitch) | Unknown(Internal) + | Unknown(Inconsistent) | Atom(_) | Label(_) | Var(_) => term diff --git a/src/language/term/Typ.re b/src/language/term/Typ.re index d921a65912..e488b44fc5 100644 --- a/src/language/term/Typ.re +++ b/src/language/term/Typ.re @@ -9,6 +9,7 @@ type cls = | MultiHole | SynSwitch | Internal + | Inconsistent | Arrow | Prod | TupLabel @@ -81,6 +82,7 @@ let cls_of_term: Grammar.typ_term('a) => cls = | Unknown(Hole(MultiHole(_))) => MultiHole | Unknown(SynSwitch) => SynSwitch | Unknown(Internal) => Internal + | Unknown(Inconsistent) => Inconsistent | Atom(c) => Atom(c) | List(_) => List | Arrow(_) => Arrow @@ -99,6 +101,7 @@ let show_cls: cls => string = | Invalid => "Invalid type" | MultiHole => "Broken type" | EmptyHole => "Type hole" + | Inconsistent => "Join of Inconsistent types" | SynSwitch => "Synthetic type" | Internal => "Internal type" | Atom(_) => "Base type" @@ -224,6 +227,8 @@ let join_type_provenance = | (Internal, SynSwitch) => SynSwitch | (Internal | Hole(_), _) | (_, Hole(_)) => Internal + | (Inconsistent, _) + | (_, Inconsistent) => Inconsistent | (SynSwitch, SynSwitch) => SynSwitch }; @@ -430,13 +435,23 @@ let equal = (t1: t, t2: t): bool => fast_equal(t1, t2); resolve parameter specifies whether, in the case of a type variable and a succesful join, to return the resolved join type, or to return the (first) type variable for readability */ -let rec join = (~resolve=false, ctx: Ctx.t, ty1: t, ty2: t): option(t) => { - let join' = join(~resolve, ctx); +let rec join = + ( + ~inconsistent: option(t)=?, + ~resolve=false, + ctx: Ctx.t, + ty1: t, + ty2: t, + ) + : option(t) => { + let join' = join(~inconsistent?, ~resolve, ctx); switch (term_of(ty1), term_of(ty2)) { | (_, Parens(ty2)) => join'(ty1, ty2) | (Parens(ty1), _) => join'(ty1, ty2) | (Unknown(p1), Unknown(p2)) => Some(Unknown(join_type_provenance(p1, p2)) |> temp) + | (Unknown(Inconsistent), _) => Some(ty1) + | (_, Unknown(Inconsistent)) => Some(ty2) | (Unknown(_), _) => Some(ty2) | (_, Unknown(_)) => Some(ty1) | (Var(n1), Var(n2)) => @@ -466,7 +481,7 @@ let rec join = (~resolve=false, ctx: Ctx.t, ty1: t, ty2: t): option(t) => { }; let+ ty_body = join(~resolve, ctx, ty1', ty2); Rec(tp1, ty_body) |> temp; - | (Rec(_), _) => None + | (Rec(_), _) => inconsistent | (Forall(x1, ty1), Forall(x2, ty2)) => let ty1' = switch (TPat.tyvar_of_utpat(x2)) { @@ -482,25 +497,25 @@ let rec join = (~resolve=false, ctx: Ctx.t, ty1: t, ty2: t): option(t) => { be exposed to the user. We preserve the variable name of the second type to preserve synthesized type variable names, which come from user annotations. */ - | (Forall(_), _) => None + | (Forall(_), _) => inconsistent | (Atom(c1), Atom(c2)) when c1 == c2 => Some(ty1) - | (Atom(_), _) => None + | (Atom(_), _) => inconsistent | (Label(_), Label("")) => Some(ty1) | (Label(""), Label(_)) => Some(ty2) | (Label(name1), Label(name2)) when LabeledTuple.match_labels(name1, name2) => Some(ty1) - | (Label(_), _) => None + | (Label(_), _) => inconsistent | (Arrow(ty1, ty2), Arrow(ty1', ty2')) => let* ty1 = join'(ty1, ty1'); let+ ty2 = join'(ty2, ty2'); Arrow(ty1, ty2) |> temp; - | (Arrow(_), _) => None + | (Arrow(_), _) => inconsistent | (TupLabel(lab1, ty1'), TupLabel(lab2, ty2')) => let* lab = join'(lab1, lab2); let+ ty = join'(ty1', ty2'); TupLabel(lab, ty) |> temp; - | (TupLabel(_), _) => None + | (TupLabel(_), _) => inconsistent | (Prod(tys1), Prod(tys2)) => if (List.length(tys1) != List.length(tys2)) { None; @@ -509,15 +524,15 @@ let rec join = (~resolve=false, ctx: Ctx.t, ty1: t, ty2: t): option(t) => { let+ tys = OptUtil.sequence(tys); Prod(tys) |> temp; } - | (Prod(_), _) => None + | (Prod(_), _) => inconsistent | (Sum(sm1), Sum(sm2)) => let+ sm' = ConstructorMap.join(equal, join(~resolve, ctx), sm1, sm2); Sum(sm') |> temp; - | (Sum(_), _) => None + | (Sum(_), _) => inconsistent | (List(ty1), List(ty2)) => let+ ty = join'(ty1, ty2); List(ty) |> temp; - | (List(_), _) => None + | (List(_), _) => inconsistent | (Ap(_), _) => failwith("Type join of ap") }; }; @@ -561,9 +576,10 @@ let rec match_synswitch = (t1: t, t2: t) => { }; }; -let join_all = (~empty: t, ctx: Ctx.t, ts: list(t)): option(t) => +let join_all = + (~inconsistent=?, ~empty: t, ctx: Ctx.t, ts: list(t)): option(t) => List.fold_left( - (acc, ty) => OptUtil.and_then(join(ctx, ty), acc), + (acc, ty) => OptUtil.and_then(join(~inconsistent?, ctx, ty), acc), Some(empty), ts, ); @@ -571,6 +587,23 @@ let join_all = (~empty: t, ctx: Ctx.t, ts: list(t)): option(t) => let is_consistent = (ctx: Ctx.t, ty1: t, ty2: t): bool => join(ctx, ty1, ty2) != None; +/* + Computes the most precise type that is consistent with all input types. + Like a multi-way join, but if any ground types are inconsistent, replaces + that position with an "unknown" type rather than failing. + This operation is NOT associative — applying it pairwise may yield + a different result than applying it to the whole list at once. + */ +let consistent_join = (ctx: Ctx.t, tys: list(t)): t => { + join_all( + ~inconsistent=Unknown(Inconsistent) |> temp, + ~empty=Unknown(SynSwitch) |> temp, + ctx, + tys, + ) + |> Option.value(~default=Unknown(SynSwitch) |> temp); +}; + /** * Determines if one type (`ty1`) is more precise than another type (`ty2`) within a given context (`ctx`). * diff --git a/src/web/app/explainthis/ExplainThis.re b/src/web/app/explainthis/ExplainThis.re index 1040d0559b..0366171ad2 100644 --- a/src/web/app/explainthis/ExplainThis.re +++ b/src/web/app/explainthis/ExplainThis.re @@ -2400,6 +2400,7 @@ let get_doc = switch (bypass_parens_typ(term).term) { | Unknown(SynSwitch) | Unknown(Internal) + | Unknown(Inconsistent) | Unknown(Hole(EmptyHole)) => get_message(HoleTyp.empty_hole) | Unknown(Hole(MultiHole(_))) => get_message(HoleTyp.multi_hole) | Atom(Int) => get_message(TerminalTyp.int) diff --git a/test/Test_Grammar.re b/test/Test_Grammar.re index c1e495ea66..8e78ed8f7d 100644 --- a/test/Test_Grammar.re +++ b/test/Test_Grammar.re @@ -149,6 +149,7 @@ let sample_type = (cls_typ: Typ.cls): Grammar.UnitGrammar.typ => { | EmptyHole => unknown(Hole(EmptyHole)) | SynSwitch => unknown(SynSwitch) | Internal => unknown(Internal) + | Inconsistent => unknown(Inconsistent) | Label => label("label") | MultiHole => unknown(Hole(MultiHole([]))) | Sum => sum([]) diff --git a/test/Test_Typ.re b/test/Test_Typ.re index 6c6b2ce924..8ceccbf047 100644 --- a/test/Test_Typ.re +++ b/test/Test_Typ.re @@ -1,5 +1,6 @@ open Alcotest; open Language; +let testable_typ = testable(Fmt.using(Typ.show, Fmt.string), Typ.fast_equal); let tests = ( "Typ", @@ -15,7 +16,7 @@ let tests = ( Forall(Var("b") |> TPat.temp, Var("b") |> Typ.temp) |> Typ.temp, ); check( - option(testable(Fmt.using(Typ.show, Fmt.string), Typ.fast_equal)), + option(testable_typ), "Forall alpha equivalent", Some( Forall(Var("a") |> TPat.temp, Var("a") |> Typ.temp) |> Typ.temp, @@ -50,5 +51,108 @@ let tests = ( ); }, ), + test_case( + "consistent_join on equivalent atomic types", + `Quick, + () => { + open IdTagged.FreshGrammar.Typ; + let t3 = + Typ.consistent_join( + Builtins.ctx_init(Some(Int)), + [string(), string()], + ); + check( + testable_typ, + "consistent_join on equivalent atomic types", + string(), + t3, + ); + }, + ), + test_case( + "consistent_join on inconsistent atomic types", + `Quick, + () => { + open IdTagged.FreshGrammar.Typ; + let t3 = + Typ.consistent_join( + Builtins.ctx_init(Some(Int)), + [string(), int()], + ); + check( + testable_typ, + "consistent_join on inconsistent atomic types", + unknown(Internal), + t3, + ); + }, + ), + test_case( + "consistent_join on lists of inconsistent atomic types", + `Quick, + () => { + open IdTagged.FreshGrammar.Typ; + let t3 = + Typ.consistent_join( + Builtins.ctx_init(Some(Int)), + [list(string()), list(int())], + ); + check( + testable_typ, + "consistent_join on equivalent function types", + list(unknown(Internal)), + t3, + ); + }, + ), + test_case( + "consistent_join on arrow types with inconsistent parts", + `Quick, + () => { + open IdTagged.FreshGrammar.Typ; + let t3 = + Typ.consistent_join( + Builtins.ctx_init(Some(Int)), + [arrow(string(), list(int())), arrow(int(), list(string()))], + ); + check( + testable_typ, + "consistent_join on arrow types with inconsistent parts", + arrow(unknown(Inconsistent), list(unknown(Inconsistent))), + t3, + ); + }, + ), + test_case( + "Consistent join collapses unknowns", + `Quick, + () => { + open IdTagged.FreshGrammar.Typ; + let t3 = + Typ.consistent_join( + Builtins.ctx_init(Some(Int)), + [unknown(Hole(EmptyHole)), int()], + ); + check(testable_typ, "Consistent join collapses unknowns", int(), t3); + }, + ), + test_case( + "Consistent join does not collapse inconsistent unknowns", + `Quick, + () => { + open IdTagged.FreshGrammar.Typ; + let t3 = + Typ.consistent_join( + Builtins.ctx_init(Some(Int)), + [int(), string(), float()], + ); + check( + testable_typ, + "Consistent join does not collapse inconsistent unknowns", + unknown(Inconsistent), + t3, + ); + }, + ), ], ); From a82b64c8a6b2fe0268a302c06d58201d96b92ff4 Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala Date: Wed, 24 Sep 2025 17:49:31 -0400 Subject: [PATCH 04/65] temp --- src/haz3lcore/lang/MakeTerm.re | 9 +++- src/haz3lcore/pretty/ExpToSegment.re | 11 +++- .../projectors/implementations/DynTypeProj.re | 5 ++ .../dynamics/transition/Ascriptions.re | 51 ++++++++++++++++--- .../dynamics/transition/Transition.re | 12 ++++- src/language/dynamics/transition/Unboxing.re | 2 + src/language/statics/Coverage.re | 26 ++++++---- src/language/statics/Statics.re | 3 ++ src/language/term/Abbreviate.re | 1 + src/language/term/Grammar.re | 6 +++ src/language/term/IdTagged.re | 13 ++--- src/language/term/TermBase.re | 5 ++ src/language/term/Typ.re | 30 +++++++++-- src/menhirParser/Conversion.re | 1 + src/util/Id.re | 16 ++---- src/web/app/common/ProjectorView.re | 7 +++ src/web/app/editors/code/CodeEditable.re | 1 + src/web/app/explainthis/ExplainThis.re | 1 + 18 files changed, 155 insertions(+), 45 deletions(-) diff --git a/src/haz3lcore/lang/MakeTerm.re b/src/haz3lcore/lang/MakeTerm.re index 136ffcc2dd..41ae557ea5 100644 --- a/src/haz3lcore/lang/MakeTerm.re +++ b/src/haz3lcore/lang/MakeTerm.re @@ -647,7 +647,7 @@ and typ_term: unsorted => (Typ.term, list(Id.t)) = { fun | Op(tiles) as tm => switch (tiles) { - | ([(_id, tile)], []) => + | ([(id, tile)], []) => ret( switch (tile) { | ([t], []) when Token.is_empty_tuple(t) => Prod([]) @@ -661,7 +661,12 @@ and typ_term: unsorted => (Typ.term, list(Id.t)) = { | ([t], []) when Token.is_quoted_label(t) => Label(Token.sub(t, 1, Token.length(t) - 2)) | (["(", ")"], [Typ(body)]) => Parens(body) - | (label, [Typ(body)]) when is_probe_wrap(label) => body.term + | (label, [Typ(body)]) when is_probe_wrap(label) => + print_endline("Parsing probe wrap for type"); + print_endline("Id: " ++ Id.str3(id)); + let should = should_instrument(id); + print_endline("Should instrument: " ++ string_of_bool(should)); + should ? Probe(body, Probe.empty) : body.term; | (["[", "]"], [Typ(body)]) => List(body) | ([t], []) when is_hole_label(t) => hole(tm) | ([t], []) => Unknown(Hole(Invalid(t))) diff --git a/src/haz3lcore/pretty/ExpToSegment.re b/src/haz3lcore/pretty/ExpToSegment.re index deadc6b53e..cb3df213c6 100644 --- a/src/haz3lcore/pretty/ExpToSegment.re +++ b/src/haz3lcore/pretty/ExpToSegment.re @@ -126,7 +126,7 @@ let external_precedence_pat = (dp: Pat.t) => | MultiHole(_) => Precedence.min }; -let external_precedence_typ = (tp: Typ.t) => +let rec external_precedence_typ = (tp: Typ.t) => switch (Typ.term_of(tp)) { // Indivisible forms never need parentheses around them | Unknown(Hole(Invalid(_))) @@ -152,6 +152,7 @@ let external_precedence_typ = (tp: Typ.t) => // Matt: I think multiholes are min because we don't know the precedence of the `⟩?⟨`s | Unknown(Hole(MultiHole(_))) => Precedence.min + | Probe(typ, _) => external_precedence_typ(typ) }; let paren_at = (internal_precedence: Precedence.t, exp: Exp.t): Exp.t => @@ -536,6 +537,13 @@ and parenthesize_typ = Hole(MultiHole(List.map(parenthesize_any(~show_filters), xs))), ) |> rewrap + | Probe(t, pr) => + Probe( + parenthesize_typ(~already_paren=true, t) + |> paren_typ_at(Precedence.min), + pr, + ) + |> rewrap }; } @@ -1410,6 +1418,7 @@ and typ_to_pretty = (~settings: Settings.t, typ: Typ.t): pretty => { @ List.flatten( List.map2((id, t) => [mk_form(TypPlus, id, [])] @ t, ids, ts), ); + | Probe(typ, _) => go(typ) }; } and tpat_to_pretty = (~settings: Settings.t, tpat: TPat.t): pretty => { diff --git a/src/haz3lcore/projectors/implementations/DynTypeProj.re b/src/haz3lcore/projectors/implementations/DynTypeProj.re index 3d10d4c6b8..77a8ada80f 100644 --- a/src/haz3lcore/projectors/implementations/DynTypeProj.re +++ b/src/haz3lcore/projectors/implementations/DynTypeProj.re @@ -24,6 +24,7 @@ module M: Projector = { | Exp(_) | Pat(_) => Some(Expected) | Any () => Some(Expected) /* Grout don't have sorts rn */ + | Typ(_) => Some(Expected) | _ => None }; }; @@ -32,6 +33,10 @@ module M: Projector = { let focusable = Focusable.non; let typ_view = (info: info, utility, view_seg: View.seg) => { + info.dynamics + |> Option.map([%derive.show: Language.Dynamics.Info.t]) + |> Option.value(~default="None") + |> (y => print_endline("Dynamics: " ++ y)); let dynamic_typ = info.dynamics |> Option.bind( diff --git a/src/language/dynamics/transition/Ascriptions.re b/src/language/dynamics/transition/Ascriptions.re index 76f2c40991..5bc5f9054d 100644 --- a/src/language/dynamics/transition/Ascriptions.re +++ b/src/language/dynamics/transition/Ascriptions.re @@ -7,16 +7,33 @@ Ascriptions should be propagated inside of expressions when consistent. e.g. [1, 2] : [Int] -> [1 : Int, 2 : Int] */ -let rec transition = (~recursive=false, d: DHExp.t): option(DHExp.t) => { + +let rec transition = + ( + ~update_probe=( + _syntax_id: Id.t, + _value: DHExp.t, + _env: Environment.t, + _call_stack: Probe.call_stack, + _pr: Probe.t, + ) => + (), + ~recursive=false, + d: DHExp.t, + ) + : option(DHExp.t) => { let recur = (d: DHExp.t): DHExp.t => if (recursive) { - transition(~recursive, d) |> Option.value(~default=d); + transition(~update_probe, ~recursive, d) |> Option.value(~default=d); } else { d; }; switch (DHExp.term_of(d)) { | Asc(e, t) => switch (DHExp.term_of(e), Typ.term_of(Typ.unroll(t))) { + | (_, Probe(t, p)) => + update_probe(Exp.rep_id(e), e, Environment.empty, [], p); + Asc(e, t) |> DHExp.fresh |> transition(~update_probe, ~recursive); | (Asc(e, t'), t) // This is only necessary because sometimes we add two ascriptions and aren't marking it as a non-value when @@ -33,9 +50,17 @@ let rec transition = (~recursive=false, d: DHExp.t): option(DHExp.t) => { } | (e, Parens(t)) => // This is an impossible case since types should be normalized before coming to transitions - transition(~recursive, Asc(e |> DHExp.fresh, t) |> DHExp.fresh) + transition( + ~update_probe, + ~recursive, + Asc(e |> DHExp.fresh, t) |> DHExp.fresh, + ) | (Closure(ce, d), t) => - transition(~recursive, Asc(d, t |> Typ.fresh) |> DHExp.fresh) + transition( + ~update_probe, + ~recursive, + Asc(d, t |> Typ.fresh) |> DHExp.fresh, + ) |> Option.map(d => Closure(ce, d) |> DHExp.fresh) | (Fun(p, e, t, v), Arrow(t1, t2)) => Some( @@ -225,9 +250,21 @@ let rec transition = (~recursive=false, d: DHExp.t): option(DHExp.t) => { }; }; -let rec transition_multiple = (d: DHExp.t): DHExp.t => { - switch (transition(~recursive=true, d)) { - | Some(d'') => transition_multiple(d'') +let rec transition_multiple = + ( + ~update_probe=( + _syntax_id: Id.t, + _value: DHExp.t, + _env: Environment.t, + _call_stack: Probe.call_stack, + _pr: Probe.t, + ) => + (), + d: DHExp.t, + ) + : DHExp.t => { + switch (transition(~update_probe, ~recursive=true, d)) { + | Some(d'') => transition_multiple(~update_probe, d'') | None => d }; }; diff --git a/src/language/dynamics/transition/Transition.re b/src/language/dynamics/transition/Transition.re index 6c6bfa0ec6..e6474e735e 100644 --- a/src/language/dynamics/transition/Transition.re +++ b/src/language/dynamics/transition/Transition.re @@ -189,6 +189,16 @@ module Transition = (EV: EV_MODE) => { d, ) : EV.result => { + print_endline("Transitioning: " ++ DHExp.show(d)); + let update_probe' = (id, d, env, call_stack, pr) => { + print_endline( + "Updating probe: " ++ Id.str3(id) ++ " with value " ++ DHExp.show(d), + ); + update_probe( + state, + Dynamics.Probe.Closure.mk(id, d, env, call_stack, pr), + ); + }; // Split DHExp into term and id information let (term, rewrap) = DHExp.unwrap(d); let wrap_ctx = (term): EvalCtx.t => @@ -906,7 +916,7 @@ module Transition = (EV: EV_MODE) => { let.wrap_closure _ = env; Indet; | Asc(d', t) => - switch (Ascriptions.transition(d)) { + switch (Ascriptions.transition(~update_probe=update_probe', d)) { | Some(d') => let. _ = otherwise(env, d); Step({ diff --git a/src/language/dynamics/transition/Unboxing.re b/src/language/dynamics/transition/Unboxing.re index e8e13bdadd..cea9ff7897 100644 --- a/src/language/dynamics/transition/Unboxing.re +++ b/src/language/dynamics/transition/Unboxing.re @@ -129,7 +129,9 @@ let rec unbox: type a. (unbox_request(a), DHExp.t) => unboxed(a) = let unbox_tup_label = (d: Exp.t): option((option(LabeledTuple.label), Exp.t)) => { switch (Ascriptions.transition_multiple(d).term) { + // TODO // TODO Think about whether we should transition here + | TupLabel({term: Label(l), _}, e) => Some((Some(l), e)) | _ => Some((None, d)) }; diff --git a/src/language/statics/Coverage.re b/src/language/statics/Coverage.re index e24d8e5cb0..75da571740 100644 --- a/src/language/statics/Coverage.re +++ b/src/language/statics/Coverage.re @@ -140,6 +140,7 @@ module Ctr = { failwith( "all_ctrs_of_type called with a non-normalized type: " ++ Typ.show(ty), ) + | Probe(ty, _) => all_ctrs_of_typ(ty) }; }; @@ -279,7 +280,7 @@ module UnseenPatternList: UnseenPatternList = { }; /* Appends any Ctr to the beginning of the unseen pattern list*/ - let cons_ctr = (ctr: Ctr.t, col_type: Typ.t, unseen_pattern: t) => { + let rec cons_ctr = (ctr: Ctr.t, col_type: Typ.t, unseen_pattern: t) => { let pat_list = unseen_pattern.pat; let cons_pat_t = (pat, unseen_pattern) => cons_pat_t(pat, unseen_pattern); @@ -419,6 +420,7 @@ module UnseenPatternList: UnseenPatternList = { "prepend_ctr called with a non-normalized type: " ++ Typ.show(col_type), ) + | Probe(ty, _) => cons_ctr(ctr, ty, unseen_pattern) }; }; @@ -455,14 +457,14 @@ module UnseenPatternList: UnseenPatternList = { /* Takes a type appends it to the start of the list. The list may receive additional modifications outside of just the type being appended. This behavior is type dependent*/ - let cons_from_type = - ( - seen_in_col: Seen.t, - col_type: Typ.t, - col_ctr: Ctr.t, - unseen_pattern: t, - ) - : t => { + let rec cons_from_type = + ( + seen_in_col: Seen.t, + col_type: Typ.t, + col_ctr: Ctr.t, + unseen_pattern: t, + ) + : t => { let all_ctrs = Ctr.all_ctrs_of_typ(col_type); let pat_list = unseen_pattern.pat; @@ -551,10 +553,13 @@ module UnseenPatternList: UnseenPatternList = { "cons_from_type called with a non-normalized type: " ++ Typ.show(col_type), ) + | Probe(ty, _) => + cons_from_type(seen_in_col, ty, col_ctr, unseen_pattern) }; }; - let cons_type_default = (col_type: Typ.t, col_ctr: Ctr.t, unseen_pattern: t) => { + let rec cons_type_default = + (col_type: Typ.t, col_ctr: Ctr.t, unseen_pattern: t) => { let all_ctrs = Ctr.all_ctrs_of_typ(col_type); let pat_list = unseen_pattern.pat; @@ -609,6 +614,7 @@ module UnseenPatternList: UnseenPatternList = { "prepend_from_type called with a non-normalized type: " ++ Typ.show(col_type), ) + | Probe(ty, _) => cons_type_default(ty, col_ctr, unseen_pattern) }; }; diff --git a/src/language/statics/Statics.re b/src/language/statics/Statics.re index 643110bff1..af4a5a796f 100644 --- a/src/language/statics/Statics.re +++ b/src/language/statics/Statics.re @@ -1988,6 +1988,9 @@ and utyp_to_info_map = |> snd; let m = utpat_to_info_map(~ctx, ~ancestors, utpat, m) |> snd; add(m); // TODO: check with andrew + | Probe(typ, _) => + let m = utyp_to_info_map(~ctx, ~ancestors, typ, m) |> snd; + add(m); }; } and utpat_to_info_map = diff --git a/src/language/term/Abbreviate.re b/src/language/term/Abbreviate.re index 14b1073b84..86377dde49 100644 --- a/src/language/term/Abbreviate.re +++ b/src/language/term/Abbreviate.re @@ -925,6 +925,7 @@ and abbreviate_typ = (typ: Typ.t): Typ.t => { ); }; } + | Probe(typ, _) => abbreviate_typ(typ).term }; rewrap(term); } diff --git a/src/language/term/Grammar.re b/src/language/term/Grammar.re index f5fcc21613..774e6306b8 100644 --- a/src/language/term/Grammar.re +++ b/src/language/term/Grammar.re @@ -125,6 +125,7 @@ and typ_term('a) = | Parens(typ_t('a)) | Rec(tpat_t('a), typ_t('a)) | Forall(tpat_t('a), typ_t('a)) + | Probe(typ_t('a), Probe.t) and typ_t('a) = Annotated.t(typ_term('a), 'a) and tpat_term('a) = | Invalid(string) @@ -346,6 +347,7 @@ and map_typ_annotation: 'a 'b. ('a => 'b, typ_t('a)) => typ_t('b) = TupLabel(map_typ_annotation(f, t1), map_typ_annotation(f, t2)) | Sum(m) => Sum(ConstructorMap.map_preserving(map_typ_annotation(f), m)) + | Probe(t, probe) => Probe(map_typ_annotation(f, t), probe) }, annotation: new_annotation, }; @@ -809,6 +811,10 @@ module Factory = (DefaultAnnotation: DefaultAnnotation) => { term: Unknown(Hole(EmptyHole)), annotation: default_annotation(ann), }; + let probe = (~ann=?, t, probe): typ_t(DefaultAnnotation.t) => { + term: Probe(t, probe), + annotation: default_annotation(ann), + }; }; module TPat = { diff --git a/src/language/term/IdTagged.re b/src/language/term/IdTagged.re index 9a18f02be5..0337b6189a 100644 --- a/src/language/term/IdTagged.re +++ b/src/language/term/IdTagged.re @@ -2,10 +2,7 @@ open Util; module IdTag = { [@deriving (show({with_path: false}), sexp, yojson, eq)] - type t = { - [@show.opaque] - ids: list(Id.t), - }; + type t = {ids: list(Id.t)}; let fresh = (): t => {ids: [Id.mk()]}; }; @@ -14,10 +11,10 @@ module IdTag = { type t('a) = Grammar.Annotated.t('a, IdTag.t); // To be used if you want to remove the id from the debug output -// let pp: ((Format.formatter, 'a) => unit, Format.formatter, t('a)) => unit = -// (fmt_a, formatter, ta) => { -// fmt_a(formatter, ta.term); -// }; +let pp: ((Format.formatter, 'a) => unit, Format.formatter, t('a)) => unit = + (fmt_a, formatter, ta) => { + fmt_a(formatter, ta.term); + }; let fresh = (term: 'a): Grammar.Annotated.t('a, IdTag.t) => { { term, diff --git a/src/language/term/TermBase.re b/src/language/term/TermBase.re index aaf140d43b..02b882af59 100644 --- a/src/language/term/TermBase.re +++ b/src/language/term/TermBase.re @@ -620,6 +620,7 @@ and Typ: { ) | Rec(tp, t) => Rec(tpat_map_term(tp), typ_map_term(t)) | Forall(tp, t) => Forall(tpat_map_term(tp), typ_map_term(t)) + | Probe(t, probe) => Probe(typ_map_term(t), probe) }, }; x |> f_typ(rec_call); @@ -649,6 +650,7 @@ and Typ: { | List(ty) => List(subst(s, x, ty)) |> rewrap | Var(y) => str == y ? s : Var(y) |> rewrap | Parens(ty) => Parens(subst(s, x, ty)) |> rewrap + | Probe(ty, probe) => Probe(subst(s, x, ty), probe) |> rewrap }; | None => ty }; @@ -709,6 +711,9 @@ and Typ: { | (Sum(_), _) => false | (Var(n1), Var(n2)) => n1 == n2 | (Var(_), _) => false + | (Probe(t1, _), Probe(t2, _)) => + eq_internal(~alpha_equivalence, n, t1, t2) + | (Probe(_), _) => false }; }; diff --git a/src/language/term/Typ.re b/src/language/term/Typ.re index 1765ba01b0..5a7f1d8184 100644 --- a/src/language/term/Typ.re +++ b/src/language/term/Typ.re @@ -92,7 +92,8 @@ let cls_of_term: Grammar.typ_term('a) => cls = | Parens(_) => Parens | Sum(_) => Sum | Rec(_) => Rec - | Forall(_) => Forall; + | Forall(_) => Forall + | Probe(_, _) => EmptyHole; let show_cls: cls => string = fun @@ -129,14 +130,16 @@ let rec is_arrow = (typ: t) => { | Sum(_) | Forall(_) | Rec(_) => false + | Probe(typ, _) => is_arrow(typ) }; }; -let is_atom = (ty: t): bool => +let rec is_atom = (ty: t): bool => switch (ty.term) { | Atom(_) => true - | Parens(_) - | TupLabel(_) + | Parens(ty) + | TupLabel(_, ty) => is_atom(ty) + | Probe(ty, _) => is_atom(ty) | Arrow(_) | Unknown(_) | List(_) @@ -168,6 +171,7 @@ let rec has_fun = (typ: t) => sm, ) | Prod(tys) => List.exists(has_fun, tys) + | Probe(typ, _) => has_fun(typ) }; let rec is_forall = (typ: t) => { @@ -184,6 +188,7 @@ let rec is_forall = (typ: t) => { | Var(_) | Sum(_) | Rec(_) => false + | Probe(typ, _) => is_forall(typ) }; }; @@ -254,6 +259,7 @@ let rec free_vars = (~bound=[], ty: t): list(Var.t) => | Rec(x, ty) | Forall(x, ty) => free_vars(~bound=(x |> TPat.tyvar_of_utpat |> Option.to_list) @ bound, ty) + | Probe(ty, _) => free_vars(~bound, ty) }; let rec vars = (ty: t): list(Var.t) => @@ -282,6 +288,7 @@ let rec vars = (ty: t): list(Var.t) => | Forall(_, ty) => vars(ty) | Label(_) => [] | TupLabel(_, ty) => vars(ty) + | Probe(ty, _) => vars(ty) }; let rec aliases_deep = (ctx: Ctx.t, ty: t): list((string, t)) => { @@ -335,6 +342,7 @@ let rec num_nodes = (ty: t): int => { | Forall(_, ty) => 1 + num_nodes(ty) | Label(_) => 1 | TupLabel(_, ty) => 1 + num_nodes(ty) + | Probe(ty, _) => 1 + num_nodes(ty) }; }; @@ -364,6 +372,7 @@ let rec count_unknowns = (ty: t): int => | Forall(_, ty) => count_unknowns(ty) | Label(_) => 0 | TupLabel(_, ty) => count_unknowns(ty) + | Probe(ty, _) => count_unknowns(ty) }; let rec contains_sum_or_var = (ty: t): bool => @@ -380,6 +389,7 @@ let rec contains_sum_or_var = (ty: t): bool => | Forall(_, ty) => contains_sum_or_var(ty) | Label(_) => false | TupLabel(_, ty) => contains_sum_or_var(ty) + | Probe(ty, _) => contains_sum_or_var(ty) }; let unroll = (ty: t): t => @@ -495,6 +505,10 @@ let rec join = let+ ty = join'(ty1, ty2); List(ty) |> temp; | (List(_), _) => inconsistent + | (Probe(ty1, _), Probe(ty2, _)) => + let+ ty = join'(ty1, ty2); + ty; + | (Probe(ty1, _), _) => join'(ty1, ty2) }; }; @@ -533,6 +547,8 @@ let rec match_synswitch = (t1: t, t2: t) => { // HACK[Matt]: The only possible forall is `Forall Syn -> Syn` | (Forall(_), Forall(_)) => t2 | (Forall(_), _) => t1 + | (Probe(ty1, _), _) => + Probe(match_synswitch(ty1, t2), Probe.empty) |> rewrap1 }; }; @@ -623,6 +639,7 @@ let rec normalize = (~rec_counter=0, ctx: Ctx.t, ty: t): t => { Rec(tpat, normalize(Ctx.extend_dummy_tvar(ctx, tpat), ty)) |> rewrap | Forall(name, ty) => Forall(name, normalize(Ctx.extend_dummy_tvar(ctx, name), ty)) |> rewrap + | Probe(ty, probe) => Probe(normalize(ctx, ty), probe) |> rewrap }; }; @@ -784,6 +801,7 @@ let rec is_syn = (ty: t): bool => | Arrow(_) | Prod(_) | Sum(_) => false + | Probe(ty, _) => is_syn(ty) }; let rec is_ana_atom = (ty: t) => @@ -800,6 +818,7 @@ let rec is_ana_atom = (ty: t) => | Arrow(_) | Prod(_) | Sum(_) => None + | Probe(ty, _) => is_ana_atom(ty) }; let rec is_syn_plus = (ty: t): bool => @@ -817,6 +836,7 @@ let rec is_syn_plus = (ty: t): bool => | List(_) | Prod(_) | Sum(_) => false + | Probe(ty, _) => is_syn_plus(ty) }; /* Does the type require parentheses when on the left of an arrow for printing? */ @@ -834,6 +854,7 @@ let rec needs_parens = (ty: t): bool => | Arrow(_, _) | Prod(_) | Sum(_) => true /* disambiguate between (A + B) -> C and A + (B -> C) */ + | Probe(ty, _) => needs_parens(ty) }; let pretty_print_tvar = (tv: TPat.t): string => @@ -884,6 +905,7 @@ let rec pretty_print = (ty: t): string => "rec " ++ pretty_print_tvar(tv) ++ " -> " ++ pretty_print(t) | Forall(tv, t) => "forall " ++ pretty_print_tvar(tv) ++ " -> " ++ pretty_print(t) + | Probe(ty, _) => pretty_print(ty) } and ctr_pretty_print = fun diff --git a/src/menhirParser/Conversion.re b/src/menhirParser/Conversion.re index 34ae9a51eb..1051018fc0 100644 --- a/src/menhirParser/Conversion.re +++ b/src/menhirParser/Conversion.re @@ -495,6 +495,7 @@ and Typ: { constructors, ); SumTyp(sumterms); + | Probe(typ, _) => of_core(typ) }; }; } diff --git a/src/util/Id.re b/src/util/Id.re index c661ffce0a..ad34d68c14 100644 --- a/src/util/Id.re +++ b/src/util/Id.re @@ -68,20 +68,12 @@ let mk_str: string => t = s => Uuidm.v5(namespace_uuid, s); let compare: (t, t) => int = Uuidm.compare; let to_string: (~upper: bool=?, t) => string = Uuidm.to_string; let of_string: (~pos: int=?, string) => option(t) = Uuidm.of_string; +let str3 = (id: t) => id |> to_string |> String.sub(_, 0, 3); + let pp: (Format.formatter, t) => unit = - (f, id) => - Format.fprintf( - f, - "Option.get(Haz3lcore.Id.of_string(\"%s\"))", - to_string(id), - ); -let show = id => - Format.sprintf( - "Option.get(Haz3lcore.Id.of_string(\"%s\"))", - to_string(id), - ); + (f, id) => Format.fprintf(f, "id(\"%s\")", str3(id)); +let show = id => Format.sprintf("id(\"%s\")", str3(id)); -let str3 = (id: t) => id |> to_string |> String.sub(_, 0, 3); let str8 = (id: t) => id |> to_string |> String.sub(_, 0, 8); let cls = (id: t) => "id" ++ str8(id); diff --git a/src/web/app/common/ProjectorView.re b/src/web/app/common/ProjectorView.re index 9089c3b64e..77c23d7e43 100644 --- a/src/web/app/common/ProjectorView.re +++ b/src/web/app/common/ProjectorView.re @@ -72,6 +72,13 @@ module Model = { ) => { List.filter_map( ((id, _)) => { + let d = + Id.Map.to_seq(dynamics) + |> Seq.map(fst) + |> Seq.map(Id.str3) + |> List.of_seq; + print_endline("Dynamic Ids: " ++ String.concat(", ", d)); + print_endline("Looking for projector id: " ++ Id.str3(id)); let* p = Id.Map.find_opt(id, projectors); let+ measurement = Measured.find_pr_opt(p, measured); let info = ProjectorInfo.mk_info(p, ~statics, ~dynamics); diff --git a/src/web/app/editors/code/CodeEditable.re b/src/web/app/editors/code/CodeEditable.re index 2ee6d7ee8b..c719c60d04 100644 --- a/src/web/app/editors/code/CodeEditable.re +++ b/src/web/app/editors/code/CodeEditable.re @@ -213,6 +213,7 @@ module View = { ~globals, ) : []; + let projectors = ProjectorView.all( x => inject(Perform(x)), diff --git a/src/web/app/explainthis/ExplainThis.re b/src/web/app/explainthis/ExplainThis.re index cd52edc245..f992ea92d5 100644 --- a/src/web/app/explainthis/ExplainThis.re +++ b/src/web/app/explainthis/ExplainThis.re @@ -2614,6 +2614,7 @@ let get_doc = ) | Sum(_) => get_message(SumTyp.labelled_sum_typs) | Unknown(Hole(Invalid(_))) => simple("Not a type or type operator") + | Probe(_) => default | Parens(_) => default // Shouldn't be hit? } | Some(InfoTPat(info)) => From 0dd5810948448478548c5725c409569edbc67556 Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala Date: Thu, 25 Sep 2025 09:48:20 -0400 Subject: [PATCH 05/65] Type probe on types --- .../projectors/implementations/DynTypeProj.re | 56 ++++++++++--------- .../dynamics/transition/Ascriptions.re | 6 +- .../dynamics/transition/PatternMatch.re | 17 ++++-- .../dynamics/transition/Transition.re | 19 ++++--- src/language/statics/Elaborator.re | 3 +- src/language/statics/Statics.re | 4 +- src/language/term/Typ.re | 6 +- test/Test_MakeTerm.re | 24 ++++++++ test/Test_PatternMatch.re | 7 ++- test/evaluator/Test_Evaluator_Probe.re | 17 ++++++ 10 files changed, 108 insertions(+), 51 deletions(-) diff --git a/src/haz3lcore/projectors/implementations/DynTypeProj.re b/src/haz3lcore/projectors/implementations/DynTypeProj.re index 77a8ada80f..1c19baf08e 100644 --- a/src/haz3lcore/projectors/implementations/DynTypeProj.re +++ b/src/haz3lcore/projectors/implementations/DynTypeProj.re @@ -9,6 +9,34 @@ let self_ty = (info: option(Info.t)): option(Typ.t) => | Some(InfoPat({self, _})) => Self.typ_of_pat(self) | _ => None }; +let get_dynamic_typ = (info: info): Typ.t => { + let dynamic_typ = + info.dynamics + |> Option.bind( + _, + (d: Dynamics.Info.t) => { + let statics = + Statics.mk(CoreSettings.on, Builtins.ctx_init(Some(Int))); + let type_of = (c: Dynamics.Probe.Closure.t) => { + IdTagged.rep_id(c.value) + |> Id.Map.find_opt(_, statics(c.value)) + |> Option.bind( + _, + fun + | InfoExp(e) => { + Some(e.ty); + } + | _ => None, + ); + }; + let types = List.map(type_of, d) |> Util.OptUtil.sequence; + + Option.map(Typ.consistent_join(Ctx.empty), types); + }, + ) + |> Option.value(~default=Typ.fresh(Unknown(Internal))); + dynamic_typ; +}; module M: Projector = { [@deriving (show({with_path: false}), sexp, yojson)] type model = @@ -37,31 +65,7 @@ module M: Projector = { |> Option.map([%derive.show: Language.Dynamics.Info.t]) |> Option.value(~default="None") |> (y => print_endline("Dynamics: " ++ y)); - let dynamic_typ = - info.dynamics - |> Option.bind( - _, - (d: Dynamics.Info.t) => { - let statics = - Statics.mk(CoreSettings.on, Builtins.ctx_init(Some(Int))); - let type_of = (c: Dynamics.Probe.Closure.t) => { - IdTagged.rep_id(c.value) - |> Id.Map.find_opt(_, statics(c.value)) - |> Option.bind( - _, - fun - | InfoExp(e) => { - Some(e.ty); - } - | _ => None, - ); - }; - let types = List.map(type_of, d) |> Util.OptUtil.sequence; - - Option.map(Typ.consistent_join(Ctx.empty), types); - }, - ) - |> Option.value(~default=Typ.fresh(Unknown(Internal))); + let dynamic_typ = get_dynamic_typ(info); div( ~attrs=[Attr.classes(["dyntype-cell"])], [Typ(dynamic_typ) |> utility.term_to_seg |> view_seg(Sort.Typ)], @@ -96,7 +100,7 @@ module M: Projector = { info: info, ~local: action => Ui_effect.t(unit), ~parent as _, - ~view_seg, + ~view_seg: View.seg, ) => View.{ inline: diff --git a/src/language/dynamics/transition/Ascriptions.re b/src/language/dynamics/transition/Ascriptions.re index 3a35d7dd72..a3a1d21d59 100644 --- a/src/language/dynamics/transition/Ascriptions.re +++ b/src/language/dynamics/transition/Ascriptions.re @@ -31,9 +31,9 @@ let rec transition = switch (DHExp.term_of(d)) { | Asc(e, t) => switch (DHExp.term_of(e), Typ.term_of(Typ.unroll(t))) { - | (_, Probe(t, p)) => - update_probe(Exp.rep_id(e), e, Environment.empty, [], p); - Asc(e, t) |> DHExp.fresh |> transition(~update_probe, ~recursive); + | (_, Probe(t', p)) => + update_probe(Typ.rep_id(t), e, Environment.empty, [], p); + Asc(e, t') |> DHExp.fresh |> transition(~update_probe, ~recursive); | (Asc(e, t'), t) // This is only necessary because sometimes we add two ascriptions and aren't marking it as a non-value when diff --git a/src/language/dynamics/transition/PatternMatch.re b/src/language/dynamics/transition/PatternMatch.re index f836ffae1a..1f077da6a1 100644 --- a/src/language/dynamics/transition/PatternMatch.re +++ b/src/language/dynamics/transition/PatternMatch.re @@ -12,8 +12,9 @@ let combine_result = (r1: match_result, r2: match_result): match_result => Matches(Environment.union(env1, env2)) }; -let rec matches = (capture, dp: Pat.t, d: DHExp.t): match_result => { - let matches = matches(capture); +let rec matches = + (~update_probe, capture, dp: Pat.t, d: DHExp.t): match_result => { + let matches = matches(~update_probe, capture); let d = Ascriptions.transition_multiple(d); switch (DHPat.term_of(dp)) { | Invalid(_) @@ -59,7 +60,13 @@ let rec matches = (capture, dp: Pat.t, d: DHExp.t): match_result => { capture(pr, dp, d, inner_match); inner_match; | Asc(p, t1) => - matches(p, Ascriptions.transition_multiple(Asc(d, t1) |> DHExp.fresh)) + matches( + p, + Ascriptions.transition_multiple( + ~update_probe, + Asc(d, t1) |> DHExp.fresh, + ), + ) }; }; @@ -70,7 +77,7 @@ type matches_and_closures = { closures: closure_closures, }; -let matches = (dp: Pat.t, d: DHExp.t): matches_and_closures => { +let matches = (~update_probe, dp: Pat.t, d: DHExp.t): matches_and_closures => { /* Closure capture for Probe instrumentation */ let closure_closures: ref(closure_closures) = ref([]); let capture = @@ -86,7 +93,7 @@ let matches = (dp: Pat.t, d: DHExp.t): matches_and_closures => { closure_closures^, ) }; - let res = matches(capture, dp, d); + let res = matches(~update_probe, capture, dp, d); { matches: res, closures: closure_closures^, diff --git a/src/language/dynamics/transition/Transition.re b/src/language/dynamics/transition/Transition.re index e6474e735e..f80adf4d29 100644 --- a/src/language/dynamics/transition/Transition.re +++ b/src/language/dynamics/transition/Transition.re @@ -189,7 +189,6 @@ module Transition = (EV: EV_MODE) => { d, ) : EV.result => { - print_endline("Transitioning: " ++ DHExp.show(d)); let update_probe' = (id, d, env, call_stack, pr) => { print_endline( "Updating probe: " ++ Id.str3(id) ++ " with value " ++ DHExp.show(d), @@ -262,7 +261,8 @@ module Transition = (EV: EV_MODE) => { and. d1' = req_final(req(state, env), d1 => Let1(dp, d1, d2) |> wrap_ctx, d1); let.wrap_closure _ = env; - let {matches, closures} = matches(dp, d1'); + let {matches, closures} = + matches(~update_probe=update_probe', dp, d1'); let matches_str = { switch (matches) { | IndetMatch @@ -297,7 +297,7 @@ module Transition = (EV: EV_MODE) => { | FixF(dp, d1, env) => let. _ = otherwise(env |> Option.value(~default=ClosureEnvironment.empty), d); - switch (matches(dp, d1).matches) { + switch (matches(~update_probe=update_probe', dp, d1).matches) { | IndetMatch | DoesNotMatch => Indet | Matches(env') => @@ -440,7 +440,7 @@ module Transition = (EV: EV_MODE) => { switch (unboxed_fun) { | Constructor(_) => Constructor | FunEnv(dp, d3, function_lexical_env) => - let matches = matches(dp, d2'); + let matches = matches(~update_probe=update_probe', dp, d2'); switch (matches.matches) { | IndetMatch | DoesNotMatch => Indet @@ -460,7 +460,7 @@ module Transition = (EV: EV_MODE) => { }); }; | FunNoEnv(dp, d3) when mode == `Substitution => - let matches = matches(dp, d2'); + let matches = matches(~update_probe=update_probe', dp, d2'); switch (matches.matches) { | IndetMatch | DoesNotMatch => Indet @@ -854,7 +854,7 @@ module Transition = (EV: EV_MODE) => { fun | [] => None | [(dp, d2), ...rules] => { - let matches = matches(dp, d1); + let matches = matches(~update_probe=update_probe', dp, d1); switch (matches.matches) { | Matches(env') => Some((env', d2, matches.closures)) | DoesNotMatch => next_rule(rules) @@ -929,7 +929,12 @@ module Transition = (EV: EV_MODE) => { let. _ = otherwise(env, d => Asc(d, t) |> rewrap) and. d' = req_final(req(state, env), d => Asc(d, t) |> wrap_ctx, d'); - switch (Ascriptions.transition(Asc(d', t) |> rewrap)) { + switch ( + Ascriptions.transition( + ~update_probe=update_probe', + Asc(d', t) |> rewrap, + ) + ) { | Some(d) => Step({ expr: d, diff --git a/src/language/statics/Elaborator.re b/src/language/statics/Elaborator.re index 8b8a1b3dff..b5adf1985d 100644 --- a/src/language/statics/Elaborator.re +++ b/src/language/statics/Elaborator.re @@ -448,8 +448,7 @@ let rec elaborate = (m: Statics.Map.t, uexp: Exp.t): (DHExp.t, Typ.t) => { in the expression. It does this to get rid of all the invalid ids we added to prevent generating too many new ids */ -let fix_typ_ids = - Exp.map_term(~f_typ=(cont, e) => e |> IdTagged.new_ids |> cont); +let fix_typ_ids = Exp.map_term(~f_typ=(cont, e) => e |> cont); let uexp_elab = (m: Statics.Map.t, uexp: Exp.t): ElaborationResult.t => { switch (elaborate(m, uexp)) { diff --git a/src/language/statics/Statics.re b/src/language/statics/Statics.re index af4a5a796f..09805981d3 100644 --- a/src/language/statics/Statics.re +++ b/src/language/statics/Statics.re @@ -1898,6 +1898,7 @@ and utyp_to_info_map = add(m) | List(t) | Parens(t) => add(go(t, m) |> snd) + | Probe(typ, _) => add(go(typ, m) |> snd) | Arrow(t1, t2) => let m = go(t1, m) |> snd; let m = go(t2, m) |> snd; @@ -1988,9 +1989,6 @@ and utyp_to_info_map = |> snd; let m = utpat_to_info_map(~ctx, ~ancestors, utpat, m) |> snd; add(m); // TODO: check with andrew - | Probe(typ, _) => - let m = utyp_to_info_map(~ctx, ~ancestors, typ, m) |> snd; - add(m); }; } and utpat_to_info_map = diff --git a/src/language/term/Typ.re b/src/language/term/Typ.re index 5a7f1d8184..b19e0846df 100644 --- a/src/language/term/Typ.re +++ b/src/language/term/Typ.re @@ -420,6 +420,8 @@ let rec join = switch (term_of(ty1), term_of(ty2)) { | (_, Parens(ty2)) => join'(ty1, ty2) | (Parens(ty1), _) => join'(ty1, ty2) + | (Probe(ty1, _), _) => join'(ty1, ty2) + | (_, Probe(ty2, _)) => join'(ty1, ty2) | (Unknown(p1), Unknown(p2)) => Some(Unknown(join_type_provenance(p1, p2)) |> temp) | (Unknown(Inconsistent), _) => Some(ty1) @@ -505,10 +507,6 @@ let rec join = let+ ty = join'(ty1, ty2); List(ty) |> temp; | (List(_), _) => inconsistent - | (Probe(ty1, _), Probe(ty2, _)) => - let+ ty = join'(ty1, ty2); - ty; - | (Probe(ty1, _), _) => join'(ty1, ty2) }; }; diff --git a/test/Test_MakeTerm.re b/test/Test_MakeTerm.re index 8cc2b7c16b..05bcd1a4fe 100644 --- a/test/Test_MakeTerm.re +++ b/test/Test_MakeTerm.re @@ -233,5 +233,29 @@ let tests = "^slider(50)", ) ), + // test_case( + // "Dyntype proj exp", + // `Quick, + // () => { + // open Haz3lcore; + // let seg = Parser.to_segment({|^^dyntype(3) : ?|}) |> Option.get; + // let term = MakeTerm.from_zip_for_sem(Zipper.unzip(seg)).term; + // print_endline("Segment: " ++ Segment.show(seg)); + // print_endline("Term: " ++ Language.Term.Exp.show(term)); + // Alcotest.fail(""); + // }, + // ), + test_case( + "Dyntype proj typ", + `Quick, + () => { + open Haz3lcore; + let seg = Parser.to_segment({|3 : ^^dyntype(?)|}) |> Option.get; + let term = MakeTerm.from_zip_for_sem(Zipper.unzip(seg)).term; + print_endline("Segment: " ++ Segment.show(seg)); + print_endline("Term: " ++ Language.Term.Exp.show(term)); + Alcotest.fail(""); + }, + ), ], ); diff --git a/test/Test_PatternMatch.re b/test/Test_PatternMatch.re index d35d060b0f..d85a3d8817 100644 --- a/test/Test_PatternMatch.re +++ b/test/Test_PatternMatch.re @@ -43,7 +43,12 @@ let tests = ( ) ); let matches: PatternMatch.match_result = - PatternMatch.matches(pat, expression).matches; + PatternMatch.matches( + ~update_probe=(_, _, _, _, _) => (), + pat, + expression, + ). + matches; check( testable( diff --git a/test/evaluator/Test_Evaluator_Probe.re b/test/evaluator/Test_Evaluator_Probe.re index 41a5a15e9e..320602b41a 100644 --- a/test/evaluator/Test_Evaluator_Probe.re +++ b/test/evaluator/Test_Evaluator_Probe.re @@ -333,5 +333,22 @@ let tests = ( ) ) }), + test_case("Probe around unknown type", `Quick, () => { + PGrammar.( + probe_test( + {|3 : PROBE(?)|}, + Exp.( + asc( + int(3), + Typ.probe( + ~ann=[probed_value(Atom(Int(Bigint.of_int(3))))], + Typ.unknown(Internal), + {refs: []}, + ), + ) + ), + ) + ) + }), ], ); From 173a5f496b5178e1fcd95aafe4ace8ec74e0dbf6 Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Thu, 25 Sep 2025 10:12:47 -0400 Subject: [PATCH 06/65] Remove projector parsing tests --- test/Test_MakeTerm.re | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/test/Test_MakeTerm.re b/test/Test_MakeTerm.re index 05bcd1a4fe..8cc2b7c16b 100644 --- a/test/Test_MakeTerm.re +++ b/test/Test_MakeTerm.re @@ -233,29 +233,5 @@ let tests = "^slider(50)", ) ), - // test_case( - // "Dyntype proj exp", - // `Quick, - // () => { - // open Haz3lcore; - // let seg = Parser.to_segment({|^^dyntype(3) : ?|}) |> Option.get; - // let term = MakeTerm.from_zip_for_sem(Zipper.unzip(seg)).term; - // print_endline("Segment: " ++ Segment.show(seg)); - // print_endline("Term: " ++ Language.Term.Exp.show(term)); - // Alcotest.fail(""); - // }, - // ), - test_case( - "Dyntype proj typ", - `Quick, - () => { - open Haz3lcore; - let seg = Parser.to_segment({|3 : ^^dyntype(?)|}) |> Option.get; - let term = MakeTerm.from_zip_for_sem(Zipper.unzip(seg)).term; - print_endline("Segment: " ++ Segment.show(seg)); - print_endline("Term: " ++ Language.Term.Exp.show(term)); - Alcotest.fail(""); - }, - ), ], ); From 6e4de9ce1950e0b9f59f30831c86d2431f46b553 Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala Date: Thu, 25 Sep 2025 10:23:34 -0400 Subject: [PATCH 07/65] Add type handling for Typ in probe --- src/haz3lcore/projectors/implementations/ProbeProj.re | 1 + 1 file changed, 1 insertion(+) diff --git a/src/haz3lcore/projectors/implementations/ProbeProj.re b/src/haz3lcore/projectors/implementations/ProbeProj.re index 1665f55d21..dbbf9990c5 100644 --- a/src/haz3lcore/projectors/implementations/ProbeProj.re +++ b/src/haz3lcore/projectors/implementations/ProbeProj.re @@ -862,6 +862,7 @@ module M: Projector = { | Exp(_) | Pat(_) => Some() | Any(_) => Some() /* Grout don't have sorts rn */ + | Typ(_) => Some() | _ => None }; From 353b0e23a11ffde45552721dbe25f2bb98a572c8 Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala Date: Mon, 29 Sep 2025 14:03:48 -0400 Subject: [PATCH 08/65] Implement dynamic type handling in type projector --- .../projectors/implementations/TypeProj.re | 43 ++++++++++++++++--- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/src/haz3lcore/projectors/implementations/TypeProj.re b/src/haz3lcore/projectors/implementations/TypeProj.re index 5cd31a43f4..ec9ff215b1 100644 --- a/src/haz3lcore/projectors/implementations/TypeProj.re +++ b/src/haz3lcore/projectors/implementations/TypeProj.re @@ -24,11 +24,41 @@ let totalize_ty = (expected_ty: option(Typ.t)): Typ.t => | None => Typ.fresh(Unknown(Internal)) }; +let get_dynamic_typ = (info: info): Typ.t => { + let dynamic_typ = + info.dynamics + |> Option.bind( + _, + (d: Dynamics.Info.t) => { + let statics = + Statics.mk(CoreSettings.on, Builtins.ctx_init(Some(Int))); + let type_of = (c: Dynamics.Probe.Closure.t) => { + IdTagged.rep_id(c.value) + |> Id.Map.find_opt(_, statics(c.value)) + |> Option.bind( + _, + fun + | InfoExp(e) => { + Some(e.ty); + } + | _ => None, + ); + }; + let types = List.map(type_of, d) |> Util.OptUtil.sequence; + + Option.map(Typ.consistent_join(Ctx.empty), types); + }, + ) + |> Option.value(~default=Typ.fresh(Unknown(Internal))); + dynamic_typ; +}; + module M: Projector = { [@deriving (show({with_path: false}), sexp, yojson)] type model = | Expected - | Self; + | Self + | Dynamic; [@deriving (show({with_path: false}), sexp, yojson)] type action = @@ -43,11 +73,12 @@ module M: Projector = { }; }; - let dynamics = false; + let dynamics = true; let focusable = Focusable.non; - let display_ty = (model, statics): option(Typ.t) => + let display_ty = (model, statics, info): option(Typ.t) => switch (model) { + | Dynamic => Some(get_dynamic_typ(info)) | _ when expected_ty(statics) |> totalize_ty |> Typ.is_syn => statics |> self_ty | Self => statics |> self_ty @@ -56,6 +87,7 @@ module M: Projector = { let display_mode = (model: model, statics: option(Language.Info.t)): string => switch (model) { + | Dynamic => "↠" | _ when self_ty(statics) == expected_ty(statics) => "⇔" | _ when expected_ty(statics) |> totalize_ty |> Typ.is_syn => "⇒" | Self => "⇒" @@ -69,7 +101,7 @@ module M: Projector = { ); let typ_view = (model, info: info, utility, view_seg: View.seg) => { - let typ = display_ty(model, info.statics) |> totalize_ty; + let typ = display_ty(model, info.statics, info) |> totalize_ty; div( ~attrs=[Attr.classes(["type-cell"])], [Typ(typ) |> utility.term_to_seg |> view_seg(Sort.Typ)], @@ -79,7 +111,8 @@ module M: Projector = { let update = (model, _, a: action) => switch (a, model) { | (ToggleDisplay, Expected) => Self - | (ToggleDisplay, Self) => Expected + | (ToggleDisplay, Self) => Dynamic + | (ToggleDisplay, Dynamic) => Expected }; let syntax_str = (info: info) => { From 4c6658cb8148fa666f39ddeda4d4eaf53de7b21d Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala Date: Mon, 29 Sep 2025 16:42:09 -0400 Subject: [PATCH 09/65] Crude styling of dynamic types in type projector --- src/haz3lcore/projectors/ProjectorBase.re | 9 ++++- .../projectors/implementations/ProbeProj.re | 9 ++++- .../projectors/implementations/TypeProj.re | 36 ++++++++++++++++--- src/language/term/Typ.re | 35 ++++++++++++++++++ src/web/app/common/ProjectorView.re | 14 ++++++-- src/web/app/editors/code/Code.re | 18 ++++++++-- src/web/www/style/projectors/proj-type.css | 4 +++ 7 files changed, 113 insertions(+), 12 deletions(-) diff --git a/src/haz3lcore/projectors/ProjectorBase.re b/src/haz3lcore/projectors/ProjectorBase.re index 9c1396be99..b0bdf89443 100644 --- a/src/haz3lcore/projectors/ProjectorBase.re +++ b/src/haz3lcore/projectors/ProjectorBase.re @@ -105,7 +105,14 @@ module View = { }; [@deriving (show({with_path: false}), sexp, yojson)] - type seg = (~background: bool=?, Sort.t, list(syntax)) => Node.t; + type seg = + ( + ~background: bool=?, + ~is_dynamic: Id.t => bool=?, + Sort.t, + list(syntax) + ) => + Node.t; let mk = (~overlay=None, ~offside=None, inline) => { inline, diff --git a/src/haz3lcore/projectors/implementations/ProbeProj.re b/src/haz3lcore/projectors/implementations/ProbeProj.re index dbbf9990c5..76628ab5f6 100644 --- a/src/haz3lcore/projectors/implementations/ProbeProj.re +++ b/src/haz3lcore/projectors/implementations/ProbeProj.re @@ -643,7 +643,14 @@ let offside_view = ( info: info, local, - view_seg: (~background: bool=?, Sort.t, list(syntax)) => Node.t, + view_seg: + ( + ~background: bool=?, + ~is_dynamic: Id.t => bool=?, + Sort.t, + list(syntax) + ) => + Node.t, utility: utility, ) => Node.div( diff --git a/src/haz3lcore/projectors/implementations/TypeProj.re b/src/haz3lcore/projectors/implementations/TypeProj.re index ec9ff215b1..ab50f2ae27 100644 --- a/src/haz3lcore/projectors/implementations/TypeProj.re +++ b/src/haz3lcore/projectors/implementations/TypeProj.re @@ -76,9 +76,9 @@ module M: Projector = { let dynamics = true; let focusable = Focusable.non; - let display_ty = (model, statics, info): option(Typ.t) => + let _display_ty = (model, statics, dyn_typ): option(Typ.t) => switch (model) { - | Dynamic => Some(get_dynamic_typ(info)) + | Dynamic => Some(dyn_typ) | _ when expected_ty(statics) |> totalize_ty |> Typ.is_syn => statics |> self_ty | Self => statics |> self_ty @@ -87,7 +87,7 @@ module M: Projector = { let display_mode = (model: model, statics: option(Language.Info.t)): string => switch (model) { - | Dynamic => "↠" + | Dynamic => "↦" | _ when self_ty(statics) == expected_ty(statics) => "⇔" | _ when expected_ty(statics) |> totalize_ty |> Typ.is_syn => "⇒" | Self => "⇒" @@ -101,10 +101,36 @@ module M: Projector = { ); let typ_view = (model, info: info, utility, view_seg: View.seg) => { - let typ = display_ty(model, info.statics, info) |> totalize_ty; + let (is_dynamic, typ) = + switch (model) { + | Dynamic => + let self_ty = self_ty(info.statics); + let dyn_typ = + get_dynamic_typ(info) + |> Grammar.map_typ_annotation(_ => IdTagged.IdTag.fresh(), _); + let ids: list(Id.t) = + Typ.diff( + Option.value(~default=Typ.fresh(Unknown(Internal)), self_ty), + dyn_typ, + ); + let is_dynamic_id = (id: Id.t): bool => { + if (List.mem(id, ids)) { + print_endline("Was dynamic id: " ++ Id.str3(id)); + } else { + (); + }; + List.mem(id, ids); + }; + (is_dynamic_id, dyn_typ); + | Expected => ((_ => false), expected_ty(info.statics) |> totalize_ty) + | Self => ((_ => false), self_ty(info.statics) |> totalize_ty) + }; + div( ~attrs=[Attr.classes(["type-cell"])], - [Typ(typ) |> utility.term_to_seg |> view_seg(Sort.Typ)], + [ + Typ(typ) |> utility.term_to_seg |> view_seg(~is_dynamic, Sort.Typ, _), + ], ); }; diff --git a/src/language/term/Typ.re b/src/language/term/Typ.re index b19e0846df..92a17d694f 100644 --- a/src/language/term/Typ.re +++ b/src/language/term/Typ.re @@ -975,3 +975,38 @@ let to_product = (tys: list(t)): t => | [ty] => ty | _ => Prod(tys) |> temp }; + +/* Computes the list of ids in t' that are not in t. Assumes initial ids are distinct. Only returns the id of the root difference. */ +let rec diff = (ty: t, ty': t): list(Id.t) => { + switch (term_of(ty), term_of(ty')) { + | (Parens(t1), Parens(t2)) => diff(t1, t2) + | (Unknown(_), Unknown(_)) => [] + | (Unknown(_), _) => [ty' |> rep_id] + | (Atom(c1), Atom(c2)) when c1 == c2 => [] + | (Atom(_), _) => [ty' |> rep_id] + | (Label(l1), Label(l2)) when l1 == l2 => [] + | (Label(_), _) => [ty' |> rep_id] + | (Var(v1), Var(v2)) when v1 == v2 => [] + | (Var(_), _) => [ty' |> rep_id] + | (Rec(tp1, t1), Rec(tp2, t2)) => diff(t1, t2) // TODO Check tpat + | (Rec(_), _) => [ty' |> rep_id] + | (Forall(tp1, t1), Forall(tp2, t2)) => diff(t1, t2) // TODO Check tpat + | (Forall(_), _) => [ty' |> rep_id] + | (Arrow(t1a, t1b), Arrow(t2a, t2b)) => diff(t1a, t2a) @ diff(t1b, t2b) + | (Arrow(_), _) => [ty' |> rep_id] + | (Prod(tys1), Prod(tys2)) when List.length(tys1) == List.length(tys2) => + List.map2(diff, tys1, tys2) |> List.concat + | (Prod(_), _) => [ty' |> rep_id] + | (TupLabel(l1, t1), TupLabel(l2, t2)) => diff(l1, l2) @ diff(t1, t2) + | (TupLabel(_, _), _) => [ty' |> rep_id] + | _ => // TODO + raise( + Failure( + "diff: unsupported types" + ++ pretty_print(ty) + ++ " and " + ++ pretty_print(ty'), + ), + ) + }; +}; diff --git a/src/web/app/common/ProjectorView.re b/src/web/app/common/ProjectorView.re index 77c23d7e43..813e812e53 100644 --- a/src/web/app/common/ProjectorView.re +++ b/src/web/app/common/ProjectorView.re @@ -183,11 +183,20 @@ let offside_wrapper = [v], ); -let simple_code = (~background=false, font_metrics, _sort, segment): Node.t => { +let simple_code = + ( + ~background=false, + ~is_dynamic=(_: Id.t) => false, + font_metrics, + _sort, + segment, + ) + : Node.t => { let shape_map = ProjectorCore.Shape.Map.empty; /* Assume this doesn't contain projectors */ let measured = Measured.of_segment(segment, shape_map); let code = Code.view( + ~is_dynamic, ~measured, ~settings=Settings.Model.init, ~shape_map, @@ -232,7 +241,8 @@ let mk_view = let parent = a => inject(Project(handle(p.id, a))); let local = a => inject(Project(SetModel(p.id, P.update(p.model, info, a)))); - let view_seg = (~background=?) => simple_code(~background?, font_metrics); + let view_seg = (~background=?, ~is_dynamic=?) => + simple_code(~background?, ~is_dynamic?, font_metrics); P.view(p.model, info, ~local, ~parent, ~view_seg); }; diff --git a/src/web/app/editors/code/Code.re b/src/web/app/editors/code/Code.re index 3f588cf7aa..a03b56cac0 100644 --- a/src/web/app/editors/code/Code.re +++ b/src/web/app/editors/code/Code.re @@ -49,6 +49,7 @@ let whitespace_token = let view = ( + ~is_dynamic=(_: Id.t) => false, ~measured: Measured.t, ~settings: Settings.Model.t, ~shape_map: ProjectorCore.Shape.Map.t, @@ -123,9 +124,20 @@ let view = List.concat_map( fun | Piece.Tile(t) => - Aba.mk(t.shards, t.children) - |> Aba.join(i => [of_delim(t, i)], of_segment) - |> List.concat + if (Tile.id(t) |> is_dynamic) { + [ + span( + ~attrs=[Attr.classes(["dynamic"])], + Aba.mk(t.shards, t.children) + |> Aba.join(i => [of_delim(t, i)], of_segment) + |> List.concat, + ), + ]; + } else { + Aba.mk(t.shards, t.children) + |> Aba.join(i => [of_delim(t, i)], of_segment) + |> List.concat; + } | Grout(g) => [of_grout(g)] | Secondary(s) => [of_secondary(s)] | Projector(pr) => [of_projector(pr)], diff --git a/src/web/www/style/projectors/proj-type.css b/src/web/www/style/projectors/proj-type.css index 3b2f8aba25..102a21af68 100644 --- a/src/web/www/style/projectors/proj-type.css +++ b/src/web/www/style/projectors/proj-type.css @@ -94,3 +94,7 @@ .projector.type .offside .code .token.explicit-hole { color: white; } + +.dynamic > .token.Typ.mono { + color: green; +} \ No newline at end of file From 4d386e85f451db7c59eb9bf03e4ad8928958d92b Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala Date: Mon, 29 Sep 2025 16:44:19 -0400 Subject: [PATCH 10/65] Mark unused patterns --- src/language/term/Typ.re | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/language/term/Typ.re b/src/language/term/Typ.re index 92a17d694f..aec6f94aeb 100644 --- a/src/language/term/Typ.re +++ b/src/language/term/Typ.re @@ -988,9 +988,9 @@ let rec diff = (ty: t, ty': t): list(Id.t) => { | (Label(_), _) => [ty' |> rep_id] | (Var(v1), Var(v2)) when v1 == v2 => [] | (Var(_), _) => [ty' |> rep_id] - | (Rec(tp1, t1), Rec(tp2, t2)) => diff(t1, t2) // TODO Check tpat + | (Rec(_tp1, t1), Rec(_tp2, t2)) => diff(t1, t2) // TODO Check tpat | (Rec(_), _) => [ty' |> rep_id] - | (Forall(tp1, t1), Forall(tp2, t2)) => diff(t1, t2) // TODO Check tpat + | (Forall(_tp1, t1), Forall(_tp2, t2)) => diff(t1, t2) // TODO Check tpat | (Forall(_), _) => [ty' |> rep_id] | (Arrow(t1a, t1b), Arrow(t2a, t2b)) => diff(t1a, t2a) @ diff(t1b, t2b) | (Arrow(_), _) => [ty' |> rep_id] @@ -999,7 +999,8 @@ let rec diff = (ty: t, ty': t): list(Id.t) => { | (Prod(_), _) => [ty' |> rep_id] | (TupLabel(l1, t1), TupLabel(l2, t2)) => diff(l1, l2) @ diff(t1, t2) | (TupLabel(_, _), _) => [ty' |> rep_id] - | _ => // TODO + | _ => + // TODO raise( Failure( "diff: unsupported types" From d8efcfd65b682a9de1c74b29fe669c8f75ee400e Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala Date: Mon, 29 Sep 2025 16:44:43 -0400 Subject: [PATCH 11/65] Remove dynamic type projector --- src/haz3lcore/projectors/ProjectorCore.re | 6 +- src/haz3lcore/projectors/ProjectorInit.re | 1 - .../projectors/implementations/DynTypeProj.re | 123 ------------------ src/web/app/input/Shortcut.re | 7 - src/web/www/style/projectors/proj-base.css | 1 - src/web/www/style/projectors/proj-dyntype.css | 97 -------------- 6 files changed, 1 insertion(+), 234 deletions(-) delete mode 100644 src/haz3lcore/projectors/implementations/DynTypeProj.re delete mode 100644 src/web/www/style/projectors/proj-dyntype.css diff --git a/src/haz3lcore/projectors/ProjectorCore.re b/src/haz3lcore/projectors/ProjectorCore.re index df9313965a..4d37886aa2 100644 --- a/src/haz3lcore/projectors/ProjectorCore.re +++ b/src/haz3lcore/projectors/ProjectorCore.re @@ -20,7 +20,6 @@ module Kind = { type t = | Fold | Info - | DynType | Probe | Checkbox | Slider @@ -38,8 +37,7 @@ module Kind = { Livelit, ]; - let projectors: list(t) = - livelit_projectors @ [Fold, Info, DynType, Probe]; + let projectors: list(t) = livelit_projectors @ [Fold, Info, Probe]; /* A friendly name for each projector. This is used * both for identifying a projector in the CSS and for @@ -48,7 +46,6 @@ module Kind = { switch (p) { | Fold => "fold" | Info => "type" - | DynType => "dyntype" | Probe => "probe" | Checkbox => "check" | Slider => "slider" @@ -66,7 +63,6 @@ module Kind = { switch (p) { | "fold" => Fold | "type" => Info - | "dyntype" => DynType | "probe" => Probe | "check" => Checkbox | "slider" => Slider diff --git a/src/haz3lcore/projectors/ProjectorInit.re b/src/haz3lcore/projectors/ProjectorInit.re index cb938f0d23..6f7696dff1 100644 --- a/src/haz3lcore/projectors/ProjectorInit.re +++ b/src/haz3lcore/projectors/ProjectorInit.re @@ -8,7 +8,6 @@ let to_module = (kind: ProjectorCore.Kind.t): (module Cooked) => switch (kind) { | Fold => (module Cook(FoldProj.M)) | Info => (module Cook(TypeProj.M)) - | DynType => (module Cook(DynTypeProj.M)) | Probe => (module Cook(ProbeProj.M)) | Slider => (module Cook(SliderProj.M)) | SliderF => (module Cook(SliderFProj.M)) diff --git a/src/haz3lcore/projectors/implementations/DynTypeProj.re b/src/haz3lcore/projectors/implementations/DynTypeProj.re deleted file mode 100644 index 1c19baf08e..0000000000 --- a/src/haz3lcore/projectors/implementations/DynTypeProj.re +++ /dev/null @@ -1,123 +0,0 @@ -open Virtual_dom.Vdom; -open Node; -open ProjectorBase; -open Language; - -let self_ty = (info: option(Info.t)): option(Typ.t) => - switch (info) { - | Some(InfoExp({self, _})) => Self.typ_of_exp(self) - | Some(InfoPat({self, _})) => Self.typ_of_pat(self) - | _ => None - }; -let get_dynamic_typ = (info: info): Typ.t => { - let dynamic_typ = - info.dynamics - |> Option.bind( - _, - (d: Dynamics.Info.t) => { - let statics = - Statics.mk(CoreSettings.on, Builtins.ctx_init(Some(Int))); - let type_of = (c: Dynamics.Probe.Closure.t) => { - IdTagged.rep_id(c.value) - |> Id.Map.find_opt(_, statics(c.value)) - |> Option.bind( - _, - fun - | InfoExp(e) => { - Some(e.ty); - } - | _ => None, - ); - }; - let types = List.map(type_of, d) |> Util.OptUtil.sequence; - - Option.map(Typ.consistent_join(Ctx.empty), types); - }, - ) - |> Option.value(~default=Typ.fresh(Unknown(Internal))); - dynamic_typ; -}; -module M: Projector = { - [@deriving (show({with_path: false}), sexp, yojson)] - type model = - | Expected - | Self; - - [@deriving (show({with_path: false}), sexp, yojson)] - type action = - | ToggleDisplay; - - let init = (any: Term.Any.t): option(model) => { - switch (any) { - | Exp(_) - | Pat(_) => Some(Expected) - | Any () => Some(Expected) /* Grout don't have sorts rn */ - | Typ(_) => Some(Expected) - | _ => None - }; - }; - - let dynamics = true; - let focusable = Focusable.non; - - let typ_view = (info: info, utility, view_seg: View.seg) => { - info.dynamics - |> Option.map([%derive.show: Language.Dynamics.Info.t]) - |> Option.value(~default="None") - |> (y => print_endline("Dynamics: " ++ y)); - let dynamic_typ = get_dynamic_typ(info); - div( - ~attrs=[Attr.classes(["dyntype-cell"])], - [Typ(dynamic_typ) |> utility.term_to_seg |> view_seg(Sort.Typ)], - ); - }; - - let update = (model, _, a: action) => - switch (a, model) { - | (ToggleDisplay, Expected) => Self - | (ToggleDisplay, Self) => Expected - }; - - let syntax_str = (info: info) => { - let max_len = 30; - let seg = Segment.unparenthesize(info.syntax); - let str = info.utility.seg_to_string(seg); - let str = Re.Str.global_replace(Re.Str.regexp("\n"), " ", str); - String.length(str) > max_len - ? String.sub(str, 0, max_len) ++ "..." : str; - }; - - let placeholder = (_m, info) => - ProjectorCore.Shape.inline(3 + String.length(syntax_str(info))); - - let syntax_view = (info: info) => info |> syntax_str |> text; - - let icon = div(~attrs=[Attr.classes(["icon"])], []); - - let view = - ( - _: model, - info: info, - ~local: action => Ui_effect.t(unit), - ~parent as _, - ~view_seg: View.seg, - ) => - View.{ - inline: - div( - ~attrs=[ - Attr.classes(["main"]), - Attr.on_double_click(_ => local(ToggleDisplay)), - ], - [syntax_view(info), icon], - ), - offside: - Some( - div( - ~attrs=[Attr.classes(["offside"])], - [typ_view(info, info.utility, view_seg)], - ), - ), - overlay: None, - }; -}; diff --git a/src/web/app/input/Shortcut.re b/src/web/app/input/Shortcut.re index cdeefa00c3..eb33bf3b7f 100644 --- a/src/web/app/input/Shortcut.re +++ b/src/web/app/input/Shortcut.re @@ -130,13 +130,6 @@ let shortcuts = (sys: Util.Key.sys): list(t) => "Type", Globals(ActiveEditor(Project(SetIndicated(Specific(Info))))), ), - mk_shortcut( - ~hotkey="alt+d", - ~mdIcon="camera", - ~section="Projection", - "Dynamic Type", - Globals(ActiveEditor(Project(SetIndicated(Specific(DynType))))), - ), mk_shortcut( ~hotkey="alt+l", ~mdIcon="camera", diff --git a/src/web/www/style/projectors/proj-base.css b/src/web/www/style/projectors/proj-base.css index 0111b55355..8e3f6e97aa 100644 --- a/src/web/www/style/projectors/proj-base.css +++ b/src/web/www/style/projectors/proj-base.css @@ -3,7 +3,6 @@ @import "panel.css"; @import "proj-probe.css"; @import "proj-type.css"; -@import "proj-dyntype.css"; @import "proj-cards.css"; @import "proj-text.css"; @import "proj-fold.css"; diff --git a/src/web/www/style/projectors/proj-dyntype.css b/src/web/www/style/projectors/proj-dyntype.css deleted file mode 100644 index 79d1824dc6..0000000000 --- a/src/web/www/style/projectors/proj-dyntype.css +++ /dev/null @@ -1,97 +0,0 @@ -/* PROJECTOR: INFO */ - -:root {} - -/* BACKING */ - -.projector.dyntype>svg { - filter: drop-shadow(0.7px 0.7px 0px var(--BR3)); -} - -.projector.dyntype.indicated>svg { - fill: var(--main-indicated); - filter: drop-shadow(0.7px 0.7px 0px var(--main-shadow)); -} - -.projector.dyntype.selected>svg { - filter: none; -} - -/* INLINE VIEW */ - -.projector.dyntype:hover { - color: var(--BLACK); -} - -.projector.dyntype .main { - font-family: var(--code-font); - font-size: var(--base-font-size); - color: var(--code-text); - cursor: pointer; - display: flex; - align-items: center; - gap: 0.4em; -} - -.projector.dyntype.indicated:not(.selected):not(.error) .main { - color: white; -} - -.projector.dyntype .icon { - width: 16px; - height: 16px; - background-image: url(../../img/noun-microscope-7023456.svg); - background-size: cover; - filter: invert(1) brightness(0.4) sepia(1) saturate(1.8) hue-rotate(245deg); -} - -.projector.dyntype.indicated:not(.selected):not(.error) .main .icon { - filter: invert(1); -} - -/* OFFSIDE VIEW */ - -.projector.dyntype .offside { - display: flex; - align-items: center; - justify-content: center; - gap: 0.6em; -} - -.projector.dyntype .offside .mode { - font-weight: 800; - color: var(--main-base); - filter: brightness(0.9); -} - -.projector.dyntype.indicated .offside .mode { - opacity: 100%; - color: var(--main-indicated); -} - -.projector.dyntype .offside .dyntype-cell { - border-radius: 0.05em 0.05em 0.05em 0.2em; - background-color: var(--main-base); - border-bottom: 1px solid var(--main-indicated); -} - -.projector.dyntype.indicated .offside .dyntype-cell { - outline-width: 1px; - outline-style: solid; - outline-color: var(--main-indicated); - border-color: var(--main-indicated); -} - -.projector.dyntype .offside .code { - position: relative; - line-height: initial; - color: var(--exp-indicated); -} - - -.projector.dyntype .code .token.mono { - color: var(--exp-indicated); -} -.projector.dyntype .code .token.poly { - color: var(--exp-indicated); -} \ No newline at end of file From 9a8e9f6c35c1ecc3278ceee83edc1c2cb932b88a Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala Date: Mon, 29 Sep 2025 17:00:36 -0400 Subject: [PATCH 12/65] Types can show their types --- src/haz3lcore/projectors/implementations/TypeProj.re | 3 +++ src/language/term/Typ.re | 9 +++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/haz3lcore/projectors/implementations/TypeProj.re b/src/haz3lcore/projectors/implementations/TypeProj.re index ab50f2ae27..021db8d109 100644 --- a/src/haz3lcore/projectors/implementations/TypeProj.re +++ b/src/haz3lcore/projectors/implementations/TypeProj.re @@ -8,6 +8,7 @@ let expected_ty = (info: option(Info.t)): option(Typ.t) => switch (info) { | Some(InfoExp({ana, _})) | Some(InfoPat({ana, _})) => Some(ana) + | Some(InfoTyp({term, _})) => Some(term) // TODO Expected doesn't make sense for types | _ => None }; @@ -15,6 +16,7 @@ let self_ty = (info: option(Info.t)): option(Typ.t) => switch (info) { | Some(InfoExp({self, _})) => Self.typ_of_exp(self) | Some(InfoPat({self, _})) => Self.typ_of_pat(self) + | Some(InfoTyp({term, _})) => Some(term) | _ => None }; @@ -67,6 +69,7 @@ module M: Projector = { let init = (any: Term.Any.t): option(model) => { switch (any) { | Exp(_) + | Typ(_) // TODO This seems to behave oddly on grout | Pat(_) => Some(Expected) | Any () => Some(Expected) /* Grout don't have sorts rn */ | _ => None diff --git a/src/language/term/Typ.re b/src/language/term/Typ.re index aec6f94aeb..d10afe7526 100644 --- a/src/language/term/Typ.re +++ b/src/language/term/Typ.re @@ -979,6 +979,8 @@ let to_product = (tys: list(t)): t => /* Computes the list of ids in t' that are not in t. Assumes initial ids are distinct. Only returns the id of the root difference. */ let rec diff = (ty: t, ty': t): list(Id.t) => { switch (term_of(ty), term_of(ty')) { + | (Probe(t1, _), _) => diff(t1, ty') + | (_, Probe(t2, _)) => diff(ty, t2) | (Parens(t1), Parens(t2)) => diff(t1, t2) | (Unknown(_), Unknown(_)) => [] | (Unknown(_), _) => [ty' |> rep_id] @@ -999,14 +1001,13 @@ let rec diff = (ty: t, ty': t): list(Id.t) => { | (Prod(_), _) => [ty' |> rep_id] | (TupLabel(l1, t1), TupLabel(l2, t2)) => diff(l1, l2) @ diff(t1, t2) | (TupLabel(_, _), _) => [ty' |> rep_id] + | (List(t1), List(t2)) => diff(t1, t2) + | (List(_), _) => [ty' |> rep_id] | _ => // TODO raise( Failure( - "diff: unsupported types" - ++ pretty_print(ty) - ++ " and " - ++ pretty_print(ty'), + "diff: unsupported types" ++ show(ty) ++ " and " ++ show(ty'), ), ) }; From 9ab6e0c3e7ce8d2e5ad8ee4f86d6ecda7850f1d1 Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Thu, 2 Oct 2025 11:33:39 -0400 Subject: [PATCH 13/65] Inefficient adding of dynamic types to cursor inspector --- src/haz3lcore/lang/MakeTerm.re | 3 -- src/language/statics/Elaborator.re | 30 ++++++++++++++--- src/web/app/Cursor.re | 14 +++++--- src/web/app/common/ProjectorView.re | 7 ---- src/web/app/editors/code/CodeWithStatics.re | 36 +++++++++++++-------- src/web/app/inspector/CursorInspector.re | 35 ++++++++++++++++++-- 6 files changed, 88 insertions(+), 37 deletions(-) diff --git a/src/haz3lcore/lang/MakeTerm.re b/src/haz3lcore/lang/MakeTerm.re index 41ae557ea5..8dbaf8ab97 100644 --- a/src/haz3lcore/lang/MakeTerm.re +++ b/src/haz3lcore/lang/MakeTerm.re @@ -662,10 +662,7 @@ and typ_term: unsorted => (Typ.term, list(Id.t)) = { Label(Token.sub(t, 1, Token.length(t) - 2)) | (["(", ")"], [Typ(body)]) => Parens(body) | (label, [Typ(body)]) when is_probe_wrap(label) => - print_endline("Parsing probe wrap for type"); - print_endline("Id: " ++ Id.str3(id)); let should = should_instrument(id); - print_endline("Should instrument: " ++ string_of_bool(should)); should ? Probe(body, Probe.empty) : body.term; | (["[", "]"], [Typ(body)]) => List(body) | ([t], []) when is_hole_label(t) => hole(tm) diff --git a/src/language/statics/Elaborator.re b/src/language/statics/Elaborator.re index b5adf1985d..d2a85d1b7f 100644 --- a/src/language/statics/Elaborator.re +++ b/src/language/statics/Elaborator.re @@ -23,22 +23,25 @@ let fresh_ascription = (d: Exp.t, t: Typ.t, t': option(Typ.t)) => { ); }; let elaborated_type = - (m: Statics.Map.t, uexp: Exp.t): (Typ.t, Typ.t, Ctx.t, CoCtx.t, Exp.t) => { - let (ana_ty, self_ty, ctx, co_ctx, term) = + (m: Statics.Map.t, uexp: Exp.t) + : (Typ.t, Typ.t, Self.exp, Ctx.t, CoCtx.t, Exp.t) => { + let (ana_ty, ty, self, ctx, co_ctx, term) = switch (Id.Map.find_opt(Exp.rep_id(uexp), m)) { - | Some(Info.InfoExp({ana, ty, ctx, co_ctx, term: new_term, _})) => ( + | Some(Info.InfoExp({ana, ty, self, ctx, co_ctx, term: new_term, _})) => ( ana, ty, + self, ctx, co_ctx, new_term, ) | _ => raise(MissingTypeInfo) }; - let elab_ty = Typ.match_synswitch(ana_ty, self_ty); + let elab_ty = Typ.match_synswitch(ana_ty, ty); ( elab_ty |> Typ.normalize(ctx) |> Typ.all_ids_temp, ana_ty, + self, ctx, co_ctx, term, @@ -177,8 +180,12 @@ let rec elaborate = (m: Statics.Map.t, uexp: Exp.t): (DHExp.t, Typ.t) => { // We store this syntax with the same ID as the original expression and store it on the Info.exp in the Statics.map // We are then pulling this out and using it in place of the actual expression. - let (elaborated_type, ana, ctx, co_ctx, statics_pseudo_elaborated) = + let (elaborated_type, ana, self, ctx, co_ctx, statics_pseudo_elaborated) = elaborated_type(m, uexp); + + let contains_unknown = + Option.map(t => Typ.count_unknowns(t) > 0, Self.typ_of_exp(self)) + |> Option.value(~default=true); let (_, rewrap) = Exp.unwrap(uexp); let uexp = rewrap(statics_pseudo_elaborated.term); @@ -439,6 +446,19 @@ let rec elaborate = (m: Statics.Map.t, uexp: Exp.t): (DHExp.t, Typ.t) => { ); Match(e', List.combine(ps', es')) |> rewrap; }; + + let dhexp = + if (contains_unknown) { + switch (dhexp) { + | {term: Probe(_), _} => dhexp + | _ => { + term: Probe(dhexp, Probe.empty), + annotation: dhexp.annotation, + } // Think about whether it's safe to reuse ids here + }; + } else { + dhexp; + }; (dhexp, elaborated_type); }; diff --git a/src/web/app/Cursor.re b/src/web/app/Cursor.re index deb323658a..c8d5f769ac 100644 --- a/src/web/app/Cursor.re +++ b/src/web/app/Cursor.re @@ -1,11 +1,14 @@ +open Haz3lcore; +open Language; type cursor('update) = { - info: option(Language.Info.t), + info: option(Info.t), + dynamics: option(list(Dynamics.Probe.Closure.t)), selected_text: option(unit => string), - selection: option(Haz3lcore.Segment.t), - indicated_piece: option(Haz3lcore.Piece.t), - editor: option(Haz3lcore.Editor.t), + selection: option(Segment.t), + indicated_piece: option(Piece.t), + editor: option(Editor.t), editor_read_only: bool, - editor_action: Haz3lcore.Action.t => option('update), + editor_action: Action.t => option('update), undo_action: option('update), redo_action: option('update), }; @@ -26,6 +29,7 @@ let map_opt = (f: 'a => option('b), cursor) => { let empty = { info: None, + dynamics: None, selected_text: None, selection: None, indicated_piece: None, diff --git a/src/web/app/common/ProjectorView.re b/src/web/app/common/ProjectorView.re index 813e812e53..7da8b621b2 100644 --- a/src/web/app/common/ProjectorView.re +++ b/src/web/app/common/ProjectorView.re @@ -72,13 +72,6 @@ module Model = { ) => { List.filter_map( ((id, _)) => { - let d = - Id.Map.to_seq(dynamics) - |> Seq.map(fst) - |> Seq.map(Id.str3) - |> List.of_seq; - print_endline("Dynamic Ids: " ++ String.concat(", ", d)); - print_endline("Looking for projector id: " ++ Id.str3(id)); let* p = Id.Map.find_opt(id, projectors); let+ measurement = Measured.find_pr_opt(p, measured); let info = ProjectorInfo.mk_info(p, ~statics, ~dynamics); diff --git a/src/web/app/editors/code/CodeWithStatics.re b/src/web/app/editors/code/CodeWithStatics.re index 9cbc23b344..ea0f6b439e 100644 --- a/src/web/app/editors/code/CodeWithStatics.re +++ b/src/web/app/editors/code/CodeWithStatics.re @@ -47,20 +47,28 @@ module Model = { let get_dynamics = (model: t) => model.dynamics; let get_cursor_info = (model: t): Cursor.cursor(Action.t) => { - info: Indicated.ci_of(model.editor.state.zipper, model.statics.info_map), - indicated_piece: - Indicated.piece''(model.editor.state.zipper) - |> Option.map(((p, _, _)) => p), - selected_text: - Some( - () => Printer.of_segment(model.editor.state.zipper.selection.content), - ), - selection: Some(model.editor.state.zipper.selection.content), - editor: Some(model.editor), - editor_read_only: true, - editor_action: x => Some(x), - undo_action: None, - redo_action: None, + let info = + Indicated.ci_of(model.editor.state.zipper, model.statics.info_map); + let id = Indicated.index(model.editor.state.zipper); + { + info, + dynamics: + Option.bind(id, Language.Dynamics.Map.lookup(_, model.dynamics)), + indicated_piece: + Indicated.piece''(model.editor.state.zipper) + |> Option.map(((p, _, _)) => p), + selected_text: + Some( + () => + Printer.of_segment(model.editor.state.zipper.selection.content), + ), + selection: Some(model.editor.state.zipper.selection.content), + editor: Some(model.editor), + editor_read_only: true, + editor_action: x => Some(x), + undo_action: None, + redo_action: None, + }; }; [@deriving (show({with_path: false}), sexp, yojson)] diff --git a/src/web/app/inspector/CursorInspector.re b/src/web/app/inspector/CursorInspector.re index 08c115a252..c746b1285c 100644 --- a/src/web/app/inspector/CursorInspector.re +++ b/src/web/app/inspector/CursorInspector.re @@ -682,14 +682,43 @@ let view_of_info = (~globals, ci): list(Node.t) => { }; }; -let inspector_view = (~globals, ci): Node.t => +let dynamic_type = closures => { + let statics = Statics.mk(CoreSettings.on, Builtins.ctx_init(Some(Int))); + let type_of = (c: Dynamics.Probe.Closure.t) => { + IdTagged.rep_id(c.value) + |> Id.Map.find_opt(_, statics(c.value)) + |> Option.bind( + _, + fun + | InfoExp(e) => { + Some(e.ty); + } + | _ => None, + ); + }; + let types = List.map(type_of, closures) |> Util.OptUtil.sequence; + + Option.map(Typ.consistent_join(Ctx.empty), types); +}; + +let inspector_view = + (~globals, ~dynamics: option(list(Dynamics.Probe.Closure.t)), ci) + : Node.t => { + let dyn = Option.bind(dynamics, dynamic_type); div( ~attrs=[ Attr.id("cursor-inspector"), clss([Info.is_error(ci) ? errc : okc]), ], - view_of_info(~globals, ci), + view_of_info(~globals, ci) + @ ( + switch (dyn) { + | None => [] + | Some(ty) => [text("Dynamic Type:"), view_type(~globals, ty)] + } + ), ); +}; let view = ( @@ -710,7 +739,7 @@ let view = | None => err_view("Whitespace or Comment") | Some(ci) => bar_view([ - inspector_view(~globals, ci), + inspector_view(~globals, ~dynamics=cursor.dynamics, ci), ProjectorPanel.view( ~inject= a => From 930e42f221f9bb4974bc15c2262c0e8ea2ef146a Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Fri, 10 Oct 2025 09:49:26 -0400 Subject: [PATCH 14/65] Fix transition_multiple call to include update_probe parameter --- src/language/dynamics/transition/PatternMatch.re | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/language/dynamics/transition/PatternMatch.re b/src/language/dynamics/transition/PatternMatch.re index 247bd07c12..5da9c0ce5c 100644 --- a/src/language/dynamics/transition/PatternMatch.re +++ b/src/language/dynamics/transition/PatternMatch.re @@ -15,7 +15,7 @@ let combine_result = (r1: match_result, r2: match_result): match_result => let rec matches = (~update_probe, capture, dp: Pat.t, d: DHExp.t): match_result => { let matches = matches(~update_probe, capture); - let d = Ascriptions.transition_multiple(d); + let d = Ascriptions.transition_multiple(~update_probe, d); switch (DHPat.term_of(dp)) { | Invalid(_) | EmptyHole From 2585f43e20e0a22e449ef96439bd191ca1c20ca4 Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Fri, 17 Oct 2025 10:01:03 -0400 Subject: [PATCH 15/65] Address merge errors --- src/haz3lcore/projectors/implementations/DynTypeProj.re | 2 +- src/language/term/Equality.re | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/haz3lcore/projectors/implementations/DynTypeProj.re b/src/haz3lcore/projectors/implementations/DynTypeProj.re index 3d10d4c6b8..e9f5fa1ad3 100644 --- a/src/haz3lcore/projectors/implementations/DynTypeProj.re +++ b/src/haz3lcore/projectors/implementations/DynTypeProj.re @@ -19,7 +19,7 @@ module M: Projector = { type action = | ToggleDisplay; - let init = (any: Term.Any.t): option(model) => { + let init = (any: Any.t): option(model) => { switch (any) { | Exp(_) | Pat(_) => Some(Expected) diff --git a/src/language/term/Equality.re b/src/language/term/Equality.re index 1c5f085ce7..5798ad940d 100644 --- a/src/language/term/Equality.re +++ b/src/language/term/Equality.re @@ -550,6 +550,8 @@ let equality = | (Unknown(Hole(MultiHole(_))), _) => false | (Unknown(Internal), Unknown(Internal)) => true | (Unknown(Internal), _) => false + | (Unknown(Inconsistent), Unknown(Inconsistent)) => true + | (Unknown(Inconsistent), _) => false // Other forms: compare. | (Atom(a1), Atom(a2)) => a1 == a2 From b08f6a11ac804699647582d7ffdfba7060351b60 Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala Date: Mon, 3 Nov 2025 20:23:57 -0500 Subject: [PATCH 16/65] Fix probe collection for types - Changed Statics.Map.t to StaticsBase.Map.t in Dynamics.re instrument functions - Updated method calls from Statics.Map to StaticsBase.Map for refs_in and bound_in - Refactored Ascriptions.re transition function to remove update_probe param and return closure_closures with DHExp.t - Modified logic to collect and propagate closures in ascription transitions for better probe handling and modularity --- src/language/dynamics/Dynamics.re | 8 +- .../dynamics/transition/Ascriptions.re | 186 ++++++++---------- .../dynamics/transition/PatternMatch.re | 31 +-- .../dynamics/transition/Transition.re | 35 +--- src/language/dynamics/transition/Unboxing.re | 3 +- test/Test_PatternMatch.re | 7 +- 6 files changed, 119 insertions(+), 151 deletions(-) diff --git a/src/language/dynamics/Dynamics.re b/src/language/dynamics/Dynamics.re index 941f3764b8..8bea811686 100644 --- a/src/language/dynamics/Dynamics.re +++ b/src/language/dynamics/Dynamics.re @@ -119,12 +119,12 @@ module Probe = { /* Intercepts a probe form and adds in static semantic information * to guide dynamic information gathering */ - let instrument_exp = (m: Statics.Map.t, id: Id.t, _: Probe.t): Probe.t => { - refs: Statics.Map.refs_in(m, id), + let instrument_exp = (m: StaticsBase.Map.t, id: Id.t, _: Probe.t): Probe.t => { + refs: StaticsBase.Map.refs_in(m, id), }; - let instrument_pat = (m: Statics.Map.t, id: Id.t, _: Probe.t): Probe.t => { - refs: Statics.Map.bound_in(m, id), + let instrument_pat = (m: StaticsBase.Map.t, id: Id.t, _: Probe.t): Probe.t => { + refs: StaticsBase.Map.bound_in(m, id), }; }; diff --git a/src/language/dynamics/transition/Ascriptions.re b/src/language/dynamics/transition/Ascriptions.re index 8a6f840bd8..b5e753f885 100644 --- a/src/language/dynamics/transition/Ascriptions.re +++ b/src/language/dynamics/transition/Ascriptions.re @@ -7,33 +7,38 @@ Ascriptions should be propagated inside of expressions when consistent. e.g. [1, 2] : [Int] -> [1 : Int, 2 : Int] */ +type closure_closures = list(Probe.call_stack => Dynamics.Probe.Closure.t); let rec transition = - ( - ~update_probe=( - _syntax_id: Id.t, - _value: DHExp.t, - _env: Environment.t(Exp.t), - _call_stack: Probe.call_stack, - _pr: Probe.t, - ) => - (), - ~recursive=false, - d: DHExp.t, - ) - : option(DHExp.t) => { - let recur = (d: DHExp.t): DHExp.t => + (~recursive=false, d: DHExp.t): option((closure_closures, DHExp.t)) => { + let recur = (d: DHExp.t): (closure_closures, DHExp.t) => if (recursive) { - transition(~update_probe, ~recursive, d) |> Option.value(~default=d); + transition(~recursive, d) |> Option.value(~default=([], d)); } else { - d; + ([], d); }; switch (DHExp.term_of(d)) { | Asc(e, t) => switch (DHExp.term_of(e), Typ.term_of(Typ.unroll(t))) { | (_, Probe(t', p)) => - update_probe(Typ.rep_id(t), e, Environment.empty, [], p); - Asc(e, t') |> DHExp.fresh |> transition(~update_probe, ~recursive); + Asc(e, t') + |> DHExp.fresh + |> transition(~recursive) + |> Option.map(((closures, d)) => + ( + List.cons( + Dynamics.Probe.Closure.mk( + Typ.rep_id(t), + e, + Environment.empty, + _, + p, + ), + closures, + ), + d, + ) + ) | (Asc(e, t'), t) // This is only necessary because sometimes we add two ascriptions and aren't marking it as a non-value when @@ -50,37 +55,40 @@ let rec transition = } | (e, Parens(t)) => // This is an impossible case since types should be normalized before coming to transitions - transition( - ~update_probe, - ~recursive, - Asc(e |> DHExp.fresh, t) |> DHExp.fresh, - ) + transition(~recursive, Asc(e |> DHExp.fresh, t) |> DHExp.fresh) | (Closure(ce, d), t) => - transition( - ~update_probe, - ~recursive, - Asc(d, t |> Typ.fresh) |> DHExp.fresh, - ) - |> Option.map(d => Closure(ce, d) |> DHExp.fresh) + transition(~recursive, Asc(d, t |> Typ.fresh) |> DHExp.fresh) + |> Option.map( + Util.PairUtil.map_snd(d => Closure(ce, d) |> DHExp.fresh), + ) | (Fun(p, e, t, v), Arrow(t1, t2)) => - Some( + Some(( + [], IdTagged.FreshGrammar.( Exp.(fn(Pat.(asc(p, t1)), asc(e, t2), t, v)) ), - ) + )) | (TupLabel({term: ExplicitNonlabel, _}, e), _) => Some(recur(Asc(e, t) |> DHExp.fresh)) | (TupLabel(l, e), TupLabel(_l2, t)) => // TODO Figure out what to do if the labels don't match - Some(TupLabel(l, recur(Asc(e, t) |> DHExp.fresh)) |> DHExp.fresh) + let (closures, e) = recur(Asc(e, t) |> DHExp.fresh); + Some((closures, TupLabel(l, e) |> DHExp.fresh)); | (Tuple(es), Prod(tys)) when List.length(es) == List.length(tys) => - Some( - Tuple( - List.map2((e, ty) => recur(Asc(e, ty) |> DHExp.fresh), es, tys), + let (closures_list, es) = + List.map2( + (e, ty) => { + let (closures, e) = recur(Asc(e, ty) |> DHExp.fresh); + (closures, e); + }, + es, + tys, ) - |> DHExp.fresh, - ) - | (_, Unknown(_)) => Some(e) + |> List.split; + Some((closures_list |> List.flatten, Tuple(es) |> DHExp.fresh)); + | (_, Unknown(_)) => + let (closures, e) = recur(e); + Some((closures, e)); | (Atom(value) as d, Atom(typ)) => switch (value, typ) { | (Int(_), Int) @@ -88,7 +96,7 @@ let rec transition = | (Nat(_), Nat) | (Float(_), Float) | (SInt(_), SInt) - | (Bool(_), Bool) => Some(d |> Exp.fresh) + | (Bool(_), Bool) => Some(([], d |> Exp.fresh)) | (Int(_), _) | (String(_), _) | (Nat(_), _) @@ -97,43 +105,31 @@ let rec transition = | (Bool(_), _) => None } | (ListLit(ds), List(ty)) => - Some( - ListLit(List.map(d => recur(Asc(d, ty) |> DHExp.fresh), ds)) - |> DHExp.fresh, - ) + let (closures, ds) = + List.map(d => recur(Asc(d, ty) |> DHExp.fresh), ds) |> List.split; + + Some((closures |> List.flatten, ListLit(ds) |> DHExp.fresh)); | (Cons(d1, d2), List(ty)) => - Some( - Cons( - recur(Asc(d1, ty) |> DHExp.fresh), - recur(Asc(d2, t) |> DHExp.fresh), - ) - |> DHExp.fresh, - ) + let (closures1, d1) = recur(Asc(d1, ty) |> DHExp.fresh); + let (closures2, d2) = recur(Asc(d2, t) |> DHExp.fresh); + Some((closures1 @ closures2, Cons(d1, d2) |> DHExp.fresh)); | (TypFun(tp, e, v), Forall(tp', t')) => let new_ty: Typ.t = switch (TPat.tyvar_of_utpat(tp)) { | Some(tyvar) => Var(tyvar) |> Typ.temp | None => Unknown(Internal) |> Typ.temp }; - Some( - TypFun( - tp, - recur(Asc(e, Typ.subst(new_ty, tp', t')) |> DHExp.fresh), - v, - ) - |> DHExp.fresh, - ); + let (closures, e) = + recur(Asc(e, Typ.subst(new_ty, tp', t')) |> DHExp.fresh); + Some((closures, TypFun(tp, e, v) |> DHExp.fresh)); | (If(e, e1, e2), t) => - Some( - If( - recur(e), - recur(Asc(e1, t |> Typ.temp) |> DHExp.fresh), - recur(Asc(e2, t |> Typ.temp) |> DHExp.fresh), - ) - |> DHExp.fresh, - ) + let (closures, e) = recur(Asc(e, t |> Typ.temp) |> DHExp.fresh); + let (closures1, e1) = recur(Asc(e1, t |> Typ.temp) |> DHExp.fresh); + let (closures2, e2) = recur(Asc(e2, t |> Typ.temp) |> DHExp.fresh); + Some((closures @ closures1 @ closures2, If(e, e1, e2) |> DHExp.fresh)); | (Match(e, rules), t) => - Some( + Some(( + [], Match( e, List.map( @@ -142,7 +138,7 @@ let rec transition = ), ) |> DHExp.fresh, - ) + )) | ( Ap( Forward, @@ -156,23 +152,21 @@ let rec transition = let entry = ConstructorMap.get_entry(c, m); switch (entry) { | Some(Some(t')) => - Some( - Ap(Forward, con, recur(Asc(payload, t') |> DHExp.fresh)) - |> DHExp.fresh, - ) + let (closures, e) = recur(Asc(payload, t') |> DHExp.fresh); + Some((closures, Ap(Forward, con, e) |> DHExp.fresh)); | Some(None) | None => None }; | (Constructor(_, Some(Some(t))), t') when Typ.is_consistent(Ctx.empty, Typ.unroll(t), t' |> Typ.temp) => - Some(e) - | (Test(_), Prod([])) => Some(e) + Some(([], e)) + | (Test(_), Prod([])) => Some(([], e)) // These are non-value cases we're handling to process ascriptions as early as possible | (BinOp(bin_op, _, _), _) => switch (Operators.semantics_of_bin_op(bin_op)) { | DefinedPoly(Equals | NotEquals) when Typ.is_consistent(Ctx.empty, t, Atom(Bool) |> Typ.temp) => - Some(e) + Some(([], e)) | Defined(_, _, ty_out, _) when Typ.is_consistent( @@ -180,7 +174,7 @@ let rec transition = t, Atom(Atom.cls_of_kind(ty_out)) |> Typ.temp, ) => - Some(e) + Some(([], e)) | Undefined(_) | DefinedPoly(_) | Defined(_) => None @@ -194,24 +188,20 @@ let rec transition = t, Atom(Atom.cls_of_kind(ty_out)) |> Typ.temp, ) => - Some(e) + Some(([], e)) | Undefined(_) | Defined(_) => None } | (ListConcat(d1, d2), List(_)) => - Some( - ListConcat( - recur(Asc(d1, t) |> DHExp.fresh), - recur(Asc(d2, t) |> DHExp.fresh), - ) - |> DHExp.fresh, - ) + let (closures, e1) = recur(Asc(d1, t) |> DHExp.fresh); + let (closures2, e2) = recur(Asc(d2, t) |> DHExp.fresh); + Some((closures @ closures2, ListConcat(e1, e2) |> DHExp.fresh)); | (Let(p, e1, e2), _) => - Some(Let(p, e1, Asc(e2, t) |> DHExp.fresh) |> DHExp.fresh) + Some(([], Let(p, e1, Asc(e2, t) |> DHExp.fresh) |> DHExp.fresh)) | (Seq(e1, e2), _) => - Some(Seq(e1, Asc(e2, t) |> DHExp.fresh) |> DHExp.fresh) + Some(([], Seq(e1, Asc(e2, t) |> DHExp.fresh) |> DHExp.fresh)) | (Parens(e), _) => - Some(Parens(Asc(e, t) |> DHExp.fresh) |> DHExp.fresh) + Some(([], Parens(Asc(e, t) |> DHExp.fresh) |> DHExp.fresh)) // We _could_ do this, but it would be a bit weird | (Use(_), _) // I'm scaredto do Use because the type-directed literals might make this look weird in the stepper | (BuiltinFun(_), _) @@ -253,21 +243,11 @@ let rec transition = }; }; -let rec transition_multiple = - ( - ~update_probe=( - _syntax_id: Id.t, - _value: DHExp.t, - _env: Environment.t(Exp.t), - _call_stack: Probe.call_stack, - _pr: Probe.t, - ) => - (), - d: DHExp.t, - ) - : DHExp.t => { - switch (transition(~update_probe, ~recursive=true, d)) { - | Some(d'') => transition_multiple(~update_probe, d'') - | None => d +let rec transition_multiple = (d: DHExp.t): (closure_closures, DHExp.t) => { + switch (transition(~recursive=true, d)) { + | Some((closures, d'')) => + let (c, d) = transition_multiple(d''); + (closures @ c, d); + | None => ([], d) }; }; diff --git a/src/language/dynamics/transition/PatternMatch.re b/src/language/dynamics/transition/PatternMatch.re index ee26d26167..a122a1547a 100644 --- a/src/language/dynamics/transition/PatternMatch.re +++ b/src/language/dynamics/transition/PatternMatch.re @@ -15,9 +15,16 @@ let combine_result = (r1: match_result, r2: match_result): match_result => }; let rec matches = - (~update_probe, capture, dp: Pat.t, d: DHExp.t): match_result => { - let matches = matches(~update_probe, capture); - let d = Ascriptions.transition_multiple(~update_probe, d); + ( + capture, + capture': (Probe.call_stack => Dynamics.Probe.Closure.t) => unit, // Use writer + dp: Pat.t, + d: DHExp.t, + ) + : match_result => { + let matches = matches(capture, capture'); + let (closures, d) = Ascriptions.transition_multiple(d); + List.iter(capture', closures); switch (DHPat.term_of(dp)) { | Invalid(_) | EmptyHole @@ -67,13 +74,10 @@ let rec matches = capture(pr, dp, d, inner_match); inner_match; | Asc(p, t1) => - matches( - p, - Ascriptions.transition_multiple( - ~update_probe, - Asc(d, t1) |> DHExp.fresh, - ), - ) + let (closures, d) = + Ascriptions.transition_multiple(Asc(d, t1) |> DHExp.fresh); + List.iter(capture', closures); + matches(p, d); }; }; @@ -84,7 +88,7 @@ type matches_and_closures = { closures: closure_closures, }; -let matches = (~update_probe, dp: Pat.t, d: DHExp.t): matches_and_closures => { +let matches = (dp: Pat.t, d: DHExp.t): matches_and_closures => { /* Closure capture for Probe instrumentation */ let closure_closures: ref(closure_closures) = ref([]); let capture = @@ -105,7 +109,10 @@ let matches = (~update_probe, dp: Pat.t, d: DHExp.t): matches_and_closures => { closure_closures^, ) }; - let res = matches(~update_probe, capture, dp, d); + let capture' = x => { + closure_closures := List.cons(x, closure_closures^); + }; + let res = matches(capture, capture', dp, d); { matches: res, closures: closure_closures^, diff --git a/src/language/dynamics/transition/Transition.re b/src/language/dynamics/transition/Transition.re index 856fdd4602..143021ea19 100644 --- a/src/language/dynamics/transition/Transition.re +++ b/src/language/dynamics/transition/Transition.re @@ -211,15 +211,6 @@ module Transition = (EV: EV_MODE) => { d, ) : EV.result => { - let update_probe' = (id, d, env, call_stack, pr) => { - print_endline( - "Cannot update probe: " - ++ Id.str3(id) - ++ " with value " - ++ DHExp.show(d), - ); - print_endline("Need to wire this up later."); - }; // Split DHExp into term and id information let (term, rewrap) = DHExp.unwrap(d); let wrap_ctx = (term): EvalCtx.t => @@ -282,8 +273,7 @@ module Transition = (EV: EV_MODE) => { and. d1' = req_final(req(env), d1 => Let1(dp, d1, d2) |> wrap_ctx, d1); let.wrap_closure _ = (env, Let(dp, d1', d2) |> rewrap); - let {matches, closures} = - matches(~update_probe=update_probe', dp, d1'); + let {matches, closures} = matches(dp, d1'); let matches_str = { switch (matches) { | IndetMatch @@ -441,7 +431,7 @@ module Transition = (EV: EV_MODE) => { switch (unboxed_fun) { | Constructor(_) => Constructor | FunEnv(dp, d3, replacement_env) => - let matches = matches(~update_probe=update_probe', dp, d2'); + let matches = matches(dp, d2'); switch (matches.matches) { | IndetMatch | DoesNotMatch => Indet @@ -458,7 +448,7 @@ module Transition = (EV: EV_MODE) => { }); }; | FunNoEnv(dp, d3) when mode == `Substitution => - let matches = matches(~update_probe=update_probe', dp, d2'); + let matches = matches(dp, d2'); switch (matches.matches) { | IndetMatch | DoesNotMatch => Indet @@ -822,7 +812,7 @@ module Transition = (EV: EV_MODE) => { fun | [] => None | [(dp, d2), ...rules] => { - let matches = matches(~update_probe=update_probe', dp, d1); + let matches = matches(dp, d1); switch (matches.matches) { | Matches(env') => Some((env', d2, matches.closures)) | DoesNotMatch => next_rule(rules) @@ -879,28 +869,23 @@ module Transition = (EV: EV_MODE) => { let.wrap_closure _ = (env, d); Indet; | Asc(d', t) => - switch (Ascriptions.transition(~update_probe=update_probe', d)) { - | Some(d') => + switch (Ascriptions.transition(d)) { + | Some((closures, d')) => let. _ = otherwise(env, d); Step({ expr: d', - side_effects: [], + side_effects: [RecordPatProbes(closures)], kind: Ascription, is_value: false, }); | None => let. _ = otherwise(env, d => Asc(d, t) |> rewrap) and. d' = req_final(req(env), d => Asc(d, t) |> wrap_ctx, d'); - switch ( - Ascriptions.transition( - ~update_probe=update_probe', - Asc(d', t) |> rewrap, - ) - ) { - | Some(d) => + switch (Ascriptions.transition(Asc(d', t) |> rewrap)) { + | Some((closures, d)) => Step({ expr: d, - side_effects: [], + side_effects: [RecordPatProbes(closures)], kind: Ascription, is_value: false, }) diff --git a/src/language/dynamics/transition/Unboxing.re b/src/language/dynamics/transition/Unboxing.re index 50cfb70581..3c83bb9623 100644 --- a/src/language/dynamics/transition/Unboxing.re +++ b/src/language/dynamics/transition/Unboxing.re @@ -128,7 +128,8 @@ let rec unbox: type a. (unbox_request(a), DHExp.t) => unboxed(a) = | (LabeledTupleEntries, Tuple(ds)) => let unbox_tup_label = (d: Exp.t): option((option(LabeledTuple.label), Exp.t)) => { - switch (Ascriptions.transition_multiple(d).term) { + switch (snd(Ascriptions.transition_multiple(d)).term) { + // TODO // TODO // TODO Think about whether we should transition here diff --git a/test/Test_PatternMatch.re b/test/Test_PatternMatch.re index 908ed2091b..bdbd820905 100644 --- a/test/Test_PatternMatch.re +++ b/test/Test_PatternMatch.re @@ -43,12 +43,7 @@ let tests = ( ) ); let matches: PatternMatch.match_result = - PatternMatch.matches( - ~update_probe=(_, _, _, _, _) => (), - pat, - expression, - ). - matches; + PatternMatch.matches(pat, expression).matches; let equal_match_result = (r1: PatternMatch.match_result, r2: PatternMatch.match_result) From 2e5350a291f6d5452e339505bd2189eb43233a89 Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala Date: Mon, 3 Nov 2025 21:23:18 -0500 Subject: [PATCH 17/65] add WriterMonad - Implement WriterMonad module in src/util/WriterMonad.re for accumulating output in computations - Add WriterMonad to Util.re exports - Add comprehensive tests in test/Test_WriterMonad.re covering return, bind, tell, listen, and pass operations - Update test/dune to include new test module This enables functional logging and output accumulation in the codebase. --- src/util/Util.re | 1 + src/util/WriterMonad.re | 46 +++++++++++++++ test/Test_WriterMonad.re | 124 +++++++++++++++++++++++++++++++++++++++ test/dune | 2 +- test/haz3ltest.re | 1 + 5 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 src/util/WriterMonad.re create mode 100644 test/Test_WriterMonad.re diff --git a/src/util/Util.re b/src/util/Util.re index 6c233dc3df..b82c69db8a 100644 --- a/src/util/Util.re +++ b/src/util/Util.re @@ -12,6 +12,7 @@ module OptUtil = OptUtil; module PairUtil = PairUtil; module Result = Result; module StateMonad = StateMonad; +module WriterMonad = WriterMonad; module StringUtil = StringUtil; module TimeUtil = TimeUtil; module TupleUtil = TupleUtil; diff --git a/src/util/WriterMonad.re b/src/util/WriterMonad.re new file mode 100644 index 0000000000..695e2aed39 --- /dev/null +++ b/src/util/WriterMonad.re @@ -0,0 +1,46 @@ +module type WRITER = { + [@deriving sexp] + type t; + let empty: t; + let append: (t, t) => t; +}; + +module type S = { + type writer; + + [@deriving sexp] + type t('a); + let return: 'a => t('a); + let bind: (t('a), 'a => t('b)) => t('b); + + let tell: writer => t(unit); + let listen: t('a) => t(('a, writer)); + let pass: t(('a, writer => writer)) => t('a); +}; + +module Make = (W: WRITER) => { + [@deriving sexp] + type writer = W.t; + + module T = { + [@deriving sexp] + type t('a) = (writer, 'a); + + let return = x => (W.empty, x); + + let bind = ((w1, x), f) => { + let (w2, y) = f(x); + (W.append(w1, w2), y); + }; + + let tell = w => (w, ()); + + let listen = ((w, x)) => (w, (x, w)); + + let pass = ((w, (x, f))) => (f(w), x); + }; + + include T; + + include Monads.Make_Monad_B(T); +}; diff --git a/test/Test_WriterMonad.re b/test/Test_WriterMonad.re new file mode 100644 index 0000000000..c37aab961b --- /dev/null +++ b/test/Test_WriterMonad.re @@ -0,0 +1,124 @@ +open Alcotest; +open Util; + +module StringWriter = { + [@deriving sexp] + type t = string; + let empty = ""; + let append = (s1, s2) => s1 ++ s2; +}; + +module StringWriterMonad = Util.WriterMonad.Make(StringWriter); + +let tests = ( + "WriterMonad", + [ + test_case( + "return produces empty writer", + `Quick, + () => { + let result = StringWriterMonad.return(42); + check( + pair(string, int), + "return with empty writer", + ("", 42), + result, + ); + }, + ), + test_case( + "tell adds to writer", + `Quick, + () => { + let result = StringWriterMonad.tell("hello"); + check( + pair(string, unit), + "tell adds message", + ("hello", ()), + result, + ); + }, + ), + test_case( + "bind combines writers", + `Quick, + () => { + let computation = + StringWriterMonad.Syntax.( + let* () = StringWriterMonad.tell("start "); + let* () = StringWriterMonad.tell("middle "); + let* () = StringWriterMonad.tell("end"); + StringWriterMonad.return("done") + ); + check( + pair(string, string), + "bind combines writers", + ("start middle end", "done"), + computation, + ); + }, + ), + test_case( + "listen captures writer", + `Quick, + () => { + let computation = + StringWriterMonad.Syntax.( + let* () = StringWriterMonad.tell("log1 "); + let* () = StringWriterMonad.tell("log2"); + StringWriterMonad.return(123) + ); + let result = StringWriterMonad.listen(computation); + check( + pair(string, pair(int, string)), + "listen captures writer", + ("log1 log2", (123, "log1 log2")), + result, + ); + }, + ), + test_case( + "pass modifies writer", + `Quick, + () => { + let computation = + StringWriterMonad.Syntax.( + let* () = StringWriterMonad.tell("original"); + StringWriterMonad.return(("result", w => "[" ++ w ++ "]")) + ); + let result = StringWriterMonad.pass(computation); + check( + pair(string, string), + "pass modifies writer", + ("[original]", "result"), + result, + ); + }, + ), + test_case( + "complex computation with let syntax", + `Quick, + () => { + let computation = + StringWriterMonad.Syntax.( + let* () = StringWriterMonad.tell("Begin "); + let* x = StringWriterMonad.return(10); + let* () = + StringWriterMonad.tell( + "Processing " ++ string_of_int(x) ++ " ", + ); + let* y = StringWriterMonad.return(x * 2); + let* () = + StringWriterMonad.tell("Result: " ++ string_of_int(y) ++ " "); + StringWriterMonad.return(y + 5) + ); + check( + pair(string, int), + "complex computation", + ("Begin Processing 10 Result: 20 ", 25), + computation, + ); + }, + ), + ], +); diff --git a/test/dune b/test/dune index 7c65972125..39ba484629 100644 --- a/test/dune +++ b/test/dune @@ -5,4 +5,4 @@ (libraries web menhirParser junit_alcotest bisect_ppx.runtime) (modes js) (preprocess - (pps ppx_deriving.show))) + (pps ppx_deriving.show ppx_sexp_conv))) diff --git a/test/haz3ltest.re b/test/haz3ltest.re index 727b1194f6..8c1e0f4ee1 100644 --- a/test/haz3ltest.re +++ b/test/haz3ltest.re @@ -23,6 +23,7 @@ let (suite, _) = Test_Menhir.tests, Test_StringUtil.tests, Test_PatternMatch.tests, + Test_WriterMonad.tests, ] @ Test_Typ.tests @ Test_Info.tests From 941e2491cc75b0f01dfb001130e77ba38bb8cd63 Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala Date: Tue, 4 Nov 2025 08:37:51 -0500 Subject: [PATCH 18/65] Use writer monad in ascriptions transition --- .../dynamics/transition/Ascriptions.re | 235 +++++++++++------- src/util/Monads.re | 1 - src/util/Monads.rei | 1 - src/util/WriterMonad.re | 4 - 4 files changed, 148 insertions(+), 93 deletions(-) diff --git a/src/language/dynamics/transition/Ascriptions.re b/src/language/dynamics/transition/Ascriptions.re index b5e753f885..05e4506e12 100644 --- a/src/language/dynamics/transition/Ascriptions.re +++ b/src/language/dynamics/transition/Ascriptions.re @@ -9,36 +9,48 @@ */ type closure_closures = list(Probe.call_stack => Dynamics.Probe.Closure.t); +module ClosureWriter = + Util.WriterMonad.Make({ + type t = closure_closures; + let empty = []; + let append = (@); + }); + let rec transition = - (~recursive=false, d: DHExp.t): option((closure_closures, DHExp.t)) => { - let recur = (d: DHExp.t): (closure_closures, DHExp.t) => + (~recursive=false, d: DHExp.t): option(ClosureWriter.t(DHExp.t)) => { + let recur = (d: DHExp.t): ClosureWriter.t(DHExp.t) => if (recursive) { - transition(~recursive, d) |> Option.value(~default=([], d)); + transition(~recursive, d) + |> Option.value(~default=ClosureWriter.return(d)); } else { - ([], d); + ClosureWriter.return(d); }; switch (DHExp.term_of(d)) { | Asc(e, t) => switch (DHExp.term_of(e), Typ.term_of(Typ.unroll(t))) { | (_, Probe(t', p)) => - Asc(e, t') - |> DHExp.fresh - |> transition(~recursive) - |> Option.map(((closures, d)) => - ( - List.cons( - Dynamics.Probe.Closure.mk( - Typ.rep_id(t), - e, - Environment.empty, - _, - p, - ), - closures, - ), - d, - ) - ) + Some( + ClosureWriter.Syntax.( + let* d = + Asc(e, t') + |> DHExp.fresh + |> transition(~recursive) + |> Option.value( + ~default=ClosureWriter.return(Asc(e, t') |> DHExp.fresh), + ); + let* () = + ClosureWriter.tell([ + Dynamics.Probe.Closure.mk( + Typ.rep_id(t), + e, + Environment.empty, + _, + p, + ), + ]); + ClosureWriter.return(d) + ), + ) | (Asc(e, t'), t) // This is only necessary because sometimes we add two ascriptions and aren't marking it as a non-value when @@ -57,38 +69,50 @@ let rec transition = // This is an impossible case since types should be normalized before coming to transitions transition(~recursive, Asc(e |> DHExp.fresh, t) |> DHExp.fresh) | (Closure(ce, d), t) => - transition(~recursive, Asc(d, t |> Typ.fresh) |> DHExp.fresh) - |> Option.map( - Util.PairUtil.map_snd(d => Closure(ce, d) |> DHExp.fresh), - ) + Some( + ClosureWriter.Syntax.( + let* d = + transition(~recursive, Asc(d, t |> Typ.fresh) |> DHExp.fresh) + |> Option.value( + ~default= + ClosureWriter.return( + Asc(d, t |> Typ.fresh) |> DHExp.fresh, + ), + ); + ClosureWriter.return(Closure(ce, d) |> DHExp.fresh) + ), + ) | (Fun(p, e, t, v), Arrow(t1, t2)) => - Some(( - [], - IdTagged.FreshGrammar.( - Exp.(fn(Pat.(asc(p, t1)), asc(e, t2), t, v)) + Some( + ClosureWriter.return( + IdTagged.FreshGrammar.( + Exp.(fn(Pat.(asc(p, t1)), asc(e, t2), t, v)) + ), ), - )) + ) | (TupLabel({term: ExplicitNonlabel, _}, e), _) => Some(recur(Asc(e, t) |> DHExp.fresh)) | (TupLabel(l, e), TupLabel(_l2, t)) => // TODO Figure out what to do if the labels don't match - let (closures, e) = recur(Asc(e, t) |> DHExp.fresh); - Some((closures, TupLabel(l, e) |> DHExp.fresh)); + Some( + ClosureWriter.Syntax.( + let* e = recur(Asc(e, t) |> DHExp.fresh); + ClosureWriter.return(TupLabel(l, e) |> DHExp.fresh) + ), + ) | (Tuple(es), Prod(tys)) when List.length(es) == List.length(tys) => - let (closures_list, es) = - List.map2( - (e, ty) => { - let (closures, e) = recur(Asc(e, ty) |> DHExp.fresh); - (closures, e); - }, - es, - tys, - ) - |> List.split; - Some((closures_list |> List.flatten, Tuple(es) |> DHExp.fresh)); + Some( + ClosureWriter.Syntax.( + let* es = + List.map2((e, ty) => recur(Asc(e, ty) |> DHExp.fresh), es, tys) + |> ClosureWriter.sequence; + ClosureWriter.return(Tuple(es) |> DHExp.fresh) + ), + ) | (_, Unknown(_)) => - let (closures, e) = recur(e); - Some((closures, e)); + Some( + ClosureWriter.Syntax.(let* e = recur(e); ClosureWriter.return(e)), + ) | (Atom(value) as d, Atom(typ)) => switch (value, typ) { | (Int(_), Int) @@ -96,7 +120,7 @@ let rec transition = | (Nat(_), Nat) | (Float(_), Float) | (SInt(_), SInt) - | (Bool(_), Bool) => Some(([], d |> Exp.fresh)) + | (Bool(_), Bool) => Some(ClosureWriter.return(d |> Exp.fresh)) | (Int(_), _) | (String(_), _) | (Nat(_), _) @@ -105,40 +129,56 @@ let rec transition = | (Bool(_), _) => None } | (ListLit(ds), List(ty)) => - let (closures, ds) = - List.map(d => recur(Asc(d, ty) |> DHExp.fresh), ds) |> List.split; - - Some((closures |> List.flatten, ListLit(ds) |> DHExp.fresh)); + Some( + ClosureWriter.Syntax.( + let* ds = + List.map(d => recur(Asc(d, ty) |> DHExp.fresh), ds) + |> ClosureWriter.sequence; + ClosureWriter.return(ListLit(ds) |> DHExp.fresh) + ), + ) | (Cons(d1, d2), List(ty)) => - let (closures1, d1) = recur(Asc(d1, ty) |> DHExp.fresh); - let (closures2, d2) = recur(Asc(d2, t) |> DHExp.fresh); - Some((closures1 @ closures2, Cons(d1, d2) |> DHExp.fresh)); + Some( + ClosureWriter.Syntax.( + let* d1 = recur(Asc(d1, ty) |> DHExp.fresh); + let* d2 = recur(Asc(d2, t) |> DHExp.fresh); + ClosureWriter.return(Cons(d1, d2) |> DHExp.fresh) + ), + ) | (TypFun(tp, e, v), Forall(tp', t')) => let new_ty: Typ.t = switch (TPat.tyvar_of_utpat(tp)) { | Some(tyvar) => Var(tyvar) |> Typ.temp | None => Unknown(Internal) |> Typ.temp }; - let (closures, e) = - recur(Asc(e, Typ.subst(new_ty, tp', t')) |> DHExp.fresh); - Some((closures, TypFun(tp, e, v) |> DHExp.fresh)); + Some( + ClosureWriter.Syntax.( + let* e = recur(Asc(e, Typ.subst(new_ty, tp', t')) |> DHExp.fresh); + ClosureWriter.return(TypFun(tp, e, v) |> DHExp.fresh) + ), + ); | (If(e, e1, e2), t) => - let (closures, e) = recur(Asc(e, t |> Typ.temp) |> DHExp.fresh); - let (closures1, e1) = recur(Asc(e1, t |> Typ.temp) |> DHExp.fresh); - let (closures2, e2) = recur(Asc(e2, t |> Typ.temp) |> DHExp.fresh); - Some((closures @ closures1 @ closures2, If(e, e1, e2) |> DHExp.fresh)); + Some( + ClosureWriter.Syntax.( + let* e = recur(Asc(e, t |> Typ.temp) |> DHExp.fresh); + let* e1 = recur(Asc(e1, t |> Typ.temp) |> DHExp.fresh); + let+ e2 = recur(Asc(e2, t |> Typ.temp) |> DHExp.fresh); + If(e, e1, e2) |> DHExp.fresh + ), + ) | (Match(e, rules), t) => - Some(( - [], - Match( - e, - List.map( - ((p, e)) => (p, Asc(e, t |> Typ.temp) |> DHExp.fresh), - rules, - ), - ) - |> DHExp.fresh, - )) + Some( + ClosureWriter.return( + Match( + e, + List.map( + ((p, e)) => (p, Asc(e, t |> Typ.temp) |> DHExp.fresh), + rules, + ), + ) + |> DHExp.fresh, + ), + ) | ( Ap( Forward, @@ -152,21 +192,25 @@ let rec transition = let entry = ConstructorMap.get_entry(c, m); switch (entry) { | Some(Some(t')) => - let (closures, e) = recur(Asc(payload, t') |> DHExp.fresh); - Some((closures, Ap(Forward, con, e) |> DHExp.fresh)); + Some( + ClosureWriter.Syntax.( + let* e = recur(Asc(payload, t') |> DHExp.fresh); + ClosureWriter.return(Ap(Forward, con, e) |> DHExp.fresh) + ), + ) | Some(None) | None => None }; | (Constructor(_, Some(Some(t))), t') when Typ.is_consistent(Ctx.empty, Typ.unroll(t), t' |> Typ.temp) => - Some(([], e)) - | (Test(_), Prod([])) => Some(([], e)) + Some(ClosureWriter.return(e)) + | (Test(_), Prod([])) => Some(ClosureWriter.return(e)) // These are non-value cases we're handling to process ascriptions as early as possible | (BinOp(bin_op, _, _), _) => switch (Operators.semantics_of_bin_op(bin_op)) { | DefinedPoly(Equals | NotEquals) when Typ.is_consistent(Ctx.empty, t, Atom(Bool) |> Typ.temp) => - Some(([], e)) + Some(ClosureWriter.return(e)) | Defined(_, _, ty_out, _) when Typ.is_consistent( @@ -174,7 +218,7 @@ let rec transition = t, Atom(Atom.cls_of_kind(ty_out)) |> Typ.temp, ) => - Some(([], e)) + Some(ClosureWriter.return(e)) | Undefined(_) | DefinedPoly(_) | Defined(_) => None @@ -188,20 +232,36 @@ let rec transition = t, Atom(Atom.cls_of_kind(ty_out)) |> Typ.temp, ) => - Some(([], e)) + Some(ClosureWriter.return(e)) | Undefined(_) | Defined(_) => None } | (ListConcat(d1, d2), List(_)) => - let (closures, e1) = recur(Asc(d1, t) |> DHExp.fresh); - let (closures2, e2) = recur(Asc(d2, t) |> DHExp.fresh); - Some((closures @ closures2, ListConcat(e1, e2) |> DHExp.fresh)); + Some( + ClosureWriter.Syntax.( + let* e1 = recur(Asc(d1, t) |> DHExp.fresh); + let* e2 = recur(Asc(d2, t) |> DHExp.fresh); + ClosureWriter.return(ListConcat(e1, e2) |> DHExp.fresh) + ), + ) | (Let(p, e1, e2), _) => - Some(([], Let(p, e1, Asc(e2, t) |> DHExp.fresh) |> DHExp.fresh)) + Some( + ClosureWriter.return( + Let(p, e1, Asc(e2, t) |> DHExp.fresh) |> DHExp.fresh, + ), + ) | (Seq(e1, e2), _) => - Some(([], Seq(e1, Asc(e2, t) |> DHExp.fresh) |> DHExp.fresh)) + Some( + ClosureWriter.return( + Seq(e1, Asc(e2, t) |> DHExp.fresh) |> DHExp.fresh, + ), + ) | (Parens(e), _) => - Some(([], Parens(Asc(e, t) |> DHExp.fresh) |> DHExp.fresh)) + Some( + ClosureWriter.return( + Parens(Asc(e, t) |> DHExp.fresh) |> DHExp.fresh, + ), + ) // We _could_ do this, but it would be a bit weird | (Use(_), _) // I'm scaredto do Use because the type-directed literals might make this look weird in the stepper | (BuiltinFun(_), _) @@ -245,8 +305,9 @@ let rec transition = let rec transition_multiple = (d: DHExp.t): (closure_closures, DHExp.t) => { switch (transition(~recursive=true, d)) { - | Some((closures, d'')) => - let (c, d) = transition_multiple(d''); + | Some(writer_result) => + let (closures, d') = writer_result; + let (c, d) = transition_multiple(d'); (closures @ c, d); | None => ([], d) }; diff --git a/src/util/Monads.re b/src/util/Monads.re index 89756c915a..dea4a3f872 100644 --- a/src/util/Monads.re +++ b/src/util/Monads.re @@ -7,7 +7,6 @@ In any case, that's a good reference. */ module type MONAD_BASIC = { - [@deriving sexp] type t('a); let return: 'a => t('a); let bind: (t('a), 'a => t('b)) => t('b); diff --git a/src/util/Monads.rei b/src/util/Monads.rei index 81a6c66969..fa26667425 100644 --- a/src/util/Monads.rei +++ b/src/util/Monads.rei @@ -2,7 +2,6 @@ * https://ocaml.janestreet.com/ocaml-core/v0.13/doc/base/Base__/Monad_intf/index.html */ module type MONAD_BASIC = { - [@deriving sexp] type t('a); let return: 'a => t('a); let bind: (t('a), 'a => t('b)) => t('b); diff --git a/src/util/WriterMonad.re b/src/util/WriterMonad.re index 695e2aed39..cdb92e1eab 100644 --- a/src/util/WriterMonad.re +++ b/src/util/WriterMonad.re @@ -1,5 +1,4 @@ module type WRITER = { - [@deriving sexp] type t; let empty: t; let append: (t, t) => t; @@ -8,7 +7,6 @@ module type WRITER = { module type S = { type writer; - [@deriving sexp] type t('a); let return: 'a => t('a); let bind: (t('a), 'a => t('b)) => t('b); @@ -19,11 +17,9 @@ module type S = { }; module Make = (W: WRITER) => { - [@deriving sexp] type writer = W.t; module T = { - [@deriving sexp] type t('a) = (writer, 'a); let return = x => (W.empty, x); From d6c64e2bc7c2e174e68685ef3426946804c9d2c8 Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Thu, 16 Oct 2025 11:38:58 -0400 Subject: [PATCH 19/65] Only probe unknown expressions when dynamic_feedback is enabled - Introduce `~probe_unknowns` flag in `Elaborator.elaborate`, `elaborate_pattern`, and `uexp_elab` functions to conditionally enable probing for unknown types in expressions and patterns. - Update `CachedStatics.elaborate` to accept and pass the flag, using `settings.dynamic_feedback` as the value. - Modify CLI and test files to explicitly set `~probe_unknowns=false` for consistent behavior. - This change allows fine-grained control over elaboration behavior, improving flexibility for handling unknown types without always triggering probes. --- src/CLI/Run.re | 1 + src/haz3lcore/derived/CachedStatics.re | 8 ++- src/language/statics/Elaborator.re | 57 +++++++++++++++++---- test/Test_Elaboration.re | 5 +- test/evaluator/Test_Evaluator_Prelude.re | 4 +- test/evaluator/Test_Evaluator_Properties.re | 5 +- 6 files changed, 64 insertions(+), 16 deletions(-) diff --git a/src/CLI/Run.re b/src/CLI/Run.re index f05dab7334..312f8760d8 100644 --- a/src/CLI/Run.re +++ b/src/CLI/Run.re @@ -6,6 +6,7 @@ let evaluate = exp => ~env=Builtins.env_init, fst( Elaborator.elaborate( + ~probe_unknowns=false, Statics.mk(CoreSettings.on, Builtins.ctx_init(Some(Int)), exp), exp, ), diff --git a/src/haz3lcore/derived/CachedStatics.re b/src/haz3lcore/derived/CachedStatics.re index 0c3bfd745a..49af6f4d71 100644 --- a/src/haz3lcore/derived/CachedStatics.re +++ b/src/haz3lcore/derived/CachedStatics.re @@ -27,7 +27,11 @@ let empty: t = { }; let elaborate = - Core.Memo.general(~cache_size_bound=1000, Elaborator.uexp_elab); + Core.Memo.general( + ~cache_size_bound=1000, + (probe_unknowns: bool, info_map: Statics.Map.t, term: Exp.t) => + Elaborator.uexp_elab(~probe_unknowns, info_map, term) + ); let dh_err = (error: string): DHExp.t => Var(error) |> DHExp.fresh; @@ -45,7 +49,7 @@ let init_from_term = (~settings, ~is_dynamic_term, ~ctx=?, term): t => { | _ when !settings.dynamics && !settings.elaborate => dh_err("Dynamics & Elaboration disabled") | _ => - switch (elaborate(info_map, term)) { + switch (elaborate(false, info_map, term)) { | DoesNotElaborate => dh_err("Elaboration returns None") | Elaborates(d, _) => d } diff --git a/src/language/statics/Elaborator.re b/src/language/statics/Elaborator.re index ed895c3c63..6fcd2dfa52 100644 --- a/src/language/statics/Elaborator.re +++ b/src/language/statics/Elaborator.re @@ -49,8 +49,8 @@ let elaborated_type = }; let elaborated_pat_type = - (m: Statics.Map.t, upat: Pat.t): (Typ.t, Typ.t, Ctx.t, Pat.t) => { - let (ana_ty, self_ty, ctx, prev_synswitch, term, label_inference) = + (m: Statics.Map.t, upat: Pat.t): (Typ.t, Typ.t, Ctx.t, Pat.t, Self.pat) => { + let (ana_ty, self_ty, ctx, prev_synswitch, term, label_inference, self) = switch (Id.Map.find_opt(Pat.rep_id(upat), m)) { | Some( Info.InfoPat({ @@ -60,6 +60,7 @@ let elaborated_pat_type = prev_synswitch, term: new_term, label_inference, + self, _, }), ) => ( @@ -69,6 +70,7 @@ let elaborated_pat_type = prev_synswitch, new_term, label_inference, + self, ) | _ => raise(MissingTypeInfo) }; @@ -87,15 +89,31 @@ let elaborated_pat_type = | _ => Typ.match_synswitch(syn_ty, ana_ty) } }; - (elab_ty |> Typ.normalize(ctx) |> Typ.all_ids_temp, ana_ty, ctx, term); + ( + elab_ty |> Typ.normalize(ctx) |> Typ.all_ids_temp, + ana_ty, + ctx, + term, + self, + ); }; let rec elaborate_pattern = - (m: Statics.Map.t, upat: Pat.t, in_container: bool): (Pat.t, Typ.t) => { + ( + ~probe_unknowns: bool, + m: Statics.Map.t, + upat: Pat.t, + in_container: bool, + ) + : (Pat.t, Typ.t) => { // Pulling upat back out of the statics map for statics level singleton tuple autolabeling - let (elaborated_type, ana, ctx, upat) = elaborated_pat_type(m, upat); + let (elaborated_type, ana, ctx, upat, self) = elaborated_pat_type(m, upat); let elaborate_pattern = (~in_container=false, m, upat) => - elaborate_pattern(m, upat, in_container); + elaborate_pattern(~probe_unknowns, m, upat, in_container); + + let contains_unknown = + Option.map(t => Typ.count_unknowns(t) > 0, Self.typ_of_pat(self)) + |> Option.value(~default=true); let (term, rewrap) = Pat.unwrap(upat); let dpat = switch (term) { @@ -175,10 +193,27 @@ let rec elaborate_pattern = }; Constructor(c, Some(t)) |> rewrap; }; + + let dpat = + if (probe_unknowns && contains_unknown) { + switch (dpat) { + | {term: Probe(_), _} => dpat + | _ => { + term: Probe(dpat, Probe.empty), + annotation: dpat.annotation, + } // Think about whether it's safe to reuse ids here + }; + } else { + dpat; + }; (dpat, elaborated_type); }; -let rec elaborate = (m: Statics.Map.t, uexp: Exp.t): (DHExp.t, Typ.t) => { +let rec elaborate = + (~probe_unknowns: bool, m: Statics.Map.t, uexp: Exp.t) + : (DHExp.t, Typ.t) => { + let elaborate = elaborate(~probe_unknowns); + let elaborate_pattern = elaborate_pattern(~probe_unknowns); // In the case of singleton labeled tuples we update the syntax in Statics. // We store this syntax with the same ID as the original expression and store it on the Info.exp in the Statics.map // We are then pulling this out and using it in place of the actual expression. @@ -456,7 +491,7 @@ let rec elaborate = (m: Statics.Map.t, uexp: Exp.t): (DHExp.t, Typ.t) => { }; let dhexp = - if (contains_unknown) { + if (probe_unknowns && contains_unknown) { switch (dhexp) { | {term: Probe(_), _} => dhexp | _ => { @@ -478,8 +513,10 @@ let rec elaborate = (m: Statics.Map.t, uexp: Exp.t): (DHExp.t, Typ.t) => { too many new ids */ let fix_typ_ids = Exp.map_term(~f_typ=(cont, e) => e |> cont); -let uexp_elab = (m: Statics.Map.t, uexp: Exp.t): ElaborationResult.t => { - switch (elaborate(m, uexp)) { +let uexp_elab = + (~probe_unknowns: bool, m: Statics.Map.t, uexp: Exp.t) + : ElaborationResult.t => { + switch (elaborate(~probe_unknowns, m, uexp)) { | exception MissingTypeInfo => DoesNotElaborate | (d, ty) => Elaborates(d |> fix_typ_ids, ty) }; diff --git a/test/Test_Elaboration.re b/test/Test_Elaboration.re index 1effaf187d..6007a84fac 100644 --- a/test/Test_Elaboration.re +++ b/test/Test_Elaboration.re @@ -20,6 +20,7 @@ let dhexp_typ = let mk_map = Statics.mk(CoreSettings.on, Builtins.ctx_init(Some(Int))); let dhexp_of_uexp = u => Elaborator.elaborate( + ~probe_unknowns=false, Statics.mk(CoreSettings.on, Builtins.ctx_init(Some(Int)), u), u, ) @@ -385,7 +386,7 @@ module PlainTests = { let uexp = parse_exp(expression); let statics = mk_map(uexp); Alcotest.skip(); - let _ = Elaborator.elaborate(statics, uexp); + let _ = Elaborator.elaborate(~probe_unknowns=false, statics, uexp); (); } }); @@ -682,7 +683,7 @@ in 1|}, exp => { switch (mk_map(exp)) { | statics => - switch (Elaborator.elaborate(statics, exp)) { + switch (Elaborator.elaborate(~probe_unknowns=false, statics, exp)) { | _ => true | exception (Failure(msg) as e) => switch (msg) { diff --git a/test/evaluator/Test_Evaluator_Prelude.re b/test/evaluator/Test_Evaluator_Prelude.re index a4d572f9c3..c7c7a43471 100644 --- a/test/evaluator/Test_Evaluator_Prelude.re +++ b/test/evaluator/Test_Evaluator_Prelude.re @@ -54,6 +54,7 @@ let parse_exp = (s: string) => { }; let elaborate = u => Elaborator.elaborate( + ~probe_unknowns=false, Statics.mk(CoreSettings.on, Builtins.ctx_init(Some(Int)), u), u, ) @@ -120,7 +121,8 @@ let full_small_step_reduction = let full_preservation_test = (uexp: TermBase.exp_t): unit => { let statics = Statics.mk(CoreSettings.on, Builtins.ctx_init(Some(Int)), uexp); - let (elaborated, ty) = Elaborator.elaborate(statics, uexp); + let (elaborated, ty) = + Elaborator.elaborate(~probe_unknowns=false, statics, uexp); let evaluated = Evaluator.evaluate(~env=Builtins.env_init, elaborated) |> fst; diff --git a/test/evaluator/Test_Evaluator_Properties.re b/test/evaluator/Test_Evaluator_Properties.re index 7419946c92..32c442ca3f 100644 --- a/test/evaluator/Test_Evaluator_Properties.re +++ b/test/evaluator/Test_Evaluator_Properties.re @@ -11,6 +11,7 @@ let qcheck_evaluator_does_not_crash_test = exp => { switch ( Elaborator.elaborate( + ~probe_unknowns=false, Statics.mk(CoreSettings.on, Builtins.ctx_init(Some(Int)), exp), exp, ) @@ -55,6 +56,7 @@ let qcheck_stepper_confluence = uexp => { switch ( Elaborator.elaborate( + ~probe_unknowns=false, Statics.mk(CoreSettings.on, Builtins.ctx_init(Some(Int)), uexp), uexp, ) @@ -185,7 +187,8 @@ let qcheck_preservation_test = { let statics = Statics.mk(CoreSettings.on, Builtins.ctx_init(Some(Int)), uexp); - let (elaborated, ty) = Elaborator.elaborate(statics, uexp); + let (elaborated, ty) = + Elaborator.elaborate(~probe_unknowns=false, statics, uexp); let stepped = single_step(elaborated); (stepped, ty); } From e4a00343dc3f2bc681d5e4c2c395663152f95b58 Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Tue, 4 Nov 2025 10:28:46 -0500 Subject: [PATCH 20/65] Fix ascriptions transition hallucination by ai --- .../dynamics/transition/Ascriptions.re | 238 +++++++----------- .../dynamics/transition/Transition.re | 8 +- 2 files changed, 96 insertions(+), 150 deletions(-) diff --git a/src/language/dynamics/transition/Ascriptions.re b/src/language/dynamics/transition/Ascriptions.re index 05e4506e12..81ad537aad 100644 --- a/src/language/dynamics/transition/Ascriptions.re +++ b/src/language/dynamics/transition/Ascriptions.re @@ -8,7 +8,6 @@ e.g. [1, 2] : [Int] -> [1 : Int, 2 : Int] */ type closure_closures = list(Probe.call_stack => Dynamics.Probe.Closure.t); - module ClosureWriter = Util.WriterMonad.Make({ type t = closure_closures; @@ -17,11 +16,12 @@ module ClosureWriter = }); let rec transition = - (~recursive=false, d: DHExp.t): option(ClosureWriter.t(DHExp.t)) => { + (~recursive=false, d: DHExp.t): ClosureWriter.t(option(DHExp.t)) => { + open ClosureWriter.Syntax; let recur = (d: DHExp.t): ClosureWriter.t(DHExp.t) => if (recursive) { - transition(~recursive, d) - |> Option.value(~default=ClosureWriter.return(d)); + let+ d' = transition(~recursive, d); + Option.value(~default=d, d'); } else { ClosureWriter.return(d); }; @@ -29,28 +29,18 @@ let rec transition = | Asc(e, t) => switch (DHExp.term_of(e), Typ.term_of(Typ.unroll(t))) { | (_, Probe(t', p)) => - Some( - ClosureWriter.Syntax.( - let* d = - Asc(e, t') - |> DHExp.fresh - |> transition(~recursive) - |> Option.value( - ~default=ClosureWriter.return(Asc(e, t') |> DHExp.fresh), - ); - let* () = - ClosureWriter.tell([ - Dynamics.Probe.Closure.mk( - Typ.rep_id(t), - e, - Environment.empty, - _, - p, - ), - ]); - ClosureWriter.return(d) - ), - ) + let* d' = recur(Asc(e, t') |> DHExp.fresh); + let+ () = + ClosureWriter.tell([ + Dynamics.Probe.Closure.mk( + Typ.rep_id(t), + e, + Environment.empty, + _, + p, + ), + ]); + Some(d'); | (Asc(e, t'), t) // This is only necessary because sometimes we add two ascriptions and aren't marking it as a non-value when @@ -62,113 +52,84 @@ let rec transition = switch ( Typ.join(Ctx.empty, Typ.unroll(t |> Typ.temp), Typ.unroll(t')) ) { - | Some(t) => Some(recur(Asc(e, t) |> DHExp.fresh)) - | None => None //TODO This is an impossible case since we checked consistency + | Some(t) => + let+ d' = recur(Asc(e, t) |> DHExp.fresh); + Some(d'); + | None => ClosureWriter.return(None) //TODO This is an impossible case since we checked consistency } | (e, Parens(t)) => // This is an impossible case since types should be normalized before coming to transitions transition(~recursive, Asc(e |> DHExp.fresh, t) |> DHExp.fresh) | (Closure(ce, d), t) => - Some( - ClosureWriter.Syntax.( - let* d = - transition(~recursive, Asc(d, t |> Typ.fresh) |> DHExp.fresh) - |> Option.value( - ~default= - ClosureWriter.return( - Asc(d, t |> Typ.fresh) |> DHExp.fresh, - ), - ); - ClosureWriter.return(Closure(ce, d) |> DHExp.fresh) - ), - ) + let+ d' = + transition(~recursive, Asc(d, t |> Typ.fresh) |> DHExp.fresh); + Option.map(d' => Closure(ce, d') |> DHExp.fresh, d'); | (Fun(p, e, t, v), Arrow(t1, t2)) => - Some( - ClosureWriter.return( + ClosureWriter.return( + Some( IdTagged.FreshGrammar.( Exp.(fn(Pat.(asc(p, t1)), asc(e, t2), t, v)) ), ), ) | (TupLabel({term: ExplicitNonlabel, _}, e), _) => - Some(recur(Asc(e, t) |> DHExp.fresh)) + let+ d' = recur(Asc(e, t) |> DHExp.fresh); + Some(d'); | (TupLabel(l, e), TupLabel(_l2, t)) => // TODO Figure out what to do if the labels don't match - Some( - ClosureWriter.Syntax.( - let* e = recur(Asc(e, t) |> DHExp.fresh); - ClosureWriter.return(TupLabel(l, e) |> DHExp.fresh) - ), - ) + let+ e = recur(Asc(e, t) |> DHExp.fresh); + Some(TupLabel(l, e) |> DHExp.fresh); | (Tuple(es), Prod(tys)) when List.length(es) == List.length(tys) => - Some( - ClosureWriter.Syntax.( - let* es = - List.map2((e, ty) => recur(Asc(e, ty) |> DHExp.fresh), es, tys) - |> ClosureWriter.sequence; - ClosureWriter.return(Tuple(es) |> DHExp.fresh) - ), - ) + let+ es = + List.map2((e, ty) => {recur(Asc(e, ty) |> DHExp.fresh)}, es, tys) + |> ClosureWriter.sequence; + Some(Tuple(es) |> DHExp.fresh); | (_, Unknown(_)) => - Some( - ClosureWriter.Syntax.(let* e = recur(e); ClosureWriter.return(e)), - ) + let+ e = recur(e); + Some(e); | (Atom(value) as d, Atom(typ)) => - switch (value, typ) { - | (Int(_), Int) - | (String(_), String) - | (Nat(_), Nat) - | (Float(_), Float) - | (SInt(_), SInt) - | (Bool(_), Bool) => Some(ClosureWriter.return(d |> Exp.fresh)) - | (Int(_), _) - | (String(_), _) - | (Nat(_), _) - | (Float(_), _) - | (SInt(_), _) - | (Bool(_), _) => None - } - | (ListLit(ds), List(ty)) => - Some( - ClosureWriter.Syntax.( - let* ds = - List.map(d => recur(Asc(d, ty) |> DHExp.fresh), ds) - |> ClosureWriter.sequence; - ClosureWriter.return(ListLit(ds) |> DHExp.fresh) - ), + ClosureWriter.return( + switch (value, typ) { + | (Int(_), Int) + | (String(_), String) + | (Nat(_), Nat) + | (Float(_), Float) + | (SInt(_), SInt) + | (Bool(_), Bool) => Some(d |> Exp.fresh) + | (Int(_), _) + | (String(_), _) + | (Nat(_), _) + | (Float(_), _) + | (SInt(_), _) + | (Bool(_), _) => None + }, ) + | (ListLit(ds), List(ty)) => + let+ ds = + List.map(d => recur(Asc(d, ty) |> DHExp.fresh), ds) + |> ClosureWriter.sequence; + + Some(ListLit(ds) |> DHExp.fresh); | (Cons(d1, d2), List(ty)) => - Some( - ClosureWriter.Syntax.( - let* d1 = recur(Asc(d1, ty) |> DHExp.fresh); - let* d2 = recur(Asc(d2, t) |> DHExp.fresh); - ClosureWriter.return(Cons(d1, d2) |> DHExp.fresh) - ), - ) + let* d1 = recur(Asc(d1, ty) |> DHExp.fresh); + let+ d2 = recur(Asc(d2, t) |> DHExp.fresh); + Some(Cons(d1, d2) |> DHExp.fresh); | (TypFun(tp, e, v), Forall(tp', t')) => let new_ty: Typ.t = switch (TPat.tyvar_of_utpat(tp)) { | Some(tyvar) => Var(tyvar) |> Typ.temp | None => Unknown(Internal) |> Typ.temp }; - Some( - ClosureWriter.Syntax.( - let* e = recur(Asc(e, Typ.subst(new_ty, tp', t')) |> DHExp.fresh); - ClosureWriter.return(TypFun(tp, e, v) |> DHExp.fresh) - ), - ); + let+ e = recur(Asc(e, Typ.subst(new_ty, tp', t')) |> DHExp.fresh); + Some(TypFun(tp, e, v) |> DHExp.fresh); | (If(e, e1, e2), t) => - Some( - ClosureWriter.Syntax.( - let* e = recur(Asc(e, t |> Typ.temp) |> DHExp.fresh); - let* e1 = recur(Asc(e1, t |> Typ.temp) |> DHExp.fresh); - let+ e2 = recur(Asc(e2, t |> Typ.temp) |> DHExp.fresh); - If(e, e1, e2) |> DHExp.fresh - ), - ) + let* e = recur(Asc(e, t |> Typ.temp) |> DHExp.fresh); + let* e1 = recur(Asc(e1, t |> Typ.temp) |> DHExp.fresh); + let+ e2 = recur(Asc(e2, t |> Typ.temp) |> DHExp.fresh); + Some(If(e, e1, e2) |> DHExp.fresh); | (Match(e, rules), t) => - Some( - ClosureWriter.return( + ClosureWriter.return( + Some( Match( e, List.map( @@ -192,25 +153,21 @@ let rec transition = let entry = ConstructorMap.get_entry(c, m); switch (entry) { | Some(Some(t')) => - Some( - ClosureWriter.Syntax.( - let* e = recur(Asc(payload, t') |> DHExp.fresh); - ClosureWriter.return(Ap(Forward, con, e) |> DHExp.fresh) - ), - ) + let+ e = recur(Asc(payload, t') |> DHExp.fresh); + Some(Ap(Forward, con, e) |> DHExp.fresh); | Some(None) - | None => None + | None => ClosureWriter.return(None) }; | (Constructor(_, Some(Some(t))), t') when Typ.is_consistent(Ctx.empty, Typ.unroll(t), t' |> Typ.temp) => - Some(ClosureWriter.return(e)) - | (Test(_), Prod([])) => Some(ClosureWriter.return(e)) + ClosureWriter.return(Some(e)) + | (Test(_), Prod([])) => ClosureWriter.return(Some(e)) // These are non-value cases we're handling to process ascriptions as early as possible | (BinOp(bin_op, _, _), _) => switch (Operators.semantics_of_bin_op(bin_op)) { | DefinedPoly(Equals | NotEquals) when Typ.is_consistent(Ctx.empty, t, Atom(Bool) |> Typ.temp) => - Some(ClosureWriter.return(e)) + ClosureWriter.return(Some(e)) | Defined(_, _, ty_out, _) when Typ.is_consistent( @@ -218,10 +175,10 @@ let rec transition = t, Atom(Atom.cls_of_kind(ty_out)) |> Typ.temp, ) => - Some(ClosureWriter.return(e)) + ClosureWriter.return(Some(e)) | Undefined(_) | DefinedPoly(_) - | Defined(_) => None + | Defined(_) => ClosureWriter.return(None) } | (UnOp(un_op, _), _) => switch (Operators.semantics_of_un_op(un_op)) { @@ -232,35 +189,25 @@ let rec transition = t, Atom(Atom.cls_of_kind(ty_out)) |> Typ.temp, ) => - Some(ClosureWriter.return(e)) + ClosureWriter.return(Some(e)) | Undefined(_) - | Defined(_) => None + | Defined(_) => ClosureWriter.return(None) } | (ListConcat(d1, d2), List(_)) => - Some( - ClosureWriter.Syntax.( - let* e1 = recur(Asc(d1, t) |> DHExp.fresh); - let* e2 = recur(Asc(d2, t) |> DHExp.fresh); - ClosureWriter.return(ListConcat(e1, e2) |> DHExp.fresh) - ), - ) + let* e1 = recur(Asc(d1, t) |> DHExp.fresh); + let+ e2 = recur(Asc(d2, t) |> DHExp.fresh); + Some(ListConcat(e1, e2) |> DHExp.fresh); | (Let(p, e1, e2), _) => - Some( - ClosureWriter.return( - Let(p, e1, Asc(e2, t) |> DHExp.fresh) |> DHExp.fresh, - ), + ClosureWriter.return( + Some(Let(p, e1, Asc(e2, t) |> DHExp.fresh) |> DHExp.fresh), ) | (Seq(e1, e2), _) => - Some( - ClosureWriter.return( - Seq(e1, Asc(e2, t) |> DHExp.fresh) |> DHExp.fresh, - ), + ClosureWriter.return( + Some(Seq(e1, Asc(e2, t) |> DHExp.fresh) |> DHExp.fresh), ) | (Parens(e), _) => - Some( - ClosureWriter.return( - Parens(Asc(e, t) |> DHExp.fresh) |> DHExp.fresh, - ), + ClosureWriter.return( + Some(Parens(Asc(e, t) |> DHExp.fresh) |> DHExp.fresh), ) // We _could_ do this, but it would be a bit weird | (Use(_), _) // I'm scaredto do Use because the type-directed literals might make this look weird in the stepper @@ -269,7 +216,7 @@ let rec transition = | (TypAp(_), _) | (Filter(_), _) | (TyAlias(_), _) - | (Asc(_), _) => None + | (Asc(_), _) => ClosureWriter.return(None) // These are non-value cases we don't want to handle | (EmptyHole, _) | (DynamicErrorHole(_), _) @@ -297,18 +244,17 @@ let rec transition = | (Test(_), _) | (HintedTest(_), _) | (Cons(_), _) - | (Constructor(_), _) => None + | (Constructor(_), _) => ClosureWriter.return(None) } - | _ => None + | _ => ClosureWriter.return(None) }; }; let rec transition_multiple = (d: DHExp.t): (closure_closures, DHExp.t) => { switch (transition(~recursive=true, d)) { - | Some(writer_result) => - let (closures, d') = writer_result; - let (c, d) = transition_multiple(d'); + | (closures, Some(d'')) => + let (c, d) = transition_multiple(d''); (closures @ c, d); - | None => ([], d) + | _ => ([], d) }; }; diff --git a/src/language/dynamics/transition/Transition.re b/src/language/dynamics/transition/Transition.re index 143021ea19..151521513b 100644 --- a/src/language/dynamics/transition/Transition.re +++ b/src/language/dynamics/transition/Transition.re @@ -870,7 +870,7 @@ module Transition = (EV: EV_MODE) => { Indet; | Asc(d', t) => switch (Ascriptions.transition(d)) { - | Some((closures, d')) => + | (closures, Some(d')) => let. _ = otherwise(env, d); Step({ expr: d', @@ -878,18 +878,18 @@ module Transition = (EV: EV_MODE) => { kind: Ascription, is_value: false, }); - | None => + | (_, None) => let. _ = otherwise(env, d => Asc(d, t) |> rewrap) and. d' = req_final(req(env), d => Asc(d, t) |> wrap_ctx, d'); switch (Ascriptions.transition(Asc(d', t) |> rewrap)) { - | Some((closures, d)) => + | (closures, Some(d)) => Step({ expr: d, side_effects: [RecordPatProbes(closures)], kind: Ascription, is_value: false, }) - | None => Constructor + | (_, None) => Constructor }; } | Undefined => From 06902fce1d8bc10aa27292cb58cf3138d8bed890 Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Tue, 4 Nov 2025 10:33:30 -0500 Subject: [PATCH 21/65] Fix if statement ascription transition --- src/language/dynamics/transition/Ascriptions.re | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/language/dynamics/transition/Ascriptions.re b/src/language/dynamics/transition/Ascriptions.re index 81ad537aad..3b633ee51b 100644 --- a/src/language/dynamics/transition/Ascriptions.re +++ b/src/language/dynamics/transition/Ascriptions.re @@ -17,6 +17,7 @@ module ClosureWriter = let rec transition = (~recursive=false, d: DHExp.t): ClosureWriter.t(option(DHExp.t)) => { + print_endline("Ascriptions.transition called on " ++ DHExp.show(d)); open ClosureWriter.Syntax; let recur = (d: DHExp.t): ClosureWriter.t(DHExp.t) => if (recursive) { @@ -123,7 +124,7 @@ let rec transition = let+ e = recur(Asc(e, Typ.subst(new_ty, tp', t')) |> DHExp.fresh); Some(TypFun(tp, e, v) |> DHExp.fresh); | (If(e, e1, e2), t) => - let* e = recur(Asc(e, t |> Typ.temp) |> DHExp.fresh); + let* e = recur(e); let* e1 = recur(Asc(e1, t |> Typ.temp) |> DHExp.fresh); let+ e2 = recur(Asc(e2, t |> Typ.temp) |> DHExp.fresh); Some(If(e, e1, e2) |> DHExp.fresh); From e08aa020d3fce81089507476524e77583398676a Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Tue, 4 Nov 2025 10:34:09 -0500 Subject: [PATCH 22/65] Remove debug print statement from ascriptions transition --- src/language/dynamics/transition/Ascriptions.re | 1 - 1 file changed, 1 deletion(-) diff --git a/src/language/dynamics/transition/Ascriptions.re b/src/language/dynamics/transition/Ascriptions.re index 3b633ee51b..7bab654437 100644 --- a/src/language/dynamics/transition/Ascriptions.re +++ b/src/language/dynamics/transition/Ascriptions.re @@ -17,7 +17,6 @@ module ClosureWriter = let rec transition = (~recursive=false, d: DHExp.t): ClosureWriter.t(option(DHExp.t)) => { - print_endline("Ascriptions.transition called on " ++ DHExp.show(d)); open ClosureWriter.Syntax; let recur = (d: DHExp.t): ClosureWriter.t(DHExp.t) => if (recursive) { From a3cf6dcdd28322083783e74be1dafc4830b0a064 Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Mon, 10 Nov 2025 10:33:41 -0500 Subject: [PATCH 23/65] Remove debug print statement for dynamic ID check --- src/haz3lcore/projectors/implementations/TypeProj.re | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/haz3lcore/projectors/implementations/TypeProj.re b/src/haz3lcore/projectors/implementations/TypeProj.re index dbae79d8f1..0c39a2c619 100644 --- a/src/haz3lcore/projectors/implementations/TypeProj.re +++ b/src/haz3lcore/projectors/implementations/TypeProj.re @@ -117,11 +117,6 @@ module M: Projector = { dyn_typ, ); let is_dynamic_id = (id: Id.t): bool => { - if (List.mem(id, ids)) { - print_endline("Was dynamic id: " ++ Id.str3(id)); - } else { - (); - }; List.mem(id, ids); }; (is_dynamic_id, dyn_typ); From 2dbf027b1d72cb9b504677ac80e270d39e113461 Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Mon, 10 Nov 2025 10:37:59 -0500 Subject: [PATCH 24/65] Stop doing ascription transition in labeled tuple entries unboxing --- src/language/dynamics/transition/Unboxing.re | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/language/dynamics/transition/Unboxing.re b/src/language/dynamics/transition/Unboxing.re index 3c83bb9623..25a7004f48 100644 --- a/src/language/dynamics/transition/Unboxing.re +++ b/src/language/dynamics/transition/Unboxing.re @@ -128,11 +128,7 @@ let rec unbox: type a. (unbox_request(a), DHExp.t) => unboxed(a) = | (LabeledTupleEntries, Tuple(ds)) => let unbox_tup_label = (d: Exp.t): option((option(LabeledTuple.label), Exp.t)) => { - switch (snd(Ascriptions.transition_multiple(d)).term) { - // TODO - // TODO - // TODO Think about whether we should transition here - + switch (d.term) { | TupLabel({term: Label(l), _}, e) => Some((Some(l), e)) | _ => Some((None, d)) }; From df49af0828f098161154472f16bab7b9f73d2f40 Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Mon, 10 Nov 2025 10:40:27 -0500 Subject: [PATCH 25/65] Revert Id show to full id - Keep reordering so you can change pp to use str3 --- src/util/Id.re | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/util/Id.re b/src/util/Id.re index ad34d68c14..cf80efa150 100644 --- a/src/util/Id.re +++ b/src/util/Id.re @@ -68,15 +68,24 @@ let mk_str: string => t = s => Uuidm.v5(namespace_uuid, s); let compare: (t, t) => int = Uuidm.compare; let to_string: (~upper: bool=?, t) => string = Uuidm.to_string; let of_string: (~pos: int=?, string) => option(t) = Uuidm.of_string; -let str3 = (id: t) => id |> to_string |> String.sub(_, 0, 3); - -let pp: (Format.formatter, t) => unit = - (f, id) => Format.fprintf(f, "id(\"%s\")", str3(id)); -let show = id => Format.sprintf("id(\"%s\")", str3(id)); +let str3 = (id: t) => id |> to_string |> String.sub(_, 0, 3); let str8 = (id: t) => id |> to_string |> String.sub(_, 0, 8); let cls = (id: t) => "id" ++ str8(id); +let pp: (Format.formatter, t) => unit = + (f, id) => + Format.fprintf( + f, + "Option.get(Haz3lcore.Id.of_string(\"%s\"))", + to_string(id), + ); +let show = id => + Format.sprintf( + "Option.get(Haz3lcore.Id.of_string(\"%s\"))", + to_string(id), + ); + [@deriving (sexp, yojson)] type binding('v) = (t, 'v); From 8e2a70a3f61d54bf8c0025a5acf1d6137b913b79 Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Mon, 10 Nov 2025 10:42:56 -0500 Subject: [PATCH 26/65] Revert IdTagged pp changes --- src/language/term/IdTagged.re | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/language/term/IdTagged.re b/src/language/term/IdTagged.re index 9cd5b9db13..48a3cca959 100644 --- a/src/language/term/IdTagged.re +++ b/src/language/term/IdTagged.re @@ -2,7 +2,10 @@ open Util; module IdTag = { [@deriving (show({with_path: false}), sexp, yojson, eq)] - type t = {ids: list(Id.t)}; + type t = { + [@show.opaque] + ids: list(Id.t), + }; let fresh = (): t => {ids: [Id.mk()]}; let temp = (): t => {ids: [Id.invalid]}; @@ -12,10 +15,10 @@ module IdTag = { type t('a) = Grammar.Annotated.t('a, IdTag.t); // To be used if you want to remove the id from the debug output -let pp: ((Format.formatter, 'a) => unit, Format.formatter, t('a)) => unit = - (fmt_a, formatter, ta) => { - fmt_a(formatter, ta.term); - }; +// let pp: ((Format.formatter, 'a) => unit, Format.formatter, t('a)) => unit = +// (fmt_a, formatter, ta) => { +// fmt_a(formatter, ta.term); +// }; let fresh = (term: 'a): Grammar.Annotated.t('a, IdTag.t) => { { term, From 5b1c7d39af9eda89e431e261a86e96e9816a8f5b Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Mon, 10 Nov 2025 10:46:54 -0500 Subject: [PATCH 27/65] add Probe to Typ.cls - Introduce new `Probe` variant to the `cls` type in Typ.re - Update `cls_of_term` to correctly map Probe terms to Probe class - Add display string "Probed type" for Probe in `show_cls` - Extend test sample generation in Test_Grammar.re to include Probe case --- src/language/term/Typ.re | 8 +++++--- test/Test_Grammar.re | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/language/term/Typ.re b/src/language/term/Typ.re index a4d24e6c67..3a6042527a 100644 --- a/src/language/term/Typ.re +++ b/src/language/term/Typ.re @@ -23,7 +23,8 @@ type cls = | Rec | Forall | ProdProjection - | ProdExtension; + | ProdExtension + | Probe; include TermBase.Typ; @@ -99,7 +100,7 @@ let cls_of_term: Grammar.typ_term('a) => cls = | Forall(_) => Forall | ProdProjection(_) => ProdProjection | ProdExtension(_) => ProdExtension - | Probe(_, _) => EmptyHole; + | Probe(_, _) => Probe; let show_cls: cls => string = fun @@ -123,7 +124,8 @@ let show_cls: cls => string = | Rec => "Recursive type" | Forall => "Forall type" | ProdProjection => "Tuple projection" - | ProdExtension => "Tuple extension"; + | ProdExtension => "Tuple extension" + | Probe => "Probed type"; let rec is_arrow = (typ: t) => { switch (typ.term) { diff --git a/test/Test_Grammar.re b/test/Test_Grammar.re index a02cdfc9e4..f5e11494de 100644 --- a/test/Test_Grammar.re +++ b/test/Test_Grammar.re @@ -160,6 +160,7 @@ let sample_type = (cls_typ: Typ.cls): Grammar.UnitGrammar.typ => { | ProdExtension => prod_extension(unknown(Hole(EmptyHole)), unknown(Hole(EmptyHole))) | Constructor => assert(false) // Excluded because there is no Typ constructor + | Probe => probe(unknown(Hole(EmptyHole)), Probe.empty) } ) ); From 5ff56a7c55ec479244625eb7097b6383e9b1f6a9 Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Mon, 10 Nov 2025 10:47:09 -0500 Subject: [PATCH 28/65] Remove comments --- src/language/statics/Elaborator.re | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/language/statics/Elaborator.re b/src/language/statics/Elaborator.re index 6fcd2dfa52..48303a41c5 100644 --- a/src/language/statics/Elaborator.re +++ b/src/language/statics/Elaborator.re @@ -201,7 +201,7 @@ let rec elaborate_pattern = | _ => { term: Probe(dpat, Probe.empty), annotation: dpat.annotation, - } // Think about whether it's safe to reuse ids here + } }; } else { dpat; @@ -497,7 +497,7 @@ let rec elaborate = | _ => { term: Probe(dhexp, Probe.empty), annotation: dhexp.annotation, - } // Think about whether it's safe to reuse ids here + } }; } else { dhexp; From 7b34b49831724b9437caf824011403e3d2cf17ed Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Mon, 10 Nov 2025 10:52:23 -0500 Subject: [PATCH 29/65] Add documentation for probe_unknowns parameter in Elaborator --- src/language/statics/Elaborator.re | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/language/statics/Elaborator.re b/src/language/statics/Elaborator.re index 48303a41c5..b8a357f55a 100644 --- a/src/language/statics/Elaborator.re +++ b/src/language/statics/Elaborator.re @@ -2,6 +2,13 @@ A nice property would be that elaboration is idempotent... */ +/* + * probe_unknowns parameter: Added for future use in PRs like https://github.com/hazelgrove/hazel/pull/1988 + * to help with adding dynamic feedback for static marks. When enabled, it will add probes + * around any expression/pattern that is partially unknown. + * Do not remove this parameter even though it's always false currently. + */ + open Util; exception MissingTypeInfo; From c76a16496b7156ca2d7fb9860bce6f06524be10c Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Mon, 10 Nov 2025 10:58:08 -0500 Subject: [PATCH 30/65] Disable type projector on types for now --- src/haz3lcore/projectors/implementations/TypeProj.re | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/haz3lcore/projectors/implementations/TypeProj.re b/src/haz3lcore/projectors/implementations/TypeProj.re index 0c39a2c619..16c0134273 100644 --- a/src/haz3lcore/projectors/implementations/TypeProj.re +++ b/src/haz3lcore/projectors/implementations/TypeProj.re @@ -8,7 +8,6 @@ let expected_ty = (info: option(Info.t)): option(Typ.t) => switch (info) { | Some(InfoExp({ana, _})) | Some(InfoPat({ana, _})) => Some(ana) - | Some(InfoTyp({term, _})) => Some(term) // TODO Expected doesn't make sense for types | _ => None }; @@ -16,7 +15,6 @@ let self_ty = (info: option(Info.t)): option(Typ.t) => switch (info) { | Some(InfoExp({self, _})) => Self.typ_of_exp(self) | Some(InfoPat({self, _})) => Self.typ_of_pat(self) - | Some(InfoTyp({term, _})) => Some(term) | _ => None }; @@ -69,7 +67,6 @@ module M: Projector = { let init = (any: Any.t): option(model) => { switch (any) { | Exp(_) - | Typ(_) // TODO This seems to behave oddly on grout | Pat(_) => Some(Expected) | Any () => Some(Expected) /* Grout don't have sorts rn */ | _ => None From 995b9af1f4fd6fef66b4d1189cec676ca1a56758 Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Mon, 10 Nov 2025 11:04:21 -0500 Subject: [PATCH 31/65] Revert codeeditable and cursorinspector changes --- src/web/app/editors/code/CodeEditable.re | 1 - src/web/app/inspector/CursorInspector.re | 35 ++---------------------- 2 files changed, 3 insertions(+), 33 deletions(-) diff --git a/src/web/app/editors/code/CodeEditable.re b/src/web/app/editors/code/CodeEditable.re index c719c60d04..2ee6d7ee8b 100644 --- a/src/web/app/editors/code/CodeEditable.re +++ b/src/web/app/editors/code/CodeEditable.re @@ -213,7 +213,6 @@ module View = { ~globals, ) : []; - let projectors = ProjectorView.all( x => inject(Perform(x)), diff --git a/src/web/app/inspector/CursorInspector.re b/src/web/app/inspector/CursorInspector.re index 95c71ac3f5..90c5acfad7 100644 --- a/src/web/app/inspector/CursorInspector.re +++ b/src/web/app/inspector/CursorInspector.re @@ -758,43 +758,14 @@ let view_of_info = (~globals, ci): list(Node.t) => { }; }; -let dynamic_type = closures => { - let statics = Statics.mk(CoreSettings.on, Builtins.ctx_init(Some(Int))); - let type_of = (c: Dynamics.Probe.Closure.t) => { - IdTagged.rep_id(c.value) - |> Id.Map.find_opt(_, statics(c.value)) - |> Option.bind( - _, - fun - | InfoExp(e) => { - Some(e.ty); - } - | _ => None, - ); - }; - let types = List.map(type_of, closures) |> Util.OptUtil.sequence; - - Option.map(Typ.consistent_join(Ctx.empty), types); -}; - -let inspector_view = - (~globals, ~dynamics: option(list(Dynamics.Probe.Closure.t)), ci) - : Node.t => { - let dyn = Option.bind(dynamics, dynamic_type); +let inspector_view = (~globals, ci): Node.t => div( ~attrs=[ Attr.id("cursor-inspector"), clss([Info.is_error(ci) ? errc : okc]), ], - view_of_info(~globals, ci) - @ ( - switch (dyn) { - | None => [] - | Some(ty) => [text("Dynamic Type:"), view_type(~globals, ty)] - } - ), + view_of_info(~globals, ci), ); -}; let view = ( @@ -815,7 +786,7 @@ let view = | None => err_view("Whitespace or Comment") | Some(ci) => bar_view([ - inspector_view(~globals, ~dynamics=cursor.dynamics, ci), + inspector_view(~globals, ci), ProjectorPanel.view( ~inject= a => From 4ec5c6d5b52f21312d6a11da35a5cc14f4774433 Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Mon, 10 Nov 2025 11:23:09 -0500 Subject: [PATCH 32/65] Fix syn display for typ proj - Add type alias 'o' for option to improve readability - Remove unused _display_ty function - Refactor display_mode with proper scoping - Enhance Expected mode handling for syn types to use self_ty This refactoring cleans up the type projection implementation, eliminates dead code, and improves type inference display accuracy. --- .../projectors/implementations/TypeProj.re | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/haz3lcore/projectors/implementations/TypeProj.re b/src/haz3lcore/projectors/implementations/TypeProj.re index 16c0134273..25f04f3778 100644 --- a/src/haz3lcore/projectors/implementations/TypeProj.re +++ b/src/haz3lcore/projectors/implementations/TypeProj.re @@ -17,7 +17,8 @@ let self_ty = (info: option(Info.t)): option(Typ.t) => | Some(InfoPat({self, _})) => Self.typ_of_pat(self) | _ => None }; - +[@deriving show({with_path: false})] +type o('a) = option('a); let totalize_ty = (expected_ty: option(Typ.t)): Typ.t => switch (expected_ty) { | Some(expected_ty) => expected_ty @@ -76,16 +77,7 @@ module M: Projector = { let dynamics = true; let focusable = Focusable.non; - let _display_ty = (model, statics, dyn_typ): option(Typ.t) => - switch (model) { - | Dynamic => Some(dyn_typ) - | _ when expected_ty(statics) |> totalize_ty |> Typ.is_syn => - statics |> self_ty - | Self => statics |> self_ty - | Expected => statics |> expected_ty - }; - - let display_mode = (model: model, statics: option(Language.Info.t)): string => + let display_mode = (model: model, statics: option(Language.Info.t)): string => { switch (model) { | Dynamic => "↦" | _ when self_ty(statics) == expected_ty(statics) => "⇔" @@ -93,7 +85,7 @@ module M: Projector = { | Self => "⇒" | Expected => "⇐" }; - + }; let mode_view = (model, info) => div( ~attrs=[Attr.classes(["mode"])], @@ -117,6 +109,10 @@ module M: Projector = { List.mem(id, ids); }; (is_dynamic_id, dyn_typ); + | Expected when expected_ty(info.statics) |> totalize_ty |> Typ.is_syn => ( + (_ => false), + self_ty(info.statics) |> totalize_ty, + ) | Expected => ((_ => false), expected_ty(info.statics) |> totalize_ty) | Self => ((_ => false), self_ty(info.statics) |> totalize_ty) }; From 30f5593b1892eb3858327510957cd78fa5315338 Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Mon, 10 Nov 2025 11:32:21 -0500 Subject: [PATCH 33/65] feat(typeproj): enhance toggle display logic for expected types Add check for non-synthetic expected type in update function to prevent toggling to 'Expected' when unavailable, improving UI by cycling only relevant display modes. Removed unused type alias 'o' for cleanup. --- .../projectors/implementations/TypeProj.re | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/haz3lcore/projectors/implementations/TypeProj.re b/src/haz3lcore/projectors/implementations/TypeProj.re index 25f04f3778..7d3244aee8 100644 --- a/src/haz3lcore/projectors/implementations/TypeProj.re +++ b/src/haz3lcore/projectors/implementations/TypeProj.re @@ -17,8 +17,7 @@ let self_ty = (info: option(Info.t)): option(Typ.t) => | Some(InfoPat({self, _})) => Self.typ_of_pat(self) | _ => None }; -[@deriving show({with_path: false})] -type o('a) = option('a); + let totalize_ty = (expected_ty: option(Typ.t)): Typ.t => switch (expected_ty) { | Some(expected_ty) => expected_ty @@ -125,12 +124,18 @@ module M: Projector = { ); }; - let update = (model, _, a: action) => + let update = (model, info, a: action) => { + let has_expected = + switch (expected_ty(info.statics)) { + | Some(ty) => !Typ.is_syn(ty) + | None => false + }; switch (a, model) { - | (ToggleDisplay, Expected) => Self + | (ToggleDisplay, Expected) => if (has_expected) {Self} else {Dynamic} | (ToggleDisplay, Self) => Dynamic - | (ToggleDisplay, Dynamic) => Expected + | (ToggleDisplay, Dynamic) => if (has_expected) {Expected} else {Self} }; + }; let syntax_str = (info: info) => { let max_len = 30; From 698e981c324c2fc5121bf3b26f0fadef9d50a520 Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Wed, 19 Nov 2025 10:32:40 -0500 Subject: [PATCH 34/65] Fix typ diff styling - Added PadIds to add additional ids to types before diff and exp to segment - Fixed css of dynamic code --- src/CLI/Print.re | 10 +- src/haz3lcore/pretty/ExpToSegment.re | 11 +- src/haz3lcore/pretty/PadIds.re | 41 ++++++ .../projectors/implementations/TypeProj.re | 12 +- src/haz3lcore/zipper/action/Introduce.re | 9 +- src/language/term/IdTagged.re | 1 + src/language/term/Typ.re | 51 ++++--- src/web/app/editors/code/Code.re | 25 ++-- src/web/app/inspector/CursorInspector.re | 1 + src/web/view/ContextInspector.re | 1 + src/web/view/Kind.re | 1 + src/web/www/style/projectors/proj-type.css | 2 +- test/Test_ExpToSegment.re | 1 + test/Test_Menhir.re | 1 + test/Test_PadIds.re | 32 +++++ test/Test_Typ.re | 129 +++++++++++++++++- test/haz3ltest.re | 1 + 17 files changed, 270 insertions(+), 59 deletions(-) create mode 100644 src/haz3lcore/pretty/PadIds.re create mode 100644 test/Test_PadIds.re diff --git a/src/CLI/Print.re b/src/CLI/Print.re index 8b8146377c..dc269295f7 100644 --- a/src/CLI/Print.re +++ b/src/CLI/Print.re @@ -1,13 +1,7 @@ open Haz3lcore; -let exp_to_segment_settings: ExpToSegment.Settings.t = { - inline: false, - fold_case_clauses: false, - fold_fn_bodies: false, - hide_fixpoints: false, - show_filters: true, - show_unknown_as_hole: true, -}; +let exp_to_segment_settings: ExpToSegment.Settings.t = + ExpToSegment.Settings.editable(~inline=false); let segmentize = ExpToSegment.exp_to_segment(~settings=exp_to_segment_settings, _); diff --git a/src/haz3lcore/pretty/ExpToSegment.re b/src/haz3lcore/pretty/ExpToSegment.re index 0188160f14..f3c5c144e2 100644 --- a/src/haz3lcore/pretty/ExpToSegment.re +++ b/src/haz3lcore/pretty/ExpToSegment.re @@ -13,6 +13,7 @@ module Settings = { hide_fixpoints: bool, show_filters: bool, show_unknown_as_hole: bool, + raise_if_padding: bool, }; let of_core = (~inline, settings: CoreSettings.t) => { @@ -22,6 +23,7 @@ module Settings = { hide_fixpoints: !settings.evaluation.show_fixpoints, show_filters: settings.evaluation.show_stepper_filters, show_unknown_as_hole: true, + raise_if_padding: false, }; let editable = (~inline) => { @@ -32,6 +34,7 @@ module Settings = { hide_fixpoints: false, show_filters: true, show_unknown_as_hole: true, + raise_if_padding: false, }; }; }; @@ -742,9 +745,12 @@ let mk_form = (form_name: Form.compound_form, id, children): Piece.t => { /* HACK[Matt]: Sometimes terms that should have multiple ids won't because evaluation only ever gives them one */ -let pad_ids = (n: int, ids: list(Id.t)): list(Id.t) => { +let pad_ids = (~settings: Settings.t, n: int, ids: list(Id.t)): list(Id.t) => { let len = List.length(ids); if (len < n) { + if (settings.raise_if_padding) { + raise(Failure("Padding required but not enough ids provided.")); + }; ids @ List.init(n - len, _ => Id.mk()); } else { ListUtil.split_n(n, ids) |> fst; @@ -801,6 +807,7 @@ let fold_fun_if = (condition, f_name: string, pieces) => that the expression has no Closures or DynamicErrorHoles */ let rec exp_to_pretty = (~settings: Settings.t, exp: Exp.t): pretty => { + let pad_ids = pad_ids(~settings); let go = (~inline=settings.inline) => exp_to_pretty( ~settings={ @@ -1235,6 +1242,7 @@ let rec exp_to_pretty = (~settings: Settings.t, exp: Exp.t): pretty => { } and pat_to_pretty = (~settings: Settings.t, pat: Pat.t): pretty => { let go = pat_to_pretty(~settings: Settings.t); + let pad_ids = pad_ids(~settings); switch (pat |> Pat.term_of) { | Invalid(t) => text_to_pretty(pat |> Pat.rep_id, Sort.Pat, t) | EmptyHole => @@ -1352,6 +1360,7 @@ and pat_to_pretty = (~settings: Settings.t, pat: Pat.t): pretty => { } and typ_to_pretty = (~settings: Settings.t, typ: Typ.t): pretty => { let go = typ_to_pretty(~settings: Settings.t); + let pad_ids = pad_ids(~settings); let go_constructor: ConstructorMap.variant(Typ.t) => pretty = fun | Variant(c, ids, None) => { diff --git a/src/haz3lcore/pretty/PadIds.re b/src/haz3lcore/pretty/PadIds.re new file mode 100644 index 0000000000..4fe05d2959 --- /dev/null +++ b/src/haz3lcore/pretty/PadIds.re @@ -0,0 +1,41 @@ +open Language; + +let necessary_ids: Typ.t => int = + ty => { + switch (ty.term) { + | Parens(_) => 1 + | Prod([]) => 1 + | Prod(tys) => List.length(tys) - 1 + | Sum(tys) => List.length(tys) + 1 + | _ => 1 + }; + }; + +/* + * This function pads the ids of a type to ensure that there are enough ids to be used so that ExpToSegment does not need to create new ids. + * This is important for maintaining the correspondence between type ids and the resulting segment ids. Specifically if you're using the the ids from the type to + * affect styling in the segment, you want to ensure that the ids in the type are the ones that get used. + * + * @param ty - The type to pad ids for. + * @return A new type with padded ids. + */ + +let pad_typ_ids = (ty: Typ.t): Typ.t => { + Typ.map_term( + ~f_typ= + (cont, ty) => { + let current_ids = ty.annotation.ids; + let needed_ids = necessary_ids(ty); + let ids = + current_ids + @ List.init(needed_ids - List.length(current_ids), _ => Id.mk()); + cont({ + ...ty, + annotation: { + ids: ids, + }, + }); + }, + ty, + ); +}; diff --git a/src/haz3lcore/projectors/implementations/TypeProj.re b/src/haz3lcore/projectors/implementations/TypeProj.re index 7d3244aee8..a4e4cf3945 100644 --- a/src/haz3lcore/projectors/implementations/TypeProj.re +++ b/src/haz3lcore/projectors/implementations/TypeProj.re @@ -96,14 +96,10 @@ module M: Projector = { switch (model) { | Dynamic => let self_ty = self_ty(info.statics); - let dyn_typ = - get_dynamic_typ(info) - |> Grammar.map_typ_annotation(_ => IdTagged.IdTag.fresh(), _); - let ids: list(Id.t) = - Typ.diff( - Option.value(~default=Typ.fresh(Unknown(Internal)), self_ty), - dyn_typ, - ); + let dyn_typ = get_dynamic_typ(info) |> PadIds.pad_typ_ids; + let sty = + Option.value(~default=Typ.fresh(Unknown(Internal)), self_ty); + let ids: list(Id.t) = Typ.diff(sty, dyn_typ); let is_dynamic_id = (id: Id.t): bool => { List.mem(id, ids); }; diff --git a/src/haz3lcore/zipper/action/Introduce.re b/src/haz3lcore/zipper/action/Introduce.re index 623b10726a..07b38b7ba0 100644 --- a/src/haz3lcore/zipper/action/Introduce.re +++ b/src/haz3lcore/zipper/action/Introduce.re @@ -219,14 +219,7 @@ module Make = let seg = I.to_segment( - ~settings={ - inline: true, - fold_case_clauses: false, - fold_fn_bodies: false, - hide_fixpoints: false, - show_filters: true, - show_unknown_as_hole: true, - }, + ~settings=ExpToSegment.Settings.editable(~inline=true), term, already_parenthesized(z), ); diff --git a/src/language/term/IdTagged.re b/src/language/term/IdTagged.re index 48a3cca959..1e51f19d4d 100644 --- a/src/language/term/IdTagged.re +++ b/src/language/term/IdTagged.re @@ -9,6 +9,7 @@ module IdTag = { let fresh = (): t => {ids: [Id.mk()]}; let temp = (): t => {ids: [Id.invalid]}; + let rep_id = ({ids, _}: t) => List.hd(ids); }; [@deriving (show({with_path: false}), sexp, yojson)] diff --git a/src/language/term/Typ.re b/src/language/term/Typ.re index 3a6042527a..4148627dba 100644 --- a/src/language/term/Typ.re +++ b/src/language/term/Typ.re @@ -1126,37 +1126,54 @@ let to_product = (tys: list(t)): t => TempGrammar.Typ.(prod(tys)); /* Computes the list of ids in t' that are not in t. Assumes initial ids are distinct. Only returns the id of the root difference. */ let rec diff = (ty: t, ty': t): list(Id.t) => { + let get_ids = () => { + let ids = ref([]); + let _ = + Grammar.map_typ_annotation( + (t: IdTagged.IdTag.t) => { + ids := t.ids @ ids^; + t; + }, + ty': t, + ); + ids^; + }; switch (term_of(ty), term_of(ty')) { | (Probe(t1, _), _) => diff(t1, ty') | (_, Probe(t2, _)) => diff(ty, t2) | (Parens(t1), Parens(t2)) => diff(t1, t2) | (Unknown(_), Unknown(_)) => [] - | (Unknown(_), _) => [ty' |> rep_id] + | (Unknown(_), _) => get_ids() | (Atom(c1), Atom(c2)) when c1 == c2 => [] - | (Atom(_), _) => [ty' |> rep_id] + | (Atom(_), _) => get_ids() | (Label(l1), Label(l2)) when l1 == l2 => [] - | (Label(_), _) => [ty' |> rep_id] + | (Label(_), _) => get_ids() + | (ExplicitNonlabel, ExplicitNonlabel) => [] + | (ExplicitNonlabel, _) => get_ids() | (Var(v1), Var(v2)) when v1 == v2 => [] - | (Var(_), _) => [ty' |> rep_id] + | (Var(_), _) => get_ids() | (Rec(_tp1, t1), Rec(_tp2, t2)) => diff(t1, t2) // TODO Check tpat - | (Rec(_), _) => [ty' |> rep_id] + | (Rec(_), _) => get_ids() | (Forall(_tp1, t1), Forall(_tp2, t2)) => diff(t1, t2) // TODO Check tpat - | (Forall(_), _) => [ty' |> rep_id] + | (Forall(_), _) => get_ids() | (Arrow(t1a, t1b), Arrow(t2a, t2b)) => diff(t1a, t2a) @ diff(t1b, t2b) - | (Arrow(_), _) => [ty' |> rep_id] + | (Arrow(_), _) => get_ids() | (Prod(tys1), Prod(tys2)) when List.length(tys1) == List.length(tys2) => List.map2(diff, tys1, tys2) |> List.concat - | (Prod(_), _) => [ty' |> rep_id] + | (Prod(_), _) => get_ids() | (TupLabel(l1, t1), TupLabel(l2, t2)) => diff(l1, l2) @ diff(t1, t2) - | (TupLabel(_, _), _) => [ty' |> rep_id] + | (TupLabel(_, _), _) => get_ids() | (List(t1), List(t2)) => diff(t1, t2) - | (List(_), _) => [ty' |> rep_id] - | _ => - // TODO - raise( - Failure( - "diff: unsupported types" ++ show(ty) ++ " and " ++ show(ty'), - ), - ) + | (List(_), _) => get_ids() + | (ProdProjection(t1, t2), ProdProjection(t1', t2')) => + diff(t1, t1') @ diff(t2, t2') + | (ProdProjection(_, _), _) => get_ids() + | (ProdExtension(t1, t2), ProdExtension(t1', t2')) => + diff(t1, t1') @ diff(t2, t2') + | (ProdExtension(_, _), _) => get_ids() + | (Sum(sm1), Sum(sm2)) when ConstructorMap.equal(fast_equal, sm1, sm2) => + [] + | (Sum(_), _) => get_ids() + | _ => raise(Failure("diff: incompatible types")) }; }; diff --git a/src/web/app/editors/code/Code.re b/src/web/app/editors/code/Code.re index a03b56cac0..7e7ae5ebd0 100644 --- a/src/web/app/editors/code/Code.re +++ b/src/web/app/editors/code/Code.re @@ -123,21 +123,16 @@ let view = let rec of_segment = (seg: Segment.t): list(Node.t) => List.concat_map( fun - | Piece.Tile(t) => - if (Tile.id(t) |> is_dynamic) { - [ - span( - ~attrs=[Attr.classes(["dynamic"])], - Aba.mk(t.shards, t.children) - |> Aba.join(i => [of_delim(t, i)], of_segment) - |> List.concat, - ), - ]; - } else { - Aba.mk(t.shards, t.children) - |> Aba.join(i => [of_delim(t, i)], of_segment) - |> List.concat; - } + | Piece.Tile(t) => [ + span( + ~attrs=[ + Attr.classes(Tile.id(t) |> is_dynamic ? ["dynamic"] : []), + ], + Aba.mk(t.shards, t.children) + |> Aba.join(i => [of_delim(t, i)], of_segment) + |> List.concat, + ), + ] | Grout(g) => [of_grout(g)] | Secondary(s) => [of_secondary(s)] | Projector(pr) => [of_projector(pr)], diff --git a/src/web/app/inspector/CursorInspector.re b/src/web/app/inspector/CursorInspector.re index 90c5acfad7..4feda83823 100644 --- a/src/web/app/inspector/CursorInspector.re +++ b/src/web/app/inspector/CursorInspector.re @@ -83,6 +83,7 @@ let code_view_settings: Haz3lcore.ExpToSegment.Settings.t = { hide_fixpoints: false, show_filters: false, show_unknown_as_hole: true, + raise_if_padding: false, }; let view_any = (~globals, any: Any.t) => diff --git a/src/web/view/ContextInspector.re b/src/web/view/ContextInspector.re index 91700dde42..998ed53992 100644 --- a/src/web/view/ContextInspector.re +++ b/src/web/view/ContextInspector.re @@ -18,6 +18,7 @@ let context_entry_view = (~globals, entry: Language.Ctx.entry): Node.t => { hide_fixpoints: false, show_filters: false, show_unknown_as_hole: true, + raise_if_padding: false, }, ); let div_name = div(~attrs=[clss(["name"])]); diff --git a/src/web/view/Kind.re b/src/web/view/Kind.re index 61c9d8ead6..dd0d33a759 100644 --- a/src/web/view/Kind.re +++ b/src/web/view/Kind.re @@ -17,6 +17,7 @@ let view = (~globals, kind: Language.Ctx.kind): Node.t => hide_fixpoints: false, show_filters: false, show_unknown_as_hole: true, + raise_if_padding: false, }, ty, ), diff --git a/src/web/www/style/projectors/proj-type.css b/src/web/www/style/projectors/proj-type.css index 102a21af68..4fa342b4fb 100644 --- a/src/web/www/style/projectors/proj-type.css +++ b/src/web/www/style/projectors/proj-type.css @@ -95,6 +95,6 @@ color: white; } -.dynamic > .token.Typ.mono { +.dynamic > .token.Typ { color: green; } \ No newline at end of file diff --git a/test/Test_ExpToSegment.re b/test/Test_ExpToSegment.re index fb364fc2de..e43d372bef 100644 --- a/test/Test_ExpToSegment.re +++ b/test/Test_ExpToSegment.re @@ -11,6 +11,7 @@ let exp_to_segment_settings: ExpToSegment.Settings.t = { hide_fixpoints: false, show_filters: true, show_unknown_as_hole: true, + raise_if_padding: false, }; let exp_to_segment = diff --git a/test/Test_Menhir.re b/test/Test_Menhir.re index 71ce8f035a..44fedac97e 100644 --- a/test/Test_Menhir.re +++ b/test/Test_Menhir.re @@ -170,6 +170,7 @@ let qcheck_menhir_serialized_equivalent_test = hide_fixpoints: false, show_filters: true, show_unknown_as_hole: true, + raise_if_padding: false, }, core_exp, ); diff --git a/test/Test_PadIds.re b/test/Test_PadIds.re new file mode 100644 index 0000000000..cfaed441bd --- /dev/null +++ b/test/Test_PadIds.re @@ -0,0 +1,32 @@ +open Haz3lcore; + +let qcheck_pads_typ_for_exp_to_segment = + QCheck.Test.make( + ~name="No ids are needed to be padded during ExpToSegment", + ~count=10000, + QCheck_Util.arb_typ(~minimal_idents=false, 30), + typ => { + let padded = PadIds.pad_typ_ids(typ); + let _ = + ExpToSegment.typ_to_segment( + ~settings={ + inline: false, + fold_case_clauses: false, + fold_fn_bodies: false, + hide_fixpoints: false, + show_filters: true, + show_unknown_as_hole: true, + raise_if_padding: true // Will raise an exception if padding + }, + padded, + ); + true; + }, + ); + +let tests = [ + ( + "PadIds", + [QCheck_alcotest.to_alcotest(qcheck_pads_typ_for_exp_to_segment)], + ), +]; diff --git a/test/Test_Typ.re b/test/Test_Typ.re index ac2a500fc9..cc5afc60c8 100644 --- a/test/Test_Typ.re +++ b/test/Test_Typ.re @@ -300,4 +300,131 @@ let fast_equal_tests = ( ], ); -let tests = [join_tests, fast_equal_tests]; +let diff_tests = ( + "Typ.diff", + [ + QCheck_alcotest.to_alcotest( + QCheck.Test.make( + ~name="diff identity", + ~count=1000, + QCheck_Util.arb_typ(~minimal_idents=true, 7), + typ => + Typ.diff(typ, typ) == [] + ), + ), + test_case( + "diff root different atom types", + `Quick, + () => { + let int_typ = Typ.fresh(Atom(Atom.Int)); + let float_typ = Typ.fresh(Atom(Atom.Float)); + let expected = [Typ.rep_id(float_typ)]; + check( + testable( + Fmt.using( + ids => String.concat(", ", List.map(Id.show, ids)), + Fmt.string, + ), + (==), + ), + "diff on different atom types", + expected, + Typ.diff(int_typ, float_typ), + ); + }, + ), + test_case( + "diff arrow different codomain", + `Quick, + () => { + let int_typ = Typ.fresh(Atom(Atom.Int)); + let float_typ = Typ.fresh(Atom(Atom.Float)); + let arrow1 = Typ.fresh(Arrow(int_typ, int_typ)); + let arrow2 = Typ.fresh(Arrow(int_typ, float_typ)); + let expected = [Typ.rep_id(float_typ)]; + check( + testable( + Fmt.using( + ids => String.concat(", ", List.map(Id.show, ids)), + Fmt.string, + ), + (==), + ), + "diff on arrows with different codomains", + expected, + Typ.diff(arrow1, arrow2), + ); + }, + ), + test_case( + "diff list different element", + `Quick, + () => { + let int_typ = Typ.fresh(Atom(Atom.Int)); + let float_typ = Typ.fresh(Atom(Atom.Float)); + let list1 = Typ.fresh(List(int_typ)); + let list2 = Typ.fresh(List(float_typ)); + let expected = [Typ.rep_id(float_typ)]; + check( + testable( + Fmt.using( + ids => String.concat(", ", List.map(Id.show, ids)), + Fmt.string, + ), + (==), + ), + "diff on lists with different elements", + expected, + Typ.diff(list1, list2), + ); + }, + ), + test_case( + "diff arrow different domain", + `Quick, + () => { + let int_typ = Typ.fresh(Atom(Atom.Int)); + let float_typ = Typ.fresh(Atom(Atom.Float)); + let string_typ = Typ.fresh(Atom(Atom.String)); + let arrow1 = Typ.fresh(Arrow(int_typ, string_typ)); + let arrow2 = Typ.fresh(Arrow(float_typ, string_typ)); + let expected = [Typ.rep_id(float_typ)]; + check( + testable( + Fmt.using( + ids => String.concat(", ", List.map(Id.show, ids)), + Fmt.string, + ), + (==), + ), + "diff on arrows with different domains", + expected, + Typ.diff(arrow1, arrow2), + ); + }, + ), + test_case( + "diff var different names", + `Quick, + () => { + let var1 = Typ.fresh(Var("x")); + let var2 = Typ.fresh(Var("y")); + let expected = [Typ.rep_id(var2)]; + check( + testable( + Fmt.using( + ids => String.concat(", ", List.map(Id.show, ids)), + Fmt.string, + ), + (==), + ), + "diff on vars with different names", + expected, + Typ.diff(var1, var2), + ); + }, + ), + ], +); + +let tests = [join_tests, fast_equal_tests, diff_tests]; diff --git a/test/haz3ltest.re b/test/haz3ltest.re index 8c1e0f4ee1..9ec912df9e 100644 --- a/test/haz3ltest.re +++ b/test/haz3ltest.re @@ -31,6 +31,7 @@ let (suite, _) = @ Test_Elaboration.tests @ Test_Evaluator.tests @ Test_Editing.tests + @ Test_PadIds.tests @ Test_Indentation.tests @ [Test_Coverage.tests, Test_Unboxing.tests] @ Test_Introduce.tests From 64d6aa661f1d7632e340c8ce1aa02a301b6985b5 Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Wed, 19 Nov 2025 10:45:34 -0500 Subject: [PATCH 35/65] Improve pad ids documentation --- src/haz3lcore/pretty/PadIds.re | 22 +++++++++------------- test/Test_PadIds.re | 4 ++-- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/haz3lcore/pretty/PadIds.re b/src/haz3lcore/pretty/PadIds.re index 4fe05d2959..683051f1e4 100644 --- a/src/haz3lcore/pretty/PadIds.re +++ b/src/haz3lcore/pretty/PadIds.re @@ -1,25 +1,21 @@ open Language; +/* Number of IDs required for ExpToSegment compatibility per type constructor */ let necessary_ids: Typ.t => int = ty => { switch (ty.term) { - | Parens(_) => 1 - | Prod([]) => 1 - | Prod(tys) => List.length(tys) - 1 - | Sum(tys) => List.length(tys) + 1 - | _ => 1 + | Prod([]) => 1 /* Empty product (unit-like) */ + | Prod(tys) => List.length(tys) - 1 /* One ID per separator */ + | Sum(tys) => List.length(tys) + 1 /* Constructors + prefix */ + | _ => 1 /* Default for other type constructors */ }; }; -/* - * This function pads the ids of a type to ensure that there are enough ids to be used so that ExpToSegment does not need to create new ids. - * This is important for maintaining the correspondence between type ids and the resulting segment ids. Specifically if you're using the the ids from the type to - * affect styling in the segment, you want to ensure that the ids in the type are the ones that get used. - * - * @param ty - The type to pad ids for. - * @return A new type with padded ids. +/** + * Pads type IDs to ensure ExpToSegment uses them instead of creating new ones, + * preserving ID correspondence for styling. Test_PadIds property test that checks + * ExpToSegment compatibility and padding equivalence. */ - let pad_typ_ids = (ty: Typ.t): Typ.t => { Typ.map_term( ~f_typ= diff --git a/test/Test_PadIds.re b/test/Test_PadIds.re index cfaed441bd..85151d2393 100644 --- a/test/Test_PadIds.re +++ b/test/Test_PadIds.re @@ -3,7 +3,7 @@ open Haz3lcore; let qcheck_pads_typ_for_exp_to_segment = QCheck.Test.make( ~name="No ids are needed to be padded during ExpToSegment", - ~count=10000, + ~count=1000, QCheck_Util.arb_typ(~minimal_idents=false, 30), typ => { let padded = PadIds.pad_typ_ids(typ); @@ -20,7 +20,7 @@ let qcheck_pads_typ_for_exp_to_segment = }, padded, ); - true; + Language.Equality.syntactic.typ(padded, PadIds.pad_typ_ids(padded)); }, ); From c27091855be2a81ad29f4145f7a2020656ccbc66 Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Wed, 19 Nov 2025 10:49:17 -0500 Subject: [PATCH 36/65] add test for no difference between identical types --- src/language/term/Typ.re | 3 ++- test/Test_Typ.re | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/language/term/Typ.re b/src/language/term/Typ.re index 4148627dba..4b9f2a0b2d 100644 --- a/src/language/term/Typ.re +++ b/src/language/term/Typ.re @@ -1142,6 +1142,8 @@ let rec diff = (ty: t, ty': t): list(Id.t) => { | (Probe(t1, _), _) => diff(t1, ty') | (_, Probe(t2, _)) => diff(ty, t2) | (Parens(t1), Parens(t2)) => diff(t1, t2) + | (Parens(t1), _) => diff(t1, ty') + | (_, Parens(t2)) => diff(ty, t2) | (Unknown(_), Unknown(_)) => [] | (Unknown(_), _) => get_ids() | (Atom(c1), Atom(c2)) when c1 == c2 => [] @@ -1174,6 +1176,5 @@ let rec diff = (ty: t, ty': t): list(Id.t) => { | (Sum(sm1), Sum(sm2)) when ConstructorMap.equal(fast_equal, sm1, sm2) => [] | (Sum(_), _) => get_ids() - | _ => raise(Failure("diff: incompatible types")) }; }; diff --git a/test/Test_Typ.re b/test/Test_Typ.re index cc5afc60c8..b76cb9f2f2 100644 --- a/test/Test_Typ.re +++ b/test/Test_Typ.re @@ -424,6 +424,16 @@ let diff_tests = ( ); }, ), + QCheck_alcotest.to_alcotest( + QCheck.Test.make( + ~name="Same type has no diff", + ~count=1000, + QCheck_Util.arb_typ(~minimal_idents=false, 30), + typ => { + Typ.diff(typ, typ) == [] + } + ), + ), ], ); From e4e1a7cb804cc23401dd29daad90c4fddde5a92f Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Wed, 19 Nov 2025 10:59:38 -0500 Subject: [PATCH 37/65] feat(projectors): add Typ handling to type projector - Extend self_ty function to extract term from InfoTyp case - Update init to recognize Typ sorts in switch cases - Refactor test formatting in Typ.diff property test for consistency --- src/haz3lcore/projectors/implementations/TypeProj.re | 4 +++- test/Test_Typ.re | 5 ++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/haz3lcore/projectors/implementations/TypeProj.re b/src/haz3lcore/projectors/implementations/TypeProj.re index a4e4cf3945..d911673232 100644 --- a/src/haz3lcore/projectors/implementations/TypeProj.re +++ b/src/haz3lcore/projectors/implementations/TypeProj.re @@ -15,6 +15,7 @@ let self_ty = (info: option(Info.t)): option(Typ.t) => switch (info) { | Some(InfoExp({self, _})) => Self.typ_of_exp(self) | Some(InfoPat({self, _})) => Self.typ_of_pat(self) + | Some(InfoTyp({term, _})) => Some(term) | _ => None }; @@ -67,7 +68,8 @@ module M: Projector = { let init = (any: Any.t): option(model) => { switch (any) { | Exp(_) - | Pat(_) => Some(Expected) + | Pat(_) + | Typ(_) => Some(Expected) | Any () => Some(Expected) /* Grout don't have sorts rn */ | _ => None }; diff --git a/test/Test_Typ.re b/test/Test_Typ.re index b76cb9f2f2..8d0b7acb4f 100644 --- a/test/Test_Typ.re +++ b/test/Test_Typ.re @@ -430,9 +430,8 @@ let diff_tests = ( ~count=1000, QCheck_Util.arb_typ(~minimal_idents=false, 30), typ => { - Typ.diff(typ, typ) == [] - } - ), + Typ.diff(typ, typ) == [] + }), ), ], ); From f1eea2e5de0490d7c151328e465bb71277b7f11c Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Wed, 19 Nov 2025 11:31:24 -0500 Subject: [PATCH 38/65] refactor(elaborate): remove unused fix_typ_ids function and simplify type handling --- src/language/statics/Elaborator.re | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/language/statics/Elaborator.re b/src/language/statics/Elaborator.re index b8a357f55a..1d804cc80e 100644 --- a/src/language/statics/Elaborator.re +++ b/src/language/statics/Elaborator.re @@ -512,19 +512,11 @@ let rec elaborate = (dhexp, elaborated_type); }; -//let dhexp_of_uexp = Core.Memo.general(~cache_size_bound=1000, dhexp_of_uexp); - -/* This function gives a new id to all the types - in the expression. It does this to get rid of - all the invalid ids we added to prevent generating - too many new ids */ -let fix_typ_ids = Exp.map_term(~f_typ=(cont, e) => e |> cont); - let uexp_elab = (~probe_unknowns: bool, m: Statics.Map.t, uexp: Exp.t) : ElaborationResult.t => { switch (elaborate(~probe_unknowns, m, uexp)) { | exception MissingTypeInfo => DoesNotElaborate - | (d, ty) => Elaborates(d |> fix_typ_ids, ty) + | (d, ty) => Elaborates(d, ty) }; }; From 222abd17b8e689b84a05dfe3f257fce17eae8a4e Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Wed, 19 Nov 2025 11:32:48 -0500 Subject: [PATCH 39/65] fix(diff): clarify assumption about distinct initial ids in diff function --- src/language/term/Typ.re | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/language/term/Typ.re b/src/language/term/Typ.re index 4b9f2a0b2d..a05ee2ce95 100644 --- a/src/language/term/Typ.re +++ b/src/language/term/Typ.re @@ -1124,7 +1124,7 @@ let remove_duplicate_labels = */ let to_product = (tys: list(t)): t => TempGrammar.Typ.(prod(tys)); -/* Computes the list of ids in t' that are not in t. Assumes initial ids are distinct. Only returns the id of the root difference. */ +/* Computes the list of ids in t' that are not in t. Assumes initial ids are distinct otherwise you may get incorrect ids. */ let rec diff = (ty: t, ty': t): list(Id.t) => { let get_ids = () => { let ids = ref([]); From d5a029162552aa0a4f0511a66766f4816aa38573 Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Wed, 19 Nov 2025 11:54:02 -0500 Subject: [PATCH 40/65] Fix typ.diff for recursive types and forall types --- src/language/term/Typ.re | 7 +++++-- test/Test_Typ.re | 42 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/language/term/Typ.re b/src/language/term/Typ.re index a05ee2ce95..a4b46e6048 100644 --- a/src/language/term/Typ.re +++ b/src/language/term/Typ.re @@ -1154,9 +1154,12 @@ let rec diff = (ty: t, ty': t): list(Id.t) => { | (ExplicitNonlabel, _) => get_ids() | (Var(v1), Var(v2)) when v1 == v2 => [] | (Var(_), _) => get_ids() - | (Rec(_tp1, t1), Rec(_tp2, t2)) => diff(t1, t2) // TODO Check tpat + | (Rec(tp1, t1), Rec(tp2, t2)) when Equality.syntactic.tpat(tp1, tp2) => + diff(t1, t2) | (Rec(_), _) => get_ids() - | (Forall(_tp1, t1), Forall(_tp2, t2)) => diff(t1, t2) // TODO Check tpat + | (Forall(tp1, t1), Forall(tp2, t2)) + when Equality.syntactic.tpat(tp1, tp2) => + diff(t1, t2) | (Forall(_), _) => get_ids() | (Arrow(t1a, t1b), Arrow(t2a, t2b)) => diff(t1a, t2a) @ diff(t1b, t2b) | (Arrow(_), _) => get_ids() diff --git a/test/Test_Typ.re b/test/Test_Typ.re index 8d0b7acb4f..1242a0d94b 100644 --- a/test/Test_Typ.re +++ b/test/Test_Typ.re @@ -299,7 +299,7 @@ let fast_equal_tests = ( ), ], ); - +let testable_id = testable(Fmt.using(Id.show, Fmt.string), (==)); let diff_tests = ( "Typ.diff", [ @@ -424,6 +424,46 @@ let diff_tests = ( ); }, ), + test_case( + "Recursive types with same tpat and type", + `Quick, + () => { + let tpat_x = TPat.fresh(Var("x")); + let var_x = Typ.fresh(Var("x")); + let rec1 = Typ.fresh(Rec(tpat_x, var_x)); + let rec2 = Typ.fresh(Rec(tpat_x, var_x)); + let expected = []; + check( + list(testable_id), + "diff on recursive types with different tpats", + expected, + Typ.diff(rec1, rec2), + ); + }, + ), + test_case( + "Recursive types with different tpats", + `Quick, + () => { + let rec1 = + Typ.fresh(Rec(TPat.fresh(Var("x")), Typ.fresh(Var("x")))); + let tpat_y = TPat.fresh(Var("y")); + let var_y = Typ.fresh(Var("y")); + let rec2 = Typ.fresh(Rec(tpat_y, var_y)); + + let expected = [ + TPat.rep_id(tpat_y), + Typ.rep_id(var_y), + Typ.rep_id(rec2), + ]; + check( + list(testable_id), + "diff on recursive types with different tpats", + expected, + Typ.diff(rec1, rec2), + ); + }, + ), QCheck_alcotest.to_alcotest( QCheck.Test.make( ~name="Same type has no diff", From 9b1af01c02fc061986114cad1e569c7dfe50ddac Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Wed, 19 Nov 2025 11:54:50 -0500 Subject: [PATCH 41/65] Remove duplicate test --- test/Test_Typ.re | 9 --------- 1 file changed, 9 deletions(-) diff --git a/test/Test_Typ.re b/test/Test_Typ.re index 1242a0d94b..26ac41db7c 100644 --- a/test/Test_Typ.re +++ b/test/Test_Typ.re @@ -464,15 +464,6 @@ let diff_tests = ( ); }, ), - QCheck_alcotest.to_alcotest( - QCheck.Test.make( - ~name="Same type has no diff", - ~count=1000, - QCheck_Util.arb_typ(~minimal_idents=false, 30), - typ => { - Typ.diff(typ, typ) == [] - }), - ), ], ); From 0f20fd135aa8901f2a9dd2e977474bf1dd4c3e3d Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Wed, 19 Nov 2025 12:06:56 -0500 Subject: [PATCH 42/65] refactor(diff): simplify testable function usage in diff tests --- test/Test_Typ.re | 40 +++++----------------------------------- 1 file changed, 5 insertions(+), 35 deletions(-) diff --git a/test/Test_Typ.re b/test/Test_Typ.re index 26ac41db7c..7084dc64f2 100644 --- a/test/Test_Typ.re +++ b/test/Test_Typ.re @@ -320,13 +320,7 @@ let diff_tests = ( let float_typ = Typ.fresh(Atom(Atom.Float)); let expected = [Typ.rep_id(float_typ)]; check( - testable( - Fmt.using( - ids => String.concat(", ", List.map(Id.show, ids)), - Fmt.string, - ), - (==), - ), + list(testable_id), "diff on different atom types", expected, Typ.diff(int_typ, float_typ), @@ -343,13 +337,7 @@ let diff_tests = ( let arrow2 = Typ.fresh(Arrow(int_typ, float_typ)); let expected = [Typ.rep_id(float_typ)]; check( - testable( - Fmt.using( - ids => String.concat(", ", List.map(Id.show, ids)), - Fmt.string, - ), - (==), - ), + list(testable_id), "diff on arrows with different codomains", expected, Typ.diff(arrow1, arrow2), @@ -366,13 +354,7 @@ let diff_tests = ( let list2 = Typ.fresh(List(float_typ)); let expected = [Typ.rep_id(float_typ)]; check( - testable( - Fmt.using( - ids => String.concat(", ", List.map(Id.show, ids)), - Fmt.string, - ), - (==), - ), + list(testable_id), "diff on lists with different elements", expected, Typ.diff(list1, list2), @@ -390,13 +372,7 @@ let diff_tests = ( let arrow2 = Typ.fresh(Arrow(float_typ, string_typ)); let expected = [Typ.rep_id(float_typ)]; check( - testable( - Fmt.using( - ids => String.concat(", ", List.map(Id.show, ids)), - Fmt.string, - ), - (==), - ), + list(testable_id), "diff on arrows with different domains", expected, Typ.diff(arrow1, arrow2), @@ -411,13 +387,7 @@ let diff_tests = ( let var2 = Typ.fresh(Var("y")); let expected = [Typ.rep_id(var2)]; check( - testable( - Fmt.using( - ids => String.concat(", ", List.map(Id.show, ids)), - Fmt.string, - ), - (==), - ), + list(testable_id), "diff on vars with different names", expected, Typ.diff(var1, var2), From c7d0ce212dda59be11d974e2a0f2b8238795eacd Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Wed, 19 Nov 2025 13:25:32 -0500 Subject: [PATCH 43/65] refactor(Typ): abandon consistent_join - Eliminated Unknown(Inconsistent) cases from pretty printing, equality checks, grammar definitions, and precedence handling - Replaced Typ.consistent_join with Typ.join_all using Internal type as default - Revert join implementation --- src/haz3lcore/pretty/ExpToSegment.re | 3 - .../projectors/implementations/TypeProj.re | 5 +- src/language/term/Equality.re | 2 - src/language/term/Grammar.re | 2 - src/language/term/TermBase.re | 1 - src/language/term/Typ.re | 61 +++-------- src/web/app/explainthis/ExplainThis.re | 1 - test/Test_Grammar.re | 1 - test/Test_Typ.re | 103 ------------------ 9 files changed, 18 insertions(+), 161 deletions(-) diff --git a/src/haz3lcore/pretty/ExpToSegment.re b/src/haz3lcore/pretty/ExpToSegment.re index f3c5c144e2..517b9fc963 100644 --- a/src/haz3lcore/pretty/ExpToSegment.re +++ b/src/haz3lcore/pretty/ExpToSegment.re @@ -138,7 +138,6 @@ let rec external_precedence_typ = (tp: Typ.t) => | Unknown(Internal) | Unknown(SynSwitch) | Unknown(Hole(EmptyHole)) - | Unknown(Inconsistent) | Var(_) | Atom(_) | Label(_) @@ -507,7 +506,6 @@ and parenthesize_typ = | Var(_) | Unknown(Hole(Invalid(_))) | Unknown(Internal) - | Unknown(Inconsistent) | Unknown(SynSwitch) | Unknown(Hole(EmptyHole)) | Atom(_) => typ @@ -1391,7 +1389,6 @@ and typ_to_pretty = (~settings: Settings.t, typ: Typ.t): pretty => { | Unknown(Hole(Invalid(s))) => text_to_pretty(typ |> Typ.rep_id, Sort.Typ, s) | Unknown(Internal) - | Unknown(Inconsistent) | Unknown(SynSwitch) | Unknown(Hole(EmptyHole)) => if (settings.show_unknown_as_hole) { diff --git a/src/haz3lcore/projectors/implementations/TypeProj.re b/src/haz3lcore/projectors/implementations/TypeProj.re index d911673232..7c400f4b46 100644 --- a/src/haz3lcore/projectors/implementations/TypeProj.re +++ b/src/haz3lcore/projectors/implementations/TypeProj.re @@ -47,7 +47,10 @@ let get_dynamic_typ = (info: info): Typ.t => { }; let types = List.map(type_of, d) |> Util.OptUtil.sequence; - Option.map(Typ.consistent_join(Ctx.empty), types); + Option.bind( + types, + Typ.join_all(~empty=Typ.fresh(Unknown(Internal)), Ctx.empty), + ); }, ) |> Option.value(~default=Typ.fresh(Unknown(Internal))); diff --git a/src/language/term/Equality.re b/src/language/term/Equality.re index 6e7eccd0ab..69bd193c94 100644 --- a/src/language/term/Equality.re +++ b/src/language/term/Equality.re @@ -553,8 +553,6 @@ let equality = | (Unknown(Hole(MultiHole(_))), _) => false | (Unknown(Internal), Unknown(Internal)) => true | (Unknown(Internal), _) => false - | (Unknown(Inconsistent), Unknown(Inconsistent)) => true - | (Unknown(Inconsistent), _) => false // Other forms: compare. | (Atom(a1), Atom(a2)) => a1 == a2 diff --git a/src/language/term/Grammar.re b/src/language/term/Grammar.re index fabae1536f..c1ee06a8c1 100644 --- a/src/language/term/Grammar.re +++ b/src/language/term/Grammar.re @@ -171,7 +171,6 @@ and type_provenance('a) = | SynSwitch | Hole(type_hole('a)) | Internal - | Inconsistent and filter('a) = { pat: exp_t('a), act: FilterAction.t, @@ -444,7 +443,6 @@ and map_type_provenance_annotation: | SynSwitch => SynSwitch | Hole(h) => Hole(map_type_hole_annotation(f, h)) | Internal => Internal - | Inconsistent => Inconsistent }; } and map_type_hole_annotation: diff --git a/src/language/term/TermBase.re b/src/language/term/TermBase.re index a6f09cb760..2d60e3cee5 100644 --- a/src/language/term/TermBase.re +++ b/src/language/term/TermBase.re @@ -364,7 +364,6 @@ and Typ: { | Unknown(Hole(Invalid(_))) | Unknown(SynSwitch) | Unknown(Internal) - | Unknown(Inconsistent) | Atom(_) | Label(_) | ExplicitNonlabel diff --git a/src/language/term/Typ.re b/src/language/term/Typ.re index a4b46e6048..70412fbe30 100644 --- a/src/language/term/Typ.re +++ b/src/language/term/Typ.re @@ -9,7 +9,6 @@ type cls = | MultiHole | SynSwitch | Internal - | Inconsistent | Arrow | Prod | TupLabel @@ -85,7 +84,6 @@ let cls_of_term: Grammar.typ_term('a) => cls = | Unknown(Hole(MultiHole(_))) => MultiHole | Unknown(SynSwitch) => SynSwitch | Unknown(Internal) => Internal - | Unknown(Inconsistent) => Inconsistent | Atom(c) => Atom(c) | List(_) => List | Arrow(_) => Arrow @@ -107,7 +105,6 @@ let show_cls: cls => string = | Invalid => "Invalid type" | MultiHole => "Broken type" | EmptyHole => "Type hole" - | Inconsistent => "Join of Inconsistent types" | SynSwitch => "Synthetic type" | Internal => "Internal type" | Atom(_) => "Base type" @@ -248,8 +245,6 @@ let join_type_provenance = | (Internal, SynSwitch) => SynSwitch | (Internal | Hole(_), _) | (_, Hole(_)) => Internal - | (Inconsistent, _) - | (_, Inconsistent) => Inconsistent | (SynSwitch, SynSwitch) => SynSwitch }; @@ -586,16 +581,8 @@ let rec normalize = (~rec_counter=0, ctx: Ctx.t, ty: t): t => { resolve parameter specifies whether, in the case of a type variable and a succesful join, to return the resolved join type, or to return the (first) type variable for readability */ -let rec join = - ( - ~inconsistent: option(t)=?, - ~resolve=false, - ctx: Ctx.t, - ty1: t, - ty2: t, - ) - : option(t) => { - let join' = join(~inconsistent?, ~resolve, ctx); +let rec join = (~resolve=false, ctx: Ctx.t, ty1: t, ty2: t): option(t) => { + let join' = join(~resolve, ctx); switch (term_of(ty1), term_of(ty2)) { | (_, Parens(ty2)) => join'(ty1, ty2) | (Parens(ty1), _) => join'(ty1, ty2) @@ -605,8 +592,6 @@ let rec join = | (_, Probe(ty2, _)) => join'(ty1, ty2) | (Unknown(p1), Unknown(p2)) => Some(Unknown(join_type_provenance(p1, p2)) |> temp) - | (Unknown(Inconsistent), _) => Some(ty1) - | (_, Unknown(Inconsistent)) => Some(ty2) | (Unknown(_), _) => Some(ty2) | (_, Unknown(_)) => Some(ty1) | (Var(n1), Var(n2)) => @@ -640,7 +625,7 @@ let rec join = }; let+ ty_body = join(~resolve, ctx, ty1', ty2); Rec(tp1, ty_body) |> temp; - | (Rec(_), _) => inconsistent + | (Rec(_), _) => None | (Forall(x1, ty1), Forall(x2, ty2)) => let ty1' = switch (TPat.tyvar_of_utpat(x2)) { @@ -656,25 +641,25 @@ let rec join = be exposed to the user. We preserve the variable name of the second type to preserve synthesized type variable names, which come from user annotations. */ - | (Forall(_), _) => inconsistent + | (Forall(_), _) => None | (Atom(c1), Atom(c2)) when c1 == c2 => Some(ty1) - | (Atom(_), _) => inconsistent + | (Atom(_), _) => None | (Label(_), Label("")) => Some(ty1) | (Label(""), Label(_)) => Some(ty2) | (Label(name1), Label(name2)) when LabeledTuple.match_labels(name1, name2) => Some(ty1) - | (Label(_), _) => inconsistent + | (Label(_), _) => None | (Arrow(ty1, ty2), Arrow(ty1', ty2')) => let* ty1 = join'(ty1, ty1'); let+ ty2 = join'(ty2, ty2'); Arrow(ty1, ty2) |> temp; - | (Arrow(_), _) => inconsistent + | (Arrow(_), _) => None | (TupLabel(lab1, ty1'), TupLabel(lab2, ty2')) => let* lab = join'(lab1, lab2); let+ ty = join'(ty1', ty2'); TupLabel(lab, ty) |> temp; - | (TupLabel(_), _) => inconsistent + | (TupLabel(_), _) => None | (Prod(tys1), Prod(tys2)) => if (List.length(tys1) != List.length(tys2)) { None; @@ -683,18 +668,18 @@ let rec join = let+ tys = OptUtil.sequence(tys); Prod(tys) |> temp; } - | (Prod(_), _) => inconsistent + | (Prod(_), _) => None | (Sum(sm1), Sum(sm2)) => let+ sm' = ConstructorMap.join(equal, join(~resolve, ctx), sm1, sm2); Sum(sm') |> temp; - | (Sum(_), _) => inconsistent + | (Sum(_), _) => None | (List(ty1), List(ty2)) => let+ ty = join'(ty1, ty2); List(ty) |> temp; - | (List(_), _) => inconsistent + | (List(_), _) => None // We would prefer for this to be a sort difference and never appear in a join. // These get marked in statics but that does not remove them from the utyp's propagated on parents. - | (ExplicitNonlabel, _) => inconsistent + | (ExplicitNonlabel, _) => None }; }; @@ -741,10 +726,9 @@ let rec match_synswitch = (t1: t, t2: t) => { }; }; -let join_all = - (~inconsistent=?, ~empty: t, ctx: Ctx.t, ts: list(t)): option(t) => +let join_all = (~empty: t, ctx: Ctx.t, ts: list(t)): option(t) => List.fold_left( - (acc, ty) => OptUtil.and_then(join(~inconsistent?, ctx, ty), acc), + (acc, ty) => OptUtil.and_then(join(ctx, ty), acc), Some(empty), ts, ); @@ -752,23 +736,6 @@ let join_all = let is_consistent = (ctx: Ctx.t, ty1: t, ty2: t): bool => join(ctx, ty1, ty2) != None; -/* - Computes the most precise type that is consistent with all input types. - Like a multi-way join, but if any ground types are inconsistent, replaces - that position with an "unknown" type rather than failing. - This operation is NOT associative — applying it pairwise may yield - a different result than applying it to the whole list at once. - */ -let consistent_join = (ctx: Ctx.t, tys: list(t)): t => { - join_all( - ~inconsistent=Unknown(Inconsistent) |> temp, - ~empty=Unknown(SynSwitch) |> temp, - ctx, - tys, - ) - |> Option.value(~default=Unknown(SynSwitch) |> temp); -}; - /** * Determines if one type (`ty1`) is more precise than another type (`ty2`) within a given context (`ctx`). * diff --git a/src/web/app/explainthis/ExplainThis.re b/src/web/app/explainthis/ExplainThis.re index d0a8de8120..d41a7e31aa 100644 --- a/src/web/app/explainthis/ExplainThis.re +++ b/src/web/app/explainthis/ExplainThis.re @@ -2406,7 +2406,6 @@ let get_doc = switch (bypass_parens_typ(term).term) { | Unknown(SynSwitch) | Unknown(Internal) - | Unknown(Inconsistent) | Unknown(Hole(EmptyHole)) => get_message(HoleTyp.empty_hole) | Unknown(Hole(MultiHole(_))) => get_message(HoleTyp.multi_hole) | Atom(Int) => get_message(TerminalTyp.int) diff --git a/test/Test_Grammar.re b/test/Test_Grammar.re index f5e11494de..d2d44d26e7 100644 --- a/test/Test_Grammar.re +++ b/test/Test_Grammar.re @@ -147,7 +147,6 @@ let sample_type = (cls_typ: Typ.cls): Grammar.UnitGrammar.typ => { | EmptyHole => unknown(Hole(EmptyHole)) | SynSwitch => unknown(SynSwitch) | Internal => unknown(Internal) - | Inconsistent => unknown(Inconsistent) | Label => label("label") | ExplicitNonlabel => explicit_non_label() | MultiHole => unknown(Hole(MultiHole([]))) diff --git a/test/Test_Typ.re b/test/Test_Typ.re index 7084dc64f2..381e7b309f 100644 --- a/test/Test_Typ.re +++ b/test/Test_Typ.re @@ -194,109 +194,6 @@ let fast_equal_tests = ( ); }, ), - test_case( - "consistent_join on equivalent atomic types", - `Quick, - () => { - open IdTagged.FreshGrammar.Typ; - let t3 = - Typ.consistent_join( - Builtins.ctx_init(Some(Int)), - [string(), string()], - ); - check( - testable_typ, - "consistent_join on equivalent atomic types", - string(), - t3, - ); - }, - ), - test_case( - "consistent_join on inconsistent atomic types", - `Quick, - () => { - open IdTagged.FreshGrammar.Typ; - let t3 = - Typ.consistent_join( - Builtins.ctx_init(Some(Int)), - [string(), int()], - ); - check( - testable_typ, - "consistent_join on inconsistent atomic types", - unknown(Internal), - t3, - ); - }, - ), - test_case( - "consistent_join on lists of inconsistent atomic types", - `Quick, - () => { - open IdTagged.FreshGrammar.Typ; - let t3 = - Typ.consistent_join( - Builtins.ctx_init(Some(Int)), - [list(string()), list(int())], - ); - check( - testable_typ, - "consistent_join on equivalent function types", - list(unknown(Internal)), - t3, - ); - }, - ), - test_case( - "consistent_join on arrow types with inconsistent parts", - `Quick, - () => { - open IdTagged.FreshGrammar.Typ; - let t3 = - Typ.consistent_join( - Builtins.ctx_init(Some(Int)), - [arrow(string(), list(int())), arrow(int(), list(string()))], - ); - check( - testable_typ, - "consistent_join on arrow types with inconsistent parts", - arrow(unknown(Inconsistent), list(unknown(Inconsistent))), - t3, - ); - }, - ), - test_case( - "Consistent join collapses unknowns", - `Quick, - () => { - open IdTagged.FreshGrammar.Typ; - let t3 = - Typ.consistent_join( - Builtins.ctx_init(Some(Int)), - [unknown(Hole(EmptyHole)), int()], - ); - check(testable_typ, "Consistent join collapses unknowns", int(), t3); - }, - ), - test_case( - "Consistent join does not collapse inconsistent unknowns", - `Quick, - () => { - open IdTagged.FreshGrammar.Typ; - let t3 = - Typ.consistent_join( - Builtins.ctx_init(Some(Int)), - [int(), string(), float()], - ); - check( - testable_typ, - "Consistent join does not collapse inconsistent unknowns", - unknown(Inconsistent), - t3, - ); - }, - ), ], ); let testable_id = testable(Fmt.using(Id.show, Fmt.string), (==)); From 52d2a2c4960207147f0331da5a8ab5875bd8a6ae Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Wed, 19 Nov 2025 13:34:01 -0500 Subject: [PATCH 44/65] refactor(ProjectorCore): simplify name lookup using StringMap --- src/haz3lcore/projectors/ProjectorCore.re | 28 ++++++++++------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/src/haz3lcore/projectors/ProjectorCore.re b/src/haz3lcore/projectors/ProjectorCore.re index 4d37886aa2..60c5bfe020 100644 --- a/src/haz3lcore/projectors/ProjectorCore.re +++ b/src/haz3lcore/projectors/ProjectorCore.re @@ -55,25 +55,21 @@ module Kind = { | TextArea => "text" }; - /* This must be updated and kept 1-to-1 with the above - * name function in order to be able to select the - * projector in the projector panel menu */ + module StringMap = Map.Make(String); + let name_to_kind: StringMap.t(t) = + List.fold_left( + (map, kind) => StringMap.add(name(kind), kind, map), + StringMap.empty, + all, + ); + let of_name = (p: string): t => - // TODO Use a map built from above - switch (p) { - | "fold" => Fold - | "type" => Info - | "probe" => Probe - | "check" => Checkbox - | "slider" => Slider - | "sliderf" => SliderF - | "text" => TextArea - | "livelit" => Livelit - | "card" => Card - | _ => failwith("Unknown projector kind") + switch (StringMap.find_opt(p, name_to_kind)) { + | Some(k) => k + | None => failwith("Unknown projector kind") }; - let is_name = str => List.mem(str, List.map(name, all)); + let is_name = StringMap.mem(_, name_to_kind); }; /* Projectors in syntax */ From 15a8df2c20a0c2ad6dc0523c2faff0f6836ee314 Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Wed, 19 Nov 2025 13:46:29 -0500 Subject: [PATCH 45/65] refactor(Test_Typ): remove redundant testable_typ variable --- test/Test_Typ.re | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/Test_Typ.re b/test/Test_Typ.re index 381e7b309f..62ca54679d 100644 --- a/test/Test_Typ.re +++ b/test/Test_Typ.re @@ -1,6 +1,5 @@ open Alcotest; open Language; -let testable_typ = testable(Fmt.using(Typ.show, Fmt.string), Typ.fast_equal); let typ = testable(Fmt.using(Typ.show, Fmt.string), Typ.fast_equal); @@ -18,7 +17,7 @@ let join_tests = ( Forall(Var("b") |> TPat.temp, Var("b") |> Typ.temp) |> Typ.temp, ); check( - option(testable_typ), + option(typ), "Forall alpha equivalent", Some( Forall(Var("a") |> TPat.temp, Var("a") |> Typ.temp) |> Typ.temp, From c2b73ee67a3ad5935d96aba7685e0515aa53607e Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Thu, 20 Nov 2025 10:28:12 -0500 Subject: [PATCH 46/65] Fix assertion description --- test/Test_Typ.re | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Test_Typ.re b/test/Test_Typ.re index 62ca54679d..fbb46de4cf 100644 --- a/test/Test_Typ.re +++ b/test/Test_Typ.re @@ -301,7 +301,7 @@ let diff_tests = ( let expected = []; check( list(testable_id), - "diff on recursive types with different tpats", + "diff on recursive types with same tpats", expected, Typ.diff(rec1, rec2), ); From 1d1ff55b08a775036b50923b7510d74b72825d5e Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Thu, 20 Nov 2025 10:49:40 -0500 Subject: [PATCH 47/65] Fix typeproj dynamic type styling - Assign fresh ids to fix temp ids in typ --- .../projectors/implementations/TypeProj.re | 20 ++++++++++--------- test/Test_Typ.re | 19 ++++++++++++++++++ 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/haz3lcore/projectors/implementations/TypeProj.re b/src/haz3lcore/projectors/implementations/TypeProj.re index 7c400f4b46..f42ff4a726 100644 --- a/src/haz3lcore/projectors/implementations/TypeProj.re +++ b/src/haz3lcore/projectors/implementations/TypeProj.re @@ -100,15 +100,17 @@ module M: Projector = { let (is_dynamic, typ) = switch (model) { | Dynamic => - let self_ty = self_ty(info.statics); - let dyn_typ = get_dynamic_typ(info) |> PadIds.pad_typ_ids; - let sty = - Option.value(~default=Typ.fresh(Unknown(Internal)), self_ty); - let ids: list(Id.t) = Typ.diff(sty, dyn_typ); - let is_dynamic_id = (id: Id.t): bool => { - List.mem(id, ids); - }; - (is_dynamic_id, dyn_typ); + let dyn_typ = + get_dynamic_typ(info) + |> Grammar.map_typ_annotation(_ => IdTagged.IdTag.fresh()) + |> PadIds.pad_typ_ids; + let self_ty = + Option.value( + ~default=Typ.fresh(Unknown(Internal)), + self_ty(info.statics), + ); + let dynamic_ids: list(Id.t) = Typ.diff(self_ty, dyn_typ); + (List.mem(_, dynamic_ids), dyn_typ); | Expected when expected_ty(info.statics) |> totalize_ty |> Typ.is_syn => ( (_ => false), self_ty(info.statics) |> totalize_ty, diff --git a/test/Test_Typ.re b/test/Test_Typ.re index fbb46de4cf..85f2e66ca3 100644 --- a/test/Test_Typ.re +++ b/test/Test_Typ.re @@ -275,6 +275,25 @@ let diff_tests = ( ); }, ), + test_case( + "(Int, a) ~ (Int, String)", + `Quick, + () => { + let int_typ = Typ.fresh(Atom(Atom.Int)); + let string_typ = Typ.fresh(Atom(Atom.String)); + let var_a = Typ.fresh(Var("a")); + let expected = [Typ.rep_id(string_typ)]; + check( + list(testable_id), + "diff on (Int, a) ~ (Int, String)", + expected, + Typ.diff( + Typ.fresh(Prod([int_typ, var_a])), + Typ.fresh(Prod([int_typ, string_typ])), + ), + ); + }, + ), test_case( "diff var different names", `Quick, From e4b95b2a6fcaf8d9f700dcd133da376c004ce976 Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Mon, 2 Feb 2026 18:43:01 -0500 Subject: [PATCH 48/65] More fixes --- src/haz3lcore/pretty/PadIds.re | 3 +- src/haz3lcore/projectors/ProjectorBase.re | 1 + .../projectors/implementations/ProbeProj.re | 12 ++- .../projectors/implementations/TypeProj.re | 2 +- .../dynamics/transition/Ascriptions.re | 88 ++++++++----------- .../dynamics/transition/PatternMatch.re | 17 +++- .../dynamics/transition/Transition.re | 4 +- src/language/statics/Elaborator.re | 32 ++++--- src/language/statics/Statics.re | 1 - src/menhirParser/Conversion.re | 1 - 10 files changed, 85 insertions(+), 76 deletions(-) diff --git a/src/haz3lcore/pretty/PadIds.re b/src/haz3lcore/pretty/PadIds.re index 683051f1e4..daa7377a74 100644 --- a/src/haz3lcore/pretty/PadIds.re +++ b/src/haz3lcore/pretty/PadIds.re @@ -28,7 +28,8 @@ let pad_typ_ids = (ty: Typ.t): Typ.t => { cont({ ...ty, annotation: { - ids: ids, + ids, + secondary: ty.annotation.secondary, }, }); }, diff --git a/src/haz3lcore/projectors/ProjectorBase.re b/src/haz3lcore/projectors/ProjectorBase.re index 7048af37e2..04da9a882b 100644 --- a/src/haz3lcore/projectors/ProjectorBase.re +++ b/src/haz3lcore/projectors/ProjectorBase.re @@ -121,6 +121,7 @@ module View = { ~single_line: bool=?, ~background: bool=?, ~is_dynamic: Id.t => bool=?, + ~text_only: bool=?, Sort.t, list(syntax) ) => diff --git a/src/haz3lcore/projectors/implementations/ProbeProj.re b/src/haz3lcore/projectors/implementations/ProbeProj.re index 3fe35b1720..d6d99d3649 100644 --- a/src/haz3lcore/projectors/implementations/ProbeProj.re +++ b/src/haz3lcore/projectors/implementations/ProbeProj.re @@ -1192,8 +1192,16 @@ module M: Projector = { let view = ({info, local, parent, view_seg, _}: View.args(model, action)) => { let settings = Settings.s^; /* Wrap view_seg to fix single_line=true for all probe displays */ - let view_seg_single_line = (~background=?, ~text_only=?, sort, segment) => - view_seg(~single_line=true, ~background?, ~text_only?, sort, segment); + let view_seg_single_line = + (~background=?, ~text_only: option(bool)=?, sort, segment) => + view_seg( + ~single_line=true, + ~background?, + ~text_only?, + ~is_dynamic=?None, + sort, + segment, + ); View.{ inline: Node.div([]), overlay: Some(overlay_view(info)), diff --git a/src/haz3lcore/projectors/implementations/TypeProj.re b/src/haz3lcore/projectors/implementations/TypeProj.re index 42426fd78d..097af27442 100644 --- a/src/haz3lcore/projectors/implementations/TypeProj.re +++ b/src/haz3lcore/projectors/implementations/TypeProj.re @@ -33,7 +33,7 @@ let get_dynamic_typ = (info: info): Typ.t => { (d: Dynamics.Info.t) => { let statics = Statics.mk(CoreSettings.on, Builtins.ctx_init(Some(Int))); - let type_of = (c: Dynamics.Probe.Closure.t) => { + let type_of = (c: Sample.t) => { IdTagged.rep_id(c.value) |> Id.Map.find_opt(_, statics(c.value)) |> Option.bind( diff --git a/src/language/dynamics/transition/Ascriptions.re b/src/language/dynamics/transition/Ascriptions.re index bbc9d1e1a7..ab06b5f678 100644 --- a/src/language/dynamics/transition/Ascriptions.re +++ b/src/language/dynamics/transition/Ascriptions.re @@ -17,7 +17,7 @@ either by returning `Some(e)` directly, or by using `IdTagged.fast_copy(DHExp.rep_id(e), ...)` when constructing a new expression structure. */ -type closure_closures = list(Probe.call_stack => Dynamics.Probe.Closure.t); +type closure_closures = list((Sample.call_stack, int, int) => Sample.t); module ClosureWriter = Util.WriterMonad.Make({ type t = closure_closures; @@ -39,19 +39,6 @@ let rec transition = switch (DHExp.term_of(d)) { | Asc(e, t) => switch (DHExp.term_of(e), Typ.term_of(Typ.unroll(t))) { - | (_, Probe(t', p)) => - let* d' = recur(Asc(e, t') |> DHExp.fresh); - let+ () = - ClosureWriter.tell([ - Dynamics.Probe.Closure.mk( - Typ.rep_id(t), - e, - Environment.empty, - _, - p, - ), - ]); - Some(d'); | (Asc(e, t'), t) // This is only necessary because sometimes we add two ascriptions and aren't marking it as a non-value when @@ -70,12 +57,20 @@ let rec transition = } | (e, Parens(t)) => // This is an impossible case since types should be normalized before coming to transitions - transition(~recursive, Asc(e |> DHExp.fresh, t) |> DHExp.fresh) + transition( + ~recursive, + ~targets, + Asc(e |> DHExp.fresh, t) |> DHExp.fresh, + ) | (Closure(ce, d), t) => let+ d' = - transition(~recursive, Asc(d, t |> Typ.fresh) |> DHExp.fresh); + transition( + ~recursive, + ~targets, + Asc(d, t |> Typ.fresh) |> DHExp.fresh, + ); Option.map(d => Closure(ce, d) |> DHExp.fresh, d'); - | (Fun(p, e, t, v), Arrow(t1, t2)) => + | (Fun(p, body, closure_ty, name), Arrow(t1, t2)) => ClosureWriter.return( Some( IdTagged.fast_copy( @@ -87,16 +82,17 @@ let rec transition = ), ) | (TupLabel({term: ExplicitNonlabel, _}, inner), _) => - ClosureWriter.return(Some(recur(Asc(inner, t) |> DHExp.fresh))) - | (TupLabel(l, e), TupLabel(_l2, t)) => + let+ d = recur(Asc(inner, t) |> DHExp.fresh); + Some(d); + | (TupLabel(l, inner), TupLabel(_l2, inner_ty)) => + let+ inner = recur(Asc(inner, inner_ty) |> DHExp.fresh); // TODO Figure out what to do if the labels don't match - let+ e = + Some( IdTagged.fast_copy( DHExp.rep_id(e), - TupLabel(l, recur(Asc(inner, inner_ty) |> DHExp.fresh)) - |> DHExp.fresh, - ); - Some(TupLabel(l, e) |> DHExp.fresh); + TupLabel(l, inner) |> DHExp.fresh, + ), + ); | (Tuple(es), Prod(tys)) when List.length(es) == List.length(tys) => let+ es = List.map2((e, ty) => {recur(Asc(e, ty) |> DHExp.fresh)}, es, tys) @@ -144,34 +140,25 @@ let rec transition = | None => Unknown(Internal) |> Typ.temp }; - ClosureWriter.return( - Some( - IdTagged.fast_copy( - DHExp.rep_id(e), - TypFun( - tp, - recur(Asc(body, Typ.subst(new_ty, tp', t')) |> DHExp.fresh), - name, - ) - |> DHExp.fresh, - ), + let+ body' = + recur(Asc(body, Typ.subst(new_ty, tp', t')) |> DHExp.fresh); + Some( + IdTagged.fast_copy( + DHExp.rep_id(e), + TypFun(tp, body', name) |> DHExp.fresh, ), ); | (If(cond, e1, e2), t) => - ClosureWriter.return( - Some( - IdTagged.fast_copy( - DHExp.rep_id(e), - If( - recur(cond), - recur(Asc(e1, t |> Typ.temp) |> DHExp.fresh), - recur(Asc(e2, t |> Typ.temp) |> DHExp.fresh), - ) - |> DHExp.fresh, - ), + let* cond = recur(cond); + let* e1 = recur(Asc(e1, t |> Typ.temp) |> DHExp.fresh); + let+ e2 = recur(Asc(e2, t |> Typ.temp) |> DHExp.fresh); + Some( + IdTagged.fast_copy( + DHExp.rep_id(e), + If(cond, e1, e2) |> DHExp.fresh, ), - ) + ); | (Match(scrut, rules), t) => ClosureWriter.return( Some( @@ -303,10 +290,11 @@ let rec transition = }; }; -let rec transition_multiple = (d: DHExp.t): (closure_closures, DHExp.t) => { - switch (transition(~recursive=true, d)) { +let rec transition_multiple = + (~targets: Sample.targets, d: DHExp.t): (closure_closures, DHExp.t) => { + switch (transition(~targets, ~recursive=true, d)) { | (closures, Some(d'')) => - let (c, d) = transition_multiple(d''); + let (c, d) = transition_multiple(~targets, d''); (closures @ c, d); | _ => ([], d) }; diff --git a/src/language/dynamics/transition/PatternMatch.re b/src/language/dynamics/transition/PatternMatch.re index 94fd8fea5c..5e5aff980f 100644 --- a/src/language/dynamics/transition/PatternMatch.re +++ b/src/language/dynamics/transition/PatternMatch.re @@ -21,7 +21,12 @@ type sample_closures = list((Sample.call_stack, int, int) => Sample.t); /* Core pattern matching logic - just a switch on pattern structure */ let match_pattern = - (recur: (Pat.t, DHExp.t) => match_result, dp: Pat.t, d: DHExp.t) + ( + ~targets: Sample.targets, + recur: (Pat.t, DHExp.t) => match_result, + dp: Pat.t, + d: DHExp.t, + ) : match_result => switch (DHPat.term_of(dp)) { | Invalid(_) @@ -68,7 +73,10 @@ let match_pattern = List.map2(recur, ps, ds) |> List.fold_left(combine_result, Matches([])); | Parens(p) => recur(p, d) | Asc(p, t1) => - recur(p, Ascriptions.transition_multiple(Asc(d, t1) |> DHExp.fresh)) + // TODO Capture closures + let (_closures, exp) = + Ascriptions.transition_multiple(~targets, Asc(d, t1) |> DHExp.fresh); + recur(p, exp); }; /* Record a sample closure if this pattern is targeted and matched */ @@ -109,12 +117,13 @@ let rec matches_inner = d: DHExp.t, ) : match_result => { - let d = Ascriptions.transition_multiple(d); + // TODO Record closures + let (_closures, d) = Ascriptions.transition_multiple(~targets, d); let pat_id = Pat.rep_id(dp); let maybe_spec = Id.Map.find_opt(pat_id, targets); let recur = matches_inner(targets, sample_closures); - let result = match_pattern(recur, dp, d); + let result = match_pattern(~targets, recur, dp, d); record_sample(sample_closures, pat_id, maybe_spec, d, result); result; }; diff --git a/src/language/dynamics/transition/Transition.re b/src/language/dynamics/transition/Transition.re index 289966f232..28152195d5 100644 --- a/src/language/dynamics/transition/Transition.re +++ b/src/language/dynamics/transition/Transition.re @@ -919,7 +919,7 @@ module Transition = (EV: EV_MODE) => { let.wrap_closure _ = (env, d); Indet; | Asc(d', t) => - switch (Ascriptions.transition(d)) { + switch (Ascriptions.transition(~targets, d)) { | (closures, Some(d')) => let. _ = otherwise(env, d); Step({ @@ -931,7 +931,7 @@ module Transition = (EV: EV_MODE) => { | (_, None) => let. _ = otherwise(env, d => Asc(d, t) |> rewrap) and. d' = req_final(req(env), d => Asc(d, t) |> wrap_ctx, d'); - switch (Ascriptions.transition(Asc(d', t) |> rewrap)) { + switch (Ascriptions.transition(~targets, Asc(d', t) |> rewrap)) { | (closures, Some(d)) => Step({ expr: d, diff --git a/src/language/statics/Elaborator.re b/src/language/statics/Elaborator.re index 2975fbff74..61eba30917 100644 --- a/src/language/statics/Elaborator.re +++ b/src/language/statics/Elaborator.re @@ -199,13 +199,15 @@ let rec elaborate_pattern = let dpat = if (probe_unknowns && contains_unknown) { - switch (dpat) { - | {term: Probe(_), _} => dpat - | _ => { - term: Probe(dpat, Probe.empty), - annotation: dpat.annotation, - } - }; + // switch (dpat) { + // | {term: Probe(_), _} => dpat + // | _ => { + // term: Probe(dpat, Probe.empty), + // annotation: dpat.annotation, + // } + // }; + // TODO + dpat; } else { dpat; }; @@ -503,13 +505,15 @@ let rec elaborate = let dhexp = if (probe_unknowns && contains_unknown) { - switch (dhexp) { - | {term: Probe(_), _} => dhexp - | _ => { - term: Probe(dhexp, Probe.empty), - annotation: dhexp.annotation, - } - }; + // switch (dhexp) { + // | {term: Probe(_), _} => dhexp + // | _ => { + // term: Probe(dhexp, Probe.empty), + // annotation: dhexp.annotation, + // } + // }; + // TODO + dhexp; } else { dhexp; }; diff --git a/src/language/statics/Statics.re b/src/language/statics/Statics.re index a9b23a3ea7..07d94866fa 100644 --- a/src/language/statics/Statics.re +++ b/src/language/statics/Statics.re @@ -1956,7 +1956,6 @@ and utyp_to_info_map = add(m) | List(t) | Parens(t) => add(go(t, m) |> snd) - | Probe(typ, _) => add(go(typ, m) |> snd) | Arrow(t1, t2) => let m = go(t1, m) |> snd; let m = go(t2, m) |> snd; diff --git a/src/menhirParser/Conversion.re b/src/menhirParser/Conversion.re index 4d032110ff..19095d2a7a 100644 --- a/src/menhirParser/Conversion.re +++ b/src/menhirParser/Conversion.re @@ -528,7 +528,6 @@ and Typ: { constructors, ); SumTyp(sumterms); - | Probe(typ, _) => of_core(typ) }; }; } From 10c26b3eaf2988ed859acca9be3b555b23802cf8 Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala Date: Mon, 2 Feb 2026 19:02:58 -0500 Subject: [PATCH 49/65] Fix compilation --- src/CLI/Print.re | 1 + src/haz3lcore/pretty/ExpToSegment.re | 10 +--------- .../projectors/implementations/TypeProj.re | 2 +- src/haz3lcore/zipper/action/Introduce.re | 1 + src/web/app/Cursor.re | 2 +- src/web/app/common/ProjectorView.re | 6 +++++- src/web/app/editors/code/Code.re | 18 ++++++++++-------- src/web/app/editors/code/CodeWithStatics.re | 3 +-- src/web/app/explainthis/ExplainThis.re | 1 - src/web/app/probesystem/ProbeSidebar.re | 1 + test/Test_ExpToSegment.re | 2 ++ test/Test_Grammar.re | 1 - test/Test_PadIds.re | 3 +++ test/evaluator/Test_Evaluator_Prelude.re | 2 +- test/evaluator/Test_Evaluator_Probes.re | 3 ++- 15 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/CLI/Print.re b/src/CLI/Print.re index 8802b66f76..df351d3cc3 100644 --- a/src/CLI/Print.re +++ b/src/CLI/Print.re @@ -10,6 +10,7 @@ let exp_to_segment_settings: ExpToSegment.Settings.t = { hide_fixpoints: false, show_filters: true, show_unknown_as_hole: true, + raise_if_padding: false, }; let segmentize = diff --git a/src/haz3lcore/pretty/ExpToSegment.re b/src/haz3lcore/pretty/ExpToSegment.re index dbdbd8d626..3a00b43d30 100644 --- a/src/haz3lcore/pretty/ExpToSegment.re +++ b/src/haz3lcore/pretty/ExpToSegment.re @@ -218,7 +218,6 @@ let rec external_precedence_typ = (tp: Typ.t) => // Matt: I think multiholes are min because we don't know the precedence of the `⟩?⟨`s | Unknown(Hole(MultiHole(_))) => Precedence.min - | Probe(typ, _) => external_precedence_typ(typ) }; /* Conditional parenthesization helpers. @@ -772,13 +771,6 @@ and parenthesize_typ = ), ) |> rewrap - | Probe(t, pr) => - Probe( - parenthesize_typ(~already_paren=true, t) - |> paren_typ_at(Precedence.min), - pr, - ) - |> rewrap }; } @@ -1997,11 +1989,11 @@ and typ_to_pretty = (~settings: Settings.t, typ: Typ.t): pretty => { List.map2((id, t) => [mk_form(TypPlus, id, [])] @ t, ids, ts), ), ); - | Probe(typ, _) => go(typ) // TODO Wrap? }; } and tpat_to_pretty = (~settings: Settings.t, tpat: TPat.t): pretty => { let wrap = wrap_with_secondary(~secondary=settings.secondary); + let pad_ids = pad_ids(~settings); /* Use settings-aware concatenation and form building */ switch (tpat |> IdTagged.term_of) { | Invalid(t) => diff --git a/src/haz3lcore/projectors/implementations/TypeProj.re b/src/haz3lcore/projectors/implementations/TypeProj.re index 097af27442..cb4ab64c98 100644 --- a/src/haz3lcore/projectors/implementations/TypeProj.re +++ b/src/haz3lcore/projectors/implementations/TypeProj.re @@ -45,7 +45,7 @@ let get_dynamic_typ = (info: info): Typ.t => { | _ => None, ); }; - let types = List.map(type_of, d) |> Util.OptUtil.sequence; + let types = List.map(type_of, d.samples) |> Util.OptUtil.sequence; Option.bind( types, diff --git a/src/haz3lcore/zipper/action/Introduce.re b/src/haz3lcore/zipper/action/Introduce.re index 4f2a3665cb..30457638c5 100644 --- a/src/haz3lcore/zipper/action/Introduce.re +++ b/src/haz3lcore/zipper/action/Introduce.re @@ -249,6 +249,7 @@ module Make = hide_fixpoints: false, show_filters: true, show_unknown_as_hole: true, + raise_if_padding: false, }, term, already_parenthesized(z), diff --git a/src/web/app/Cursor.re b/src/web/app/Cursor.re index 0a431f4ec5..e982190b2f 100644 --- a/src/web/app/Cursor.re +++ b/src/web/app/Cursor.re @@ -2,7 +2,7 @@ open Haz3lcore; open Language; type cursor('update) = { info: option(Info.t), - dynamics: option(list(Dynamics.Probe.Closure.t)), + dynamics: option(list(Sample.t)), selected_text: option(unit => string), selection: option(Segment.t), indicated_piece: option(Piece.t), diff --git a/src/web/app/common/ProjectorView.re b/src/web/app/common/ProjectorView.re index b682f557c2..b9e4b2197a 100644 --- a/src/web/app/common/ProjectorView.re +++ b/src/web/app/common/ProjectorView.re @@ -291,18 +291,22 @@ let flex_code = ~single_line=false, /* Perf optimization if you promise it's single-line */ ~background=?, ~text_only=false, + ~is_dynamic=?, sort, segment, - ) => + ) => { + let is_dynamic = Option.value(~default=(_: Id.t) => false, is_dynamic); text_only ? text_code(segment) : simple_code( ~background?, + ~is_dynamic, ~is_single_line=single_line, font_metrics, sort, segment, ); +}; /* Route top-level metadata to the projector view function. */ let mk_view = diff --git a/src/web/app/editors/code/Code.re b/src/web/app/editors/code/Code.re index 9c9c36c456..25426ebce5 100644 --- a/src/web/app/editors/code/Code.re +++ b/src/web/app/editors/code/Code.re @@ -151,14 +151,16 @@ let view = | None => () }; - span( - ~attrs=[ - Attr.classes(Tile.id(t) |> is_dynamic ? ["dynamic"] : []), - ], - Aba.mk(t.shards, t.children) - |> Aba.join(i => [of_delim(t, i)], of_segment) - |> List.concat, - ); + [ + span( + ~attrs=[ + Attr.classes(Tile.id(t) |> is_dynamic ? ["dynamic"] : []), + ], + Aba.mk(t.shards, t.children) + |> Aba.join(i => [of_delim(t, i)], of_segment) + |> List.concat, + ), + ]; } | Grout(g) => [of_grout(g)] | Secondary(s) => [of_secondary(s)] diff --git a/src/web/app/editors/code/CodeWithStatics.re b/src/web/app/editors/code/CodeWithStatics.re index 211b34f66c..0f466c68ab 100644 --- a/src/web/app/editors/code/CodeWithStatics.re +++ b/src/web/app/editors/code/CodeWithStatics.re @@ -57,8 +57,7 @@ module Model = { let get_cursor_info = (model: t): Cursor.cursor(Action.t) => { info: Indicated.ci_of(model.editor.state.zipper, model.statics.info_map), - dynamics: - Option.bind(id, Language.Dynamics.Map.lookup(_, model.dynamics)), + dynamics: None, indicated_piece: Indicated.piece''(model.editor.state.zipper) |> Option.map(((p, _, _)) => p), diff --git a/src/web/app/explainthis/ExplainThis.re b/src/web/app/explainthis/ExplainThis.re index 662ce6ec0e..2175524a1c 100644 --- a/src/web/app/explainthis/ExplainThis.re +++ b/src/web/app/explainthis/ExplainThis.re @@ -2676,7 +2676,6 @@ let get_doc = | ExplicitNonlabel | ProdProjection(_) | ProdExtension(_) - | Probe(_) | Parens(_) => default // Shouldn't be hit? } | Some(InfoTPat(info)) => diff --git a/src/web/app/probesystem/ProbeSidebar.re b/src/web/app/probesystem/ProbeSidebar.re index 5ac220d61e..b2914e4045 100644 --- a/src/web/app/probesystem/ProbeSidebar.re +++ b/src/web/app/probesystem/ProbeSidebar.re @@ -173,6 +173,7 @@ let legend_sample_view = ~single_line=true, ~background=false, ~text_only, + ~is_dynamic=?None, ), _ => Effect.Ignore, _ => Effect.Ignore, diff --git a/test/Test_ExpToSegment.re b/test/Test_ExpToSegment.re index 86e1723e15..023d71be45 100644 --- a/test/Test_ExpToSegment.re +++ b/test/Test_ExpToSegment.re @@ -441,6 +441,7 @@ let exp_to_segment_roundtrip_settings: ExpToSegment.Settings.t = { hide_fixpoints: false, show_filters: true, show_unknown_as_hole: true, + raise_if_padding: false, }; let exp_to_segment_roundtrip = @@ -1019,6 +1020,7 @@ let grout_structural_settings: ExpToSegment.Settings.t = { hide_fixpoints: false, show_filters: true, show_unknown_as_hole: true, + raise_if_padding: false, }; /* String-to-string grout tests: parse strings, verify round-trip preserves text. diff --git a/test/Test_Grammar.re b/test/Test_Grammar.re index 831941a264..a594041cf7 100644 --- a/test/Test_Grammar.re +++ b/test/Test_Grammar.re @@ -161,7 +161,6 @@ let sample_type = (cls_typ: Typ.cls): Grammar.UnitGrammar.typ => { | ProdExtension => prod_extension(unknown(Hole(EmptyHole)), unknown(Hole(EmptyHole))) | Constructor => assert(false) // Excluded because there is no Typ constructor - | Probe => probe(unknown(Hole(EmptyHole)), Probe.empty) } ) ); diff --git a/test/Test_PadIds.re b/test/Test_PadIds.re index 7abcb20070..fa0437ceba 100644 --- a/test/Test_PadIds.re +++ b/test/Test_PadIds.re @@ -10,6 +10,9 @@ let qcheck_pads_typ_for_exp_to_segment = let _ = ExpToSegment.typ_to_segment( ~settings={ + secondary: AutoFormat, + parenthesization: Defensive, + label_format: QuoteWhenNecessary, inline: false, fold_case_clauses: false, fold_fn_bodies: `NoFold, diff --git a/test/evaluator/Test_Evaluator_Prelude.re b/test/evaluator/Test_Evaluator_Prelude.re index d1c007cb90..1cdcb3767c 100644 --- a/test/evaluator/Test_Evaluator_Prelude.re +++ b/test/evaluator/Test_Evaluator_Prelude.re @@ -104,7 +104,7 @@ let elaborate = u => /* Elaborate an expression with existing statics map */ let elaborate_with_info = (info_map, u) => - Elaborator.elaborate(info_map, u) |> fst; + Elaborator.elaborate(~probe_unknowns=false, info_map, u) |> fst; (exp, probes) => ( { diff --git a/test/evaluator/Test_Evaluator_Probes.re b/test/evaluator/Test_Evaluator_Probes.re index 078ec90290..37488e54ff 100644 --- a/test/evaluator/Test_Evaluator_Probes.re +++ b/test/evaluator/Test_Evaluator_Probes.re @@ -85,7 +85,8 @@ let get_samples_by_line = (code: string): IntMap.t(list(string)) => { ); /* Elaborate and evaluate */ - let elaborated = Elaborator.elaborate(info_map, term) |> fst; + let elaborated = + Elaborator.elaborate(~probe_unknowns=false, info_map, term) |> fst; let (_, state) = Evaluator.evaluate(~targets, ~env=Builtins.env_init, elaborated); let probes = EvaluatorState.get_probes(state); From 79cf327f871db9e07b441529010bdaac3c8d5d97 Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala Date: Mon, 2 Feb 2026 20:41:26 -0500 Subject: [PATCH 50/65] progress --- src/haz3lcore/ProbePerform.re | 8 ++++--- .../dynamics/transition/Ascriptions.re | 2 +- src/language/statics/Coverage.re | 21 +++++++++---------- src/language/statics/Info.re | 4 ++-- src/menhirParser/Interface.re | 12 ++++++++++- src/web/app/editors/code/ContextMenu.re | 4 ++-- 6 files changed, 31 insertions(+), 20 deletions(-) diff --git a/src/haz3lcore/ProbePerform.re b/src/haz3lcore/ProbePerform.re index 8432287374..7305b4c61b 100644 --- a/src/haz3lcore/ProbePerform.re +++ b/src/haz3lcore/ProbePerform.re @@ -87,12 +87,13 @@ let rec target_subterm_ids = (id: Id.t, info_map: Statics.Map.t) => ] | _ => [id] } - /* Filter out terms that can't meaningfully be probed */ + /* Filter out terms that can't meaningfully be probed (type patterns, labels, etc.) */ | info when !Info.is_typable_term(info) => [] /* Default: use rep_id for expressions and patterns to handle multi-tile forms (tuples, list literals, case expressions) where non-representative tile IDs would otherwise cause probe_map/evaluator ID mismatch */ | Some(InfoExp({term, _})) => [IdTagged.rep_id(term)] + | Some(InfoTyp({term, _})) => [IdTagged.rep_id(term)] | Some(InfoPat({term, _})) => [Pat.rep_id(term)] | _ => [id] }; @@ -529,9 +530,10 @@ let rm_probes_in_selection = ); }; -/* Check if type annotation is allowed for the given id. */ +/* Check if type annotation is allowed for the given id. + Uses target_subterm_ids to support types (which redirect to their parent expressions). */ let can_statics = (id: Id.t, info_map: Statics.Map.t): bool => - Info.is_typable_term(Statics.Map.lookup(id, info_map)); + target_subterm_ids(id, info_map) != []; /* Toggle type annotation on the indicated term. Unlike probes, type annotations don't support auto mode or pins. */ diff --git a/src/language/dynamics/transition/Ascriptions.re b/src/language/dynamics/transition/Ascriptions.re index ab06b5f678..36c5555bd8 100644 --- a/src/language/dynamics/transition/Ascriptions.re +++ b/src/language/dynamics/transition/Ascriptions.re @@ -107,7 +107,7 @@ let rec transition = Some( IdTagged.fast_copy(DHExp.rep_id(e), Cons(d1, d2) |> DHExp.fresh), ); - | (Atom(value) as d, Atom(typ)) => + | (Atom(value), Atom(typ)) => ClosureWriter.return( switch (value, typ) { | (Int(_), Int) diff --git a/src/language/statics/Coverage.re b/src/language/statics/Coverage.re index 416425683a..47ff582887 100644 --- a/src/language/statics/Coverage.re +++ b/src/language/statics/Coverage.re @@ -283,7 +283,7 @@ module UnseenPatternList: UnseenPatternList = { }; /* Appends any Ctr to the beginning of the unseen pattern list*/ - let rec cons_ctr = (ctr: Ctr.t, col_type: Typ.t, unseen_pattern: t) => { + let cons_ctr = (ctr: Ctr.t, col_type: Typ.t, unseen_pattern: t) => { let pat_list = unseen_pattern.pat; let cons_pat_t = (pat, unseen_pattern) => cons_pat_t(pat, unseen_pattern); @@ -463,14 +463,14 @@ module UnseenPatternList: UnseenPatternList = { /* Takes a type appends it to the start of the list. The list may receive additional modifications outside of just the type being appended. This behavior is type dependent*/ - let rec cons_from_type = - ( - seen_in_col: Seen.t, - col_type: Typ.t, - col_ctr: Ctr.t, - unseen_pattern: t, - ) - : t => { + let cons_from_type = + ( + seen_in_col: Seen.t, + col_type: Typ.t, + col_ctr: Ctr.t, + unseen_pattern: t, + ) + : t => { let all_ctrs = Ctr.all_ctrs_of_typ(col_type); let pat_list = unseen_pattern.pat; @@ -566,8 +566,7 @@ module UnseenPatternList: UnseenPatternList = { }; }; - let rec cons_type_default = - (col_type: Typ.t, col_ctr: Ctr.t, unseen_pattern: t) => { + let cons_type_default = (col_type: Typ.t, col_ctr: Ctr.t, unseen_pattern: t) => { let all_ctrs = Ctr.all_ctrs_of_typ(col_type); let pat_list = unseen_pattern.pat; diff --git a/src/language/statics/Info.re b/src/language/statics/Info.re index 11a68a3093..89eebb0bce 100644 --- a/src/language/statics/Info.re +++ b/src/language/statics/Info.re @@ -409,8 +409,8 @@ let is_typable_term: option(t) => bool = fun | Some(InfoExp({term: {term: Deferral(_) | Label(_) | TyAlias(_), _}, _})) => false - | Some(InfoTyp(_) | InfoTPat(_) | Secondary(_)) => false - | Some(InfoExp(_) | InfoPat(_)) => true + | Some(InfoTPat(_) | Secondary(_)) => false + | Some(InfoExp(_) | InfoPat(_) | InfoTyp(_)) => true | None => false; let exp_co_ctx: exp => CoCtx.t = ({co_ctx, _}) => co_ctx; diff --git a/src/menhirParser/Interface.re b/src/menhirParser/Interface.re index dfc8cd5f97..5f0b5548ba 100644 --- a/src/menhirParser/Interface.re +++ b/src/menhirParser/Interface.re @@ -19,4 +19,14 @@ let parse = (f, s) => { result; }; -let parse_program = s => parse(Parser.program, s); +let parse_program = s => { + print_endline("Parsing program: " ++ s); + parse( + lexbuf => { + let t = Parser.program(lexbuf); + print_endline("Token "); + t; + }, + s, + ); +}; diff --git a/src/web/app/editors/code/ContextMenu.re b/src/web/app/editors/code/ContextMenu.re index b640b02fcc..8f0aeb21a0 100644 --- a/src/web/app/editors/code/ContextMenu.re +++ b/src/web/app/editors/code/ContextMenu.re @@ -227,7 +227,7 @@ let manual_probe_data = ) : list(menu_item_data) => switch (ci) { - | Some(InfoExp(_) | InfoPat(_)) when can_probe => [ + | Some(InfoExp(_) | InfoPat(_) | InfoTyp(_)) when can_probe => [ { name: switch (probe_status) { @@ -275,7 +275,7 @@ let type_annotation_data = ) : list(menu_item_data) => switch (ci) { - | Some(InfoExp(_) | InfoPat(_)) when can_type => [ + | Some(InfoExp(_) | InfoPat(_) | InfoTyp(_)) when can_type => [ { name: switch (probe_status) { From c733274bb563be3ab539a3cd2c912b94afa618c3 Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala Date: Mon, 2 Feb 2026 21:00:56 -0500 Subject: [PATCH 51/65] Add type probe recording for ascriptions and update test description --- .../dynamics/transition/Ascriptions.re | 28 +++++++++++++++++++ test/evaluator/Test_Evaluator_Probes.re | 2 +- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/language/dynamics/transition/Ascriptions.re b/src/language/dynamics/transition/Ascriptions.re index 36c5555bd8..6a56796255 100644 --- a/src/language/dynamics/transition/Ascriptions.re +++ b/src/language/dynamics/transition/Ascriptions.re @@ -25,6 +25,32 @@ module ClosureWriter = let append = (@); }); +/* Record a sample for a probed type. + * When a type has a probe on it (i.e., its ID is in targets), and we're + * processing an ascription, we record the value being ascribed as a sample + * for that type's probe. */ +let record_type_probe = + (~targets: Sample.targets, typ: Typ.t, value: DHExp.t) + : ClosureWriter.t(unit) => { + let typ_id = Typ.rep_id(typ); + switch (Id.Map.find_opt(typ_id, targets)) { + | Some(spec) => + ClosureWriter.tell([ + (call_stack: Sample.call_stack, step_start: int, step_end: int) => + Sample.mk( + ~step_start, + ~step_end, + typ_id, + value, + Environment.empty, + call_stack, + spec, + ), + ]) + | None => ClosureWriter.return() + }; +}; + let rec transition = (~recursive=false, ~targets: Sample.targets, d: DHExp.t) : ClosureWriter.t(option(DHExp.t)) => { @@ -99,6 +125,8 @@ let rec transition = |> ClosureWriter.sequence; Some(IdTagged.fast_copy(DHExp.rep_id(e), Tuple(es) |> DHExp.fresh)); | (_, Unknown(_)) => + /* Record sample if this type is probed */ + let* () = record_type_probe(~targets, t, e); let+ e = recur(e); Some(e); | (Cons(d1, d2), List(ty)) => diff --git a/test/evaluator/Test_Evaluator_Probes.re b/test/evaluator/Test_Evaluator_Probes.re index 37488e54ff..1b3305c58f 100644 --- a/test/evaluator/Test_Evaluator_Probes.re +++ b/test/evaluator/Test_Evaluator_Probes.re @@ -821,7 +821,7 @@ in f(1)|}, */ probe_line_test( - "Probe on function application with ascription", + "Probe on type in ascription", {|3 : ^^probe(?)|}, [(0, ["3"])], ), From aa266139e960e964473a19130f8988150793bc9c Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala Date: Tue, 3 Feb 2026 10:10:21 -0500 Subject: [PATCH 52/65] Record type samples during ascription transitions --- src/language/dynamics/transition/Ascriptions.re | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/language/dynamics/transition/Ascriptions.re b/src/language/dynamics/transition/Ascriptions.re index 6a56796255..d8e69289dd 100644 --- a/src/language/dynamics/transition/Ascriptions.re +++ b/src/language/dynamics/transition/Ascriptions.re @@ -64,6 +64,8 @@ let rec transition = }; switch (DHExp.term_of(d)) { | Asc(e, t) => + /* Record sample if this type is probed */ + let* () = record_type_probe(~targets, t, e); switch (DHExp.term_of(e), Typ.term_of(Typ.unroll(t))) { | (Asc(e, t'), t) // This is only necessary because sometimes we add two ascriptions and aren't marking it as a non-value @@ -125,8 +127,6 @@ let rec transition = |> ClosureWriter.sequence; Some(IdTagged.fast_copy(DHExp.rep_id(e), Tuple(es) |> DHExp.fresh)); | (_, Unknown(_)) => - /* Record sample if this type is probed */ - let* () = record_type_probe(~targets, t, e); let+ e = recur(e); Some(e); | (Cons(d1, d2), List(ty)) => @@ -313,7 +313,7 @@ let rec transition = | (Cons(_), _) | (Constructor(_), _) | (ProofObject(_), _) => ClosureWriter.return(None) - } + }; | _ => ClosureWriter.return(None) }; }; From f6d39754551fcecb6dd137448cfe74d9d674ed71 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Tue, 3 Feb 2026 16:13:15 +0000 Subject: [PATCH 53/65] Fix type projector mode switching Failure(nth) exception Replace List.nth with List.nth_opt in move_cursor function to safely handle out-of-bounds list access when navigating probe samples. This fixes the Failure(nth) exception that occurred when trying to toggle the type projector modes. Fixes #2097 Co-authored-by: andrew blinn --- src/haz3lcore/projectors/implementations/ProbeProj.re | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/haz3lcore/projectors/implementations/ProbeProj.re b/src/haz3lcore/projectors/implementations/ProbeProj.re index d6d99d3649..2ca02a762b 100644 --- a/src/haz3lcore/projectors/implementations/ProbeProj.re +++ b/src/haz3lcore/projectors/implementations/ProbeProj.re @@ -833,12 +833,9 @@ let move_cursor = /* Cursor would be outside window, reset to next visible sample */ | Some(idx) => let next_idx_maybe = idx - offset; - if (next_idx_maybe >= 0 && next_idx_maybe < List.length(samples)) { - parent( - SampleCursor(Capture(List.nth(samples, next_idx_maybe), ap_id)), - ); - } else { - Effect.Ignore; + switch (List.nth_opt(samples, next_idx_maybe)) { + | Some(sample) => parent(SampleCursor(Capture(sample, ap_id))) + | None => Effect.Ignore }; | _ => Effect.Ignore }; From 7f821c9edc9750beef30c81644c737e9b4bd79b3 Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala Date: Tue, 3 Feb 2026 12:20:53 -0500 Subject: [PATCH 54/65] Use refractor id instead of projector in set model --- src/haz3lcore/projectors/ProjectorPerform.re | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/haz3lcore/projectors/ProjectorPerform.re b/src/haz3lcore/projectors/ProjectorPerform.re index fdefdf73d8..d56ace881a 100644 --- a/src/haz3lcore/projectors/ProjectorPerform.re +++ b/src/haz3lcore/projectors/ProjectorPerform.re @@ -195,18 +195,14 @@ let go = if (ProjectorCore.Kind.is_refractor(kind)) { Zipper.update_manuals( map => - ListUtil.assoc_update( - projector_idx_to_id(idx), - fun - | Some(entry: Refractors.entry) => - Some( - Refractors.{ - kind: entry.kind, - model: new_model, - }, - ) - | None => None, - map, + ListUtil.update_nth(idx, map, ((id, entry: Refractors.entry)) => + ( + id, + Refractors.{ + kind: entry.kind, + model: new_model, + }, + ) ), z, ); From d8b16b2dc3a04384c189c118d56056ad51d8c1dc Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala Date: Tue, 3 Feb 2026 12:25:24 -0500 Subject: [PATCH 55/65] Revert unnecessary changes --- src/haz3lcore/lang/MakeTerm.re | 6 +----- src/haz3lcore/pretty/ExpToSegment.re | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/haz3lcore/lang/MakeTerm.re b/src/haz3lcore/lang/MakeTerm.re index 9d939aba23..1baaaaf750 100644 --- a/src/haz3lcore/lang/MakeTerm.re +++ b/src/haz3lcore/lang/MakeTerm.re @@ -721,7 +721,7 @@ and typ_term: unsorted => (Typ.term, list(Id.t)) = { fun | Op(tiles) as tm => switch (tiles) { - | ([(id, tile)], []) => + | ([(_id, tile)], []) => ret( switch (tile) { | ([t], []) when Token.is_empty_tuple(t) => Prod([]) @@ -737,10 +737,6 @@ and typ_term: unsorted => (Typ.term, list(Id.t)) = { | ([t], []) when Token.is_quoted_label(t) => Label(Token.sub(t, 1, Token.length(t) - 2)) | (["(", ")"], [Typ(body)]) => Parens(body) - // TODO - // | (label, [Typ(body)]) when is_probe_wrap(label) => - // let should = should_instrument(id); - // should ? Probe(body, Probe.empty) : body.term; | (["PROJ_WRAP", "PROJ_WRAP"], [Typ(body)]) => body.term | (["[", "]"], [Typ(body)]) => List(body) | ([t], []) when is_hole_label(t) => hole(tm) diff --git a/src/haz3lcore/pretty/ExpToSegment.re b/src/haz3lcore/pretty/ExpToSegment.re index 3a00b43d30..8e35906b04 100644 --- a/src/haz3lcore/pretty/ExpToSegment.re +++ b/src/haz3lcore/pretty/ExpToSegment.re @@ -190,7 +190,7 @@ let external_precedence_pat = (dp: Pat.t) => | MultiHole(_) => Precedence.min }; -let rec external_precedence_typ = (tp: Typ.t) => +let external_precedence_typ = (tp: Typ.t) => switch (Typ.term_of(tp)) { // Indivisible forms never need parentheses around them | Unknown(Hole(Invalid(_))) From 4e6c1ae0bd47dfb9851e1c2b57f21c243f1747a5 Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala Date: Tue, 3 Feb 2026 13:55:09 -0500 Subject: [PATCH 56/65] Add type probe tests --- test/evaluator/Test_Evaluator_Probes.re | 90 +++++++++++++++++++------ 1 file changed, 70 insertions(+), 20 deletions(-) diff --git a/test/evaluator/Test_Evaluator_Probes.re b/test/evaluator/Test_Evaluator_Probes.re index 1b3305c58f..7ebfb0c115 100644 --- a/test/evaluator/Test_Evaluator_Probes.re +++ b/test/evaluator/Test_Evaluator_Probes.re @@ -800,31 +800,80 @@ in f(1)|}, {|^^probe(1; 2) : Int|}, [(0, ["2"])], ), - // TODO - /* test_case("Probe around unknown type", `Quick, () => { - PGrammar.( - probe_test( - {|3 : PROBE(?)|}, - Exp.( - asc( - int(3), - Typ.probe( - ~ann=[probed_value(Atom(Int(Bigint.of_int(3))))], - Typ.unknown(Internal), - {refs: []}, - ), - ) - ), - ) - ) - }), +]; - */ +let type_probe_tests = [ + /* Basic type probes in expression ascriptions */ probe_line_test( - "Probe on type in ascription", + "Probe on unknown type in ascription", {|3 : ^^probe(?)|}, [(0, ["3"])], ), + probe_line_test( + "Probe on Int type in ascription", + {|42 : ^^probe(Int)|}, + [(0, ["42"])], + ), + probe_line_test( + "Probe on list type in ascription", + {|[1, 2, 3] : ^^probe([Int])|}, + [(0, ["[1, 2, 3]"])], + ), + probe_line_test( + "Probe on tuple type in ascription", + {|(1, "a") : ^^probe((Int, String))|}, + [(0, ["(1, \"a\")"])], + ), + probe_line_test( + "Probe on labeled tuple type in ascription", + {|(1, "hi") : ^^probe((a=Int, b=String))|}, + [(0, ["(a=1, b=\"hi\")"])], + ), + /* Type probes in pattern ascriptions */ + probe_line_test( + "Probe on type in pattern ascription", + {|let x : ^^probe(Int) = 5 in x|}, + [(0, ["5"])], + ), + probe_line_test( + "Probe on labeled tuple type in pattern ascription", + {|let x : ^^probe((a=Int, b=String)) = (1, "hi") in x|}, + [(0, ["(a=1, b=\"hi\")"])], + ), + /* Type probes with type aliases */ + probe_line_test( + "Probe on type alias in expression ascription", + {|type MyInt = Int in 5 : ^^probe(MyInt)|}, + [(0, ["5"])], + ), + probe_line_test( + "Probe on type alias in pattern ascription", + {|type MyInt = Int in let x : ^^probe(MyInt) = 5 in x|}, + [(0, ["5"])], + ), + /* Probe on sum type definition */ + probe_line_test( + "Probe on sum type definition", + {|type T = ^^probe(A + B(Int)) in A|}, + [(0, ["A"])], + ), + /* Probe on constructor payload type */ + probe_line_test( + "Probe on constructor payload type", + {|type T = +A(^^probe(Int)) in A(1)|}, + [(0, ["1"])], + ), + probe_line_test( + "Probe on constructor payload type with computation", + {|type T = +A(^^probe(Int)) in A(1 + 2)|}, + [(0, ["3"])], + ), + /* Polymorphic type application - note: may hit TypFun evaluation bug */ + probe_line_test( + "Probe on type in polymorphic application", + {|let id = typfun T -> fun (x : T) -> x in id@<^^probe(Int)>(42)|}, + [(0, ["42"])], + ), ]; let tests = [ @@ -835,4 +884,5 @@ let tests = [ ("Evaluator.Probes.Nested", nested_probe_tests), ("Evaluator.Probes.Recursion", recursion_tests), ("Evaluator.Probes.Ascription", ascription_tests), + ("Evaluator.Probes.Types", type_probe_tests), ]; From ee65e54a2ae8393d2e44d00ebd1c8005ad9210dc Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala Date: Tue, 3 Feb 2026 14:02:39 -0500 Subject: [PATCH 57/65] Preserve Parens ID during normalization for accurate type probes --- src/language/term/Typ.re | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/language/term/Typ.re b/src/language/term/Typ.re index 692f248a8a..aeea3282e2 100644 --- a/src/language/term/Typ.re +++ b/src/language/term/Typ.re @@ -600,7 +600,12 @@ let rec normalize = (~rec_counter=0, ctx: Ctx.t, ty: t): t => { | Atom(_) | ExplicitNonlabel | Label(_) => ty - | Parens(t) => normalize(ctx, t) + | Parens(t) => + /* Preserve the Parens ID on the normalized inner type. + This is critical for type probes: when a probe is attached to parens + like ^^probe((Int, String)), the probe ID is on the Parens wrapper. + We need to copy that ID onto the result so record_type_probe can find it. */ + IdTagged.fast_copy(rep_id(ty), normalize(ctx, t)) | List(t) => List(normalize(ctx, t)) |> rewrap | Arrow(t1, t2) => Arrow(normalize(ctx, t1), normalize(ctx, t2)) |> rewrap From 94ab61a06432d2a0397b623de204319a4d749eb1 Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala Date: Tue, 3 Feb 2026 14:31:22 -0500 Subject: [PATCH 58/65] Type probe fixes --- .../dynamics/transition/Ascriptions.re | 11 ++++++++-- .../dynamics/transition/PatternMatch.re | 20 +++++++++++++------ test/evaluator/Test_Evaluator_Probes.re | 8 ++++---- 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/language/dynamics/transition/Ascriptions.re b/src/language/dynamics/transition/Ascriptions.re index d8e69289dd..19d488a278 100644 --- a/src/language/dynamics/transition/Ascriptions.re +++ b/src/language/dynamics/transition/Ascriptions.re @@ -64,8 +64,15 @@ let rec transition = }; switch (DHExp.term_of(d)) { | Asc(e, t) => - /* Record sample if this type is probed */ - let* () = record_type_probe(~targets, t, e); + /* Record sample if this type is probed. + Skip recording if the inner expression is Parens, since we'll record again + when the ascription is pushed inside and parens are removed. This prevents + duplicate samples for expressions like (1, "a") : ^^probe((Int, String)). */ + let* () = + switch (DHExp.term_of(e)) { + | Parens(_) => ClosureWriter.return() + | _ => record_type_probe(~targets, t, e) + }; switch (DHExp.term_of(e), Typ.term_of(Typ.unroll(t))) { | (Asc(e, t'), t) // This is only necessary because sometimes we add two ascriptions and aren't marking it as a non-value diff --git a/src/language/dynamics/transition/PatternMatch.re b/src/language/dynamics/transition/PatternMatch.re index 5e5aff980f..44b13a77de 100644 --- a/src/language/dynamics/transition/PatternMatch.re +++ b/src/language/dynamics/transition/PatternMatch.re @@ -19,10 +19,13 @@ let combine_result = (r1: match_result, r2: match_result): match_result => * Collected during pattern matching when patterns are targeted. */ type sample_closures = list((Sample.call_stack, int, int) => Sample.t); -/* Core pattern matching logic - just a switch on pattern structure */ +/* Core pattern matching logic - just a switch on pattern structure. + sample_closures is optional: when provided, type probe closures from + pattern ascriptions (Asc) are captured. */ let match_pattern = ( ~targets: Sample.targets, + ~sample_closures: option(ref(sample_closures))=?, recur: (Pat.t, DHExp.t) => match_result, dp: Pat.t, d: DHExp.t, @@ -73,9 +76,13 @@ let match_pattern = List.map2(recur, ps, ds) |> List.fold_left(combine_result, Matches([])); | Parens(p) => recur(p, d) | Asc(p, t1) => - // TODO Capture closures - let (_closures, exp) = + let (closures, exp) = Ascriptions.transition_multiple(~targets, Asc(d, t1) |> DHExp.fresh); + /* Capture type probe closures from the pattern ascription */ + switch (sample_closures) { + | Some(ref_closures) => ref_closures := closures @ ref_closures^ + | None => () + }; recur(p, exp); }; @@ -117,13 +124,14 @@ let rec matches_inner = d: DHExp.t, ) : match_result => { - // TODO Record closures - let (_closures, d) = Ascriptions.transition_multiple(~targets, d); + // Capture type probe closures from value ascriptions + let (closures, d) = Ascriptions.transition_multiple(~targets, d); + sample_closures := closures @ sample_closures^; let pat_id = Pat.rep_id(dp); let maybe_spec = Id.Map.find_opt(pat_id, targets); let recur = matches_inner(targets, sample_closures); - let result = match_pattern(~targets, recur, dp, d); + let result = match_pattern(~targets, ~sample_closures, recur, dp, d); record_sample(sample_closures, pat_id, maybe_spec, d, result); result; }; diff --git a/test/evaluator/Test_Evaluator_Probes.re b/test/evaluator/Test_Evaluator_Probes.re index 7ebfb0c115..58b4258d60 100644 --- a/test/evaluator/Test_Evaluator_Probes.re +++ b/test/evaluator/Test_Evaluator_Probes.re @@ -854,19 +854,19 @@ let type_probe_tests = [ /* Probe on sum type definition */ probe_line_test( "Probe on sum type definition", - {|type T = ^^probe(A + B(Int)) in A|}, + {|type T = ^^probe(A + B(Int)) in A : T|}, [(0, ["A"])], ), /* Probe on constructor payload type */ probe_line_test( "Probe on constructor payload type", - {|type T = +A(^^probe(Int)) in A(1)|}, + {|type T = +A(^^probe(Int)) in A(1) : T|}, [(0, ["1"])], ), probe_line_test( "Probe on constructor payload type with computation", - {|type T = +A(^^probe(Int)) in A(1 + 2)|}, - [(0, ["3"])], + {|type T = +A(^^probe(Int)) in A(1 + 2) : T|}, + [(0, ["1 + 2"])] // Indet because we reduce the cast to int on the plus ), /* Polymorphic type application - note: may hit TypFun evaluation bug */ probe_line_test( From 5c4fcb2f33d50f1766ece04830754fc17cddae1a Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala Date: Wed, 4 Feb 2026 10:28:04 -0500 Subject: [PATCH 59/65] Rip out probes on types --- src/haz3lcore/ProbePerform.re | 8 +- .../projectors/implementations/ProbeProj.re | 22 +- .../projectors/implementations/TypeProj.re | 1 - .../dynamics/transition/Ascriptions.re | 291 +++++++----------- .../dynamics/transition/PatternMatch.re | 27 +- .../dynamics/transition/Transition.re | 16 +- src/language/dynamics/transition/Unboxing.re | 3 +- src/language/statics/Info.re | 4 +- src/language/term/Typ.re | 13 +- src/menhirParser/Interface.re | 12 +- src/web/app/editors/code/ContextMenu.re | 4 +- test/evaluator/Test_Evaluator_Probes.re | 75 ----- 12 files changed, 153 insertions(+), 323 deletions(-) diff --git a/src/haz3lcore/ProbePerform.re b/src/haz3lcore/ProbePerform.re index 7305b4c61b..8432287374 100644 --- a/src/haz3lcore/ProbePerform.re +++ b/src/haz3lcore/ProbePerform.re @@ -87,13 +87,12 @@ let rec target_subterm_ids = (id: Id.t, info_map: Statics.Map.t) => ] | _ => [id] } - /* Filter out terms that can't meaningfully be probed (type patterns, labels, etc.) */ + /* Filter out terms that can't meaningfully be probed */ | info when !Info.is_typable_term(info) => [] /* Default: use rep_id for expressions and patterns to handle multi-tile forms (tuples, list literals, case expressions) where non-representative tile IDs would otherwise cause probe_map/evaluator ID mismatch */ | Some(InfoExp({term, _})) => [IdTagged.rep_id(term)] - | Some(InfoTyp({term, _})) => [IdTagged.rep_id(term)] | Some(InfoPat({term, _})) => [Pat.rep_id(term)] | _ => [id] }; @@ -530,10 +529,9 @@ let rm_probes_in_selection = ); }; -/* Check if type annotation is allowed for the given id. - Uses target_subterm_ids to support types (which redirect to their parent expressions). */ +/* Check if type annotation is allowed for the given id. */ let can_statics = (id: Id.t, info_map: Statics.Map.t): bool => - target_subterm_ids(id, info_map) != []; + Info.is_typable_term(Statics.Map.lookup(id, info_map)); /* Toggle type annotation on the indicated term. Unlike probes, type annotations don't support auto mode or pins. */ diff --git a/src/haz3lcore/projectors/implementations/ProbeProj.re b/src/haz3lcore/projectors/implementations/ProbeProj.re index 2ca02a762b..e4af2dce9b 100644 --- a/src/haz3lcore/projectors/implementations/ProbeProj.re +++ b/src/haz3lcore/projectors/implementations/ProbeProj.re @@ -833,9 +833,12 @@ let move_cursor = /* Cursor would be outside window, reset to next visible sample */ | Some(idx) => let next_idx_maybe = idx - offset; - switch (List.nth_opt(samples, next_idx_maybe)) { - | Some(sample) => parent(SampleCursor(Capture(sample, ap_id))) - | None => Effect.Ignore + if (next_idx_maybe >= 0 && next_idx_maybe < List.length(samples)) { + parent( + SampleCursor(Capture(List.nth(samples, next_idx_maybe), ap_id)), + ); + } else { + Effect.Ignore; }; | _ => Effect.Ignore }; @@ -1170,7 +1173,6 @@ module M: Projector = { | Exp(_) | Pat(_) => Some() | Any(_) => Some() /* Grout don't have sorts rn */ - | Typ(_) => Some() | _ => None }; @@ -1189,16 +1191,8 @@ module M: Projector = { let view = ({info, local, parent, view_seg, _}: View.args(model, action)) => { let settings = Settings.s^; /* Wrap view_seg to fix single_line=true for all probe displays */ - let view_seg_single_line = - (~background=?, ~text_only: option(bool)=?, sort, segment) => - view_seg( - ~single_line=true, - ~background?, - ~text_only?, - ~is_dynamic=?None, - sort, - segment, - ); + let view_seg_single_line = (~background=?, ~text_only=?, sort, segment) => + view_seg(~single_line=true, ~background?, ~text_only?, sort, segment); View.{ inline: Node.div([]), overlay: Some(overlay_view(info)), diff --git a/src/haz3lcore/projectors/implementations/TypeProj.re b/src/haz3lcore/projectors/implementations/TypeProj.re index cb4ab64c98..ac91ab1919 100644 --- a/src/haz3lcore/projectors/implementations/TypeProj.re +++ b/src/haz3lcore/projectors/implementations/TypeProj.re @@ -15,7 +15,6 @@ let self_ty = (info: option(Info.t)): option(Typ.t) => switch (info) { | Some(InfoExp({self, _})) => Self.typ_of_exp(self) | Some(InfoPat({self, _})) => Self.typ_of_pat(self) - | Some(InfoTyp({term, _})) => Some(term) | _ => None }; diff --git a/src/language/dynamics/transition/Ascriptions.re b/src/language/dynamics/transition/Ascriptions.re index 19d488a278..5d0d184523 100644 --- a/src/language/dynamics/transition/Ascriptions.re +++ b/src/language/dynamics/transition/Ascriptions.re @@ -17,62 +17,15 @@ either by returning `Some(e)` directly, or by using `IdTagged.fast_copy(DHExp.rep_id(e), ...)` when constructing a new expression structure. */ -type closure_closures = list((Sample.call_stack, int, int) => Sample.t); -module ClosureWriter = - Util.WriterMonad.Make({ - type t = closure_closures; - let empty = []; - let append = (@); - }); - -/* Record a sample for a probed type. - * When a type has a probe on it (i.e., its ID is in targets), and we're - * processing an ascription, we record the value being ascribed as a sample - * for that type's probe. */ -let record_type_probe = - (~targets: Sample.targets, typ: Typ.t, value: DHExp.t) - : ClosureWriter.t(unit) => { - let typ_id = Typ.rep_id(typ); - switch (Id.Map.find_opt(typ_id, targets)) { - | Some(spec) => - ClosureWriter.tell([ - (call_stack: Sample.call_stack, step_start: int, step_end: int) => - Sample.mk( - ~step_start, - ~step_end, - typ_id, - value, - Environment.empty, - call_stack, - spec, - ), - ]) - | None => ClosureWriter.return() - }; -}; - -let rec transition = - (~recursive=false, ~targets: Sample.targets, d: DHExp.t) - : ClosureWriter.t(option(DHExp.t)) => { - open ClosureWriter.Syntax; - let recur = (d: DHExp.t): ClosureWriter.t(DHExp.t) => +let rec transition = (~recursive=false, d: DHExp.t): option(DHExp.t) => { + let recur = (d: DHExp.t): DHExp.t => if (recursive) { - let+ d' = transition(~recursive, ~targets, d); - Option.value(~default=d, d'); + transition(~recursive, d) |> Option.value(~default=d); } else { - ClosureWriter.return(d); + d; }; switch (DHExp.term_of(d)) { | Asc(e, t) => - /* Record sample if this type is probed. - Skip recording if the inner expression is Parens, since we'll record again - when the ascription is pushed inside and parens are removed. This prevents - duplicate samples for expressions like (1, "a") : ^^probe((Int, String)). */ - let* () = - switch (DHExp.term_of(e)) { - | Parens(_) => ClosureWriter.return() - | _ => record_type_probe(~targets, t, e) - }; switch (DHExp.term_of(e), Typ.term_of(Typ.unroll(t))) { | (Asc(e, t'), t) // This is only necessary because sometimes we add two ascriptions and aren't marking it as a non-value @@ -85,130 +38,125 @@ let rec transition = switch ( Typ.meet(Ctx.empty, Typ.unroll(t |> Typ.temp), Typ.unroll(t')) ) { - | Some(t) => - let+ d' = recur(Asc(e, t) |> DHExp.fresh); - Some(d'); - | None => ClosureWriter.return(None) //TODO This is an impossible case since we checked consistency + | Some(t) => Some(recur(Asc(e, t) |> DHExp.fresh)) + | None => None //TODO This is an impossible case since we checked consistency } | (e, Parens(t)) => // This is an impossible case since types should be normalized before coming to transitions - transition( - ~recursive, - ~targets, - Asc(e |> DHExp.fresh, t) |> DHExp.fresh, - ) + transition(~recursive, Asc(e |> DHExp.fresh, t) |> DHExp.fresh) | (Closure(ce, d), t) => - let+ d' = - transition( - ~recursive, - ~targets, - Asc(d, t |> Typ.fresh) |> DHExp.fresh, - ); - Option.map(d => Closure(ce, d) |> DHExp.fresh, d'); + transition(~recursive, Asc(d, t |> Typ.fresh) |> DHExp.fresh) + |> Option.map(d => Closure(ce, d) |> DHExp.fresh) | (Fun(p, body, closure_ty, name), Arrow(t1, t2)) => - ClosureWriter.return( - Some( - IdTagged.fast_copy( - DHExp.rep_id(e), - IdTagged.FreshGrammar.( - Exp.(fn(Pat.(asc(p, t1)), asc(body, t2), closure_ty, name)) - ), + Some( + IdTagged.fast_copy( + DHExp.rep_id(e), + IdTagged.FreshGrammar.( + Exp.(fn(Pat.(asc(p, t1)), asc(body, t2), closure_ty, name)) ), ), ) | (TupLabel({term: ExplicitNonlabel, _}, inner), _) => - let+ d = recur(Asc(inner, t) |> DHExp.fresh); - Some(d); + Some(recur(Asc(inner, t) |> DHExp.fresh)) | (TupLabel(l, inner), TupLabel(_l2, inner_ty)) => - let+ inner = recur(Asc(inner, inner_ty) |> DHExp.fresh); // TODO Figure out what to do if the labels don't match Some( IdTagged.fast_copy( DHExp.rep_id(e), - TupLabel(l, inner) |> DHExp.fresh, + TupLabel(l, recur(Asc(inner, inner_ty) |> DHExp.fresh)) + |> DHExp.fresh, ), - ); + ) | (Tuple(es), Prod(tys)) when List.length(es) == List.length(tys) => - let+ es = - List.map2((e, ty) => {recur(Asc(e, ty) |> DHExp.fresh)}, es, tys) - |> ClosureWriter.sequence; - Some(IdTagged.fast_copy(DHExp.rep_id(e), Tuple(es) |> DHExp.fresh)); - | (_, Unknown(_)) => - let+ e = recur(e); - Some(e); - | (Cons(d1, d2), List(ty)) => - let* d1 = recur(Asc(d1, ty) |> DHExp.fresh); - let+ d2 = recur(Asc(d2, t) |> DHExp.fresh); Some( - IdTagged.fast_copy(DHExp.rep_id(e), Cons(d1, d2) |> DHExp.fresh), - ); - | (Atom(value), Atom(typ)) => - ClosureWriter.return( - switch (value, typ) { - | (Int(_), Int) - | (String(_), String) - | (Nat(_), Nat) - | (Float(_), Float) - | (SInt(_), SInt) - | (Bool(_), Bool) => Some(e) - | (Int(_), _) - | (String(_), _) - | (Nat(_), _) - | (Float(_), _) - | (SInt(_), _) - | (Bool(_), _) => None - }, + IdTagged.fast_copy( + DHExp.rep_id(e), + Tuple( + List.map2( + (e, ty) => recur(Asc(e, ty) |> DHExp.fresh), + es, + tys, + ), + ) + |> DHExp.fresh, + ), ) + | (_, Unknown(_)) => Some(e) + | (Atom(value), Atom(typ)) => + switch (value, typ) { + | (Int(_), Int) + | (String(_), String) + | (Nat(_), Nat) + | (Float(_), Float) + | (SInt(_), SInt) + | (Bool(_), Bool) => Some(e) + | (Int(_), _) + | (String(_), _) + | (Nat(_), _) + | (Float(_), _) + | (SInt(_), _) + | (Bool(_), _) => None + } | (ListLit(ds), List(ty)) => - let+ ds = - List.map(d => recur(Asc(d, ty) |> DHExp.fresh), ds) - |> ClosureWriter.sequence; - Some( - IdTagged.fast_copy(DHExp.rep_id(e), ListLit(ds) |> DHExp.fresh), - ); - + IdTagged.fast_copy( + DHExp.rep_id(e), + ListLit(List.map(d => recur(Asc(d, ty) |> DHExp.fresh), ds)) + |> DHExp.fresh, + ), + ) + | (Cons(d1, d2), List(ty)) => + Some( + IdTagged.fast_copy( + DHExp.rep_id(e), + Cons( + recur(Asc(d1, ty) |> DHExp.fresh), + recur(Asc(d2, t) |> DHExp.fresh), + ) + |> DHExp.fresh, + ), + ) | (TypFun(tp, body, name), Poly(tp', t')) => let new_ty: Typ.t = switch (TPat.tyvar_of_utpat(tp)) { | Some(tyvar) => Var(tyvar) |> Typ.temp | None => Unknown(Internal) |> Typ.temp }; - - let+ body' = - recur(Asc(body, Typ.subst(new_ty, tp', t')) |> DHExp.fresh); Some( IdTagged.fast_copy( DHExp.rep_id(e), - TypFun(tp, body', name) |> DHExp.fresh, + TypFun( + tp, + recur(Asc(body, Typ.subst(new_ty, tp', t')) |> DHExp.fresh), + name, + ) + |> DHExp.fresh, ), ); - | (If(cond, e1, e2), t) => - let* cond = recur(cond); - let* e1 = recur(Asc(e1, t |> Typ.temp) |> DHExp.fresh); - let+ e2 = recur(Asc(e2, t |> Typ.temp) |> DHExp.fresh); Some( IdTagged.fast_copy( DHExp.rep_id(e), - If(cond, e1, e2) |> DHExp.fresh, + If( + recur(cond), + recur(Asc(e1, t |> Typ.temp) |> DHExp.fresh), + recur(Asc(e2, t |> Typ.temp) |> DHExp.fresh), + ) + |> DHExp.fresh, ), - ); + ) | (Match(scrut, rules), t) => - ClosureWriter.return( - Some( - IdTagged.fast_copy( - DHExp.rep_id(e), - Match( - scrut, - List.map( - ((p, body)) => - (p, Asc(body, t |> Typ.temp) |> DHExp.fresh), - rules, - ), - ) - |> DHExp.fresh, - ), + Some( + IdTagged.fast_copy( + DHExp.rep_id(e), + Match( + scrut, + List.map( + ((p, body)) => (p, Asc(body, t |> Typ.temp) |> DHExp.fresh), + rules, + ), + ) + |> DHExp.fresh, ), ) | ( @@ -224,23 +172,25 @@ let rec transition = let entry = ConstructorMap.get_entry(c, m); switch (entry) { | Some(Some(t')) => - let+ e = recur(Asc(payload, t') |> DHExp.fresh); - Some(Ap(Forward, con, e) |> DHExp.fresh); + Some( + Ap(Forward, con, recur(Asc(payload, t') |> DHExp.fresh)) + |> DHExp.fresh, + ) | Some(None) - | None => ClosureWriter.return(None) + | None => None }; | (Constructor(_, Some(Some(t))), t') when Typ.is_consistent(Ctx.empty, Typ.unroll(t), t' |> Typ.temp) => - ClosureWriter.return(Some(e)) + Some(e) | (ProofObject(e1), ProofOf(e2)) when Exp.fast_equal(e1, e2) => - ClosureWriter.return(Some(ProofObject(e1) |> DHExp.fresh)) - | (Test(_), Prod([])) => ClosureWriter.return(Some(e)) + Some(ProofObject(e1) |> DHExp.fresh) + | (Test(_), Prod([])) => Some(e) // These are non-value cases we're handling to process ascriptions as early as possible | (BinOp(bin_op, _, _), _) => switch (Operators.semantics_of_bin_op(bin_op)) { | DefinedPoly(Equals | NotEquals) when Typ.is_consistent(Ctx.empty, t, Atom(Bool) |> Typ.temp) => - ClosureWriter.return(Some(e)) + Some(e) | Defined(_, _, ty_out, _) when Typ.is_consistent( @@ -248,10 +198,10 @@ let rec transition = t, Atom(Atom.cls_of_kind(ty_out)) |> Typ.temp, ) => - ClosureWriter.return(Some(e)) + Some(e) | Undefined(_) | DefinedPoly(_) - | Defined(_) => ClosureWriter.return(None) + | Defined(_) => None } | (UnOp(un_op, _), _) => switch (Operators.semantics_of_un_op(un_op)) { @@ -262,26 +212,24 @@ let rec transition = t, Atom(Atom.cls_of_kind(ty_out)) |> Typ.temp, ) => - ClosureWriter.return(Some(e)) + Some(e) | Undefined(_) - | Defined(_) => ClosureWriter.return(None) + | Defined(_) => None } | (ListConcat(d1, d2), List(_)) => - let* e1 = recur(Asc(d1, t) |> DHExp.fresh); - let+ e2 = recur(Asc(d2, t) |> DHExp.fresh); - Some(ListConcat(e1, e2) |> DHExp.fresh); - | (Let(p, e1, e2), _) => - ClosureWriter.return( - Some(Let(p, e1, Asc(e2, t) |> DHExp.fresh) |> DHExp.fresh), + Some( + ListConcat( + recur(Asc(d1, t) |> DHExp.fresh), + recur(Asc(d2, t) |> DHExp.fresh), + ) + |> DHExp.fresh, ) + | (Let(p, e1, e2), _) => + Some(Let(p, e1, Asc(e2, t) |> DHExp.fresh) |> DHExp.fresh) | (Seq(e1, e2), _) => - ClosureWriter.return( - Some(Seq(e1, Asc(e2, t) |> DHExp.fresh) |> DHExp.fresh), - ) + Some(Seq(e1, Asc(e2, t) |> DHExp.fresh) |> DHExp.fresh) | (Parens(e), _) => - ClosureWriter.return( - Some(Parens(Asc(e, t) |> DHExp.fresh) |> DHExp.fresh), - ) + Some(Parens(Asc(e, t) |> DHExp.fresh) |> DHExp.fresh) // We _could_ do this, but it would be a bit weird | (Use(_), _) // I'm scaredto do Use because the type-directed literals might make this look weird in the stepper | (BuiltinFun(_), _) @@ -291,7 +239,7 @@ let rec transition = | (TyAlias(_), _) | (Theorem(_), _) | (Forall(_), _) - | (Asc(_), _) => ClosureWriter.return(None) + | (Asc(_), _) => None // These are non-value cases we don't want to handle | (EmptyHole, _) | (DynamicErrorHole(_), _) @@ -318,19 +266,16 @@ let rec transition = | (Test(_), _) | (HintedTest(_), _) | (Cons(_), _) - | (Constructor(_), _) - | (ProofObject(_), _) => ClosureWriter.return(None) - }; - | _ => ClosureWriter.return(None) + | (ProofObject(_), _) + | (Constructor(_), _) => None + } + | _ => None }; }; -let rec transition_multiple = - (~targets: Sample.targets, d: DHExp.t): (closure_closures, DHExp.t) => { - switch (transition(~targets, ~recursive=true, d)) { - | (closures, Some(d'')) => - let (c, d) = transition_multiple(~targets, d''); - (closures @ c, d); - | _ => ([], d) +let rec transition_multiple = (d: DHExp.t): DHExp.t => { + switch (transition(~recursive=true, d)) { + | Some(d'') => transition_multiple(d'') + | None => d }; }; diff --git a/src/language/dynamics/transition/PatternMatch.re b/src/language/dynamics/transition/PatternMatch.re index 44b13a77de..94fd8fea5c 100644 --- a/src/language/dynamics/transition/PatternMatch.re +++ b/src/language/dynamics/transition/PatternMatch.re @@ -19,17 +19,9 @@ let combine_result = (r1: match_result, r2: match_result): match_result => * Collected during pattern matching when patterns are targeted. */ type sample_closures = list((Sample.call_stack, int, int) => Sample.t); -/* Core pattern matching logic - just a switch on pattern structure. - sample_closures is optional: when provided, type probe closures from - pattern ascriptions (Asc) are captured. */ +/* Core pattern matching logic - just a switch on pattern structure */ let match_pattern = - ( - ~targets: Sample.targets, - ~sample_closures: option(ref(sample_closures))=?, - recur: (Pat.t, DHExp.t) => match_result, - dp: Pat.t, - d: DHExp.t, - ) + (recur: (Pat.t, DHExp.t) => match_result, dp: Pat.t, d: DHExp.t) : match_result => switch (DHPat.term_of(dp)) { | Invalid(_) @@ -76,14 +68,7 @@ let match_pattern = List.map2(recur, ps, ds) |> List.fold_left(combine_result, Matches([])); | Parens(p) => recur(p, d) | Asc(p, t1) => - let (closures, exp) = - Ascriptions.transition_multiple(~targets, Asc(d, t1) |> DHExp.fresh); - /* Capture type probe closures from the pattern ascription */ - switch (sample_closures) { - | Some(ref_closures) => ref_closures := closures @ ref_closures^ - | None => () - }; - recur(p, exp); + recur(p, Ascriptions.transition_multiple(Asc(d, t1) |> DHExp.fresh)) }; /* Record a sample closure if this pattern is targeted and matched */ @@ -124,14 +109,12 @@ let rec matches_inner = d: DHExp.t, ) : match_result => { - // Capture type probe closures from value ascriptions - let (closures, d) = Ascriptions.transition_multiple(~targets, d); - sample_closures := closures @ sample_closures^; + let d = Ascriptions.transition_multiple(d); let pat_id = Pat.rep_id(dp); let maybe_spec = Id.Map.find_opt(pat_id, targets); let recur = matches_inner(targets, sample_closures); - let result = match_pattern(~targets, ~sample_closures, recur, dp, d); + let result = match_pattern(recur, dp, d); record_sample(sample_closures, pat_id, maybe_spec, d, result); result; }; diff --git a/src/language/dynamics/transition/Transition.re b/src/language/dynamics/transition/Transition.re index 28152195d5..6b788c9e99 100644 --- a/src/language/dynamics/transition/Transition.re +++ b/src/language/dynamics/transition/Transition.re @@ -919,27 +919,27 @@ module Transition = (EV: EV_MODE) => { let.wrap_closure _ = (env, d); Indet; | Asc(d', t) => - switch (Ascriptions.transition(~targets, d)) { - | (closures, Some(d')) => + switch (Ascriptions.transition(d)) { + | Some(d') => let. _ = otherwise(env, d); Step({ expr: d', - side_effects: [RecordPatProbes(closures)], + side_effects: [], kind: Ascription, is_value: false, }); - | (_, None) => + | None => let. _ = otherwise(env, d => Asc(d, t) |> rewrap) and. d' = req_final(req(env), d => Asc(d, t) |> wrap_ctx, d'); - switch (Ascriptions.transition(~targets, Asc(d', t) |> rewrap)) { - | (closures, Some(d)) => + switch (Ascriptions.transition(Asc(d', t) |> rewrap)) { + | Some(d) => Step({ expr: d, - side_effects: [RecordPatProbes(closures)], + side_effects: [], kind: Ascription, is_value: false, }) - | (_, None) => Constructor + | None => Constructor }; } | Undefined => diff --git a/src/language/dynamics/transition/Unboxing.re b/src/language/dynamics/transition/Unboxing.re index a40d311ec9..da1875b104 100644 --- a/src/language/dynamics/transition/Unboxing.re +++ b/src/language/dynamics/transition/Unboxing.re @@ -131,7 +131,8 @@ let rec unbox: type a. (unbox_request(a), DHExp.t) => unboxed(a) = | (LabeledTupleEntries, Tuple(ds)) => let unbox_tup_label = (d: Exp.t): option((option(LabeledTuple.label), Exp.t)) => { - switch (d.term) { + switch (Ascriptions.transition_multiple(d).term) { + // TODO Think about whether we should transition here | TupLabel({term: Label(l), _}, e) => Some((Some(l), e)) | _ => Some((None, d)) }; diff --git a/src/language/statics/Info.re b/src/language/statics/Info.re index 89eebb0bce..11a68a3093 100644 --- a/src/language/statics/Info.re +++ b/src/language/statics/Info.re @@ -409,8 +409,8 @@ let is_typable_term: option(t) => bool = fun | Some(InfoExp({term: {term: Deferral(_) | Label(_) | TyAlias(_), _}, _})) => false - | Some(InfoTPat(_) | Secondary(_)) => false - | Some(InfoExp(_) | InfoPat(_) | InfoTyp(_)) => true + | Some(InfoTyp(_) | InfoTPat(_) | Secondary(_)) => false + | Some(InfoExp(_) | InfoPat(_)) => true | None => false; let exp_co_ctx: exp => CoCtx.t = ({co_ctx, _}) => co_ctx; diff --git a/src/language/term/Typ.re b/src/language/term/Typ.re index aeea3282e2..6ca521ca44 100644 --- a/src/language/term/Typ.re +++ b/src/language/term/Typ.re @@ -141,12 +141,12 @@ let rec is_arrow = (typ: t) => { }; }; -let rec is_atom = (ty: t): bool => +let is_atom = (ty: t): bool => switch (ty.term) { | Atom(_) => true - | Parens(ty) - | TupLabel(_, ty) => is_atom(ty) | ProofOf(_) + | Parens(_) + | TupLabel(_) | Arrow(_) | Unknown(_) | List(_) @@ -600,12 +600,7 @@ let rec normalize = (~rec_counter=0, ctx: Ctx.t, ty: t): t => { | Atom(_) | ExplicitNonlabel | Label(_) => ty - | Parens(t) => - /* Preserve the Parens ID on the normalized inner type. - This is critical for type probes: when a probe is attached to parens - like ^^probe((Int, String)), the probe ID is on the Parens wrapper. - We need to copy that ID onto the result so record_type_probe can find it. */ - IdTagged.fast_copy(rep_id(ty), normalize(ctx, t)) + | Parens(t) => normalize(ctx, t) | List(t) => List(normalize(ctx, t)) |> rewrap | Arrow(t1, t2) => Arrow(normalize(ctx, t1), normalize(ctx, t2)) |> rewrap diff --git a/src/menhirParser/Interface.re b/src/menhirParser/Interface.re index 5f0b5548ba..dfc8cd5f97 100644 --- a/src/menhirParser/Interface.re +++ b/src/menhirParser/Interface.re @@ -19,14 +19,4 @@ let parse = (f, s) => { result; }; -let parse_program = s => { - print_endline("Parsing program: " ++ s); - parse( - lexbuf => { - let t = Parser.program(lexbuf); - print_endline("Token "); - t; - }, - s, - ); -}; +let parse_program = s => parse(Parser.program, s); diff --git a/src/web/app/editors/code/ContextMenu.re b/src/web/app/editors/code/ContextMenu.re index 8f0aeb21a0..b640b02fcc 100644 --- a/src/web/app/editors/code/ContextMenu.re +++ b/src/web/app/editors/code/ContextMenu.re @@ -227,7 +227,7 @@ let manual_probe_data = ) : list(menu_item_data) => switch (ci) { - | Some(InfoExp(_) | InfoPat(_) | InfoTyp(_)) when can_probe => [ + | Some(InfoExp(_) | InfoPat(_)) when can_probe => [ { name: switch (probe_status) { @@ -275,7 +275,7 @@ let type_annotation_data = ) : list(menu_item_data) => switch (ci) { - | Some(InfoExp(_) | InfoPat(_) | InfoTyp(_)) when can_type => [ + | Some(InfoExp(_) | InfoPat(_)) when can_type => [ { name: switch (probe_status) { diff --git a/test/evaluator/Test_Evaluator_Probes.re b/test/evaluator/Test_Evaluator_Probes.re index 58b4258d60..0084a9a809 100644 --- a/test/evaluator/Test_Evaluator_Probes.re +++ b/test/evaluator/Test_Evaluator_Probes.re @@ -802,80 +802,6 @@ in f(1)|}, ), ]; -let type_probe_tests = [ - /* Basic type probes in expression ascriptions */ - probe_line_test( - "Probe on unknown type in ascription", - {|3 : ^^probe(?)|}, - [(0, ["3"])], - ), - probe_line_test( - "Probe on Int type in ascription", - {|42 : ^^probe(Int)|}, - [(0, ["42"])], - ), - probe_line_test( - "Probe on list type in ascription", - {|[1, 2, 3] : ^^probe([Int])|}, - [(0, ["[1, 2, 3]"])], - ), - probe_line_test( - "Probe on tuple type in ascription", - {|(1, "a") : ^^probe((Int, String))|}, - [(0, ["(1, \"a\")"])], - ), - probe_line_test( - "Probe on labeled tuple type in ascription", - {|(1, "hi") : ^^probe((a=Int, b=String))|}, - [(0, ["(a=1, b=\"hi\")"])], - ), - /* Type probes in pattern ascriptions */ - probe_line_test( - "Probe on type in pattern ascription", - {|let x : ^^probe(Int) = 5 in x|}, - [(0, ["5"])], - ), - probe_line_test( - "Probe on labeled tuple type in pattern ascription", - {|let x : ^^probe((a=Int, b=String)) = (1, "hi") in x|}, - [(0, ["(a=1, b=\"hi\")"])], - ), - /* Type probes with type aliases */ - probe_line_test( - "Probe on type alias in expression ascription", - {|type MyInt = Int in 5 : ^^probe(MyInt)|}, - [(0, ["5"])], - ), - probe_line_test( - "Probe on type alias in pattern ascription", - {|type MyInt = Int in let x : ^^probe(MyInt) = 5 in x|}, - [(0, ["5"])], - ), - /* Probe on sum type definition */ - probe_line_test( - "Probe on sum type definition", - {|type T = ^^probe(A + B(Int)) in A : T|}, - [(0, ["A"])], - ), - /* Probe on constructor payload type */ - probe_line_test( - "Probe on constructor payload type", - {|type T = +A(^^probe(Int)) in A(1) : T|}, - [(0, ["1"])], - ), - probe_line_test( - "Probe on constructor payload type with computation", - {|type T = +A(^^probe(Int)) in A(1 + 2) : T|}, - [(0, ["1 + 2"])] // Indet because we reduce the cast to int on the plus - ), - /* Polymorphic type application - note: may hit TypFun evaluation bug */ - probe_line_test( - "Probe on type in polymorphic application", - {|let id = typfun T -> fun (x : T) -> x in id@<^^probe(Int)>(42)|}, - [(0, ["42"])], - ), -]; - let tests = [ ("Evaluator.Probes.Basic", basic_tests), ("Evaluator.Probes.Operators", operator_tests), @@ -884,5 +810,4 @@ let tests = [ ("Evaluator.Probes.Nested", nested_probe_tests), ("Evaluator.Probes.Recursion", recursion_tests), ("Evaluator.Probes.Ascription", ascription_tests), - ("Evaluator.Probes.Types", type_probe_tests), ]; From 5d94433669644da7ed200e5fef9fe34dd4d5b956 Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala Date: Wed, 4 Feb 2026 10:45:02 -0500 Subject: [PATCH 60/65] Remove probe_unknowns from elaborator --- src/CLI/Run.re | 1 - src/haz3lcore/derived/CachedStatics.re | 7 +- src/language/statics/Elaborator.re | 92 ++++----------------- test/Test_Elaboration.re | 5 +- test/evaluator/Test_Evaluator_Prelude.re | 6 +- test/evaluator/Test_Evaluator_Probes.re | 3 +- test/evaluator/Test_Evaluator_Properties.re | 5 +- 7 files changed, 24 insertions(+), 95 deletions(-) diff --git a/src/CLI/Run.re b/src/CLI/Run.re index f10bc3b5cc..d8020bdd21 100644 --- a/src/CLI/Run.re +++ b/src/CLI/Run.re @@ -3,7 +3,6 @@ open Language; let elaborate = (exp: Exp.t): Exp.t => fst( Elaborator.elaborate( - ~probe_unknowns=false, Statics.mk(CoreSettings.on, Builtins.ctx_init(Some(Int)), exp), exp, ), diff --git a/src/haz3lcore/derived/CachedStatics.re b/src/haz3lcore/derived/CachedStatics.re index a6bfdb87ed..26d7a1b42c 100644 --- a/src/haz3lcore/derived/CachedStatics.re +++ b/src/haz3lcore/derived/CachedStatics.re @@ -26,9 +26,8 @@ let empty: t = { let elaborate = Core.Memo.general( - ~cache_size_bound=1000, - (probe_unknowns: bool, info_map: Statics.Map.t, term: Exp.t) => - Elaborator.uexp_elab(~probe_unknowns, info_map, term) + ~cache_size_bound=1000, (info_map: Statics.Map.t, term: Exp.t) => + Elaborator.uexp_elab(info_map, term) ); let dh_err = (error: string): DHExp.t => Var(error) |> DHExp.fresh; @@ -102,7 +101,7 @@ let init_from_term = | _ when !settings.dynamics && !settings.elaborate => dh_err("Dynamics & Elaboration disabled") | _ => - switch (elaborate(false, info_map, term)) { + switch (elaborate(info_map, term)) { | DoesNotElaborate => dh_err("Elaboration returns None") | Elaborates(d, _) => d } diff --git a/src/language/statics/Elaborator.re b/src/language/statics/Elaborator.re index 61eba30917..21e978c6ec 100644 --- a/src/language/statics/Elaborator.re +++ b/src/language/statics/Elaborator.re @@ -2,13 +2,6 @@ A nice property would be that elaboration is idempotent... */ -/* - * probe_unknowns parameter: Added for future use in PRs like https://github.com/hazelgrove/hazel/pull/1988 - * to help with adding dynamic feedback for static marks. When enabled, it will add probes - * around any expression/pattern that is partially unknown. - * Do not remove this parameter even though it's always false currently. - */ - open Util; exception MissingTypeInfo; @@ -30,14 +23,12 @@ let fresh_ascription = (d: Exp.t, t: Typ.t, t': option(Typ.t)) => { ); }; let elaborated_type = - (m: Statics.Map.t, uexp: Exp.t) - : (Typ.t, Typ.t, Self.exp, Ctx.t, CoCtx.t, Exp.t) => { - let (ana_ty, ty, self, ctx, co_ctx, term) = + (m: Statics.Map.t, uexp: Exp.t): (Typ.t, Typ.t, Ctx.t, CoCtx.t, Exp.t) => { + let (ana_ty, ty, ctx, co_ctx, term) = switch (Id.Map.find_opt(Exp.rep_id(uexp), m)) { - | Some(Info.InfoExp({ana, ty, self, ctx, co_ctx, term: new_term, _})) => ( + | Some(Info.InfoExp({ana, ty, ctx, co_ctx, term: new_term, _})) => ( ana, ty, - self, ctx, co_ctx, new_term, @@ -48,7 +39,6 @@ let elaborated_type = ( elab_ty |> Typ.normalize(ctx) |> Typ.all_ids_temp, ana_ty, - self, ctx, co_ctx, term, @@ -56,8 +46,8 @@ let elaborated_type = }; let elaborated_pat_type = - (m: Statics.Map.t, upat: Pat.t): (Typ.t, Typ.t, Ctx.t, Pat.t, Self.pat) => { - let (ana_ty, self_ty, ctx, prev_synswitch, term, label_inference, self) = + (m: Statics.Map.t, upat: Pat.t): (Typ.t, Typ.t, Ctx.t, Pat.t) => { + let (ana_ty, self_ty, ctx, prev_synswitch, term, label_inference) = switch (Id.Map.find_opt(Pat.rep_id(upat), m)) { | Some( Info.InfoPat({ @@ -67,7 +57,6 @@ let elaborated_pat_type = prev_synswitch, term: new_term, label_inference, - self, _, }), ) => ( @@ -77,7 +66,6 @@ let elaborated_pat_type = prev_synswitch, new_term, label_inference, - self, ) | _ => raise(MissingTypeInfo) }; @@ -96,31 +84,16 @@ let elaborated_pat_type = | _ => Typ.match_synswitch(syn_ty, ana_ty) } }; - ( - elab_ty |> Typ.normalize(ctx) |> Typ.all_ids_temp, - ana_ty, - ctx, - term, - self, - ); + (elab_ty |> Typ.normalize(ctx) |> Typ.all_ids_temp, ana_ty, ctx, term); }; let rec elaborate_pattern = - ( - ~probe_unknowns: bool, - m: Statics.Map.t, - upat: Pat.t, - in_container: bool, - ) - : (Pat.t, Typ.t) => { + (m: Statics.Map.t, upat: Pat.t, in_container: bool): (Pat.t, Typ.t) => { // Pulling upat back out of the statics map for statics level singleton tuple autolabeling - let (elaborated_type, ana, ctx, upat, self) = elaborated_pat_type(m, upat); + let (elaborated_type, ana, ctx, upat) = elaborated_pat_type(m, upat); let elaborate_pattern = (~in_container=false, m, upat) => - elaborate_pattern(~probe_unknowns, m, upat, in_container); + elaborate_pattern(m, upat, in_container); - let contains_unknown = - Option.map(t => Typ.count_unknowns(t) > 0, Self.typ_of_pat(self)) - |> Option.value(~default=true); let (term, rewrap) = Pat.unwrap(upat); let dpat = switch (term) { @@ -197,38 +170,19 @@ let rec elaborate_pattern = Constructor(c, Some(t)) |> rewrap; }; - let dpat = - if (probe_unknowns && contains_unknown) { - // switch (dpat) { - // | {term: Probe(_), _} => dpat - // | _ => { - // term: Probe(dpat, Probe.empty), - // annotation: dpat.annotation, - // } - // }; - // TODO - dpat; - } else { - dpat; - }; (dpat, elaborated_type); }; -let rec elaborate = - (~probe_unknowns: bool, m: Statics.Map.t, uexp: Exp.t) - : (DHExp.t, Typ.t) => { - let elaborate = elaborate(~probe_unknowns); - let elaborate_pattern = elaborate_pattern(~probe_unknowns); +let rec elaborate = (m: Statics.Map.t, uexp: Exp.t): (DHExp.t, Typ.t) => { + let elaborate = elaborate; + let elaborate_pattern = elaborate_pattern; // In the case of singleton labeled tuples we update the syntax in Statics. // We store this syntax with the same ID as the original expression and store it on the Info.exp in the Statics.map // We are then pulling this out and using it in place of the actual expression. - let (elaborated_type, ana, self, ctx, co_ctx, statics_pseudo_elaborated) = + let (elaborated_type, ana, ctx, co_ctx, statics_pseudo_elaborated) = elaborated_type(m, uexp); - let contains_unknown = - Option.map(t => Typ.count_unknowns(t) > 0, Self.typ_of_exp(self)) - |> Option.value(~default=true); let (_, rewrap) = Exp.unwrap(uexp); let uexp = rewrap(statics_pseudo_elaborated.term); @@ -503,27 +457,11 @@ let rec elaborate = Match(e', List.combine(ps', es')) |> rewrap; }; - let dhexp = - if (probe_unknowns && contains_unknown) { - // switch (dhexp) { - // | {term: Probe(_), _} => dhexp - // | _ => { - // term: Probe(dhexp, Probe.empty), - // annotation: dhexp.annotation, - // } - // }; - // TODO - dhexp; - } else { - dhexp; - }; (dhexp, elaborated_type); }; -let uexp_elab = - (~probe_unknowns: bool, m: Statics.Map.t, uexp: Exp.t) - : ElaborationResult.t => { - switch (elaborate(~probe_unknowns, m, uexp)) { +let uexp_elab = (m: Statics.Map.t, uexp: Exp.t): ElaborationResult.t => { + switch (elaborate(m, uexp)) { | exception MissingTypeInfo => DoesNotElaborate | (d, ty) => Elaborates(d, ty) }; diff --git a/test/Test_Elaboration.re b/test/Test_Elaboration.re index 386e78dcbf..e698fb0e98 100644 --- a/test/Test_Elaboration.re +++ b/test/Test_Elaboration.re @@ -20,7 +20,6 @@ let dhexp_typ = let mk_map = Statics.mk(CoreSettings.on, Builtins.ctx_init(Some(Int))); let dhexp_of_uexp = u => Elaborator.elaborate( - ~probe_unknowns=false, Statics.mk(CoreSettings.on, Builtins.ctx_init(Some(Int)), u), u, ) @@ -386,7 +385,7 @@ module PlainTests = { let uexp = parse_exp(expression); let statics = mk_map(uexp); Alcotest.skip(); - let _ = Elaborator.elaborate(~probe_unknowns=false, statics, uexp); + let _ = Elaborator.elaborate(statics, uexp); (); } }); @@ -683,7 +682,7 @@ in 1|}, exp => { switch (mk_map(exp)) { | statics => - switch (Elaborator.elaborate(~probe_unknowns=false, statics, exp)) { + switch (Elaborator.elaborate(statics, exp)) { | _ => true | exception (Failure(msg) as e) => switch (msg) { diff --git a/test/evaluator/Test_Evaluator_Prelude.re b/test/evaluator/Test_Evaluator_Prelude.re index 1cdcb3767c..bdd2a7944e 100644 --- a/test/evaluator/Test_Evaluator_Prelude.re +++ b/test/evaluator/Test_Evaluator_Prelude.re @@ -96,7 +96,6 @@ let parse_with_probes = (s: string): (Exp.t, Statics.Map.t, Sample.targets) => { let elaborate = u => Elaborator.elaborate( - ~probe_unknowns=false, Statics.mk(CoreSettings.on, Builtins.ctx_init(Some(Int)), u), u, ) @@ -104,7 +103,7 @@ let elaborate = u => /* Elaborate an expression with existing statics map */ let elaborate_with_info = (info_map, u) => - Elaborator.elaborate(~probe_unknowns=false, info_map, u) |> fst; + Elaborator.elaborate(info_map, u) |> fst; (exp, probes) => ( { @@ -172,8 +171,7 @@ let full_small_step_reduction = let full_preservation_test = (uexp: TermBase.exp_t): unit => { let statics = Statics.mk(CoreSettings.on, Builtins.ctx_init(Some(Int)), uexp); - let (elaborated, ty) = - Elaborator.elaborate(~probe_unknowns=false, statics, uexp); + let (elaborated, ty) = Elaborator.elaborate(statics, uexp); let evaluated = Evaluator.evaluate(~env=Builtins.env_init, elaborated) |> fst; diff --git a/test/evaluator/Test_Evaluator_Probes.re b/test/evaluator/Test_Evaluator_Probes.re index 0084a9a809..da8ce027c4 100644 --- a/test/evaluator/Test_Evaluator_Probes.re +++ b/test/evaluator/Test_Evaluator_Probes.re @@ -85,8 +85,7 @@ let get_samples_by_line = (code: string): IntMap.t(list(string)) => { ); /* Elaborate and evaluate */ - let elaborated = - Elaborator.elaborate(~probe_unknowns=false, info_map, term) |> fst; + let elaborated = Elaborator.elaborate(info_map, term) |> fst; let (_, state) = Evaluator.evaluate(~targets, ~env=Builtins.env_init, elaborated); let probes = EvaluatorState.get_probes(state); diff --git a/test/evaluator/Test_Evaluator_Properties.re b/test/evaluator/Test_Evaluator_Properties.re index bbe1700838..4746bea46f 100644 --- a/test/evaluator/Test_Evaluator_Properties.re +++ b/test/evaluator/Test_Evaluator_Properties.re @@ -11,7 +11,6 @@ let qcheck_evaluator_does_not_crash_test = exp => { switch ( Elaborator.elaborate( - ~probe_unknowns=false, Statics.mk(CoreSettings.on, Builtins.ctx_init(Some(Int)), exp), exp, ) @@ -56,7 +55,6 @@ let qcheck_stepper_confluence = uexp => { switch ( Elaborator.elaborate( - ~probe_unknowns=false, Statics.mk(CoreSettings.on, Builtins.ctx_init(Some(Int)), uexp), uexp, ) @@ -190,8 +188,7 @@ let qcheck_preservation_test = { let statics = Statics.mk(CoreSettings.on, Builtins.ctx_init(Some(Int)), uexp); - let (elaborated, ty) = - Elaborator.elaborate(~probe_unknowns=false, statics, uexp); + let (elaborated, ty) = Elaborator.elaborate(statics, uexp); let stepped = single_step(elaborated); (stepped, ty); } From 9ab851dc20a45188b4813fa73a9f0eaea33f4314 Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala Date: Wed, 4 Feb 2026 10:47:10 -0500 Subject: [PATCH 61/65] Revert extraneous changes --- src/haz3lcore/derived/CachedStatics.re | 5 +---- src/language/statics/Elaborator.re | 9 ++------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/haz3lcore/derived/CachedStatics.re b/src/haz3lcore/derived/CachedStatics.re index 26d7a1b42c..41706e66d9 100644 --- a/src/haz3lcore/derived/CachedStatics.re +++ b/src/haz3lcore/derived/CachedStatics.re @@ -25,10 +25,7 @@ let empty: t = { }; let elaborate = - Core.Memo.general( - ~cache_size_bound=1000, (info_map: Statics.Map.t, term: Exp.t) => - Elaborator.uexp_elab(info_map, term) - ); + Core.Memo.general(~cache_size_bound=1000, Elaborator.uexp_elab); let dh_err = (error: string): DHExp.t => Var(error) |> DHExp.fresh; diff --git a/src/language/statics/Elaborator.re b/src/language/statics/Elaborator.re index 21e978c6ec..fc221e6009 100644 --- a/src/language/statics/Elaborator.re +++ b/src/language/statics/Elaborator.re @@ -24,7 +24,7 @@ let fresh_ascription = (d: Exp.t, t: Typ.t, t': option(Typ.t)) => { }; let elaborated_type = (m: Statics.Map.t, uexp: Exp.t): (Typ.t, Typ.t, Ctx.t, CoCtx.t, Exp.t) => { - let (ana_ty, ty, ctx, co_ctx, term) = + let (ana_ty, self_ty, ctx, co_ctx, term) = switch (Id.Map.find_opt(Exp.rep_id(uexp), m)) { | Some(Info.InfoExp({ana, ty, ctx, co_ctx, term: new_term, _})) => ( ana, @@ -35,7 +35,7 @@ let elaborated_type = ) | _ => raise(MissingTypeInfo) }; - let elab_ty = Typ.match_synswitch(ana_ty, ty); + let elab_ty = Typ.match_synswitch(ana_ty, self_ty); ( elab_ty |> Typ.normalize(ctx) |> Typ.all_ids_temp, ana_ty, @@ -93,7 +93,6 @@ let rec elaborate_pattern = let (elaborated_type, ana, ctx, upat) = elaborated_pat_type(m, upat); let elaborate_pattern = (~in_container=false, m, upat) => elaborate_pattern(m, upat, in_container); - let (term, rewrap) = Pat.unwrap(upat); let dpat = switch (term) { @@ -169,20 +168,16 @@ let rec elaborate_pattern = }; Constructor(c, Some(t)) |> rewrap; }; - (dpat, elaborated_type); }; let rec elaborate = (m: Statics.Map.t, uexp: Exp.t): (DHExp.t, Typ.t) => { - let elaborate = elaborate; - let elaborate_pattern = elaborate_pattern; // In the case of singleton labeled tuples we update the syntax in Statics. // We store this syntax with the same ID as the original expression and store it on the Info.exp in the Statics.map // We are then pulling this out and using it in place of the actual expression. let (elaborated_type, ana, ctx, co_ctx, statics_pseudo_elaborated) = elaborated_type(m, uexp); - let (_, rewrap) = Exp.unwrap(uexp); let uexp = rewrap(statics_pseudo_elaborated.term); From 457534b7d30ab65b5d384aadca72f8b82da713bb Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala Date: Wed, 4 Feb 2026 10:53:07 -0500 Subject: [PATCH 62/65] Remove WriterMonad module --- src/util/Util.re | 1 - src/util/WriterMonad.re | 42 ------------- test/Test_WriterMonad.re | 124 --------------------------------------- test/haz3ltest.re | 1 - 4 files changed, 168 deletions(-) delete mode 100644 src/util/WriterMonad.re delete mode 100644 test/Test_WriterMonad.re diff --git a/src/util/Util.re b/src/util/Util.re index 53a658d0fc..3286abefdb 100644 --- a/src/util/Util.re +++ b/src/util/Util.re @@ -13,7 +13,6 @@ module PairUtil = PairUtil; module CsvUtil = CsvUtil; module Result = Result; module StateMonad = StateMonad; -module WriterMonad = WriterMonad; module StringUtil = StringUtil; module TimeUtil = TimeUtil; module TupleUtil = TupleUtil; diff --git a/src/util/WriterMonad.re b/src/util/WriterMonad.re deleted file mode 100644 index cdb92e1eab..0000000000 --- a/src/util/WriterMonad.re +++ /dev/null @@ -1,42 +0,0 @@ -module type WRITER = { - type t; - let empty: t; - let append: (t, t) => t; -}; - -module type S = { - type writer; - - type t('a); - let return: 'a => t('a); - let bind: (t('a), 'a => t('b)) => t('b); - - let tell: writer => t(unit); - let listen: t('a) => t(('a, writer)); - let pass: t(('a, writer => writer)) => t('a); -}; - -module Make = (W: WRITER) => { - type writer = W.t; - - module T = { - type t('a) = (writer, 'a); - - let return = x => (W.empty, x); - - let bind = ((w1, x), f) => { - let (w2, y) = f(x); - (W.append(w1, w2), y); - }; - - let tell = w => (w, ()); - - let listen = ((w, x)) => (w, (x, w)); - - let pass = ((w, (x, f))) => (f(w), x); - }; - - include T; - - include Monads.Make_Monad_B(T); -}; diff --git a/test/Test_WriterMonad.re b/test/Test_WriterMonad.re deleted file mode 100644 index c37aab961b..0000000000 --- a/test/Test_WriterMonad.re +++ /dev/null @@ -1,124 +0,0 @@ -open Alcotest; -open Util; - -module StringWriter = { - [@deriving sexp] - type t = string; - let empty = ""; - let append = (s1, s2) => s1 ++ s2; -}; - -module StringWriterMonad = Util.WriterMonad.Make(StringWriter); - -let tests = ( - "WriterMonad", - [ - test_case( - "return produces empty writer", - `Quick, - () => { - let result = StringWriterMonad.return(42); - check( - pair(string, int), - "return with empty writer", - ("", 42), - result, - ); - }, - ), - test_case( - "tell adds to writer", - `Quick, - () => { - let result = StringWriterMonad.tell("hello"); - check( - pair(string, unit), - "tell adds message", - ("hello", ()), - result, - ); - }, - ), - test_case( - "bind combines writers", - `Quick, - () => { - let computation = - StringWriterMonad.Syntax.( - let* () = StringWriterMonad.tell("start "); - let* () = StringWriterMonad.tell("middle "); - let* () = StringWriterMonad.tell("end"); - StringWriterMonad.return("done") - ); - check( - pair(string, string), - "bind combines writers", - ("start middle end", "done"), - computation, - ); - }, - ), - test_case( - "listen captures writer", - `Quick, - () => { - let computation = - StringWriterMonad.Syntax.( - let* () = StringWriterMonad.tell("log1 "); - let* () = StringWriterMonad.tell("log2"); - StringWriterMonad.return(123) - ); - let result = StringWriterMonad.listen(computation); - check( - pair(string, pair(int, string)), - "listen captures writer", - ("log1 log2", (123, "log1 log2")), - result, - ); - }, - ), - test_case( - "pass modifies writer", - `Quick, - () => { - let computation = - StringWriterMonad.Syntax.( - let* () = StringWriterMonad.tell("original"); - StringWriterMonad.return(("result", w => "[" ++ w ++ "]")) - ); - let result = StringWriterMonad.pass(computation); - check( - pair(string, string), - "pass modifies writer", - ("[original]", "result"), - result, - ); - }, - ), - test_case( - "complex computation with let syntax", - `Quick, - () => { - let computation = - StringWriterMonad.Syntax.( - let* () = StringWriterMonad.tell("Begin "); - let* x = StringWriterMonad.return(10); - let* () = - StringWriterMonad.tell( - "Processing " ++ string_of_int(x) ++ " ", - ); - let* y = StringWriterMonad.return(x * 2); - let* () = - StringWriterMonad.tell("Result: " ++ string_of_int(y) ++ " "); - StringWriterMonad.return(y + 5) - ); - check( - pair(string, int), - "complex computation", - ("Begin Processing 10 Result: 20 ", 25), - computation, - ); - }, - ), - ], -); diff --git a/test/haz3ltest.re b/test/haz3ltest.re index 59ef9d9bfc..158559f46a 100644 --- a/test/haz3ltest.re +++ b/test/haz3ltest.re @@ -24,7 +24,6 @@ let (suite, _) = Test_Menhir.tests, Test_StringUtil.tests, Test_PatternMatch.tests, - Test_WriterMonad.tests, Test_Equality.tests, Test_Substitution.tests, ] From e50c2c5b324743d3ad831c7bdc5d605254a7644a Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala Date: Wed, 4 Feb 2026 11:06:05 -0500 Subject: [PATCH 63/65] Update probes doc buffer to have a from_lvs example for dynamic type example --- src/web/init/docs/Probes.ml | 129 ++++++++++++++++++++++++++++++++++-- 1 file changed, 125 insertions(+), 4 deletions(-) diff --git a/src/web/init/docs/Probes.ml b/src/web/init/docs/Probes.ml index 7d2f9c9553..c5f2b33d13 100644 --- a/src/web/init/docs/Probes.ml +++ b/src/web/init/docs/Probes.ml @@ -2022,10 +2022,14 @@ let out : string * Haz3lcore.PersistentSegment.t = 716774da-818d-43e5-8fea-14f8a9566587)(content(Comment\"# STATICS \ PROBES: These show inferred type information. #\"))))(Secondary((id \ d4bd4767-725a-4208-a7a6-636d4ecc41c3)(content(Whitespace\"\\n\"))))(Secondary((id \ - d4c05b17-0a5f-4775-9866-71a91a92bb06)(content(Comment\"# Double \ - clicking toggles from analytic to synthetic type. \ + 8b88339d-6aba-4f4d-adc5-118affc27855)(content(Comment\"# Double \ + clicking toggles between analytic type, synthetic type, \ #\"))))(Secondary((id \ 9062ef1a-3700-4912-9897-3b3c45829642)(content(Whitespace\"\\n\"))))(Secondary((id \ + 7b692ad2-7f24-4e98-8400-1b313aef30f6)(content(Comment\"# and dynamic \ + type. For dynamic type the dynamic component is shown in green. \ + #\"))))(Secondary((id \ + 4be2eff9-0ea6-46a9-8ab9-22cac9029cec)(content(Whitespace\"\\n\"))))(Secondary((id \ 59ed61a7-31b6-4d83-bca7-ac30c1302de7)(content(Whitespace\"\\n\"))))(Tile((id \ b026144b-b546-4411-8c42-b00c63cfce83)(label(let = in))(mold((out \ Exp)(in_(Pat Exp))(nibs(((shape Convex)(sort Exp))((shape(Concave \ @@ -2128,6 +2132,118 @@ let out : string * Haz3lcore.PersistentSegment.t = Exp))))))(shards(0))(children()))))))))(Secondary((id \ 0ecbef06-b1e6-4ec1-afcb-10a9b52a5b5d)(content(Whitespace\" \ \")))))))))(Secondary((id \ + 0e1b0f10-8f5b-45db-bf0a-63a30095c9be)(content(Whitespace\"\\n\"))))(Tile((id \ + 8766e1a8-42fa-4d1c-9569-1ad816a791da)(label(let = in))(mold((out \ + Exp)(in_(Pat Exp))(nibs(((shape Convex)(sort Exp))((shape(Concave \ + 45))(sort Exp))))))(shards(0 1 2))(children(((Secondary((id \ + a005db8f-10e1-47c4-b3d7-1233cd3273ab)(content(Whitespace\" \ + \"))))(Tile((id \ + aa00b5ea-0a31-47fc-a45d-560a32bbfeab)(label(d))(mold((out \ + Pat)(in_())(nibs(((shape Convex)(sort Pat))((shape Convex)(sort \ + Pat))))))(shards(0))(children())))(Secondary((id \ + 7c03a3ad-c7e2-4b2c-a133-060410a805c9)(content(Whitespace\" \ + \")))))((Secondary((id \ + aa0bc12f-4fdb-4488-b467-ab7bfcedaa4e)(content(Whitespace\" \ + \"))))(Tile((id \ + ccb5478b-987f-4364-8290-873db5c6ce1b)(label(from_lvs))(mold((out \ + Exp)(in_())(nibs(((shape Convex)(sort Exp))((shape Convex)(sort \ + Exp))))))(shards(0))(children())))(Tile((id \ + ce8d0375-c6b7-4735-b875-0beaba96612b)(label(\"(\"\")\"))(mold((out \ + Exp)(in_(Exp))(nibs(((shape(Concave 23))(sort Exp))((shape \ + Convex)(sort Exp))))))(shards(0 1))(children(((Tile((id \ + eb0d87d8-aed0-4f1f-b079-a6aecd5474f5)(label([ ]))(mold((out \ + Exp)(in_(Exp))(nibs(((shape Convex)(sort Exp))((shape Convex)(sort \ + Exp))))))(shards(0 1))(children(((Tile((id \ + 1939e804-2b76-4677-a72d-783de0df6c08)(label(\"(\"\")\"))(mold((out \ + Exp)(in_(Exp))(nibs(((shape Convex)(sort Exp))((shape Convex)(sort \ + Exp))))))(shards(0 1))(children(((Tile((id \ + 47a93940-5640-4da6-af49-40b527626208)(label(label))(mold((out \ + Exp)(in_())(nibs(((shape Convex)(sort Exp))((shape Convex)(sort \ + Exp))))))(shards(0))(children())))(Tile((id \ + 088c26b1-f774-42e8-8e19-1e2798fb18d8)(label(=))(mold((out \ + Exp)(in_())(nibs(((shape(Concave 39))(sort Exp))((shape(Concave \ + 39))(sort Exp))))))(shards(0))(children())))(Tile((id \ + ec28e481-eb56-4282-b236-23e09e7a1e72)(label(\"\\\"a\\\"\"))(mold((out \ + Exp)(in_())(nibs(((shape Convex)(sort Exp))((shape Convex)(sort \ + Exp))))))(shards(0))(children())))(Tile((id \ + db6c6eed-9645-4331-ac34-72c9e9b8d3e7)(label(,))(mold((out \ + Exp)(in_())(nibs(((shape(Concave 44))(sort Exp))((shape(Concave \ + 44))(sort Exp))))))(shards(0))(children())))(Secondary((id \ + d6b8fe43-d30f-48e7-a5a2-faa94665fb8d)(content(Whitespace\" \ + \"))))(Tile((id \ + 9ae68c01-bfa9-40da-a68d-8804cc0b1977)(label(value))(mold((out \ + Exp)(in_())(nibs(((shape Convex)(sort Exp))((shape Convex)(sort \ + Exp))))))(shards(0))(children())))(Tile((id \ + b63751c0-397d-4445-a1a7-02cbf98800bd)(label(=))(mold((out \ + Exp)(in_())(nibs(((shape(Concave 39))(sort Exp))((shape(Concave \ + 39))(sort Exp))))))(shards(0))(children())))(Tile((id \ + 27635fe8-33ea-4242-bd06-716d93017e57)(label(a))(mold((out \ + Exp)(in_())(nibs(((shape Convex)(sort Exp))((shape Convex)(sort \ + Exp))))))(shards(0))(children()))))))))(Tile((id \ + 5af1b488-24e3-4efe-9106-0624bb514b47)(label(,))(mold((out \ + Exp)(in_())(nibs(((shape(Concave 44))(sort Exp))((shape(Concave \ + 44))(sort Exp))))))(shards(0))(children())))(Secondary((id \ + 85372a1f-9c71-47a8-9ffb-0ebca6237106)(content(Whitespace\"\\n\"))))(Secondary((id \ + e9f33122-bbb9-403d-8074-b50a784352da)(content(Whitespace\" \ + \"))))(Secondary((id \ + 0cde39bc-5fb3-4b16-8c5e-f3c39f4278e6)(content(Whitespace\" \ + \"))))(Secondary((id \ + dd3bcbe8-73dd-4147-a3b6-14af740e3c8f)(content(Whitespace\" \ + \"))))(Secondary((id \ + 14c6bfcd-5eb2-45ea-9e29-977d17fa78b4)(content(Whitespace\" \ + \"))))(Secondary((id \ + caced31d-22ce-4aa9-9e89-3f41a43defe9)(content(Whitespace\" \ + \"))))(Secondary((id \ + 82afef74-1241-4ff7-87d6-14cf403aaa46)(content(Whitespace\" \ + \"))))(Secondary((id \ + 78f8a0d0-f346-46cf-b1bf-795c1617c4df)(content(Whitespace\" \ + \"))))(Secondary((id \ + e972d74c-6279-4fc4-b2e0-04c7413df24d)(content(Whitespace\" \ + \"))))(Secondary((id \ + ab49805c-087f-4498-809d-1f363cbdd90b)(content(Whitespace\" \ + \"))))(Secondary((id \ + f80a1b2e-2bef-46ad-b59f-9418e6136672)(content(Whitespace\" \ + \"))))(Secondary((id \ + 4bac5aa5-68c2-4620-abe3-9e67287e2dcf)(content(Whitespace\" \ + \"))))(Secondary((id \ + b77ec293-d674-4e42-bee3-9cfabb88d4b6)(content(Whitespace\" \ + \"))))(Secondary((id \ + c3d49f6d-6add-4406-9647-f5e1873c4c45)(content(Whitespace\" \ + \"))))(Secondary((id \ + d7a9e69b-9c95-4b7b-8cca-4238fec1a828)(content(Whitespace\" \ + \"))))(Secondary((id \ + 07d338d0-c882-46ce-b769-5424ae49d1ab)(content(Whitespace\" \ + \"))))(Secondary((id \ + 8eca16ec-40f5-46f0-abe7-7f3d42168cd5)(content(Whitespace\" \ + \"))))(Tile((id \ + c06c19bf-6643-470e-a231-f61b9d0cd45d)(label(\"(\"\")\"))(mold((out \ + Exp)(in_(Exp))(nibs(((shape Convex)(sort Exp))((shape Convex)(sort \ + Exp))))))(shards(0 1))(children(((Tile((id \ + c6e2b858-df98-4198-b6af-9e072575cc94)(label(label))(mold((out \ + Exp)(in_())(nibs(((shape Convex)(sort Exp))((shape Convex)(sort \ + Exp))))))(shards(0))(children())))(Tile((id \ + 1605e2b8-9895-489f-87a7-45dda5b024bd)(label(=))(mold((out \ + Exp)(in_())(nibs(((shape(Concave 39))(sort Exp))((shape(Concave \ + 39))(sort Exp))))))(shards(0))(children())))(Tile((id \ + bdd44f66-5f25-4492-88b8-8cd8e96d0969)(label(\"\\\"b\\\"\"))(mold((out \ + Exp)(in_())(nibs(((shape Convex)(sort Exp))((shape Convex)(sort \ + Exp))))))(shards(0))(children())))(Tile((id \ + 3cfac31e-124c-4889-b4b0-2a924c705a43)(label(,))(mold((out \ + Exp)(in_())(nibs(((shape(Concave 44))(sort Exp))((shape(Concave \ + 44))(sort Exp))))))(shards(0))(children())))(Secondary((id \ + 81561d5b-83a9-474b-9061-87f6987ac236)(content(Whitespace\" \ + \"))))(Tile((id \ + a5c7f241-2cb5-439d-92da-5f2219faa388)(label(value))(mold((out \ + Exp)(in_())(nibs(((shape Convex)(sort Exp))((shape Convex)(sort \ + Exp))))))(shards(0))(children())))(Tile((id \ + beafa075-68be-47fa-a508-e7d24b671f72)(label(=))(mold((out \ + Exp)(in_())(nibs(((shape(Concave 39))(sort Exp))((shape(Concave \ + 39))(sort Exp))))))(shards(0))(children())))(Tile((id \ + dadcc25f-887f-440b-aca4-69eb1ea74396)(label(b))(mold((out \ + Exp)(in_())(nibs(((shape Convex)(sort Exp))((shape Convex)(sort \ + Exp))))))(shards(0))(children()))))))))))))))))))(Secondary((id \ + c93065af-c949-4c8d-b96e-e32a41432bdc)(content(Whitespace\" \ + \")))))))))(Secondary((id \ fc419efe-9b69-43d5-a8ec-2711a99cef14)(content(Whitespace\"\\n\"))))(Tile((id \ 35a67ab1-de80-4647-8ff3-35d78ece4ac1)(label(b))(mold((out \ Exp)(in_())(nibs(((shape Convex)(sort Exp))((shape Convex)(sort \ @@ -2306,13 +2422,18 @@ let out : string * Haz3lcore.PersistentSegment.t = test ^^probe(new_frunk(4)) == 314 end;\n\ test ^^probe(new_frunk(6)) == 330 end;\n\n\ # STATICS PROBES: These show inferred type information. #\n\ - # Double clicking toggles from analytic to synthetic type. #\n\n\ + # Double clicking toggles between analytic type, synthetic type, #\n\ + # and dynamic type. For dynamic type the dynamic component is shown \ + in green. #\n\n\ let a: Bool = ^^statics(1) in\n\ let b: Bool = ^^statics(true) in \n\ let c: (String, ?) = ^^statics((?, 1)) in\n\ + let d = ^^statics(from_lvs([(label=\"a\", value=a),\n\ + \ (label=\"b\", value=b)])) in\n\ b"; refractors = - "((01b8885d-2bb0-4d1b-89df-d1723fcc919f((kind \ + "((ce8d0375-c6b7-4735-b875-0beaba96612b((kind Statics)(model \ + Dynamic)))(01b8885d-2bb0-4d1b-89df-d1723fcc919f((kind \ Probe)(model\"()\")))(0626cd00-4c2d-426c-988a-ff577e24b50c((kind \ Probe)(model\"()\")))(07e0c1c5-957e-4eaa-8ec8-187588fc3595((kind \ Statics)(model Self)))(161e5fc2-1c02-4cf6-b2bc-88aa38334dbe((kind \ From 23e38c30a0c870d4c0ffc27de0356059b4ac3bc5 Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala Date: Tue, 10 Feb 2026 11:54:22 -0500 Subject: [PATCH 64/65] Update context menu labels for type probe actions --- src/haz3lcore/projectors/implementations/TypeProj.re | 2 +- src/web/app/editors/code/ContextMenu.re | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/haz3lcore/projectors/implementations/TypeProj.re b/src/haz3lcore/projectors/implementations/TypeProj.re index ac91ab1919..9e5e94d864 100644 --- a/src/haz3lcore/projectors/implementations/TypeProj.re +++ b/src/haz3lcore/projectors/implementations/TypeProj.re @@ -82,7 +82,7 @@ module M: Projector = { let display_mode = (model: model, statics: option(Language.Info.t)): string => { switch (model) { - | Dynamic => "↦" + | Dynamic => "⇓" | _ when self_ty(statics) == expected_ty(statics) => "⇔" | _ when expected_ty(statics) |> totalize_ty |> Typ.is_syn => "⇒" | Self => "⇒" diff --git a/src/web/app/editors/code/ContextMenu.re b/src/web/app/editors/code/ContextMenu.re index b640b02fcc..c0d80cefc9 100644 --- a/src/web/app/editors/code/ContextMenu.re +++ b/src/web/app/editors/code/ContextMenu.re @@ -279,10 +279,10 @@ let type_annotation_data = { name: switch (probe_status) { - | Statics(_) => "Remove statics" + | Statics(_) => "Remove type probe" | Manual(_) - | Auto => "Switch to statics" - | Non => "Add statics" + | Auto => "Switch to type probe" + | Non => "Add type probe" }, shortcut: Some(Shortcuts.type_annotation()), action: Probe(ToggleStatics), From 4ae0e2c1095e0af8049d30c71046351406d4d6fa Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala Date: Tue, 10 Feb 2026 13:01:41 -0500 Subject: [PATCH 65/65] Add hover tooltip for type probe mode --- .../projectors/implementations/TypeProj.re | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/haz3lcore/projectors/implementations/TypeProj.re b/src/haz3lcore/projectors/implementations/TypeProj.re index 9e5e94d864..8ae6e7f182 100644 --- a/src/haz3lcore/projectors/implementations/TypeProj.re +++ b/src/haz3lcore/projectors/implementations/TypeProj.re @@ -89,9 +89,23 @@ module M: Projector = { | Expected => "⇐" }; }; + let mode_description = + (model: model, statics: option(Language.Info.t)): string => { + switch (model) { + | Dynamic => "Dynamic type (from runtime values)" + | _ when self_ty(statics) == expected_ty(statics) => "Self type matches expected type" + | _ when expected_ty(statics) |> totalize_ty |> Typ.is_syn => "Self type" + | Self => "Self type" + | Expected => "Expected type" + }; + }; + let mode_view = (model, info) => div( - ~attrs=[Attr.classes(["mode"])], + ~attrs=[ + Attr.classes(["mode"]), + Attr.title(mode_description(model, info)), + ], [text(display_mode(model, info))], );