diff --git a/lib/face.ml b/lib/face.ml index e524c983..68ce3db4 100644 --- a/lib/face.ml +++ b/lib/face.ml @@ -549,10 +549,49 @@ let format_borrow_error (face : face) (err : Borrow.borrow_error) : string = (* ─── Resolve errors ─────────────────────────────────────────────────── *) +(* Known exports of stdlib/prelude.affine. When an UndefinedVariable / + UndefinedType fires with one of these names, the error formatter + suggests `use prelude::{...}` — the discoverability gap that issue + #138's "no flat builtin seeding" + stdlib roadmap #5 surfaced. + Keep this list in sync with stdlib/prelude.affine; a missing name + here means a worse error message, not incorrect behaviour. *) +let prelude_exports = [ + (* Types *) + "Option"; "Result"; + (* Constructors *) + "Some"; "None"; "Ok"; "Err"; + (* Functions *) + "map"; "filter"; "fold"; "contains"; + "sum"; "product"; "min"; "max"; "clamp"; + "not"; "all"; "any"; "range"; "repeat"; +] + +let prelude_hint (name : string) : string = + if List.mem name prelude_exports then + Printf.sprintf "\n hint: `%s` is defined in `stdlib/prelude.affine` — add `use prelude::{%s};` (or `use prelude::*;`) at the top of the file." + name name + else "" + (** Format a name-resolution error for the given face. *) let format_resolve_error (face : face) (err : Resolve.resolve_error) : string = match face with - | Canonical -> Resolve.show_resolve_error err + | Canonical -> + (match err with + | Resolve.UndefinedVariable id -> + Printf.sprintf "undefined value: `%s`%s" id.name (prelude_hint id.name) + | Resolve.UndefinedType id -> + Printf.sprintf "undefined type: `%s`%s" id.name (prelude_hint id.name) + | Resolve.UndefinedEffect id -> + Printf.sprintf "undefined effect: `%s`" id.name + | Resolve.UndefinedModule id -> + Printf.sprintf "undefined module: `%s`\n hint: add `use %s::{...};` at the top of the file." + id.name id.name + | Resolve.DuplicateDefinition id -> + Printf.sprintf "duplicate definition of `%s`" id.name + | Resolve.VisibilityError (id, msg) -> + Printf.sprintf "`%s` is not accessible here: %s" id.name msg + | Resolve.ImportError msg -> + Printf.sprintf "import error: %s" msg) | Python -> (match err with | Resolve.UndefinedVariable id -> diff --git a/lib/typecheck.ml b/lib/typecheck.ml index f3bbff4c..1aaa7671 100644 --- a/lib/typecheck.ml +++ b/lib/typecheck.ml @@ -113,9 +113,29 @@ type type_error = raised when a row is declared; an undeclared row stays permissive under tracking-only v1. *) +(* Known exports of stdlib/prelude.affine. Mirrors the same list in + lib/face.ml — when an UnboundVariable fires at type-check time with + one of these names, the formatter suggests `use prelude::{...}`. + Same discoverability fix as the resolver path; the typechecker has + its own miss path (e.g. match arms reference unbound constructors + that the resolver missed). Keep in sync with stdlib/prelude.affine. *) +let prelude_exports = [ + "Option"; "Result"; + "Some"; "None"; "Ok"; "Err"; + "map"; "filter"; "fold"; "contains"; + "sum"; "product"; "min"; "max"; "clamp"; + "not"; "all"; "any"; "range"; "repeat"; +] + +let prelude_hint (name : string) : string = + if List.mem name prelude_exports then + Printf.sprintf "\n hint: `%s` is defined in `stdlib/prelude.affine` — add `use prelude::{%s};` (or `use prelude::*;`) at the top of the file." + name name + else "" + (** Format a type error for human consumption. *) let show_type_error = function - | UnboundVariable v -> "Unbound variable: " ^ v + | UnboundVariable v -> "Unbound variable: " ^ v ^ prelude_hint v | TypeMismatch { expected; got } -> Printf.sprintf "Type mismatch: expected %s, got %s" (ty_to_string expected) (ty_to_string got)