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
|
=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.
|
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]
|
** 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
|
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
|
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
|
** DONE A mixed array literal with no want is a dyn vector
|
||||||
CLOSED: [2026-09-25]
|
CLOSED: [2026-09-25]
|
||||||
Elements that agree, numbers meeting at the wider, are typed; elements that mix
|
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
|
* 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.
|
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
|
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
|
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 =
|
and arr_elem_type ctx (items : Ast.expr list) : Types.t option =
|
||||||
let natural (i : Ast.expr) =
|
let natural (i : Ast.expr) =
|
||||||
match i.Ast.e with
|
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. *)
|
the one worth reading. *)
|
||||||
| _, first :: _ -> ignore (check ctx first); None
|
| _, first :: _ -> ignore (check ctx first); None
|
||||||
| [], [] -> None)
|
| [], [] -> None)
|
||||||
else List.fold_left
|
else
|
||||||
(fun found c -> match found with Some _ -> found | None -> settle c)
|
match
|
||||||
None candidates
|
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
|
(* 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
|
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
|
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
|
both callers, so the next kind of type added cannot be added to one of
|
||||||
them. *)
|
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 =
|
and type_named ctx n =
|
||||||
(* A type variable names a type here too, which is what lets [(vec-new t)]
|
(* 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
|
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
|
expect ctx loc ~want
|
||||||
(List.fold_left (fun acc arg -> pick acc (check ctx ~want:ty arg))
|
(List.fold_left (fun acc arg -> pick acc (check ctx ~want:ty arg))
|
||||||
(pick a b) rest)
|
(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
|
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
|
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 smallest positive one. *)
|
||||||
the prelude's reduction of a slice, and the call is an ordinary one —
|
| "max-value" | "min-value" ->
|
||||||
the same split [vec-new] makes between a type and an allocator. *)
|
arity ctx loc name 1 args;
|
||||||
| ("max-of" | "min-of")
|
if not (type_arg ctx (List.hd args)) then
|
||||||
when (match args with
|
fail (List.hd args).Ast.loc "%s takes a type, as in (%s i32)" name name;
|
||||||
| [ 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) ->
|
|
||||||
let a = List.hd args in
|
let a = List.hd args in
|
||||||
let ty =
|
let ty =
|
||||||
match type_of_expr a, a.Ast.e with
|
match type_of_expr a, a.Ast.e with
|
||||||
| Some t, _ -> resolve ctx.env t
|
| Some t, _ -> resolve ctx.env t
|
||||||
| _, Ast.Var n -> resolve_name ctx.env ~seen:[] a.Ast.loc n
|
| _, 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
|
in
|
||||||
let max = String.equal name "max-of" in
|
let max = String.equal name "max-value" in
|
||||||
let v =
|
let v =
|
||||||
match ty with
|
match ty with
|
||||||
| Types.Int k ->
|
| Types.Int k ->
|
||||||
@ -10734,6 +10791,13 @@ let builtins : (string * string * string) list =
|
|||||||
i16-y) is an i16.");
|
i16-y) is an i16.");
|
||||||
("max", "max [ordered? ...] ordered?",
|
("max", "max [ordered? ...] ordered?",
|
||||||
"The largest of two or more operands, each evaluated exactly once.");
|
"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",
|
("zeroed", "zeroed [] T",
|
||||||
"The all-bytes-zero value of whatever it is being stored into, so it \
|
"The all-bytes-zero value of whatever it is being stored into, so it \
|
||||||
only means anything where a type is expected of 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 [+]
|
;; or more numbers, and a defn cannot shadow a builtin: nothing shadows [+]
|
||||||
;; either. These reduce a slice, which is a different operation with a
|
;; either. These reduce a slice, which is a different operation with a
|
||||||
;; different arity, so the different name is honest rather than a workaround.
|
;; 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
|
;; A type's own limits are (min-value T) and (max-value T).
|
||||||
;; limits, and the checker answers those itself.
|
|
||||||
(defn min-of [s [$t]] (Option $t)
|
(defn min-of [s [$t]] (Option $t)
|
||||||
{:where (ordered? $t)}
|
{:where (ordered? $t)}
|
||||||
(if (= (length s) 0)
|
(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 concrete type and inside a generic whose bound admits numbers.
|
||||||
|
|
||||||
;; A selection sort, descending, whose running best starts at the least value
|
;; A selection sort, descending, whose running best starts at the least value
|
||||||
@ -6,7 +6,7 @@
|
|||||||
(defn sort-desc [s [$t]] ()
|
(defn sort-desc [s [$t]] ()
|
||||||
{:where (numeric? $t)}
|
{:where (numeric? $t)}
|
||||||
(dotimes [i (length s)]
|
(dotimes [i (length s)]
|
||||||
(let [best (min-of $t)
|
(let [best (min-value $t)
|
||||||
at-best i]
|
at-best i]
|
||||||
(dotimes [j (- (length s) i)]
|
(dotimes [j (- (length s) i)]
|
||||||
(let [k (+ i j)]
|
(let [k (+ i j)]
|
||||||
@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
(defn largest [s [$t]] $t
|
(defn largest [s [$t]] $t
|
||||||
{:where (numeric? $t)}
|
{:where (numeric? $t)}
|
||||||
(let [best (min-of t)]
|
(let [best (min-value t)]
|
||||||
(dotimes [i (length s)]
|
(dotimes [i (length s)]
|
||||||
(when (> (at s i) best) (set best (at s i))))
|
(when (> (at s i) best) (set best (at s i))))
|
||||||
best))
|
best))
|
||||||
@ -31,16 +31,16 @@
|
|||||||
(println ""))
|
(println ""))
|
||||||
|
|
||||||
(defn main [] i32
|
(defn main [] i32
|
||||||
(println (max-of u8))
|
(println (max-value u8))
|
||||||
(println (min-of u8))
|
(println (min-value u8))
|
||||||
(println (max-of i8))
|
(println (max-value i8))
|
||||||
(println (min-of i8))
|
(println (min-value i8))
|
||||||
(println (max-of i32))
|
(println (max-value i32))
|
||||||
(println (min-of i64))
|
(println (min-value i64))
|
||||||
(println (max-of u64))
|
(println (max-value u64))
|
||||||
(println (= (max-of f32) f32-max))
|
(println (= (max-value f32) f32-max))
|
||||||
(println (= (min-of f64) (- f64-max)))
|
(println (= (min-value f64) (- f64-max)))
|
||||||
(println (= (max-of i16) i16-max))
|
(println (= (max-value i16) i16-max))
|
||||||
(let [a [(i32 3) -7 12 0 -2147483648 5]
|
(let [a [(i32 3) -7 12 0 -2147483648 5]
|
||||||
b [2.5 -1.0 1e300 -1e308]
|
b [2.5 -1.0 1e300 -1e308]
|
||||||
c [(u8 4) 0 200 9]]
|
c [(u8 4) 0 200 9]]
|
||||||
@ -585,14 +585,14 @@ let () =
|
|||||||
outputs "a prelude function shadowed" "programs/shadow-prelude.flan" sp_out;
|
outputs "a prelude function shadowed" "programs/shadow-prelude.flan" sp_out;
|
||||||
outputs ~x86:true "a prelude function shadowed, x86"
|
outputs ~x86:true "a prelude function shadowed, x86"
|
||||||
"programs/shadow-prelude.flan" sp_out;
|
"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 =
|
let maxof_out =
|
||||||
"255\n0\n127\n-128\n2147483647\n-9223372036854775808\n\
|
"255\n0\n127\n-128\n2147483647\n-9223372036854775808\n\
|
||||||
18446744073709551615\ntrue\ntrue\ntrue\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
|
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 "max-value and min-value" "programs/max-value.flan" maxof_out;
|
||||||
outputs ~opt:"-O0" "max-of and min-of, -O0" "programs/max-of.flan" maxof_out;
|
outputs ~opt:"-O0" "max-value and min-value, -O0" "programs/max-value.flan" maxof_out;
|
||||||
outputs ~x86:true "max-of and min-of, x86" "programs/max-of.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. *)
|
(* (- x) negates, on every numeric type, a type variable and a dyn. *)
|
||||||
let neg_out =
|
let neg_out =
|
||||||
"-3\n7\n-2.5\n-inf\n-1.5\n255\n-4\n-2.5\n-inf\n-9000000000\n\
|
"-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) ->
|
(fun (n : Loc.note) ->
|
||||||
contains n.Loc.nmsg "this array's first element is $t")
|
contains n.Loc.nmsg "this array's first element is $t")
|
||||||
d.Loc.notes));
|
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"
|
rejects_check "every element needing a type names the first's refusal"
|
||||||
"(defn main [] i32 (let [a [None None]] 0))"
|
"(defn main [] i32 (let [a [None None]] 0))"
|
||||||
~needle:"what None is an Option of";
|
~needle:"what None is an Option of";
|
||||||
|
|
||||||
(* ── (max-of T) and (min-of T) ──────────────────────────────────── *)
|
(* ── (max-value T) and (min-value T) ──────────────────────────────── *)
|
||||||
infers "max-of carries its type" "(max-of u16)" "u16";
|
infers "max-value carries its type" "(max-value u16)" "u16";
|
||||||
infers "min-of at a float" "(min-of f32)" "f32";
|
infers "min-value at a float" "(min-value f32)" "f32";
|
||||||
(match checked "(defn f [x $t] $t (max-of $t))" with
|
(match checked "(defn f [x $t] $t (max-value $t))" with
|
||||||
| _ -> check "max-of at an unbounded type variable is refused" false
|
| _ -> check "max-value at an unbounded type variable is refused" false
|
||||||
| exception Loc.Error d ->
|
| 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)}"
|
(contains d.Loc.dmsg "write {:where (numeric? $t)}"
|
||||||
&& not (contains d.Loc.dmsg "Fn")));
|
&& not (contains d.Loc.dmsg "Fn")));
|
||||||
infers "two literal if arms meet at the wider" "(if true 1 2.5)" "f64";
|
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 integer if arms stay i32" "(if true 1 2)" "i32";
|
||||||
infers "two literal match arms meet at the wider"
|
infers "two literal match arms meet at the wider"
|
||||||
"(match (Some 1) (Some v) 1 None 2.5)" "f64";
|
"(match (Some 1) (Some v) 1 None 2.5)" "f64";
|
||||||
accepts "max-of at a type variable the bound admits"
|
accepts "max-value at a type variable the bound admits"
|
||||||
"(defn f [x $t] $t {:where (integer? $t)} (max-of t))";
|
"(defn f [x $t] $t {:where (integer? $t)} (max-value t))";
|
||||||
rejects_check "max-of at a type that is not a number names the bound"
|
rejects_check "max-value at a type that is not a number names the bound"
|
||||||
"(defn f [] string (max-of string))"
|
"(defn f [] string (max-value string))"
|
||||||
~needle:"max-of takes a numeric? type, and string is not one";
|
~needle:"max-value takes a numeric? type, and string is not one";
|
||||||
accepts "max-of of a slice is still the prelude's reduction"
|
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))";
|
"(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) ─────────────────────────────────────────────────── *)
|
(* ── (the T e) ─────────────────────────────────────────────────── *)
|
||||||
infers "the gives a literal its type" "(the u8 200)" "u8";
|
infers "the gives a literal its type" "(the u8 200)" "u8";
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user