From 7665ddfdda288c978ad6a120d42e810b333e4a10 Mon Sep 17 00:00:00 2001 From: Heidi Jiang Date: Thu, 29 Jan 2026 19:20:31 -0500 Subject: [PATCH 1/7] Fix data alias error message Adds a test checking that Pyret correctly threw an error. Co-authored-by: Bobby Palazzi --- lang/src/arr/compiler/resolve-scope.arr | 33 +++++++++++++++---------- lang/tests/pyret/tests/test-modules.arr | 30 ++++++++++++++++++++++ 2 files changed, 50 insertions(+), 13 deletions(-) diff --git a/lang/src/arr/compiler/resolve-scope.arr b/lang/src/arr/compiler/resolve-scope.arr index 77da061d2..f6612f143 100644 --- a/lang/src/arr/compiler/resolve-scope.arr +++ b/lang/src/arr/compiler/resolve-scope.arr @@ -1328,19 +1328,26 @@ fun resolve-names(p :: A.Program, thismodule-uri :: String, initial-env :: C.Com cases(Option) maybe-uri block: | none => # path must be a single element if there's no URI of a remote module # e.g. provide: D end NOT provide: M.D end - data-expr = datatypes.get-value-now(path.first.toname()) - maybe-add(provided-datatypes, data-expr.name, {l; none; data-expr.namet}) - data-checker-name = A.make-checker-name(data-expr.name) - data-checker-vb = val-env.get-value(data-checker-name) - maybe-add(provided-values, data-checker-name, {l; none; data-checker-vb.atom}) - data-alias-tb = type-env.get-value(data-expr.name) - maybe-add(provided-types, data-expr.name, {l; none; data-alias-tb.atom}) - for each(v from data-expr.variants) block: - variant-vb = val-env.get-value(v.name) - checker-name = A.make-checker-name(v.name) - variant-checker-vb = val-env.get-value(checker-name) - maybe-add(provided-values, v.name, {l; none; variant-vb.atom}) - maybe-add(provided-values, checker-name, {l; none; variant-checker-vb.atom}) + + data-expr = datatypes.get-now(path.first.toname()) + cases(Option) data-expr block: + | some(shadow data-expr) => + maybe-add(provided-datatypes, data-expr.name, {l; none; data-expr.namet}) + data-checker-name = A.make-checker-name(data-expr.name) + data-checker-vb = val-env.get-value(data-checker-name) + maybe-add(provided-values, data-checker-name, {l; none; data-checker-vb.atom}) + data-alias-tb = type-env.get-value(data-expr.name) + maybe-add(provided-types, data-expr.name, {l; none; data-alias-tb.atom}) + for each(v from data-expr.variants) block: + variant-vb = val-env.get-value(v.name) + checker-name = A.make-checker-name(v.name) + variant-checker-vb = val-env.get-value(checker-name) + maybe-add(provided-values, v.name, {l; none; variant-vb.atom}) + maybe-add(provided-values, checker-name, {l; none; variant-checker-vb.atom}) + end + | none => + name-errors := link(C.unbound-id(A.s-id(l, A.s-name(l, path.last().toname()))), name-errors) + { none; A.s-name(l, path.last().toname()) } end | some(uri) => diff --git a/lang/tests/pyret/tests/test-modules.arr b/lang/tests/pyret/tests/test-modules.arr index cca2c5d32..cdb106835 100644 --- a/lang/tests/pyret/tests/test-modules.arr +++ b/lang/tests/pyret/tests/test-modules.arr @@ -481,3 +481,33 @@ end errs is%(error-with) "j" end +check "https://github.com/brownplt/pyret-lang/issues/1790": + m = make-fresh-module-testing-context() + m.save-module("main.arr", ``` +provide: + data Foo +end + +type Foo = {} +```) + + errs = m.compile-error-messages("main.arr") + errs is%(error-with) "Foo" +end + +check "https://github.com/brownplt/pyret-lang/issues/1790": + m = make-fresh-module-testing-context() + m.save-module("main.arr", ``` +provide: + data Foo +end + +data Bar: + | variant +end +```) + + errs = m.compile-error-messages("main.arr") + errs is%(error-with) "Foo" +end + From f09a16c0b9aeeaec787443b38d9592c7acb56f35 Mon Sep 17 00:00:00 2001 From: Zack Eisbach Date: Sun, 8 Feb 2026 10:55:04 -0500 Subject: [PATCH 2/7] change letrec ANFing includes a regression test Co-authored-by: Ananya Rath Co-authored-by: Jacob Lefkowitz --- lang/src/arr/compiler/anf.arr | 25 ++++++++++++++++------ lang/tests/pyret/regression/letrec-ann.arr | 7 ++++++ 2 files changed, 26 insertions(+), 6 deletions(-) create mode 100644 lang/tests/pyret/regression/letrec-ann.arr diff --git a/lang/src/arr/compiler/anf.arr b/lang/src/arr/compiler/anf.arr index 2a57d222c..0218c6cb6 100644 --- a/lang/src/arr/compiler/anf.arr +++ b/lang/src/arr/compiler/anf.arr @@ -202,13 +202,26 @@ fun anf(e :: A.Expr, k :: ANFCont) -> N.AExpr: end | s-letrec(l, binds, body, _) => - let-binds = for map(b from binds): - A.s-var-bind(b.l, b.b, A.s-undefined(l)) + undef-inits = for map(b from binds): + b-without-ann = A.s-bind(b.b.l, b.b.shadows, b.b.id, A.a-blank) + A.s-var-bind(b.l, b-without-ann, A.s-undefined(l)) end - assigns = for map(b from binds): - A.s-assign(b.l, b.b.id, b.value) - end - anf(A.s-let-expr(l, let-binds, A.s-block(l, assigns + [list: body]), true), k) + { rev-tmp-binds; rev-assigns } = + for fold({ tmp-binds-acc; assigns-acc } from { [list: ]; [list: ] }, b from binds): + if A.is-a-blank(b.b.ann) or A.is-a-any(b.b.ann): + new-assign = A.s-assign(b.l, b.b.id, b.value) + { tmp-binds-acc; link(new-assign, assigns-acc) } + else: + tmp-name-id = mk-id(b.l, "letrec_ann").id + b-with-tmp = A.s-bind(b.b.l, b.b.shadows, tmp-name-id, b.b.ann) + new-bind = A.s-let-bind(b.l, b-with-tmp, b.value) + new-assign = A.s-assign(b.l, b.b.id, A.s-id(b.l, tmp-name-id)) + { link(new-bind, tmp-binds-acc); link(new-assign, assigns-acc) } + end + end + inner-let = A.s-let-expr(l, rev-tmp-binds.reverse(), + A.s-block(l, rev-assigns.reverse() + [list: body]), true) + anf(A.s-let-expr(l, undef-inits, inner-let, false), k) | s-data-expr(l, data-name, data-name-t, params, mixins, variants, shared, _check-loc, _check) => fun anf-member(member :: A.VariantMember): diff --git a/lang/tests/pyret/regression/letrec-ann.arr b/lang/tests/pyret/regression/letrec-ann.arr new file mode 100644 index 000000000..d97ee5bd3 --- /dev/null +++ b/lang/tests/pyret/regression/letrec-ann.arr @@ -0,0 +1,7 @@ +check "https://github.com/brownplt/pyret-lang/issues/1700": + letrec n :: Number%(is-zero) = 10, is-zero = lam(x): x == 0 end: n end + raises "uninitialized-id" + + letrec is-zero = lam(x): x == 0, n :: Number%(is-zero) = 10 end: n end + raises "type-mismatch" +end From 2f17d7b64218c077ef9f531a3110d1b170bfdc41 Mon Sep 17 00:00:00 2001 From: Heidi Jiang Date: Fri, 6 Feb 2026 20:35:57 -0500 Subject: [PATCH 3/7] added -v flag to run-phase script --- lang/README.md | 4 ++-- lang/src/scripts/run-phase | 23 ++++++++++++++++++++--- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/lang/README.md b/lang/README.md index e64e9bed2..7bb43a217 100644 --- a/lang/README.md +++ b/lang/README.md @@ -59,12 +59,12 @@ in Pyret development, read on: The easiest way to *run* a Pyret program from the command-line is: - $ ./src/scripts/phaseX [command-line-args...] + $ ./src/scripts/phaseX [-v] [command-line-args...] Where `X` is `0`, `A`, `B`, or `C`, indicating a phase (described below). For example: - $ ./src/scripts/phaseA src/scripts/show-compilation.arr examples/ahoy-world.arr + $ ./src/scripts/phaseA -v src/scripts/show-compilation.arr examples/ahoy-world.arr Alternatively, you can compile and run a standalone JavaScript file via: diff --git a/lang/src/scripts/run-phase b/lang/src/scripts/run-phase index 47ecab14b..61eef7fe3 100755 --- a/lang/src/scripts/run-phase +++ b/lang/src/scripts/run-phase @@ -15,6 +15,16 @@ then exit 1 fi +VERBOSE=0 +if [ "$#" -gt 0 ] && [ "$1" = "-v" ]; then + VERBOSE=1 + shift 1 +fi +if [ "$#" -lt 1 ]; then + echo "Expecting 1 argument; received $#" + echo "Usage: $PHASE [-v] file.arr [args]" + exit 1 +fi PHASE="$SYMLINK_NAME" PYRET_JARR="build/$PHASE/pyret.jarr" FILE=$1 @@ -40,9 +50,16 @@ fi shift 1 -if [ "$#" -gt 0 ] +if [ "$VERBOSE" = 1 ] then - $NODE $PYRET_JARR -no-display-progress --run $FILE - "$@" + PROGRESS_FLAG="" else - $NODE $PYRET_JARR -no-display-progress --run $FILE + PROGRESS_FLAG="-no-display-progress" fi + +if [ "$#" -gt 0 ] +then + $NODE $PYRET_JARR $PROGRESS_FLAG --run $FILE - "$@" +else + $NODE $PYRET_JARR $PROGRESS_FLAG --run $FILE +fi \ No newline at end of file From b4df4c640fcc3663055bf155b507a911dbf5a7dc Mon Sep 17 00:00:00 2001 From: Ananya Rath Date: Sun, 15 Feb 2026 22:09:13 -0500 Subject: [PATCH 4/7] add is-ignorable method to Ann in ast --- lang/src/arr/compiler/anf-loop-compiler.arr | 10 +++++----- lang/src/arr/compiler/anf.arr | 6 +++--- lang/src/arr/trove/ast.arr | 7 ++++++- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/lang/src/arr/compiler/anf-loop-compiler.arr b/lang/src/arr/compiler/anf-loop-compiler.arr index 03319f7f7..24cc05cc8 100644 --- a/lang/src/arr/compiler/anf-loop-compiler.arr +++ b/lang/src/arr/compiler/anf-loop-compiler.arr @@ -801,9 +801,9 @@ end fun compile-anns(visitor, step, binds :: List, entry-label): var cur-target = entry-label new-cases = for lists.fold(acc from cl-empty, b from binds): - if A.is-a-blank(b.ann) or A.is-a-any(b.ann) block: + if b.ann.is-ignorable() block: acc - else if A.is-a-tuple(b.ann) and b.ann.fields.all(lam(a): A.is-a-blank(a) or A.is-a-any(a) end): + else if A.is-a-tuple(b.ann) and b.ann.fields.all(lam(a): a.is-ignorable() end): new-label = visitor.make-label() new-case = j-case(cur-target, @@ -861,7 +861,7 @@ fun compile-annotated-let(visitor, b :: BindType, compiled-e :: DAG.CaseResults% raise(string-append(string-append("Unknown ", b.value.label()), " in compile-annotated-let")) end shadow b = b.value - if A.is-a-blank(b.ann) or A.is-a-any(b.ann): + if b.ann.is-ignorable(): c-block( j-block( cl-append( @@ -872,7 +872,7 @@ fun compile-annotated-let(visitor, b :: BindType, compiled-e :: DAG.CaseResults% ), compiled-body.new-cases ) - else if A.is-a-tuple(b.ann) and b.ann.fields.all(lam(a): A.is-a-blank(a) or A.is-a-any(a) end): + else if A.is-a-tuple(b.ann) and b.ann.fields.all(lam(a): a.is-ignorable() end): step = visitor.cur-step after-ann = visitor.make-label() after-ann-case = j-case(after-ann, j-block(compiled-body.block.stmts)) @@ -1833,7 +1833,7 @@ compiler-visitor = { fun make-variant-constructor(l2, base-id, brands-id, members, refl-name, refl-ref-fields-mask, refl-fields, constructor-id): nonblank-anns = for filter(m from members): - not(A.is-a-blank(m.bind.ann)) and not(A.is-a-any(m.bind.ann)) + not(m.bind.ann.is-ignorable()) end compiled-anns = for fold(acc from {anns: cl-empty, others: cl-empty}, m from nonblank-anns): compiled = compile-ann(m.bind.ann, none, self) diff --git a/lang/src/arr/compiler/anf.arr b/lang/src/arr/compiler/anf.arr index 0218c6cb6..50485cf52 100644 --- a/lang/src/arr/compiler/anf.arr +++ b/lang/src/arr/compiler/anf.arr @@ -181,7 +181,7 @@ fun anf(e :: A.Expr, k :: ANFCont) -> N.AExpr: | link(f, r) => cases(A.LetBind) f: | s-var-bind(l2, b, val) => - if A.is-a-blank(b.ann) or A.is-a-any(b.ann): + if b.ann.is-ignorable(): anf-name(val, "var", lam(new-val): N.a-var(l2, N.a-bind(l2, b.id, b.ann), N.a-val(new-val.l, new-val), anf(A.s-let-expr(l, r, body, blocky), k)) @@ -312,7 +312,7 @@ fun anf(e :: A.Expr, k :: ANFCont) -> N.AExpr: anf(A.s-let-expr(l, bindings, A.s-id(l, name.id), false), k) | s-lam(l, name, params, args, ret, doc, body, _, _, _) => - if A.is-a-blank(ret) or A.is-a-any(ret): + if ret.is-ignorable(): k(N.a-lam(l, name, args.map(lam(a): N.a-bind(a.l, a.id, a.ann) end), ret, anf-term(body))) else: temp = mk-id(l, "ann_check_temp") @@ -322,7 +322,7 @@ fun anf(e :: A.Expr, k :: ANFCont) -> N.AExpr: A.s-id(l, temp.id), false)))) end | s-method(l, name, params, args, ret, doc, body, _, _, _) => - if A.is-a-blank(ret) or A.is-a-any(ret): + if ret.is-ignorable(): k(N.a-method(l, name, args.map(lam(a): N.a-bind(a.l, a.id, a.ann) end), ret, anf-term(body))) else: temp = mk-id(l, "ann_check_temp") diff --git a/lang/src/arr/trove/ast.arr b/lang/src/arr/trove/ast.arr index 0a2d26a97..ed1a73ed5 100644 --- a/lang/src/arr/trove/ast.arr +++ b/lang/src/arr/trove/ast.arr @@ -1781,9 +1781,11 @@ data Ann: | a-blank with: method label(self): "a-blank" end, method tosource(self): str-any end, + method is-ignorable(self): true end, | a-any(l :: Loc) with: method label(self): "a-any" end, method tosource(self): str-any end, + method is-ignorable(self): true end, | a-name(l :: Loc, id :: Name) with: method label(self): "a-name" end, method tosource(self): self.id.tosource() end, @@ -1848,10 +1850,13 @@ data Ann: method tosource(self): self.obj.tosource() + PP.str("." + self.field) end, | a-checked(checked :: Ann, residual :: Ann) with: method label(self): "a-checked" end, - method tosource(self): self.residual.tosource() end + method tosource(self): self.residual.tosource() end, sharing: method visit(self, visitor): self._match(visitor, lam(val): raise("No visitor field for " + self.label()) end) + end, + method is-ignorable(self): + false end end From 0f134a8a8ca708f6f85f901f9f0974c949ad4d26 Mon Sep 17 00:00:00 2001 From: Angela Weigl Date: Sat, 21 Feb 2026 22:27:44 -0500 Subject: [PATCH 5/7] change equality error handling to accept args/function input AND operator input Bugfix: identical on functions raises internal error instead of same error as <=> --- lang/src/arr/trove/error.arr | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lang/src/arr/trove/error.arr b/lang/src/arr/trove/error.arr index 288d1508a..e2cdc7acc 100644 --- a/lang/src/arr/trove/error.arr +++ b/lang/src/arr/trove/error.arr @@ -2302,8 +2302,12 @@ data RuntimeError: else if src-available(loc): cases(O.Option) maybe-ast(loc): | some(ast) => - left-loc = ast.left.l - right-loc = ast.right.l + {left-loc; right-loc} = cases(Any) ast: + | s-op(_, _, _, left-expr, right-expr) => + {left-expr.l; right-expr.l} + | s-app(_, _, args) => + {args.get(0).l; args.get(1).l} + end [ED.sequence: ed-intro("equality comparison", loc, -1, true), ED.cmcode(loc), From ecd8323e435b25ae9975c2d84e19329c29756a2e Mon Sep 17 00:00:00 2001 From: Angela Weigl Date: Fri, 13 Mar 2026 02:18:00 -0500 Subject: [PATCH 6/7] Better load-table error message for bogus source --- lang/src/arr/trove/error.arr | 33 ++++++++++++++++++--------- lang/src/scripts/show-compilation.arr | 14 +++++++++++- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/lang/src/arr/trove/error.arr b/lang/src/arr/trove/error.arr index e2cdc7acc..1c23c8d5b 100644 --- a/lang/src/arr/trove/error.arr +++ b/lang/src/arr/trove/error.arr @@ -718,18 +718,29 @@ data RuntimeError: else if src-available(self.loc): cases(O.Option) maybe-ast(self.loc): | some(ast) => - shadow ast = cases(Any) ast: - | s-dot(_,_,_) => ast - | s-app(_,f,_) => f + if self.field == "load": + # load-table ... source: "..." end desugars to "...".load(...), + # so self.field is "load" and the pre-desugar AST is s-load-table. + [ED.error: + ed-intro("table loader expression", self.loc, -1, true), + ED.cmcode(self.loc), + [ED.para: + ED.text("The source could not be loaded:")], + ED.embed(self.non-obj)] + else: + shadow ast = cases(Any) ast: + | s-dot(_,_,_) => ast + | s-app(_,f,_) => f + end + [ED.error: + ed-intro("field lookup expression", self.loc, -1, true), + ED.cmcode(self.loc), + [ED.para: + ED.text("The "), + ED.highlight(ED.text("left side"), [ED.locs: ast.obj.l], 0), + ED.text(" was not an object:")], + ED.embed(self.non-obj)] end - [ED.error: - ed-intro("field lookup expression", self.loc, -1, true), - ED.cmcode(self.loc), - [ED.para: - ED.text("The "), - ED.highlight(ED.text("left side"), [ED.locs: ast.obj.l], 0), - ED.text(" was not an object:")], - ED.embed(self.non-obj)] | none => [ED.error: ed-intro("field lookup expression", self.loc, -1, true), diff --git a/lang/src/scripts/show-compilation.arr b/lang/src/scripts/show-compilation.arr index 484d3ca19..f1c6dfbe2 100644 --- a/lang/src/scripts/show-compilation.arr +++ b/lang/src/scripts/show-compilation.arr @@ -6,6 +6,7 @@ import parse-pyret as P import string-dict as SD import pprint as PP import pathlib as PL +import render-error-display as RED import file("../../src/arr/compiler/desugar.arr") as D import file("../../src/arr/compiler/desugar-check.arr") as DC import ast as A @@ -128,7 +129,18 @@ cases (C.ParsedArguments) parsed-options block: comp = cases(E.Either) compiled block: | left(v) => println("Compilation failed") - {_; traces} = v + {loadables; traces} = v + errors = loadables + .filter(CL.is-error-compilation) + .map(_.result-printer) + .map(_.problems) + .foldr(_ + _, empty) + + for each(err from errors) block: + println(to-repr(err)) + println(RED.display-to-string(err.render-reason(), torepr, empty)) + end + traces.get-value-now(PL.basename(file, "")) | right(v) => {_; traces} = v From a236fb40d17eb9535c20aa06f93fed895aef5648 Mon Sep 17 00:00:00 2001 From: bpalazzi512 <60822022+bpalazzi512@users.noreply.github.com> Date: Sat, 21 Feb 2026 14:23:57 -0500 Subject: [PATCH 7/7] Adding .arr and .jarr file icons in vscode --- vscode/.gitignore | 1 + vscode/media/icons/jarr.svg | 25 +++++++++ vscode/media/icons/pyret.svg | 100 +++++++++++++++++++++++++++++++++++ vscode/package.json | 17 ++++++ 4 files changed, 143 insertions(+) create mode 100644 vscode/media/icons/jarr.svg create mode 100644 vscode/media/icons/pyret.svg diff --git a/vscode/.gitignore b/vscode/.gitignore index c6287e778..92e85ab56 100644 --- a/vscode/.gitignore +++ b/vscode/.gitignore @@ -3,3 +3,4 @@ out dist node_modules +build diff --git a/vscode/media/icons/jarr.svg b/vscode/media/icons/jarr.svg new file mode 100644 index 000000000..fba685115 --- /dev/null +++ b/vscode/media/icons/jarr.svg @@ -0,0 +1,25 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/vscode/media/icons/pyret.svg b/vscode/media/icons/pyret.svg new file mode 100644 index 000000000..d0cb5cca4 --- /dev/null +++ b/vscode/media/icons/pyret.svg @@ -0,0 +1,100 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/vscode/package.json b/vscode/package.json index 9b3dd0df4..562429781 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -33,7 +33,24 @@ "extensions": [ ".arr" ], + "icon": { + "light": "./media/icons/pyret.svg", + "dark": "./media/icons/pyret.svg" + }, "configuration": "./language-configuration.json" + }, + { + "id": "jarr", + "aliases": [ + "jarr" + ], + "extensions": [ + ".jarr" + ], + "icon": { + "light": "./media/icons/jarr.svg", + "dark": "./media/icons/jarr.svg" + } } ], "grammars": [