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
|
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
|
it exact rather than heuristic, because types are only ever introduced by
|
||||||
defstruct, defunion and defalias — all syntactically obvious. *)
|
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) =
|
and is_type_form types (f : Form.t) =
|
||||||
match f.v with
|
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 *)
|
| Vec _ -> true (* [T] and [n T] are only types *)
|
||||||
| Map _ -> true (* {K V} in this position *)
|
| 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
|
| _ -> false
|
||||||
|
|
||||||
and variant (f : Form.t) : Ast.variant =
|
and variant (f : Form.t) : Ast.variant =
|
||||||
@ -797,6 +820,11 @@ let declared_types (forms : Form.t list) : Names.t =
|
|||||||
match f.v with
|
match f.v with
|
||||||
| List [ { v = Sym ("defstruct" | "defunion" | "defalias"); _ };
|
| List [ { v = Sym ("defstruct" | "defunion" | "defalias"); _ };
|
||||||
{ v = Sym n; _ }; _ ] -> Names.add n acc
|
{ 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)
|
| _ -> acc)
|
||||||
builtin_types forms
|
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"
|
outputs ~opt:"-O0" "destructuring, -O0" "programs/destructure.flan"
|
||||||
destructure_out;
|
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"
|
if !failures = 0 then print_endline "acceptance: all tests passed"
|
||||||
else begin
|
else begin
|
||||||
Printf.printf "\n%d failure(s)\n" !failures;
|
Printf.printf "\n%d failure(s)\n" !failures;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user