diff --git a/lib/parse.ml b/lib/parse.ml index a7c4102..3c822d2 100644 --- a/lib/parse.ml +++ b/lib/parse.ml @@ -772,12 +772,35 @@ let rec decl types (f : Form.t) : Ast.decl = pre-pass in [program] collects from the file's own declarations. That makes it exact rather than heuristic, because types are only ever introduced by defstruct, defunion and defalias — all syntactically obvious. *) +(* A name a package brought in — [rl/Vector2]. It cannot be in [types]: the set + comes from this file's own declarations, and an import is not resolved until + after parsing, so a package's structs are unknown here by construction. + + The signal is the alias plus the capital. An alias is syntactically obvious, + collected by the same pre-pass; and a bare capitalised symbol is never a + *value* in this language — a struct or union constructor is [(Name {...})], + a List, and an enum member is a keyword. So [alias/Name] in a type position + is a type, and the case the comment above warns about — a body form eaten as + a return type — cannot arise, because there is no body form of that shape. + + A lowercase qualified name stays an expression, which is what [rl/get-color] + in [(defn f [] rl/get-color)] has to be. *) +and qualified_type types s = + match String.index_opt s '/' with + | None -> false + | Some i -> + let alias = String.sub s 0 i and name = String.sub s (i + 1) (String.length s - i - 1) in + Names.mem ("import " ^ alias) types + && name <> "" + && name.[0] >= 'A' && name.[0] <= 'Z' + and is_type_form types (f : Form.t) = match f.v with - | Sym s -> Names.mem s types + | Sym s -> Names.mem s types || qualified_type types s | Vec _ -> true (* [T] and [n T] are only types *) | Map _ -> true (* {K V} in this position *) - | List ({ v = Sym n; _ } :: _) -> Names.mem n types + | List ({ v = Sym n; _ } :: _) -> + Names.mem n types || qualified_type types n | _ -> false and variant (f : Form.t) : Ast.variant = @@ -797,6 +820,11 @@ let declared_types (forms : Form.t list) : Names.t = match f.v with | List [ { v = Sym ("defstruct" | "defunion" | "defalias"); _ }; { v = Sym n; _ }; _ ] -> Names.add n acc + (* The aliases too, under a key no symbol can collide with, so that + [qualified_type] can tell [rl/Vector2] from a name with a slash in + it that nothing imported. *) + | List [ { v = Sym "import"; _ }; { v = Sym a; _ }; { v = Str _; _ } ] -> + Names.add ("import " ^ a) acc | _ -> acc) builtin_types forms diff --git a/test/programs/pkg-return.flan b/test/programs/pkg-return.flan new file mode 100644 index 0000000..2b031d7 --- /dev/null +++ b/test/programs/pkg-return.flan @@ -0,0 +1,27 @@ +;;;; A package-qualified struct in return position. +;;;; +;;;; The parser decides "is this form a return type or the first body form?" +;;;; from the set of type names the file declares — and an import is resolved +;;;; after parsing, so a package's structs cannot be in that set. The result +;;;; was that (defn mk [] rl/Vector2 ...) read the return type as the body and +;;;; failed with "unknown name rl/Vector2", which named the symptom and not the +;;;; cause. The alias plus the capital is the signal: a bare capitalised symbol +;;;; is never a value here, since a constructor is (Name {...}) and an enum +;;;; member is a keyword. +(import edn "vendor:edn") + +(defstruct Local [n i32]) + +;;; The case that used to fail: a package struct as the declared return type. +(defn fresh [src [u8]] edn/Cursor (edn/cursor src)) + +;;; And the one that must keep working: a lowercase qualified name in the same +;;; position is an expression, not a type. +(defn local [] Local (Local {:n 5})) + +(defn main [] i32 + (let [c (fresh (bytes "[1 2]")) + t (edn/next (addr c))] + (print-i64 (i64 (.kind t))) (newline)) + (print-i64 (i64 (.n (local)))) (newline) + 0) diff --git a/test/test_acceptance.ml b/test/test_acceptance.ml index c88148e..ec28dc1 100644 --- a/test/test_acceptance.ml +++ b/test/test_acceptance.ml @@ -1033,6 +1033,16 @@ ERR@7 unexpected token: not the kind the caller was reading outputs ~opt:"-O0" "destructuring, -O0" "programs/destructure.flan" destructure_out; + (* A package-qualified struct as a declared return type, which the parser + used to read as the first body form. Reported by the raylib lane, which + hit it on rl/Vector2 and worked around it rather than reaching into a + file it did not own. *) + let pkgret_out = "9\n5\n" in + outputs "a package struct in return position" "programs/pkg-return.flan" + pkgret_out; + outputs ~opt:"-O0" "a package struct in return position, -O0" + "programs/pkg-return.flan" pkgret_out; + if !failures = 0 then print_endline "acceptance: all tests passed" else begin Printf.printf "\n%d failure(s)\n" !failures;