Skip to content

Trait holes: supertrait at the impl's type, pins need Equal, blanket impl error, type-named variants - #145

Merged
rendro merged 22 commits into
w1/trait-callsfrom
w1/trait-holes
Oct 23, 2026
Merged

rendro merged 22 commits into
w1/trait-callsfrom
w1/trait-holes

Conversation

@rendro

@rendro rendro commented Oct 23, 2026 •

Copy link
Copy Markdown
Owner

Wave 1, lane T, step 2 (stacked on #136): the four trait holes, decided by the owner on 2026-10-22.

  • An impl for a generic type must prove its supertrait for the type's arguments, with its where clause as what it may assume. A checked program could fail at run time before ("no method 'sup' for type 'String'", or a closure printed as Display). Error at the impl: type 'Box(a)' implements 'Same' but does not implement supertrait 'Equal': 'a' needs 'Equal', help add `where a: Equal` to the impl.
  • A pin pattern needs Equal like ==: the pin '^f' requires a comparable type, got 'Fn(Int) -> Int', at the pin. Channels stay pinnable.
  • A blanket trait T for a is an error: trait impl target 'a' is not a declared type (it was accepted silently and never applied).
  • A name that is both a type and a variant (type Box(a) { Box(a), Empty }) means the type in Box.default() and as a type a argument.
  • traits.md and pattern-matching.md describe the rules; the sentence saying impls with different trait arguments coexist is gone.

Verified by an independent verifier; its findings are fixed. The owner approved every new message text on 2026-10-23. The rule that an impl method may not be named like a field of its record is dropped (owner, 2026-10-23).

…mpls, a type named like its variant

- A supertrait holds at the impl's own type, its type variables having
  what the header gives them (with the supertraits of those bounds):
  `trait Same for Box(a) {}` with `trait Same: Equal` is
  "type 'Box(a)' implements 'Same' but does not implement supertrait
  'Equal': 'a' needs 'Equal'", help "add `where a: Equal` to the
  impl". A structural supertrait asks it of the variables the type's
  parts are of, one with a written impl what that impl's header asks;
  a bare target (`trait Same for Pair`) names its variables by
  letters, help "write the impl for `Pair(a, b)`, with `where a:
  Equal`". (Such programs checked and then failed, or showed a
  function, or ordered maps, when run.)
- A pin needs Equal as `==` does: "the pin '^f' requires a comparable
  type, got 'Fn(Int) -> Int'" at the pin; a type variable without the
  bound "..., which the pin '^x' needs"; channels stay pinnable.
- `trait T for a` is an error, "trait impl target 'a' is not a
  declared type" (it was skipped without a word).
- A name that is both a type and its variant (`type Box(a) { Box(a),
  Empty }`) is the type before a method (`Box.default()`,
  `lib.Box.default()`) and as a `type t` argument (`mk(Box)`).
- traits.md: impls of one trait at different arguments do not coexist
  (one impl per type, whatever the arguments); the supertrait rule at
  the impl's type; blanket impls; pins need Equal (pattern-matching.md).
Goldens (fail on 51dfe06): typecheck/traits/supertrait__at_the_impls_type,
typecheck/traits/blanket_impl__is_an_error, lang/patterns/pin__needs_equal,
lang/traits/type_named_like_its_variant__is_the_type; guards
lang/traits/supertrait__the_header_gives_it, lang/patterns/pin__compares_as_equal_does.
Removed: two goldens of the old behaviour (a lowercase impl target
checks; an enum shadowed by its variant is no type argument).
@rendro
rendro added this pull request to stack #146 October 23, 2026 11:49
rendro added 20 commits October 23, 2026 12:42
B1: `let f = Greet.greet` (and `Box.default`) is a syntactic value, as
`let s = string.from` is: the let is general, local, top-level and in
the REPL. (The value restriction asked for a resolved name, which a
method through its trait or type gets only from the checker.)
B2: the VM's structural `hash` answers a Bytes value through
CallMethod, as nested Bytes already did.
Goldens: lang/traits/trait_call__a_method_value_is_generic,
lang/traits/hash_of_bytes__through_the_trait (both fail on 51dfe06).
G1: the name of a trait's method, at a call (`Area.area(s)`,
`shapes.Area.area(s)`, `Int.empty()`) or where the trait or an impl
declares it, is the method: references find its declaration in the
trait, every impl's method (in any module) and its calls; rename edits
them all (refused for a builtin trait's method, or where the trait
already has a method of the new name: new text "the trait already has
a method `new`"); highlight and declaration likewise. A method name
after a type is no longer taken for a use of the trait.
Goldens: lsp/trait_method_references_and_rename (new),
lsp/rename_by_meaning (a method's rename now edits the method).
…em; every trait of the method is named; module help keeps the other arguments

The help after a value or through a type writes the trait by the name an
import gave it (Area), through the alias of its module (sh.Area) or by its
module path (shapes.Area), and the module of a function by its alias
(l.length(xs)). When two traits in sight have the method, the help names
both: "`go` is a method of trait `Aa` and of trait `Bb`: call it as
`Aa.go(n)` or `Bb.go(n)`". A module function with more parameters is
written with `, ..` (map.get(m, ..)). Both messages of one program spell
a type the same way.
…the type in a method call and a type argument

`Box.default()` and `mk(Box)` with a type `Box` and a variant `Box` of
type `Other` reach the type; `Box` as a value is still the variant.
… variable that needs a bound

help: write the impl for `Pair(a, b)`, with `where a: Equal, b: Equal`
(and "add `where a: Equal, b: Equal` to the impl" when the header names
the variables).
…ted again at its uses

After `trait impl target 'a' is not a declared type`, a call of the trait's
method on a type is not also "type 'Int' does not implement trait 'Shout'"
(in the module of the impl; one in another module still says it).
…efault body's Self, get helps that name the supertrait

New texts:
  help: `sup` is a method of supertrait `Super`: call it as `Super.sup(x)`
    (for `Sub.sup(x)`, in place of a did-you-mean on the subtrait's methods)
  help: add `where Self: Greet2`, or make `Greet2` a supertrait: `trait Def: Greet2`
    (a default body of `Def` that needs `Self: Greet2`)
…pe; two missing fields name the type

New texts:
  trait 'Area' is not in scope
  help: import it: `import shapes.{ Area }`
    (was: undefined variable 'Area', help: did you mean `shapes.Area`? ...)
  type 'Person' has no fields 'email', 'phone'
    (was: record Person has no fields 'email', 'phone')
…rted, asks nothing more of the types it leaves unknown

`Greet.greet()` is the arity error alone, without "cannot infer the type
this use of 'greet' needs"; so is `[1, 2] |> list.map(Greet.greet)` before
`import list` in the REPL (the module error alone).
", by argument 2" for the second argument; the verifier's text is
"`CallMethod` finds its method by argument 2 of 1" (changed text j).
In `3 |> Put.put(4)` the cursor at `4` is on parameter 2, for every call
a pipe goes into (`list.map`, a function of the module).
…ue of it

After `Int.` (a type of the prelude too) or `Box.`, completion lists the
type's variants and the methods of its impls' traits that take no value
of the type (`empty`), with their signatures.
… given now

The tuple-index and shadowed-Int cases asserted the absence of
"unknown method ...", a text no longer given anywhere; they now assert
the absence of the current generic text ("has no field '0'").
…e forms that exist now

The comment lines of goldens that named receiver-form calls
(`.display()`, `.compare()`, `.hash()`, `.message()`, `.encode()`,
`x.show()`) name the call through the trait, interpolation or
`string.from`; so do generics.md (`Stream.first(w)`), the module docs
of init_order.rs and vm/encode.rs, and the fuzz corpus files that still
called `e.message()`.

Changed runtime text (an internal type confusion of the VM):
  string interpolation requires string values, got List — convert the value with `string.from` first
  (was: ... — call `.to_string()` or `.display()` on the value first)
…re deleted or given their purpose back

Deleted: resolve__error_message_through_builtin_type and
resolve__builtin_trait_method_through_builtin_type (tautologies; replaced
by resolve__builtin_trait_method_not_through_a_builtin_type, the errors and
helps of `io.IoError.message(e)`, `Int.compare(1, 2)`, `Int.display(1)`),
round73f display_method_equals_message_for_io_error (the round74 io case
says it), type_audit_regressions__receiver_method_correct_arity_accepted.
Renamed: round101_polymorphic_display_method_gate__* to ..._bound_gate__*,
round74 io_error_three_forms_agree to io_error_two_forms_agree,
container_fn_compare polymorphic_equal_method to polymorphic_equal_bound.
Given their purpose back: the forms a rewrite made identical are the
pipe into a call through a trait, a method value, `string.from`,
`println(t)`, a qualified trait call, `Compare.compare`/`Hash.hash` of
a derived impl, or deleted where the old form has no new one (`x.equal(y)`
beside `x == y`); the round88 unit-variant cases dispatch a program's
trait on a unit variant; headers say what the case does.
…an impl of the trait

The rename would make the impl's method named like a field, an error.
New text (LSP rename refusal):
  `area` cannot be renamed to `r`: `Circle`, which has an impl of the trait, has a field `r`, and the rename would change what a name means
…s record is an error (T7)

A dot after a value is always the field and a trait's method is called
through its trait, so nothing can be read two ways (owner, 2026-10-23,
reversing the Step E decision of 2026-10-10). Removed: the check
(methods_named_like_fields), its code E0341 MethodNamedLikeField and its
texts, RecordInfo::field_spans (only it read them), the rule's 23
goldens; the LSP rename of a trait's method to a field name is allowed
again (text x of the fix round is gone with it).

traits.md shows a field and a method of one name: `job.run(5)` calls
the field, `Runner.run(job, 5)` the method. field_and_method__of_one_name
(was field_method__what_is_no_clash) calls both, written and default
methods, through a bound, on a generic record, in a closure.
Conflict: src/vm/run.rs, the interpolation failure: main's new arms
(text and its length cap) with this branch's text for a value that is
no text ("convert the value with `string.from` first").
error-codes.md loses the E0341 row (the code went with T7).
hidden_module__an_alias_names_the_module_too re-blessed: the module help
writes the module as this module imports it (`l.length(list)`).
@rendro
rendro marked this pull request as ready for review October 23, 2026 22:43
@rendro
rendro merged commit 0192b2b into main Oct 23, 2026
23 of 46 checks passed
@rendro
rendro deleted the w1/trait-holes branch October 23, 2026 23:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant