Repository navigation
Trait methods are called through their trait - #136
Merged
Merged
Conversation
`Greet.greet(x)`, `5 |> Greet.greet`, `list.map(xs, Greet.greet)`,
`shapes.Area.area(s)`, `Compare.compare(a, b)`, `Hash.hash(x)`,
`Error.message(e)`: a trait name before a dot names the trait's
method. The checker gives the method's Self a type of its own, which
the arguments decide, and the use owes `Self: Trait` (so an
unannotated `fn hello(x) { Greet.greet(x) }` is generic over the
bound). The select pass picks the impl from that type; the compiler
reuses today's dispatch: a static call of the impl's method for a
known type, CallMethod for a type variable, and a closure that does
one of those for the method taken as a value.
Display's and Equal's methods are not callable this way, nor a method
that takes no value of the type (it is called on a type). The old
receiver form still works in this commit; the scripted rewrite and its
removal follow.
`Greet.greet(x)` checks x first, as the receiver of a method call was, and a value of a type with a written impl reaches that impl's method with what its header and body ask (so a decoder's refusal names `Src.load` as the function that passes the type on). Any other receiver (a type variable, a type with the trait by its structure) owes `Self: Trait` as before. An arity error counts the receiver and names the call: `Greet.greet` expects 1 argument, got 2.
The rewrite script (lane T scratch, re-runnable) dumps every call site
the old checker resolved and rewrites, by class:
- x.m(a) -> Trait.m(x, a): 2,602 calls, 5 of them `l |> x.m(a)`
(-> Trait.m(x, l, a)) and 1 bare `l |> x.m`;
- Int.m(a), a.m(..) for a method that takes Self -> Trait.m(..): 32+4;
- x.display() -> "{x}" (string.from(x) where x holds quotes or
braces, 24 imports of string added), T.display as value ->
string.from: 352+6;
- a.equal(b) -> a == b: 87;
- constructors (Int.empty(), a.empty()) and old-checker error sites
stay; 3 trait imports added to existing `import m.{ .. }` lines.
Expected outputs changed only where they quote a rewritten line or a
column moved (22 files; every error/note/help line is unchanged).
The chain-of-1024-calls golden is written as a chain of pipes (nested
calls would hit the parser's nesting limit); a call through the trait
does not owe a bound an impl's method adds (that impl is an error), so
where_impl_level_enforcement keeps only the impl's error. Docs: the
```silt blocks the old checker could check (57 calls). Tests of the
old forms themselves are left for the removal commit.
The receiver form of trait methods goes: `x.greet()` is a call of the function a field `greet` holds, and `Int.greet(5)` (a method that takes Self, through a type) is an error; `Int.empty()` and `a.empty()` stay for methods that only make a value. A dot that names nothing is `type 'Int' has no field 'greet'`, with the help that names the trait (`call it as Greet.greet(x)`) or the builtin module's function (`call it as list.length(xs)`). Gone with it: the deferred method selection, the method-not-a-value error, the out-of-reach trait help. REPL goldens' sessions rewritten by the script (14 cases). Tests and expected outputs follow.
By class: - tests of the old messages for a dot that names nothing (unknown method/field on a value, anon record, rows, deferred receivers, type variables): the owner's `type 'X' has no field 'f'` with its help (21 directives updated); - ambiguity of a method on a value (11 cases): gone with the receiver form; the cases become calls through each trait (told apart, run), or are removed where nothing is left to test; constructor ambiguity through a type or a `type a` keeps A8's help; - `Type.m(x)` for a method that takes Self, `Int.display`, `Int.compare` as values: the through-the-type error, or the trait form (`Compare.compare`, `Hash.hash`, `Div.div` as values); - method reach (a trait reached only through another module): gone; one case kept for a trait named where it is called; - the soundness manifest's codes for those rows (E0305 has-no-field, E0308 through the type). New goldens: dot_after_a_value__names_a_field, dot_after_a_value__a_method_of_a_builtin_trait_or_that_makes_a_value, through_the_type__a_method_that_takes_self. An impl of an unknown trait poisons the name, so a call through it is not reported again. Through a type, a method that makes a value is the only candidate. LSP goldens follow with the LSP commit.
By hand, guided by the new errors: `e.message()` -> `Error.message(e)` in the tcp/tls/io tests (48), user trait calls in the REPL-task, LSP-session, stale-method-row, assoc-cycle, folding and inlay tests, `.display()` -> interpolation or `string.from`, `.hash()` -> `Hash.hash`; unit tests of the checker, compiler and VM likewise (the two that used a blanket `trait Display for a` use a trait of their own). The tracked fuzz seeds by the script (103 files, 247 calls, 119 `.display()`), so the oracle's corpus runs to its end again. Display's editor text no longer names `.display()`.
Completion after `Trait.` (and `m.Trait.`) offers the methods a call through the trait calls, with their signatures and docs (none for Display and Equal); after a value it offers the value's fields only: the method narrowing on receivers, Tables::methods and its tests go. Hover on the method of `Sized.size(b)` shows the method as its trait declares it and its doc comment; signature help knows `Tr.m(` and `m.Tr.m(`, counting the argument of type Self; semantic tokens mark the trait (INTERFACE) and the method (FUNCTION). Definition and references already followed the checker's selection; renaming a trait renames its name at the calls. LSP goldens: their sources rewritten (positions moved to match), two that tested method completion on values rewritten for fields and `json.Decode.`, one removed; new trait_call_in_the_editor.
traits.md: a method that takes a value of the type is called through its trait (also from another module, piped, as a value); supertrait methods through their own trait; "Methods That Make a Value" (on a type or a `type a` parameter, never through the trait or on a value); method references through the trait; "A dot after a value" replaces "Calls on a value of unknown type", with the owner's messages; the built-in traits section (no callable display/equal; Compare.compare, Hash.hash, Error.message); where an impl is written (a call names the trait). generics.md, modules.md, types.md, loops-and-pipes.md, bindings-and-functions.md, error-handling.md, json.md, the stdlib pages, getting-started, README, why-silt and the From proposal: the same, with `Error.message(e)` for the error texts.
- VM: the native `display` and `equal` methods (no program calls them; interpolation and `==` have their own gates), the descriptor-drops-itself path of a native method through a type. - Compiler: `Int.display(1)`-style native calls through a type and the two-argument native method values; a native method through a type is `T.decode` alone. A call on a type held in a value is Dynamic only. - Checker: the `on_type` flag of method_entry/method_providers/ ambiguous_method_call/structural_provider (every call is through a type now); structural_method is `T.decode`'s; last_field_access_was_method is receiver_taken (the argument of type Self checked with the callee). - init_order: a call through a trait is of the type its Self argument has, not of no type. - Comments and docs of the old form reworded.
The heavy test of a million calls through a bound calls `Size.m(x)`; the doc example of the field errors is written as `silt fmt` writes it; a golden's expected place moved with its renamed file.
A method whose argument of type Self is not its first (`fn scale(by: Int, x: Self) -> Self`) is called through its trait as any other: `Scale.scale(2, x)`. Where the type of that argument is known where the code runs only, CallMethod and TailCallMethod find the method by the argument their new operand `at` names (0 for every other call; the disassembly names it when it is not the first, "by argument 1"), and the verifier refuses an `at` the call has no argument for. Selection::Dynamic carries it. Run-time dispatch stays what it was otherwise (wave 2 replaces it for user traits). Goldens: lang/traits/trait_call__self_not_first, cli/disasm/disasm_rendering__a_method_found_by_a_later_argument.
rendro
added this pull request to stack #146
October 23, 2026 11:49
rendro
added a commit
that referenced
this pull request
Oct 23, 2026
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).
rendro
added a commit
that referenced
this pull request
Oct 23, 2026
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).
rendro
added a commit
that referenced
this pull request
Oct 23, 2026
…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.
rendro
added a commit
that referenced
this pull request
Oct 23, 2026
…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`)
rendro
added a commit
that referenced
this pull request
Oct 23, 2026
…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')
rendro
added a commit
that referenced
this pull request
Oct 23, 2026
…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).
rendro
added a commit
that referenced
this pull request
Oct 23, 2026
", by argument 2" for the second argument; the verifier's text is "`CallMethod` finds its method by argument 2 of 1" (changed text j).
rendro
added a commit
that referenced
this pull request
Oct 23, 2026
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).
rendro
added a commit
that referenced
this pull request
Oct 23, 2026
…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.
rendro
added a commit
that referenced
this pull request
Oct 23, 2026
… 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'").
rendro
added a commit
that referenced
this pull request
Oct 23, 2026
…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)
rendro
added a commit
that referenced
this pull request
Oct 23, 2026
…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.
rendro
added a commit
that referenced
this pull request
Oct 23, 2026
…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
rendro
added a commit
that referenced
this pull request
Oct 23, 2026
Conflicts: src/lsp/completion.rs (doc comment of dot_completions: ours, the receiver-form wording is gone), docs/getting-started.md (main's runnable block, with `Error.message(e)`). Rewritten to the trait call form what main gained: 11 sites in 4 goldens (sort_with x2, json_syntax, tls_server) and 2 in docs/stdlib/list.md. hidden_module__* re-blessed with this branch's texts; their headers say a dot reads a field; no_help_... asserts the current no-field text.
rendro
marked this pull request as ready for review
October 23, 2026 22:43
rendro
added a commit
that referenced
this pull request
Oct 23, 2026
…impl error, type-named variants (#145) * The four trait holes: supertraits at the impl's type, pins, blanket impls, 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). * fix(#136): a trait method value is general; Hash.hash of Bytes 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). * fix(#136): references and rename of a trait's method 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). * fix(#136): helps write a trait and a module as this module reaches them; 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. * fix(#145): a name that is a type and a variant of another type means 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. * fix(#145): the help for a supertrait an impl's type lacks names every 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). * fix(#145): a trait whose impl for every type is rejected is not reported 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). * fix(#136): a supertrait's method called through the subtrait, and a default 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`) * fix(#136): a trait of a module written by its bare name is not in scope; 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') * fix(#136): a call with too few arguments, or of a callee already reported, 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). * fix(#136): disassembly and the verifier count arguments from 1 ", by argument 2" for the second argument; the verifier's text is "`CallMethod` finds its method by argument 2 of 1" (changed text j). * fix(#136): signature help counts the piped value as the first argument 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). * fix(#136): the trait of a qualified call (`json.Encode.encode(x)`) is an INTERFACE token * fix(#136): completion after a type offers the methods that make a value 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. * fix(#136): stderr-not-contains assertions point at the texts that are 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'"). * fix(#136): comments, docs, the fuzz corpus and a runtime text name the 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) * fix(#136): goldens the rewrite left testing nothing their names say are 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. * fix(#136): a trait's method is not renamed to a field of a type with 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 * style(#136, #145): rustfmt of the fix round * fix(#145): drop the rule that an impl method named like a field of its 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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Wave 1, lane T, step 1: the trait call form decided by the owner on 2026-10-22.
Greet.greet(5),5 |> Greet.greet,Monoid.combine(a, b),list.map(names, Greet.greet); also whenSelfis not the first parameter. Used as a value it is generic like any function.Int.empty(),a.empty(),AppError.from.x.greet()and the type-qualifiedInt.greet(5)are gone, with every trace..display()and.equal()are no longer callable ("{x}"/string.from(x)and==);Compare.compare,Hash.hash,Error.messageare the forms.Trait., hover, signature help, semantic tokens, references and rename of trait methods.Verified by an independent verifier; its findings are fixed. The owner approved every new message text on 2026-10-23.