From 16f90a7ef50bcfddee8f2f35f0ccaa04bc2b8334 Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Sun, 20 Sep 2026 18:14:58 +0700 Subject: [PATCH] The dot advice says assign where the form is an assignment (set p.x 1) was being told that a field is read with an accessor, which is a sentence that does not apply to the form it is printed under. The place spelling is (set (.x p) 1), checked to be a real form. --- lib/check.ml | 31 +++++++++++++++++++------------ test/test_flan.ml | 5 +++++ 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/lib/check.ml b/lib/check.ml index 6713d33..3d0b36c 100644 --- a/lib/check.ml +++ b/lib/check.ml @@ -4413,8 +4413,8 @@ and value_candidates ctx = dot-infix habit. The second is the near miss, over values only — see [value_candidates]. *) -and unknown_name : 'a. ctx -> Loc.t -> string -> 'a = - fun ctx loc name -> +and unknown_name : 'a. ?setting:bool -> ctx -> Loc.t -> string -> 'a = + fun ?(setting = false) ctx loc name -> let dot = String.index_opt name '.' in let head, field = match dot with @@ -4440,25 +4440,32 @@ and unknown_name : 'a. ctx -> Loc.t -> string -> 'a = | Some sn, _ -> let s = Option.get (fields_named ctx.env sn) in let notes = declared_note ctx.env sn in + (* In a [set] the accessor is the *place*, so the spelling to give is + [(set (.x p) 1)] and not [(.x p)] on its own. Saying "read" at an + assignment would be a sentence that does not apply to the form it is + printed under. *) + let how = + if setting then Printf.sprintf "a field is assigned through an \ + accessor, so write (set (.%s %s) ...)" + field head + else Printf.sprintf "a field is read with an accessor, so write (.%s %s)" + field head + in if Tast.field_index s field <> None then - Loc.failk "check/dot-access" loc ~notes - "unknown name %s — a field is read with an accessor, so write (.%s %s)" - name field head + Loc.failk "check/dot-access" loc ~notes "unknown name %s — %s" name how else Loc.failk "check/dot-access" loc ~notes - "unknown name %s — a field is read with an accessor, (.%s %s), and \ - %s has no field %s" - name field head sn field + "unknown name %s — %s, and %s has no field %s" name how sn field | None, Some t -> Loc.failk "check/dot-access" loc "unknown name %s — a dot is part of the name here, not field access. \ - Fields are read with an accessor, (.%s %s), and %s is %s, which has \ - no fields" + A field is reached through an accessor, (.%s %s), and %s is %s, \ + which has no fields" name field head head (Types.to_string t) | None, None -> Loc.failk "check/unknown-name" loc "unknown name %s — nothing named %s is in scope either. A field is \ - read with an accessor, (.%s %s), not with a dot" + reached through an accessor, (.%s %s), not with a dot" name head field head end else @@ -4578,7 +4585,7 @@ and check_place ctx loc (p : Ast.place) : Tast.place * Types.t = into the image and there is nothing to assign to. Declare it with \ defvar if it has to change" name | Some (ty, false) -> Tast.Pglobal name, ty - | None -> captured ctx loc name; unknown_name ctx loc name) + | None -> captured ctx loc name; unknown_name ~setting:true ctx loc name) | Ast.Pfield (target, name) -> let target, sname = struct_target ctx target in let s = Option.get (fields_named ctx.env sname) in diff --git a/test/test_flan.ml b/test/test_flan.ml index f09a568..a95d824 100644 --- a/test/test_flan.ml +++ b/test/test_flan.ml @@ -1739,6 +1739,11 @@ let () = rejects_check "and says so when the field is not there either" "(defstruct P [x i32]) (defn f [] i32 (let [p (P {.x 1})] p.z))" ~needle:"(.z p), and P has no field z"; + rejects_check "and in a set it is the place that is spelled" + "(defstruct P [x i32]) (defn f [] i32 (let [p (P {.x 1})] (set p.x 2) 0))" + ~needle:"a field is assigned through an accessor, so write (set (.x p) ...)"; + accepts "which is a real form" + "(defstruct P [x i32]) (defn f [] i32 (let [p (P {.x 1})] (set (.x p) 2) (.x p)))"; rejects_check "a dotted head that is not a struct says what it is" "(defn f [] i32 (let [n 1] n.x))" ~needle:"n is i32, which has no fields"; (* A capitalised head keeps the case spelling it always had: [Shape.Circle]