Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
d36b9c6
The four trait holes: supertraits at the impl's type, pins, blanket i…
rendro Oct 23, 2026
9a7a238
Merge branch 'w1/trait-calls' into w1/trait-holes
rendro Oct 23, 2026
07d5abb
fix(#136): a trait method value is general; Hash.hash of Bytes
rendro Oct 23, 2026
c5b64e6
fix(#136): references and rename of a trait's method
rendro Oct 23, 2026
903962d
fix(#136): helps write a trait and a module as this module reaches th…
rendro Oct 23, 2026
7158dcd
fix(#145): a name that is a type and a variant of another type means …
rendro Oct 23, 2026
ef7fe2b
fix(#145): the help for a supertrait an impl's type lacks names every…
rendro Oct 23, 2026
5b8c0de
fix(#145): a trait whose impl for every type is rejected is not repor…
rendro Oct 23, 2026
22edfdf
fix(#136): a supertrait's method called through the subtrait, and a d…
rendro Oct 23, 2026
84098af
fix(#136): a trait of a module written by its bare name is not in sco…
rendro Oct 23, 2026
b245df2
fix(#136): a call with too few arguments, or of a callee already repo…
rendro Oct 23, 2026
148a3a0
fix(#136): disassembly and the verifier count arguments from 1
rendro Oct 23, 2026
76ca182
fix(#136): signature help counts the piped value as the first argument
rendro Oct 23, 2026
381ed89
fix(#136): the trait of a qualified call (`json.Encode.encode(x)`) is…
rendro Oct 23, 2026
90bd8a6
fix(#136): completion after a type offers the methods that make a val…
rendro Oct 23, 2026
e1bb362
fix(#136): stderr-not-contains assertions point at the texts that are…
rendro Oct 23, 2026
3a3d165
fix(#136): comments, docs, the fuzz corpus and a runtime text name th…
rendro Oct 23, 2026
42e89c3
fix(#136): goldens the rewrite left testing nothing their names say a…
rendro Oct 23, 2026
4d99e31
fix(#136): a trait's method is not renamed to a field of a type with …
rendro Oct 23, 2026
5a5a15a
style(#136, #145): rustfmt of the fix round
rendro Oct 23, 2026
6574c05
fix(#145): drop the rule that an impl method named like a field of it…
rendro Oct 23, 2026
c2f14c0
Merge w1/trait-calls (main 8e6882d6 merged) into w1/trait-holes
rendro Oct 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion docs/language/error-codes.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ numbers that are missing belong to codes that are gone.
| `E0338` | A field named twice in a record type, literal, pattern or update. |
| `E0339` | A top-level `let` whose initialiser can reach the `let` itself. |
| `E0340` | A statement whose value nothing uses. |
| `E0341` | An impl that gives a record type a method named like one of the type's fields. |
| `E0342` | A type given to a decoder that has no decoder for it (a field of a type no decoder exists for, an enum, a generic type, ...). |

## Compiling and the entry point (4xx, 5xx)
Expand Down
2 changes: 1 addition & 1 deletion docs/language/generics.md
Original file line number Diff line number Diff line change
Expand Up @@ -619,7 +619,7 @@ trait Stream for Wrap {
```

Inside the trait body, `Self::Item` refers to whatever the impl will
bind. At the call site (`w.first()` on a `Wrap`), the compiler reduces
bind. At the call site (`Stream.first(w)` on a `Wrap`), the compiler reduces
the projection to `Int` via the impl's `type Item = Int` binding.

A trait may declare more than one associated type:
Expand Down
5 changes: 5 additions & 0 deletions docs/language/pattern-matching.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,11 @@ The variable is one from outside the pattern. A name the pattern binds
itself is not it: `(x, ^x)` compares the second element with an `x` bound
before the `match`, and is an error when there is none.

A pin compares as `==` does: the value's type has `Equal`. A function has
none, so pinning one is an error at the pin (`the pin '^f' requires a
comparable type, got 'Fn(Int) -> Int'`); a channel is equal to itself
only, and is pinned as below.

Works in any pattern position. Common with `channel.select`:

```silt
Expand Down
62 changes: 48 additions & 14 deletions docs/language/traits.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,30 +81,32 @@ illustrate trait *declaration* use a fresh local name; impls of the
real built-in `Display` trait look identical and are shown in
[Built-in Traits](#built-in-traits) below.)

An impl gives a record type no method named like one of the record's
fields, whether the impl writes the method or takes it as the trait's
default: `job.run(5)` calls the function the field `run` holds, and a
method of that name would read as the same thing.
A record may have a field and an impl a method of one name: a dot after
a value is always the field, and a trait's method is called through its
trait. `job.run(5)` calls the function the field `run` holds,
`Runner.run(job, 5)` the method of the impl.

```silt
type Job {
name: String,
run: Fn(Int) -> Int,
}

trait Run {
trait Runner {
fn run(self, x: Int) -> Int
}

-- error: method 'run' of the impl of 'Run' for 'Job' is named like a
-- field of 'Job'
-- help: a field and a method of one type cannot share a name: rename
-- the method or the field
trait Run for Job {
trait Runner for Job {
fn run(self, x: Int) -> Int {
x
x + 100
}
}

fn main() {
let job = Job { name: "double", run: { x -> x * 2 } }
println(job.run(5)) -- 10
println(Runner.run(job, 5)) -- 105
}
```

## Supertrait Bounds
Expand Down Expand Up @@ -190,6 +192,28 @@ trait Ordered for MyInt { ... }
-- (only when MyInt has no Equal, e.g. it has a function field)
```

An impl for a parameterized type has the supertrait at its own type,
with what its header gives the type's variables: a `Box(a)` has `Equal`
when its `a` does, so the header says so:

```silt
trait Same: Equal {
fn same(self, other: Self) -> Bool {
self == other
}
}

type Box(a) {
Bx(a),
}

trait Same for Box(a) {}
-- error: type 'Box(a)' implements 'Same' but does not implement supertrait 'Equal': 'a' needs 'Equal'
-- help: add `where a: Equal` to the impl
```

`trait Same for Box(a) where a: Equal {}` is the impl.

## Default Methods

A trait method can carry a body inside the trait declaration itself. The
Expand Down Expand Up @@ -334,6 +358,9 @@ use — a single `trait Wrap for Box(a)` impl handles both `Box(42)` and

Rules:

- **An impl is for a declared type.** `trait X for a` is an error
(`trait impl target 'a' is not a declared type`): a function bounded by
the trait says "every type that has it", `fn x(v: a) where a: Show`.
- **Only lowercase binders.** `trait X for Box(Int)` is a parse error —
silt has no specialization.
- **Binders must be distinct.** `trait X for Pair(a, a)` is a parse error.
Expand Down Expand Up @@ -477,9 +504,11 @@ trait From(Celsius) for Fahrenheit {
```

Parameterized traits may have supertraits (`trait Child(a): Parent(a)`) and
`where` clauses on both the trait declaration and its methods. The full
specification — including how trait parameters flow through `where` clauses
and how multiple impls with different arguments coexist — lives in
`where` clauses on both the trait declaration and its methods. A type has
at most one impl of a trait, whatever its arguments: `trait From(Celsius)
for Fahrenheit` and `trait From(Kelvin) for Fahrenheit` are a duplicate.
The full specification — including how trait parameters flow through
`where` clauses — lives in
[Generics — Parameterized trait declarations](generics.md#parameterized-trait-declarations).

## Methods That Make a Value
Expand Down Expand Up @@ -530,6 +559,11 @@ concrete type passed in. See
[Generics — Trait methods on types](generics.md#trait-methods-on-types) for
the full rules.

A type with a variant of its own name (`type Box(a) { Box(a), Empty }`)
is the type where a type is meant: `Box.default()` calls the method of
its impl, and `make(Box)` gives the type to a `type t` parameter;
`Box(1)` is the variant.

## Method References

A method named through its trait is a function value, as a module's
Expand Down
2 changes: 1 addition & 1 deletion fuzz/corpus/fuzz_compiler/http_client.silt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type Repo {
fn fetch_repos(user) {
-- http.get returns HttpError, bytes.to_string BytesError and
-- json.parse_list JsonError. Fold them into a single `String` channel
-- via `e.message()` so fetch_repos surfaces one uniform error type to
-- via `Error.message(e)` so fetch_repos surfaces one uniform error type to
-- its caller.
let resp = match http.get("https://api.github.com/users/{user}/repos") {
Ok(r) -> r
Expand Down
2 changes: 1 addition & 1 deletion fuzz/corpus/fuzz_compiler/text_stats.silt
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ fn main() {
let filename = get_filename()
match io.read_file(filename) {
Err(e) -> {
println("Error reading file '{filename}': {e.message()}")
println("Error reading file '{filename}': {Error.message(e)}")
}
Ok(content) -> {
let lines = content
Expand Down
2 changes: 1 addition & 1 deletion fuzz/corpus/fuzz_formatter/http_client.silt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type Repo {
fn fetch_repos(user) {
-- http.get returns HttpError, bytes.to_string BytesError and
-- json.parse_list JsonError. Fold them into a single `String` channel
-- via `e.message()` so fetch_repos surfaces one uniform error type to
-- via `Error.message(e)` so fetch_repos surfaces one uniform error type to
-- its caller.
let resp = match http.get("https://api.github.com/users/{user}/repos") {
Ok(r) -> r
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ fn main() {
}
Err(err_info) -> {
let (path, d Ped Perierr) = err_info
println("[Worker {worker_id}] FAILED {path}: {err.message()}")
println("[Worker {worker_id}] FAILED {path}: {Error.message(err)}")
loop(collected + 1, successes, failures + 1)
}
}
Expand Down
2 changes: 1 addition & 1 deletion fuzz/corpus/fuzz_formatter/text_stats.silt
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ fn main() {
let filename = get_filename()
match io.read_file(filename) {
Err(e) -> {
println("Error reading file '{filename}': {e.message()}")
println("Error reading file '{filename}': {Error.message(e)}")
}
Ok(content) -> {
let lines = content
Expand Down
2 changes: 1 addition & 1 deletion fuzz/corpus/fuzz_lexer/http_client.silt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type Repo {
fn fetch_repos(user) {
-- http.get returns HttpError, bytes.to_string BytesError and
-- json.parse_list JsonError. Fold them into a single `String` channel
-- via `e.message()` so fetch_repos surfaces one uniform error type to
-- via `Error.message(e)` so fetch_repos surfaces one uniform error type to
-- its caller.
let resp = match http.get("https://api.github.com/users/{user}/repos") {
Ok(r) -> r
Expand Down
2 changes: 1 addition & 1 deletion fuzz/corpus/fuzz_lexer/text_stats.silt
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ fn main() {
let filename = get_filename()
match io.read_file(filename) {
Err(e) -> {
println("Error reading file '{filename}': {e.message()}")
println("Error reading file '{filename}': {Error.message(e)}")
}
Ok(content) -> {
let lines = content
Expand Down
2 changes: 1 addition & 1 deletion fuzz/corpus/fuzz_parser/http_client.silt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type Repo {
fn fetch_repos(user) {
-- http.get returns HttpError, bytes.to_string BytesError and
-- json.parse_list JsonError. Fold them into a single `String` channel
-- via `e.message()` so fetch_repos surfaces one uniform error type to
-- via `Error.message(e)` so fetch_repos surfaces one uniform error type to
-- its caller.
let resp = match http.get("https://api.github.com/users/{user}/repos") {
Ok(r) -> r
Expand Down
2 changes: 1 addition & 1 deletion fuzz/corpus/fuzz_parser/text_stats.silt
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ fn main() {
let filename = get_filename()
match io.read_file(filename) {
Err(e) -> {
println("Error reading file '{filename}': {e.message()}")
println("Error reading file '{filename}': {Error.message(e)}")
}
Ok(content) -> {
let lines = content
Expand Down
2 changes: 1 addition & 1 deletion fuzz/corpus/fuzz_roundtrip/http_client.silt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type Repo {
fn fetch_repos(user) {
-- http.get returns HttpError, bytes.to_string BytesError and
-- json.parse_list JsonError. Fold them into a single `String` channel
-- via `e.message()` so fetch_repos surfaces one uniform error type to
-- via `Error.message(e)` so fetch_repos surfaces one uniform error type to
-- its caller.
let resp = match http.get("https://api.github.com/users/{user}/repos") {
Ok(r) -> r
Expand Down
2 changes: 1 addition & 1 deletion fuzz/corpus/fuzz_roundtrip/text_stats.silt
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ fn main() {
let filename = get_filename()
match io.read_file(filename) {
Err(e) -> {
println("Error reading file '{filename}': {e.message()}")
println("Error reading file '{filename}': {Error.message(e)}")
}
Ok(content) -> {
let lines = content
Expand Down
10 changes: 5 additions & 5 deletions fuzz/corpus/fuzz_typechecker/cross_module_errors.silt
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ type Config {

-- Wrapper enum: one variant per module whose errors we want to
-- surface, plus a Custom for ad-hoc domain errors. Silt's auto-derived
-- Display renders each variant cleanly; `.message()` falls through to
-- Display renders each variant cleanly; `Error.message` falls through to
-- the underlying module's trait impl.

type AppError {
Expand Down Expand Up @@ -62,11 +62,11 @@ fn main() {
Err(e) -> match e {
-- Granular handling per domain.
ConfigRead(NotFound(path)) -> println("create a config at: {path}")
ConfigRead(inner) -> println("config file: {inner.message()}")
ConfigParse(inner) -> println("config JSON invalid: {inner.message()}")
ConfigRead(inner) -> println("config file: {Error.message(inner)}")
ConfigParse(inner) -> println("config JSON invalid: {Error.message(inner)}")
ApiCall(Timeout) -> println("API timed out — retry later")
ApiCall(inner) -> println("API call failed: {inner.message()}")
ApiResponse(inner) -> println("API response invalid: {inner.message()}")
ApiCall(inner) -> println("API call failed: {Error.message(inner)}")
ApiResponse(inner) -> println("API response invalid: {Error.message(inner)}")
Custom(msg) -> println("app: {msg}")
}
}
Expand Down
2 changes: 1 addition & 1 deletion fuzz/corpus/fuzz_typechecker/http_client.silt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type Repo {
fn fetch_repos(user) {
-- http.get returns HttpError, bytes.to_string BytesError and
-- json.parse_list JsonError. Fold them into a single `String` channel
-- via `e.message()` so fetch_repos surfaces one uniform error type to
-- via `Error.message(e)` so fetch_repos surfaces one uniform error type to
-- its caller.
let resp = match http.get("https://api.github.com/users/{user}/repos") {
Ok(r) -> r
Expand Down
2 changes: 1 addition & 1 deletion fuzz/corpus/fuzz_typechecker/text_stats.silt
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ fn main() {
let filename = get_filename()
match io.read_file(filename) {
Err(e) -> {
println("Error reading file '{filename}': {e.message()}")
println("Error reading file '{filename}': {Error.message(e)}")
}
Ok(content) -> {
let lines = content
Expand Down
5 changes: 3 additions & 2 deletions src/bytecode/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,8 @@ fn check(
if at >= argc =>
{
return fail(format!(
"`{op}` finds its method by argument {at} of {argc}"
"`{op}` finds its method by argument {} of {argc}",
at + 1
));
}
Instr::CallBuiltin { builtin: id, argc } => {
Expand Down Expand Up @@ -735,7 +736,7 @@ mod tests {
];
assert_eq!(
rejected(code, vec![Value::String("foo".into())]),
"at offset 1: `CallMethod` finds its method by argument 1 of 1"
"at offset 1: `CallMethod` finds its method by argument 2 of 1"
);
}

Expand Down
3 changes: 0 additions & 3 deletions src/diagnostic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,9 +200,6 @@ codes! {
InitCycle = "E0339", Type;
/// A statement whose value nothing uses.
UnusedValue = "E0340", Type;
/// An impl that gives a record type a method named like one of the
/// type's fields.
MethodNamedLikeField = "E0341", Type;
/// A type given to a decoder that has no decoder for it (a field of
/// a type no decoder exists for, an enum, a generic type, ...).
Undecodable = "E0342", Type;
Expand Down
3 changes: 2 additions & 1 deletion src/disassemble.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,10 @@ fn disassemble_instruction(
Some(t) => format!("{line} of {t}"),
None => line,
};
// (Arguments counted from 1, as a reader counts them.)
match at {
0 => line,
at => format!("{line}, by argument {at}"),
at => format!("{line}, by argument {}", at + 1),
}
}

Expand Down
Loading
Loading