diff --git a/docs/language/error-codes.md b/docs/language/error-codes.md index 3f61703b3..6d1f6dbc7 100644 --- a/docs/language/error-codes.md +++ b/docs/language/error-codes.md @@ -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) diff --git a/docs/language/generics.md b/docs/language/generics.md index 331652968..3100c3eeb 100644 --- a/docs/language/generics.md +++ b/docs/language/generics.md @@ -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: diff --git a/docs/language/pattern-matching.md b/docs/language/pattern-matching.md index ec4d1eb83..9a18dbaa2 100644 --- a/docs/language/pattern-matching.md +++ b/docs/language/pattern-matching.md @@ -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 diff --git a/docs/language/traits.md b/docs/language/traits.md index 22a34bb73..08e252b66 100644 --- a/docs/language/traits.md +++ b/docs/language/traits.md @@ -81,10 +81,10 @@ 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 { @@ -92,19 +92,21 @@ type Job { 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 @@ -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 @@ -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. @@ -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 @@ -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 diff --git a/fuzz/corpus/fuzz_compiler/http_client.silt b/fuzz/corpus/fuzz_compiler/http_client.silt index 12a3ca26f..153cad638 100644 --- a/fuzz/corpus/fuzz_compiler/http_client.silt +++ b/fuzz/corpus/fuzz_compiler/http_client.silt @@ -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 diff --git a/fuzz/corpus/fuzz_compiler/text_stats.silt b/fuzz/corpus/fuzz_compiler/text_stats.silt index 877cc62f7..38e21f8a2 100644 --- a/fuzz/corpus/fuzz_compiler/text_stats.silt +++ b/fuzz/corpus/fuzz_compiler/text_stats.silt @@ -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 diff --git a/fuzz/corpus/fuzz_formatter/http_client.silt b/fuzz/corpus/fuzz_formatter/http_client.silt index 12a3ca26f..153cad638 100644 --- a/fuzz/corpus/fuzz_formatter/http_client.silt +++ b/fuzz/corpus/fuzz_formatter/http_client.silt @@ -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 diff --git a/fuzz/corpus/fuzz_formatter/regression_bug3_trailing_on_block_comment_closer_line b/fuzz/corpus/fuzz_formatter/regression_bug3_trailing_on_block_comment_closer_line index da198acbb..ec0ae1acd 100644 --- a/fuzz/corpus/fuzz_formatter/regression_bug3_trailing_on_block_comment_closer_line +++ b/fuzz/corpus/fuzz_formatter/regression_bug3_trailing_on_block_comment_closer_line @@ -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) } } diff --git a/fuzz/corpus/fuzz_formatter/text_stats.silt b/fuzz/corpus/fuzz_formatter/text_stats.silt index e86b6ddf9..70c9f8731 100644 --- a/fuzz/corpus/fuzz_formatter/text_stats.silt +++ b/fuzz/corpus/fuzz_formatter/text_stats.silt @@ -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 diff --git a/fuzz/corpus/fuzz_lexer/http_client.silt b/fuzz/corpus/fuzz_lexer/http_client.silt index 12a3ca26f..153cad638 100644 --- a/fuzz/corpus/fuzz_lexer/http_client.silt +++ b/fuzz/corpus/fuzz_lexer/http_client.silt @@ -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 diff --git a/fuzz/corpus/fuzz_lexer/text_stats.silt b/fuzz/corpus/fuzz_lexer/text_stats.silt index e86b6ddf9..70c9f8731 100644 --- a/fuzz/corpus/fuzz_lexer/text_stats.silt +++ b/fuzz/corpus/fuzz_lexer/text_stats.silt @@ -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 diff --git a/fuzz/corpus/fuzz_parser/http_client.silt b/fuzz/corpus/fuzz_parser/http_client.silt index 12a3ca26f..153cad638 100644 --- a/fuzz/corpus/fuzz_parser/http_client.silt +++ b/fuzz/corpus/fuzz_parser/http_client.silt @@ -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 diff --git a/fuzz/corpus/fuzz_parser/text_stats.silt b/fuzz/corpus/fuzz_parser/text_stats.silt index e86b6ddf9..70c9f8731 100644 --- a/fuzz/corpus/fuzz_parser/text_stats.silt +++ b/fuzz/corpus/fuzz_parser/text_stats.silt @@ -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 diff --git a/fuzz/corpus/fuzz_roundtrip/http_client.silt b/fuzz/corpus/fuzz_roundtrip/http_client.silt index 12a3ca26f..153cad638 100644 --- a/fuzz/corpus/fuzz_roundtrip/http_client.silt +++ b/fuzz/corpus/fuzz_roundtrip/http_client.silt @@ -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 diff --git a/fuzz/corpus/fuzz_roundtrip/text_stats.silt b/fuzz/corpus/fuzz_roundtrip/text_stats.silt index e86b6ddf9..70c9f8731 100644 --- a/fuzz/corpus/fuzz_roundtrip/text_stats.silt +++ b/fuzz/corpus/fuzz_roundtrip/text_stats.silt @@ -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 diff --git a/fuzz/corpus/fuzz_typechecker/cross_module_errors.silt b/fuzz/corpus/fuzz_typechecker/cross_module_errors.silt index 0c831973f..a8aa2b1ff 100644 --- a/fuzz/corpus/fuzz_typechecker/cross_module_errors.silt +++ b/fuzz/corpus/fuzz_typechecker/cross_module_errors.silt @@ -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 { @@ -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}") } } diff --git a/fuzz/corpus/fuzz_typechecker/http_client.silt b/fuzz/corpus/fuzz_typechecker/http_client.silt index 12a3ca26f..153cad638 100644 --- a/fuzz/corpus/fuzz_typechecker/http_client.silt +++ b/fuzz/corpus/fuzz_typechecker/http_client.silt @@ -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 diff --git a/fuzz/corpus/fuzz_typechecker/text_stats.silt b/fuzz/corpus/fuzz_typechecker/text_stats.silt index 877cc62f7..38e21f8a2 100644 --- a/fuzz/corpus/fuzz_typechecker/text_stats.silt +++ b/fuzz/corpus/fuzz_typechecker/text_stats.silt @@ -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 diff --git a/src/bytecode/verify.rs b/src/bytecode/verify.rs index b5f2791b9..56b7bd95a 100644 --- a/src/bytecode/verify.rs +++ b/src/bytecode/verify.rs @@ -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 } => { @@ -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" ); } diff --git a/src/diagnostic.rs b/src/diagnostic.rs index 496f6f0bb..b90e9937f 100644 --- a/src/diagnostic.rs +++ b/src/diagnostic.rs @@ -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; diff --git a/src/disassemble.rs b/src/disassemble.rs index e0c4083ad..1498f8910 100644 --- a/src/disassemble.rs +++ b/src/disassemble.rs @@ -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), } } diff --git a/src/lsp/completion.rs b/src/lsp/completion.rs index e3501f3e2..c6c6f8c00 100644 --- a/src/lsp/completion.rs +++ b/src/lsp/completion.rs @@ -50,8 +50,9 @@ impl Server { if let Some(items) = self.trait_method_completions(doc, &prefix, cursor) { return Some(CompletionResponse::Array(items)); } - // An enum's name before the dot: its variants. - if let Some(items) = self.enum_variant_completions(doc, &prefix, cursor) { + // A type's name before the dot: its variants, and the + // methods called on it. + if let Some(items) = self.type_member_completions(doc, &prefix, cursor) { return Some(CompletionResponse::Array(items)); } let builtin = self.bound_builtin_module(doc, &prefix, cursor); @@ -387,9 +388,10 @@ impl Server { } /// The variants of the enum `prefix` names in the open document `doc` - /// (declared there or imported by name), unless a local binding of - /// that name shadows it. - fn enum_variant_completions( + /// (declared there or imported by name, or a type of the prelude), + /// and the methods of its traits that make a value of it + /// (`Int.empty`), unless a local binding of that name shadows it. + fn type_member_completions( &self, doc: &Document, prefix: &str, @@ -409,7 +411,8 @@ impl Server { // `T.` or `m.T.` (a type of an imported module). let (_, builtin) = crate::typechecker::names::builtins(); let binding = match prefix.split_once('.') { - None => scope.types.get(&intern(prefix)), + None => (scope.types.get(&intern(prefix))) + .or_else(|| builtin.prelude.types.get(&intern(prefix))), Some((m, t)) => { let Some(Binding::Module(id)) = scope.values.get(&intern(m)) else { return None; @@ -425,20 +428,33 @@ impl Server { let Some(Binding::Def(ty)) = binding else { return None; }; - let variants = defs.variants(*ty); - if variants.is_empty() { + let crate::defs::DefKind::Type(type_id) = defs.get(*ty).kind else { return None; - } - Some( - variants - .iter() - .map(|v| CompletionItem { - label: resolve(defs.get(*v).name), - kind: Some(CompletionItemKind::CONSTRUCTOR), - ..CompletionItem::default() - }) - .collect(), - ) + }; + let mut items: Vec = (defs.variants(*ty).iter()) + .map(|v| CompletionItem { + label: resolve(defs.get(*v).name), + kind: Some(CompletionItemKind::CONSTRUCTOR), + ..CompletionItem::default() + }) + .collect(); + let mut makers: Vec = (self.type_maker_methods(doc, type_id).into_iter()) + .map(|m| CompletionItem { + label: resolve(m.name), + kind: Some(CompletionItemKind::METHOD), + detail: Some(m.label), + documentation: m.doc.map(|d| { + Documentation::MarkupContent(MarkupContent { + kind: MarkupKind::Markdown, + value: d, + }) + }), + ..CompletionItem::default() + }) + .collect(); + makers.sort_by(|a, b| a.label.cmp(&b.label)); + items.extend(makers); + (!items.is_empty()).then_some(items) } /// The builtin module `prefix` is bound to in the open document `doc` diff --git a/src/lsp/names.rs b/src/lsp/names.rs index df255ea37..ce71d8888 100644 --- a/src/lsp/names.rs +++ b/src/lsp/names.rs @@ -368,7 +368,26 @@ impl<'a> Names<'a> { Some(Res::Def(id)) => self.table().get(id).is_type(), Some(Res::Local | Res::Error) | None => false, }; - if qualified { + // `Int.empty`: the method of the trait the checker + // selected, of the impl for the type. + let through_type = match (receiver.res, expr.res) { + (Some(Res::Def(ty)), Some(Res::Def(tr))) if qualified => { + match (self.table().get(ty).kind, self.table().get(tr).kind) { + (crate::defs::DefKind::Type(ty), crate::defs::DefKind::Trait(tr)) => { + Some(Selection::Impl { tr, ty }) + } + _ => None, + } + } + _ => None, + }; + if let Some(selection) = through_type { + self.methods.push(MethodUse { + span: *field_span, + name: *field, + selection, + }); + } else if qualified { self.name(*field_span, expr.res); } else if let Some( selection @ (Selection::Impl { .. } diff --git a/src/lsp/semantic_tokens.rs b/src/lsp/semantic_tokens.rs index 472febaf4..d4597ac90 100644 --- a/src/lsp/semantic_tokens.rs +++ b/src/lsp/semantic_tokens.rs @@ -312,6 +312,13 @@ fn emit_expr_tokens( let start = receiver.span.start as usize; push_token_at_offset(source, start, &resolve(*name), TT_INTERFACE, out); } + // `m.Tr.f`: the module as any module, the trait an + // INTERFACE. + ExprKind::FieldAccess(module, name, name_span) => { + emit_expr_tokens(module, source, doc, server, out); + let start = name_span.start as usize; + push_token_at_offset(source, start, &resolve(*name), TT_INTERFACE, out); + } _ => emit_expr_tokens(receiver, source, doc, server, out), } let start = field_span.start as usize; diff --git a/src/lsp/signature_help.rs b/src/lsp/signature_help.rs index 0624f4f11..8f1f40a9b 100644 --- a/src/lsp/signature_help.rs +++ b/src/lsp/signature_help.rs @@ -197,10 +197,11 @@ pub(super) fn build_signature_from_def( /// The call that the end of `tokens` is in, as the name written in /// front of its `(` (`f`, or `m.f`) and the number of its arguments -/// that are complete (the index of the one being written): the -/// innermost `(` that is not closed. A list, a record, a block or a -/// string interpolation that is open inside it is part of the argument -/// being written, and its commas are its own. +/// that are complete (the index of the one being written), the piped +/// value of `x |> f(` counted as the first: the innermost `(` that is +/// not closed. A list, a record, a block or a string interpolation that +/// is open inside it is part of the argument being written, and its +/// commas are its own. fn call_site(tokens: &[Tok]) -> Option<(String, u32)> { #[derive(PartialEq)] enum Open { @@ -251,6 +252,11 @@ fn call_site(tokens: &[Tok]) -> Option<(String, u32)> { .is_some_and(|tok| tok.kind == Token::Dot) }; let name = name_at(paren.checked_sub(1))?; + // The argument a pipe gives the call comes before those written. + let piped = |path: usize| { + let at = (paren.checked_sub(path + 1)).and_then(|i| tokens.get(i)); + commas + u32::from(at.is_some_and(|tok| tok.kind == Token::Pipe)) + }; // `m.f(`: a member of a module, or `Tr.f(`, a method through its // trait; `m.Tr.f(`, through a trait of a module. A longer path is a // field of a value, which has no signature here. @@ -261,11 +267,11 @@ fn call_site(tokens: &[Tok]) -> Option<(String, u32)> { if is_dot(paren.checked_sub(6)) { return None; } - return Some((format!("{outer}.{module}.{name}"), *commas)); + return Some((format!("{outer}.{module}.{name}"), piped(5))); } - return Some((format!("{module}.{name}"), *commas)); + return Some((format!("{module}.{name}"), piped(3))); } - Some((name, *commas)) + Some((name, piped(1))) } // ── Tests ───────────────────────────────────────────────────────── diff --git a/src/lsp/trait_hover.rs b/src/lsp/trait_hover.rs index efaa667ac..3df87e8ed 100644 --- a/src/lsp/trait_hover.rs +++ b/src/lsp/trait_hover.rs @@ -140,6 +140,28 @@ impl Server { .map(|f| trait_method(q.as_ref(), f)) .collect() } + + /// The methods called on the type `ty` (`Int.empty()`), those that + /// make a value of it, of each trait an impl gives it, each with its + /// signature. + pub(super) fn type_maker_methods(&self, doc: &Document, ty: TypeId) -> Vec { + let Some(session) = (doc.module.as_ref()) + .and_then(|module| self.projects.get(&module.project)) + .map(|project| &project.session) + else { + return Vec::new(); + }; + let mut found = Vec::new(); + for (tr, name) in session.tables().makers_of(ty) { + let Some((decl, q)) = self.trait_decl(doc, tr) else { + continue; + }; + if let Some(f) = decl.methods.iter().find(|f| f.name == name) { + found.push(trait_method(q.as_ref(), f)); + } + } + found + } } /// A method of a trait as a call through the trait shows it. diff --git a/src/lsp/workspace.rs b/src/lsp/workspace.rs index 257ad7dac..6f0ba2659 100644 --- a/src/lsp/workspace.rs +++ b/src/lsp/workspace.rs @@ -58,6 +58,10 @@ pub(super) enum Target { /// The local binding of the document whose name starts at this byte /// (see `local_bindings.rs`). Local(usize), + /// The method `name` of the trait `tr`: its declaration in the + /// trait, its impls' methods, its calls (`Tr.name(..)`, + /// `T.name(..)`). + Method { tr: DefKey, name: Symbol }, } /// Whether byte `cursor` is in `span` or directly behind it. @@ -111,6 +115,33 @@ fn in_recovery_stub(program: &Program, at: usize) -> bool { }) } +/// The method whose name a trait or an impl of `program` declares at +/// byte `at`: its span, its trait and its name. +fn method_declared_at( + program: &Program, + session: &Session, + module: ModuleId, + at: usize, +) -> Option<(Span, DefId, Symbol)> { + program.decls.iter().find_map(|decl| { + let (tr, methods) = match decl { + Decl::TraitImpl(ti) => match ti.trait_res { + Some(Res::Def(tr)) => (tr, &ti.methods), + _ => return None, + }, + Decl::Trait(t) => { + let defs = session.defs(); + let tr = (defs.of_module(module).iter().copied()) + .find(|id| defs.get(*id).span == t.name_span)?; + (tr, &t.methods) + } + _ => return None, + }; + let m = methods.iter().find(|m| holds(m.name_span, at))?; + Some((m.name_span, tr, m.name)) + }) +} + /// What a name under the cursor is, for hover. pub(super) enum Named { /// A definition of a file, with what its module knows of it. @@ -193,6 +224,29 @@ impl Server { if declared.is_some() { return declared; } + // A method: at a call, or where a trait or an impl writes it. + if let Some(used) = names.methods.iter().find(|m| holds(m.span, cursor)) { + let tr = match used.selection { + Selection::Impl { tr, .. } + | Selection::Native { tr } + | Selection::Dynamic { tr, .. } => tr, + Selection::Field | Selection::FieldCall => return None, + }; + let tr = def_key(session, tr.0); + return Some(( + used.span, + Target::Method { + tr, + name: used.name, + }, + )); + } + if let Some((span, tr, name)) = + method_declared_at(doc.program.as_ref()?, session, module, cursor) + { + let tr = def_key(session, tr); + return Some((span, Target::Method { tr, name })); + } let local = names.locals.iter().find(|l| holds(l.span, cursor))?; Some((local.span, Target::Local(local.binding(&doc.locals)?))) } @@ -206,6 +260,7 @@ impl Server { }; let keys = match target { Target::Local(_) => return Named::Local, + Target::Method { .. } => return Named::Other, Target::Defs(keys) => keys, }; let info = keys.iter().find_map(|key| { @@ -326,6 +381,8 @@ impl Server { .iter() .any(|key| matches!(key, DefKey::Builtin(_))) .then(|| "is a builtin and cannot be renamed".to_string()), + Target::Method { tr, .. } => matches!(tr, DefKey::Builtin(_)) + .then(|| "is a method of a builtin trait and cannot be renamed".to_string()), Target::Local(binding) => { let doc = self.documents.get(uri)?; let local = doc.locals.iter().find(|b| b.binding_offset == *binding)?; @@ -349,6 +406,14 @@ impl Server { ) -> Vec { let mut places = match target { Target::Local(binding) => self.local_places(uri, *binding, include_definition), + Target::Method { tr, name } => { + self.check_importers(); + (self + .method_places(tr, *name, include_definition) + .into_iter()) + .map(|(place, _)| place) + .collect() + } Target::Defs(keys) => { if self.def_uses.is_none() { self.check_importers(); @@ -423,6 +488,12 @@ impl Server { .into_iter() .collect() } + Target::Method { tr, name } => self + .method_places(tr, *name, true) + .into_iter() + .filter(|(_, declaration)| *declaration) + .map(|(place, _)| place.location) + .collect(), Target::Defs(keys) => { let mut locations: Vec = Vec::new(); for key in keys { @@ -453,6 +524,73 @@ impl Server { Some(Location::new(uri, span_to_range(&span, source))) } + /// Every place that names the method `name` of the trait `tr` in + /// the checked modules of every session: its calls, and with + /// `include_definition` its declaration in the trait and the + /// methods of its impls, each with whether it is a declaration. + fn method_places( + &self, + tr: &DefKey, + name: Symbol, + include_definition: bool, + ) -> Vec<(Place, bool)> { + let mut places = Vec::new(); + for project in self.projects.values() { + let session = &project.session; + let Some(tr) = def_of_key(session, tr) else { + continue; + }; + let trait_def = session.defs().get(tr); + for module in session.graph().modules() { + let Some(analysis) = session.module_analysis(module.id) else { + continue; + }; + let source = module.file.and_then(|file| session.sources().get(file)); + let (Some(uri), Some(source)) = (self.uri_for_path(&module.path), source) else { + continue; + }; + let place = |span: Span, declaration: bool| { + let location = Location::new(uri.clone(), span_to_range(&span, source)); + let place = Place { + location, + punned: false, + }; + (place, declaration) + }; + let names = Names::of(&analysis.ast, session, module.id); + for used in names.methods.iter().filter(|m| m.name == name) { + let of = match used.selection { + Selection::Impl { tr, .. } + | Selection::Native { tr } + | Selection::Dynamic { tr, .. } => tr.0, + Selection::Field | Selection::FieldCall => continue, + }; + if of == tr { + places.push(place(used.span, false)); + } + } + if !include_definition { + continue; + } + for decl in &analysis.ast.decls { + let methods = match decl { + Decl::Trait(t) + if trait_def.module == module.id && t.name_span == trait_def.span => + { + &t.methods + } + Decl::TraitImpl(ti) if ti.trait_res == Some(Res::Def(tr)) => &ti.methods, + _ => continue, + }; + for m in methods.iter().filter(|m| m.name == name) { + places.push(place(m.name_span, true)); + } + } + } + } + places + } + /// The declaration of the method whose name at a call holds byte /// `cursor` of the open document `uri`: the method of the impl the /// checker selected, when that impl writes it, else the trait's. @@ -744,6 +882,7 @@ impl Server { .is_some_and(|program| in_recovery_stub(program, *binding)); return own.filter(|_| in_stub); } + Target::Method { tr, .. } => std::slice::from_ref(tr), Target::Defs(keys) => keys, }; if own.is_some() { @@ -783,6 +922,12 @@ impl Server { .map(|place| place.location) .collect(); } + Target::Method { tr, name } => { + return (self.method_places(tr, *name, true).into_iter()) + .map(|(place, _)| place.location) + .filter(|location| location.uri == *uri) + .collect(); + } Target::Defs(keys) => keys, }; let Some(doc) = self.documents.get(uri) else { @@ -821,6 +966,25 @@ impl Server { /// renamed name would be taken for that, or that for it. pub(super) fn rename_clash(&mut self, uri: &Uri, target: &Target, new: &str) -> Option { let new_sym = crate::intern::intern(new); + // A method is named only after its trait or type: what can clash + // is another method of the trait. + if let Target::Method { tr, .. } = target { + let taken = self.projects.values().any(|project| { + let session = &project.session; + def_of_key(session, tr).is_some_and(|id| { + let def = session.defs().get(id); + session.module_analysis(def.module).is_some_and(|analysis| { + analysis.ast.decls.iter().any(|decl| match decl { + Decl::Trait(t) if t.name_span == def.span => { + t.methods.iter().any(|m| m.name == new_sym) + } + _ => false, + }) + }) + }) + }); + return taken.then(|| format!("the trait already has a method `{new}`")); + } let places = self.places_of(uri, target, true); // One of silt's own names, visible in every module: a builtin // function, type, trait or constructor. diff --git a/src/typechecker/builtin_needs.rs b/src/typechecker/builtin_needs.rs index b970f8abb..2cc238565 100644 --- a/src/typechecker/builtin_needs.rs +++ b/src/typechecker/builtin_needs.rs @@ -1000,7 +1000,12 @@ fn same_shape(a: &[&Type], b: &[&Type]) -> bool { /// Bind each of the variables `vars` of `pattern` to the part of `ty` at /// its place, into `mapping` (the first place a variable is at decides). -fn match_type(pattern: &Type, ty: &Type, vars: &[TyVar], mapping: &mut HashMap) { +pub(super) fn match_type( + pattern: &Type, + ty: &Type, + vars: &[TyVar], + mapping: &mut HashMap, +) { let mut zip = |xs: &[Type], ys: &[Type]| { for (x, y) in xs.iter().zip(ys) { match_type(x, y, vars, mapping); diff --git a/src/typechecker/declare_traits.rs b/src/typechecker/declare_traits.rs index e8f065bda..36a5703c2 100644 --- a/src/typechecker/declare_traits.rs +++ b/src/typechecker/declare_traits.rs @@ -1,6 +1,182 @@ use super::*; impl TypeChecker { + /// Whether the impl of `tr` for `head` was reported for a type + /// variable of its type that its header does not give a trait the + /// supertrait `sup` asks of it at that type (`'a' needs 'Equal'`). + fn supertrait_needs_reported( + &mut self, + tr: TraitKey, + head: TypeRef, + sup: TraitKey, + span: Span, + ) -> bool { + let Some(self_ty) = self.tables.impl_self_types.get(&(tr, head)).cloned() else { + return false; + }; + let mut needs: Vec<(TyVar, TraitKey)> = Vec::new(); + if !self.trait_needs(sup, &self_ty, 0, &mut needs) { + return false; + } + // What the header gives, with the supertraits of each bound. + let mut given: Vec<(TyVar, TraitKey)> = Vec::new(); + for pred in self + .tables + .impl_preds + .get(&(tr, head)) + .into_iter() + .flatten() + { + if let Pred::Trait { + tr: bound, + subject: Type::Var(v), + .. + } = pred + { + given.push((*v, *bound)); + given.extend(self.supertraits_of(*bound).into_iter().map(|s| (*v, s))); + } + } + let mut missing: Vec<(TyVar, TraitKey)> = Vec::new(); + for need in needs.iter().filter(|need| !given.contains(need)) { + if !missing.contains(need) { + missing.push(*need); + } + } + let Some(&(var, trait_needed)) = missing.first() else { + return false; + }; + // The impl's type variables by the names its header writes + // (letters for those of a bare target, which names none). + let mut names: Vec<(TyVar, Symbol)> = self + .impl_var_names + .get(&(tr, head)) + .cloned() + .unwrap_or_default(); + let named = !names.is_empty(); + for v in free_vars_in(&self_ty) { + if !names.iter().any(|(known, _)| *known == v) { + let letter = char::from(b'a' + (names.len() % 26) as u8); + names.push((v, intern(&letter.to_string()))); + } + } + let mapping: HashMap = names + .iter() + .map(|(v, name)| { + ( + *v, + Type::Rigid(RigidId { + var: *v, + name: *name, + }), + ) + }) + .collect(); + let shown_ty = self.show_type(&substitute_vars(&self_ty, &mapping)); + let name_of = |var: TyVar| { + (names.iter()) + .find(|(v, _)| *v == var) + .map_or_else(|| "a".to_string(), |(_, name)| resolve(*name)) + }; + let var_name = name_of(var); + let (tr_shown, sup_shown, needed) = ( + self.show_trait(tr), + self.show_trait(sup), + self.show_trait(trait_needed), + ); + // The help names every variable that needs a bound. + let bounds: Vec = (missing.iter()) + .map(|(v, t)| format!("{}: {}", name_of(*v), self.show_trait(*t))) + .collect(); + let bounds = bounds.join(", "); + let help = match named { + true => format!("add `where {bounds}` to the impl"), + false => format!("write the impl for `{shown_ty}`, with `where {bounds}`"), + }; + self.error_help( + Code::MissingTraitImpl, + ( + format!( + "type '{shown_ty}' implements '{tr_shown}' but does not implement supertrait \ + '{sup_shown}': '{var_name}' needs '{needed}'" + ), + Some(help), + ), + span, + ); + true + } + + /// What the trait `tr` asks of the type variables of `ty` for `ty` + /// to have it, gathered into `needs`: the variable itself; a part of + /// a type that has the trait by its structure; what the header of + /// the impl written for the type's head asks, at the type's + /// arguments. `false` where a part that is no variable lacks it. + fn trait_needs( + &self, + tr: TraitKey, + ty: &Type, + depth: usize, + needs: &mut Vec<(TyVar, TraitKey)>, + ) -> bool { + if depth > 16 { + return true; + } + let ty = crate::types::canonical::canonicalize(&self.tables.resolver, &self.apply(ty)); + if let Type::Var(v) = ty { + if !needs.contains(&(v, tr)) { + needs.push((v, tr)); + } + return true; + } + let Some(head) = self.type_name_for_impl(&ty) else { + return true; + }; + if self.by_structure(tr, head) { + // (What json decodes is the decoder's to say.) + if Self::is_decode(tr) { + return true; + } + let mut walk = super::solve::Walk::default(); + if self.structure_gap(tr, &ty, &mut walk, 0).is_some() { + return false; + } + for part in walk.open { + if let Type::Var(v) = part + && !needs.contains(&(v, tr)) + { + needs.push((v, tr)); + } + } + return (walk.written.iter()).all(|part| self.trait_needs(tr, part, depth + 1, needs)); + } + if !self.tables.trait_impl_set.contains(&(tr, head)) { + return false; + } + let Some(impl_ty) = self.tables.impl_self_types.get(&(tr, head)) else { + return true; + }; + let mut mapping: HashMap = HashMap::new(); + super::builtin_needs::match_type(impl_ty, &ty, &free_vars_in(impl_ty), &mut mapping); + (self + .tables + .impl_preds + .get(&(tr, head)) + .into_iter() + .flatten()) + .all(|pred| match pred { + Pred::Trait { + tr: bound, subject, .. + } => self.trait_needs( + *bound, + &substitute_vars(subject, &mapping), + depth + 1, + needs, + ), + _ => true, + }) + } + // ── Validate trait implementations ──────────────────────────────── pub(super) fn validate_trait_impls(&mut self) { @@ -66,6 +242,13 @@ impl TypeChecker { ); continue; } + // The supertrait holds at the impl's own type, its type + // variables having what the impl's header gives them + // (`trait Same for Box(a) where a: Equal`), not at the + // head alone: a `Box(a)` has `Equal` when its `a` does. + if self.supertrait_needs_reported(*trait_name, *type_name, *supertrait, diag_span) { + continue; + } // Resolve the supertrait's expected args against the // enclosing trait's impl args. Skip when there are no // supertrait args declared (bare `: Super` form). @@ -787,18 +970,24 @@ impl TypeChecker { // declared: `trait Greet for Widget { ... }` with no `type // Widget` would attach methods to a phantom type. A lowercase // target (`trait Display for a { ... }`) names a type variable, - // not a type: there is nothing to register it for. + // not a type: an impl is for a declared type, never for every + // type. if self.names_rejected(ti.target_res, ti.target_type) { return; } let Some(written) = self.named_type(ti.target_res, ti.target_type) else { let name_str = resolve(ti.target_type); - if !name_str.starts_with(|c: char| c.is_lowercase()) { - self.error( - Code::UnknownType, - format!("trait impl target '{name_str}' is not a declared type"), - ti.span, - ); + self.error( + Code::UnknownType, + format!("trait impl target '{name_str}' is not a declared type"), + ti.span, + ); + self.unresolved_impl_methods + .extend(ti.methods.iter().map(|m| m.name)); + if name_str.starts_with(char::is_lowercase) + && let Some(tr) = self.impl_trait(ti) + { + self.blanket_traits.insert(tr); } return; }; @@ -1120,6 +1309,17 @@ impl TypeChecker { self.tables .impl_self_types .insert((trait_key, target_type), self_type.clone()); + // The names the header gives the impl's type variables, for a + // message about one (`validate_trait_impls`). + self.impl_var_names.insert( + (trait_key, target_type), + (impl_param_map.iter()) + .filter_map(|(name, ty)| match self.apply(ty) { + Type::Var(v) => Some((v, *name)), + _ => None, + }) + .collect(), + ); // Resolve impl-level where clauses (e.g. `trait X for Box(a) where // a: Show`) to `(TyVar, trait)` pairs against the impl_param_map. @@ -1466,8 +1666,6 @@ impl TypeChecker { } } - self.methods_named_like_fields(ti, target_type, trait_info_clone.as_ref()); - // ── The methods ───────────────────────────────────────────── // // An impl's method has the type its trait declares, with the @@ -1902,85 +2100,6 @@ impl TypeChecker { } } - /// Report each method the impl `ti` gives its type, the record - /// `target_type`, that is named like a field of the record: by the - /// name alone, whatever the field holds. `r.name(..)` is then a call - /// of what the field holds, on every record, and a method's call on - /// everything else, whether or not the receiver's type is known - /// where the call is written. - /// - /// A method the impl writes is reported where it is written; a - /// default method of the trait the impl leaves out, at the impl. - /// (A supertrait's methods are those of the supertrait's own impl.) - fn methods_named_like_fields( - &mut self, - ti: &TraitImpl, - target_type: TypeRef, - info: Option<&TraitInfo>, - ) { - let Some(record) = self.tables.records.get(&target_type).cloned() else { - return; - }; - // Where the record declares the field `name`, if it has one. - let field_span = |name: Symbol| { - let at = record.fields.iter().position(|(field, _)| *field == name)?; - record.field_spans.get(at).copied() - }; - // The impl as it is written (its target may be an alias of the - // record; a type of another module with its module), the record - // by its name. - let tr = self.impl_trait_name(ti); - let ty = self.show_type(&Type::Generic(target_type, vec![])); - let of = self.impl_target_name(ti, target_type); - let mut found: Vec<(Span, String, Symbol, Span)> = Vec::new(); - let mut written: Vec = Vec::new(); - for method in &ti.methods { - if written.contains(&method.name) { - continue; - } - written.push(method.name); - // (A method the trait does not declare is reported as that.) - let declared = - info.is_none_or(|info| info.methods.iter().any(|(name, _)| *name == method.name)); - if declared && let Some(field) = field_span(method.name) { - let message = format!( - "method '{}' of the impl of '{tr}' for '{of}' is named like a field of '{ty}'", - method.name - ); - found.push((method.name_span, message, method.name, field)); - } - } - let defaults = info.into_iter().flat_map(|info| { - info.methods - .iter() - .map(|(name, _)| *name) - .filter(|name| info.default_method_bodies.contains_key(name)) - }); - for name in defaults { - if written.contains(&name) { - continue; - } - if let Some(field) = field_span(name) { - let message = format!( - "the impl of '{tr}' for '{of}' takes the default method '{name}' of the \ - trait, which is named like a field of '{ty}'" - ); - found.push((ti.span, message, name, field)); - } - } - for (span, message, name, field) in found { - let mut d = Diagnostic::error(Code::MethodNamedLikeField, span, message); - // (A builtin record's fields are declared in no file.) - if field.is_in_source() { - d = d.with_label(field, format!("the field '{name}' of '{ty}'")); - } - self.errors.push(d.with_help( - "a field and a method of one type cannot share a name: rename the method or \ - the field", - )); - } - } - /// Report the impl method `method`, written with a number of /// parameters other than the `declared` ones of its trait. fn impl_method_arity(&mut self, ti: &TraitImpl, method: &FnDecl, declared: usize) { diff --git a/src/typechecker/declare_types.rs b/src/typechecker/declare_types.rs index 4ad07815c..10cf8a25d 100644 --- a/src/typechecker/declare_types.rs +++ b/src/typechecker/declare_types.rs @@ -297,7 +297,6 @@ impl TypeChecker { ty, RecordInfo { fields: field_types.clone(), - field_spans: fields.iter().map(|f| f.name_span).collect(), }, ); diff --git a/src/typechecker/infer/pattern.rs b/src/typechecker/infer/pattern.rs index 2000fbc2c..847d02e46 100644 --- a/src/typechecker/infer/pattern.rs +++ b/src/typechecker/infer/pattern.rs @@ -853,6 +853,20 @@ impl TypeChecker { // itself (`(x, ^x)`) is not that name. if let Some(pinned_ty) = cx.pins.get(name).cloned().flatten() { self.unify(expected, &pinned_ty, span); + // A pin compares as `==` does: the type has `Equal`. + self.want( + Pred::Trait { + tr: TraitKey::builtin("Equal"), + args: Vec::new(), + subject: pinned_ty, + asker: None, + }, + Origin { + span: pattern_span, + callee: Some(*name), + op: Some(super::super::solve::PIN), + }, + ); } else { let msg = format_undefined_variable_message(*name, env, "in pin pattern"); self.error_help(Code::UndefinedVariable, msg, pattern_span); diff --git a/src/typechecker/inference.rs b/src/typechecker/inference.rs index 6ee83b8fb..aef1594ce 100644 --- a/src/typechecker/inference.rs +++ b/src/typechecker/inference.rs @@ -368,6 +368,7 @@ impl TypeChecker { return; } let needs = match (origin.callee, origin.op) { + (Some(pinned), Some(super::solve::PIN)) => format!(", which the pin '^{pinned}' needs"), (Some(callee), _) => format!(", which '{callee}' needs"), (None, Some(op)) => format!(", which {op} needs"), (None, None) => String::new(), @@ -394,7 +395,14 @@ impl TypeChecker { r.name ), ) - .with_help(format!("add `where {}: {bound}`", r.name)); + .with_help(match self.default_body_of { + // A default body may also have the trait from a supertrait. + Some(own) if resolve(r.name) == "Self" => format!( + "add `where Self: {bound}`, or make `{bound}` a supertrait: `trait {}: {bound}`", + self.show_trait(own) + ), + _ => format!("add `where {}: {bound}`", r.name), + }); self.errors.push(diagnostic); } @@ -639,7 +647,7 @@ impl TypeChecker { return None; } if self.self_param(matches[0].0, field).is_some() { - self.through_its_trait(&inner.to_string(), matches[0].0, field, span); + self.through_its_trait(&inner.to_string(), &[matches[0].0], field, span); return Some(Type::Error); } self.method_trait = Some(matches[0].0); @@ -656,7 +664,7 @@ impl TypeChecker { && self.self_param(tr, field).is_some() { let shown = self.show_type(&inner); - self.through_its_trait(&shown, tr, field, span); + self.through_its_trait(&shown, &[tr], field, span); return Some(Type::Error); } if let Some(trait_name) = entry.trait_name @@ -682,6 +690,32 @@ impl TypeChecker { } } + /// Where `expr` names a variant that has the name of a type (its own, + /// `Box` of `type Box(a) { Box(a), Empty }`, or one in sight), let it + /// name the type. + fn type_named_like_its_variant(&self, expr: &mut Expr) { + if !matches!(expr.kind, ExprKind::Ident(_) | ExprKind::FieldAccess(..)) { + return; + } + let (Some(crate::defs::Res::Def(id)), Some(defs)) = (expr.res, self.defs.as_ref()) else { + return; + }; + let Some(ty) = defs.variant_type(id) else { + return; + }; + let name = defs.get(id).name; + if let ExprKind::Ident(written) = expr.kind + && let Some((t, _)) = + (self.spelled.iter()).find(|(t, n)| **n == written && defs.get(**t).is_type()) + { + expr.res = Some(crate::defs::Res::Def(*t)); + } else if ty.name == name + && let crate::defs::DefKind::Type(t) = ty.kind + { + expr.res = Some(crate::defs::Res::Def(t.0)); + } + } + /// The trait an expression names (`Greet`, `m.Greet`, `Compare`), as /// the resolver resolved it; `None` for anything else. pub(super) fn trait_named(&self, expr: &Expr) -> Option { @@ -713,9 +747,26 @@ impl TypeChecker { }; let Some((_, method_ty)) = info.methods.iter().find(|(n, _)| *n == field) else { let message = format!("trait '{}' has no method '{field}'", self.show_trait(tr)); - let names: Vec = info.methods.iter().map(|(n, _)| resolve(*n)).collect(); - let help = super::suggest::suggest_similar(&resolve(field), &names) - .map(|name| format!("did you mean '{name}'?")); + // A method of a supertrait is called through that trait. + let sup = (self.supertraits_of(tr).into_iter()).find(|s| { + (self.tables.traits.get(s)) + .is_some_and(|i| i.methods.iter().any(|(n, _)| *n == field)) + }); + let help = match sup { + Some(s) => uncallable_builtin(s, field, "x").or_else(|| { + let shown = self.spell_trait(s); + let rest = if self.method_arity(s, field) > 1 { ", .." } else { "" }; + Some(format!( + "`{field}` is a method of supertrait `{shown}`: call it as `{shown}.{field}(x{rest})`" + )) + }), + None => { + let names: Vec = + info.methods.iter().map(|(n, _)| resolve(*n)).collect(); + super::suggest::suggest_similar(&resolve(field), &names) + .map(|name| format!("did you mean '{name}'?")) + } + }; self.error_help(Code::UnknownMethod, (message, help), span); return Type::Error; }; @@ -888,37 +939,48 @@ impl TypeChecker { if let Some(how) = uncallable_builtin(tr, field, written) { return Some(how); } - let shown = self.show_trait(tr); let params = self.method_arity(tr, field); - return Some(match self.self_param(tr, field) { - Some(_) => { - let rest = if params > 1 { ", .." } else { "" }; - format!( - "`{field}` is a method of trait `{shown}`: call it as \ - `{shown}.{field}({written}{rest})`" - ) - } - None => { - // (A type variable is called on as the `type a` - // parameter that would name it.) - let (on, parameter) = match ty { - Type::Rigid(r) => ( - r.name.to_string(), - format!(", where `{0}` is a `type {0}` parameter", r.name), - ), - _ => ( - self.type_name_for_impl(ty) - .map_or_else(|| "T".to_string(), |t| t.name.to_string()), - String::new(), - ), - }; - let args = if params > 0 { ".." } else { "" }; - format!( - "`{field}` is a method of trait `{shown}` that makes a value: call it \ - on the type, `{on}.{field}({args})`{parameter}" - ) - } - }); + if self.self_param(tr, field).is_some() { + // (Every trait in sight that gives it, by how this module + // writes it.) + let taking: Vec = (traits.iter()) + .filter(|t| uncallable_builtin(**t, field, written).is_none()) + .filter(|t| self.self_param(**t, field).is_some()) + .map(|t| self.spell_trait(*t)) + .collect(); + let rest = if params > 1 { ", .." } else { "" }; + let calls: Vec = (taking.iter()) + .map(|shown| format!("`{shown}.{field}({written}{rest})`")) + .collect(); + let of: Vec = taking + .iter() + .map(|shown| format!("trait `{shown}`")) + .collect(); + return Some(format!( + "`{field}` is a method of {}: call it as {}", + of.join(" and of "), + calls.join(" or ") + )); + } + let shown = self.spell_trait(tr); + // (A type variable is called on as the `type a` parameter + // that would name it.) + let (on, parameter) = match ty { + Type::Rigid(r) => ( + r.name.to_string(), + format!(", where `{0}` is a `type {0}` parameter", r.name), + ), + _ => ( + self.type_name_for_impl(ty) + .map_or_else(|| "T".to_string(), |t| self.spell_type(t)), + String::new(), + ), + }; + let args = if params > 0 { ".." } else { "" }; + return Some(format!( + "`{field}` is a method of trait `{shown}` that makes a value: call it on the type, \ + `{on}.{field}({args})`{parameter}" + )); } let module = match ty { Type::List(_) => "list", @@ -933,29 +995,79 @@ impl TypeChecker { Type::Generic(name, _) if name.is_builtin("Bytes") => "bytes", _ => return None, }; - crate::builtins::registry::registry() + let row = crate::builtins::registry::registry() .row(module, &resolve(field)) - .filter(|row| row.enabled) - .map(|_| { - format!( - "`{field}` is a function of module `{module}`: call it as \ - `{module}.{field}({written})`" - ) - }) + .filter(|row| row.enabled)?; + let rest = if row.params.len() > 1 { ", .." } else { "" }; + // (As this module writes the module: `l.length` after `import + // list as l`.) + let written_module = (crate::session::ModuleId::builtin(module)) + .and_then(|id| self.module_spelled.get(&id)) + .map_or_else(|| module.to_string(), |alias| resolve(*alias)); + Some(format!( + "`{field}` is a function of module `{module}`: call it as \ + `{written_module}.{field}({written}{rest})`" + )) + } + + /// The trait `tr` as this module writes it: by the name it binds it + /// to (its own, an imported item), else through the module it + /// imports it by (`sh.Area` after `import shapes as sh`), else with + /// the module that declares it. + pub(super) fn spell_trait(&self, tr: TraitKey) -> String { + if let Some((name, None)) = crate::defs::builtin_trait_of_id(tr.id.0) { + return name.to_string(); + } + self.spell_def(tr.id.0) + .unwrap_or_else(|| self.show_trait(tr)) + } + + /// The type `ty` as this module writes it (see `spell_trait`). + pub(super) fn spell_type(&self, ty: TypeRef) -> String { + self.spell_def(ty.id.0) + .unwrap_or_else(|| self.show_type(&Type::Generic(ty, Vec::new()))) + } + + /// The definition `id` as this module writes it: by the name it + /// binds it to, or through the module it imports it by. + fn spell_def(&self, id: crate::defs::DefId) -> Option { + if let Some(name) = self.spelled.get(&id) { + return Some(resolve(*name)); + } + let def = self.def(id)?; + if def.module == self.module { + return Some(resolve(def.name)); + } + let alias = self.module_spelled.get(&def.module)?; + Some(format!("{alias}.{}", def.name)) } /// Report `T.field`, through the type `on`, of the method `field` of /// the trait `tr`, which takes a value of the type: it is called /// through its trait. - fn through_its_trait(&mut self, on: &str, tr: TraitKey, field: Symbol, span: Span) { - let shown = self.show_trait(tr); - let more = self.self_param(tr, field).is_some() && self.method_arity(tr, field) > 1; - let rest = if more { ", .." } else { "" }; - let help = match uncallable_builtin(tr, field, "x") { - Some(how) => how, - None => format!( - "`{field}` is a method of trait `{shown}`: call it as `{shown}.{field}(x{rest})`" - ), + fn through_its_trait(&mut self, on: &str, traits: &[TraitKey], field: Symbol, span: Span) { + let callable: Vec = (traits.iter().copied()) + .filter(|t| uncallable_builtin(*t, field, "x").is_none()) + .collect(); + let help = match (callable.as_slice(), traits.first()) { + ([], Some(first)) => uncallable_builtin(*first, field, "x").unwrap_or_default(), + _ => { + let of: Vec = (callable.iter()) + .map(|t| format!("trait `{}`", self.spell_trait(*t))) + .collect(); + let calls: Vec = (callable.iter()) + .map(|t| { + let more = self.method_arity(*t, field) > 1; + let rest = if more { ", .." } else { "" }; + format!("`{}.{field}(x{rest})`", self.spell_trait(*t)) + }) + .collect(); + format!( + "`{field}` is a method of {}: call it as {}", + of.join(" and of "), + calls.join(" or ") + ) + } }; self.error_help( Code::UnknownMethod, @@ -1820,6 +1932,15 @@ impl TypeChecker { { self.expected_closure = Some(expected); } + // A `type a` argument that names both a type and its + // variant (`type Box(a) { Box(a), Empty }`) is the + // type. + if let Some(param) = param + && matches!(self.apply(param), + Type::Generic(name, _) if name.is_builtin(crate::defs::TYPE_OF)) + { + self.type_named_like_its_variant(arg); + } // A decoder's type argument written as a type of // types (`List(Int)`, `(Int, String)`) is that type, // which the decoder's own check refuses. @@ -1942,6 +2063,13 @@ impl TypeChecker { } }; self.error_help(Code::ArityMismatch, (message, help), span); + // A parameter no argument is given is of no type a + // later message could ask about (`Greet.greet()`). + for param in params.iter().skip(args.len() + implicit_self) { + if let Type::Var(_) = self.apply(param) { + self.unify(param, &Type::Error, span); + } + } } (**ret).clone() } @@ -1956,11 +2084,20 @@ impl TypeChecker { ret } other => { - for arg in args.iter_mut() { - let _ = self.infer_expr(arg, env); - } + let arg_types: Vec = + args.iter_mut().map(|a| self.infer_expr(a, env)).collect(); match (other, form) { - (Type::Error, _) => Type::Error, + // What an argument of a callee already reported + // leaves unknown is asked about no more + // (`list.map(xs, Greet.greet)` before `import list`). + (Type::Error, _) => { + for ty in &arg_types { + for v in free_vars_in(&self.apply(ty)) { + self.unify(&Type::Var(v), &Type::Error, span); + } + } + Type::Error + } (Type::Never, _) => Type::Never, (_, CallForm::Call) => { let mut d = Diagnostic::error( @@ -2360,12 +2497,14 @@ impl TypeChecker { if let Some(tr) = self.entry_trait(&entry, field) && self.self_param(tr, field).is_some() { - // (The help names a trait whose method is - // called: a program's before `Display`.) - let tr = (self.method_providers(key.0, field).into_iter()) - .find(|t| uncallable_builtin(*t, field, "x").is_none()) - .unwrap_or(tr); - self.through_its_trait(&resolve(ty.name), tr, field, span); + // (The help names the traits whose method is + // called: a program's, not `Display`'s.) + let mut traits = self.method_providers(key.0, field); + if traits.is_empty() { + traits.push(tr); + } + let on = self.show_type(&Type::Generic(key.0, Vec::new())); + self.through_its_trait(&on, &traits, field, span); expr.ty = Some(Type::Error); return Type::Error; } @@ -4065,8 +4204,14 @@ impl TypeChecker { | ExprKind::Unit | ExprKind::Ident(_) | ExprKind::Lambda { .. } => true, - // A name through its module or its enum: `m.f`, `Color.Red`. - ExprKind::FieldAccess(..) => matches!(expr.res, Some(crate::defs::Res::Def(_))), + // A name through its module or its enum: `m.f`, `Color.Red`; + // a method through its trait or its type: `Greet.greet`, + // `Box.default`. + ExprKind::FieldAccess(obj, ..) => { + matches!(expr.res, Some(crate::defs::Res::Def(_))) + || self.trait_named(obj).is_some() + || self.res_type(obj.res).is_some() + } ExprKind::Tuple(elems) => elems.iter().all(|e| self.is_syntactic_value(e)), ExprKind::List(elems) => elems.iter().all(|e| match e { ListElem::Single(expr) => self.is_syntactic_value(expr), diff --git a/src/typechecker/init_order.rs b/src/typechecker/init_order.rs index dad4b982b..ccc82e12c 100644 --- a/src/typechecker/init_order.rs +++ b/src/typechecker/init_order.rs @@ -65,7 +65,7 @@ //! of an argument (`json.stringify`), `toml.stringify`, `toml.pretty` //! and the parameters of a postgres statement (whose signatures ask //! nothing of them, but which write them with the same writer), and -//! `.encode()` on a type with no impl of its own. Decoding a document +//! `json.Encode.encode(x)` on a type with no impl of its own. Decoding a document //! into a type is the same with the module's `json.Decode` impls of //! the type and of the types of its fields: a builtin whose signature //! asks `json.Decode` of its type argument (`json.parse`), and diff --git a/src/typechecker/mod.rs b/src/typechecker/mod.rs index 1970d3d79..9842ef869 100644 --- a/src/typechecker/mod.rs +++ b/src/typechecker/mod.rs @@ -151,6 +151,8 @@ pub struct TypeChecker { /// needs it finds on a variable its trait's or impl's methods share /// are its own. pub(super) body_owner: Option, + /// The trait whose default method body is being checked. + pub(super) default_body_of: Option, /// The calls of trait methods on values of known types, waiting for /// the module's bodies (`settle_builtin_needs`). pub(super) via_waiting: Vec, @@ -300,6 +302,16 @@ pub struct TypeChecker { /// The argument of type `Self` of the call through a trait whose /// callee is being inferred, with its place (`check_call`). pub(super) trait_receiver: Option<(Type, Span)>, + /// The type variables of each impl this module writes, by the names + /// its header gives them (`trait Same for Box(a)`). + pub(super) impl_var_names: HashMap<(TraitKey, TypeRef), Vec<(TyVar, Symbol)>>, + /// The names the module binds definitions of other modules to (an + /// item it imports, `import shapes.{ Area }`), for a help that + /// writes one as the module does. + pub(super) spelled: HashMap, + /// The names the module binds the modules it imports to (`sh` for + /// `import shapes as sh`). + pub(super) module_spelled: HashMap, /// The traits the module names by its imports (`import m.{ T }`), /// and the modules it imports: their traits it sees too. pub(super) seen_traits: std::collections::HashSet, @@ -355,6 +367,9 @@ pub struct TypeChecker { /// The methods of the impls whose trait or type the resolver resolved /// to nothing: a call of one is not reported again as unknown. pub(super) unresolved_impl_methods: std::collections::HashSet, + /// The traits of the module's rejected impls for every type + /// (`trait T for a`): a type that lacks one is not reported again. + pub(super) blanket_traits: std::collections::HashSet, /// Whether the module is a REPL cell: its `let`s are offered to the /// next cell, which may fix what is unknown of their types. pub(super) is_cell: bool, @@ -401,6 +416,7 @@ impl TypeChecker { given_waiting: Vec::new(), via_needs: HashMap::new(), body_owner: None, + default_body_of: None, via_waiting: Vec::new(), leaf_memo: HashMap::new(), leaf_busy: Vec::new(), @@ -443,6 +459,9 @@ impl TypeChecker { method_trait: None, trait_call_self: HashMap::new(), trait_receiver: None, + impl_var_names: HashMap::new(), + spelled: HashMap::new(), + module_spelled: HashMap::new(), seen_traits: std::collections::HashSet::new(), seen_modules: std::collections::HashSet::new(), builtin_imports: std::collections::HashSet::new(), @@ -460,6 +479,7 @@ impl TypeChecker { own_traits: HashMap::new(), rejected_types: std::collections::HashSet::new(), unresolved_impl_methods: std::collections::HashSet::new(), + blanket_traits: std::collections::HashSet::new(), is_cell: false, named_types: std::collections::HashSet::new(), signatures_only: false, @@ -1353,7 +1373,9 @@ impl TypeChecker { }; let key = self.own_trait(t.name); self.body_owner = Some(builtin_needs::body_key(key, "", method.name)); + self.default_body_of = Some(key); self.check_method_body(method, &sig, env); + self.default_body_of = None; self.body_owner = None; } } @@ -1536,6 +1558,17 @@ pub fn check_module(program: &mut Program, context: ModuleContext<'_>) -> Module checker.seen_modules.insert(*id); } } + for (name, binding) in scope.types.iter().chain(scope.values.iter()) { + match binding { + names::Binding::Def(id) => { + checker.spelled.entry(*id).or_insert(*name); + } + names::Binding::Module(id) => { + checker.module_spelled.entry(*id).or_insert(*name); + } + _ => {} + } + } // A builtin module is imported when the module binds a name of it, // or the module itself. for binding in scope.types.values().chain(scope.values.values()) { diff --git a/src/typechecker/names.rs b/src/typechecker/names.rs index 0939f8b37..af1d07c85 100644 --- a/src/typechecker/names.rs +++ b/src/typechecker/names.rs @@ -2523,7 +2523,7 @@ impl Resolver<'_> { self.error(Diagnostic::error(Code::UnresolvedName, span, NO_IF).with_help(NO_IF_HELP)); return; } - let message = format!("undefined variable '{name_str}'"); + let mut message = format!("undefined variable '{name_str}'"); let foreign = match name_str.as_str() { "break" | "continue" => { Some("silt has no 'break'/'continue' — return early or restructure the recursion") @@ -2554,11 +2554,21 @@ impl Resolver<'_> { .first() .filter(|_| elsewhere.iter().all(no_value)) .map(|(module, _)| *module); + // A trait of a module: it is imported by its name. + let trait_module = (elsewhere.first()) + .filter(|(module, _)| { + crate::module::missing_feature(&resolve(*module)).is_none() + }) + .filter(|(module, _)| self.is_trait_of(*module, name)) + .map(|(module, _)| *module); if let Some(module) = no_value_module { Some(super::builtin_env::bare_module_type_is_no_value_help( &name_str, &format!("{module}.{name}"), )) + } else if let Some(module) = trait_module { + message = format!("trait '{name_str}' is not in scope"); + Some(format!("import it: `import {module}.{{ {name} }}`")) } else if !elsewhere.is_empty() { Some(self.elsewhere_help(name, &elsewhere)) } else if let Some(help) = self.alias_help(name) { @@ -2589,6 +2599,30 @@ impl Resolver<'_> { self.error(d); } + /// Whether the module of the name `module` (bound here, imported, or + /// a builtin one) offers a trait `name`. + fn is_trait_of(&self, module: Symbol, name: Symbol) -> bool { + let bound = (self.scope.values.values()).filter_map(|b| match b { + Binding::Module(id) => Some(*id), + _ => None, + }); + let imported = (self.imports.values()).filter_map(|i| match i { + Imported::Module(id, _) | Imported::Builtin(id) => Some(*id), + _ => None, + }); + let mut ids = bound + .chain(imported) + .chain(ModuleId::builtin(&resolve(module))); + let Some(exports) = ids + .find(|id| self.module_name(*id) == Some(module)) + .and_then(|id| self.module_exports(id)) + else { + return false; + }; + matches!(exports.member(name), Some(Binding::Def(d)) + if matches!(self.defs.get(*d).kind, DefKind::Trait(_))) + } + /// The name the module imported as `name` is bound as, when an /// `import name as n` binds it. fn alias_of(&self, name: Symbol) -> Option { @@ -2696,6 +2730,13 @@ impl Resolver<'_> { } let ExprKind::Ident(head_name) = obj.kind else { self.expr(obj); + // `m.Box.default()`: the type, not its variant of that name. + if let Some(Res::Def(d)) = obj.res + && let Some(ty) = self.type_named_like_its_variant(d) + && !resolve(field).starts_with(char::is_uppercase) + { + obj.res = Some(Res::Def(ty)); + } return None; }; let head_span = obj.span; @@ -2750,7 +2791,19 @@ impl Resolver<'_> { None } Some(Binding::Def(d)) => { - obj.res = Some(Res::Def(d)); + // `Box.default()` with `type Box(a) { Box(a), .. }`, or with + // a type `Box` and a variant `Box` of another type: a + // variant has no methods, the type of that name does. + let ty = (self.defs.variant_type(d).is_some()) + .then(|| match self.lookup_type(head_name) { + Some(Binding::Def(t)) if self.defs.get(t).is_type() => Some(t), + _ => self.type_named_like_its_variant(d), + }) + .flatten(); + obj.res = Some(Res::Def(match ty { + Some(ty) if !resolve(field).starts_with(char::is_uppercase) => ty, + _ => d, + })); None } None if crate::module::is_builtin_module(&resolve(head_name)) @@ -2788,6 +2841,18 @@ impl Resolver<'_> { } } + /// The type of the variant `d` when it has the variant's name + /// (`type Box(a) { Box(a), Empty }`): where a type is meant (a method + /// through it, a `type a` argument) the name means it. + pub(super) fn type_named_like_its_variant(&self, d: DefId) -> Option { + let def = self.defs.get(d); + let ty = self.defs.variant_type(d)?; + match ty.kind { + DefKind::Type(t) if ty.name == def.name => Some(t.0), + _ => None, + } + } + /// Whether the type `d` is an enum (one with variants). fn enum_like(&self, d: DefId) -> bool { !self.defs.variants(d).is_empty() diff --git a/src/typechecker/scheme.rs b/src/typechecker/scheme.rs index 7eb29a919..fda8dcd89 100644 --- a/src/typechecker/scheme.rs +++ b/src/typechecker/scheme.rs @@ -588,6 +588,26 @@ impl TypeChecker { if self.names_rejected(res, def.name) { return Some(Scheme::mono(Type::Error)); } + // An enum written as a value whose name a variant of it has too + // (`type Box(a) { Box(a), Empty }`): the name is bound to the + // variant, and the type's value is made here. + if let crate::defs::DefKind::Type(t) = def.kind { + let ty = TypeRef { + id: t, + name: def.name, + }; + if let Some(info) = self.tables.enums.get(&ty) + && info.variants.iter().any(|v| v.name == def.name) + { + let vars = info.param_var_ids.clone(); + let args = vars.iter().map(|v| Type::Var(*v)).collect(); + return Some(Scheme { + vars, + preds: Vec::new(), + ty: Type::type_of(Type::Generic(ty, args)), + }); + } + } if def.module == self.module && !matches!(def.kind, crate::defs::DefKind::Variant { .. }) { return env.lookup(def.name).cloned(); } diff --git a/src/typechecker/size_locks.rs b/src/typechecker/size_locks.rs index 208910686..dee3cb379 100644 --- a/src/typechecker/size_locks.rs +++ b/src/typechecker/size_locks.rs @@ -23,10 +23,9 @@ fn enum_info_size_locked() { #[test] fn record_info_size_locked() { - // `field_spans`: where an impl's method named like a field points. assert_eq!( std::mem::size_of::(), - 48, + 24, "RecordInfo size changed — see module doc" ); } diff --git a/src/typechecker/solve.rs b/src/typechecker/solve.rs index 135142459..bc4e14397 100644 --- a/src/typechecker/solve.rs +++ b/src/typechecker/solve.rs @@ -130,6 +130,10 @@ impl Gap { } } +/// The `op` of the `Origin` of what a pin pattern (`^x`) owes: it +/// compares as `==` does. +pub(super) const PIN: &str = "^"; + /// Where a predicate is owed, and what asked for it. #[derive(Debug, Clone, Copy)] pub(crate) struct Origin { @@ -1263,7 +1267,7 @@ impl TypeChecker { } /// The supertraits of the trait `t`, theirs too. - fn supertraits_of(&self, t: TraitKey) -> Vec { + pub(super) fn supertraits_of(&self, t: TraitKey) -> Vec { let mut found: Vec = Vec::new(); let mut next = vec![t]; while let Some(t) = next.pop() { @@ -1430,16 +1434,15 @@ impl TypeChecker { if let Some(op) = origin.op && gap.is_whole() { - self.error( - Code::UnsupportedOperation, - operator_message( - &resolve(trait_name.name), - op, - &resolved, - &self.show_type(&resolved), + let shown = self.show_type(&resolved); + let message = match (op, origin.callee) { + // (A pin compares as `==` does.) + (PIN, Some(pinned)) => format!( + "the pin '^{pinned}' requires a comparable type, got '{shown}'" ), - span, - ); + _ => operator_message(&resolve(trait_name.name), op, &resolved, &shown), + }; + self.error(Code::UnsupportedOperation, message, span); return; } let message = match gap.is_whole() { @@ -1777,6 +1780,10 @@ impl TypeChecker { .trait_impl_set .contains(&(trait_name, type_name)) { + // The impl for every type was reported. + if self.blanket_traits.contains(&trait_name) { + return; + } // An operator's own operand: the operator's message. if let Some(op) = origin.op { self.error( diff --git a/src/typechecker/tables.rs b/src/typechecker/tables.rs index 5b10b1cbb..8e989dac1 100644 --- a/src/typechecker/tables.rs +++ b/src/typechecker/tables.rs @@ -21,9 +21,6 @@ pub(super) struct VariantInfo { #[derive(Debug, Clone, Default)] pub(super) struct RecordInfo { pub(super) fields: Vec<(Symbol, Type)>, - /// Where each field is named in the declaration, in the order of - /// `fields`: what a diagnostic about a field points at. - pub(super) field_spans: Vec, } /// Information about a declared trait. @@ -485,6 +482,27 @@ impl Tables { .collect() } + /// The methods called on the type `ty` (`Int.empty()`): each method + /// that takes no value of its trait's type, of each impl `ty` has + /// rows of, by trait and name. + pub fn makers_of(&self, ty: crate::defs::TypeId) -> Vec<(crate::defs::TraitId, Symbol)> { + let mut found: Vec<(crate::defs::TraitId, Symbol)> = Vec::new(); + for (of, name, tr) in self.impl_methods.keys() { + let Some(info) = (of.id == ty).then(|| self.traits.get(tr)).flatten() else { + continue; + }; + let takes_self = (info.methods.iter()).any(|(n, method)| { + n == name + && matches!(method, Type::Fun(params, _) + if params.iter().any(|p| matches!(p, Type::Var(v) if *v == info.self_var))) + }); + if !takes_self && !found.contains(&(tr.id, *name)) { + found.push((tr.id, *name)); + } + } + found + } + /// The fields of the record type `ty`, with their types, in /// declaration order; none for a type that is no record. pub fn record_fields_of(&self, ty: TypeRef) -> &[(Symbol, Type)] { diff --git a/src/typechecker/unify.rs b/src/typechecker/unify.rs index d2d29a57f..e7f297419 100644 --- a/src/typechecker/unify.rs +++ b/src/typechecker/unify.rs @@ -640,7 +640,7 @@ impl TypeChecker { match missing.as_slice() { [field] => format!("type '{shown}' has no field '{field}'"), _ => format!( - "record {shown} has no fields {}", + "type '{shown}' has no fields {}", missing .iter() .map(|f| format!("'{f}'")) diff --git a/src/vm/dispatch.rs b/src/vm/dispatch.rs index ecb7154f9..e52be5adb 100644 --- a/src/vm/dispatch.rs +++ b/src/vm/dispatch.rs @@ -204,6 +204,7 @@ impl Vm { | Value::Set(_) | Value::Variant(..) | Value::Record(..) + | Value::Bytes(_) | Value::Unit => { use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; diff --git a/src/vm/encode.rs b/src/vm/encode.rs index 70a7a81fe..4afae3235 100644 --- a/src/vm/encode.rs +++ b/src/vm/encode.rs @@ -1,5 +1,5 @@ //! Writing a value as data: `json.stringify`, `json.pretty`, -//! `json.encode`, `.encode()`, `toml.stringify`, `toml.pretty` and the +//! `json.encode`, `json.Encode.encode`, `toml.stringify`, `toml.pretty` and the //! parameters of a postgres statement. //! //! A value is written by the one writer (`builtins::data`), but a record diff --git a/src/vm/run.rs b/src/vm/run.rs index 670483519..5de502b5e 100644 --- a/src/vm/run.rs +++ b/src/vm/run.rs @@ -728,7 +728,7 @@ impl Vm { Err(Unwritten::NoText(at)) => { fail!(VmError::type_confusion(format!( "string interpolation requires string values, got {} \ - — call `.to_string()` or `.display()` on the value first", + — convert the value with `string.from` first", self.user_facing_type_name(&self.stack[at]) ))); } diff --git a/tests/golden/cli/disasm/disasm_rendering__a_method_found_by_a_later_argument.silt b/tests/golden/cli/disasm/disasm_rendering__a_method_found_by_a_later_argument.silt index 501702d5a..2d1f4b1be 100644 --- a/tests/golden/cli/disasm/disasm_rendering__a_method_found_by_a_later_argument.silt +++ b/tests/golden/cli/disasm/disasm_rendering__a_method_found_by_a_later_argument.silt @@ -2,7 +2,7 @@ -- A call through a trait whose argument of type Self is not the first, -- on a bounded type variable: `CallMethod` finds the method by that -- argument, which the disassembly names. --- stdout-contains: TailCallMethod 0 2 ; "scale" of Scale, by argument 1 +-- stdout-contains: TailCallMethod 0 2 ; "scale" of Scale, by argument 2 trait Scale { fn scale(by: Int, x: Self) -> Self } diff --git a/tests/golden/cli/repl/repl__field_method__impl_in_a_later_entry.silt b/tests/golden/cli/repl/repl__field_method__impl_in_a_later_entry.silt deleted file mode 100644 index 2d4b5b2f7..000000000 --- a/tests/golden/cli/repl/repl__field_method__impl_in_a_later_entry.silt +++ /dev/null @@ -1,5 +0,0 @@ --- cmd: repl --- stdin: type H { run: Int, n: Int }\ntrait Run { fn run(self) -> Int }\ntrait Run for H { fn run(self) -> Int { 7 } }\nlet h = H { run: 1, n: 2 }\nh.run\nfn bounded(r: a) -> Int where a: Run { Run.run(r) }\nbounded(h)\ntrait Run for Int { fn run(self) -> Int { self } }\nbounded(3)\n:quit\n --- An entry that adds an impl whose method is named like a field of a --- record of an earlier entry is the error, and adds nothing: the --- session goes on without the impl (`bounded(h)` has no `Run` for `H`). diff --git a/tests/golden/cli/repl/repl__field_method__impl_in_a_later_entry.stderr b/tests/golden/cli/repl/repl__field_method__impl_in_a_later_entry.stderr deleted file mode 100644 index d51f6400e..000000000 --- a/tests/golden/cli/repl/repl__field_method__impl_in_a_later_entry.stderr +++ /dev/null @@ -1,15 +0,0 @@ -error[type]: method 'run' of the impl of 'Run' for 'H' is named like a field of 'H' - --> :1:22 - | - 1 | trait Run for H { fn run(self) -> Int { 7 } } - | ^^^ method 'run' of the impl of 'Run' for 'H' is named like a field of 'H' - ::: :1:10 - | - 1 | type H { run: Int, n: Int } - | --- the field 'run' of 'H' - = help: a field and a method of one type cannot share a name: rename the method or the field -error[type]: type 'H' does not implement trait 'Run' - --> :1:9 - | - 1 | bounded(h) - | ^ type 'H' does not implement trait 'Run' diff --git a/tests/golden/cli/repl/repl__field_method__impl_in_a_later_entry.stdout b/tests/golden/cli/repl/repl__field_method__impl_in_a_later_entry.stdout deleted file mode 100644 index a40c7ffa2..000000000 --- a/tests/golden/cli/repl/repl__field_method__impl_in_a_later_entry.stdout +++ /dev/null @@ -1,3 +0,0 @@ -Silt REPL (type :quit to exit, :help for commands) -1 -3 diff --git a/tests/golden/cli/repl/repl__field_method__redeclared_type_is_a_new_type.silt b/tests/golden/cli/repl/repl__field_method__redeclared_type_is_a_new_type.silt deleted file mode 100644 index 306fd5a5c..000000000 --- a/tests/golden/cli/repl/repl__field_method__redeclared_type_is_a_new_type.silt +++ /dev/null @@ -1,6 +0,0 @@ --- cmd: repl --- stdin: type H { n: Int }\ntrait Run { fn run(self) -> Int }\ntrait Run for H { fn run(self) -> Int { 7 } }\nlet old = H { n: 2 }\ntype H { run: Int, n: Int }\nlet new = H { run: 1, n: 2 }\nRun.run(old)\nnew.run\nfn bounded(r: a) -> Int where a: Run { Run.run(r) }\nbounded(old)\nbounded(new)\ntrait Run for H { fn run(self) -> Int { 8 } }\n:quit\n --- A type declared again is a new type with no impls (stage 5 decision --- D4): declaring `H` again with a field `run` is no clash with the impl --- of `Run` for the old `H`, which the old values keep. The new `H` has --- no `Run`; an impl of it for the new `H` is the error. diff --git a/tests/golden/cli/repl/repl__field_method__redeclared_type_is_a_new_type.stderr b/tests/golden/cli/repl/repl__field_method__redeclared_type_is_a_new_type.stderr deleted file mode 100644 index ed9f6ab94..000000000 --- a/tests/golden/cli/repl/repl__field_method__redeclared_type_is_a_new_type.stderr +++ /dev/null @@ -1,15 +0,0 @@ -error[type]: type 'H' does not implement trait 'Run' - --> :1:9 - | - 1 | bounded(new) - | ^^^ type 'H' does not implement trait 'Run' -error[type]: method 'run' of the impl of 'Run' for 'H' is named like a field of 'H' - --> :1:22 - | - 1 | trait Run for H { fn run(self) -> Int { 8 } } - | ^^^ method 'run' of the impl of 'Run' for 'H' is named like a field of 'H' - ::: :1:10 - | - 1 | type H { run: Int, n: Int } - | --- the field 'run' of 'H' - = help: a field and a method of one type cannot share a name: rename the method or the field diff --git a/tests/golden/cli/repl/repl__field_method__redeclared_type_is_a_new_type.stdout b/tests/golden/cli/repl/repl__field_method__redeclared_type_is_a_new_type.stdout deleted file mode 100644 index 2a8cfb742..000000000 --- a/tests/golden/cli/repl/repl__field_method__redeclared_type_is_a_new_type.stdout +++ /dev/null @@ -1,4 +0,0 @@ -Silt REPL (type :quit to exit, :help for commands) -7 -1 -7 diff --git a/tests/golden/heavy/compute/stage6_perf__showing_values.silt b/tests/golden/heavy/compute/stage6_perf__showing_values.silt index dcd69f748..c6c2e7f8d 100644 --- a/tests/golden/heavy/compute/stage6_perf__showing_values.silt +++ b/tests/golden/heavy/compute/stage6_perf__showing_values.silt @@ -1,7 +1,7 @@ -- timeout: 60 -- A compute program of the performance guard: showing values. Records, -- variants, lists, tuples, options and maps are interpolated, converted --- with `string.from` and `.display()`, and printed, in a tail-recursive +-- with `string.from`, and printed, in a tail-recursive -- loop; no type has a Display impl of its own. import list import string diff --git a/tests/golden/lang/callables/pipe__into_a_method_call.silt b/tests/golden/lang/callables/pipe__into_a_method_call.silt index eee737ff6..bb0bcb091 100644 --- a/tests/golden/lang/callables/pipe__into_a_method_call.silt +++ b/tests/golden/lang/callables/pipe__into_a_method_call.silt @@ -1,5 +1,6 @@ -- A value piped into a call is the call's first argument, whatever the --- callee is: `5 |> a.add()` is `a.add(5)`, as `5 |> f(1)` is `f(5, 1)`. +-- callee is: `a |> Adder.add(5)` is `Adder.add(a, 5)`, as `5 |> f(1)` is +-- `f(5, 1)`. import list type Acc { base: Int } trait Adder { fn add(self, x: Int) -> Int } @@ -8,9 +9,9 @@ trait Adder for Int { fn add(self, x: Int) -> Int { self + x } } type Opt { Wrap(Int) } fn main() { let a = Acc { base: 100 } + println(a |> Adder.add(5)) println(Adder.add(a, 5)) - println(Adder.add(a, 5)) - println(Adder.add(3, 5)) + println(3 |> Adder.add(5)) println(5 |> Wrap) println(5 |> Wrap()) println([1, 2] |> list.map({ x -> x + 1 })) diff --git a/tests/golden/lang/errors/module_function_help__alias_and_other_arguments.silt b/tests/golden/lang/errors/module_function_help__alias_and_other_arguments.silt new file mode 100644 index 000000000..138fd1b1d --- /dev/null +++ b/tests/golden/lang/errors/module_function_help__alias_and_other_arguments.silt @@ -0,0 +1,13 @@ +-- exit: 1 +-- The help for a module function after a value writes the module as this +-- module imports it and keeps the other arguments as `..`. +-- stderr-contains: help: `length` is a function of module `list`: call it as `l.length(xs)` +-- stderr-contains: help: `get` is a function of module `map`: call it as `map.get(m, ..)` +import list as l +import map +fn main() { + let xs = [1, 2] + println(xs.length()) + let m = #{1: 2} + println(m.get(1)) +} diff --git a/tests/golden/lang/errors/round73f_deferred_fixes__display_method_equals_message_for_io_error.silt b/tests/golden/lang/errors/round73f_deferred_fixes__display_method_equals_message_for_io_error.silt deleted file mode 100644 index c324cba63..000000000 --- a/tests/golden/lang/errors/round73f_deferred_fixes__display_method_equals_message_for_io_error.silt +++ /dev/null @@ -1,19 +0,0 @@ --- e.display() and e.message() render a stdlib error the same way. -import io -fn main() { - match io.read_file("nope_round74_lock_does_not_exist.txt") { - Ok(_) -> () - Err(e) -> { - let display_method_form = "{e}" - let message_form = Error.message(e) - match display_method_form == message_form { - true -> println("EQUIV") - false -> { - println("DRIFT") - println(display_method_form) - println(message_form) - } - } - } - } -} diff --git a/tests/golden/lang/errors/round73f_deferred_fixes__display_method_equals_message_for_io_error.stdout b/tests/golden/lang/errors/round73f_deferred_fixes__display_method_equals_message_for_io_error.stdout deleted file mode 100644 index adf777c66..000000000 --- a/tests/golden/lang/errors/round73f_deferred_fixes__display_method_equals_message_for_io_error.stdout +++ /dev/null @@ -1 +0,0 @@ -EQUIV diff --git a/tests/golden/lang/errors/round92_tuple_index_message__multi_digit.silt b/tests/golden/lang/errors/round92_tuple_index_message__multi_digit.silt index 89df2db8d..167d99b10 100644 --- a/tests/golden/lang/errors/round92_tuple_index_message__multi_digit.silt +++ b/tests/golden/lang/errors/round92_tuple_index_message__multi_digit.silt @@ -1,7 +1,7 @@ -- cmd: check -- exit: 1 -- stderr-contains: tuple indexing ('t.11') is not supported --- stderr-not-contains: unknown method '11' +-- stderr-not-contains: has no field '11' fn main() { let t = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12) println(t.11) diff --git a/tests/golden/lang/errors/round92_tuple_index_message__targeted_message.silt b/tests/golden/lang/errors/round92_tuple_index_message__targeted_message.silt index 07e8efc81..381b72e5d 100644 --- a/tests/golden/lang/errors/round92_tuple_index_message__targeted_message.silt +++ b/tests/golden/lang/errors/round92_tuple_index_message__targeted_message.silt @@ -2,7 +2,7 @@ -- exit: 1 -- stderr-contains: tuple indexing ('t.0') is not supported -- stderr-contains: destructure instead: 'let (a, b) = t' --- stderr-not-contains: unknown method '0' +-- stderr-not-contains: has no field '0' fn main() { let t = (1, 2) println(t.0) diff --git a/tests/golden/lang/errors/unannotated_fields__two_missing_name_the_type.silt b/tests/golden/lang/errors/unannotated_fields__two_missing_name_the_type.silt new file mode 100644 index 000000000..e5be178a8 --- /dev/null +++ b/tests/golden/lang/errors/unannotated_fields__two_missing_name_the_type.silt @@ -0,0 +1,9 @@ +-- exit: 1 +-- Two fields a record lacks, read in an unannotated function: the +-- message names the type, as for one field. +-- stderr-contains: error[type]: type 'Person' has no fields 'email', 'phone' +type Person { name: String } +fn f(p) { "{p.email} {p.phone}" } +fn main() { + println(f(Person { name: "a" })) +} diff --git a/tests/golden/lang/json_encode/a_let_that_writes_a_value_needs_what_its_impl_reads.silt b/tests/golden/lang/json_encode/a_let_that_writes_a_value_needs_what_its_impl_reads.silt index ba83706df..ca3bde78a 100644 --- a/tests/golden/lang/json_encode/a_let_that_writes_a_value_needs_what_its_impl_reads.silt +++ b/tests/golden/lang/json_encode/a_let_that_writes_a_value_needs_what_its_impl_reads.silt @@ -1,6 +1,6 @@ -- A top-level `let` that writes a value whose type has a written -- `json.Encode` impl needs what the impl reads: it is initialised after --- `later`, whether it writes the value with `json.stringify`, `.encode()`, +-- `later`, whether it writes the value with `json.stringify`, `json.Encode.encode`, -- a function with a `where a: json.Encode`, `toml.stringify` or -- `json.encode`, as one that shows a value needs what a `Display` impl -- reads. diff --git a/tests/golden/lang/json_encode/json_encode_is_the_value_of_what_stringify_writes.silt b/tests/golden/lang/json_encode/json_encode_is_the_value_of_what_stringify_writes.silt index 200c4b7a8..7d8acad7a 100644 --- a/tests/golden/lang/json_encode/json_encode_is_the_value_of_what_stringify_writes.silt +++ b/tests/golden/lang/json_encode/json_encode_is_the_value_of_what_stringify_writes.silt @@ -1,7 +1,7 @@ -- `json.encode(x)` is the `json.Value` of what `json.stringify(x)` writes: -- what `json.parse` reads of that text (`1.0` is `json.Int(1)`, a record -- an object, whose keys come out sorted), written impls included, at the --- top too. `.encode()` is the same on a value of any type that has +-- top too. `json.Encode.encode(x)` is the same on a value of any type that has -- `json.Encode`, through a bound and by a concrete type, and an impl -- uses `json.encode` for the parts it writes. import json diff --git a/tests/golden/lang/patterns/pin__compares_as_equal_does.silt b/tests/golden/lang/patterns/pin__compares_as_equal_does.silt new file mode 100644 index 000000000..ab94215b4 --- /dev/null +++ b/tests/golden/lang/patterns/pin__compares_as_equal_does.silt @@ -0,0 +1,26 @@ +-- A pin compares values of a type with `Equal` as `==` does: an Int, a +-- record, and a channel, which is equal to itself only. +import channel +type P { n: Int } +fn same_ch(c: Channel(Int), d: Channel(Int)) -> Bool { + match d { + ^c -> true + _ -> false + } +} +fn main() { + let n = 3 + let p = P { n: 1 } + println(match 3 { + ^n -> "three" + _ -> "no" + }) + println(match P { n: 1 } { + ^p -> "p" + _ -> "other" + }) + let c = channel.new(1) + let d = channel.new(1) + println(same_ch(c, c)) + println(same_ch(c, d)) +} diff --git a/tests/golden/lang/patterns/pin__compares_as_equal_does.stdout b/tests/golden/lang/patterns/pin__compares_as_equal_does.stdout new file mode 100644 index 000000000..0dc5824f4 --- /dev/null +++ b/tests/golden/lang/patterns/pin__compares_as_equal_does.stdout @@ -0,0 +1,4 @@ +three +p +true +false diff --git a/tests/golden/lang/patterns/pin__needs_equal.silt b/tests/golden/lang/patterns/pin__needs_equal.silt new file mode 100644 index 000000000..e5ff733bc --- /dev/null +++ b/tests/golden/lang/patterns/pin__needs_equal.silt @@ -0,0 +1,28 @@ +-- cmd: check +-- exit: 1 +-- A pin compares as `==` does: the pinned value's type has `Equal`. A +-- function has none: an error at the pin, also through a function that +-- pins its parameter (at the call), and for a type variable without the +-- bound. (It used to check, and fail when run.) +fn is_it(f, g) { + match g { + ^f -> true + _ -> false + } +} +fn generic(x: a, y: a) -> Bool { + match y { + ^x -> true + _ -> false + } +} +fn main() { + let f = { x -> x + 1 } + let g = f + let r = match g { + ^f -> true + _ -> false + } + println(r) + println(is_it(f, f)) +} diff --git a/tests/golden/lang/patterns/pin__needs_equal.stderr b/tests/golden/lang/patterns/pin__needs_equal.stderr new file mode 100644 index 000000000..ed7128222 --- /dev/null +++ b/tests/golden/lang/patterns/pin__needs_equal.stderr @@ -0,0 +1,18 @@ +error[type]: type variable `a` is not known to implement trait 'Equal', which the pin '^x' needs + --> pin__needs_equal.silt:15:5 + | + 15 | ^x -> true + | ^^ type variable `a` is not known to implement trait 'Equal', which the pin '^x' needs + = help: add `where a: Equal` + +error[type]: the pin '^f' requires a comparable type, got 'Fn(Int) -> Int' + --> pin__needs_equal.silt:23:5 + | + 23 | ^f -> true + | ^^ the pin '^f' requires a comparable type, got 'Fn(Int) -> Int' + +error[type]: type 'Fn' does not implement trait 'Equal' + --> pin__needs_equal.silt:27:17 + | + 27 | println(is_it(f, f)) + | ^ type 'Fn' does not implement trait 'Equal' diff --git a/tests/golden/lang/resolve/resolve__builtin_trait_method_not_through_a_builtin_type.silt b/tests/golden/lang/resolve/resolve__builtin_trait_method_not_through_a_builtin_type.silt new file mode 100644 index 000000000..456640235 --- /dev/null +++ b/tests/golden/lang/resolve/resolve__builtin_trait_method_not_through_a_builtin_type.silt @@ -0,0 +1,24 @@ +-- cmd: check +-- exit: 1 +-- A builtin trait's method is not reached through a builtin type, by its +-- module path or an imported name: it is called through its trait (or, +-- for Display, by interpolation). +-- stderr-contains: error[type]: type 'io.IoError' has no method 'message' +-- stderr-contains: help: `message` is a method of trait `Error`: call it as `Error.message(x)` +-- stderr-contains: error[type]: type 'Int' has no method 'compare' +-- stderr-contains: help: `compare` is a method of trait `Compare`: call it as `Compare.compare(x, ..)` +-- stderr-contains: error[type]: type 'Int' has no method 'display' +-- stderr-contains: help: show a value with interpolation, `"{x}"`, or with `string.from(x)` +import io +import io.{ IoError } +fn main() { + match io.read_file("/nonexistent/silt/r5") { + Ok(_) -> println("ok") + Err(e) -> { + println(io.IoError.message(e)) + println(IoError.message(e)) + } + } + println(Int.compare(1, 2)) + println(Int.display(1)) +} diff --git a/tests/golden/lang/resolve/resolve__builtin_trait_method_through_builtin_type.silt b/tests/golden/lang/resolve/resolve__builtin_trait_method_through_builtin_type.silt deleted file mode 100644 index dd2f0beda..000000000 --- a/tests/golden/lang/resolve/resolve__builtin_trait_method_through_builtin_type.silt +++ /dev/null @@ -1,19 +0,0 @@ --- verdict: same --- The builtin traits' methods of a builtin type are reached through the --- type as through a value: `Int.display(1)` is `1.display()`, called or --- as a function value. -import string -fn main() { - println("{1}") - println(Compare.compare(1, 2)) - println(1 == 1) - println(Hash.hash(1) == Hash.hash(1)) - println(Compare.compare(1.5, 0.5)) - println("a" == "b") - println("{true}") - println("{[1, 2]}") - let show = string.from - println(show(4)) - let cmp = Compare.compare - println(cmp("a", "b")) -} diff --git a/tests/golden/lang/resolve/resolve__builtin_trait_method_through_builtin_type.stdout b/tests/golden/lang/resolve/resolve__builtin_trait_method_through_builtin_type.stdout deleted file mode 100644 index f56bd700e..000000000 --- a/tests/golden/lang/resolve/resolve__builtin_trait_method_through_builtin_type.stdout +++ /dev/null @@ -1,10 +0,0 @@ -1 --1 -true -true -1 -false -true -[1, 2] -4 --1 diff --git a/tests/golden/lang/resolve/resolve__error_message_through_builtin_type.silt b/tests/golden/lang/resolve/resolve__error_message_through_builtin_type.silt deleted file mode 100644 index 40e92c148..000000000 --- a/tests/golden/lang/resolve/resolve__error_message_through_builtin_type.silt +++ /dev/null @@ -1,18 +0,0 @@ --- verdict: same --- The Error trait's `message` of a builtin error type is reached through --- the type as `display` is: qualified (`io.IoError.message(e)`), through --- an imported name (`IoError.message(e)`), and as a function value. -import io -import io.{ IoError } -fn main() { - match io.read_file("/nonexistent/silt/r5") { - Ok(_) -> println("ok") - Err(e) -> { - println(Error.message(e) == Error.message(e)) - println(Error.message(e) == Error.message(e)) - println("{e}" == "{e}") - let m = Error.message - println(m(e) == Error.message(e)) - } - } -} diff --git a/tests/golden/lang/soundness/i5.silt b/tests/golden/lang/soundness/i5.silt index c5a2a8747..d64b9d176 100644 --- a/tests/golden/lang/soundness/i5.silt +++ b/tests/golden/lang/soundness/i5.silt @@ -6,6 +6,5 @@ type W(a) { W(a) } fn main() { let w1 = W({ x -> x + 1 }) println("{w1}") - println("{w1}") println(w1) } diff --git a/tests/golden/lang/stdlib/data_decode__a_trait_method_of_another_module_passes_its_type_on/case.stderr b/tests/golden/lang/stdlib/data_decode__a_trait_method_of_another_module_passes_its_type_on/case.stderr index 62b2d48f0..03325ba18 100644 --- a/tests/golden/lang/stdlib/data_decode__a_trait_method_of_another_module_passes_its_type_on/case.stderr +++ b/tests/golden/lang/stdlib/data_decode__a_trait_method_of_another_module_passes_its_type_on/case.stderr @@ -1,23 +1,23 @@ error[type]: `json.parse` cannot decode `Blob`: field `data` has type `Bytes`, which has no decoder - --> main.silt:14:26 + --> main.silt:15:26 | - 14 | println(Loader.load(s, Blob)) + 15 | println(Loader.load(s, Blob)) | ^^^^ `json.parse` cannot decode `Blob`: field `data` has type `Bytes`, which has no decoder = note: `Src.load` passes it to `json.parse` = help: decodable field types are Int, Float, String, Bool, time.Date, time.Time, time.DateTime, json.Value, List(T), Option(T), Map(String, T), tuples, non-generic record and enum types, types with a `json.Decode` impl, and aliases of these error[type]: `json.parse` cannot decode `Blob`: field `data` has type `Bytes`, which has no decoder - --> main.silt:15:25 + --> main.silt:16:25 | - 15 | println(Loader.own(s, Blob)) + 16 | println(Loader.own(s, Blob)) | ^^^^ `json.parse` cannot decode `Blob`: field `data` has type `Bytes`, which has no decoder = note: `Src.own` passes it to `json.parse` = help: decodable field types are Int, Float, String, Bool, time.Date, time.Time, time.DateTime, json.Value, List(T), Option(T), Map(String, T), tuples, non-generic record and enum types, types with a `json.Decode` impl, and aliases of these error[type]: `json.parse` cannot decode `Blob`: field `data` has type `Bytes`, which has no decoder - --> main.silt:16:25 + --> main.silt:17:32 | - 16 | println(Loader.own(s, Blob)) - | ^^^^ `json.parse` cannot decode `Blob`: field `data` has type `Bytes`, which has no decoder + 17 | println(loader.Loader.own(s, Blob)) + | ^^^^ `json.parse` cannot decode `Blob`: field `data` has type `Bytes`, which has no decoder = note: `Src.own` passes it to `json.parse` = help: decodable field types are Int, Float, String, Bool, time.Date, time.Time, time.DateTime, json.Value, List(T), Option(T), Map(String, T), tuples, non-generic record and enum types, types with a `json.Decode` impl, and aliases of these diff --git a/tests/golden/lang/stdlib/data_decode__a_trait_method_of_another_module_passes_its_type_on/main.silt b/tests/golden/lang/stdlib/data_decode__a_trait_method_of_another_module_passes_its_type_on/main.silt index 81fdc82bf..d7f56572f 100644 --- a/tests/golden/lang/stdlib/data_decode__a_trait_method_of_another_module_passes_its_type_on/main.silt +++ b/tests/golden/lang/stdlib/data_decode__a_trait_method_of_another_module_passes_its_type_on/main.silt @@ -2,9 +2,10 @@ -- exit: 1 -- A trait of one module whose default method gives its `type a` to a -- decoder, an impl of it in another module with a method of its own --- that does the same, and calls in a third: each call is checked, the --- default's too. +-- that does the same, and calls in a third, by the trait's name and +-- through its module: each call is checked, the default's too. import src +import loader import loader.{ Loader } type Blob { data: Bytes } @@ -13,5 +14,5 @@ fn main() { let s = src.Src { s: "\{}" } println(Loader.load(s, Blob)) println(Loader.own(s, Blob)) - println(Loader.own(s, Blob)) + println(loader.Loader.own(s, Blob)) } diff --git a/tests/golden/lang/traits/default_method__self_needs_a_trait_help.silt b/tests/golden/lang/traits/default_method__self_needs_a_trait_help.silt new file mode 100644 index 000000000..be28d9516 --- /dev/null +++ b/tests/golden/lang/traits/default_method__self_needs_a_trait_help.silt @@ -0,0 +1,10 @@ +-- exit: 1 +-- A default body that calls a method of another trait on `self`: the +-- help offers the bound and the supertrait. +-- stderr-contains: error[type]: type variable `Self` is not known to implement trait 'Greet2', which 'g2' needs +-- stderr-contains: help: add `where Self: Greet2`, or make `Greet2` a supertrait: `trait Def: Greet2` +trait Greet2 { fn g2(self) -> String } +trait Def { fn def(self) -> String { "def {Greet2.g2(self)}" } } +fn main() { + println(1) +} diff --git a/tests/golden/lang/traits/deferred_record_receiver_method.silt b/tests/golden/lang/traits/deferred_record_receiver_method.silt index 2a1a1abc3..6bbbab952 100644 --- a/tests/golden/lang/traits/deferred_record_receiver_method.silt +++ b/tests/golden/lang/traits/deferred_record_receiver_method.silt @@ -1,6 +1,7 @@ --- A method call on a lambda parameter that becomes a record type only --- once the call is inferred (`list.map(ps) { p -> p.greet() }`) is the --- record type's method, as for an Int or a List receiver. +-- A call through a trait on a lambda parameter that becomes a record +-- type only once the call is inferred +-- (`list.map(ps) { p -> Greet.greet(p) }`) is the record type's method, +-- as for an Int or a List receiver. import list type P { v: Int } trait Greet { fn greet(self) -> String } diff --git a/tests/golden/lang/traits/derived_impls_call_the_builtin_traits.silt b/tests/golden/lang/traits/derived_impls_call_the_builtin_traits.silt index 1b5d22bf3..53a9226a0 100644 --- a/tests/golden/lang/traits/derived_impls_call_the_builtin_traits.silt +++ b/tests/golden/lang/traits/derived_impls_call_the_builtin_traits.silt @@ -23,7 +23,6 @@ fn main() { println(Compare.compare(Wrap { p: Pt { x: 1 } }, Wrap { p: Pt { x: 2 } })) println([Pt { x: 2 }, Pt { x: 1 }] |> list.sort) println(Pt { x: 1 } == Pt { x: 1 }) - println(Pt { x: 1 } == Pt { x: 1 }) println(Hash.hash(Pt { x: 1 }) == Hash.hash(Pt { x: 2 })) println(map.get(#{ Pt { x: 1 }: "a" }, Pt { x: 1 })) } diff --git a/tests/golden/lang/traits/derived_impls_call_the_builtin_traits.stdout b/tests/golden/lang/traits/derived_impls_call_the_builtin_traits.stdout index 93f32046f..fd9ea7839 100644 --- a/tests/golden/lang/traits/derived_impls_call_the_builtin_traits.stdout +++ b/tests/golden/lang/traits/derived_impls_call_the_builtin_traits.stdout @@ -4,6 +4,5 @@ true -1 [Pt {x: 1}, Pt {x: 2}] true -true false Some(a) diff --git a/tests/golden/lang/traits/display__a_written_impl_is_what_every_way_of_showing_prints.silt b/tests/golden/lang/traits/display__a_written_impl_is_what_every_way_of_showing_prints.silt index e16f303d1..fa7497f64 100644 --- a/tests/golden/lang/traits/display__a_written_impl_is_what_every_way_of_showing_prints.silt +++ b/tests/golden/lang/traits/display__a_written_impl_is_what_every_way_of_showing_prints.silt @@ -1,7 +1,6 @@ --- There is one Display. A type's written impl is what `.display()`, --- interpolation, `println`, `print`, `string.from`, a `where a: --- Display` function and the default `Error.message` print, alone and --- inside a list. +-- There is one Display. A type's written impl is what interpolation, +-- `println`, `print`, `string.from`, a `where a: Display` function and +-- the default `Error.message` print, alone and inside a list. import string type Temp { @@ -23,7 +22,6 @@ fn show(x: a) -> String where a: Display { fn main() { let t = Temp { degrees: 21 } println("{t}") - println("{t}") println(t) println(string.from(t)) println(show(t)) @@ -32,6 +30,5 @@ fn main() { println("") println([t]) println("{[t]}") - println("{[t]}") println(show([t])) } diff --git a/tests/golden/lang/traits/display__a_written_impl_is_what_every_way_of_showing_prints.stdout b/tests/golden/lang/traits/display__a_written_impl_is_what_every_way_of_showing_prints.stdout index 61f63dfcf..e8e531716 100644 --- a/tests/golden/lang/traits/display__a_written_impl_is_what_every_way_of_showing_prints.stdout +++ b/tests/golden/lang/traits/display__a_written_impl_is_what_every_way_of_showing_prints.stdout @@ -4,8 +4,6 @@ 21C 21C 21C -21C -[21C] [21C] [21C] [21C] diff --git a/tests/golden/lang/traits/display__an_impl_shows_its_fields_with_their_impls.silt b/tests/golden/lang/traits/display__an_impl_shows_its_fields_with_their_impls.silt index c314b0dbf..7baf92b95 100644 --- a/tests/golden/lang/traits/display__an_impl_shows_its_fields_with_their_impls.silt +++ b/tests/golden/lang/traits/display__an_impl_shows_its_fields_with_their_impls.silt @@ -1,6 +1,7 @@ -- A Display impl that interpolates its fields shows each field as the --- field's own impl says; an impl may call `.display()` of a field, and +-- field's own impl says; an impl may show a field with `string.from`, and -- may be called through a bounded type variable and in a task. +import string import task type Temp { @@ -20,7 +21,7 @@ type Span { trait Display for Span { fn display(self) -> String { - "{self.low}..{self.high}" + "{self.low}..{string.from(self.high)}" } } diff --git a/tests/golden/lang/traits/hash_of_bytes__through_the_trait.silt b/tests/golden/lang/traits/hash_of_bytes__through_the_trait.silt new file mode 100644 index 000000000..915ac1453 --- /dev/null +++ b/tests/golden/lang/traits/hash_of_bytes__through_the_trait.silt @@ -0,0 +1,11 @@ +-- `Hash.hash` of a Bytes value, known where the call is written or only +-- where it runs, is the hash a map key of it has (it was "no method +-- 'hash' for type 'Bytes'" when run). +import bytes +fn hg(a: t) -> Int where t: Hash { Hash.hash(a) } +fn main() { + let a = bytes.from_string("a") + println(Hash.hash(a) == Hash.hash(bytes.from_string("a"))) + println(hg(a) == Hash.hash(a)) + println(Hash.hash([a]) == hg([a])) +} diff --git a/tests/golden/lang/resolve/resolve__error_message_through_builtin_type.stdout b/tests/golden/lang/traits/hash_of_bytes__through_the_trait.stdout similarity index 75% rename from tests/golden/lang/resolve/resolve__error_message_through_builtin_type.stdout rename to tests/golden/lang/traits/hash_of_bytes__through_the_trait.stdout index 1140ff52e..b979d62f4 100644 --- a/tests/golden/lang/resolve/resolve__error_message_through_builtin_type.stdout +++ b/tests/golden/lang/traits/hash_of_bytes__through_the_trait.stdout @@ -1,4 +1,3 @@ true true true -true diff --git a/tests/golden/lang/traits/imported_type_with_written_display/main.silt b/tests/golden/lang/traits/imported_type_with_written_display/main.silt index 8fd5294f0..0fab9c1f0 100644 --- a/tests/golden/lang/traits/imported_type_with_written_display/main.silt +++ b/tests/golden/lang/traits/imported_type_with_written_display/main.silt @@ -8,5 +8,5 @@ import types.{ Tag } fn main() { let t = Tag { name: "silt" } println("{t}") - println("{t}") + println(t) } diff --git a/tests/golden/lang/traits/imported_type_with_written_display_check/main.silt b/tests/golden/lang/traits/imported_type_with_written_display_check/main.silt index b42a8a34a..e81727971 100644 --- a/tests/golden/lang/traits/imported_type_with_written_display_check/main.silt +++ b/tests/golden/lang/traits/imported_type_with_written_display_check/main.silt @@ -9,5 +9,5 @@ import types.{ Tag } fn main() { let t = Tag { name: "silt" } println("{t}") - println("{t}") + println(t) } diff --git a/tests/golden/lang/traits/round88_unit_variant_method_dispatch__bare_unit_variant.silt b/tests/golden/lang/traits/round88_unit_variant_method_dispatch__bare_unit_variant.silt index 1368fd380..fe828ec0a 100644 --- a/tests/golden/lang/traits/round88_unit_variant_method_dispatch__bare_unit_variant.silt +++ b/tests/golden/lang/traits/round88_unit_variant_method_dispatch__bare_unit_variant.silt @@ -1,10 +1,14 @@ --- stderr-not-contains: undefined global: Red.display +-- A unit variant is a value a call through a trait dispatches on. type Color { Red, Green, Blue } -trait Display for Color { - fn display(self) -> String { "a color" } +trait Name { + fn name(self) -> String +} + +trait Name for Color { + fn name(self) -> String { "a color" } } fn main() { - println("{Red}") + println(Name.name(Red)) } diff --git a/tests/golden/lang/traits/round88_unit_variant_method_dispatch__let_bound_unit_variant.silt b/tests/golden/lang/traits/round88_unit_variant_method_dispatch__let_bound_unit_variant.silt index d6d18319f..44c56633f 100644 --- a/tests/golden/lang/traits/round88_unit_variant_method_dispatch__let_bound_unit_variant.silt +++ b/tests/golden/lang/traits/round88_unit_variant_method_dispatch__let_bound_unit_variant.silt @@ -1,10 +1,16 @@ +-- A let-bound unit variant is a value a call through a trait dispatches +-- on. type Color { Red, Green, Blue } -trait Display for Color { - fn display(self) -> String { "a color" } +trait Name { + fn name(self) -> String +} + +trait Name for Color { + fn name(self) -> String { "a color" } } fn main() { let r = Red - println("{r}") + println(Name.name(r)) } diff --git a/tests/golden/lang/traits/round88_unit_variant_method_dispatch__paren_unit_variant.silt b/tests/golden/lang/traits/round88_unit_variant_method_dispatch__paren_unit_variant.silt index 1368fd380..9f5809e37 100644 --- a/tests/golden/lang/traits/round88_unit_variant_method_dispatch__paren_unit_variant.silt +++ b/tests/golden/lang/traits/round88_unit_variant_method_dispatch__paren_unit_variant.silt @@ -1,10 +1,15 @@ --- stderr-not-contains: undefined global: Red.display +-- A unit variant in parentheses is a value a call through a trait +-- dispatches on. type Color { Red, Green, Blue } -trait Display for Color { - fn display(self) -> String { "a color" } +trait Name { + fn name(self) -> String +} + +trait Name for Color { + fn name(self) -> String { "a color" } } fn main() { - println("{Red}") + println(Name.name((Red))) } diff --git a/tests/golden/lang/traits/round88_unit_variant_method_dispatch__returns_value_from_trait_body.silt b/tests/golden/lang/traits/round88_unit_variant_method_dispatch__returns_value_from_trait_body.silt index 678ecaf7a..50584ba3e 100644 --- a/tests/golden/lang/traits/round88_unit_variant_method_dispatch__returns_value_from_trait_body.silt +++ b/tests/golden/lang/traits/round88_unit_variant_method_dispatch__returns_value_from_trait_body.silt @@ -1,4 +1,5 @@ --- stderr-not-contains: undefined global: Red.display +-- A unit variant shown by interpolation runs its Display impl's body and +-- shows what it returns. type Color { Red, Green, Blue } trait Display for Color { diff --git a/tests/golden/lang/traits/same_named_traits_deferred_receiver_has_no_field/main.silt b/tests/golden/lang/traits/same_named_traits_deferred_receiver_has_no_field/main.silt index 7c67f9bc8..975ff5e48 100644 --- a/tests/golden/lang/traits/same_named_traits_deferred_receiver_has_no_field/main.silt +++ b/tests/golden/lang/traits/same_named_traits_deferred_receiver_has_no_field/main.silt @@ -3,7 +3,7 @@ -- inferred names a field, which Int has not, where both modules' trait -- `Show` have a method `show`: the help names the first. -- stderr-contains: error[type]: type 'Int' has no field 'show' --- stderr-contains: help: `show` is a method of trait `a.Show`: call it as `a.Show.show(y)` +-- stderr-contains: help: `show` is a method of trait `a.Show` and of trait `b.Show`: call it as `a.Show.show(y)` or `b.Show.show(y)` -- stderr-contains: --> main.silt:12:34 import a import b diff --git a/tests/golden/lang/traits/same_named_traits_two_modules/main.silt b/tests/golden/lang/traits/same_named_traits_two_modules/main.silt index aab0b83d0..aff3bb3d1 100644 --- a/tests/golden/lang/traits/same_named_traits_two_modules/main.silt +++ b/tests/golden/lang/traits/same_named_traits_two_modules/main.silt @@ -1,6 +1,6 @@ -- Two modules each declare a trait `Show` with `fn show` and implement -- it for Int. Both impls exist; a call means the method of the trait --- its module sees: a's `x.show()` calls a.Show's, b's calls b.Show's, +-- its module sees: a's `Show.show(x)` calls a.Show's, b's calls b.Show's, -- concretely and through a `where t: Show` bound. An impl of a.Show for -- a type of this module is called through a's function. import a diff --git a/tests/golden/lang/traits/same_named_traits_unknown_receiver_reads_a_field/main.silt b/tests/golden/lang/traits/same_named_traits_unknown_receiver_reads_a_field/main.silt index 4a9f7af98..0faf7a892 100644 --- a/tests/golden/lang/traits/same_named_traits_unknown_receiver_reads_a_field/main.silt +++ b/tests/golden/lang/traits/same_named_traits_unknown_receiver_reads_a_field/main.silt @@ -2,7 +2,7 @@ -- A dot after a value of unknown type is a field: `g` takes a record -- with a field `show`, whatever traits in sight have a method `show`. -- stderr-contains: error[type]: type 'Int' has no field 'show' --- stderr-contains: help: `show` is a method of trait `a.Show`: call it as `a.Show.show(x)` +-- stderr-contains: help: `show` is a method of trait `a.Show` and of trait `b.Show`: call it as `a.Show.show(x)` or `b.Show.show(x)` import a import b fn main() { diff --git a/tests/golden/lang/traits/static_method__method_called_on_the_type.silt b/tests/golden/lang/traits/static_method__method_called_on_the_type.silt index f3ab1f6a9..eaeca3150 100644 --- a/tests/golden/lang/traits/static_method__method_called_on_the_type.silt +++ b/tests/golden/lang/traits/static_method__method_called_on_the_type.silt @@ -1,6 +1,6 @@ --- A method with `self` called on its type takes the receiver as its --- first argument; a method without `self` is called on the type or on --- a `type` parameter. +-- A method with `self` is called through its trait, called or as a +-- value, with the receiver as its first argument; a method without +-- `self` is called on the type or on a `type` parameter. trait Vals { fn total(self) -> Int fn zero() -> Self @@ -21,7 +21,8 @@ fn zero_of(type b) -> b where b: Vals { fn main() { println(Vals.total(7)) - println(Vals.total(7)) + let total = Vals.total + println(total(7)) println(Int.zero()) println(zero_of(Int)) } diff --git a/tests/golden/lang/traits/static_method__with_parameters_on_the_type.silt b/tests/golden/lang/traits/static_method__with_parameters_on_the_type.silt index 74ffc5d9e..69f910732 100644 --- a/tests/golden/lang/traits/static_method__with_parameters_on_the_type.silt +++ b/tests/golden/lang/traits/static_method__with_parameters_on_the_type.silt @@ -1,6 +1,7 @@ -- What works: a method without `self` on the type and on a `type` --- parameter, and as a function value; a method with `self` on a value --- and, with the receiver as its first argument, on the type. +-- parameter, and as a function value; a method with `self` through its +-- trait, with the receiver as its first argument, called and piped +-- into. trait Mk { fn make(n: Int) -> Self fn join(a: Self, b: Self) -> Self @@ -30,5 +31,5 @@ fn main() { let f = Int.make println(f(3)) println(Mk.twice(4)) - println(Mk.twice(4)) + println(4 |> Mk.twice) } diff --git a/tests/golden/lang/traits/structural__equal_compare_hash_agree.silt b/tests/golden/lang/traits/structural__equal_compare_hash_agree.silt index 74cca7198..8e14caa56 100644 --- a/tests/golden/lang/traits/structural__equal_compare_hash_agree.silt +++ b/tests/golden/lang/traits/structural__equal_compare_hash_agree.silt @@ -1,5 +1,5 @@ -- Equal, Compare and Hash are the value's structure, the same for the --- operator, the method, a `where` bound, a sort, a map key and a set +-- operator, the trait's method, a `where` bound, a sort, a map key and a set -- member. A record is ordered by its fields in the order the type -- declares them (not by their names), an enum by its variants in the -- order it declares them and then by payload. @@ -58,7 +58,6 @@ fn main() { let x = Tagged { tag: "t", item: [Some(a), None] } let y = Tagged { tag: "t", item: [Some(Version { minor: 1, major: 9 }), None] } println(x == y) - println(x == y) println(same(x, y)) println(hashes(x, y)) println(Hash.hash(x) == Hash.hash(y)) @@ -69,7 +68,6 @@ fn main() { println(same(x, Tagged { tag: "u", item: [] })) -- Floats: 0.0 and -0.0 are equal, hash alike and order as equal. println(0.0 == -0.0) - println(0.0 == -0.0) println(hashes(0.0, -0.0)) println(Compare.compare(0.0, -0.0)) println(set.length(#[0.0, -0.0])) diff --git a/tests/golden/lang/traits/structural__equal_compare_hash_agree.stdout b/tests/golden/lang/traits/structural__equal_compare_hash_agree.stdout index c105e3ed6..d81f6209e 100644 --- a/tests/golden/lang/traits/structural__equal_compare_hash_agree.stdout +++ b/tests/golden/lang/traits/structural__equal_compare_hash_agree.stdout @@ -12,7 +12,6 @@ true true true true -true 1 Some(found) true @@ -20,7 +19,6 @@ true false true true -true 0 1 true diff --git a/tests/golden/lang/traits/supertrait__the_header_gives_it.silt b/tests/golden/lang/traits/supertrait__the_header_gives_it.silt new file mode 100644 index 000000000..b7df0798c --- /dev/null +++ b/tests/golden/lang/traits/supertrait__the_header_gives_it.silt @@ -0,0 +1,35 @@ +-- An impl whose header gives its type variables what a supertrait needs +-- of them has the supertrait: directly, through a bound's supertrait, +-- and where a part of the type is of no variable. +trait Same: Equal { + fn same(self, o: Self) -> Bool { self == o } +} +trait Ord2: Compare { + fn lt(self, o: Self) -> Bool { self < o } +} +trait Super { + fn sup(self) -> String +} +trait Sub: Super { + fn sub(self) -> String { "sub of {Super.sup(self)}" } +} +type Box(a) { Bx(a) } +type Tagged(a) { Tagged(String, a) } +trait Super for Int { + fn sup(self) -> String { "int" } +} +trait Sub for Int {} +trait Same for Box(a) where a: Equal {} +trait Ord2 for Box(a) where a: Compare {} +trait Super for Box(a) where a: Super { + fn sup(self) -> String { match self { Bx(x) -> "box({Super.sup(x)})" } } +} +trait Sub for Box(a) where a: Sub {} +trait Same for Tagged(a) where a: Ord2 {} +trait Ord2 for Int {} +fn main() { + println(Same.same(Bx(1), Bx(1))) + println(Ord2.lt(Bx(1), Bx(2))) + println(Sub.sub(Bx(Bx(1)))) + println(Same.same(Tagged("t", 1), Tagged("t", 2))) +} diff --git a/tests/golden/lang/traits/supertrait__the_header_gives_it.stdout b/tests/golden/lang/traits/supertrait__the_header_gives_it.stdout new file mode 100644 index 000000000..5934d1dd7 --- /dev/null +++ b/tests/golden/lang/traits/supertrait__the_header_gives_it.stdout @@ -0,0 +1,4 @@ +true +true +sub of box(box(int)) +false diff --git a/tests/golden/lang/traits/trait_call__a_method_value_is_generic.silt b/tests/golden/lang/traits/trait_call__a_method_value_is_generic.silt new file mode 100644 index 000000000..5209d531e --- /dev/null +++ b/tests/golden/lang/traits/trait_call__a_method_value_is_generic.silt @@ -0,0 +1,20 @@ +-- A method named through its trait, or one that makes a value named +-- through its type, is a value as a function's name is: a `let` of it, +-- local or top-level, is general, and is used at two types. +trait Greet { fn greet(self) -> String } +trait Greet for Int { fn greet(self) -> String { "i" } } +trait Greet for String { fn greet(self) -> String { "s" } } +trait Default { fn default() -> Self } +type Box(a) { Bx(a), Empty } +trait Default for Box(a) { fn default() -> Self { Empty } } +let top = Greet.greet +fn main() { + let f = Greet.greet + println("{f(1)} {f("a")} {top(2)} {top("b")}") + let c = Compare.compare + println("{c(1, 2)} {c("b", "a")}") + let mk = Box.default + let a: Box(Int) = mk() + let b: Box(String) = mk() + println("{a} {b}") +} diff --git a/tests/golden/lang/traits/trait_call__a_method_value_is_generic.stdout b/tests/golden/lang/traits/trait_call__a_method_value_is_generic.stdout new file mode 100644 index 000000000..abe774de2 --- /dev/null +++ b/tests/golden/lang/traits/trait_call__a_method_value_is_generic.stdout @@ -0,0 +1,3 @@ +i s i s +-1 1 +Empty Empty diff --git a/tests/golden/lang/traits/trait_call__a_supertrait_method_through_the_subtrait.silt b/tests/golden/lang/traits/trait_call__a_supertrait_method_through_the_subtrait.silt new file mode 100644 index 000000000..c92fe10f9 --- /dev/null +++ b/tests/golden/lang/traits/trait_call__a_supertrait_method_through_the_subtrait.silt @@ -0,0 +1,12 @@ +-- exit: 1 +-- A method of a supertrait is called through the trait that declares +-- it: the help names that call. +-- stderr-contains: error[type]: trait 'Sub' has no method 'sup' +-- stderr-contains: help: `sup` is a method of supertrait `Super`: call it as `Super.sup(x)` +trait Super { fn sup(self) -> String } +trait Sub: Super { fn sub(self) -> String } +trait Super for Int { fn sup(self) -> String { "int" } } +trait Sub for Int { fn sub(self) -> String { "s" } } +fn main() { + println(Sub.sup(5)) +} diff --git a/tests/golden/lang/traits/trait_call__a_trait_not_in_scope/main.silt b/tests/golden/lang/traits/trait_call__a_trait_not_in_scope/main.silt new file mode 100644 index 000000000..8d2d31bc3 --- /dev/null +++ b/tests/golden/lang/traits/trait_call__a_trait_not_in_scope/main.silt @@ -0,0 +1,10 @@ +-- exit: 1 +-- A trait of an imported module written by its bare name: it is not in +-- scope, and the help imports it. +-- stderr-contains: error[type]: trait 'Area' is not in scope +-- stderr-contains: help: import it: `import shapes.{ Area }` +import shapes +fn main() { + let s = shapes.Sq { side: 3 } + println(Area.area(s)) +} diff --git a/tests/golden/lang/traits/trait_call__a_trait_not_in_scope/shapes.silt b/tests/golden/lang/traits/trait_call__a_trait_not_in_scope/shapes.silt new file mode 100644 index 000000000..5aea8b328 --- /dev/null +++ b/tests/golden/lang/traits/trait_call__a_trait_not_in_scope/shapes.silt @@ -0,0 +1,3 @@ +pub type Sq { side: Int } +pub trait Area { fn area(self) -> Int } +trait Area for Sq { fn area(self) -> Int { self.side * self.side } } diff --git a/tests/golden/lang/traits/trait_call__help_names_an_imported_trait_by_its_name/main.silt b/tests/golden/lang/traits/trait_call__help_names_an_imported_trait_by_its_name/main.silt new file mode 100644 index 000000000..23428000c --- /dev/null +++ b/tests/golden/lang/traits/trait_call__help_names_an_imported_trait_by_its_name/main.silt @@ -0,0 +1,13 @@ +-- exit: 1 +-- The help writes the trait as this module reaches it: by the name an +-- import gave it. Both messages spell the type the same way. +-- stderr-contains: error[type]: type 'shapes.Sq' has no method 'area' +-- stderr-contains: help: `area` is a method of trait `Area`: call it as `Area.area(x)` +-- stderr-contains: error[type]: type 'shapes.Sq' has no field 'area' +-- stderr-contains: help: `area` is a method of trait `Area`: call it as `Area.area(s)` +import shapes.{ Area, Sq } +fn main() { + let s = Sq { side: 3 } + println(Sq.area(s)) + println(s.area()) +} diff --git a/tests/golden/lang/traits/trait_call__help_names_an_imported_trait_by_its_name/shapes.silt b/tests/golden/lang/traits/trait_call__help_names_an_imported_trait_by_its_name/shapes.silt new file mode 100644 index 000000000..5aea8b328 --- /dev/null +++ b/tests/golden/lang/traits/trait_call__help_names_an_imported_trait_by_its_name/shapes.silt @@ -0,0 +1,3 @@ +pub type Sq { side: Int } +pub trait Area { fn area(self) -> Int } +trait Area for Sq { fn area(self) -> Int { self.side * self.side } } diff --git a/tests/golden/lang/traits/trait_call__help_names_every_trait_of_the_method.silt b/tests/golden/lang/traits/trait_call__help_names_every_trait_of_the_method.silt new file mode 100644 index 000000000..94b65b214 --- /dev/null +++ b/tests/golden/lang/traits/trait_call__help_names_every_trait_of_the_method.silt @@ -0,0 +1,14 @@ +-- exit: 1 +-- When two traits in sight have the method, the help names both, after a +-- value and through a type. +-- stderr-contains: help: `go` is a method of trait `Aa` and of trait `Bb`: call it as `Aa.go(n)` or `Bb.go(n)` +-- stderr-contains: help: `go` is a method of trait `Aa` and of trait `Bb`: call it as `Aa.go(x)` or `Bb.go(x)` +trait Aa { fn go(self) -> Int } +trait Bb { fn go(self) -> Int } +trait Aa for Int { fn go(self) -> Int { 1 } } +trait Bb for Int { fn go(self) -> Int { 2 } } +fn main() { + let n = 5 + println(n.go()) + println(Int.go(n)) +} diff --git a/tests/golden/lang/traits/trait_call__help_names_the_trait_by_its_alias/main.silt b/tests/golden/lang/traits/trait_call__help_names_the_trait_by_its_alias/main.silt new file mode 100644 index 000000000..dcb6593db --- /dev/null +++ b/tests/golden/lang/traits/trait_call__help_names_the_trait_by_its_alias/main.silt @@ -0,0 +1,10 @@ +-- exit: 1 +-- The help writes the trait as this module reaches it: through the +-- alias of its module. +-- stderr-contains: error[type]: type 'shapes.Sq' has no field 'area' +-- stderr-contains: help: `area` is a method of trait `sh.Area`: call it as `sh.Area.area(s)` +import shapes as sh +fn main() { + let s = sh.Sq { side: 3 } + println(s.area()) +} diff --git a/tests/golden/lang/traits/trait_call__help_names_the_trait_by_its_alias/shapes.silt b/tests/golden/lang/traits/trait_call__help_names_the_trait_by_its_alias/shapes.silt new file mode 100644 index 000000000..5aea8b328 --- /dev/null +++ b/tests/golden/lang/traits/trait_call__help_names_the_trait_by_its_alias/shapes.silt @@ -0,0 +1,3 @@ +pub type Sq { side: Int } +pub trait Area { fn area(self) -> Int } +trait Area for Sq { fn area(self) -> Int { self.side * self.side } } diff --git a/tests/golden/lang/traits/trait_call__no_argument_is_one_error.silt b/tests/golden/lang/traits/trait_call__no_argument_is_one_error.silt new file mode 100644 index 000000000..ec36bddcd --- /dev/null +++ b/tests/golden/lang/traits/trait_call__no_argument_is_one_error.silt @@ -0,0 +1,10 @@ +-- exit: 1 +-- A call through a trait given no argument is an arity error, and no +-- question about the type it was not given. +-- stderr-contains: error[type]: `Greet.greet` expects 1 argument, got 0 +-- stderr-not-contains: cannot infer +trait Greet { fn greet(self) -> String } +trait Greet for Int { fn greet(self) -> String { "i" } } +fn main() { + println(Greet.greet()) +} diff --git a/tests/golden/lang/traits/type_named_like_a_variant_of_another_type__is_the_type.silt b/tests/golden/lang/traits/type_named_like_a_variant_of_another_type__is_the_type.silt new file mode 100644 index 000000000..062a5d3b8 --- /dev/null +++ b/tests/golden/lang/traits/type_named_like_a_variant_of_another_type__is_the_type.silt @@ -0,0 +1,14 @@ +-- A name that is a type and a variant of another type: a method through +-- it and a `type a` argument mean the type, a value means the variant. +trait Default { fn default() -> Self } +type Box(a) { Bx(a), Empty } +type Other { Box, Nah } +trait Default for Box(a) { fn default() -> Self { Empty } } +fn mk(type t) -> t where t: Default { t.default() } +fn main() { + let b: Box(Int) = Box.default() + println(b) + let c: Box(Int) = mk(Box) + println(c) + println(Box) +} diff --git a/tests/golden/lang/traits/type_named_like_a_variant_of_another_type__is_the_type.stdout b/tests/golden/lang/traits/type_named_like_a_variant_of_another_type__is_the_type.stdout new file mode 100644 index 000000000..554ab7878 --- /dev/null +++ b/tests/golden/lang/traits/type_named_like_a_variant_of_another_type__is_the_type.stdout @@ -0,0 +1,3 @@ +Empty +Empty +Box diff --git a/tests/golden/lang/traits/type_named_like_its_variant__is_the_type/case.stdout b/tests/golden/lang/traits/type_named_like_its_variant__is_the_type/case.stdout new file mode 100644 index 000000000..15fb2ea85 --- /dev/null +++ b/tests/golden/lang/traits/type_named_like_its_variant__is_the_type/case.stdout @@ -0,0 +1,3 @@ +Blank Blank Empty Empty +Cell(1) +Box(2) diff --git a/tests/golden/lang/traits/type_named_like_its_variant__is_the_type/lib.silt b/tests/golden/lang/traits/type_named_like_its_variant__is_the_type/lib.silt new file mode 100644 index 000000000..114a9a6ca --- /dev/null +++ b/tests/golden/lang/traits/type_named_like_its_variant__is_the_type/lib.silt @@ -0,0 +1,14 @@ +pub trait Default { + fn default() -> Self +} + +pub type Box(a) { + Box(a), + Empty, +} + +trait Default for Box(a) { + fn default() -> Self { + Empty + } +} diff --git a/tests/golden/lang/traits/type_named_like_its_variant__is_the_type/main.silt b/tests/golden/lang/traits/type_named_like_its_variant__is_the_type/main.silt new file mode 100644 index 000000000..2e1ae4e7e --- /dev/null +++ b/tests/golden/lang/traits/type_named_like_its_variant__is_the_type/main.silt @@ -0,0 +1,33 @@ +-- A name that is both a type and one of its variants +-- (`type Box(a) { Box(a), Empty }`) is the type where a type is meant: +-- before a method that makes a value (`Box.default()`, also through a +-- module, `lib.Box.default()`) and as a `type t` argument (`mk(Box)`). +-- Elsewhere it is the variant. (It used to be the variant everywhere: +-- "unknown method 'default' on type Fn".) +import lib +import lib.{ Default } + +type Cell(a) { + Cell(a), + Blank, +} + +trait Default for Cell(a) { + fn default() -> Self { + Blank + } +} + +fn mk(type t) -> t where t: Default { + t.default() +} + +fn main() { + let a: Cell(Int) = Cell.default() + let b: Cell(Int) = mk(Cell) + let c: lib.Box(Int) = lib.Box.default() + let d: lib.Box(Int) = mk(lib.Box) + println("{a} {b} {c} {d}") + println(Cell(1)) + println(lib.Box(2)) +} diff --git a/tests/golden/lang/values/program_ioerror_beside_builtin.silt b/tests/golden/lang/values/program_ioerror_beside_builtin.silt index 4374fecd1..37e6b374f 100644 --- a/tests/golden/lang/values/program_ioerror_beside_builtin.silt +++ b/tests/golden/lang/values/program_ioerror_beside_builtin.silt @@ -1,7 +1,7 @@ -- A program's type named like the builtin io.IoError, with impls of its -- own: its impls do not replace the builtin type's, and each prints as --- itself (`println` of a value shows its fields; `.display()` runs the --- impl). +-- itself (`println` of a value shows its fields; `Error.message` runs +-- the impl). import io type IoError { Oops(String) } diff --git a/tests/golden/lang/values/same_named_records_methods_by_type/case.stdout b/tests/golden/lang/values/same_named_records_methods_by_type/case.stdout index ea0f56e3b..4690ad286 100644 --- a/tests/golden/lang/values/same_named_records_methods_by_type/case.stdout +++ b/tests/golden/lang/values/same_named_records_methods_by_type/case.stdout @@ -1,6 +1,5 @@ a.Pt 1 b.Pt 2 -a.Pt 1 -b.Pt 2 2 6 +8 diff --git a/tests/golden/lang/values/same_named_records_methods_by_type/main.silt b/tests/golden/lang/values/same_named_records_methods_by_type/main.silt index 56bfd6f37..84e246ca9 100644 --- a/tests/golden/lang/values/same_named_records_methods_by_type/main.silt +++ b/tests/golden/lang/values/same_named_records_methods_by_type/main.silt @@ -1,7 +1,8 @@ -- Two modules' record types of one name, Pt, each with its own impls of --- a builtin trait and of a trait of one name: a method call on a value --- and a call through the type (`a.Pt.display(p)`) run the impl of the --- value's own type, which the VM finds by the type's id. +-- a builtin trait and of a trait of one name: interpolation, a call +-- through each module's trait (`a.Scale.scale(p)`) and that method as a +-- value run the impl of the value's own type, which the VM finds by the +-- type's id. import a import b fn main() { @@ -9,8 +10,9 @@ fn main() { let q = b.make(2) println("{p}") println("{q}") - println("{p}") - println("{q}") println(a.Scale.scale(p)) println(b.Scale.scale(q)) + let scale_a = a.Scale.scale + let scale_b = b.Scale.scale + println(scale_a(p) + scale_b(q)) } diff --git a/tests/golden/lsp/completion_after_a_type_offers_its_making_methods.silt b/tests/golden/lsp/completion_after_a_type_offers_its_making_methods.silt new file mode 100644 index 000000000..43f8f9c64 --- /dev/null +++ b/tests/golden/lsp/completion_after_a_type_offers_its_making_methods.silt @@ -0,0 +1,19 @@ +-- cmd: lsp +-- lsp: completion 16:15 +-- lsp: completion 17:15 +-- After a type and a dot completion offers its variants and the methods +-- of its traits that make a value of it (called on the type). +trait Monoid { + fn empty() -> Self + fn combine(self, o: Self) -> Self +} +trait Monoid for Int { fn empty() -> Self { 0 } + fn combine(self, o: Self) -> Self { self + o } } +type Box { Full, Gone } +trait Monoid for Box { fn empty() -> Self { Gone } + fn combine(self, o: Self) -> Self { o } } +fn main() { + let a = Int.empty() + let b = Box.empty() + println("{a} {Monoid.combine(b, b)}") +} diff --git a/tests/golden/lsp/completion_after_a_type_offers_its_making_methods.stdout b/tests/golden/lsp/completion_after_a_type_offers_its_making_methods.stdout new file mode 100644 index 000000000..2fabf44ca --- /dev/null +++ b/tests/golden/lsp/completion_after_a_type_offers_its_making_methods.stdout @@ -0,0 +1,6 @@ +> completion 16:15 +empty +> completion 17:15 +Full +Gone +empty diff --git a/tests/golden/lsp/rename_by_meaning.silt b/tests/golden/lsp/rename_by_meaning.silt index 30b1d4ad4..8662f6f1b 100644 --- a/tests/golden/lsp/rename_by_meaning.silt +++ b/tests/golden/lsp/rename_by_meaning.silt @@ -20,8 +20,8 @@ -- lsp: references 80:10 -- lsp: rename 94:24 Holder -- A rename edits what the name means and nothing else. A method name at --- a call is no name of its trait (and has no rename); its definition is --- the method. `self` is not renamed. A field written without its value +-- a call is no name of its trait: its rename is the method's, as is its +-- definition. `self` is not renamed. A field written without its value -- is written out. The binders of an or-pattern's alternatives are one -- binding. A new name that is taken where the old one is written is -- refused. A block ends its `let`s. `::Item` names the trait. diff --git a/tests/golden/lsp/rename_by_meaning.stdout b/tests/golden/lsp/rename_by_meaning.stdout index 78bcb2fa8..7c14206c1 100644 --- a/tests/golden/lsp/rename_by_meaning.stdout +++ b/tests/golden/lsp/rename_by_meaning.stdout @@ -7,7 +7,8 @@ rename_by_meaning.silt:52:3-52:8 => Sizable rename_by_meaning.silt:52:23-52:28 => Sizable rename_by_meaning.silt:100:86-100:91 => Sizable > rename 52:10 resize -(nothing) +rename_by_meaning.silt:34:6-34:11 => resize +rename_by_meaning.silt:52:9-52:14 => resize > definition 52:10 rename_by_meaning.silt:34:6-34:11 > definition 52:30 diff --git a/tests/golden/lsp/signature_help_counts_the_piped_argument.silt b/tests/golden/lsp/signature_help_counts_the_piped_argument.silt new file mode 100644 index 000000000..1fe0c82bc --- /dev/null +++ b/tests/golden/lsp/signature_help_counts_the_piped_argument.silt @@ -0,0 +1,21 @@ +-- cmd: lsp +-- lsp: signature 18:24 +-- lsp: signature 19:27 +-- lsp: signature 20:20 +-- A piped value is the first argument of the call it goes into: the +-- argument written first in `3 |> Put.put(4)` is the second, also for a +-- module function and a function of this module. +import list +trait Put { fn put(self, n: Int) -> Int } +trait Put for Int { fn put(self, n: Int) -> Int { self + n } } +fn double(a: Int) -> Int { + a * 2 +} +fn add(a: Int, b: Int) -> Int { + a + b +} +fn main() { + println(3 |> Put.put(4)) + println([1] |> list.map(double)) + println(1 |> add(2)) +} diff --git a/tests/golden/lsp/signature_help_counts_the_piped_argument.stdout b/tests/golden/lsp/signature_help_counts_the_piped_argument.stdout new file mode 100644 index 000000000..76975a428 --- /dev/null +++ b/tests/golden/lsp/signature_help_counts_the_piped_argument.stdout @@ -0,0 +1,9 @@ +> signature 18:24 +fn put(self, n: Int) -> Int +argument 2: n: Int +> signature 19:27 +fn list.map(xs: List(a), f: Fn(a) -> b) -> List(b) +argument 2: f: Fn(a) -> b +> signature 20:20 +fn add(a: Int, b: Int) -> Int +argument 2: b: Int diff --git a/tests/golden/lsp/trait_method_references_and_rename/case.stdout b/tests/golden/lsp/trait_method_references_and_rename/case.stdout new file mode 100644 index 000000000..a7256258d --- /dev/null +++ b/tests/golden/lsp/trait_method_references_and_rename/case.stdout @@ -0,0 +1,39 @@ +> references 26:24 +main.silt:20:6-20:10 +main.silt:26:23-26:27 +main.silt:27:16-27:20 +shapes.silt:4:6-4:10 +shapes.silt:6:10-6:14 +shapes.silt:11:6-11:10 +> references 20:6 +main.silt:20:6-20:10 +main.silt:26:23-26:27 +main.silt:27:16-27:20 +shapes.silt:4:6-4:10 +shapes.silt:6:10-6:14 +shapes.silt:11:6-11:10 +> rename 27:17 surface +main.silt:20:6-20:10 => surface +main.silt:26:23-26:27 => surface +main.silt:27:16-27:20 => surface +shapes.silt:4:6-4:10 => surface +shapes.silt:6:10-6:14 => surface +shapes.silt:11:6-11:10 => surface +> rename 26:19 Measured +main.silt:15:17-15:21 => Measured +main.silt:19:7-19:11 => Measured +main.silt:26:18-26:22 => Measured +main.silt:27:11-27:15 => Measured +main.silt:27:40-27:44 => Measured +shapes.silt:3:11-3:15 => Measured +shapes.silt:6:5-6:9 => Measured +shapes.silt:10:7-10:11 => Measured +> rename 27:17 r +main.silt:20:6-20:10 => r +main.silt:26:23-26:27 => r +main.silt:27:16-27:20 => r +shapes.silt:4:6-4:10 => r +shapes.silt:6:10-6:14 => r +shapes.silt:11:6-11:10 => r +> rename 27:17 twice +error: `area` cannot be renamed to `twice`: the trait already has a method `twice`, and the rename would change what a name means diff --git a/tests/golden/lsp/trait_method_references_and_rename/main.silt b/tests/golden/lsp/trait_method_references_and_rename/main.silt new file mode 100644 index 000000000..6494e5637 --- /dev/null +++ b/tests/golden/lsp/trait_method_references_and_rename/main.silt @@ -0,0 +1,28 @@ +-- cmd: lsp +-- lsp: references 26:24 +-- lsp: references 20:6 +-- lsp: rename 27:17 surface +-- lsp: rename 26:19 Measured +-- lsp: rename 27:17 r +-- lsp: rename 27:17 twice +-- References and rename of a trait's method go to its declaration in +-- the trait, the methods of its impls (in any module) and its calls, +-- by the trait's name, qualified or imported; a rename of the trait +-- renames its name at the calls, not the method's. A rename to a field +-- of a type with an impl of the trait renames the method; one to another +-- method of the trait is refused. +import shapes +import shapes.{ Area, Square } + +type Circle { r: Int } + +trait Area for Circle { + fn area(self) -> Int { + self.r * self.r * 3 + } +} + +fn main() { + println(shapes.Area.area(Square { side: 2 })) + println(Area.area(Circle { r: 1 }) + Area.twice(Circle { r: 1 })) +} diff --git a/tests/golden/lsp/trait_method_references_and_rename/shapes.silt b/tests/golden/lsp/trait_method_references_and_rename/shapes.silt new file mode 100644 index 000000000..a3a39992c --- /dev/null +++ b/tests/golden/lsp/trait_method_references_and_rename/shapes.silt @@ -0,0 +1,14 @@ +pub type Square { side: Int } + +pub trait Area { + fn area(self) -> Int + fn twice(self) -> Int { + Area.area(self) * 2 + } +} + +trait Area for Square { + fn area(self) -> Int { + self.side * self.side + } +} diff --git a/tests/golden/meta/errors/round73_error_enum_registry_parity_tests__every_error_enum_message_dispatches.silt b/tests/golden/meta/errors/round73_error_enum_registry_parity_tests__every_error_enum_message_dispatches.silt index 9542462eb..79094eec1 100644 --- a/tests/golden/meta/errors/round73_error_enum_registry_parity_tests__every_error_enum_message_dispatches.silt +++ b/tests/golden/meta/errors/round73_error_enum_registry_parity_tests__every_error_enum_message_dispatches.silt @@ -1,4 +1,4 @@ --- `.message()` dispatches for a variant of every stdlib error enum +-- `Error.message(e)` dispatches for a variant of every stdlib error enum -- (the registry-driven `.message` globals and trait-dispatch table). -- The enums of modules a build may leave out are in cases of their own: -- HttpError and TcpError in diff --git a/tests/golden/meta/errors/round73_error_enum_registry_parity_tests__the_http_and_tcp_error_enum_messages_dispatch.silt b/tests/golden/meta/errors/round73_error_enum_registry_parity_tests__the_http_and_tcp_error_enum_messages_dispatch.silt index 62a6726ea..fb11e8447 100644 --- a/tests/golden/meta/errors/round73_error_enum_registry_parity_tests__the_http_and_tcp_error_enum_messages_dispatch.silt +++ b/tests/golden/meta/errors/round73_error_enum_registry_parity_tests__the_http_and_tcp_error_enum_messages_dispatch.silt @@ -1,6 +1,6 @@ -- requires-feature: http -- requires-feature: tcp --- `.message()` dispatches for a variant of the error enums of `http` +-- `Error.message(e)` dispatches for a variant of the error enums of `http` -- and `tcp`, which a build without those cargo features does not have -- (the other stdlib error enums are in -- round73_error_enum_registry_parity_tests__every_error_enum_message_dispatches). diff --git a/tests/golden/meta/typecheck/type_audit_regressions__receiver_method_correct_arity_accepted.silt b/tests/golden/meta/typecheck/type_audit_regressions__receiver_method_correct_arity_accepted.silt deleted file mode 100644 index 45c5d6945..000000000 --- a/tests/golden/meta/typecheck/type_audit_regressions__receiver_method_correct_arity_accepted.silt +++ /dev/null @@ -1,6 +0,0 @@ --- cmd: check --- exit: 0 -import string -fn main() { - println(string.from("hello")) -} diff --git a/tests/golden/typecheck/auto_derive/auto_derive_generic_synth__direct_method_call.silt b/tests/golden/typecheck/auto_derive/auto_derive_generic_synth__direct_method_call.silt index e58b393b2..91c38db6b 100644 --- a/tests/golden/typecheck/auto_derive/auto_derive_generic_synth__direct_method_call.silt +++ b/tests/golden/typecheck/auto_derive/auto_derive_generic_synth__direct_method_call.silt @@ -1,3 +1,5 @@ +-- The derived Equal, Compare and Hash of a generic record, called +-- directly: by `==` and through each trait. type Wrapped(a) { value: a } fn main() { let w = Wrapped { value: 7 } @@ -5,4 +7,6 @@ fn main() { let u = Wrapped { value: 8 } println(w == v) println(w == u) + println(Compare.compare(w, u)) + println(Hash.hash(w) == Hash.hash(v)) } diff --git a/tests/golden/typecheck/auto_derive/auto_derive_generic_synth__direct_method_call.stdout b/tests/golden/typecheck/auto_derive/auto_derive_generic_synth__direct_method_call.stdout index da29283aa..63eaa7447 100644 --- a/tests/golden/typecheck/auto_derive/auto_derive_generic_synth__direct_method_call.stdout +++ b/tests/golden/typecheck/auto_derive/auto_derive_generic_synth__direct_method_call.stdout @@ -1,2 +1,4 @@ true false +-1 +true diff --git a/tests/golden/typecheck/diagnostics/hidden_module__an_alias_names_the_module_too.stderr b/tests/golden/typecheck/diagnostics/hidden_module__an_alias_names_the_module_too.stderr index 7ddffeb39..4d09d2b3e 100644 --- a/tests/golden/typecheck/diagnostics/hidden_module__an_alias_names_the_module_too.stderr +++ b/tests/golden/typecheck/diagnostics/hidden_module__an_alias_names_the_module_too.stderr @@ -3,7 +3,7 @@ error[type]: type 'List(Int)' has no field 'length' | 10 | println(list.length(list)) | ^^^^^^^^^^^ type 'List(Int)' has no field 'length' - = help: `length` is a function of module `list`: call it as `list.length(list)` + = help: `length` is a function of module `list`: call it as `l.length(list)` error[type]: type 'List(Int)' has no field 'length' --> hidden_module__an_alias_names_the_module_too.silt:12:11 diff --git a/tests/golden/typecheck/diagnostics/round80_typechecker_fixes__shadow_builtin_int.silt b/tests/golden/typecheck/diagnostics/round80_typechecker_fixes__shadow_builtin_int.silt index 966a9958c..87f9c5e9b 100644 --- a/tests/golden/typecheck/diagnostics/round80_typechecker_fixes__shadow_builtin_int.silt +++ b/tests/golden/typecheck/diagnostics/round80_typechecker_fixes__shadow_builtin_int.silt @@ -2,6 +2,6 @@ -- exit: 1 -- Declaring `type Int` is rejected at the declaration, without the auto-derive cascade. -- stderr-contains: shadows builtin type 'Int' --- stderr-not-contains: unknown method 'x' on type Int +-- stderr-not-contains: has no field 'x' type Int { x: String } fn main() {} diff --git a/tests/golden/typecheck/display/display_impl__an_empty_impl_lacks_its_method.silt b/tests/golden/typecheck/display/display_impl__an_empty_impl_lacks_its_method.silt index 7b3ad7bc5..24b5fb2f8 100644 --- a/tests/golden/typecheck/display/display_impl__an_empty_impl_lacks_its_method.silt +++ b/tests/golden/typecheck/display/display_impl__an_empty_impl_lacks_its_method.silt @@ -4,7 +4,7 @@ -- as an impl of any trait does. (It was accepted, and the type was -- shown by its structure.) That is the impl's one error: the type has -- the trait by its impl for every question after it (printing, a bound, --- `.display()`, `D.display`), and none of them is reported as well. +-- `string.from`, interpolation), and none of them is reported as well. import string type D { @@ -28,7 +28,9 @@ fn main() { println([D { n: 1 }]) println(string.from(D { n: 1 })) println(show(D { n: 2 })) - println(string.from(D { n: 3 })) - println(string.from(F { f: { x -> x } })) + let d = D { n: 3 } + println("{d}") println(string.from(F { f: { x -> x } })) + let f = F { f: { x -> x } } + println("{f}") } diff --git a/tests/golden/typecheck/display/round101_polymorphic_display_method_gate__channel.silt b/tests/golden/typecheck/display/round101_polymorphic_display_bound_gate__channel.silt similarity index 65% rename from tests/golden/typecheck/display/round101_polymorphic_display_method_gate__channel.silt rename to tests/golden/typecheck/display/round101_polymorphic_display_bound_gate__channel.silt index 5b61085ea..29d8b32e1 100644 --- a/tests/golden/typecheck/display/round101_polymorphic_display_method_gate__channel.silt +++ b/tests/golden/typecheck/display/round101_polymorphic_display_bound_gate__channel.silt @@ -1,4 +1,4 @@ --- Polymorphic `.display()` on a channel is rejected where the function with the declared `Display` bound is called, with no interpolation suffix. +-- A channel given to a function with a Display bound (`where a: Display`) is rejected at the call, with no interpolation suffix. -- exit: 1 -- stderr-contains: does not implement trait 'Display' -- stderr-contains: 'Channel' diff --git a/tests/golden/typecheck/display/round101_polymorphic_display_method_gate__channel.stdout b/tests/golden/typecheck/display/round101_polymorphic_display_bound_gate__channel.stdout similarity index 100% rename from tests/golden/typecheck/display/round101_polymorphic_display_method_gate__channel.stdout rename to tests/golden/typecheck/display/round101_polymorphic_display_bound_gate__channel.stdout diff --git a/tests/golden/typecheck/display/round101_polymorphic_display_method_gate__display_values_still_work.silt b/tests/golden/typecheck/display/round101_polymorphic_display_bound_gate__display_values_still_work.silt similarity index 85% rename from tests/golden/typecheck/display/round101_polymorphic_display_method_gate__display_values_still_work.silt rename to tests/golden/typecheck/display/round101_polymorphic_display_bound_gate__display_values_still_work.silt index 616b9888f..b9a73922c 100644 --- a/tests/golden/typecheck/display/round101_polymorphic_display_method_gate__display_values_still_work.silt +++ b/tests/golden/typecheck/display/round101_polymorphic_display_bound_gate__display_values_still_work.silt @@ -1,4 +1,4 @@ --- Positive control: Int, String, a stdlib error variant and a user record render via polymorphic `.display()`. +-- Positive control: Int, String, a stdlib error variant and a user record are shown through a Display bound (`where a: Display`). -- stdout-contains: 42 -- stdout-contains: hi -- stdout-contains: cannot parse empty string diff --git a/tests/golden/typecheck/display/round101_polymorphic_display_method_gate__lambda.silt b/tests/golden/typecheck/display/round101_polymorphic_display_bound_gate__lambda.silt similarity index 61% rename from tests/golden/typecheck/display/round101_polymorphic_display_method_gate__lambda.silt rename to tests/golden/typecheck/display/round101_polymorphic_display_bound_gate__lambda.silt index 2b43216c0..d9f3b0d78 100644 --- a/tests/golden/typecheck/display/round101_polymorphic_display_method_gate__lambda.silt +++ b/tests/golden/typecheck/display/round101_polymorphic_display_bound_gate__lambda.silt @@ -1,4 +1,4 @@ --- Polymorphic `.display()` on a function is rejected where the function with the declared `Display` bound is called, with no interpolation suffix. +-- A function given to a function with a Display bound (`where a: Display`) is rejected at the call, with no interpolation suffix. -- exit: 1 -- stderr-contains: does not implement trait 'Display' -- stderr-contains: 'Fn' diff --git a/tests/golden/typecheck/display/round101_polymorphic_display_method_gate__lambda.stdout b/tests/golden/typecheck/display/round101_polymorphic_display_bound_gate__lambda.stdout similarity index 100% rename from tests/golden/typecheck/display/round101_polymorphic_display_method_gate__lambda.stdout rename to tests/golden/typecheck/display/round101_polymorphic_display_bound_gate__lambda.stdout diff --git a/tests/golden/typecheck/display/round101_polymorphic_display_method_gate__list_of_fn_rejected.silt b/tests/golden/typecheck/display/round101_polymorphic_display_bound_gate__list_of_fn_rejected.silt similarity index 79% rename from tests/golden/typecheck/display/round101_polymorphic_display_method_gate__list_of_fn_rejected.silt rename to tests/golden/typecheck/display/round101_polymorphic_display_bound_gate__list_of_fn_rejected.silt index f0ec6e5de..49203a38e 100644 --- a/tests/golden/typecheck/display/round101_polymorphic_display_method_gate__list_of_fn_rejected.silt +++ b/tests/golden/typecheck/display/round101_polymorphic_display_bound_gate__list_of_fn_rejected.silt @@ -1,7 +1,7 @@ -- cmd: check -- exit: 1 -- stderr-contains: error[type]: type 'List(Fn(_) -> _)' does not implement trait 'Display': element is of type 'Fn(_) -> _', which does not --- `.display()` on a list is the list's when its elements have Display: +-- A list is shown when its elements have Display: -- a list of functions has none, as for `println`. fn main() { let f = { y -> y } diff --git a/tests/golden/typecheck/display/round101_polymorphic_display_method_gate__task_handle.silt b/tests/golden/typecheck/display/round101_polymorphic_display_bound_gate__task_handle.silt similarity index 65% rename from tests/golden/typecheck/display/round101_polymorphic_display_method_gate__task_handle.silt rename to tests/golden/typecheck/display/round101_polymorphic_display_bound_gate__task_handle.silt index c29725e2a..f7f2012f5 100644 --- a/tests/golden/typecheck/display/round101_polymorphic_display_method_gate__task_handle.silt +++ b/tests/golden/typecheck/display/round101_polymorphic_display_bound_gate__task_handle.silt @@ -1,4 +1,4 @@ --- Polymorphic `.display()` on a task handle is rejected where the function with the declared `Display` bound is called, with no interpolation suffix. +-- A task handle given to a function with a Display bound (`where a: Display`) is rejected at the call, with no interpolation suffix. -- exit: 1 -- stderr-contains: does not implement trait 'Display' -- stderr-contains: 'task.Handle' diff --git a/tests/golden/typecheck/display/round101_polymorphic_display_method_gate__task_handle.stdout b/tests/golden/typecheck/display/round101_polymorphic_display_bound_gate__task_handle.stdout similarity index 100% rename from tests/golden/typecheck/display/round101_polymorphic_display_method_gate__task_handle.stdout rename to tests/golden/typecheck/display/round101_polymorphic_display_bound_gate__task_handle.stdout diff --git a/tests/golden/typecheck/display/round74_display_eq_message_runtime__io_error_three_forms_agree.stdout b/tests/golden/typecheck/display/round74_display_eq_message_runtime__io_error_three_forms_agree.stdout deleted file mode 100644 index d50925481..000000000 --- a/tests/golden/typecheck/display/round74_display_eq_message_runtime__io_error_three_forms_agree.stdout +++ /dev/null @@ -1,2 +0,0 @@ -EQUIV_ALL_THREE -CANONICAL_TEXT diff --git a/tests/golden/typecheck/display/round74_display_eq_message_runtime__io_error_three_forms_agree.silt b/tests/golden/typecheck/display/round74_display_eq_message_runtime__io_error_two_forms_agree.silt similarity index 55% rename from tests/golden/typecheck/display/round74_display_eq_message_runtime__io_error_three_forms_agree.silt rename to tests/golden/typecheck/display/round74_display_eq_message_runtime__io_error_two_forms_agree.silt index b989c3c94..dcb9358fc 100644 --- a/tests/golden/typecheck/display/round74_display_eq_message_runtime__io_error_three_forms_agree.silt +++ b/tests/golden/typecheck/display/round74_display_eq_message_runtime__io_error_two_forms_agree.silt @@ -1,4 +1,4 @@ --- `"{e}"`, `e.message()` and `e.display()` agree on the message form for an IoError. +-- `"{e}"` and `Error.message(e)` agree on the message form for an IoError. import io fn main() { match io.read_file("nope_round74_runtime_lock.txt") { @@ -6,31 +6,14 @@ fn main() { Err(e) -> { let display_form = "{e}" let message_form = Error.message(e) - let display_method_form = "{e}" match display_form == message_form { - true -> () + true -> println("EQUIV") false -> { println("DRIFT_FORMAT_VS_MESSAGE") println(display_form) println(message_form) } } - match display_method_form == message_form { - true -> () - false -> { - println("DRIFT_DISPLAY_METHOD_VS_MESSAGE") - println(display_method_form) - println(message_form) - } - } - match display_method_form == display_form { - true -> println("EQUIV_ALL_THREE") - false -> { - println("DRIFT_DISPLAY_METHOD_VS_FORMAT") - println(display_method_form) - println(display_form) - } - } -- And finally: assert the canonical message-form text -- (not the constructor form). If the message form ever -- drifts to the constructor form, this catches it. diff --git a/tests/golden/typecheck/display/round74_display_eq_message_runtime__io_error_two_forms_agree.stdout b/tests/golden/typecheck/display/round74_display_eq_message_runtime__io_error_two_forms_agree.stdout new file mode 100644 index 000000000..10978e556 --- /dev/null +++ b/tests/golden/typecheck/display/round74_display_eq_message_runtime__io_error_two_forms_agree.stdout @@ -0,0 +1,2 @@ +EQUIV +CANONICAL_TEXT diff --git a/tests/golden/typecheck/display/round74_display_eq_message_runtime__json_error_display_eq_message.silt b/tests/golden/typecheck/display/round74_display_eq_message_runtime__json_error_display_eq_message.silt index 2d6b0ac42..8be1f0be2 100644 --- a/tests/golden/typecheck/display/round74_display_eq_message_runtime__json_error_display_eq_message.silt +++ b/tests/golden/typecheck/display/round74_display_eq_message_runtime__json_error_display_eq_message.silt @@ -1,16 +1,16 @@ --- `e.display()` equals `e.message()` for a JsonError. +-- `"{e}"` equals `Error.message(e)` for a JsonError. import json fn main() { match json.parse("not valid json at all", String) { Ok(_) -> () Err(e) -> { - let display_method_form = "{e}" + let display_form = "{e}" let message_form = Error.message(e) - match display_method_form == message_form { + match display_form == message_form { true -> println("EQUIV") false -> { println("DRIFT") - println(display_method_form) + println(display_form) println(message_form) } } diff --git a/tests/golden/typecheck/display/round74_display_eq_message_runtime__user_enum_keeps_constructor_form.silt b/tests/golden/typecheck/display/round74_display_eq_message_runtime__user_enum_keeps_constructor_form.silt index c51c35871..bf0fe682c 100644 --- a/tests/golden/typecheck/display/round74_display_eq_message_runtime__user_enum_keeps_constructor_form.silt +++ b/tests/golden/typecheck/display/round74_display_eq_message_runtime__user_enum_keeps_constructor_form.silt @@ -1,4 +1,4 @@ --- A user enum's `display()` keeps the constructor form. +-- A user enum's interpolation keeps the constructor form. type Color { Red, Green(Int), Blue } fn main() { let c = Green(42) diff --git a/tests/golden/typecheck/fn_compare/container_fn_compare_runtime_gate__compare_method_int_lists.silt b/tests/golden/typecheck/fn_compare/container_fn_compare_runtime_gate__compare_method_int_lists.silt index b82a88a12..d25953a59 100644 --- a/tests/golden/typecheck/fn_compare/container_fn_compare_runtime_gate__compare_method_int_lists.silt +++ b/tests/golden/typecheck/fn_compare/container_fn_compare_runtime_gate__compare_method_int_lists.silt @@ -1,3 +1,3 @@ --- Positive control: `.compare()` on Int lists gives -1. +-- Positive control: `Compare.compare` on Int lists gives -1. fn cmpm(a: t, b: t) -> Int where t: Compare { Compare.compare(a, b) } fn main() { println(cmpm([1, 2], [3, 4])) } diff --git a/tests/golden/typecheck/fn_compare/container_fn_compare_runtime_gate__concrete_hash_fn_list.silt b/tests/golden/typecheck/fn_compare/container_fn_compare_runtime_gate__concrete_hash_fn_list.silt index 9e60ba9f1..925106981 100644 --- a/tests/golden/typecheck/fn_compare/container_fn_compare_runtime_gate__concrete_hash_fn_list.silt +++ b/tests/golden/typecheck/fn_compare/container_fn_compare_runtime_gate__concrete_hash_fn_list.silt @@ -1,4 +1,4 @@ --- A concrete `.hash()` on a list of functions fails at run time. +-- A concrete `Hash.hash` of a list of functions fails at run time. -- exit: 1 -- stderr-contains: error[type]: type 'List(Fn(_) -> _)' does not implement trait 'Hash': element is of type 'Fn(_) -> _', which does not fn main() { diff --git a/tests/golden/typecheck/fn_compare/container_fn_compare_runtime_gate__polymorphic_compare_method_fn_lists.silt b/tests/golden/typecheck/fn_compare/container_fn_compare_runtime_gate__polymorphic_compare_method_fn_lists.silt index d635cbb14..8caf9e858 100644 --- a/tests/golden/typecheck/fn_compare/container_fn_compare_runtime_gate__polymorphic_compare_method_fn_lists.silt +++ b/tests/golden/typecheck/fn_compare/container_fn_compare_runtime_gate__polymorphic_compare_method_fn_lists.silt @@ -1,4 +1,4 @@ --- `.compare()` through a polymorphic wrapper on lists of functions fails. +-- `Compare.compare` through a polymorphic wrapper on lists of functions fails. -- exit: 1 -- stderr-contains: error[type]: type 'List(Fn(_) -> _)' does not implement trait 'Compare': element is of type 'Fn(_) -> _', which does not fn cmpm(a: t, b: t) -> Int where t: Compare { Compare.compare(a, b) } diff --git a/tests/golden/typecheck/fn_compare/container_fn_compare_runtime_gate__polymorphic_equal_method_fn_lists.silt b/tests/golden/typecheck/fn_compare/container_fn_compare_runtime_gate__polymorphic_equal_bound_fn_lists.silt similarity index 75% rename from tests/golden/typecheck/fn_compare/container_fn_compare_runtime_gate__polymorphic_equal_method_fn_lists.silt rename to tests/golden/typecheck/fn_compare/container_fn_compare_runtime_gate__polymorphic_equal_bound_fn_lists.silt index 492fdacc3..50bd10cb2 100644 --- a/tests/golden/typecheck/fn_compare/container_fn_compare_runtime_gate__polymorphic_equal_method_fn_lists.silt +++ b/tests/golden/typecheck/fn_compare/container_fn_compare_runtime_gate__polymorphic_equal_bound_fn_lists.silt @@ -1,4 +1,4 @@ --- `.equal()` through a polymorphic wrapper on lists of functions fails. +-- `==` through a polymorphic wrapper (`where t: Equal`) on lists of functions fails. -- exit: 1 -- stderr-contains: error[type]: type 'List(Fn(_) -> _)' does not implement trait 'Equal': element is of type 'Fn(_) -> _', which does not fn eqm(a: t, b: t) -> Bool where t: Equal { a == b } diff --git a/tests/golden/typecheck/fn_compare/container_fn_compare_runtime_gate__polymorphic_hash_fn_lists.silt b/tests/golden/typecheck/fn_compare/container_fn_compare_runtime_gate__polymorphic_hash_fn_lists.silt index f93acd78d..1ccfe5e68 100644 --- a/tests/golden/typecheck/fn_compare/container_fn_compare_runtime_gate__polymorphic_hash_fn_lists.silt +++ b/tests/golden/typecheck/fn_compare/container_fn_compare_runtime_gate__polymorphic_hash_fn_lists.silt @@ -1,4 +1,4 @@ --- `.hash()` through a polymorphic wrapper on lists of functions fails. +-- `Hash.hash` through a polymorphic wrapper on lists of functions fails. -- exit: 1 -- stderr-contains: error[type]: type 'List(Fn(_) -> _)' does not implement trait 'Hash': element is of type 'Fn(_) -> _', which does not fn h(a: x) -> Int where x: Hash { Hash.hash(a) } diff --git a/tests/golden/typecheck/fn_compare/container_fn_compare_runtime_gate__polymorphic_hash_fn_tuples.silt b/tests/golden/typecheck/fn_compare/container_fn_compare_runtime_gate__polymorphic_hash_fn_tuples.silt index 3be032b2b..933d6c387 100644 --- a/tests/golden/typecheck/fn_compare/container_fn_compare_runtime_gate__polymorphic_hash_fn_tuples.silt +++ b/tests/golden/typecheck/fn_compare/container_fn_compare_runtime_gate__polymorphic_hash_fn_tuples.silt @@ -1,4 +1,4 @@ --- `.hash()` through a polymorphic wrapper on tuples holding functions fails. +-- `Hash.hash` through a polymorphic wrapper on tuples holding functions fails. -- exit: 1 -- stderr-contains: error[type]: type '(Fn(_) -> _, Int)' does not implement trait 'Hash': part #1 is of type 'Fn(_) -> _', which does not fn h(a: x) -> Int where x: Hash { Hash.hash(a) } diff --git a/tests/golden/typecheck/init_order/init_order__a_function_shows_what_it_is_named_at__an_own_type.silt b/tests/golden/typecheck/init_order/init_order__a_function_shows_what_it_is_named_at__an_own_type.silt index f15f41200..c4be342a4 100644 --- a/tests/golden/typecheck/init_order/init_order__a_function_shows_what_it_is_named_at__an_own_type.silt +++ b/tests/golden/typecheck/init_order/init_order__a_function_shows_what_it_is_named_at__an_own_type.silt @@ -1,7 +1,7 @@ -- A function that shows a value of its own type variable, named at a -- type of the module: each `let` reaches that type's impl, and is -- initialised after the `let` the impl reads. Through interpolation, --- `.display()`, `string.from`, two variables, a call of itself, a +-- `string.from`, two variables, a call of itself, a -- function value handed on, a wrapper, a local name, a closure the -- function returns, a record field, and a function without annotations. import list diff --git a/tests/golden/typecheck/init_order/init_order__showing_a_value_reaches_its_display_impls.silt b/tests/golden/typecheck/init_order/init_order__showing_a_value_reaches_its_display_impls.silt index 8ea2dad82..0a87774cf 100644 --- a/tests/golden/typecheck/init_order/init_order__showing_a_value_reaches_its_display_impls.silt +++ b/tests/golden/typecheck/init_order/init_order__showing_a_value_reaches_its_display_impls.silt @@ -1,6 +1,6 @@ -- Showing a value calls the Display impls of its parts: a top-level --- `let` that interpolates, prints, converts with `string.from` or calls --- `.display()` is initialised after what those impls read, for a part +-- `let` that interpolates, prints or converts with `string.from` is +-- initialised after what those impls read, for a part -- at any depth of the value's type, fields of the module's types -- included, and through a builtin handed on as a value. Showing a value -- with no such part orders nothing. diff --git a/tests/golden/typecheck/modules/impl_place__a_refused_display_impl_is_no_impl/main.silt b/tests/golden/typecheck/modules/impl_place__a_refused_display_impl_is_no_impl/main.silt index 04d476f59..7f6826cd4 100644 --- a/tests/golden/typecheck/modules/impl_place__a_refused_display_impl_is_no_impl/main.silt +++ b/tests/golden/typecheck/modules/impl_place__a_refused_display_impl_is_no_impl/main.silt @@ -1,9 +1,9 @@ -- cmd: check -- exit: 1 -- A Display impl written where it may not be is refused, and is no impl: --- the type is shown, printed, bounded and asked `.display()` by its --- structure, as without it. One error (there were seven: every use of --- the type's Display was refused too, except `.display()` and a +-- the type is shown, printed, bounded and converted with `string.from` +-- by its structure, as without it. One error (there were seven: every +-- use of the type's Display was refused too, except the method and a -- supertrait, which said yes). import types.{ Tag } diff --git a/tests/golden/typecheck/operators/compare__tuples_bool_and_unit_are_ordered.silt b/tests/golden/typecheck/operators/compare__tuples_bool_and_unit_are_ordered.silt index a9e92c33d..2fe1d5fd9 100644 --- a/tests/golden/typecheck/operators/compare__tuples_bool_and_unit_are_ordered.silt +++ b/tests/golden/typecheck/operators/compare__tuples_bool_and_unit_are_ordered.silt @@ -1,4 +1,4 @@ --- What has `Compare` is ordered by `<` and `.compare()`, directly and +-- What has `Compare` is ordered by `<` and `Compare.compare`, directly and -- through a bound: a tuple part by part, `false` before `true`, `()` -- equal to itself. import list diff --git a/tests/golden/typecheck/traits/blanket_impl__is_an_error.silt b/tests/golden/typecheck/traits/blanket_impl__is_an_error.silt new file mode 100644 index 000000000..a64383dd8 --- /dev/null +++ b/tests/golden/typecheck/traits/blanket_impl__is_an_error.silt @@ -0,0 +1,14 @@ +-- cmd: check +-- exit: 1 +-- An impl is for a declared type: `trait Shout for a` names a type +-- variable, and is an error, whether or not anything calls the method. +-- (It used to be skipped without a word.) +trait Shout { + fn shout(self) -> String +} +trait Shout for a { + fn shout(self) -> String { "!" } +} +fn main() { + println("declared only") +} diff --git a/tests/golden/typecheck/traits/blanket_impl__is_an_error.stderr b/tests/golden/typecheck/traits/blanket_impl__is_an_error.stderr new file mode 100644 index 000000000..5c1ed884c --- /dev/null +++ b/tests/golden/typecheck/traits/blanket_impl__is_an_error.stderr @@ -0,0 +1,5 @@ +error[type]: trait impl target 'a' is not a declared type + --> blanket_impl__is_an_error.silt:9:1 + | + 9 | trait Shout for a { + | ^^^^^^^^^^^^^^^^^^^ trait impl target 'a' is not a declared type diff --git a/tests/golden/typecheck/traits/blanket_impl__its_uses_are_not_reported_again.silt b/tests/golden/typecheck/traits/blanket_impl__its_uses_are_not_reported_again.silt new file mode 100644 index 000000000..ec7045921 --- /dev/null +++ b/tests/golden/typecheck/traits/blanket_impl__its_uses_are_not_reported_again.silt @@ -0,0 +1,11 @@ +-- cmd: check +-- exit: 1 +-- A use of a trait whose impl for every type is rejected is not reported +-- again as a type that lacks the trait. +-- stderr-contains: error[type]: trait impl target 'a' is not a declared type +-- stderr-not-contains: does not implement trait 'Shout' +trait Shout { fn shout(self) -> String { "default" } } +trait Shout for a {} +fn main() { + println(Shout.shout(5)) +} diff --git a/tests/golden/typecheck/traits/field_and_method__of_one_name.silt b/tests/golden/typecheck/traits/field_and_method__of_one_name.silt new file mode 100644 index 000000000..ef6853934 --- /dev/null +++ b/tests/golden/typecheck/traits/field_and_method__of_one_name.silt @@ -0,0 +1,56 @@ +-- verdict: same +-- A record may have a field and an impl a method of one name: a dot +-- after a value is the field, and the method is called through its +-- trait. `h.run(1)` calls the function the field `run` holds, +-- `Run.run(h, 1)` the impl's method, written or the trait's default, on +-- a known type, through a bound, on a generic record and where the +-- record's type is decided after the call is read (a closure's +-- parameter). An impl for an enum, a function named like a field and an +-- anonymous record are as before. +import list +type Shape { Circle(Int), Square(Int) } +trait Area { fn area(self) -> Int } +trait Area for Shape { + fn area(self) -> Int { + match self { + Circle(r) -> r * r * 3 + Square(s) -> s * s + } + } +} +trait Run { fn run(self, x: Int) -> Int } +trait Run for Int { fn run(self, x: Int) -> Int { self + x } } +type H { run: Fn(Int) -> Int, area: Int } +trait Run for H { fn run(self, x: Int) -> Int { self.area + x } } +trait Named { fn name(self) -> String { "by default" } } +type P { name: String } +trait Named for P {} +type Box(a) { value: a, run: Fn(a) -> a } +trait Run for Box(a) { fn run(self, x: Int) -> Int { x * 100 } } +fn run(x: Int) -> Int { x * 2 } +fn bounded(r: a, x: Int) -> Int where a: Run { Run.run(r, x) } +fn inferred(r, x) { Run.run(r, x) } +fn main() { + let h = H { run: { x -> x + 1 }, area: 9 } + println(h.run(1)) + println(Run.run(h, 1)) + println(1 |> h.run) + println(h |> Run.run(1)) + println(h.area) + println(bounded(h, 3)) + let p = P { name: "field" } + println(p.name) + println(Named.name(p)) + let b = Box { value: 2, run: { v -> v + 1 } } + println(b.run(2)) + println(Run.run(b, 2)) + println(Run.run(3, 4)) + println(run(5)) + println(Area.area(Circle(2))) + let anon = {run: { x -> x - 1 }} + println(anon.run(0)) + let hs = [h, H { run: { x -> x * 10 }, area: 0 }] + println(list.map(hs, { r -> r.run(7) })) + println(list.map(hs, { r -> Run.run(r, 7) })) + println(inferred(h, 4)) +} diff --git a/tests/golden/typecheck/traits/field_and_method__of_one_name.stdout b/tests/golden/typecheck/traits/field_and_method__of_one_name.stdout new file mode 100644 index 000000000..c315e3ea0 --- /dev/null +++ b/tests/golden/typecheck/traits/field_and_method__of_one_name.stdout @@ -0,0 +1,17 @@ +2 +10 +2 +10 +9 +12 +field +by default +3 +200 +7 +10 +12 +-1 +[8, 70] +[16, 7] +13 diff --git a/tests/golden/typecheck/traits/field_method__alias_of_the_record.silt b/tests/golden/typecheck/traits/field_method__alias_of_the_record.silt deleted file mode 100644 index 67a622e8b..000000000 --- a/tests/golden/typecheck/traits/field_method__alias_of_the_record.silt +++ /dev/null @@ -1,10 +0,0 @@ --- exit: 1 --- verdict: same --- An impl written for an alias of a record is the record's impl. -type H { run: Int } -type A = H -trait Run { fn run(self) -> Int } -trait Run for A { fn run(self) -> Int { 1 } } -fn main() { - println(H { run: 2 }.run) -} diff --git a/tests/golden/typecheck/traits/field_method__alias_of_the_record.stderr b/tests/golden/typecheck/traits/field_method__alias_of_the_record.stderr deleted file mode 100644 index 0c135746f..000000000 --- a/tests/golden/typecheck/traits/field_method__alias_of_the_record.stderr +++ /dev/null @@ -1,10 +0,0 @@ -error[type]: method 'run' of the impl of 'Run' for 'A' is named like a field of 'H' - --> field_method__alias_of_the_record.silt:7:22 - | - 7 | trait Run for A { fn run(self) -> Int { 1 } } - | ^^^ method 'run' of the impl of 'Run' for 'A' is named like a field of 'H' - ::: field_method__alias_of_the_record.silt:4:10 - | - 4 | type H { run: Int } - | --- the field 'run' of 'H' - = help: a field and a method of one type cannot share a name: rename the method or the field diff --git a/tests/golden/typecheck/traits/field_method__builtin_record.silt b/tests/golden/typecheck/traits/field_method__builtin_record.silt deleted file mode 100644 index 83fab64d0..000000000 --- a/tests/golden/typecheck/traits/field_method__builtin_record.silt +++ /dev/null @@ -1,16 +0,0 @@ --- exit: 1 --- verdict: same --- A builtin record type has fields as a program's record has: a --- program's trait implemented for one with a method named like a field --- is the same error. (The field is declared in no file, so there is no --- label for it.) -import time -trait Part { fn day(self) -> Int } -trait Part for time.Date { fn day(self) -> Int { 1 } } -trait Span { - fn ns(self) -> Int { 0 } -} -trait Span for time.Duration {} -fn main() { - println(1) -} diff --git a/tests/golden/typecheck/traits/field_method__builtin_record.stderr b/tests/golden/typecheck/traits/field_method__builtin_record.stderr deleted file mode 100644 index 7b0753587..000000000 --- a/tests/golden/typecheck/traits/field_method__builtin_record.stderr +++ /dev/null @@ -1,13 +0,0 @@ -error[type]: method 'day' of the impl of 'Part' for 'time.Date' is named like a field of 'time.Date' - --> field_method__builtin_record.silt:9:31 - | - 9 | trait Part for time.Date { fn day(self) -> Int { 1 } } - | ^^^ method 'day' of the impl of 'Part' for 'time.Date' is named like a field of 'time.Date' - = help: a field and a method of one type cannot share a name: rename the method or the field - -error[type]: the impl of 'Span' for 'time.Duration' takes the default method 'ns' of the trait, which is named like a field of 'time.Duration' - --> field_method__builtin_record.silt:13:1 - | - 13 | trait Span for time.Duration {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the impl of 'Span' for 'time.Duration' takes the default method 'ns' of the trait, which is named like a field of 'time.Duration' - = help: a field and a method of one type cannot share a name: rename the method or the field diff --git a/tests/golden/typecheck/traits/field_method__builtin_record_of_http.silt b/tests/golden/typecheck/traits/field_method__builtin_record_of_http.silt deleted file mode 100644 index 65e041d08..000000000 --- a/tests/golden/typecheck/traits/field_method__builtin_record_of_http.silt +++ /dev/null @@ -1,13 +0,0 @@ --- exit: 1 --- verdict: same --- requires-feature: http --- `http.Response` and `http.Request` are records with the fields --- `body` and `path`. -import http -trait Size { fn body(self) -> Int } -trait Size for http.Response { fn body(self) -> Int { 1 } } -trait Route { fn path(self) -> String { "/" } } -trait Route for http.Request {} -fn main() { - println(1) -} diff --git a/tests/golden/typecheck/traits/field_method__builtin_record_of_http.stderr b/tests/golden/typecheck/traits/field_method__builtin_record_of_http.stderr deleted file mode 100644 index f9e8a4889..000000000 --- a/tests/golden/typecheck/traits/field_method__builtin_record_of_http.stderr +++ /dev/null @@ -1,13 +0,0 @@ -error[type]: method 'body' of the impl of 'Size' for 'http.Response' is named like a field of 'http.Response' - --> field_method__builtin_record_of_http.silt:8:35 - | - 8 | trait Size for http.Response { fn body(self) -> Int { 1 } } - | ^^^^ method 'body' of the impl of 'Size' for 'http.Response' is named like a field of 'http.Response' - = help: a field and a method of one type cannot share a name: rename the method or the field - -error[type]: the impl of 'Route' for 'http.Request' takes the default method 'path' of the trait, which is named like a field of 'http.Request' - --> field_method__builtin_record_of_http.silt:10:1 - | - 10 | trait Route for http.Request {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the impl of 'Route' for 'http.Request' takes the default method 'path' of the trait, which is named like a field of 'http.Request' - = help: a field and a method of one type cannot share a name: rename the method or the field diff --git a/tests/golden/typecheck/traits/field_method__default_method.silt b/tests/golden/typecheck/traits/field_method__default_method.silt deleted file mode 100644 index de8bae59e..000000000 --- a/tests/golden/typecheck/traits/field_method__default_method.silt +++ /dev/null @@ -1,21 +0,0 @@ --- exit: 1 --- verdict: same --- A default method of the trait that the impl leaves out is a method the --- impl gives the type: named like a field, it is the error, at the --- impl's header. The field's type does not matter: `run` holds an Int. --- A default method the impl writes itself is reported at the method. -trait Run { - fn run(self, x: Int) -> Int { x } - fn name(self) -> String { "?" } - fn stop(self) -> Int -} -type H { run: Int, name: String } -trait Run for H { - fn name(self) -> String { "h" } - fn stop(self) -> Int { 0 } -} -fn main() { - let h = H { run: 1, name: "n" } - println(h.run) - println(Run.stop(h)) -} diff --git a/tests/golden/typecheck/traits/field_method__default_method.stderr b/tests/golden/typecheck/traits/field_method__default_method.stderr deleted file mode 100644 index e7f1c118e..000000000 --- a/tests/golden/typecheck/traits/field_method__default_method.stderr +++ /dev/null @@ -1,21 +0,0 @@ -error[type]: the impl of 'Run' for 'H' takes the default method 'run' of the trait, which is named like a field of 'H' - --> field_method__default_method.silt:13:1 - | - 13 | trait Run for H { - | ^^^^^^^^^^^^^^^^^ the impl of 'Run' for 'H' takes the default method 'run' of the trait, which is named like a field of 'H' - ::: field_method__default_method.silt:12:10 - | - 12 | type H { run: Int, name: String } - | --- the field 'run' of 'H' - = help: a field and a method of one type cannot share a name: rename the method or the field - -error[type]: method 'name' of the impl of 'Run' for 'H' is named like a field of 'H' - --> field_method__default_method.silt:14:6 - | - 14 | fn name(self) -> String { "h" } - | ^^^^ method 'name' of the impl of 'Run' for 'H' is named like a field of 'H' - ::: field_method__default_method.silt:12:20 - | - 12 | type H { run: Int, name: String } - | ---- the field 'name' of 'H' - = help: a field and a method of one type cannot share a name: rename the method or the field diff --git a/tests/golden/typecheck/traits/field_method__generic_record.silt b/tests/golden/typecheck/traits/field_method__generic_record.silt deleted file mode 100644 index b303f2a39..000000000 --- a/tests/golden/typecheck/traits/field_method__generic_record.silt +++ /dev/null @@ -1,11 +0,0 @@ --- exit: 1 --- verdict: same --- A field of a generic record whose type is the record's parameter --- clashes by its name as any other does, whatever the parameter is. -type G(a) { run: a, tag: Int } -trait Run { fn run(self, x: Int) -> Int } -trait Run for G(a) { fn run(self, x: Int) -> Int { x + self.tag } } -fn main() { - let g = G { run: "s", tag: 1 } - println(g.tag) -} diff --git a/tests/golden/typecheck/traits/field_method__generic_record.stderr b/tests/golden/typecheck/traits/field_method__generic_record.stderr deleted file mode 100644 index fea065882..000000000 --- a/tests/golden/typecheck/traits/field_method__generic_record.stderr +++ /dev/null @@ -1,10 +0,0 @@ -error[type]: method 'run' of the impl of 'Run' for 'G' is named like a field of 'G' - --> field_method__generic_record.silt:7:25 - | - 7 | trait Run for G(a) { fn run(self, x: Int) -> Int { x + self.tag } } - | ^^^ method 'run' of the impl of 'Run' for 'G' is named like a field of 'G' - ::: field_method__generic_record.silt:5:13 - | - 5 | type G(a) { run: a, tag: Int } - | --- the field 'run' of 'G' - = help: a field and a method of one type cannot share a name: rename the method or the field diff --git a/tests/golden/typecheck/traits/field_method__impl_before_type_and_trait.silt b/tests/golden/typecheck/traits/field_method__impl_before_type_and_trait.silt deleted file mode 100644 index 25a3da6f2..000000000 --- a/tests/golden/typecheck/traits/field_method__impl_before_type_and_trait.silt +++ /dev/null @@ -1,11 +0,0 @@ --- exit: 1 --- verdict: same --- The order of the declarations does not matter: the impl is written --- before its trait and before the record type. -trait Run for H { fn run(self, x: Int) -> Int { -1 } } -fn main() { - let h = H { run: { x -> x + 1 }, n: 0 } - println(h.run(1)) -} -trait Run { fn run(self, x: Int) -> Int } -type H { run: Fn(Int) -> Int, n: Int } diff --git a/tests/golden/typecheck/traits/field_method__impl_before_type_and_trait.stderr b/tests/golden/typecheck/traits/field_method__impl_before_type_and_trait.stderr deleted file mode 100644 index 290db4ff8..000000000 --- a/tests/golden/typecheck/traits/field_method__impl_before_type_and_trait.stderr +++ /dev/null @@ -1,10 +0,0 @@ -error[type]: method 'run' of the impl of 'Run' for 'H' is named like a field of 'H' - --> field_method__impl_before_type_and_trait.silt:5:22 - | - 5 | trait Run for H { fn run(self, x: Int) -> Int { -1 } } - | ^^^ method 'run' of the impl of 'Run' for 'H' is named like a field of 'H' - ::: field_method__impl_before_type_and_trait.silt:11:10 - | - 11 | type H { run: Fn(Int) -> Int, n: Int } - | --- the field 'run' of 'H' - = help: a field and a method of one type cannot share a name: rename the method or the field diff --git a/tests/golden/typecheck/traits/field_method__impl_in_the_traits_module/case.stderr b/tests/golden/typecheck/traits/field_method__impl_in_the_traits_module/case.stderr deleted file mode 100644 index 4e8252e38..000000000 --- a/tests/golden/typecheck/traits/field_method__impl_in_the_traits_module/case.stderr +++ /dev/null @@ -1,10 +0,0 @@ -error[type]: method 'run' of the impl of 'Run' for 'shapes.H' is named like a field of 'shapes.H' - --> runner.silt:3:29 - | - 3 | trait Run for shapes.H { fn run(self, x: Int) -> Int { x + self.n } } - | ^^^ method 'run' of the impl of 'Run' for 'shapes.H' is named like a field of 'shapes.H' - ::: shapes.silt:1:14 - | - 1 | pub type H { run: Fn(Int) -> Int, n: Int } - | --- the field 'run' of 'shapes.H' - = help: a field and a method of one type cannot share a name: rename the method or the field diff --git a/tests/golden/typecheck/traits/field_method__impl_in_the_traits_module/main.silt b/tests/golden/typecheck/traits/field_method__impl_in_the_traits_module/main.silt deleted file mode 100644 index c33efe92d..000000000 --- a/tests/golden/typecheck/traits/field_method__impl_in_the_traits_module/main.silt +++ /dev/null @@ -1,12 +0,0 @@ --- exit: 1 --- verdict: same --- An impl may be written in the module of its trait, for a record of --- another module: there too a method named like a field of the record --- is the error, and the label is at the field, in the record's module. -import runner -import shapes -fn main() { - let h = shapes.H { run: { x -> x + 1 }, n: 0 } - println(h.run(1)) - println(runner.go(3)) -} diff --git a/tests/golden/typecheck/traits/field_method__impl_in_the_traits_module/runner.silt b/tests/golden/typecheck/traits/field_method__impl_in_the_traits_module/runner.silt deleted file mode 100644 index d5203391a..000000000 --- a/tests/golden/typecheck/traits/field_method__impl_in_the_traits_module/runner.silt +++ /dev/null @@ -1,5 +0,0 @@ -import shapes -pub trait Run { fn run(self, x: Int) -> Int } -trait Run for shapes.H { fn run(self, x: Int) -> Int { x + self.n } } -trait Run for Int { fn run(self, x: Int) -> Int { self + x } } -pub fn go(x: Int) -> Int { Run.run(x, 1) } diff --git a/tests/golden/typecheck/traits/field_method__impl_in_the_traits_module/shapes.silt b/tests/golden/typecheck/traits/field_method__impl_in_the_traits_module/shapes.silt deleted file mode 100644 index 0f5c41bf9..000000000 --- a/tests/golden/typecheck/traits/field_method__impl_in_the_traits_module/shapes.silt +++ /dev/null @@ -1 +0,0 @@ -pub type H { run: Fn(Int) -> Int, n: Int } diff --git a/tests/golden/typecheck/traits/field_method__in_a_test_file.silt b/tests/golden/typecheck/traits/field_method__in_a_test_file.silt deleted file mode 100644 index c9e750e34..000000000 --- a/tests/golden/typecheck/traits/field_method__in_a_test_file.silt +++ /dev/null @@ -1,10 +0,0 @@ --- cmd: test --- exit: 1 --- `silt test` checks a test file as `silt check` does. -import test -type H { run: Int } -trait Run { fn run(self) -> Int } -trait Run for H { fn run(self) -> Int { 1 } } -fn test_field() { - test.assert_eq(H { run: 2 }.run, 2) -} diff --git a/tests/golden/typecheck/traits/field_method__in_a_test_file.stderr b/tests/golden/typecheck/traits/field_method__in_a_test_file.stderr deleted file mode 100644 index 47002f7f9..000000000 --- a/tests/golden/typecheck/traits/field_method__in_a_test_file.stderr +++ /dev/null @@ -1,13 +0,0 @@ -error[type]: method 'run' of the impl of 'Run' for 'H' is named like a field of 'H' - --> field_method__in_a_test_file.silt:7:22 - | - 7 | trait Run for H { fn run(self) -> Int { 1 } } - | ^^^ method 'run' of the impl of 'Run' for 'H' is named like a field of 'H' - ::: field_method__in_a_test_file.silt:5:10 - | - 5 | type H { run: Int } - | --- the field 'run' of 'H' - = help: a field and a method of one type cannot share a name: rename the method or the field -field_method__in_a_test_file.silt: failed to compile — type errors (see above) - -0 tests: 0 passed, 0 failed, 0 skipped (1 file failed to compile) diff --git a/tests/golden/typecheck/traits/field_method__method_without_receiver.silt b/tests/golden/typecheck/traits/field_method__method_without_receiver.silt deleted file mode 100644 index dbff722c1..000000000 --- a/tests/golden/typecheck/traits/field_method__method_without_receiver.silt +++ /dev/null @@ -1,10 +0,0 @@ --- exit: 1 --- verdict: same --- The rule is by the name alone: a method the trait declares without --- `self`, called on the type (`H.make()`), is a method of the impl too. -trait Make { fn make() -> Self } -type H { make: Int } -trait Make for H { fn make() -> H { H { make: 1 } } } -fn main() { - println(H.make().make) -} diff --git a/tests/golden/typecheck/traits/field_method__method_without_receiver.stderr b/tests/golden/typecheck/traits/field_method__method_without_receiver.stderr deleted file mode 100644 index 12bc5c88a..000000000 --- a/tests/golden/typecheck/traits/field_method__method_without_receiver.stderr +++ /dev/null @@ -1,10 +0,0 @@ -error[type]: method 'make' of the impl of 'Make' for 'H' is named like a field of 'H' - --> field_method__method_without_receiver.silt:7:23 - | - 7 | trait Make for H { fn make() -> H { H { make: 1 } } } - | ^^^^ method 'make' of the impl of 'Make' for 'H' is named like a field of 'H' - ::: field_method__method_without_receiver.silt:6:10 - | - 6 | type H { make: Int } - | ---- the field 'make' of 'H' - = help: a field and a method of one type cannot share a name: rename the method or the field diff --git a/tests/golden/typecheck/traits/field_method__supertrait_default_method.silt b/tests/golden/typecheck/traits/field_method__supertrait_default_method.silt deleted file mode 100644 index 986fe672b..000000000 --- a/tests/golden/typecheck/traits/field_method__supertrait_default_method.silt +++ /dev/null @@ -1,13 +0,0 @@ --- exit: 1 --- verdict: same --- A type has a supertrait's methods through its impl of the --- supertrait: that impl is where a default method of the supertrait --- named like a field is reported, not the impl of the trait below it. -trait Base { fn size(self) -> Int { 0 } } -trait Top: Base { fn top(self) -> Int } -type H { size: Int } -trait Base for H {} -trait Top for H { fn top(self) -> Int { self.size } } -fn main() { - println(Top.top(H { size: 2 })) -} diff --git a/tests/golden/typecheck/traits/field_method__supertrait_default_method.stderr b/tests/golden/typecheck/traits/field_method__supertrait_default_method.stderr deleted file mode 100644 index 53b7d1102..000000000 --- a/tests/golden/typecheck/traits/field_method__supertrait_default_method.stderr +++ /dev/null @@ -1,10 +0,0 @@ -error[type]: the impl of 'Base' for 'H' takes the default method 'size' of the trait, which is named like a field of 'H' - --> field_method__supertrait_default_method.silt:9:1 - | - 9 | trait Base for H {} - | ^^^^^^^^^^^^^^^^^^^ the impl of 'Base' for 'H' takes the default method 'size' of the trait, which is named like a field of 'H' - ::: field_method__supertrait_default_method.silt:8:10 - | - 8 | type H { size: Int } - | ---- the field 'size' of 'H' - = help: a field and a method of one type cannot share a name: rename the method or the field diff --git a/tests/golden/typecheck/traits/field_method__what_is_no_clash.silt b/tests/golden/typecheck/traits/field_method__what_is_no_clash.silt deleted file mode 100644 index 8635c5c17..000000000 --- a/tests/golden/typecheck/traits/field_method__what_is_no_clash.silt +++ /dev/null @@ -1,43 +0,0 @@ --- verdict: same --- What the rule does not reject: an impl for an enum (a variant's --- fields have no names), a field named like a method of a trait the --- type does not implement, a function named like a field, a method --- named like a field of another record, an anonymous record. A call --- `r.run(x)` on a record calls the function its field holds, also where --- the record's type is decided after the call is read (a closure's --- parameter). -import list -type Shape { Circle(Int), Square(Int) } -trait Area { fn area(self) -> Int } -trait Area for Shape { - fn area(self) -> Int { - match self { - Circle(r) -> r * r * 3 - Square(s) -> s * s - } - } -} -trait Run { fn run(self, x: Int) -> Int } -trait Run for Int { fn run(self, x: Int) -> Int { self + x } } -type H { run: Fn(Int) -> Int, area: Int } -type Other { n: Int } -trait Run for Other { fn run(self, x: Int) -> Int { self.n * x } } -fn run(x: Int) -> Int { x * 2 } -fn bounded(r: a, x: Int) -> Int where a: Run { Run.run(r, x) } -fn inferred(r, x) { Run.run(r, x) } -fn main() { - let h = H { run: { x -> x + 1 }, area: 9 } - println(h.run(1)) - println(1 |> h.run) - println(h.area) - println(Run.run(3, 4)) - println(Run.run(Other { n: 5 }, 2)) - println(bounded(Other { n: 5 }, 3)) - println(run(5)) - println(Area.area(Circle(2))) - let anon = {run: { x -> x - 1 }} - println(anon.run(0)) - let hs = [h, H { run: { x -> x * 10 }, area: 0 }] - println(list.map(hs, { r -> r.run(7) })) - println(inferred(Other { n: 5 }, 4)) -} diff --git a/tests/golden/typecheck/traits/field_method__what_is_no_clash.stdout b/tests/golden/typecheck/traits/field_method__what_is_no_clash.stdout deleted file mode 100644 index 9456299f8..000000000 --- a/tests/golden/typecheck/traits/field_method__what_is_no_clash.stdout +++ /dev/null @@ -1,11 +0,0 @@ -2 -2 -9 -7 -10 -15 -10 -12 --1 -[8, 70] -20 diff --git a/tests/golden/typecheck/traits/field_method__written_method.silt b/tests/golden/typecheck/traits/field_method__written_method.silt deleted file mode 100644 index c7f559574..000000000 --- a/tests/golden/typecheck/traits/field_method__written_method.silt +++ /dev/null @@ -1,20 +0,0 @@ --- exit: 1 --- verdict: same --- An impl that gives a record type a method named like one of its --- fields is an error at the method in the impl. (Before the rule, --- `r.run(x)` called the field's function where the type of `r` was --- known, and the method where it was not: through a bound, in a --- function with no annotations, in a closure.) -type H { run: Fn(Int) -> Int, n: Int } -trait Run { fn run(self, x: Int) -> Int } -trait Run for H { fn run(self, x: Int) -> Int { -1 } } -fn bounded(r: a, x: Int) -> Int where a: Run { Run.run(r, x) } -fn inferred(r, x) { Run.run(r, x) } -fn main() { - let h = H { run: { x -> x + 1 }, n: 0 } - println(h.run(1)) - println(bounded(h, 1)) - println(inferred(h, 1)) - let k = { r -> Run.run(r, 2) } - println(k(h)) -} diff --git a/tests/golden/typecheck/traits/field_method__written_method.stderr b/tests/golden/typecheck/traits/field_method__written_method.stderr deleted file mode 100644 index 61cf8bf80..000000000 --- a/tests/golden/typecheck/traits/field_method__written_method.stderr +++ /dev/null @@ -1,10 +0,0 @@ -error[type]: method 'run' of the impl of 'Run' for 'H' is named like a field of 'H' - --> field_method__written_method.silt:10:22 - | - 10 | trait Run for H { fn run(self, x: Int) -> Int { -1 } } - | ^^^ method 'run' of the impl of 'Run' for 'H' is named like a field of 'H' - ::: field_method__written_method.silt:8:10 - | - 8 | type H { run: Fn(Int) -> Int, n: Int } - | --- the field 'run' of 'H' - = help: a field and a method of one type cannot share a name: rename the method or the field diff --git a/tests/golden/typecheck/traits/no_self__a_method_without_self_is_no_candidate_on_a_value.silt b/tests/golden/typecheck/traits/no_self__a_method_without_self_is_no_candidate_on_a_value.silt index a131a3d4e..3eb7040ba 100644 --- a/tests/golden/typecheck/traits/no_self__a_method_without_self_is_no_candidate_on_a_value.silt +++ b/tests/golden/typecheck/traits/no_self__a_method_without_self_is_no_candidate_on_a_value.silt @@ -1,10 +1,9 @@ --- A method without `self` is never a candidate for a call on a value, --- of any trait: where another trait gives the type a method of the --- name that takes `self`, `x.decode(3)` calls that one, no ambiguity. --- So on a known type, on a value of a bounded type variable, on a --- value whose type is not known where the call is checked, in a --- closure whose receiver becomes known later, and beside a written --- `json.Decode` impl. +-- A method without `self` of one trait does not stand in the way of a +-- method of the name that takes `self` of another: `Rd.decode(x, 3)` +-- calls `Rd`'s, no ambiguity. So on a known type, on a value of a +-- bounded type variable, on a value whose type is not known where the +-- call is checked, in a closure whose argument becomes known later, +-- and beside a written `json.Decode` impl. import json trait Mk { diff --git a/tests/golden/typecheck/traits/structure__compare_method_on_structured_types.silt b/tests/golden/typecheck/traits/structure__compare_method_on_structured_types.silt index 267c6d500..113a675e9 100644 --- a/tests/golden/typecheck/traits/structure__compare_method_on_structured_types.silt +++ b/tests/golden/typecheck/traits/structure__compare_method_on_structured_types.silt @@ -1,4 +1,4 @@ --- `.compare()`, `.equal()` and `.hash()` are there for every type that +-- `Compare.compare`, `==` and `Hash.hash` are there for every type that -- has the trait by its structure. type Pt { x: Int, y: Int } fn main() { diff --git a/tests/golden/typecheck/traits/supertrait__at_the_impls_type.silt b/tests/golden/typecheck/traits/supertrait__at_the_impls_type.silt new file mode 100644 index 000000000..7adc0685e --- /dev/null +++ b/tests/golden/typecheck/traits/supertrait__at_the_impls_type.silt @@ -0,0 +1,42 @@ +-- cmd: check +-- exit: 1 +-- An impl for a parameterized type has each supertrait at its own type, +-- its type variables having what the header gives them: a structural +-- supertrait (Equal, Display, Compare, Hash) needs it of the variables +-- the type's parts are of, a supertrait with a written impl what that +-- impl's header asks. A bare target names its variables by letters. +-- (Each used to check and fail, or show and order wrongly, when run.) +trait Same: Equal { + fn same(self, o: Self) -> Bool { self == o } +} +trait Show2: Display { + fn s(self) -> String { "[{self}]" } +} +trait Ord2: Compare { + fn lt(self, o: Self) -> Bool { self < o } +} +trait Keyed: Hash { + fn key(self) -> Int { Hash.hash(self) } +} +trait Super { + fn sup(self) -> String +} +trait Sub: Super { + fn sub(self) -> String { "sub of {Super.sup(self)}" } +} +type Box(a) { Bx(a) } +type Pair(a, b) { Pr(a, b) } +type Plain(a) { Plain } +trait Same for Box(a) {} +trait Show2 for Box(a) {} +trait Ord2 for Box(a) {} +trait Keyed for Pair(a, b) where a: Hash {} +trait Keyed for Plain(a) {} +trait Super for Box(a) where a: Super { + fn sup(self) -> String { match self { Bx(x) -> "box({Super.sup(x)})" } } +} +trait Sub for Box(a) {} +trait Same for Pair {} +fn main() { + println("x") +} diff --git a/tests/golden/typecheck/traits/supertrait__at_the_impls_type.stderr b/tests/golden/typecheck/traits/supertrait__at_the_impls_type.stderr new file mode 100644 index 000000000..448011078 --- /dev/null +++ b/tests/golden/typecheck/traits/supertrait__at_the_impls_type.stderr @@ -0,0 +1,41 @@ +error[type]: type 'Box(a)' implements 'Same' but does not implement supertrait 'Equal': 'a' needs 'Equal' + --> supertrait__at_the_impls_type.silt:30:1 + | + 30 | trait Same for Box(a) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^ type 'Box(a)' implements 'Same' but does not implement supertrait 'Equal': 'a' needs 'Equal' + = help: add `where a: Equal` to the impl + +error[type]: type 'Box(a)' implements 'Show2' but does not implement supertrait 'Display': 'a' needs 'Display' + --> supertrait__at_the_impls_type.silt:31:1 + | + 31 | trait Show2 for Box(a) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^ type 'Box(a)' implements 'Show2' but does not implement supertrait 'Display': 'a' needs 'Display' + = help: add `where a: Display` to the impl + +error[type]: type 'Box(a)' implements 'Ord2' but does not implement supertrait 'Compare': 'a' needs 'Compare' + --> supertrait__at_the_impls_type.silt:32:1 + | + 32 | trait Ord2 for Box(a) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^ type 'Box(a)' implements 'Ord2' but does not implement supertrait 'Compare': 'a' needs 'Compare' + = help: add `where a: Compare` to the impl + +error[type]: type 'Pair(a, b)' implements 'Keyed' but does not implement supertrait 'Hash': 'b' needs 'Hash' + --> supertrait__at_the_impls_type.silt:33:1 + | + 33 | trait Keyed for Pair(a, b) where a: Hash {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type 'Pair(a, b)' implements 'Keyed' but does not implement supertrait 'Hash': 'b' needs 'Hash' + = help: add `where b: Hash` to the impl + +error[type]: type 'Box(a)' implements 'Sub' but does not implement supertrait 'Super': 'a' needs 'Super' + --> supertrait__at_the_impls_type.silt:38:1 + | + 38 | trait Sub for Box(a) {} + | ^^^^^^^^^^^^^^^^^^^^^^^ type 'Box(a)' implements 'Sub' but does not implement supertrait 'Super': 'a' needs 'Super' + = help: add `where a: Super` to the impl + +error[type]: type 'Pair(a, b)' implements 'Same' but does not implement supertrait 'Equal': 'a' needs 'Equal' + --> supertrait__at_the_impls_type.silt:39:1 + | + 39 | trait Same for Pair {} + | ^^^^^^^^^^^^^^^^^^^^^^ type 'Pair(a, b)' implements 'Same' but does not implement supertrait 'Equal': 'a' needs 'Equal' + = help: write the impl for `Pair(a, b)`, with `where a: Equal, b: Equal` diff --git a/tests/golden/typecheck/traits/supertrait__the_help_names_every_variable.silt b/tests/golden/typecheck/traits/supertrait__the_help_names_every_variable.silt new file mode 100644 index 000000000..8d4a0f20e --- /dev/null +++ b/tests/golden/typecheck/traits/supertrait__the_help_names_every_variable.silt @@ -0,0 +1,12 @@ +-- cmd: check +-- exit: 1 +-- The help for a supertrait an impl's type lacks names every variable +-- of the type that needs the bound. +-- stderr-contains: error[type]: type 'Pair(a, b)' implements 'Same' but does not implement supertrait 'Equal': 'a' needs 'Equal' +-- stderr-contains: help: write the impl for `Pair(a, b)`, with `where a: Equal, b: Equal` +trait Same: Equal { fn same(self, o: Self) -> Bool { self == o } } +type Pair(a, b) { Pr(a, b) } +trait Same for Pair {} +fn main() { + println(1) +} diff --git a/tests/golden/typecheck/traits/trait_impl_undeclared_target__lowercase_tyvar_target_ok.silt b/tests/golden/typecheck/traits/trait_impl_undeclared_target__lowercase_tyvar_target_ok.silt deleted file mode 100644 index af599ec0b..000000000 --- a/tests/golden/typecheck/traits/trait_impl_undeclared_target__lowercase_tyvar_target_ok.silt +++ /dev/null @@ -1,7 +0,0 @@ --- cmd: check --- exit: 0 --- stderr-not-contains: error -trait Display for a { - fn display(self) -> String { "?" } -} -fn main() { } diff --git a/tests/golden/typecheck/traits/type_param_trait_impl__variant_shadowed_enum_not_type_arg.silt b/tests/golden/typecheck/traits/type_param_trait_impl__variant_shadowed_enum_not_type_arg.silt deleted file mode 100644 index b59674aa0..000000000 --- a/tests/golden/typecheck/traits/type_param_trait_impl__variant_shadowed_enum_not_type_arg.silt +++ /dev/null @@ -1,13 +0,0 @@ --- cmd: check --- exit: 1 --- stderr-contains: expected type _, got --- An enum shadowed by its same-named variant cannot be passed as a type argument. -type Box(a) { Box(a) } - -fn tag(type a) -> String { "ok" } - -fn use_it() { - let _ = tag(Box) -} - -fn main() {} diff --git a/tests/golden/typecheck/traits/vm_trait_dispatch_runtime__compare_on_list.silt b/tests/golden/typecheck/traits/vm_trait_dispatch_runtime__compare_on_list.silt index 00faf273b..0c4f85dba 100644 --- a/tests/golden/typecheck/traits/vm_trait_dispatch_runtime__compare_on_list.silt +++ b/tests/golden/typecheck/traits/vm_trait_dispatch_runtime__compare_on_list.silt @@ -1,4 +1,4 @@ --- `.compare()` on List(Int) through a Compare bound orders element-wise. +-- `Compare.compare` on List(Int) through a Compare bound orders element-wise. fn cmp_gen(a: a, b: a) -> Int where a: Compare { Compare.compare(a, b) } fn main() { let x: List(Int) = [1, 2, 3] diff --git a/tests/golden/typecheck/traits/vm_trait_dispatch_runtime__compare_on_unit.silt b/tests/golden/typecheck/traits/vm_trait_dispatch_runtime__compare_on_unit.silt index ae44be97c..552bd797e 100644 --- a/tests/golden/typecheck/traits/vm_trait_dispatch_runtime__compare_on_unit.silt +++ b/tests/golden/typecheck/traits/vm_trait_dispatch_runtime__compare_on_unit.silt @@ -1,3 +1,3 @@ --- `.compare()` on Unit through a Compare bound is 0. +-- `Compare.compare` on Unit through a Compare bound is 0. fn cmp_gen(a: a, b: a) -> Int where a: Compare { Compare.compare(a, b) } fn main() { println(cmp_gen((), ())) } diff --git a/tests/golden/typecheck/traits/vm_trait_dispatch_runtime__hash_on_err_string.silt b/tests/golden/typecheck/traits/vm_trait_dispatch_runtime__hash_on_err_string.silt index 0c5b6a777..35a57ffd8 100644 --- a/tests/golden/typecheck/traits/vm_trait_dispatch_runtime__hash_on_err_string.silt +++ b/tests/golden/typecheck/traits/vm_trait_dispatch_runtime__hash_on_err_string.silt @@ -1,3 +1,3 @@ --- `.hash()` through a Hash bound runs on a Result variant. +-- `Hash.hash` through a Hash bound runs on a Result variant. fn h(v: a) -> Int where a: Hash { Hash.hash(v) } fn main() { println(h(Err("boom")) == h(Err("boom"))) } diff --git a/tests/golden/typecheck/traits/vm_trait_dispatch_runtime__hash_on_float_string_bool_list.silt b/tests/golden/typecheck/traits/vm_trait_dispatch_runtime__hash_on_float_string_bool_list.silt index 6e4da06db..bc5259ae7 100644 --- a/tests/golden/typecheck/traits/vm_trait_dispatch_runtime__hash_on_float_string_bool_list.silt +++ b/tests/golden/typecheck/traits/vm_trait_dispatch_runtime__hash_on_float_string_bool_list.silt @@ -1,4 +1,4 @@ --- `.hash()` through a Hash bound runs on Float, String, Bool and List. +-- `Hash.hash` through a Hash bound runs on Float, String, Bool and List. fn h(a: a) -> Int where a: Hash { Hash.hash(a) } fn main() { println(h(3.14) == h(3.14)) diff --git a/tests/golden/typecheck/traits/vm_trait_dispatch_runtime__hash_on_int.silt b/tests/golden/typecheck/traits/vm_trait_dispatch_runtime__hash_on_int.silt index 028408d1f..2776095fc 100644 --- a/tests/golden/typecheck/traits/vm_trait_dispatch_runtime__hash_on_int.silt +++ b/tests/golden/typecheck/traits/vm_trait_dispatch_runtime__hash_on_int.silt @@ -1,4 +1,4 @@ --- `.hash()` on Int through a Hash bound runs (the value itself is not pinned). +-- `Hash.hash` on Int through a Hash bound runs (the value itself is not pinned). fn h(a: a) -> Int where a: Hash { Hash.hash(a) } fn main() { let v = h(42) diff --git a/tests/golden/typecheck/traits/vm_trait_dispatch_runtime__hash_on_range.silt b/tests/golden/typecheck/traits/vm_trait_dispatch_runtime__hash_on_range.silt index 99e34016a..099a7c534 100644 --- a/tests/golden/typecheck/traits/vm_trait_dispatch_runtime__hash_on_range.silt +++ b/tests/golden/typecheck/traits/vm_trait_dispatch_runtime__hash_on_range.silt @@ -1,4 +1,4 @@ --- `.hash()` through a Hash bound runs on a range. +-- `Hash.hash` through a Hash bound runs on a range. fn h(v: a) -> Int where a: Hash { Hash.hash(v) } fn main() { let r = 1..5 diff --git a/tests/golden/typecheck/traits/vm_trait_dispatch_runtime__hash_on_some_int.silt b/tests/golden/typecheck/traits/vm_trait_dispatch_runtime__hash_on_some_int.silt index 32231beca..0fb5235df 100644 --- a/tests/golden/typecheck/traits/vm_trait_dispatch_runtime__hash_on_some_int.silt +++ b/tests/golden/typecheck/traits/vm_trait_dispatch_runtime__hash_on_some_int.silt @@ -1,3 +1,3 @@ --- `.hash()` through a Hash bound runs on an Option variant. +-- `Hash.hash` through a Hash bound runs on an Option variant. fn h(v: a) -> Int where a: Hash { Hash.hash(v) } fn main() { println(h(Some(1)) == h(Some(1))) } diff --git a/tests/lsp/lsp_semantic_tokens_tests.rs b/tests/lsp/lsp_semantic_tokens_tests.rs index 2d67d0693..6a994a5b1 100644 --- a/tests/lsp/lsp_semantic_tokens_tests.rs +++ b/tests/lsp/lsp_semantic_tokens_tests.rs @@ -132,3 +132,47 @@ fn semantic_tokens_of_a_call_through_a_trait() { ); client.shutdown(); } + +/// A call through a trait of a module (`json.Encode.encode(5)`): the +/// trait's name is an INTERFACE token there too. +#[test] +fn semantic_tokens_of_a_call_through_a_trait_of_a_module() { + let mut client = LspClient::spawn(); + let uri = "file:///tmp/silt_sem_tokens_qualified_trait_call.silt"; + let src = "import json\nfn main() { println(json.Encode.encode(5)) }\n"; + client.did_open_and_wait(uri, src); + let resp = client.request( + "textDocument/semanticTokens/full", + json!({ "textDocument": { "uri": uri } }), + ); + let data = resp + .pointer("/result/data") + .and_then(|v| v.as_array()) + .unwrap_or_else(|| panic!("expected /result/data array; got: {resp}")); + let (mut line, mut start) = (0u64, 0u64); + let mut tokens: Vec<(u64, u64, u64, u64)> = Vec::new(); + for chunk in data.as_chunks::<5>().0 { + let dl = chunk[0].as_u64().expect("deltaLine"); + let ds = chunk[1].as_u64().expect("deltaStart"); + match dl { + 0 => start += ds, + _ => { + line += dl; + start = ds; + } + } + let len = chunk[2].as_u64().expect("length"); + tokens.push((line, start, len, chunk[3].as_u64().expect("tokenType"))); + } + // `fn main() { println(json.Encode.encode(5)) }`: `Encode` at 25, + // `encode` at 32. + assert!( + tokens.contains(&(1, 25, 6, TT_INTERFACE)), + "the trait's name: {tokens:?}" + ); + assert!( + tokens.contains(&(1, 32, 6, TT_FUNCTION)), + "the method's name: {tokens:?}" + ); + client.shutdown(); +}