A program's function named as a prelude function takes the name over for its own file, with a warning, and the prelude's own calls keep the prelude's

This commit is contained in:
Joseph Ferano 2026-09-25 11:51:58 +07:00
parent c671bba490
commit 2ea47f91eb
6 changed files with 121 additions and 3 deletions

View File

@ -129,7 +129,7 @@
(defconst flan--special
'("quote" "do" "let" "if" "when" "cond" "and" "or"
"while" "until" "break" "continue" "return" "set"
"array" "array-fill" "array-gen" "match" "fn" "dotimes" "loop" "recur"
"array" "array-fill" "array-gen" "the" "match" "fn" "dotimes" "loop" "recur"
"defer" "some" "try" "signal" "error"
"handler-bind" "handler-case" "restart-case" "invoke-restart")
"The heads `Parse.form' dispatches on — the forms with a meaning of their own.

View File

@ -12436,9 +12436,59 @@ let escape_check (fn : Tast.fn) =
deny "a return" [ last ]
| _ -> ())
(* A program's function named as a prelude function takes the name over, the
way a definition of a builtin's name does: every call written in the file
that defines it reaches the program's, and every call anywhere else — the
prelude's own among them, which were written against the prelude's
signature — keeps reaching the prelude's. The prelude's is renamed out of
the way, under a qualifier no source can spell, rather than dropped.
Functions only: a type or a global of the prelude's name is still defined
twice. *)
let prelude_alias = "prelude~"
let shadow_prelude (prelude : Ast.decl list) (decls : Ast.decl list) =
let fn_name (d : Ast.decl) =
match d.Ast.d with
| Ast.Defn fn | Ast.Declare (fn, _) | Ast.DeclareC (fn, _) -> Some fn.Ast.name
| _ -> None
in
let theirs = List.filter_map fn_name prelude in
let taken =
List.filter_map
(fun (d : Ast.decl) ->
match fn_name d with
| Some n when List.mem n theirs -> Some (n, d.Ast.dloc)
| _ -> None)
decls
in
let warnings =
List.map
(fun (n, at) ->
Loc.diag ~kind:"check/shadows-prelude" at
(Printf.sprintf
"%s shadows the prelude's %s — every call in this file now \
reaches your definition"
n n))
taken
in
let prelude, decls =
List.fold_left
(fun (prelude, decls) (n, (at : Loc.t)) ->
( List.map (Load.rename_refs [ n ] prelude_alias) prelude,
List.map
(fun (d : Ast.decl) ->
if String.equal d.Ast.dloc.Loc.file at.Loc.file then d
else Load.rename_refs [ n ] prelude_alias d)
decls ))
(prelude, decls) taken
in
(prelude @ decls, warnings)
let build_program ~keep_going (decls : Ast.decl list) : Tast.program * env =
let env = new_env () in
let decls = Parse.program (Prelude.forms ()) @ decls in
let decls, prelude_warnings =
shadow_prelude (Parse.program (Prelude.forms ())) decls
in
(* Before anything is collected: every (declare-c ...) becomes an ordinary
flattened [declare] with a Flan [defn] over it, and the C that does the
flattening comes back to be compiled into the build. Nothing below this
@ -12461,7 +12511,7 @@ let build_program ~keep_going (decls : Ast.decl list) : Tast.program * env =
(fun (d : Loc.diag) ->
prerr_endline
(Loc.entry ~mark:'~' ~label:"warning: " d.Loc.dloc d.Loc.dmsg))
(shadowed_builtins decls);
(shadowed_builtins decls @ prelude_warnings);
(* Pass one, and it stops at the first thing it refuses. That is not
laziness: every name, type and signature in the file comes from here, so a
declaration this pass could not make sense of leaves a hole that pass two

View File

@ -555,6 +555,37 @@ let qualify_decl owned alias (d : Ast.decl) : Ast.decl =
in
{ d with Ast.d = k }
(* [qualify_decl]'s rename of every use of an [owned] name, without the rename
of the declaration's own name unless that name is one of them. How a
program's definition of a name the prelude also defines takes the name
over: the prelude's declaration and its uses outside the program's file
move to the qualified name, and the program's file keeps the bare one. *)
let rename_refs owned alias (d : Ast.decl) : Ast.decl =
match Ast.declared_name d, d.Ast.d with
| _, (Ast.Package _ | Ast.Import _) -> d
| Some n, _ when List.mem n owned -> qualify_decl owned alias d
| _ ->
let q = qualify_decl owned alias d in
let named (fn : Ast.fn) (o : Ast.fn) = { fn with Ast.name = o.Ast.name } in
let k =
match q.Ast.d, d.Ast.d with
| Ast.Declare (fn, c), Ast.Declare (o, _) -> Ast.Declare (named fn o, c)
| Ast.DeclareC (fn, c), Ast.DeclareC (o, _) -> Ast.DeclareC (named fn o, c)
| Ast.Defn fn, Ast.Defn o -> Ast.Defn (named fn o)
| Ast.Defgeneric fn, Ast.Defgeneric o -> Ast.Defgeneric (named fn o)
| Ast.Defmulti fn, Ast.Defmulti o -> Ast.Defmulti (named fn o)
| Ast.Defenum (_, ms), Ast.Defenum (n, _) -> Ast.Defenum (n, ms)
| Ast.Defalias (_, t), Ast.Defalias (n, _) -> Ast.Defalias (n, t)
| Ast.Defconst (_, t, v), Ast.Defconst (n, _, _) -> Ast.Defconst (n, t, v)
| Ast.Defstruct (_, fs), Ast.Defstruct (n, _) -> Ast.Defstruct (n, fs)
| Ast.Defunion (_, fs), Ast.Defunion (n, _) -> Ast.Defunion (n, fs)
| Ast.Defdata (_, vs), Ast.Defdata (n, _) -> Ast.Defdata (n, vs)
| Ast.Defvar (_, t, i, r), Ast.Defvar (n, _, _, _) -> Ast.Defvar (n, t, i, r)
| Ast.Defclass (_, ss), Ast.Defclass (n, _) -> Ast.Defclass (n, ss)
| k, _ -> k
in
{ q with Ast.d = k }
(* ── Qualifying a package's macros ──────────────────────────────────
The rename above works over the Ast and a macro cannot go that way. By the
time [Parse] is finished with a [defmacro] its quasiquote has been desugared

View File

@ -0,0 +1,13 @@
;;;; A program's function named as a prelude function takes the name over for
;;;; the calls in its own file, and the prelude's own calls keep the prelude's:
;;;; ceil-f32 is written over the prelude's floor-f32, and still answers 3.
(defn abs-f32 [v f32] f32 (if (< v 0.0) (- v) (+ v (f32 100.0))))
(defn floor-f32 [x f32] f32 (f32 999.0))
(defn abs [x i32] i32 (* x 10))
(defn main [] i32
(println (abs-f32 (f32 -2.5)))
(println (abs-f32 (f32 2.5)))
(println (floor-f32 (f32 2.3)))
(println (ceil-f32 (f32 2.3)))
(println (abs -3))
0)

View File

@ -567,6 +567,12 @@ let () =
outputs "mixed array literals and the" "programs/array-mixed.flan" mixed_out;
outputs ~x86:true "mixed array literals and the, x86"
"programs/array-mixed.flan" mixed_out;
(* A program's function named as a prelude function takes the name over
for its own file; the prelude's own calls keep the prelude's. *)
let sp_out = "2.5\n102.5\n999\n3\n-30\n" in
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;
(* (- 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\

View File

@ -5277,6 +5277,24 @@ let () =
| exception Loc.Error _ -> 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
the defining file. *)
let prelude_src = "(defn abs-f32 [v f32] f32 v)" in
(match
snd (Check.shadow_prelude (Parse.program (Prelude.forms ()))
(program prelude_src))
with
| [ d ] ->
check "a defn of a prelude function's name warns once"
(d.Loc.kind = "check/shadows-prelude"
&& d.Loc.dmsg
= "abs-f32 shadows the prelude's abs-f32 — every call in this file \
now reaches your definition")
| _ -> check "a defn of a prelude function's name warns exactly once" false);
accepts "a defn of a prelude function's name is not defined twice"
prelude_src;
rejects_check "a struct of a prelude type's name is still defined twice"
"(defstruct Form [x i32])" ~needle:"Form is defined twice";
(* An operator is a builtin like any other and shadows like any other.
Pinned in both halves because it is the case most likely to be thought
of as special and quietly excepted later: the warning is the same