A parameter vector paired by a lowercase type the program declares is warned at, naming the type and where it is declared

This commit is contained in:
Joseph Ferano 2026-09-25 15:37:26 +07:00
parent 1716b146cd
commit e616a29ceb
3 changed files with 83 additions and 10 deletions

View File

@ -854,13 +854,6 @@ Already fixed by 3672da2, which blames the arm that is not a compiler temp; the
caret is on the last operand and =test/test_flan.ml= asserts its column. Rules
out relabelling the else arm, a bool sentinel, and inverting the condition.
** NEXT Signature pairing's cold-rebuild edge
Decided 2026-09-25: the type takes precedence, as today. The warning is at the parameter site: where a name in a parameter vector is read as a program-declared type but could also have been read as a parameter name, the parameter vector gets a warning naming the type and where it is declared.
Whether a parameter vector reads as one annotated parameter or two dyn ones
depends on what type names exist, so adding a type can silently re-pair an
existing signature between compiles. A changed-pairing warning was proposed and
not queued.
** DONE A typed container crosses into dyn as a view, and only from permanent storage
CLOSED: [2026-09-20]
The descriptor is pointer, length and element type — a slice plus the piece a

View File

@ -1631,9 +1631,37 @@ let dyn_param_or_typo env n loc =
parameters are lowercase"
n
let pair_params ?(also = fun _ -> false) env (items : Ast.pitem list)
: Ast.field list =
(* The pairings a parameter vector owes to a type the program declares under
a name that is also a legal parameter name — a lowercase one, since a
capitalised name is refused as a parameter. [(defn f [p point] ...)] is one
parameter while [point] is a type and two dyn ones the moment it is not,
so adding or removing the type re-pairs the signature with no edit to it.
The type still wins; this is the warning at the parameter, filled by
[pair_decls] and printed by [build_program] with the other warnings. *)
let pairing_warnings : Loc.diag list ref = ref []
let pair_params ?(also = fun _ -> false) ?(declared = fun _ -> None) env
(items : Ast.pitem list) : Ast.field list =
let is_type_name env n = is_type_name env n || also n in
let warn_pairing n t tloc =
let bare =
match String.rindex_opt t '/' with
| Some i -> String.sub t (i + 1) (String.length t - i - 1)
| None -> t
in
match declared t with
| Some (what, (at : Loc.t))
when bare <> "" && bare.[0] >= 'a' && bare.[0] <= 'z' ->
pairing_warnings :=
Loc.diag ~kind:"check/parameter-reads-a-type" tloc
(Printf.sprintf
"[%s %s] is one parameter %s of type %s, the %s declared at %s, \
and not two dyn parameters. If two were meant, give the second \
a name no type has"
n t n t what (Loc.to_string at))
:: !pairing_warnings
| _ -> ()
in
let dyn loc = { Ast.t = Ast.Tname "dyn"; tloc = loc } in
let rec go = function
| [] -> []
@ -1649,6 +1677,7 @@ let pair_params ?(also = fun _ -> false) env (items : Ast.pitem list)
| Ast.Pname (n, loc) :: Ast.Ptype t :: rest ->
{ Ast.fname = n; fty = t; floc = loc } :: go rest
| Ast.Pname (n, loc) :: Ast.Pname (t, tloc) :: rest when is_type_name env t ->
warn_pairing n t tloc;
{ Ast.fname = n; fty = { Ast.t = Ast.Tname t; tloc }; floc = loc } :: go rest
(* The slot after this one is not a type, so this one is a parameter with
no type written — unless the slot after it only *looks* unlike a type
@ -1778,10 +1807,32 @@ let pair_decls env (decls : Ast.decl list) : Ast.decl list =
match d.Ast.d with Ast.Defclass (n, _) -> Some n | _ -> None)
decls
in
(* The types the program declares, with what kind and where, for
[pair_params]'s warning. The prelude's are left out: its names are the
language's, not a declaration the reader made. *)
let types = Hashtbl.create 16 in
List.iter
(fun (d : Ast.decl) ->
let add n what =
if d.Ast.dloc.Loc.file <> Prelude.file then
Hashtbl.replace types n (what, d.Ast.dloc)
in
match d.Ast.d with
| Ast.Defstruct (n, _, _) -> add n "struct"
| Ast.Defenum (n, _) -> add n "enum"
| Ast.Defalias (n, _) -> add n "alias"
| Ast.Defdata (n, _) -> add n "data type"
| Ast.Defunion (n, _) -> add n "union"
| _ -> ())
decls;
pairing_warnings := [];
let fn (f : Ast.fn) =
match f.Ast.praw with
| None -> f
| Some items -> { f with Ast.params = pair_params env items; praw = None }
| Some items ->
{ f with
Ast.params = pair_params ~declared:(Hashtbl.find_opt types) env items;
praw = None }
in
List.map
(fun (d : Ast.decl) ->
@ -14108,6 +14159,12 @@ let build_program ~keep_going ?tolerate (decls : Ast.decl list) :
cannot make the next body fail — which is what makes a declaration a
resync point that needs no resynchronising. *)
let decls = collect env decls in
if !print_warnings then
List.iter
(fun (d : Loc.diag) ->
prerr_endline
(Loc.entry ~mark:'~' ~label:"warning: " d.Loc.dloc d.Loc.dmsg))
(List.rev !pairing_warnings);
check_finite env;
check_union_members env;
let s = Loc.sink ~on:keep_going in

View File

@ -5814,6 +5814,29 @@ let () =
(match checked shadow_src with
| _ -> true
| exception Loc.Error _ -> false);
(* A parameter vector paired by a lowercase type the program declares reads
as two dyn parameters the day the type goes, so the pairing is warned at,
naming the type and where it is declared. A capitalised type cannot be a
parameter name, so it has nothing to warn about. *)
(match
checked "(defstruct point [x i32])\n(defstruct Vec2 [x i32])\n\
(defn px [p point] i32 (.x p))\n(defn vx [v Vec2] i32 (.x v))"
with
| _ ->
(match !Check.pairing_warnings with
| [ d ] ->
check "a lowercase declared type in a parameter vector is warned at"
(d.Loc.kind = "check/parameter-reads-a-type"
&& d.Loc.dloc.Loc.line = 3 && d.Loc.dloc.Loc.col = 13
&& d.Loc.dmsg
= "[p point] is one parameter p of type point, the struct \
declared at <test>:1:1, and not two dyn parameters. If two \
were meant, give the second a name no type has")
| ds ->
check
(Printf.sprintf "one pairing warning, not %d" (List.length ds))
false)
| exception Loc.Error _ -> check "the paired program checks" false);
check "a program that shadows nothing is warned at not at all"
(Check.shadowed_builtins (program "(defn f [] i32 1)") = []);
(* A prelude function's name is taken over the same way, for the calls in