vec-new and map-new take a type expression where they take a type, so (vec-new [u8]) makes a Vec of byte slices
This commit is contained in:
parent
b4e19bebf8
commit
aa629da2e5
@ -97,6 +97,12 @@ and expr_kind =
|
||||
fails on an unknown name. This is that position's answer, and it says what
|
||||
it does rather than looking like a vector of two things. *)
|
||||
| ArrayOf of texpr (* the whole array type, built by Parse *)
|
||||
(* (vec-new [u8]) and (map-new string [u8]) — a type written where an
|
||||
argument goes. Only the type positions of those two forms read one, and
|
||||
only when the form's shape says type and not value: brackets, or a
|
||||
parenthesised Ptr, Option, Vec, Map, Fn or CFn. A bare name stays a
|
||||
[Var], which the checker already answers as a type. *)
|
||||
| TypeArg of texpr
|
||||
(* (array-fill [r c] v) and (array-gen [r c] f) — a fixed array of any rank
|
||||
as an *expression*, which is what [ArrayOf] and [dotimes] between them
|
||||
could not be: [ArrayOf] produces the zeroed value only, and [dotimes] is
|
||||
@ -403,7 +409,7 @@ let map_children f (e : expr) : expr =
|
||||
let kind =
|
||||
match e.e with
|
||||
| Int _ | Float _ | Byte _ | Str _ | Kw _ | Quote _ | Var _ | ArrayOf _
|
||||
| Break _ | Continue _ -> e.e
|
||||
| TypeArg _ | Break _ | Continue _ -> e.e
|
||||
| Do es -> Do (List.map ex es)
|
||||
| Let (bs, es) -> Let (List.map bind bs, List.map ex es)
|
||||
| If (c, a, b) -> If (ex c, ex a, Option.map ex b)
|
||||
|
||||
31
lib/check.ml
31
lib/check.ml
@ -3547,6 +3547,11 @@ let rec check ctx ?want (e : Ast.expr) : Tast.expr =
|
||||
| Ast.ArrayOf t ->
|
||||
let ty = resolve ctx.env t in
|
||||
expect ctx loc ~want (mk loc ty (Tast.Zero ty))
|
||||
(* Parse writes one only into a type position of vec-new or map-new, and
|
||||
those read it before it could get here. *)
|
||||
| Ast.TypeArg _ ->
|
||||
fail loc "internal: a type argument reached the checker outside vec-new or \
|
||||
map-new — this is a compiler bug"
|
||||
| Ast.ArrayFill (dims, v) -> check_array_fill ctx ~want loc dims v
|
||||
| Ast.ArrayGen (dims, f) -> check_array_gen ctx ~want loc dims f
|
||||
| Ast.Match (scrutinee, arms) -> check_match ctx ~tail ?want loc scrutinee arms
|
||||
@ -6564,12 +6569,15 @@ and type_named ctx n =
|
||||
|| Hashtbl.mem ctx.env.enums n
|
||||
|| Hashtbl.mem ctx.env.aliases n
|
||||
|
||||
(* The element type for [vec-new]: a leading bare symbol naming a type, or the
|
||||
expectation at the site. A bare symbol shadowed by a local or a global is
|
||||
that binding — an allocator, in practice — and not a type. *)
|
||||
(* The element type for [vec-new]: a leading bare symbol naming a type, a
|
||||
leading type expression — [(vec-new [u8])], [(vec-new (Ptr Cell))], which
|
||||
Parse has already read as one — or the expectation at the site. A bare
|
||||
symbol shadowed by a local or a global is that binding — an allocator, in
|
||||
practice — and not a type. *)
|
||||
and vec_new_elem ctx ~want loc args =
|
||||
let named =
|
||||
match args with
|
||||
| { Ast.e = Ast.TypeArg t; _ } :: rest -> Some (resolve ctx.env t, rest)
|
||||
| { Ast.e = Ast.Var n; _ } :: rest
|
||||
when lookup ctx n = None
|
||||
&& (not (Hashtbl.mem ctx.env.globals n))
|
||||
@ -6604,10 +6612,21 @@ and map_new_types ctx ~want loc args =
|
||||
&& (not (Hashtbl.mem ctx.env.globals n))
|
||||
&& type_named ctx n
|
||||
in
|
||||
(* A type position holds a bare name or a type expression Parse has read
|
||||
as one, as [vec-new]'s does. *)
|
||||
let as_type (a : Ast.expr) =
|
||||
match a.Ast.e with
|
||||
| Ast.TypeArg t -> Some (resolve ctx.env t)
|
||||
| Ast.Var n when is_type n -> Some (resolve_name ctx.env ~seen:[] loc n)
|
||||
| _ -> None
|
||||
in
|
||||
match args with
|
||||
| { Ast.e = Ast.Var k; _ } :: { Ast.e = Ast.Var v; _ } :: rest
|
||||
when is_type k && is_type v ->
|
||||
resolve_name ctx.env ~seen:[] loc k, resolve_name ctx.env ~seen:[] loc v, rest
|
||||
| k :: v :: rest when as_type k <> None && as_type v <> None ->
|
||||
Option.get (as_type k), Option.get (as_type v), rest
|
||||
| { Ast.e = Ast.TypeArg _; _ } :: _ ->
|
||||
fail loc
|
||||
"(map-new) names a key and no value — write both, as (map-new string \
|
||||
i32), or give the binding a type"
|
||||
| { Ast.e = Ast.Var k; _ } :: rest when is_type k && rest = [] ->
|
||||
fail loc
|
||||
"(map-new %s) names a key and no value — write both, as (map-new %s \
|
||||
|
||||
@ -307,6 +307,7 @@ let rec rename_expr owned alias bound (e : Ast.expr) : Ast.expr =
|
||||
Ast.MapLit (tag, List.map (fun (k, v) -> (go k, go v)) kvs)
|
||||
| Ast.Arr items -> Ast.Arr (gos items)
|
||||
| Ast.ArrayOf t -> Ast.ArrayOf (rename_texpr owned alias t)
|
||||
| Ast.TypeArg t -> Ast.TypeArg (rename_texpr owned alias t)
|
||||
(* The dimensions too, for the reason [rename_texpr] gives about the one
|
||||
inside [Tarray]: a dimension written as a name is an ordinary
|
||||
compile-time constant of the package and has to be qualified like any
|
||||
@ -787,7 +788,7 @@ let rec expr_uses acc (e : Ast.expr) =
|
||||
| Ast.Bare kvs -> List.iter (fun (_, v) -> go v) kvs
|
||||
| Ast.MapLit (_, kvs) -> List.iter (fun (k, v) -> go k; go v) kvs
|
||||
| Ast.Arr items -> gos items
|
||||
| Ast.ArrayOf t -> texpr_uses acc t
|
||||
| Ast.ArrayOf t | Ast.TypeArg t -> texpr_uses acc t
|
||||
(* A dimension written as a name is a use of that constant, exactly as it is
|
||||
inside [Tarray]. *)
|
||||
| Ast.ArrayFill (ds, v) | Ast.ArrayGen (ds, v) ->
|
||||
|
||||
27
lib/parse.ml
27
lib/parse.ml
@ -425,6 +425,33 @@ and form f mk (head : Form.t) (args : Form.t list) : Ast.expr =
|
||||
| [ target; value ] -> mk (Ast.Set (place target, expr value))
|
||||
| _ -> fail f "set is (set place value)")
|
||||
|
||||
(* ── (vec-new [u8]) and (map-new string [u8]) ───────────────────────
|
||||
The type positions of these two take a type expression as well as a bare
|
||||
name. A bare name is left for the checker, which knows whether it names a
|
||||
type or an allocator; a bracket or a parenthesised type constructor can
|
||||
only be a type there, so it is read as one now, with [texpr], the reader
|
||||
a parameter list's types go through. *)
|
||||
| Sym (("vec-new" | "builtin/vec-new" | "map-new" | "builtin/map-new") as n) ->
|
||||
let slots =
|
||||
if n = "vec-new" || n = "builtin/vec-new" then 1 else 2
|
||||
in
|
||||
let is_type (a : Form.t) =
|
||||
match a.v with
|
||||
| Vec _ -> true
|
||||
| List ({ v = Sym ("Ptr" | "Option" | "Vec" | "Map" | "Fn" | "CFn"); _ }
|
||||
:: _ :: _) -> true
|
||||
| _ -> false
|
||||
in
|
||||
let args =
|
||||
List.mapi
|
||||
(fun i a ->
|
||||
if i < slots && is_type a then
|
||||
{ Ast.e = Ast.TypeArg (texpr a); loc = a.loc }
|
||||
else expr a)
|
||||
args
|
||||
in
|
||||
mk (Ast.Call (expr head, args))
|
||||
|
||||
(* ── (array 4 rl/Vector2) ───────────────────────────────────────────
|
||||
A zeroed fixed array, told its count and its element type. The type
|
||||
spelling [4 rl/Vector2] is unchanged and still works everywhere a type is
|
||||
|
||||
25
test/programs/vec-new-type.flan
Normal file
25
test/programs/vec-new-type.flan
Normal file
@ -0,0 +1,25 @@
|
||||
;;;; vec-new and map-new take a type expression where they take a type, so a
|
||||
;;;; local can hold a Vec of slices, of arrays or of pointers with nothing
|
||||
;;;; else naming the element type. The last Vec names an allocator after its
|
||||
;;;; type, which is the one argument that may follow.
|
||||
|
||||
(defn main [] i32
|
||||
(let [a (arena-new 4096)
|
||||
words (vec-new [u8])
|
||||
pairs (vec-new [2 i32])
|
||||
ptrs (vec-new (Ptr i32))
|
||||
opts (vec-new (Option i64) a)
|
||||
m (map-new string [u8])
|
||||
x (i32 7)]
|
||||
(push words (bytes-view "ab"))
|
||||
(push words (bytes-view "cde"))
|
||||
(push pairs [3 4])
|
||||
(push ptrs (addr x))
|
||||
(push opts (Some (i64 9)))
|
||||
(put m "k" (bytes-view "xyz"))
|
||||
(println (length words) (length (at words 1))
|
||||
(at (at pairs 0) 1) (deref (at ptrs 0))
|
||||
(match (at opts 0) (Some v) v None -1)
|
||||
(match (get m "k") (Some v) (length v) None -1))
|
||||
(free words) (free pairs) (free ptrs) (free m))
|
||||
0)
|
||||
@ -502,6 +502,12 @@ let () =
|
||||
rebinds every name at once, and interleaved writes would print 1. *)
|
||||
outputs "loop and recur" "programs/recur.flan"
|
||||
"10\n2\n21\n8\n10000000\n64\n012\n0\n4\n012\n6\n17\n";
|
||||
(* A type expression where vec-new and map-new take a type: a slice, an
|
||||
array, a pointer and an option, with nothing else naming the element. *)
|
||||
outputs "vec-new takes a type expression" "programs/vec-new-type.flan"
|
||||
"2 3 4 7 9 3\n";
|
||||
outputs ~x86:true "vec-new takes a type expression, x86"
|
||||
"programs/vec-new-type.flan" "2 3 4 7 9 3\n";
|
||||
(* into. The count of pulls is the assertion a unit test cannot make: one
|
||||
pass, one call per element per stage it reaches, and no intermediate
|
||||
collection anywhere. The two show lines either side of it are the same
|
||||
|
||||
@ -6031,6 +6031,13 @@ let () =
|
||||
rejects_check "vec-new with no element type and nothing to take one from"
|
||||
~needle:"nothing here says what (vec-new) is a Vec of"
|
||||
"(defn f [x $t] i32 (do x (let [v (vec-new)] (free v) 0)))";
|
||||
(* A type expression in a type position, generic or not. *)
|
||||
accepts "vec-new over a slice of a type variable"
|
||||
"(defn f [x [$t]] i32 (let [v (vec-new [$t])] (push v x) \
|
||||
(let [n (length v)] (free v) n)))";
|
||||
rejects_check "map-new with a key type expression and no value type"
|
||||
~needle:"(map-new) names a key and no value"
|
||||
"(defn f [] i32 (let [m (map-new [u8])] (free m) 0))";
|
||||
(* And a sigil on a name nothing binds is answered as the unbound variable
|
||||
it is, rather than as a missing element type — with the names that *are*
|
||||
bound, because inside a signature that introduces one the mistake is
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user