A package struct can be a return type
The parser decides "return type or 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 by construction. (defn mk [] rl/Vector2
...) therefore read the return type as the body and failed with "unknown name
rl/Vector2", which names the symptom and not the cause.
The signal is the alias plus the capital, and both halves are needed. An alias
is syntactically obvious and the same pre-pass collects it. 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 the hazard the
surrounding comment warns about, a body form eaten as a return type, has no
form of this shape to eat. A lowercase qualified name stays an expression,
which is what rl/get-color has to be.
Found by the raylib lane, which hit it on rl/Vector2 and reported it rather
than reaching into a file it did not own.
This commit is contained in:
parent
71877a5baa
commit
90d3d6694e
32
lib/parse.ml
32
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
|
||||
|
||||
|
||||
27
test/programs/pkg-return.flan
Normal file
27
test/programs/pkg-return.flan
Normal file
@ -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)
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user