A numeric type's limits are (max-value T) and (min-value T), and an array literal of numbers with no common type is refused with the conversion named
This commit is contained in:
parent
cc95006024
commit
325c3662a8
8
TODO.org
8
TODO.org
@ -1079,11 +1079,6 @@ An unknown call whose near miss is a value — =(context-allocator)= against
|
||||
=context/allocator=, or a global — says the name is a value written without
|
||||
parentheses, and names no call at all when the call had arguments.
|
||||
|
||||
** NEXT (max-value T) and (min-value T)
|
||||
Decided 2026-09-25: the type-limit constants as a form taking a type, Odin's
|
||||
max(T), valid at any numeric type or a numeric?-bounded variable. For a float,
|
||||
min-of is the most negative finite value.
|
||||
|
||||
** NEXT (Ptr const T), the pointer beside [const T]
|
||||
Decided 2026-09-25: addr through a read-only slice gives a (Ptr const T), which
|
||||
nothing writes through; (Ptr T) widens to it and never back; a C parameter
|
||||
@ -1565,7 +1560,8 @@ static tracking of destroy, which is move semantics.
|
||||
** DONE A mixed array literal with no want is a dyn vector
|
||||
CLOSED: [2026-09-25]
|
||||
Elements that agree, numbers meeting at the wider, are typed; elements that mix
|
||||
are a dyn vector. Rules out the first element typing the rest.
|
||||
are a dyn vector, except numbers with no common type, which are refused. Rules
|
||||
out the first element typing the rest.
|
||||
|
||||
* Dev loop
|
||||
|
||||
|
||||
106
lib/check.ml
106
lib/check.ml
@ -5792,7 +5792,8 @@ and check_arr ctx ~want loc items =
|
||||
being told what it is — [None], a bare struct — takes the same type.
|
||||
Elements that do not agree — [[10 "Hi"]], a dyn beside anything that is
|
||||
not one — are a dyn vector, which is what the same brackets are where a
|
||||
dyn is expected. *)
|
||||
dyn is expected. Numbers that do not agree are refused instead; see
|
||||
[numbers_disagree]. *)
|
||||
and arr_elem_type ctx (items : Ast.expr list) : Types.t option =
|
||||
let natural (i : Ast.expr) =
|
||||
match i.Ast.e with
|
||||
@ -5854,9 +5855,52 @@ and arr_elem_type ctx (items : Ast.expr list) : Types.t option =
|
||||
the one worth reading. *)
|
||||
| _, first :: _ -> ignore (check ctx first); None
|
||||
| [], [] -> None)
|
||||
else List.fold_left
|
||||
(fun found c -> match found with Some _ -> found | None -> settle c)
|
||||
None candidates
|
||||
else
|
||||
match
|
||||
List.fold_left
|
||||
(fun found c -> match found with Some _ -> found | None -> settle c)
|
||||
None candidates
|
||||
with
|
||||
| Some t -> Some t
|
||||
| None ->
|
||||
let numeric t = match t with Types.Int _ | Types.Float _ -> true | _ -> false in
|
||||
if needs = [] && List.for_all numeric (tys @ lit_tys) then
|
||||
numbers_disagree ctx
|
||||
(List.filter_map
|
||||
(fun i -> Option.map (fun t -> (i, t)) (natural i)) items)
|
||||
else None
|
||||
|
||||
(* Numbers with no type they all meet at — an i32 beside an f32, an i64 beside
|
||||
a u64 — are refused rather than boxed into a dyn vector: the elements are
|
||||
all numbers, and which one should move is the program's to say. The fix
|
||||
named converts the second of the first disagreeing pair, into the float
|
||||
when one of the two is a float and into the first's type otherwise. *)
|
||||
and numbers_disagree : 'a. ctx -> (Ast.expr * Types.t) list -> 'a =
|
||||
fun ctx elems ->
|
||||
match elems with
|
||||
| [] -> fail Loc.unknown "internal: an array of numbers with no elements"
|
||||
| (first, t1) :: rest ->
|
||||
let second, t2 =
|
||||
match List.find_opt (fun (_, t) -> Types.join t1 t = None) rest with
|
||||
| Some p -> p
|
||||
| None -> List.nth elems (List.length elems - 1)
|
||||
in
|
||||
let target, moved, moved_ty, other =
|
||||
match t1, t2 with
|
||||
| Types.Int _, Types.Float _ -> t2, first, t1, second
|
||||
| _ -> t1, second, t2, first
|
||||
in
|
||||
ignore ctx;
|
||||
let tn = Types.to_string target in
|
||||
Loc.failk "check/array-numbers-disagree" moved.Ast.loc
|
||||
~notes:[ Loc.note other.Ast.loc (Printf.sprintf "this element is %s" tn) ]
|
||||
"this array's elements are %s and %s, and neither holds every value of \
|
||||
the other — %s"
|
||||
(Types.to_string moved_ty) tn
|
||||
(match spell_arg "" moved with
|
||||
| "" ->
|
||||
Printf.sprintf "convert the %s element with the %s cast" (Types.to_string moved_ty) tn
|
||||
| x -> Printf.sprintf "convert one, as in (%s %s)" tn x)
|
||||
|
||||
(* Elements that do not agree and cannot all become a dyn either: a struct
|
||||
beside a number, a type variable beside a literal. The dyn vector's refusal
|
||||
@ -7263,6 +7307,16 @@ and file_guard ctx loc ~path_slot ~op mk_steps =
|
||||
missing annotation for a program that had written one. One list, read by
|
||||
both callers, so the next kind of type added cannot be added to one of
|
||||
them. *)
|
||||
(* An argument written as a type: a type expression, or a bare name that is a
|
||||
type and not a local or a global of the same spelling. *)
|
||||
and type_arg ctx (a : Ast.expr) =
|
||||
type_of_expr a <> None
|
||||
|| (match a.Ast.e with
|
||||
| Ast.Var n ->
|
||||
lookup ctx n = None && (not (Hashtbl.mem ctx.env.globals n))
|
||||
&& type_named ctx n
|
||||
| _ -> false)
|
||||
|
||||
and type_named ctx n =
|
||||
(* A type variable names a type here too, which is what lets [(vec-new t)]
|
||||
and [(vec-new $t)] be written in a generic body: inside an instantiation
|
||||
@ -7783,31 +7837,34 @@ and named_call ?(qualified = false) ctx ~want loc name args =
|
||||
expect ctx loc ~want
|
||||
(List.fold_left (fun acc arg -> pick acc (check ctx ~want:ty arg))
|
||||
(pick a b) rest)
|
||||
(* (max-of T) and (min-of T): the type-limit constants, by type, so a
|
||||
(* A type handed to the prelude's slice reductions: the reach for the
|
||||
type-limit constants under the name of the reduction beside them. *)
|
||||
| ("max-of" | "min-of")
|
||||
when (not (shadows_builtin ctx loc name))
|
||||
&& (match args with [ a ] -> type_arg ctx a | _ -> false) ->
|
||||
let which = if String.equal name "max-of" then "max-value" else "min-value" in
|
||||
fail loc
|
||||
"%s reduces a slice to its %s element, and this is a type — the %s value \
|
||||
of a type is (%s %s)"
|
||||
name (if which = "max-value" then "largest" else "least")
|
||||
(if which = "max-value" then "largest" else "least") which
|
||||
(spell_arg "i32" (List.hd args))
|
||||
(* (max-value T) and (min-value T): the type-limit constants, by type, so a
|
||||
generic body can name its own type's. Odin's max(T) and min(T), and the
|
||||
same answer for a float: the largest finite value and its negation, not
|
||||
the smallest positive one. Given a value rather than a type, the name is
|
||||
the prelude's reduction of a slice, and the call is an ordinary one —
|
||||
the same split [vec-new] makes between a type and an allocator. *)
|
||||
| ("max-of" | "min-of")
|
||||
when (match args with
|
||||
| [ a ] ->
|
||||
type_of_expr a <> None
|
||||
|| (match a.Ast.e with
|
||||
| Ast.Var n ->
|
||||
lookup ctx n = None
|
||||
&& (not (Hashtbl.mem ctx.env.globals n))
|
||||
&& type_named ctx n
|
||||
| _ -> false)
|
||||
| _ -> false) ->
|
||||
the smallest positive one. *)
|
||||
| "max-value" | "min-value" ->
|
||||
arity ctx loc name 1 args;
|
||||
if not (type_arg ctx (List.hd args)) then
|
||||
fail (List.hd args).Ast.loc "%s takes a type, as in (%s i32)" name name;
|
||||
let a = List.hd args in
|
||||
let ty =
|
||||
match type_of_expr a, a.Ast.e with
|
||||
| Some t, _ -> resolve ctx.env t
|
||||
| _, Ast.Var n -> resolve_name ctx.env ~seen:[] a.Ast.loc n
|
||||
| _ -> fail a.Ast.loc "internal: max-of's type argument is not a type"
|
||||
| _ -> fail a.Ast.loc "internal: %s's type argument is not a type" name
|
||||
in
|
||||
let max = String.equal name "max-of" in
|
||||
let max = String.equal name "max-value" in
|
||||
let v =
|
||||
match ty with
|
||||
| Types.Int k ->
|
||||
@ -10734,6 +10791,13 @@ let builtins : (string * string * string) list =
|
||||
i16-y) is an i16.");
|
||||
("max", "max [ordered? ...] ordered?",
|
||||
"The largest of two or more operands, each evaluated exactly once.");
|
||||
("max-value", "max-value [type] T",
|
||||
"The largest value of a numeric type: (max-value u8) is 255, and at a \
|
||||
float the largest finite value. Takes a type variable under \
|
||||
{:where (numeric? $t)}.");
|
||||
("min-value", "min-value [type] T",
|
||||
"The least value of a numeric type: (min-value i8) is -128, 0 at an \
|
||||
unsigned type, and at a float the negation of the largest finite value.");
|
||||
("zeroed", "zeroed [] T",
|
||||
"The all-bytes-zero value of whatever it is being stored into, so it \
|
||||
only means anything where a type is expected of it.");
|
||||
|
||||
@ -427,8 +427,7 @@ let source = {flan|
|
||||
;; or more numbers, and a defn cannot shadow a builtin: nothing shadows [+]
|
||||
;; either. These reduce a slice, which is a different operation with a
|
||||
;; different arity, so the different name is honest rather than a workaround.
|
||||
;; Given a type instead of a slice, (min-of i8) and (max-of $t) are the type's
|
||||
;; limits, and the checker answers those itself.
|
||||
;; A type's own limits are (min-value T) and (max-value T).
|
||||
(defn min-of [s [$t]] (Option $t)
|
||||
{:where (ordered? $t)}
|
||||
(if (= (length s) 0)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
;;;; (max-of T) and (min-of T): a numeric type's limits, named by the type, at
|
||||
;;;; (max-value T) and (min-value T): a numeric type's limits, named by the type, at
|
||||
;;;; a concrete type and inside a generic whose bound admits numbers.
|
||||
|
||||
;; A selection sort, descending, whose running best starts at the least value
|
||||
@ -6,7 +6,7 @@
|
||||
(defn sort-desc [s [$t]] ()
|
||||
{:where (numeric? $t)}
|
||||
(dotimes [i (length s)]
|
||||
(let [best (min-of $t)
|
||||
(let [best (min-value $t)
|
||||
at-best i]
|
||||
(dotimes [j (- (length s) i)]
|
||||
(let [k (+ i j)]
|
||||
@ -17,7 +17,7 @@
|
||||
|
||||
(defn largest [s [$t]] $t
|
||||
{:where (numeric? $t)}
|
||||
(let [best (min-of t)]
|
||||
(let [best (min-value t)]
|
||||
(dotimes [i (length s)]
|
||||
(when (> (at s i) best) (set best (at s i))))
|
||||
best))
|
||||
@ -31,16 +31,16 @@
|
||||
(println ""))
|
||||
|
||||
(defn main [] i32
|
||||
(println (max-of u8))
|
||||
(println (min-of u8))
|
||||
(println (max-of i8))
|
||||
(println (min-of i8))
|
||||
(println (max-of i32))
|
||||
(println (min-of i64))
|
||||
(println (max-of u64))
|
||||
(println (= (max-of f32) f32-max))
|
||||
(println (= (min-of f64) (- f64-max)))
|
||||
(println (= (max-of i16) i16-max))
|
||||
(println (max-value u8))
|
||||
(println (min-value u8))
|
||||
(println (max-value i8))
|
||||
(println (min-value i8))
|
||||
(println (max-value i32))
|
||||
(println (min-value i64))
|
||||
(println (max-value u64))
|
||||
(println (= (max-value f32) f32-max))
|
||||
(println (= (min-value f64) (- f64-max)))
|
||||
(println (= (max-value i16) i16-max))
|
||||
(let [a [(i32 3) -7 12 0 -2147483648 5]
|
||||
b [2.5 -1.0 1e300 -1e308]
|
||||
c [(u8 4) 0 200 9]]
|
||||
@ -585,14 +585,14 @@ let () =
|
||||
outputs "a prelude function shadowed" "programs/shadow-prelude.flan" sp_out;
|
||||
outputs ~x86:true "a prelude function shadowed, x86"
|
||||
"programs/shadow-prelude.flan" sp_out;
|
||||
(* (max-of T) and (min-of T), concrete and inside a generic. *)
|
||||
(* (max-value T) and (min-value T), concrete and inside a generic. *)
|
||||
let maxof_out =
|
||||
"255\n0\n127\n-128\n2147483647\n-9223372036854775808\n\
|
||||
18446744073709551615\ntrue\ntrue\ntrue\n\
|
||||
12 5 3 0 -7 -2147483648 \n1e+300 2.5 -1 -1e+308 \n200\n-5\ntrue\n" in
|
||||
outputs "max-of and min-of" "programs/max-of.flan" maxof_out;
|
||||
outputs ~opt:"-O0" "max-of and min-of, -O0" "programs/max-of.flan" maxof_out;
|
||||
outputs ~x86:true "max-of and min-of, x86" "programs/max-of.flan" maxof_out;
|
||||
outputs "max-value and min-value" "programs/max-value.flan" maxof_out;
|
||||
outputs ~opt:"-O0" "max-value and min-value, -O0" "programs/max-value.flan" maxof_out;
|
||||
outputs ~x86:true "max-value and min-value, x86" "programs/max-value.flan" maxof_out;
|
||||
(* (- x) negates, on every numeric type, a type variable and a dyn. *)
|
||||
let neg_out =
|
||||
"-3\n7\n-2.5\n-inf\n-1.5\n255\n-4\n-2.5\n-inf\n-9000000000\n\
|
||||
|
||||
@ -6546,30 +6546,44 @@ let () =
|
||||
(fun (n : Loc.note) ->
|
||||
contains n.Loc.nmsg "this array's first element is $t")
|
||||
d.Loc.notes));
|
||||
rejects_check "numbers with no common type are refused and the fix named"
|
||||
"(defn f [x i32 y f32] i32 (let [a [x y]] 0))"
|
||||
~needle:"elements are i32 and f32, and neither holds every value of the \
|
||||
other — convert one, as in (f32 x)";
|
||||
accepts "the conversion that refusal names compiles"
|
||||
"(defn f [x i32 y f32] i32 (let [a [(f32 x) y]] 0))";
|
||||
rejects_check "two integer types with no common type are refused"
|
||||
"(defn f [x i64 y u64] i32 (let [a [x y]] 0))" ~needle:"as in (i64 y)";
|
||||
accepts "the integer conversion that refusal names compiles"
|
||||
"(defn f [x i64 y u64] i32 (let [a [x (i64 y)]] 0))";
|
||||
rejects_check "every element needing a type names the first's refusal"
|
||||
"(defn main [] i32 (let [a [None None]] 0))"
|
||||
~needle:"what None is an Option of";
|
||||
|
||||
(* ── (max-of T) and (min-of T) ──────────────────────────────────── *)
|
||||
infers "max-of carries its type" "(max-of u16)" "u16";
|
||||
infers "min-of at a float" "(min-of f32)" "f32";
|
||||
(match checked "(defn f [x $t] $t (max-of $t))" with
|
||||
| _ -> check "max-of at an unbounded type variable is refused" false
|
||||
(* ── (max-value T) and (min-value T) ──────────────────────────────── *)
|
||||
infers "max-value carries its type" "(max-value u16)" "u16";
|
||||
infers "min-value at a float" "(min-value f32)" "f32";
|
||||
(match checked "(defn f [x $t] $t (max-value $t))" with
|
||||
| _ -> check "max-value at an unbounded type variable is refused" false
|
||||
| exception Loc.Error d ->
|
||||
check "max-of at an unbounded type variable names the bound and only it"
|
||||
check "max-value at an unbounded type variable names the bound and only it"
|
||||
(contains d.Loc.dmsg "write {:where (numeric? $t)}"
|
||||
&& not (contains d.Loc.dmsg "Fn")));
|
||||
infers "two literal if arms meet at the wider" "(if true 1 2.5)" "f64";
|
||||
infers "two integer if arms stay i32" "(if true 1 2)" "i32";
|
||||
infers "two literal match arms meet at the wider"
|
||||
"(match (Some 1) (Some v) 1 None 2.5)" "f64";
|
||||
accepts "max-of at a type variable the bound admits"
|
||||
"(defn f [x $t] $t {:where (integer? $t)} (max-of t))";
|
||||
rejects_check "max-of at a type that is not a number names the bound"
|
||||
"(defn f [] string (max-of string))"
|
||||
~needle:"max-of takes a numeric? type, and string is not one";
|
||||
accepts "max-of of a slice is still the prelude's reduction"
|
||||
accepts "max-value at a type variable the bound admits"
|
||||
"(defn f [x $t] $t {:where (integer? $t)} (max-value t))";
|
||||
rejects_check "max-value at a type that is not a number names the bound"
|
||||
"(defn f [] string (max-value string))"
|
||||
~needle:"max-value takes a numeric? type, and string is not one";
|
||||
rejects_check "max-value of a value says it takes a type"
|
||||
"(defn f [x i32] i32 (max-value x))" ~needle:"max-value takes a type";
|
||||
accepts "max-of of a slice is the prelude's reduction"
|
||||
"(defn f [xs [i32]] (Option i32) (max-of xs))";
|
||||
rejects_check "max-of of a type names max-value"
|
||||
"(defn f [] u8 (max-of u8))" ~needle:"the largest value of a type is (max-value u8)";
|
||||
|
||||
(* ── (the T e) ─────────────────────────────────────────────────── *)
|
||||
infers "the gives a literal its type" "(the u8 200)" "u8";
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user