From 8e8d5705716c734879602696afa4c40226edb6d6 Mon Sep 17 00:00:00 2001 From: Geoff Romer Date: Tue, 10 Dec 2024 17:58:40 -0800 Subject: [PATCH 1/7] Proposal: Variadics (#2240) Proposes a set of core features for declaring and implementing generic variadic functions. A "pack expansion" is a syntactic unit beginning with `...`, which is a kind of compile-time loop over sequences called "packs". Packs are initialized and referred to using "pack bindings", which are marked with the `each` keyword at the point of declaration and the point of use. The syntax and behavior of a pack expansion depends on its context, and in some cases by a keyword following the `...`: - In a tuple literal expression (such as a function call argument list), `...` iteratively evaluates its operand expression, and treats the values as successive elements of the tuple. - `...and` and `...or` iteratively evaluate a boolean expression, combining the values using `and` and `or`, and ending the loop early if the underlying operator short-circuits. - In a statement context, `...` iteratively executes a statement. - In a tuple literal pattern (such as a function parameter list), `...` iteratively matches the elements of the scrutinee tuple. In conjunction with pack bindings, this enables functions to take an arbitrary number of arguments. --------- Co-authored-by: josh11b Co-authored-by: Richard Smith Co-authored-by: josh11b <15258583+josh11b@users.noreply.github.com> Co-authored-by: Chandler Carruth Co-authored-by: Carbon Infra Bot --- docs/design/variadics.md | 950 +++++++++++++++ docs/project/principles/library_apis_only.md | 16 +- proposals/p2240.md | 1136 ++++++++++++++++++ 3 files changed, 2092 insertions(+), 10 deletions(-) create mode 100644 docs/design/variadics.md create mode 100644 proposals/p2240.md diff --git a/docs/design/variadics.md b/docs/design/variadics.md new file mode 100644 index 0000000000000..0f9454ec92005 --- /dev/null +++ b/docs/design/variadics.md @@ -0,0 +1,950 @@ +# Variadics + + + + + +## Table of contents + +- [Basics](#basics) + - [Overview](#overview) + - [Packs and each-names](#packs-and-each-names) + - [Pack expansions](#pack-expansions) + - [Pack expansion expressions and statements](#pack-expansion-expressions-and-statements) + - [Pack expansion patterns](#pack-expansion-patterns) + - [Additional examples](#additional-examples) +- [Execution Semantics](#execution-semantics) + - [Expressions and statements](#expressions-and-statements) + - [Pattern matching](#pattern-matching) +- [Typechecking](#typechecking) + - [Tuples, packs, segments, and shapes](#tuples-packs-segments-and-shapes) + - [Iterative typechecking of pack expansions](#iterative-typechecking-of-pack-expansions) + - [Typechecking patterns](#typechecking-patterns) + - [Typechecking pattern matches](#typechecking-pattern-matches) +- [Appendix: Type system formalism](#appendix-type-system-formalism) + - [Explicit deduced arities](#explicit-deduced-arities) + - [Typing and shaping rules](#typing-and-shaping-rules) + - [Reduction rules](#reduction-rules) + - [Equivalence, equality, and convertibility](#equivalence-equality-and-convertibility) + - [Pattern match typechecking algorithm](#pattern-match-typechecking-algorithm) + - [Canonicalization algorithm](#canonicalization-algorithm) +- [Alternatives considered](#alternatives-considered) +- [References](#references) + + + +## Basics + +### Overview + +A "pack expansion" is a syntactic unit beginning with `...`, which is a kind of +compile-time loop over sequences called "packs". Packs are initialized and +referred to using "each-names", which are marked with the `each` keyword at the +point of declaration and the point of use. + +The syntax and behavior of a pack expansion depends on its context, and in some +cases on a keyword following the `...`: + +- In a tuple literal expression (such as a function call argument list), `...` + iteratively evaluates its operand expression, and treats the values as + successive elements of the tuple. +- `...and` and `...or` iteratively evaluate a boolean expression, combining + the values using `and` and `or`. Normal short-circuiting behavior for the + resulting `and` and `or` operators applies at runtime. +- In a statement context, `...` iteratively executes a statement. +- In a tuple literal pattern (such as a function parameter list), `...` + iteratively matches the elements of the scrutinee tuple. In conjunction with + pack bindings, this enables functions to take an arbitrary number of + arguments. + +This example illustrates many of the key concepts: + +```carbon +// Takes an arbitrary number of vectors with arbitrary element types, and +// returns a vector of tuples where the i'th element of the vector is +// a tuple of the i'th elements of the input vectors. +fn Zip[... each ElementType:! type] + (... each vector: Vector(each ElementType)) + -> Vector((... each ElementType)) { + ... var each iter: auto = each vector.Begin(); + var result: Vector((... each ElementType)); + while (...and each iter != each vector.End()) { + result.push_back((... each iter)); + ... each iter++; + } + return result; +} +``` + +### Packs and each-names + +A _pack_ is a sequence of a fixed number of values called "elements", which may +be of different types. Packs are very similar to tuple values in many ways, but +they are not first-class values -- in particular, no run-time expression +evaluates to a pack. The _arity_ of a pack is a compile-time value representing +the number of values in the sequence. + +An _each-name_ consists of the keyword `each` followed by the name of a pack, +and can only occur inside a pack expansion. On the Nth iteration of the pack +expansion, an each-name refers to the Nth element of the named pack. As a +result, a binding pattern with an each-name, such as `each ElementType:! type`, +acts as a declaration of all the elements of the named pack, and thereby +implicitly acts as a declaration of the pack itself. + +Note that `each` is part of the name syntax, not an expression operator, so it +binds more tightly than any expression syntax. For example, the loop condition +`...and each iter != each vector.End()` in the implementation of `Zip` is +equivalent to `...and (each iter) != (each vector).End()`. + +### Pack expansions + +A _pack expansion_ is an instance of one of the following syntactic forms: + +- A statement of the form "`...` _statement_". +- A tuple expression element of the form "`...` _expression_", with the same + precedence as `,`. +- A tuple pattern element of the form "`...` _pattern_", with the same + precedence as `,`. +- An implicit parameter list element of the form "`...` _pattern_", with the + same precedence as `,`. +- An expression of the form "`...` `and` _expression_" or "`...` `or` + _expression_", with the same precedence as `and` and `or`. + +The statement, expression, or pattern following the `...` (and the `and`/`or`, +if present) is called the _body_ of the expansion. + +The `...` token can also occur in a tuple expression element of the form "`...` +`expand` _expression_", with the same precedence as `,`. However, that syntax is +not considered a pack expansion, and has its own semantics: _expression_ must +have a tuple type, and "`...` `expand` _expression_" evaluates _expression_ and +treats its elements as elements of the enclosing tuple literal. This is +especially useful for using non-literal tuple values as function call arguments: + +```carbon +fn F(x: i32, y: String); +fn MakeArgs() -> (i32, String); + +F(...expand MakeArgs()); +``` + +`...and`, `...or`, and `...expand` can be trivially distinguished with one token +of lookahead, and the other meanings of `...` can be distinguished from each +other by the context they appear in. As a corollary, if the nearest enclosing +delimiters around a `...` are parentheses, they will be interpreted as forming a +tuple rather than as grouping. Thus, expressions like `(... each ElementType)` +in the above example are tuple literals, even though they don't contain commas. + +By convention, `...` is always followed by whitespace, except that `...and`, +`...or`, and `...expand` are written with no whitespace between the two tokens. +This serves to emphasize that the keyword is not part of the expansion body, but +rather a modifier on the syntax and semantics of `...`. + +All each-names in a given expansion must refer to packs with the same arity, +which we will also refer to as the arity of the expansion. If an expansion +contains no each-names, it must be a pattern, or an expression in the type +position of a binding pattern, and its arity is deduced from the scrutinee. + +A pack expansion or `...expand` expression cannot contain another pack expansion +or `...expand` expression. + +An each-name cannot be used in the same pack expansion that declares it. In most +if not all cases, an each-name that violates this rule can be changed to an +ordinary name, because each-names are only necessary when you need to transfer a +pack from one pack expansion to another. + +#### Pack expansion expressions and statements + +A pack expansion expression or statement can be thought of as a kind of loop +that executes at compile time (specifically, monomorphization time), where the +expansion body is implicitly parameterized by an integer value called the _pack +index_, which ranges from 0 to one less than the arity of the expansion. The +pack index is implicitly used as an index into the packs referred to by +each-names. This is easiest to see with statement pack expansions. For example, +if `a`, `x`, and `y` are packs with arity 3, then +`... each a += each x * each y;` is roughly equivalent to + +```carbon +a[:0:] += x[:0:] * y[:0:]; +a[:1:] += x[:1:] * y[:1:]; +a[:2:] += x[:2:] * y[:2:]; +``` + +Here we are using `[:N:]` as a hypothetical pack indexing operator for purposes +of illustration; packs cannot actually be indexed in Carbon code. + +> **Future work:** We're open to eventually adding indexing of variadics, but +> that remains future work and will need its own proposal. + +`...and` and `...or` behave like chains of the corresponding boolean operator, +so `...and F(each x, each y)` behaves like +`true and F(x[:0:], y[:0:]) and F(x[:1:], y[:1:]) and F(x[:2:], y[:2:])`. They +can also be interpreted as looping constructs, although the rewrite is less +straightforward because Carbon doesn't have a way to write a loop in an +expression context. An expression like `...and F(each x, each y)` can be thought +of as evaluating to the value of `result` after executing the following code +fragment: + +``` +var result: bool = true; +for (let i:! i32 in (0, 1, 2)) { + result = result && F(x[:i:], y[:i:]); + if (result == false) { break; } +} +``` + +`...` in a tuple literal behaves like a series of comma-separated tuple +elements, so `(... F(each x, each y))` is equivalent to +`(F(x[:0:], y[:0:]), F(x[:1:], y[:1:]), F(x[:2:], y[:2:]))`. This can't be +expressed as a loop in Carbon code, but it is still fundamentally iterative. + +#### Pack expansion patterns + +A pack expansion pattern "`...` _subpattern_" appears as part of a tuple pattern +(or an implicit parameter list), and matches a sequence of tuple elements if +each element matches _subpattern_. For example, in the signature of `Zip` shown +earlier, the parameter list consists of a single pack expansion pattern +`... each vector: Vector(each ElementType)`, and so the entire argument list +will be matched against the binding pattern +`each vector: Vector(each ElementType)`. + +Since _subpattern_ will be matched against multiple scrutinees (or none) in a +single pattern-matching operation, a binding pattern within a pack expansion +pattern must declare an each-name (such as `each vector` in the `Zip` example), +and the Nth iteration of the pack expansion will initialize the Nth element of +the named pack from the Nth scrutinee. The binding pattern's type expression may +contain an each-name (such as `each ElementType` in the `Zip` example), but if +so, it must be a deduced parameter of the enclosing pattern. + +> **Future work:** That restriction can probably be relaxed, but we currently +> don't have motivating use cases to constrain the design. + +### Additional examples + +```carbon +// Computes the sum of its arguments, which are i64s +fn SumInts(... each param: i64) -> i64 { + var sum: i64 = 0; + ... sum += each param; + return sum; +} +``` + +```carbon +// Concatenates its arguments, which are all convertible to String +fn StrCat[... each T:! ConvertibleToString](... each param: each T) -> String { + var len: i64 = 0; + ... len += each param.Length(); + var result: String = ""; + result.Reserve(len); + ... result.Append(each param.ToString()); + return result; +} +``` + +```carbon +// Returns the minimum of its arguments, which must all have the same type T. +fn Min[T:! Comparable & Value](first: T, ... each next: T) -> T { + var result: T = first; + ... if (each next < result) { + result = each next; + } + return result; +} +``` + +```carbon +// Invokes f, with the tuple `args` as its arguments. +fn Apply[... each T:! type, F:! CallableWith(... each T)] + (f: F, args: (... each T)) -> auto { + return f(...expand args); +} +``` + +```carbon +// Toy example of mixing variadic and non-variadic parameters. +// Takes an i64, any number of f64s, and then another i64. +fn MiddleVariadic(first: i64, ... each middle: f64, last: i64); +``` + +```carbon +// Toy example of using the result of variadic type deduction. +fn TupleConcat[... each T1: type, ... each T2: type]( + t1: (... each T1), t2: (... each T2)) -> (... each T1, ... each T2) { + return (...expand t1, ...expand t2); +} +``` + +## Execution Semantics + +### Expressions and statements + +In all of the following, N is the arity of the pack expansion being discussed, +and `$I` is a notional variable representing the pack index. These semantics are +implemented at monomorphization time, so the value of N is a known integer +constant. Although the value of `$I` can vary during execution, it is +nevertheless treated as a constant. + +A statement of the form "`...` _statement_" is evaluated by executing +_statement_ N times, with `$I` ranging from 0 to N - 1. + +An expression of the form "`...and` _expression_" is evaluated as follows: a +notional `bool` variable `$R` is initialized to `true`, and then "`$R = $R and` +_expression_" is executed up to N times, with `$I` ranging from 0 to N - 1. If +at any point `$R` becomes false, this iteration is terminated early. The final +value of `$R` is the value of the expression. + +An expression of the form "`...or` _expression_" is evaluated the same way, but +with `or` in place of `and`, and `true` and `false` transposed. + +A tuple expression element of the form "`...` _expression_" evaluates to a +sequence of N values, where the k'th value is the value of _operand_ where `$I` +is equal to k - 1. + +An each-name evaluates to the `$I`th value of the pack it refers to (indexed +from zero). + +### Pattern matching + +The semantics of pack expansion patterns are chosen to follow the general +principle that pattern matching is the inverse of expression evaluation, so for +example if the pattern `(... each x: auto)` matches some scrutinee value `s`, +the expression `(... each x)` should be equal to `s`. These semantics are +implemented at monomorphization time, so all types are known constants, and all +all arities are known. + +A tuple pattern can contain no more than one subpattern of the form "`...` +_operand_". When such a subpattern is present, the N elements of the pattern +before the `...` expansion are matched with the first N elements of the +scrutinee, and the M elements of the pattern after the `...` expansion are +matched with the last M elements of the scrutinee. If the scrutinee does not +have at least N + M elements, the pattern does not match. + +The remaining elements of the scrutinee are iteratively matched against +_operand_, in order. In each iteration, `$I` is equal to the index of the +scrutinee element being matched, minus N. + +On the Nth iteration, a binding pattern binds the Nth element of the named pack +to the Nth scrutinee value. + +## Typechecking + +### Tuples, packs, segments, and shapes + +In order to discuss the underlying type system for variadics, we will need to +introduce some pseudo-syntax to represent values and expressions that occur in +the type system, but cannot be expressed directly in user code. We will use +non-ASCII glyphs such as `«»‖⟬⟭` for that pseudo-syntax, to distinguish it from +valid Carbon syntax. + +In the context of variadics, we will say that a tuple literal consists of a +comma-separated sequence of _segments_, and reserve the term "elements" for the +components of a tuple literal after pack expansion. For example, the expression +`(... each foo)` may evaluate to a tuple value with any number of elements, but +the expression itself has exactly one segment. + +Each segment has a type, which expresses (potentially symbolically) both the +types of the elements of the segment and the arity of the segment. The type of a +tuple literal is a tuple literal of the types of its segments. For example, +suppose we are trying to find the type of `z` in this code: + +```carbon +fn F[... each T:! type]((... each x: Optional(each T)), (... each y: i32)) { + let z: auto = (0 as f32, ... each x, ... each y); +} +``` + +We proceed by finding the type of each segment. The type of `0 as f32` is `f32`, +by the usual non-variadic typing rules. The type of `... each x` is +`... Optional(each T)`, because `Optional(each T)` is the declared type of +`each x`, and the type of a pack expansion is a pack expansion of the type of +its body. + +The type of `... each y` is more complicated. Conceptually, it consists of some +number of repetitions of `i32`. We don't know exactly how many repetitions, +because it's implicitly specified by the caller: it's the arity of the second +argument tuple. Effectively, that arity acts as a hidden deduced parameter of +`F`. + +So to represent this type, we need two new pseudo-syntaxes: + +- `‖each X‖` refers to the deduced arity of the pack expansion that contains + the declaration of `each X`. +- `«E; N»` evaluates to `N` repetitions of `E`. This is called a _arity + coercion_, because it coerces the expression `E` to have arity `N`. `E` must + not contain any pack expansions, each-names, or pack literals (see below). + +Combining the two, the type of `... each y` is `... «i32; ‖each y‖»`. Thus, the +type of `z` is `(f32, ... Optional(each T), ... «i32; ‖each y‖»)`. + +Now, consider a modified version of that example: + +```carbon +fn F[... each T:! type]((... each x: Optional(each T)), (... each y: i32)) { + let (... each z: auto) = (0 as f32, ... each x, ... each y); +} +``` + +`each z` is a pack, but it has the same elements as the tuple `z` in our earlier +example, so we represent its type in the same way, as a sequence of segments: +`⟬f32, Optional(each T), «i32; ‖each y‖»⟭`. The `⟬⟭` delimiters make this a +_pack literal_ rather than a tuple literal. Notice one subtle difference: the +segments of a pack literal do not contain `...`. In effect, every segment of a +pack literal acts as a separate loop body. As with the tuple literal syntax, the +pack literal pseudo-syntax can also be used in patterns. + +The _shape_ of a pack literal is a tuple of the arities of its segments, so the +shape of `⟬f32, Optional(each T), «i32; ‖each y‖»⟭` is +`(1, ‖each T‖, ‖each y‖)`. Other expressions and patterns also have shapes. In +particular, the shape of an arity coercion `«E; A»` is `(A,)`, the shape of +`each X` is `(‖each X‖,)`, and the shape of an expression that does not contain +pack literals, shape coercions, or each-names is `(1,)`. The arity of an +expression is the sum of the elements of its shape. See the +[appendix](#typing-and-shaping-rules) for the full rules for determining the +shape of an expression. + +If a pack literal is part of some enclosing expression that doesn't contain +`...`, it can be _expanded_, which moves the outer expression inside the pack +literal. For example, `... Optional(⟬each X, Y⟭)` is equivalent to +`... ⟬Optional(each X), Optional(Y)⟭`. Similarly, an arity coercion can be +expanded so long as the parent node is not `...`, a pattern, or a pack literal. +See the [appendix](#reduction-rules) for the full rules governing this +operation. _Fully expanding_ an expression or pattern that does not contain a +pack expansion means repeatedly expanding any pack literals and arity coercions +within it, until they cannot be expanded any further. + +The _scalar components_ of a fully-expanded expression `E` are a set, defined as +follows: + +- If `E` is a pack literal, its scalar components are the union of the scalar + components of the segments. +- If `E` is an arity coercion `«F; S»`, the only scalar component of `E` is + `F`. +- Otherwise, the only scalar component of `E` is `E`. + +The scalar components of any other expression that does not contain `...` are +the scalar components of its fully expanded form. + +By construction, a segment of a pack literal never has more than one scalar +component. Also by construction, a scalar component cannot contain a pack +literal, pack expansion, or arity coercion, but it can contain each-names, so we +can operate on it using the ordinary rules of non-variadic expressions so long +as we treat the names as opaque. + +### Iterative typechecking of pack expansions + +Since the execution semantics of an expansion are defined in terms of a notional +rewritten form where we simultaneously iterate over each-names, in principle we +can typecheck the expansion by typechecking the rewritten form. However, the +rewritten form usually would not typecheck as ordinary Carbon code, because the +each-names can have different types on different iterations. Furthermore, the +difference in types can propagate through expressions: if `each x` and `each y` +can have different types on different iterations, then so can `each x * each y`. +In effect, we have to typecheck the loop body separately for each iteration. + +However, at typechecking time we usually don't even know how many iterations +there will be, much less what type an each-name will have on any particular +iteration, because the types of the each-names are packs, which are sequences of +segments, not sequences of elements. To solve that problem, we require that the +types of all each-names in a pack expansion must have the same shape. This +enables us to typecheck the pack expansion by simultaneously iterating over +segments instead of input elements. + +As a result, the type of an expression or pattern within a pack expansion is a +sequence of segments, or in other words a _pack_, representing the types it +takes on over the course of the iteration. Note, however, that even though such +an expression has a pack type, it does not evaluate to a pack value. Rather, it +evaluates to a sequence of non-pack values over the course of the pack expansion +loop, and its pack type summarizes the types of that sequence. + +Within a given iteration, typechecking follows the usual rules of non-variadic +typechecking, except that when we need the type of an each-name, we use the +scalar component of the current segment of its type. As noted above, we can +operate on a scalar component using the ordinary rules of non-variadic +typechecking. + +Once the body of a pack expansion has been typechecked, typechecking the +expansion itself is relatively straightforward: + +- A statement pack expansion requires no further typechecking, because + statements don't have types. +- An `...and` or `...or` expression has type `bool`, and every segment of the + operand's type pack must have a type that's convertible to `bool`. +- For a `...` tuple element expression or pattern, the segments of the + operand's type pack become segments of the type of the enclosing tuple. + +> **TODO:** Discuss typechecking `...expand`. + +### Typechecking patterns + +A _full pattern_ consists of an optional deduced parameter list, a pattern, and +an optional return type expression. + +A pack expansion pattern has _fixed arity_ if it contains at least one usage of +an each-name that is not a parameter of the enclosing full pattern. Otherwise it +has _deduced arity_. A tuple pattern can have at most one segment with deduced +arity. For example: + +```carbon +class C(... each T:! type) { + fn F[... each U:! type](... each t: each T, ... each u: each U); +} +``` + +In the signature of `F`, `... each t: each T` has fixed arity, since the arity +is determined by the arguments passed to `C`, before the call to `F`. On the +other hand, `... each u: each U` has deduced arity, because the arity of +`each U` is determined by the arguments passed to `F`. + +After typechecking a full pattern, we attempt to merge as many tuple segments as +possible, in order to simplify the subsequent pattern matching. For example, +consider the following function declaration: + +```carbon +fn Min[T:! type](first: T, ... each next: T) -> T; +``` + +During typechecking, we rewrite that function signature so that it only has one +parameter: + +```carbon +fn Min[T:! type](... each args: «T; ‖each next‖+1») -> T; +``` + +(We represent the arity as `‖each next‖+1` to capture the fact that `each args` +must match at least one element.) + +When the pattern is heterogeneous, the merging process may be more complex. For +example: + +```carbon +fn ZipAtLeastOne[First:! type, ... each Next:! type] + (first: Vector(First), ... each next: Vector(each Next)) + -> Vector((First, ... each Next)); +``` + +During typechecking, we transform that function signature to the following form: + +```carbon +fn ZipAtLeastOne[... ⟬First, each Next⟭:! «type; ‖each next‖+1»] + (... each __args: Vector(⟬First, each Next⟭)) + -> Vector((... ⟬First, each Next⟭)); +``` + +We can then rewrite that by replacing the pack of names `⟬First, each Next⟭` +with an invented name `each __Args`, so that the function has only one +parameter: + +```carbon +fn ZipAtLeastOne[... each __Args:! «type; ‖each next‖+1»] + (... each __args: Vector(each __Args)) + -> Vector((... each __Args)); +``` + +We can replace a name pack with an invented each-name only if all of the +following conditions hold: + +- The name pack doesn't use any name more than once. For example, we can't + apply this rewrite to `⟬X, each Y, X⟭`. +- The name pack contains exactly one each-name. For example, we can't apply + this rewrite to `⟬X, Y⟭`. +- The replacement removes all usages of the constituent names, including their + declarations. For example, we can't apply this rewrite to `⟬X, each Y⟭` in + this code, because the resulting signature would have return type `X` but no + declaration of `X`: + ```carbon + fn F[... ⟬X, each Y⟭:! «type; ‖each next‖+1»] + (... each __args: each ⟬X, each Y⟭) -> X; + ``` +- The pack expansions being rewritten do not contain any pack literals other + than the name pack being replaced. For example, we can't apply this rewrite + to `⟬X, each Y⟭` in this code, because the pack expansion in the deduced + parameter list also contains the pack literal `⟬I, each type⟭`: + ```carbon + fn F[... ⟬X, each Y⟭:! ⟬I, each type⟭](... each __args: each ⟬X, each Y⟭); + ``` + Notice that as a corollary of this rule, all the names in the name pack must + have the same type. + +See the [appendix](#pattern-match-typechecking-algorithm) for a more formal +discussion of the rewriting process. + +### Typechecking pattern matches + +To typecheck a pattern match between a tuple pattern and a tuple scrutinee, we +try to split and merge the segments of the scrutinee type so that it has the +same number of segments as the pattern type, and corresponding segments have the +same arity. For example, consider this call to `ZipAtLeastOne` (as defined in +the previous section): + +```carbon +fn F[... each T:! type](... each t: Vector(each T), u: Vector(i32)) { + ZipAtLeastOne(... each t, u); +} +``` + +The pattern type is `(... Vector(⟬First, each Next⟭))`, so we need to rewrite +the scrutinee type `(... Vector(each T), Vector(i32))` to have a single tuple +segment with an arity that matches `‖each Next‖+1`. We can do that by merging +the scrutinee segments to obtain `(... ⟬Vector(each T), Vector(i32)⟭)`. This has +a single segment with arity `‖each T‖+1`, which can match `‖each Next‖+1` +because the deduced arity `‖each Next‖` behaves as a deduced parameter of the +pattern, so they match by deducing `‖each Next‖ == ‖each T‖`. + +When merging segments of the scrutinee, we don't attempt to form name packs and +replace them with invented names, but we also don't need to: we don't require a +merged scrutinee segments to have a single scalar component. + +The search for this rewrite processes each pattern segment to the left of the +segment with deduced arity, in order from left to right. For each pattern +segment, it greedily merges unmatched scrutinee segments from left to right +until their cumulative shape is greater than or equal to the shape of the +pattern segment, and then splits off a scrutinee segment on the right if +necessary to make the shapes exactly match. Pattern segments to the right of the +segment with deduced arity are processed the same way, but with left and right +reversed, so that segments are always processed from the outside in. + +See the [appendix](#appendix-type-system-formalism) for the rewrite rules that +govern merging and splitting. + +Once we have the pattern and scrutinee segments in one-to-one correspondence, we +check each scalar component of the scrutinee type against the scalar component +of the corresponding pattern type segment (by construction, the pattern type +segment has only one scalar component). Since we are checking scalar components +against scalar components, this proceeds according to the usual rules of +non-variadic typechecking. + +> **TODO:** Extend this approach to fall back to a complementary approach, where +> the pattern and scrutinee trade roles: we maximally merge the scrutinee tuple, +> while requiring each segment to have a single scalar component, and then +> merge/split the pattern tuple to match it, without requiring pattern tuple +> segments to have a single scalar component. This isn't quite symmetric with +> the current approach, because when processing the scrutinee we can't merge +> deduced parameters (scrutinees don't have any), but we can invent new `let` +> bindings. + +## Appendix: Type system formalism + +A _pack literal_ is a comma-separated sequence of segments, enclosed in `⟬⟭` +delimiters. A pack literal can appear in an expression, pattern, or name +context, and every segment must be valid in the context where the pack literal +appears (for example, the segments of a pack literal in a name context must all +be names). Pack literals cannot be nested, and cannot appear outside a pack +expansion. + +### Explicit deduced arities + +In this formalism, deduced arities are explicit rather than implicit, so Carbon +code must be desugared into this formalism as follows: + +For each pack expansion pattern, we introduce a binding pattern `__N:! Arity` as +a deduced parameter of the enclosing full pattern, where `__N` is a name chosen +to avoid collisions. Then, for each binding pattern of the form `each X: T` +within that expansion, if `T` does not contain an each-name, the binding pattern +is rewritten as `each X: «T; __N»`. If this does not introduce any usages of +`__N`, we remove its declaration. + +`Arity` is a compiler-internal type which represents non-negative integers. The +only operation it supports is `+`, with non-negative integer literals and other +`Arity`s. `Arity` is used only during type checking, so `+` has no run-time +semantics, and its only symbolic semantics are that it is commutative and +associative. + +### Typing and shaping rules + +The shape of an AST node within a pack expansion is determined as follows: + +- The shape of an arity coercion is the value of the expression after the `;`. +- The shape of a pack literal is the concatenation of the arities of its + segments. +- The shape of an each-name expression is the shape of the binding pattern + that declared the name. +- If a binding pattern's name and type components have the same number of + segments, and each name segment is an each-name if and only if the + corresponding type segment's shape is not 1, then the shape of the binding + pattern is the shape of the type expression. Otherwise, the binding pattern + is ill-shaped. +- For any other AST node: + - If all the node's children have shape 1, its shape is 1. + - If there is some shape `S` such that all of the node's children have + shape either 1 or `S`, its shape is `S`. + - Otherwise, the node is ill-shaped. + +> **TODO:** The "well-shaped" rules as stated are slightly too restrictive. For +> example, `⟬each X, Y⟭: «Z; N+1»` is well-shaped, and `(⟬each X, Y⟭, «Z; N+1»)` +> is well-shaped if the shape of `each X` is `N`. + +The type of an expression or pattern can be computed as follows: + +- The type of `each x: auto` is `each __X`, a newly-invented deduced parameter + of the enclosing full pattern, which behaves as if it was declared as + `... each __X:! type`. +- The type of an each-name expression is the type expression of the binding + pattern that declared it. +- The type of an arity coercion `«E; S»` is `«T; S»`, where `T` is the type of + `E`. +- The type of a pack literal is a pack literal consisting of the concatenated + types of its segments. This concatenation flattens any nested pack literals + (for example `⟬A, ⟬B, C⟭⟭` becomes `⟬A, B, C⟭`) +- The type of a pack expansion expression or pattern is `...B`, where `B` is + the type of its body. +- The type of a tuple literal is a tuple literal consisting of the types of + its segments. +- If an expression or pattern `E` contains a pack literal or arity coercion + that is not inside a pack expansion, the type of `E` is the type of the + fully expanded form of `E`. + +> **TODO:** address `...expand`, `...and` and `...or`. + +### Reduction rules + +Unless otherwise specified, all expressions in these rules must be free of side +effects. Note that every reduction rule is also an equivalence: the utterance +before the reduction is equivalent to the utterance after, so these rules can +sometimes be run in reverse (particularly during deduction). + +Utterances that are reduced by these rules must be well-shaped (and the reduced +form will likewise be well-shaped), but need not be well-typed. This enables us +to apply these reductions while determining whether an utterance is well-typed, +as in the case of typing an expression or pattern that contains a pack literal +or arity coercion, above. + +_Singular pack removal:_ if `E` is a pack segment, `⟬E⟭` reduces to `E`. + +_Singular expansion removal:_ `...E` reduces to `E`, if the shape of `E` is +`(1,)`. + +_Pack expansion splitting:_ If `E` is a segment and `S` is a sequence of +segments, `...⟬E, S⟭` reduces to `...E, ...⟬S⟭`. + +_Pack expanding:_ If `F` is a function, `X` is an utterance that does not +contain pack literals, each-names, or arity coercions, and `⟬P1, P2⟭` and +`⟬Q1, Q2⟭` both have the shape `(S1, S2)`, then +`F(⟬P1, P2⟭, X, ⟬Q1, Q2⟭, «Y; S1+S2»)` reduces to +`⟬F(P1, X, Q1, «Y; S1»), F(P2, X, Q2, «Y; S2»)⟭`. This rule generalizes in +several dimensions: + +- `F` can have any number of arity coercion and other non-pack-literal + arguments, and any positive number of pack literal arguments, and they can + be in any order. +- The pack literal arguments can have any number of segments (but the + well-shapedness requirement means they must have the same number of + segments). +- `F()` can be any expression syntax other than `...`, not just a function + call. For example, this rule implies that `⟬X1, X2⟭ * ⟬Y1, Y2⟭` reduces to + `⟬X1 * Y1, X2 * Y2⟭`, where the `*` operator plays the role of `F`. +- `F()` can also a be a pattern syntax. For example, this rule implies that + `(⟬x1: X1, x2: X2⟭, ⟬y1: Y1, y2: Y2⟭)` reduces to + `⟬(x1: X1, y1: Y1), (x2: X2, y2: Y2)⟭`, where the tuple pattern syntax + `( , )` plays the role of `F`. +- When binding pattern syntax takes the role of `F`, the name part of the + binding pattern must be a name pack. For example, `⟬x1, x2⟭: ⟬X1, X2⟭` + reduces to `⟬x1: X1, x2: X2⟭`, but `each x: ⟬X1, X2⟭` cannot be reduced by + this rule. + +_Coercion expanding:_ If `F` is a function, `S` is a shape, and `Y` is an +expression that does not contain pack literals or arity coercions, +`F(«X; S», Y, «Z; S»)` reduces to `«F(X, Y, Z); S»`. As with pack expanding, +this rule generalizes: + +- `F` can have any number of non-arity-coercion arguments, and any positive + number of arity coercion arguments, and they can be in any order. +- `F()` can be any expression syntax other than `...` or pack literal + formation, not just a function call. Unlike pack expanding, coercion + expanding does not apply if `F` is a pattern syntax. + +_Coercion removal:_ `«E; 1»` reduces to `E`. + +_Tuple indexing:_ Let `I` be an integer template constant, let `X` be a tuple +segment, and let `Ys` be a sequence of tuple segments. + +- If the arity `A` of `X` is less than `I+1`, then `(X, Ys).(I)` reduces to + `(Ys).(I-A)`. +- Otherwise: + - If `X` is not a pack expansion, then `(X, Ys).(I)` reduces to `X`. + - If `X` is of the form `...⟬«E; S»⟭`, then `(X, Ys).(I)` reduces to `E`. + +### Equivalence, equality, and convertibility + +_Pack renaming:_ Let `Ns` be a sequence of names, let `⟬Ns⟭: «T; N»` be a name +binding pattern (which may be a symbolic or template binding as well as a +runtime binding), and let `__A` be an identifier that does not collide with any +name that's visible where `⟬Ns⟭` is visible. We can rewrite all occurrences of +`⟬Ns⟭` to `each __A` in the scope of the binding pattern (including the pattern +itself) if all of the following conditions hold: + +- `Ns` contains at least one each-name. +- No name in `Ns` is used in the scope outside of `Ns`. +- No name occurs more than once in `Ns`. +- No other pack literals occur in the same pack expansion as an occurrence of + `⟬Ns⟭`. + +_Expansion convertibility:_ `...T` is convertible to `...U` if the arity of `U` +equals the arity of `T`, and the scalar components of `T` are each convertible +to all scalar components of `U`. + +_Shape equality:_ Let `(S1s)`, `(S2s)`, `(S3s)`, and `(S4s)` be shapes. +`(S1s, S2s)` equals `(S3s, S4s)` if `(S1s)` equals `(S3s)` and `(S2s)` equals +`(S4s)`. + +### Pattern match typechecking algorithm + +A full pattern is in _normal form_ if it contains no pack literals, and every +arity coercion is fully expanded. For example, +`[__N:! Arity](... each x: Vector(«i32; __N»))` is not in normal form, but +`[__N:! Arity](... each x: «Vector(i32); __N»)` is. Note that all user-written +full patterns are in normal form. Note also that by construction, this means +that the type of the body of every pack expansion has a single scalar component. +The _canonical form_ of a full pattern is the unique normal form (if any) that +is "maximally merged", meaning that every tuple pattern and tuple literal has +the smallest number of segments. For example, the canonical form of +`[__N:! Arity](... each x: «i32; __N», y: i32)` is +`[__N:! Arity](... each __args: «i32; __N+1»)`. + +> **TODO:** Specify algorithm for converting a full pattern to canonical form, +> or establishing that there is no such form. See next section for a start. + +If a function with type `F` is called with argument type `A`, we typecheck the +call by converting `F` to canonical form, and then checking whether `A` is +convertible to the parameter type by applying the deduction rules in the +previous sections. If that succeeds, we apply the resulting binding map to the +function return type to obtain the type of the call expression. + +> **TODO:** Specify the algorithm more precisely. In particular, discuss how to +> rewrite `A` as needed to make the shapes line up, but don't rewrite `F` after +> canonicalization. + +Typechecking for pattern match operations other than function calls is defined +in terms of typechecking a function call: We check a scrutinee type `S` against +a pattern `P` by checking `__F(S,)` against a hypothetical function signature +`fn __F(P,)->();`. + +> **Future work:** Extend this approach to support merging the argument list as +> well as the parameter list. + +#### Canonicalization algorithm + +The canonical form can be found by starting with a normal form, and +incrementally merging an adjacent singular parameter type into the variadic +parameter type. + +For example, consider the following function: + +```carbon +fn F[First:! type, Second:! type, ... each Next:! type] + (first: Vector(First), second: Vector(Second), + ... each next: Vector(each Next)) -> (First, Second, ... each Next); +``` + +First, we desugar the implicit arity: + +```carbon +fn F[__N:! Arity, First:! type, Second:! type, ... each Next:! «type; __N»] + (first: Vector(First), second: Vector(Second), + ... each next: Vector(each Next)) -> (First, Second, ... each Next); +``` + +Then we attempt to merge `Second` with `each Next` as follows (note that for +brevity, some of the steps presented here actually contain multiple independent +reductions): + +```carbon +// Singular pack removal (in reverse) +fn F[__N:! Arity, First:! type, Second:! type, ... ⟬each Next:! «type; __N»⟭] + (first: Vector(First), second: Vector(Second), + ... each next: Vector(⟬each Next⟭)) -> (First, Second, ... ⟬each Next⟭); +// Pack expanding +fn F[__N:! Arity, First:! type, Second:! type, ... ⟬each Next:! «type; __N»⟭] + (first: Vector(First), second: Vector(Second), + ... each next: ⟬Vector(each Next)⟭) -> (First, Second, ... ⟬each Next⟭); +// Pack expanding +fn F[__N:! Arity, First:! type, Second:! type, ... ⟬each Next:! «type; __N»⟭] + (first: Vector(First), second: Vector(Second), + ... ⟬each next: Vector(each Next)⟭) -> (First, Second, ... ⟬each Next⟭); +// Pack expansion splitting (in reverse) +fn F[__N:! Arity, First:! type, ... ⟬Second:! type, each Next:! «type; __N»⟭] + (first: Vector(First), ... ⟬second: Vector(Second), + each next: Vector(each Next)⟭) + -> (First, ... ⟬Second, each Next⟭); +// Pack expanding (in reverse) +fn F[__N:! Arity, First:! type, ... ⟬Second, each Next⟭:! «type; __N+1»] + (first: Vector(First), ... ⟬second, each next⟭: ⟬Vector(Second), Vector(each Next)⟭) + -> (First, ... ⟬Second, each Next⟭); +// Pack expanding (in reverse) +fn F[__N:! Arity, First:! type, ... ⟬Second, each Next⟭:! «type; __N+1»] + (first: Vector(First), ... ⟬second, each next⟭: Vector(⟬Second, each Next⟭)) + -> (First, ... ⟬Second, each Next⟭); +// Pack renaming +fn F[__N:! Arity, First:! type, ... each __A:! «type; __N+1»] + (first: Vector(First), ... each __a: Vector(each __A)) + -> (First, ... each __A); +``` + +This brings us back to a normal form, while reducing the number of tuple +segments. We can now repeat that process to merge the remaining parameter type: + +```carbon +fn F[__N:! Arity, First:! type, ... ⟬each __A:! «type; __N+1»⟭] + (first: Vector(First), ... each __a: Vector(⟬each __A⟭)) + -> (First, ... ⟬each __A⟭); +// Pack expanding +fn F[__N:! Arity, First:! type, ... ⟬each __A:! «type; __N+1»⟭] + (first: Vector(First), ... each __a: ⟬Vector(each __A)⟭) + -> (First, ... ⟬each __A⟭); +// Pack expanding +fn F[__N:! Arity, First:! type, ... ⟬each __A:! «type; __N+1»⟭] + (first: Vector(First), ... ⟬each __a: Vector(each __A)⟭) + -> (First, ... ⟬each __A⟭); +// Pack expansion splitting (in reverse) +fn F[__N:! Arity, ... ⟬First:! type, each __A:! «type; __N+1»⟭] + (... ⟬first: Vector(First), each __a: Vector(each __A)⟭) + -> (... ⟬First, each __A⟭); +// Pack expanding (in reverse) +fn F[__N:! Arity, ... ⟬First, each __A⟭:! «type; __N+2»⟭] + (... ⟬first, each __a⟭: ⟬Vector(First), Vector(each __A)⟭) + -> (... ⟬First, each __A⟭); +// Pack expanding (in reverse) +fn F[__N:! Arity, ... ⟬First, each __A⟭:! «type; __N+2»⟭] + (... ⟬first, each __a⟭: Vector(⟬First, each __A⟭)) + -> (... ⟬First, each __A⟭); +// Pack renaming +fn F[__N:! Arity, ... __B:! «type; __N+2»⟭] + (... __b: Vector(__B)) + -> (... __B); +``` + +Here again, this is a normal form, and there is demonstrably no way to perform +any further merging, so this must be the canonical form. + +> **TODO:** define the algorithm in more general terms, and discuss ways that +> merging can fail. + +## Alternatives considered + +- [Member packs](/proposals/p2240.md#member-packs) +- [Single semantic model for pack expansions](/proposals/p2240.md#single-semantic-model-for-pack-expansions) +- [Generalize `expand`](/proposals/p2240.md#generalize-expand) +- [Omit `expand`](/proposals/p2240.md#omit-expand) +- [Support expanding arrays](/proposals/p2240.md#support-expanding-arrays) +- [Omit each-names](/proposals/p2240.md#omit-each-names) + - [Disallow pack-type bindings](/proposals/p2240.md#disallow-pack-type-bindings) +- [Fold expressions](/proposals/p2240.md#fold-expressions) +- [Allow multiple pack expansions in a tuple pattern](/proposals/p2240.md#allow-multiple-pack-expansions-in-a-tuple-pattern) +- [Allow nested pack expansions](/proposals/p2240.md#allow-nested-pack-expansions) +- [Use postfix instead of prefix `...`](/proposals/p2240.md#use-postfix-instead-of-prefix-) +- [Avoid context-sensitity in pack expansions](/proposals/p2240.md#avoid-context-sensitity-in-pack-expansions) + - [Fold-like syntax](/proposals/p2240.md#fold-like-syntax) + - [Variadic blocks](/proposals/p2240.md#variadic-blocks) + - [Keyword syntax](/proposals/p2240.md#keyword-syntax) +- [Require parentheses around `each`](/proposals/p2240.md#require-parentheses-around-each) +- [Fused expansion tokens](/proposals/p2240.md#fused-expansion-tokens) +- [No parameter merging](/proposals/p2240.md#no-parameter-merging) +- [Exhaustive function call typechecking](/proposals/p2240.md#exhaustive-function-call-typechecking) + +## References + +- Proposal + [#2240: Variadics](https://github.com/carbon-language/carbon-lang/pull/2240) diff --git a/docs/project/principles/library_apis_only.md b/docs/project/principles/library_apis_only.md index 7b0a73074b5b4..b73233375f44f 100644 --- a/docs/project/principles/library_apis_only.md +++ b/docs/project/principles/library_apis_only.md @@ -88,16 +88,12 @@ is more restricted, and this principle will not apply to them. Most importantly, function types might not be first-class types, in which case they need not be library types. -The logic for translating a literal expression to a value of the appropriate -type is arguably part of that type's public API, but will not be part of that -type's class definition. - -Tuple types will probably not fully conform to this principle, because doing so -would be circular: there is no way to name a tuple type that doesn't rely on -tuple syntax, and no way to define a class body for a tuple type that doesn't -contain tuple patterns. However, we will strive to ensure that it is possible to -define a parameterized class type within Carbon that supports all the same -operations as built-in tuple types. +Some types (such as tuples, structs, and certain integer types) will have +built-in literal syntaxes for creating values of those types. Furthermore, in +some cases (such as tuples and structs) the type's literal syntax will also be +usable as a pattern syntax. The logic for performing those operations is +arguably part of those types' public API, but will not be part of those types' +class definitions. ## Alternatives considered diff --git a/proposals/p2240.md b/proposals/p2240.md new file mode 100644 index 0000000000000..dfecfcd771fef --- /dev/null +++ b/proposals/p2240.md @@ -0,0 +1,1136 @@ +# Variadics + + + +[Pull request](https://github.com/carbon-language/carbon-lang/pull/2240) + + + +## Table of contents + +- [Abstract](#abstract) +- [Problem](#problem) +- [Background](#background) +- [Proposal](#proposal) + - [Examples](#examples) + - [Comparisons](#comparisons) +- [Rationale](#rationale) +- [Alternatives considered](#alternatives-considered) + - [Member packs](#member-packs) + - [Single semantic model for pack expansions](#single-semantic-model-for-pack-expansions) + - [Generalize `expand`](#generalize-expand) + - [Omit `expand`](#omit-expand) + - [Support expanding arrays](#support-expanding-arrays) + - [Omit each-names](#omit-each-names) + - [Disallow pack-type bindings](#disallow-pack-type-bindings) + - [Fold expressions](#fold-expressions) + - [Allow multiple pack expansions in a tuple pattern](#allow-multiple-pack-expansions-in-a-tuple-pattern) + - [Allow nested pack expansions](#allow-nested-pack-expansions) + - [Use postfix instead of prefix `...`](#use-postfix-instead-of-prefix-) + - [Avoid context-sensitity in pack expansions](#avoid-context-sensitity-in-pack-expansions) + - [Fold-like syntax](#fold-like-syntax) + - [Variadic blocks](#variadic-blocks) + - [Keyword syntax](#keyword-syntax) + - [Require parentheses around `each`](#require-parentheses-around-each) + - [Fused expansion tokens](#fused-expansion-tokens) + - [No parameter merging](#no-parameter-merging) + - [Exhaustive function call typechecking](#exhaustive-function-call-typechecking) + + + +## Abstract + +Proposes a set of core features for declaring and implementing generic variadic +functions. + +A "pack expansion" is a syntactic unit beginning with `...`, which is a kind of +compile-time loop over sequences called "packs". Packs are initialized and +referred to using "each-names", which are marked with the `each` keyword at the +point of declaration and the point of use. + +The syntax and behavior of a pack expansion depends on its context, and in some +cases by a keyword following the `...`: + +- In a tuple literal expression (such as a function call argument list), `...` + iteratively evaluates its operand expression, and treats the values as + successive elements of the tuple. +- `...and` and `...or` iteratively evaluate a boolean expression, combining + the values using `and` and `or`, and ending the loop early if the underlying + operator short-circuits. +- In a statement context, `...` iteratively executes a statement. +- In a tuple literal pattern (such as a function parameter list), `...` + iteratively matches the elements of the scrutinee tuple. In conjunction with + pack bindings, this enables functions to take an arbitrary number of + arguments. + +## Problem + +Carbon needs a way to define functions and parameterized types that are +_variadic_, meaning they can take a variable number of arguments. + +## Background + +C has long supported variadic functions through the "varargs" mechanism, but +that's heavily disfavored in C++ because it isn't type-safe. Instead, C++ +provides a separate feature for defining variadic _templates_, which can be +functions, classes, or even variables. However, variadic templates currently +suffer from several shortcomings. Most notably: + +- They must be templates, which means they cannot be definition-checked, and + suffer from a variety of other costs such as needing to be defined in header + files, and code bloat due to template instantiation. +- It is inordinately difficult to define a variadic function whose parameters + have a fixed type, and the signature of such a function does not clearly + communicate that fixed type to readers. +- The design encourages using recursion rather than iteration to process the + elements of a variadic parameter list. This results in more template + instantiations, and typically has at least quadratic overhead in the size of + the pack (at compile time, and sometimes at run time). In recent versions of + C++ it is also possible to iterate over packs procedurally, using a + [fold expressions](https://en.cppreference.com/w/cpp/language/fold) over the + comma operator, but that technique is awkward to use and not widely known. + +There have been a number of C++ standard proposals to address some of these +issues, and improve variadic templates in other ways, such as +[P1219R2: Homogeneous variadic function parameters](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1219r2.html), +[P1306R1: Expansion Statements](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1306r1.pdf), +[P1858R2: Generalized Pack Declaration and Usage](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p1858r2.html), +and +[P2277R0: Packs Outside of Templates](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2277r0.html). +However, C++ has chosen not to pursue definition-checking even for non-variadic +functions, so definition-checked variadics seem out of reach. The most recent +proposal to support fixed-type parameter packs was +[rejected](https://github.com/cplusplus/papers/issues/297). A proposal to +support iterating over parameter packs was inactive for several years, but has +very recently been [revived](https://github.com/cplusplus/papers/issues/156). + +Swift supports +[variadic parameters](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/functions/#Variadic-Parameters) +so long as all elements have the same type, and has recently approved +[SE-0393: Value and Type Parameter Packs](https://github.com/apple/swift-evolution/blob/main/proposals/0393-parameter-packs.md), +which adds support for definition-checked, heterogeneous variadic parameters +(with a disjoint syntax). +[SE-0404: Pack Iteration](https://github.com/simanerush/swift-evolution/blob/se-0404-pack-iteration/proposals/0404-pack-iteration.md), +which extends that to support iterating through a variadic parameter list, has +been positively received, but hasn't yet been approved. + +There have been several attempts to add such a feature to Rust, but that work is +[currently inactive](https://github.com/rust-lang/rfcs/issues/376#issuecomment-830034029). + +## Proposal + +See `/docs/design/variadics.md` in this pull request. + +### Examples + +```carbon +// Computes the sum of its arguments, which are i64s +fn SumInts(... each param: i64) -> i64 { + var sum: i64 = 0; + ... sum += each param; + return sum; +} +``` + +```carbon +// Concatenates its arguments, which are all convertible to String +fn StrCat[... each T:! ConvertibleToString](... each param: each T) -> String { + var len: i64 = 0; + ... len += each param.Length(); + var result: String = ""; + result.Reserve(len); + ... result.Append(each param.ToString()); + return result; +} +``` + +```carbon +// Returns the minimum of its arguments, which must all have the same type T. +fn Min[T:! Comparable & Value](var result: T, ... each next: T) -> T { + ... if (each next < result) { + result = each next; + } + return result; +} +``` + +```carbon +// Invokes f, with the tuple `args` as its arguments. +fn Apply[... each T:! type, F:! CallableWith(... each T)] + (f: F, args: (... each T)) -> auto { + return f(...expand args); +} +``` + +```carbon +// Takes an arbitrary number of vectors with arbitrary element types, and +// returns a vector of tuples where the i'th element of the vector is +// a tuple of the i'th elements of the input vectors. +fn Zip[... each ElementType:! type] + (... each vector: Vector(each ElementType)) + -> Vector((... each ElementType)) { + ... var each iter: auto = each vector.Begin(); + var result: Vector((... each ElementType)); + while (...and each iter != each vector.End()) { + result.push_back((... each iter)); + ... each iter++; + } + return result; +} +``` + +```carbon +// Toy example of mixing variadic and non-variadic parameters. +// Takes an i64, any number of f64s, and then another i64. +fn MiddleVariadic(first: i64, ... each middle: f64, last: i64); +``` + +```carbon +// Toy example of using the result of variadic type deduction. +fn TupleConcat[... each T1: type, ... each T2: type]( + t1: (... each T1), t2: (... each T2)) -> (... each T1, ... each T2) { + return (...expand t1, ...expand t2); +} +``` + +### Comparisons + +The following table compares selected examples of Carbon variadics against +equivalent code written in C++20 (with and without the extensions discussed +[earlier](#background)) and Swift. + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CarbonC++20C++20 with extensionsSwift with extensions
+ +```carbon +// Computes the sum of its arguments, which are i64s +fn SumInts(... each param: i64) -> i64 { + var sum: i64 = 0; + ... sum += each param; + return sum; +} +``` + + + +```cpp +template + requires (std::convertible_to && ...) +int64_t SumInts(const Params&... params) { + return (static_cast(params) + ... + 0); +} +``` + + + +With P1219R2: + +```C++ +int64_t SumInts(int64... params) { + return (static_cast(params) + ... + 0); +} +``` + + + +(No extensions) + +```swift +func SumInts(_ params: Int64...) { + var sum: Int64 = 0 + for param in params { + sum += param + } + return sum +} +``` + +
+ +```carbon +fn Min[T:! Comparable & Value](first: T, ... each next: T) -> T { + var result: T = first; + ... if (each next < result) { + result = each next; + } + return result; +} +``` + + + +```cpp +template + requires std::totally_ordered && std::copyable && + (std::same_as && ...) +T Min(T first, Params... rest) { + if constexpr (sizeof...(rest) == 0) { + // Base case. + return first; + } else { + T min_rest = Min(rest...); + if (min_rest < first) { + return min_rest; + } else { + return first; + } + } +} +``` + + + +With P1219R2 and P1306R2 + +```cpp +template + requires std::totally_ordered && std::copyable +T Min(const T& first, const T&... rest) { + T result = first; + template for (const T& t: rest) { + if (t < result) { + result = t; + } + } + return result; +} +``` + + + +(No extensions) + +```swift +func Min(_ first: T, _ rest: T...) -> T { + var result: T = first; + for t in rest { + if (t < result) { + result = t + } + } + return result +} +``` + +
+ +```carbon +fn StrCat[... each T:! ConvertibleToString](... each param: each T) -> String { + var len: i64 = 0; + ... len += each param.Length(); + var result: String = ""; + result.Reserve(len); + ... result.Append(each param.ToString()); + return result; +} +``` + + + +```cpp +template +std::string StrCat(const Ts&... params) { + std::string result; + result.reserve((params.Length() + ... + 0)); + (result.append(params.ToString()), ...); + return result; +} +``` + + + +With P1306R2 + +```cpp +template +std::string StrCat(const Ts&... params) { + std::string result; + result.reserve((params.Length() + ... + 0)); + template for (auto param: params) { + result.append(param.ToString()); + } + return result; +} +``` + + + +With SE-0393 and SE-404 + +```swift +func StrCat(_ param: repeat each T) -> String { + var len: Int64 = 0; + for param in repeat each param { + len += param.Length() + } + var result: String = "" + result.reserveCapacity(len) + for param in repeat each param { + result.append(param.ToString()) + } + return result +} +``` + +
+ +## Rationale + +Carbon needs variadics to effectively support +[interoperation with and migration from C++](/docs/project/goals.md#interoperability-with-and-migration-from-existing-c-code), +where variadic templates are fairly common. Variadics also make code +[easier to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write), +because some APIs (such as `printf`) can't be naturally expressed in terms of a +fixed number of parameters. + +Furthermore, Carbon needs to support _generic_ variadics for the same reasons it +needs to support generic non-variadic functions: for example, +definition-checking makes APIs +[easier to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write), +and [easier to evolve](/docs/project/goals.md#software-and-language-evolution). +Furthermore, the language as a whole is easier to understand and write code in +if separate features like variadics and generics compose in natural ways, rather +than being mutually exclusive. + +Variadics are also important for supporting +[performance-critical software](/docs/project/goals.md#performance-critical-software), +because variadic APIs can be more efficient than their non-variadic +counterparts. For example, `StrCat` is fundamentally more efficient than +something like a chain of `operator+` calls on `std::string`, because it does +not need to materialize a series of partial results, and it can pre-allocate a +buffer large enough for the final result. + +Variadics are also needed to support the principle that +[all APIs are library APIs](/docs/project/principles/library_apis_only.md), +because the library representations of types such as tuples and callables will +need to be variadic. This proposal may appear to deviate from that principle in +some ways, but that appearance is misleading: + +- The design of pack expansion expressions treats the tuple literal syntax as + built-in, but this isn't a problem because literal syntaxes are explicitly + excluded from the principle. +- The design of pack expansion patterns treats tuple types as built-in. This + is arguably consistent with the principle, if we regard a tuple pattern as a + kind of tuple literal (note that they have identical syntax). This proposal + also revises the text of the principle to make that more explicit. +- Pack types themselves are built-in types, with no library API. However, the + principle only applies to first-class types, and pack types are decidedly + not first-class: they cannot be function return types, they cannot even be + named, and an expression cannot evaluate to a value with a pack type unless + it's within a pack expansion _and_ it has compile-time expression phase (and + even that narrow exception only exists to make the formalism more + convenient). + +## Alternatives considered + +### Member packs + +We could potentially support declaring each-names as class members. However, +this raises some novel design issues. In particular, pack bindings currently +rely exclusively on type deduction for information like the arity of the pack, +but for class members, there usually isn't an initializer available to drive +type deduction. + +In addition, it's usually if not always possible to work around the lack of +member packs by using members with tuple or array types instead. Consequently, +this feature is deferred to future work. + +### Single semantic model for pack expansions + +There's a subtle discrepancy in how this proposal models expression pack +expansions: at run time, all pack expansions are modeled as procedural loops +that successively evaluate the expansion body for each element of the input +pack, and within each iteration, expressions have scalar values. However, in the +type system, expressions within a pack expansion are notionally evaluated once, +producing a pack value. In effect, this treats pack expansions like SIMD code, +with expressions operating on "vectors" of data in parallel, rather than +iteratively executing the code on a series of scalar values. + +This discrepancy leads to an impedance mismatch where the two models meet. In +particular, it leads to the result that expressions within a pack expansion have +pack types, but do not evaluate to pack values. This contravenes one of the +basic expectations of a type system, that the type of an expression equals (or +is at least a supertype of) the type of its value. + +It's tempting to resolve the inconsistency by applying the parallel model at run +time as well as in the type system. However, that isn't feasible, because the +parallel model has the same limitation for variadics as it does for SIMD: it +can't model branching control flow. For example, consider +`(... if (expand cond) then F(expand param) else G(expand param))`: if +`expand param` truly evaluated to a pack value, then evaluating this expression +would require N calls to _both_ `F` and `G`, rather than N calls to _either_ `F` +or `G`. Even for expressions that don't contain control flow, the same problem +applies when they occur within a statement pack expansion that does. We can't +even statically detect these problems, because a branch could be hidden inside a +function call. And this isn't just a performance problem -- if `F` or `G` have +side effects, it can also be a correctness problem. + +An earlier version of this proposal tried to address this problem in a more +limited way by saying that expressions within a pack expansion don't have types +at all, but instead have "type packs". This shift in terminology nominally +avoids the problem of having expressions that don't evaluate to a value of the +expression's type, but it doesn't seem to be very clarifying in practice, and it +doesn't address the substance of the problem. + +### Generalize `expand` + +The syntax "`...expand` _expression_" behaves like syntactic sugar for +`... each x`, where `x` is an invented pack binding in the same scope, defined +as if by "`let (... each x: auto) =` _expression_". We could generalize that by +saying that `expand` is a prefix operator with the same precedence as `*` that +can be used anywhere in a pack expansion, where "`expand` _expression_" is +syntactic sugar for `each x` (with `x` defined as before, in the scope +containing the pack expansion). This would make `expand` more useful, and also +resolve the anomaly where `...expand` is the only syntax that begins with `...` +but is not a pack expansion. It is also a precondition for several of the +alternatives discussed below. + +However, those semantics could be very surprising in practice. For example: + +```carbon +...if (Condition()) { + var x: auto = expand F(y); +} +``` + +In this code, `F(y)` is evaluated before the pack expansion is entered, which +means that it is evaluated unconditionally, and it cannot refer to names +declared inside the `if` block. + +We can avoid the name-resolution issue by disallowing `expand` in statement pack +expansions, but the sequencing of evaluation could still be surprising, +particularly with `if` expressions. + +### Omit `expand` + +As noted above, `...expand` is fundamentally syntactic sugar, so we could omit +it altogether. This would somewhat simplify the design, and avoid the anomaly of +having one syntax that starts with `...` but isn't a pack expansion. However, +that would make it substantially less ergonomic to do things like expand a tuple +into an argument list, which we expect to be relatively common. + +### Support expanding arrays + +Statically-sized arrays are very close to being a special case of tuple types: +the only difference between an array type `[i32; 2]` (using Rust syntax) and a +tuple type `(i32, i32)` is that the array type can be indexed with a run-time +subscript. Consequently, it would be fairly natural to allow `expand` to operate +on arrays as well as tuples, and even to allow arrays of types to be treated as +tuple types (in the same way that tuples of types can be treated as tuple +types). + +This functionality is omitted from the current proposal because we have no +motivating use cases, but it could be added as an extension. Note that there are +important motivating use cases under some of the alternatives considered below. + +### Omit each-names + +Rather than having packs be distinguished by their names, we could instead +distinguish them by their types. For example, under the current proposal, the +signature of `Zip` is: + +```carbon +fn Zip[... each ElementType:! type] + (... each vector: Vector(each ElementType)) + -> Vector((... each ElementType)); +``` + +With this alternative, it could instead be written: + +```carbon +fn Zip[ElementTypes:! [type;]] + (... vectors: Vector(expand ElementTypes)) + -> Vector((... expand ElementTypes)); +``` + +This employs several features not in the primary proposal: + +- In cases where the declared type of the each-name does not vary across + iterations (like `ElementType`), we can re-express it as an array binding if + [`expand` supports arrays](#support-expanding-arrays), and if + [`expand` is a stand-alone operator](#generalize-expand). Note that we only + need this in type position of a binding pattern, where we could more easily + restrict `expand` to avoid the problems discussed earlier. +- In cases where the declared type of the binding does vary, that fact alone + implies that the binding refers to a pack, so we can effectively infer the + presence of `each` from the type, rather than make the user spell it out + explicitly. + +This slight change in syntax belies a much larger shift in the underlying +semantics: since these are ordinary bindings, a given call to `Zip` must bind +each of them to a single value that represents the whole sequence of arguments +(which is why their names are now plural). In the case of `ElementTypes`, that +follows straightforwardly from its type: it represents the argument types as an +array of `type`s. The situation with `vectors` is more subtle: we have to +interpret `Vector(expand ElementTypes)` as the type of the whole sequence of +argument values, rather than as a generic description of the type of a single +argument. In other words, we have to interpret it as a pack type, and that means +`vectors` notionally binds to a run-time pack value. + +Consequently, when `vectors` is used in the function body, it doesn't need an +`each` prefix: we've chosen to express variadicity in terms of types, and it +already has a pack type, so it can be directly used as an expansion site. + +This approach has a few advantages: + +- We don't have to introduce the potentially-confusing concept of a binding + that binds to multiple values simultaneously. +- It avoids the anomaly where we have pack types in the type system, but no + actual values of those types. +- Removing the `each` keyword makes it more natural to spell `expand` as a + symbolic token (earlier versions of this proposal used `[:]`), which is more + concise and doesn't need surrounding whitespace. +- For fully homogeneous variadics (such as `SumInts` and `Min`) it's actually + possible to write the function body as an ordinary loop with no variadics, + by expressing the signature in terms of a non-pack binding with an array + type. + +However, it also has some major disadvantages: + +- The implicit expansion of pack-type bindings hurts readability. For example, + it's easy to overlook the fact that the loop condition + `while (...and expand iters != vectors.End())` in `Zip` has two expansion + sites, not just one. This problem is especially acute in cases where a + non-local name has a pack type. +- We have to forbid template-dependent names from having pack types (see + [leads issue #1162](https://github.com/carbon-language/carbon-lang/issues/1162)), + because the possibility that an expression might be an expansion site in + some instantiations but not others would cause serious readability and + implementability issues. +- A given _use_ of such a binding really represents a single value at a time, + in the same way that the iteration variable of a for-each loop does, so + giving the binding a plural name and a pack type creates confusion in that + context rather than alleviating it. + +It's also worth noting that we may eventually want to introduce operations that +treat the sequence of bound values as a unit, such as to determine the length of +the sequence (like `sizeof...` in C++), or even to index into it. This approach +might seem more amenable to that, because it conceptually treats the sequence of +values as a value in itself, which could have its own operations. However, this +approach leaves no "room" in the syntax to spell those operations, because any +mention of a pack-type binding implicitly refers to one of its elements. + +Conversely, the status quo proposal seems to leave a clear syntactic opening for +those operations: you can refer to the sequence as a whole by omitting `each`, +so `each vector.Size()` refers to the size of the current iteration's `vector`, +whereas `vector.Size()` could refer to the size of the sequence of bound values. +However, this could easily turn out to be a "wrong default": omitting `each` +seems easy to do by accident, and easy to misread during code review. + +There are other solutions to this problem that work equally well with the status +quo or this alternative. In particular, it's already possible to express these +operations outside of a pack expansion by converting to a tuple, as in +`(... each vector).Size()` (status quo) or `(... vectors).Size()` (this +alternative). That may be sufficient to address those use cases, especially if +we relax the restrictions on nesting pack expansions. Failing that, +variadic-only spellings for these operations (like `sizeof...` in C++) would +also work with both approaches. So this issue does not seem like an important +differentiator between the two approaches. + +#### Disallow pack-type bindings + +As a variant of the above approach, it's possible to omit both each-names and +pack-type bindings, and instead rely on variadic tuple-type bindings. For +example, the signature of `Zip` could instead be: + +```carbon +fn Zip[ElementTypes:! [type;]] + (... expand vectors: (... Vector(expand ElementTypes))) + -> Vector((... expand ElementTypes)); +``` + +This signature doesn't change the callsite semantics, but within the function +body `vectors` will be a tuple rather than a pack. This avoids or mitigates all +of the major disadvantages of pack-type bindings, but it comes at a substantial +cost: the function signature is substantially more complex and opaque. That +seems likely to be a bad tradeoff -- the disadvantages of pack-type bindings +mostly concern the function body, but readability of variadic function +signatures seems much more important than readability of variadic function +bodies, because the signatures will be read far more often, and by programmers +who have less familiarity with variadics. + +This approach requires us to relax the ban on nested pack expansions. This does +create some risk of confusion about which pack expansion a given `expand` +belongs to, but probably much less than if we allowed unrestricted nesting. + +The leads chose not to pursue this approach in +[leads issue #1162](https://github.com/carbon-language/carbon-lang/issues/1162). + +### Fold expressions + +We could generalize the `...and` and `...or` syntax to support a wider variety +of binary operators, and to permit specifying an initial value for the chain of +binary operators, as with C++'s +[fold expressions](https://en.cppreference.com/w/cpp/language/fold). This would +be more consistent with C++, and would give users more control over +associativity and over the behavior of the arity-zero case. + +However, fold expressions are arguably too general in some respects: folding +over a non-commutative operator like `-` is more likely to be confusing than to +be useful. Similarly, there are few if any plausible use cases for customizing +the arity-zero behavior of `and` or `or`. Conversely, fold expressions are +arguably not general enough in other respects, because they only support folding +over a fixed set of operators, not over functions or compound expressions. + +Furthermore, in order to support folds over operator tokens that can be either +binary or prefix-unary (such as `*`), we would need to choose a different syntax +for tuple element lists. Otherwise, `...*each foo` would be ambiguous between +`*foo[:0:], *foo[:1:],` etc. and `foo[:0:] * foo[:1:] *` etc. + +Note that even if Carbon supported more general C++-like fold expressions, we +would still probably have to give `and` and `or` special-case treatment, because +they are short-circuiting. + +As a point of comparison, C++ fold expressions give special-case treatment to +the same two operators, along with `,`: they are the only ones where the initial +value can be omitted (such as `... && args` rather than `true && ... && args`) +even if the pack may be empty. Furthermore, folding over `&&` appears to have +been the original motivation for adding fold expressions to C++; it's not clear +if there are important motivating use cases for the other operators. + +Given that we are only supporting a minimal set of operators, allowing `...` to +occur in ordinary binary syntax has few advantages and several drawbacks: + +- It might conflict with a future general fold facility. +- It would invite users to try other operators, and would probably give less + clear errors if they do. +- It would substantially complicate parsing and the AST. +- It would force users to make a meaningless choice between `x or ...` and + `... or x`, and likewise for `and`. + +See also the discussion [below](#fold-like-syntax) of using `...,` and `...;` in +place of the tuple and statement forms of `...`. This is inspired by fold +expressions, but distinct from them, because `,` and `;` are not truly binary +operators, and it's targeting a different problem. + +### Allow multiple pack expansions in a tuple pattern + +As currently proposed, we allow multiple `...` expressions within a tuple +literal expression, but only allow one `...` pattern within a tuple pattern. It +is superficially tempting to relax this restriction, but fundamentally +infeasible. + +Allowing multiple `...` patterns would create a potential for ambiguity about +where their scrutinees begin and end. For example, given a signature like +`fn F(... each xs: i32, ... each ys: i32)`, there is no way to tell where `xs` +ends and `ys` begins in the argument list; every choice is equally valid. That +ambiguity can be avoided if the types are different, but that would make type +_non_-equality a load-bearing part of the pattern. That's a very unusual thing +to need to reason about in the type system, so it's liable to be a source of +surprise and confusion for programmers, and in particular it looks difficult if +not impossible to usefully express with generic types, which would greatly limit +the usefulness of such a feature. + +Function authors can straightforwardly work around this restriction by adding +delimiters. For example, the current design disallows +`fn F(... each xs: i32, ... each ys: i32)`, but it allows +`fn F((... each xs: i32), (... each ys: i32))`, which is not only easier to +support, but makes the callsite safer and more readable, since the boundary +between the `xs` and `ys` arguments is explicitly marked. By contrast, if we +disallowed multiple `...` expressions in a function argument list, function +callers who ran into that restriction would often find it difficult or +impossible to work around. Note, however, that this workaround presupposes that +function signatures can have bindings below top-level, which is +[currently undecided](https://github.com/carbon-language/carbon-lang/issues/1229). + +To take a more abstract view of this situation: when we reuse expression syntax +as pattern syntax, we are effectively inverting expression evaluation, by asking +the language to find the operands that would cause an expression to evaluate to +a given value. That's only possible if the operations involved are invertible, +meaning that they do not lose information. When a tuple literal contains +multiple `...` expressions, evaluating it effectively discards structural +information about for example where `xs` ends and `ys` begins. The operation of +forming a tuple from multiple packs is not invertible, and consequently we +cannot use it as a pattern operation. Our rule effectively says that if the +function needs that structural information, it must ask the caller to provide +it, rather than asking the compiler to infer it. + +### Allow nested pack expansions + +Earlier versions of this design allowed pack expansions to contain other pack +expansions. This is in some ways a natural generalization, but it added +nontrivial complexity to the design. In particular, when an each-name is +lexically within two or more pack expansions, we need a rule for determining +which pack expansion iterates over it, in a way that is unsurprising and +supports the intended use cases. However, we have few if any motivating use +cases for it, which made it difficult to evaluate that aspect of the design. +Consequently, this proposal does not support nested pack expansions, although it +tries to avoid ruling them out as a future extension. + +### Use postfix instead of prefix `...` + +`...` is a postfix operator in C++, which aligns with the natural-language use +of "…", so it would be more consistent with both if `...`, `...and`, and `...or` +were postfix operators spelled `...`, `and...`, and `or...`, and likewise if +statement pack expansions were marked by a `...` at the end rather than the +beginning. + +However, prefix syntaxes are usually easier to parse (particularly for humans), +because they ensure that by the time you start parsing an utterance, you already +know the context in which it is used. This is clearest in the case of +statements: the reader might have to read an arbitrary amount of code in the +block before realizing that the code they've been reading will be executed +variadically, so that seems out of the question. The cases of `and`, `or`, and +`,` are less clear-cut, but we have chosen to make them all prefix operators for +consistency with statements. + +### Avoid context-sensitity in pack expansions + +This proposal "overloads" the `...` token with multiple different meanings +(including different precedences), and the meaning depends in part on the +surrounding context, despite Carbon's principle of +[avoiding context-sensitivity](/docs/project/principles/low_context_sensitivity.md). +We could instead represent the different meanings using separate syntaxes. + +There are several variants of this approach, but they all have substantial +drawbacks (see the following subsections). Furthermore, the problems associated +with context-sensitivity appear to be fairly mild in this case: the difference +between a tuple literal context and a statement context is usually quite local, +and is usually so fundamental that confusion seems unlikely. + +#### Fold-like syntax + +We could use a modifier after `...` to select the expansion's meaning (as we +already do with `and` and `or`). In particular, we could write `...,` to +iteratively form elements of a tuple, and write `...;` to iteratively execute a +statement. This avoids context-sensitivity (apart from `...,` having a dual role +in expressions and patterns, like many other syntaxes), and has an underlying +unity: `...,`, `...;` `...and`, and `...or` represent "folds" over the `,`, `;`, +`and`, and `or` tokens, respectively. As a side benefit, this would preserve the +property that a tuple literal always contains a `,` character (unlike the +current proposal). + +However, this approach has major readability problems. Using `...;` as a prefix +operator is completely at odds with the fact that `;` marks the end of a +statement, not the beginning. Furthermore, it would probably be surprising to +use `...;` in contexts where `;` is not needed, because the end of the statement +is marked with `}`. + +The problems with `...,` are less severe, but still substantial. In this syntax +`,` does not behave like a separator, but our eyes are trained to read it as +one, and that habit is difficult to unlearn. For example, most readers have +found that they can't help automatically reading `(..., each x)` as having two +sub-expressions, `...` and `each x`. This effect is particularly disruptive when +skimming a larger body of code, such as: + +```carbon +fn TupleConcat[..., each T1: type, ..., each T2: type]( + t1: (..., each T1), t2: (..., each T2)) -> (..., each T1, ..., each T2) { + return (..., expand t1, ..., expand t2); +} +``` + +#### Variadic blocks + +We could replace the statement form of `...` with a variadic block syntax such +as `...{ }`. However, this doesn't give us an alternative for the tuple form of +`...`, and yet heightens the problems with it: `...{` could read as as applying +the `...` operator to a struct literal. + +Furthermore, it gives us no way to variadically declare a variable that's +visible outside the expansion (such as `each iter` in the `Zip` example). This +can be worked around by declaring those variables as tuples, but this adds +unnecessary complexity to the code. + +#### Keyword syntax + +We could drop `...` altogether, and use a separate keyword for each kind of pack +expansion. For example, we could use `repeat` for variadic lists of tuple +elements, `do_repeat` for variadic statements, and `all_of` and `any_of` in +place of `...and` and `...or`. This leads to code like: + +```carbon +// Takes an arbitrary number of vectors with arbitrary element types, and +// returns a vector of tuples where the i'th element of the vector is +// a tuple of the i'th elements of the input vectors. +fn Zip[repeat each ElementType:! type] + (repeat each vector: Vector(each ElementType)) + -> Vector((repeat each ElementType)) { + do_repeat var each iter: auto = each vector.Begin(); + var result: Vector((repeat each ElementType)); + while (all_of each iter != each vector.End()) { + result.push_back((repeat each iter)); + repeat each iter++; + } + return result; +} +``` + +This approach is heavily influenced by +[Swift variadics](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0393-parameter-packs.md), +but not quite the same. It has some major advantages: the keywords are more +consistent with `each` (and `expand` to some extent), substantially less +visually noisy than `...`, and they may also be more self-explanatory. However, +it does have some substantial drawbacks. + +Most notably, there is no longer any syntactic commonality between the different +tokens that mark the root of an expansion. That makes it harder to visually +identify expansions, and could also make variadics harder to learn, because the +spelling does not act as a mnemonic cue. And while it's already not ideal that +under the primary proposal a tuple literal is identified by the presence of +either `,` or `...`, it seems even worse if one of those two tokens is instead a +keyword. + +Relatedly, the keywords have less clear precedence relationships, because +`all_of` and `any_of` can't as easily "borrow" their precedence from their +non-variadic counterparts. For example, consider this line from `Zip`: + +```carbon +while (...and each iter != each vector.End()) { +``` + +Under this alternative, that becomes: + +```carbon +while (all_of each iter != each vector.End()) { +``` + +I find the precedence relationships in the initial `all_of expand iters !=` more +opaque than in `...and expand iters !=`, to the extent that we might need to +require additional parentheses: + +```carbon + while (all_of (expand iters != each vectors.End())) { +``` + +That avoids outright ambiguity, but obliging readers to maintain a mental stack +of parentheses in order to parse the expression creates its own readability +problems. + +It's appealing that the `repeat` keyword combines with `each` to produce code +that's almost readable as English, but it creates a temptation to read `expand` +the same way, which will usually be misleading. For example, `repeat expand foo` +sounds like it is repeatedly expanding `foo`, but in fact it expands it only +once. It's possible that a different spelling of `expand` could avoid that +problem, but I haven't been able to find one that does so while also avoiding +the potential for confusion with `each`. This is somewhat mitigated by the fact +that `expand` expressions are likely to be rare. + +It's somewhat awkward, and potentially even confusing, to use an imperative word +like `repeat` in a pattern context. By design, the pattern language is +descriptive rather than imperative: it describes the values that match rather +than giving instructions for how to match them. As a result, in a pattern like +`(repeat each param: i64)`, it's not clear what action is being repeated. + +Finally, it bears mentioning that the keywords occupy lexical space that could +otherwise be used for identifiers. Notably, `all_of`, `any_of`, and `repeat` are +all names of functions in the C++ standard library. This is not a fundamental +problem, because we expect Carbon to have some way of "quoting" a keyword for +use as an identifier (such as Rust's +[raw identifiers](https://doc.rust-lang.org/rust-by-example/compatibility/raw_identifiers.html)), +but it is likely to be a source of friction. + +### Require parentheses around `each` + +We could give `each` a lower precedence, so that expressions such as +`each vector.End()` would need to be written as `(each vector).End()`. This +could make the code clearer for readers, especially if they are new to Carbon +variadics. However, this would make the code visually busier, and might give the +misleading impression that `each` can be applied to anything other than an +identifier. I propose that we wait and see whether the unparenthesized syntax +has readability problems in practice, before attempting to solve those problems. + +We have discussed a more general solution to this kind of problem, where a +prefix operator could be embedded in a `->` token, in order to apply the prefix +operator to the left-hand operand without needing parentheses. However, this +approach is much more appealing when the prefix operator is a symbolic token: +`x-?>y` may be a plausible alternative to `(?x).y`, but `x-each>y` seems much +harder to visually parse. Furthermore, this approach is hard to reconcile with +treating `each` as fundamentally part of the name, rather than an operator +applied to the name. + +### Fused expansion tokens + +Instead of treating `...and` and `...or` as two tokens with whitespace +discouraged between them, we could treat them as single tokens. This might more +accurately reflect the fact that they are semantically different operations than +`...`, and reduce the potential for readability problems in code that doesn't +follow our recommended whitespace conventions. However, that could lead to a +worse user experience if users accidentally insert a space after the `...`. + +### No parameter merging + +Under the current proposal, the compiler attempts to merge function parameters +in order to support use cases like this one, where merging the parameters of +`Min` enables us to pair each argument with a single logical parameter that will +match it: + +```carbon +fn Min[T:! type](first: T, ... each next: T) -> T; + +fn F(... each arg: i32) { + Min(... each arg, 0 as i32); +} +``` + +However, this approach makes typechecking hard to understand (and predict), +because the complex conditions governing merging mean that subtle differences in +the code can cause dramatic differences in the semantics. For example: + +```carbon +fn F[A:! I, ... each B:! I](a: A, ... each b: each B); +fn G[A:! I, ... each B:! I](a: A, ... each b: each B) -> A; +``` + +These two function signatures are identical other than their return types, but +they actually have different requirements on their arguments: `G` requires the +first argument to be singular, whereas `F` only requires _some_ argument to be +singular. It seems likely to be hard to teach programmers that the function's +return type sometimes affects whether a given argument list is valid. Relatedly, +it's hard to see how a diagnostic could concisely explain why a given call to +`G` is invalid, in a way that doesn't seem to also apply to `F`. + +We could solve that problem by omitting parameter merging, and interpreting all +of the above signatures as requiring that the first argument must be singular, +because the first parameter is singular. Thus, there would be a clear and +predictable connection between the parameter list and the requirements on the +argument list. + +In order to support use cases like `Min` where the author doesn't intend to +impose such a requirement, we would need to provide some syntax for declaring +`Min` so that it has a single parameter, but can't be called with no arguments. +More generally, this syntax would probably need to support setting an arbitrary +minimum number of arguments, not just 1. For example, an earlier version of this +proposal used `each(>=N)` to require that a parameter match at least N +arguments, so `Min` could be written like this: + +```carbon +fn Min[T:! type](... each(>=1) param: T) -> T; +``` + +However, this alternative has several drawbacks: + +- We haven't been able to find a satisfactory arity-constraint syntax. In + addition to its aesthetic problems, `each(>=1) param` disrupts the mental + model where `each` is part of the name, and it's conceptually awkward + because the constraint actually applies to the pack expansion as a whole, + not to the each-name in particular. However, it's even harder to find an + arity-constraint syntax that could attach to `...` without creating + ambiguity. Furthermore, any arity-constraint syntax would be an additional + syntax that users need to learn, and an additional choice they need to make + when writing a function signature. +- Ideally, generic code should typecheck if every possible monomorphization of + it would typecheck. This alternative does not live up to that principle -- + see, for example, the above example of `Min`. The current design does not + fully achieve that aspiration either, but it's far more difficult to find + plausible examples where it fails. +- The first/rest style will probably be more natural to programmers coming + from C++, and if they define APIs in that style, there isn't any plausible + way for them to find out that they're imposing an unwanted constraint on + callers, until someone actually tries to make a call with the wrong shape. + +### Exhaustive function call typechecking + +The current proposal uses merging and splitting to try to align the argument and +parameter lists so that each argument has exactly one parameter than can match +it. We also plan to extend this design to also try the opposite approach, +aligning them so that each parameter has exactly one argument that it can match. +However, it isn't always possible to align arguments and parameters in that way. +For example: + +```carbon +fn F[... each T:! type](x: i32, ... each y: each T); + +fn G(... each z: i32) { + F(... each z, 0 as i16); +} +``` + +Every possible monomorphization of this code would typecheck, but we can't merge +the parameters because they have different types, and we can't merge the +arguments for the same reason. We also can't split the variadic parameter or the +variadic argument, because either of them could be empty. + +The fundamental problem is that, although every possible monomorphization +typechecks, some monomorphizations are structurally different from others. For +example, if `each z` is empty, the monomorphized code converts `0 as i16` to +`i32`, but otherwise `0 as i16` is passed into `F` unmodified. + +We could support such use cases by determining which parameters can potentially +match which arguments, and then typechecking each pair. For example, we could +typecheck the above code by cases: + +- If `each z` is empty, `x: i32` matches `0 as i16` (which typechecks because + `i16` is convertible to `i32`), and `each y: each T` matches nothing. +- If `each z` is not empty, `x: i32` matches its first element (which + typechecks because `i32` is convertible to `i32`), and `each y: each T` + matches the remaining elements of `each z`, followed by `0 as i16` (which + typechecks by binding `each T` to `⟬«i32; ‖each z‖-1», i16⟭`). + +More generally, this approach works by identifying all of the structurally +different ways that arguments could match parameters, typechecking them all in +parallel, and then combining the results with logical "and". + +However, the number of such cases (and hence the cost of typechecking) grows +quadratically, because the number of cases grows with the number of parameters, +and the case analysis has to be repeated for each variadic argument. +[Fast development cycles](/docs/project/goals.md#fast-and-scalable-development) +are a priority for Carbon, so if at all possible we want to avoid situations +where compilation costs grow faster than linearly with the amount of code. + +Furthermore, typechecking a function call doesn't merely need to output a +boolean decision about whether the code typechecks. In order to typecheck the +code that uses the call, and support subsequent phases of compilation, it needs +to also output the type of the call expression, and that can depend on the +values of deduced parameters of the function. + +These more complex outputs make it much harder to combine the results of +typechecking the separate cases. To do this in a general way, we would need to +incorporate some form of case branching directly into the type system. For +example: + +```carbon +fn P[T:! I, ... each U:! J](t: T, ... each u: each U) -> T; + +fn Q[X:! I&J, ... each Y:! I&J](x: X, ... each y: each Y) -> auto { + return P(... each y, x); +} + +fn R[A:! I&J ... each B:! I&J](a: A, ... each b: each B) { + Q(... each b, a); +} +``` + +The typechecker would need to represent the type of `P(... each x, y)` as +something like `(... each Y, X).0`. That subscript `.0` acts as a disguised form +of case branching, because now any subsequent code that depends on +`P(... each y, x)` needs to be typechecked separately for the cases where +`... each Y` is and is not empty. In this case, that even leaks back into the +caller `R` through `Q`'s return type, which compounds the complexity: the type +of `Q(... each b, a)` would need to be something like +`((... each B, A).(1..‖each B‖), (... each B, A).0).0` (where `.(M..N)` is a +hypothetical tuple slice notation). + +All of this may be feasible, but the cost in type system complexity and +performance would be daunting, and the benefits are at best unclear, because we +have not yet found plausible motivating use cases that benefit from this kind of +typechecking. From 47285b62072a842893828f16ae36bcc765195ea9 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 11 Dec 2024 08:33:53 -0800 Subject: [PATCH 2/7] Include a fully-qualified name when stringifying types. (#4657) For example, format the `ImplicitAs` interface as `Core.ImplicitAs` rather than simply `ImplicitAs`. When importing an entity in a namespace, also import a declaration of the enclosing namespace if necessary so that we can determine its name. --- toolchain/check/import_ref.cpp | 46 +++-- .../testdata/array/fail_bound_overflow.carbon | 2 +- .../testdata/array/fail_invalid_type.carbon | 2 +- .../testdata/array/fail_type_mismatch.carbon | 4 +- .../testdata/as/adapter_conversion.carbon | 2 +- .../testdata/as/fail_no_conversion.carbon | 2 +- .../check/testdata/as/fail_not_type.carbon | 2 +- .../basics/fail_non_type_as_type.carbon | 2 +- .../class/adapter/extend_adapt.carbon | 4 +- .../class/adapter/fail_adapt_bad_decl.carbon | 4 +- .../testdata/class/adapter/init_adapt.carbon | 8 +- .../class/cross_package_import.carbon | 2 +- .../testdata/class/fail_base_bad_type.carbon | 10 +- .../class/fail_compound_type_mismatch.carbon | 2 +- .../class/fail_derived_to_base.carbon | 4 +- .../check/testdata/class/fail_self.carbon | 2 +- .../check/testdata/class/generic/adapt.carbon | 4 +- .../check/testdata/class/generic/call.carbon | 2 +- .../testdata/class/generic/import.carbon | 2 +- .../class/generic/member_access.carbon | 2 +- .../testdata/class/generic/stringify.carbon | 6 +- toolchain/check/testdata/class/self.carbon | 2 +- .../check/testdata/const/fail_collapse.carbon | 2 +- toolchain/check/testdata/deduce/array.carbon | 2 +- .../function/call/fail_param_type.carbon | 2 +- .../call/fail_return_type_mismatch.carbon | 2 +- .../impl/fail_impl_bad_interface.carbon | 2 +- .../impl/lookup/no_prelude/import.carbon | 2 +- .../index/fail_array_non_int_indexing.carbon | 2 +- .../index/fail_negative_indexing.carbon | 2 +- .../fail_assoc_const_bad_default.carbon | 2 +- .../testdata/let/compile_time_bindings.carbon | 2 +- .../check/testdata/let/fail_generic.carbon | 4 +- .../namespace/imported_indirect.carbon | 174 +++++++++++++++++- .../builtin/fail_type_mismatch.carbon | 2 +- .../fail_type_mismatch_assignment.carbon | 2 +- .../builtin/fail_type_mismatch_once.carbon | 4 +- .../builtin/fail_unimplemented_op.carbon | 2 +- .../testdata/operators/overloaded/eq.carbon | 8 +- .../operators/overloaded/fail_no_impl.carbon | 8 +- .../overloaded/fail_no_impl_for_arg.carbon | 4 +- .../operators/overloaded/index.carbon | 2 +- .../operators/overloaded/ordered.carbon | 8 +- .../pointer/fail_type_mismatch.carbon | 2 +- .../testdata/return/fail_type_mismatch.carbon | 2 +- .../testdata/struct/fail_type_assign.carbon | 2 +- .../testdata/struct/fail_value_as_type.carbon | 2 +- toolchain/check/testdata/struct/import.carbon | 4 +- .../access/fail_negative_indexing.carbon | 2 +- .../tuple/access/fail_non_int_indexing.carbon | 2 +- .../tuple/fail_element_type_mismatch.carbon | 2 +- .../testdata/tuple/fail_type_assign.carbon | 2 +- .../testdata/tuple/fail_value_as_type.carbon | 2 +- toolchain/check/testdata/tuple/import.carbon | 4 +- .../var/fail_storage_is_literal.carbon | 2 +- .../testdata/where_expr/constraints.carbon | 8 +- .../testdata/where_expr/equal_rewrite.carbon | 18 +- .../testdata/while/fail_bad_condition.carbon | 2 +- toolchain/sem_ir/stringify_type.cpp | 43 +++-- 59 files changed, 332 insertions(+), 121 deletions(-) diff --git a/toolchain/check/import_ref.cpp b/toolchain/check/import_ref.cpp index c585ed8193595..b01b91d9b4493 100644 --- a/toolchain/check/import_ref.cpp +++ b/toolchain/check/import_ref.cpp @@ -1082,18 +1082,7 @@ static auto GetLocalNameScopeId(ImportRefResolver& resolver, } // Get the constant value for the scope. - auto const_id = SemIR::ConstantId::Invalid; - CARBON_KIND_SWITCH(*inst) { - case SemIR::Namespace::Kind: - // If the namespace has already been imported, we can use its constant. - // However, if it hasn't, we use Invalid instead of adding it to the - // work stack. That's expected to be okay when resolving references. - const_id = resolver.local_constant_values_for_import_insts().Get(inst_id); - break; - - default: - const_id = GetLocalConstantId(resolver, inst_id); - } + auto const_id = GetLocalConstantId(resolver, inst_id); if (!const_id.is_valid()) { return SemIR::NameScopeId::Invalid; } @@ -2280,6 +2269,36 @@ static auto TryResolveTypedInst(ImportRefResolver& resolver, .bit_width_id = bit_width_id}); } +static auto TryResolveTypedInst(ImportRefResolver& resolver, + SemIR::Namespace inst, + SemIR::InstId import_inst_id) -> ResolveResult { + const auto& name_scope = + resolver.import_name_scopes().Get(inst.name_scope_id); + auto parent_scope_id = + GetLocalNameScopeId(resolver, name_scope.parent_scope_id()); + + if (resolver.HasNewWork()) { + return ResolveResult::Retry(); + } + + auto namespace_type_id = resolver.local_context().GetSingletonType( + SemIR::NamespaceType::SingletonInstId); + auto namespace_decl = + SemIR::Namespace{.type_id = namespace_type_id, + .name_scope_id = SemIR::NameScopeId::Invalid, + .import_id = SemIR::AbsoluteInstId::Invalid}; + auto inst_id = resolver.local_context().AddPlaceholderInstInNoBlock( + resolver.local_context().MakeImportedLocAndInst( + AddImportIRInst(resolver, import_inst_id), namespace_decl)); + + auto name_id = GetLocalNameId(resolver, name_scope.name_id()); + namespace_decl.name_scope_id = + resolver.local_name_scopes().Add(inst_id, name_id, parent_scope_id); + resolver.local_context().ReplaceInstBeforeConstantUse(inst_id, + namespace_decl); + return {.const_id = resolver.local_constant_values().Get(inst_id)}; +} + static auto TryResolveTypedInst(ImportRefResolver& resolver, SemIR::PointerType inst) -> ResolveResult { CARBON_CHECK(inst.type_id == SemIR::TypeType::SingletonTypeId); @@ -2539,6 +2558,9 @@ static auto TryResolveInstCanonical(ImportRefResolver& resolver, case CARBON_KIND(SemIR::IntType inst): { return TryResolveTypedInst(resolver, inst); } + case CARBON_KIND(SemIR::Namespace inst): { + return TryResolveTypedInst(resolver, inst, inst_id); + } case CARBON_KIND(SemIR::PointerType inst): { return TryResolveTypedInst(resolver, inst); } diff --git a/toolchain/check/testdata/array/fail_bound_overflow.carbon b/toolchain/check/testdata/array/fail_bound_overflow.carbon index c52f0c50ba635..d2ee2ebd969aa 100644 --- a/toolchain/check/testdata/array/fail_bound_overflow.carbon +++ b/toolchain/check/testdata/array/fail_bound_overflow.carbon @@ -17,7 +17,7 @@ var a: [i32; 39999999999999999993]; // CHECK:STDERR: fail_bound_overflow.carbon:[[@LINE+6]]:9: error: cannot implicitly convert from `Core.IntLiteral` to `type` [ImplicitAsConversionFailure] // CHECK:STDERR: var b: [1; 39999999999999999993]; // CHECK:STDERR: ^ -// CHECK:STDERR: fail_bound_overflow.carbon:[[@LINE+3]]:9: note: type `Core.IntLiteral` does not implement interface `ImplicitAs(type)` [MissingImplInMemberAccessNote] +// CHECK:STDERR: fail_bound_overflow.carbon:[[@LINE+3]]:9: note: type `Core.IntLiteral` does not implement interface `Core.ImplicitAs(type)` [MissingImplInMemberAccessNote] // CHECK:STDERR: var b: [1; 39999999999999999993]; // CHECK:STDERR: ^ var b: [1; 39999999999999999993]; diff --git a/toolchain/check/testdata/array/fail_invalid_type.carbon b/toolchain/check/testdata/array/fail_invalid_type.carbon index 0da5e35b5dda6..ebd146e51f09e 100644 --- a/toolchain/check/testdata/array/fail_invalid_type.carbon +++ b/toolchain/check/testdata/array/fail_invalid_type.carbon @@ -11,7 +11,7 @@ // CHECK:STDERR: fail_invalid_type.carbon:[[@LINE+6]]:9: error: cannot implicitly convert from `Core.IntLiteral` to `type` [ImplicitAsConversionFailure] // CHECK:STDERR: var a: [1; 1]; // CHECK:STDERR: ^ -// CHECK:STDERR: fail_invalid_type.carbon:[[@LINE+3]]:9: note: type `Core.IntLiteral` does not implement interface `ImplicitAs(type)` [MissingImplInMemberAccessNote] +// CHECK:STDERR: fail_invalid_type.carbon:[[@LINE+3]]:9: note: type `Core.IntLiteral` does not implement interface `Core.ImplicitAs(type)` [MissingImplInMemberAccessNote] // CHECK:STDERR: var a: [1; 1]; // CHECK:STDERR: ^ var a: [1; 1]; diff --git a/toolchain/check/testdata/array/fail_type_mismatch.carbon b/toolchain/check/testdata/array/fail_type_mismatch.carbon index fd3aefda10a89..15afe0df36d70 100644 --- a/toolchain/check/testdata/array/fail_type_mismatch.carbon +++ b/toolchain/check/testdata/array/fail_type_mismatch.carbon @@ -11,7 +11,7 @@ // CHECK:STDERR: fail_type_mismatch.carbon:[[@LINE+7]]:19: error: cannot implicitly convert from `String` to `i32` [ImplicitAsConversionFailure] // CHECK:STDERR: var a: [i32; 3] = (1, "Hello", "World"); // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~ -// CHECK:STDERR: fail_type_mismatch.carbon:[[@LINE+4]]:19: note: type `String` does not implement interface `ImplicitAs(i32)` [MissingImplInMemberAccessNote] +// CHECK:STDERR: fail_type_mismatch.carbon:[[@LINE+4]]:19: note: type `String` does not implement interface `Core.ImplicitAs(i32)` [MissingImplInMemberAccessNote] // CHECK:STDERR: var a: [i32; 3] = (1, "Hello", "World"); // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~ // CHECK:STDERR: @@ -21,7 +21,7 @@ var t1: (i32, String, String); // CHECK:STDERR: fail_type_mismatch.carbon:[[@LINE+7]]:19: error: cannot implicitly convert from `String` to `i32` [ImplicitAsConversionFailure] // CHECK:STDERR: var b: [i32; 3] = t1; // CHECK:STDERR: ^~ -// CHECK:STDERR: fail_type_mismatch.carbon:[[@LINE+4]]:19: note: type `String` does not implement interface `ImplicitAs(i32)` [MissingImplInMemberAccessNote] +// CHECK:STDERR: fail_type_mismatch.carbon:[[@LINE+4]]:19: note: type `String` does not implement interface `Core.ImplicitAs(i32)` [MissingImplInMemberAccessNote] // CHECK:STDERR: var b: [i32; 3] = t1; // CHECK:STDERR: ^~ // CHECK:STDERR: diff --git a/toolchain/check/testdata/as/adapter_conversion.carbon b/toolchain/check/testdata/as/adapter_conversion.carbon index 2cd4c50777839..f12a256395ea0 100644 --- a/toolchain/check/testdata/as/adapter_conversion.carbon +++ b/toolchain/check/testdata/as/adapter_conversion.carbon @@ -100,7 +100,7 @@ class B { // CHECK:STDERR: fail_adapt_init_from_struct.carbon:[[@LINE+6]]:12: error: cannot convert from `{.x: Core.IntLiteral}` to `B` with `as` [ExplicitAsConversionFailure] // CHECK:STDERR: var b: B = {.x = 1} as B; // CHECK:STDERR: ^~~~~~~~~~~~~ -// CHECK:STDERR: fail_adapt_init_from_struct.carbon:[[@LINE+3]]:12: note: type `{.x: Core.IntLiteral}` does not implement interface `As(B)` [MissingImplInMemberAccessNote] +// CHECK:STDERR: fail_adapt_init_from_struct.carbon:[[@LINE+3]]:12: note: type `{.x: Core.IntLiteral}` does not implement interface `Core.As(B)` [MissingImplInMemberAccessNote] // CHECK:STDERR: var b: B = {.x = 1} as B; // CHECK:STDERR: ^~~~~~~~~~~~~ var b: B = {.x = 1} as B; diff --git a/toolchain/check/testdata/as/fail_no_conversion.carbon b/toolchain/check/testdata/as/fail_no_conversion.carbon index 4892372605c2e..5bfd2ebcdabaf 100644 --- a/toolchain/check/testdata/as/fail_no_conversion.carbon +++ b/toolchain/check/testdata/as/fail_no_conversion.carbon @@ -11,7 +11,7 @@ // CHECK:STDERR: fail_no_conversion.carbon:[[@LINE+6]]:21: error: cannot convert from `Core.IntLiteral` to `(i32, i32)` with `as` [ExplicitAsConversionFailure] // CHECK:STDERR: let n: (i32, i32) = 1 as (i32, i32); // CHECK:STDERR: ^~~~~~~~~~~~~~~ -// CHECK:STDERR: fail_no_conversion.carbon:[[@LINE+3]]:21: note: type `Core.IntLiteral` does not implement interface `As((i32, i32))` [MissingImplInMemberAccessNote] +// CHECK:STDERR: fail_no_conversion.carbon:[[@LINE+3]]:21: note: type `Core.IntLiteral` does not implement interface `Core.As((i32, i32))` [MissingImplInMemberAccessNote] // CHECK:STDERR: let n: (i32, i32) = 1 as (i32, i32); // CHECK:STDERR: ^~~~~~~~~~~~~~~ let n: (i32, i32) = 1 as (i32, i32); diff --git a/toolchain/check/testdata/as/fail_not_type.carbon b/toolchain/check/testdata/as/fail_not_type.carbon index 7d8c42261c6c5..bc2c00c5ee85c 100644 --- a/toolchain/check/testdata/as/fail_not_type.carbon +++ b/toolchain/check/testdata/as/fail_not_type.carbon @@ -11,7 +11,7 @@ // CHECK:STDERR: fail_not_type.carbon:[[@LINE+6]]:19: error: cannot implicitly convert from `Core.IntLiteral` to `type` [ImplicitAsConversionFailure] // CHECK:STDERR: let n: i32 = 1 as 2; // CHECK:STDERR: ^ -// CHECK:STDERR: fail_not_type.carbon:[[@LINE+3]]:19: note: type `Core.IntLiteral` does not implement interface `ImplicitAs(type)` [MissingImplInMemberAccessNote] +// CHECK:STDERR: fail_not_type.carbon:[[@LINE+3]]:19: note: type `Core.IntLiteral` does not implement interface `Core.ImplicitAs(type)` [MissingImplInMemberAccessNote] // CHECK:STDERR: let n: i32 = 1 as 2; // CHECK:STDERR: ^ let n: i32 = 1 as 2; diff --git a/toolchain/check/testdata/basics/fail_non_type_as_type.carbon b/toolchain/check/testdata/basics/fail_non_type_as_type.carbon index 8b69c739b9dfc..6e2db17f5a8c8 100644 --- a/toolchain/check/testdata/basics/fail_non_type_as_type.carbon +++ b/toolchain/check/testdata/basics/fail_non_type_as_type.carbon @@ -11,7 +11,7 @@ // CHECK:STDERR: fail_non_type_as_type.carbon:[[@LINE+6]]:1: error: cannot implicitly convert from `Core.IntLiteral` to `type` [ImplicitAsConversionFailure] // CHECK:STDERR: var x: type = 42; // CHECK:STDERR: ^~~~~~~~~~~~~~~~~ -// CHECK:STDERR: fail_non_type_as_type.carbon:[[@LINE+3]]:1: note: type `Core.IntLiteral` does not implement interface `ImplicitAs(type)` [MissingImplInMemberAccessNote] +// CHECK:STDERR: fail_non_type_as_type.carbon:[[@LINE+3]]:1: note: type `Core.IntLiteral` does not implement interface `Core.ImplicitAs(type)` [MissingImplInMemberAccessNote] // CHECK:STDERR: var x: type = 42; // CHECK:STDERR: ^~~~~~~~~~~~~~~~~ var x: type = 42; diff --git a/toolchain/check/testdata/class/adapter/extend_adapt.carbon b/toolchain/check/testdata/class/adapter/extend_adapt.carbon index b85fcf75739a3..3959adb39ad26 100644 --- a/toolchain/check/testdata/class/adapter/extend_adapt.carbon +++ b/toolchain/check/testdata/class/adapter/extend_adapt.carbon @@ -51,7 +51,7 @@ fn F(a: SomeClassAdapter) { // CHECK:STDERR: fail_todo_method_access.carbon:[[@LINE+10]]:3: error: cannot implicitly convert from `SomeClassAdapter` to `SomeClass` [ImplicitAsConversionFailure] // CHECK:STDERR: a.F(); // CHECK:STDERR: ^ - // CHECK:STDERR: fail_todo_method_access.carbon:[[@LINE+7]]:3: note: type `SomeClassAdapter` does not implement interface `ImplicitAs(SomeClass)` [MissingImplInMemberAccessNote] + // CHECK:STDERR: fail_todo_method_access.carbon:[[@LINE+7]]:3: note: type `SomeClassAdapter` does not implement interface `Core.ImplicitAs(SomeClass)` [MissingImplInMemberAccessNote] // CHECK:STDERR: a.F(); // CHECK:STDERR: ^ // CHECK:STDERR: fail_todo_method_access.carbon:[[@LINE-14]]:8: note: initializing function parameter [InCallToFunctionParam] @@ -78,7 +78,7 @@ fn F(a: SomeClassAdapter) -> i32 { // CHECK:STDERR: fail_todo_field_access.carbon:[[@LINE+7]]:10: error: cannot implicitly convert from `SomeClassAdapter` to `SomeClass` [ImplicitAsConversionFailure] // CHECK:STDERR: return a.b; // CHECK:STDERR: ^~~ - // CHECK:STDERR: fail_todo_field_access.carbon:[[@LINE+4]]:10: note: type `SomeClassAdapter` does not implement interface `ImplicitAs(SomeClass)` [MissingImplInMemberAccessNote] + // CHECK:STDERR: fail_todo_field_access.carbon:[[@LINE+4]]:10: note: type `SomeClassAdapter` does not implement interface `Core.ImplicitAs(SomeClass)` [MissingImplInMemberAccessNote] // CHECK:STDERR: return a.b; // CHECK:STDERR: ^~~ // CHECK:STDERR: diff --git a/toolchain/check/testdata/class/adapter/fail_adapt_bad_decl.carbon b/toolchain/check/testdata/class/adapter/fail_adapt_bad_decl.carbon index 0476c356edac9..f0fa494924792 100644 --- a/toolchain/check/testdata/class/adapter/fail_adapt_bad_decl.carbon +++ b/toolchain/check/testdata/class/adapter/fail_adapt_bad_decl.carbon @@ -16,7 +16,7 @@ class Bad { // CHECK:STDERR: fail_not_type.carbon:[[@LINE+7]]:3: error: cannot implicitly convert from `Core.IntLiteral` to `type` [ImplicitAsConversionFailure] // CHECK:STDERR: adapt 100; // CHECK:STDERR: ^~~~~~~~~~ - // CHECK:STDERR: fail_not_type.carbon:[[@LINE+4]]:3: note: type `Core.IntLiteral` does not implement interface `ImplicitAs(type)` [MissingImplInMemberAccessNote] + // CHECK:STDERR: fail_not_type.carbon:[[@LINE+4]]:3: note: type `Core.IntLiteral` does not implement interface `Core.ImplicitAs(type)` [MissingImplInMemberAccessNote] // CHECK:STDERR: adapt 100; // CHECK:STDERR: ^~~~~~~~~~ // CHECK:STDERR: @@ -37,7 +37,7 @@ class Bad { // CHECK:STDERR: fail_extend_not_type.carbon:[[@LINE+7]]:3: error: cannot implicitly convert from `Core.IntLiteral` to `type` [ImplicitAsConversionFailure] // CHECK:STDERR: extend adapt 100; // CHECK:STDERR: ^~~~~~~~~~~~~~~~~ - // CHECK:STDERR: fail_extend_not_type.carbon:[[@LINE+4]]:3: note: type `Core.IntLiteral` does not implement interface `ImplicitAs(type)` [MissingImplInMemberAccessNote] + // CHECK:STDERR: fail_extend_not_type.carbon:[[@LINE+4]]:3: note: type `Core.IntLiteral` does not implement interface `Core.ImplicitAs(type)` [MissingImplInMemberAccessNote] // CHECK:STDERR: extend adapt 100; // CHECK:STDERR: ^~~~~~~~~~~~~~~~~ // CHECK:STDERR: diff --git a/toolchain/check/testdata/class/adapter/init_adapt.carbon b/toolchain/check/testdata/class/adapter/init_adapt.carbon index 3f29fae62ba26..7ab5d6abecf3a 100644 --- a/toolchain/check/testdata/class/adapter/init_adapt.carbon +++ b/toolchain/check/testdata/class/adapter/init_adapt.carbon @@ -55,7 +55,7 @@ let a: C = {.a = 1, .b = 2}; // CHECK:STDERR: fail_not_implicit.carbon:[[@LINE+7]]:1: error: cannot implicitly convert from `C` to `AdaptC` [ImplicitAsConversionFailure] // CHECK:STDERR: let b: AdaptC = a; // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~ -// CHECK:STDERR: fail_not_implicit.carbon:[[@LINE+4]]:1: note: type `C` does not implement interface `ImplicitAs(AdaptC)` [MissingImplInMemberAccessNote] +// CHECK:STDERR: fail_not_implicit.carbon:[[@LINE+4]]:1: note: type `C` does not implement interface `Core.ImplicitAs(AdaptC)` [MissingImplInMemberAccessNote] // CHECK:STDERR: let b: AdaptC = a; // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~ // CHECK:STDERR: @@ -64,7 +64,7 @@ let b: AdaptC = a; // CHECK:STDERR: fail_not_implicit.carbon:[[@LINE+7]]:1: error: cannot implicitly convert from `AdaptC` to `C` [ImplicitAsConversionFailure] // CHECK:STDERR: let c: C = b; // CHECK:STDERR: ^~~~~~~~~~~~~ -// CHECK:STDERR: fail_not_implicit.carbon:[[@LINE+4]]:1: note: type `AdaptC` does not implement interface `ImplicitAs(C)` [MissingImplInMemberAccessNote] +// CHECK:STDERR: fail_not_implicit.carbon:[[@LINE+4]]:1: note: type `AdaptC` does not implement interface `Core.ImplicitAs(C)` [MissingImplInMemberAccessNote] // CHECK:STDERR: let c: C = b; // CHECK:STDERR: ^~~~~~~~~~~~~ // CHECK:STDERR: @@ -77,7 +77,7 @@ fn MakeAdaptC() -> AdaptC; // CHECK:STDERR: fail_not_implicit.carbon:[[@LINE+7]]:1: error: cannot implicitly convert from `C` to `AdaptC` [ImplicitAsConversionFailure] // CHECK:STDERR: var d: AdaptC = MakeC(); // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~ -// CHECK:STDERR: fail_not_implicit.carbon:[[@LINE+4]]:1: note: type `C` does not implement interface `ImplicitAs(AdaptC)` [MissingImplInMemberAccessNote] +// CHECK:STDERR: fail_not_implicit.carbon:[[@LINE+4]]:1: note: type `C` does not implement interface `Core.ImplicitAs(AdaptC)` [MissingImplInMemberAccessNote] // CHECK:STDERR: var d: AdaptC = MakeC(); // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~ // CHECK:STDERR: @@ -86,7 +86,7 @@ var d: AdaptC = MakeC(); // CHECK:STDERR: fail_not_implicit.carbon:[[@LINE+6]]:1: error: cannot implicitly convert from `AdaptC` to `C` [ImplicitAsConversionFailure] // CHECK:STDERR: var e: C = MakeAdaptC(); // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~ -// CHECK:STDERR: fail_not_implicit.carbon:[[@LINE+3]]:1: note: type `AdaptC` does not implement interface `ImplicitAs(C)` [MissingImplInMemberAccessNote] +// CHECK:STDERR: fail_not_implicit.carbon:[[@LINE+3]]:1: note: type `AdaptC` does not implement interface `Core.ImplicitAs(C)` [MissingImplInMemberAccessNote] // CHECK:STDERR: var e: C = MakeAdaptC(); // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~ var e: C = MakeAdaptC(); diff --git a/toolchain/check/testdata/class/cross_package_import.carbon b/toolchain/check/testdata/class/cross_package_import.carbon index 79eff108ca137..d2fbf9b2d7345 100644 --- a/toolchain/check/testdata/class/cross_package_import.carbon +++ b/toolchain/check/testdata/class/cross_package_import.carbon @@ -48,7 +48,7 @@ library "[[@TEST_NAME]]"; import Other library "other_extern"; -// CHECK:STDERR: fail_extern.carbon:[[@LINE+8]]:8: error: variable has incomplete type `C` [IncompleteTypeInVarDecl] +// CHECK:STDERR: fail_extern.carbon:[[@LINE+8]]:8: error: variable has incomplete type `Other.C` [IncompleteTypeInVarDecl] // CHECK:STDERR: var c: Other.C = {}; // CHECK:STDERR: ^~~~~~~ // CHECK:STDERR: fail_extern.carbon:[[@LINE-5]]:1: in import [InImport] diff --git a/toolchain/check/testdata/class/fail_base_bad_type.carbon b/toolchain/check/testdata/class/fail_base_bad_type.carbon index e4c2546563c52..fe003970a5f36 100644 --- a/toolchain/check/testdata/class/fail_base_bad_type.carbon +++ b/toolchain/check/testdata/class/fail_base_bad_type.carbon @@ -31,7 +31,7 @@ class DeriveFromNonType { // CHECK:STDERR: fail_derive_from_non_type.carbon:[[@LINE+7]]:16: error: cannot implicitly convert from `Core.IntLiteral` to `type` [ImplicitAsConversionFailure] // CHECK:STDERR: extend base: 32; // CHECK:STDERR: ^~ - // CHECK:STDERR: fail_derive_from_non_type.carbon:[[@LINE+4]]:16: note: type `Core.IntLiteral` does not implement interface `ImplicitAs(type)` [MissingImplInMemberAccessNote] + // CHECK:STDERR: fail_derive_from_non_type.carbon:[[@LINE+4]]:16: note: type `Core.IntLiteral` does not implement interface `Core.ImplicitAs(type)` [MissingImplInMemberAccessNote] // CHECK:STDERR: extend base: 32; // CHECK:STDERR: ^~ // CHECK:STDERR: @@ -57,7 +57,7 @@ class DeriveFromi32 { // CHECK:STDERR: fail_derive_from_i32.carbon:[[@LINE+7]]:53: error: cannot implicitly convert from `DeriveFromi32*` to `i32*` [ImplicitAsConversionFailure] // CHECK:STDERR: fn ConvertToBadBasei32(p: DeriveFromi32*) -> i32* { return p; } // CHECK:STDERR: ^~~~~~~~~ -// CHECK:STDERR: fail_derive_from_i32.carbon:[[@LINE+4]]:53: note: type `DeriveFromi32*` does not implement interface `ImplicitAs(i32*)` [MissingImplInMemberAccessNote] +// CHECK:STDERR: fail_derive_from_i32.carbon:[[@LINE+4]]:53: note: type `DeriveFromi32*` does not implement interface `Core.ImplicitAs(i32*)` [MissingImplInMemberAccessNote] // CHECK:STDERR: fn ConvertToBadBasei32(p: DeriveFromi32*) -> i32* { return p; } // CHECK:STDERR: ^~~~~~~~~ // CHECK:STDERR: @@ -82,7 +82,7 @@ class DeriveFromTuple { // CHECK:STDERR: fail_derive_from_tuple.carbon:[[@LINE+7]]:61: error: cannot implicitly convert from `DeriveFromTuple*` to `(Base,)*` [ImplicitAsConversionFailure] // CHECK:STDERR: fn ConvertToBadBaseTuple(p: DeriveFromTuple*) -> (Base,)* { return p; } // CHECK:STDERR: ^~~~~~~~~ -// CHECK:STDERR: fail_derive_from_tuple.carbon:[[@LINE+4]]:61: note: type `DeriveFromTuple*` does not implement interface `ImplicitAs((Base,)*)` [MissingImplInMemberAccessNote] +// CHECK:STDERR: fail_derive_from_tuple.carbon:[[@LINE+4]]:61: note: type `DeriveFromTuple*` does not implement interface `Core.ImplicitAs((Base,)*)` [MissingImplInMemberAccessNote] // CHECK:STDERR: fn ConvertToBadBaseTuple(p: DeriveFromTuple*) -> (Base,)* { return p; } // CHECK:STDERR: ^~~~~~~~~ // CHECK:STDERR: @@ -107,7 +107,7 @@ class DeriveFromStruct { // CHECK:STDERR: fail_derive_from_struct.carbon:[[@LINE+7]]:74: error: cannot implicitly convert from `DeriveFromStruct*` to `{.a: i32, .b: i32}*` [ImplicitAsConversionFailure] // CHECK:STDERR: fn ConvertToBadBaseStruct(p: DeriveFromStruct*) -> {.a: i32, .b: i32}* { return p; } // CHECK:STDERR: ^~~~~~~~~ -// CHECK:STDERR: fail_derive_from_struct.carbon:[[@LINE+4]]:74: note: type `DeriveFromStruct*` does not implement interface `ImplicitAs({.a: i32, .b: i32}*)` [MissingImplInMemberAccessNote] +// CHECK:STDERR: fail_derive_from_struct.carbon:[[@LINE+4]]:74: note: type `DeriveFromStruct*` does not implement interface `Core.ImplicitAs({.a: i32, .b: i32}*)` [MissingImplInMemberAccessNote] // CHECK:STDERR: fn ConvertToBadBaseStruct(p: DeriveFromStruct*) -> {.a: i32, .b: i32}* { return p; } // CHECK:STDERR: ^~~~~~~~~ // CHECK:STDERR: @@ -140,7 +140,7 @@ class DeriveFromIncomplete { // CHECK:STDERR: fail_derive_from_incomplete.carbon:[[@LINE+7]]:74: error: cannot implicitly convert from `DeriveFromIncomplete*` to `Incomplete*` [ImplicitAsConversionFailure] // CHECK:STDERR: fn ConvertToBadBaseIncomplete(p: DeriveFromIncomplete*) -> Incomplete* { return p; } // CHECK:STDERR: ^~~~~~~~~ -// CHECK:STDERR: fail_derive_from_incomplete.carbon:[[@LINE+4]]:74: note: type `DeriveFromIncomplete*` does not implement interface `ImplicitAs(Incomplete*)` [MissingImplInMemberAccessNote] +// CHECK:STDERR: fail_derive_from_incomplete.carbon:[[@LINE+4]]:74: note: type `DeriveFromIncomplete*` does not implement interface `Core.ImplicitAs(Incomplete*)` [MissingImplInMemberAccessNote] // CHECK:STDERR: fn ConvertToBadBaseIncomplete(p: DeriveFromIncomplete*) -> Incomplete* { return p; } // CHECK:STDERR: ^~~~~~~~~ // CHECK:STDERR: diff --git a/toolchain/check/testdata/class/fail_compound_type_mismatch.carbon b/toolchain/check/testdata/class/fail_compound_type_mismatch.carbon index 923dc78f4c202..80224c35fc376 100644 --- a/toolchain/check/testdata/class/fail_compound_type_mismatch.carbon +++ b/toolchain/check/testdata/class/fail_compound_type_mismatch.carbon @@ -20,7 +20,7 @@ fn AccessBInA(a: A) -> i32 { // CHECK:STDERR: fail_compound_type_mismatch.carbon:[[@LINE+6]]:10: error: cannot implicitly convert from `A` to `B` [ImplicitAsConversionFailure] // CHECK:STDERR: return a.(B.b); // CHECK:STDERR: ^~~~~~~ - // CHECK:STDERR: fail_compound_type_mismatch.carbon:[[@LINE+3]]:10: note: type `A` does not implement interface `ImplicitAs(B)` [MissingImplInMemberAccessNote] + // CHECK:STDERR: fail_compound_type_mismatch.carbon:[[@LINE+3]]:10: note: type `A` does not implement interface `Core.ImplicitAs(B)` [MissingImplInMemberAccessNote] // CHECK:STDERR: return a.(B.b); // CHECK:STDERR: ^~~~~~~ return a.(B.b); diff --git a/toolchain/check/testdata/class/fail_derived_to_base.carbon b/toolchain/check/testdata/class/fail_derived_to_base.carbon index 26c99b83a5ffc..2d9ef7c6f38b4 100644 --- a/toolchain/check/testdata/class/fail_derived_to_base.carbon +++ b/toolchain/check/testdata/class/fail_derived_to_base.carbon @@ -24,7 +24,7 @@ class B2 { // CHECK:STDERR: fail_derived_to_base.carbon:[[@LINE+7]]:38: error: cannot implicitly convert from `B2*` to `A1*` [ImplicitAsConversionFailure] // CHECK:STDERR: fn ConvertUnrelated(p: B2*) -> A1* { return p; } // CHECK:STDERR: ^~~~~~~~~ -// CHECK:STDERR: fail_derived_to_base.carbon:[[@LINE+4]]:38: note: type `B2*` does not implement interface `ImplicitAs(A1*)` [MissingImplInMemberAccessNote] +// CHECK:STDERR: fail_derived_to_base.carbon:[[@LINE+4]]:38: note: type `B2*` does not implement interface `Core.ImplicitAs(A1*)` [MissingImplInMemberAccessNote] // CHECK:STDERR: fn ConvertUnrelated(p: B2*) -> A1* { return p; } // CHECK:STDERR: ^~~~~~~~~ // CHECK:STDERR: @@ -35,7 +35,7 @@ class Incomplete; // CHECK:STDERR: fail_derived_to_base.carbon:[[@LINE+6]]:47: error: cannot implicitly convert from `Incomplete*` to `A2*` [ImplicitAsConversionFailure] // CHECK:STDERR: fn ConvertIncomplete(p: Incomplete*) -> A2* { return p; } // CHECK:STDERR: ^~~~~~~~~ -// CHECK:STDERR: fail_derived_to_base.carbon:[[@LINE+3]]:47: note: type `Incomplete*` does not implement interface `ImplicitAs(A2*)` [MissingImplInMemberAccessNote] +// CHECK:STDERR: fail_derived_to_base.carbon:[[@LINE+3]]:47: note: type `Incomplete*` does not implement interface `Core.ImplicitAs(A2*)` [MissingImplInMemberAccessNote] // CHECK:STDERR: fn ConvertIncomplete(p: Incomplete*) -> A2* { return p; } // CHECK:STDERR: ^~~~~~~~~ fn ConvertIncomplete(p: Incomplete*) -> A2* { return p; } diff --git a/toolchain/check/testdata/class/fail_self.carbon b/toolchain/check/testdata/class/fail_self.carbon index 7db7065faf566..f811759befd35 100644 --- a/toolchain/check/testdata/class/fail_self.carbon +++ b/toolchain/check/testdata/class/fail_self.carbon @@ -46,7 +46,7 @@ fn CallWrongSelf(ws: WrongSelf) { // CHECK:STDERR: fail_self.carbon:[[@LINE+9]]:3: error: cannot implicitly convert from `WrongSelf` to `Class` [ImplicitAsConversionFailure] // CHECK:STDERR: ws.F(); // CHECK:STDERR: ^~ - // CHECK:STDERR: fail_self.carbon:[[@LINE+6]]:3: note: type `WrongSelf` does not implement interface `ImplicitAs(Class)` [MissingImplInMemberAccessNote] + // CHECK:STDERR: fail_self.carbon:[[@LINE+6]]:3: note: type `WrongSelf` does not implement interface `Core.ImplicitAs(Class)` [MissingImplInMemberAccessNote] // CHECK:STDERR: ws.F(); // CHECK:STDERR: ^~ // CHECK:STDERR: fail_self.carbon:[[@LINE-10]]:8: note: initializing function parameter [InCallToFunctionParam] diff --git a/toolchain/check/testdata/class/generic/adapt.carbon b/toolchain/check/testdata/class/generic/adapt.carbon index 222c8b49d7871..031f2b22e1f70 100644 --- a/toolchain/check/testdata/class/generic/adapt.carbon +++ b/toolchain/check/testdata/class/generic/adapt.carbon @@ -51,7 +51,7 @@ fn Access(a: Adapter) -> i32 { // CHECK:STDERR: fail_todo_extend_adapt_specific_type.carbon:[[@LINE+7]]:10: error: cannot implicitly convert from `Adapter` to `C(i32)` [ImplicitAsConversionFailure] // CHECK:STDERR: return a.x; // CHECK:STDERR: ^~~ - // CHECK:STDERR: fail_todo_extend_adapt_specific_type.carbon:[[@LINE+4]]:10: note: type `Adapter` does not implement interface `ImplicitAs(C(i32))` [MissingImplInMemberAccessNote] + // CHECK:STDERR: fail_todo_extend_adapt_specific_type.carbon:[[@LINE+4]]:10: note: type `Adapter` does not implement interface `Core.ImplicitAs(C(i32))` [MissingImplInMemberAccessNote] // CHECK:STDERR: return a.x; // CHECK:STDERR: ^~~ // CHECK:STDERR: @@ -84,7 +84,7 @@ fn ImportedAccess(a: Adapter) -> i32 { // CHECK:STDERR: fail_todo_import_extend_adapt_specific_type.carbon:[[@LINE+6]]:10: error: cannot implicitly convert from `Adapter` to `C(i32)` [ImplicitAsConversionFailure] // CHECK:STDERR: return a.x; // CHECK:STDERR: ^~~ - // CHECK:STDERR: fail_todo_import_extend_adapt_specific_type.carbon:[[@LINE+3]]:10: note: type `Adapter` does not implement interface `ImplicitAs(C(i32))` [MissingImplInMemberAccessNote] + // CHECK:STDERR: fail_todo_import_extend_adapt_specific_type.carbon:[[@LINE+3]]:10: note: type `Adapter` does not implement interface `Core.ImplicitAs(C(i32))` [MissingImplInMemberAccessNote] // CHECK:STDERR: return a.x; // CHECK:STDERR: ^~~ return a.x; diff --git a/toolchain/check/testdata/class/generic/call.carbon b/toolchain/check/testdata/class/generic/call.carbon index af27c369f0486..1f3613066ef56 100644 --- a/toolchain/check/testdata/class/generic/call.carbon +++ b/toolchain/check/testdata/class/generic/call.carbon @@ -58,7 +58,7 @@ class Class(T:! type, N:! i32) {} // CHECK:STDERR: fail_no_conversion.carbon:[[@LINE+9]]:8: error: cannot implicitly convert from `Core.IntLiteral` to `type` [ImplicitAsConversionFailure] // CHECK:STDERR: var a: Class(5, i32*); // CHECK:STDERR: ^~~~~~~~~~~~~~ -// CHECK:STDERR: fail_no_conversion.carbon:[[@LINE+6]]:8: note: type `Core.IntLiteral` does not implement interface `ImplicitAs(type)` [MissingImplInMemberAccessNote] +// CHECK:STDERR: fail_no_conversion.carbon:[[@LINE+6]]:8: note: type `Core.IntLiteral` does not implement interface `Core.ImplicitAs(type)` [MissingImplInMemberAccessNote] // CHECK:STDERR: var a: Class(5, i32*); // CHECK:STDERR: ^~~~~~~~~~~~~~ // CHECK:STDERR: fail_no_conversion.carbon:[[@LINE-8]]:1: note: while deducing parameters of generic declared here [DeductionGenericHere] diff --git a/toolchain/check/testdata/class/generic/import.carbon b/toolchain/check/testdata/class/generic/import.carbon index 9424168a6c5ac..27d0e3273086b 100644 --- a/toolchain/check/testdata/class/generic/import.carbon +++ b/toolchain/check/testdata/class/generic/import.carbon @@ -58,7 +58,7 @@ fn Use() { // CHECK:STDERR: fail_generic_arg_mismatch.carbon:[[@LINE+7]]:3: error: cannot implicitly convert from `CompleteClass(i32)` to `CompleteClass(i32*)` [ImplicitAsConversionFailure] // CHECK:STDERR: var v: CompleteClass(i32*) = F(); // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // CHECK:STDERR: fail_generic_arg_mismatch.carbon:[[@LINE+4]]:3: note: type `CompleteClass(i32)` does not implement interface `ImplicitAs(CompleteClass(i32*))` [MissingImplInMemberAccessNote] + // CHECK:STDERR: fail_generic_arg_mismatch.carbon:[[@LINE+4]]:3: note: type `CompleteClass(i32)` does not implement interface `Core.ImplicitAs(CompleteClass(i32*))` [MissingImplInMemberAccessNote] // CHECK:STDERR: var v: CompleteClass(i32*) = F(); // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // CHECK:STDERR: diff --git a/toolchain/check/testdata/class/generic/member_access.carbon b/toolchain/check/testdata/class/generic/member_access.carbon index c02c4e65b6e25..34d75357ab9a8 100644 --- a/toolchain/check/testdata/class/generic/member_access.carbon +++ b/toolchain/check/testdata/class/generic/member_access.carbon @@ -42,7 +42,7 @@ fn StaticMemberFunctionCall(T:! type) -> Class(T) { // CHECK:STDERR: fail_todo_static_member_fn_call.carbon:[[@LINE+6]]:3: error: cannot implicitly convert from `Class(T)` to `Class(T)` [ImplicitAsConversionFailure] // CHECK:STDERR: return Class(T).Make(); // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~ - // CHECK:STDERR: fail_todo_static_member_fn_call.carbon:[[@LINE+3]]:3: note: type `Class(T)` does not implement interface `ImplicitAs(Class(T))` [MissingImplInMemberAccessNote] + // CHECK:STDERR: fail_todo_static_member_fn_call.carbon:[[@LINE+3]]:3: note: type `Class(T)` does not implement interface `Core.ImplicitAs(Class(T))` [MissingImplInMemberAccessNote] // CHECK:STDERR: return Class(T).Make(); // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~ return Class(T).Make(); diff --git a/toolchain/check/testdata/class/generic/stringify.carbon b/toolchain/check/testdata/class/generic/stringify.carbon index 64720dd14b1cd..382f305d4ee72 100644 --- a/toolchain/check/testdata/class/generic/stringify.carbon +++ b/toolchain/check/testdata/class/generic/stringify.carbon @@ -19,7 +19,7 @@ var v: NoParams; // CHECK:STDERR: fail_empty_params.carbon:[[@LINE+7]]:1: error: cannot implicitly convert from `NoParams` to `EmptyParams()` [ImplicitAsConversionFailure] // CHECK:STDERR: var w: EmptyParams() = v; // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~ -// CHECK:STDERR: fail_empty_params.carbon:[[@LINE+4]]:1: note: type `NoParams` does not implement interface `ImplicitAs(EmptyParams())` [MissingImplInMemberAccessNote] +// CHECK:STDERR: fail_empty_params.carbon:[[@LINE+4]]:1: note: type `NoParams` does not implement interface `Core.ImplicitAs(EmptyParams())` [MissingImplInMemberAccessNote] // CHECK:STDERR: var w: EmptyParams() = v; // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~ // CHECK:STDERR: @@ -40,7 +40,7 @@ var v: Outer({}*); // CHECK:STDERR: fail_nested.carbon:[[@LINE+7]]:1: error: cannot implicitly convert from `Outer({}*)` to `Inner({.a: i32}*)` [ImplicitAsConversionFailure] // CHECK:STDERR: var w: Outer({}*).Inner({.a: i32}*) = v; // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// CHECK:STDERR: fail_nested.carbon:[[@LINE+4]]:1: note: type `Outer({}*)` does not implement interface `ImplicitAs(Inner({.a: i32}*))` [MissingImplInMemberAccessNote] +// CHECK:STDERR: fail_nested.carbon:[[@LINE+4]]:1: note: type `Outer({}*)` does not implement interface `Core.ImplicitAs(Inner({.a: i32}*))` [MissingImplInMemberAccessNote] // CHECK:STDERR: var w: Outer({}*).Inner({.a: i32}*) = v; // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // CHECK:STDERR: @@ -55,7 +55,7 @@ class C(N:! i32) {} // CHECK:STDERR: fail_int_value.carbon:[[@LINE+6]]:1: error: cannot implicitly convert from `()` to `C(123)` [ImplicitAsConversionFailure] // CHECK:STDERR: var v: C(123) = (); // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~ -// CHECK:STDERR: fail_int_value.carbon:[[@LINE+3]]:1: note: type `()` does not implement interface `ImplicitAs(C(123))` [MissingImplInMemberAccessNote] +// CHECK:STDERR: fail_int_value.carbon:[[@LINE+3]]:1: note: type `()` does not implement interface `Core.ImplicitAs(C(123))` [MissingImplInMemberAccessNote] // CHECK:STDERR: var v: C(123) = (); // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~ var v: C(123) = (); diff --git a/toolchain/check/testdata/class/self.carbon b/toolchain/check/testdata/class/self.carbon index 07e1effb44501..4ba9a050fa960 100644 --- a/toolchain/check/testdata/class/self.carbon +++ b/toolchain/check/testdata/class/self.carbon @@ -35,7 +35,7 @@ class Class { // CHECK:STDERR: fail_return_self_value.carbon:[[@LINE+6]]:25: error: cannot implicitly convert from `Class` to `type` [ImplicitAsConversionFailure] // CHECK:STDERR: fn F[self: Self]() -> self; // CHECK:STDERR: ^~~~ - // CHECK:STDERR: fail_return_self_value.carbon:[[@LINE+3]]:25: note: type `Class` does not implement interface `ImplicitAs(type)` [MissingImplInMemberAccessNote] + // CHECK:STDERR: fail_return_self_value.carbon:[[@LINE+3]]:25: note: type `Class` does not implement interface `Core.ImplicitAs(type)` [MissingImplInMemberAccessNote] // CHECK:STDERR: fn F[self: Self]() -> self; // CHECK:STDERR: ^~~~ fn F[self: Self]() -> self; diff --git a/toolchain/check/testdata/const/fail_collapse.carbon b/toolchain/check/testdata/const/fail_collapse.carbon index d7b1e1776a5d5..9cffd7c0824de 100644 --- a/toolchain/check/testdata/const/fail_collapse.carbon +++ b/toolchain/check/testdata/const/fail_collapse.carbon @@ -16,7 +16,7 @@ fn G(p: const (const i32)**) -> i32** { // CHECK:STDERR: fail_collapse.carbon:[[@LINE+6]]:3: error: cannot implicitly convert from `const i32**` to `i32**` [ImplicitAsConversionFailure] // CHECK:STDERR: return p; // CHECK:STDERR: ^~~~~~~~~ - // CHECK:STDERR: fail_collapse.carbon:[[@LINE+3]]:3: note: type `const i32**` does not implement interface `ImplicitAs(i32**)` [MissingImplInMemberAccessNote] + // CHECK:STDERR: fail_collapse.carbon:[[@LINE+3]]:3: note: type `const i32**` does not implement interface `Core.ImplicitAs(i32**)` [MissingImplInMemberAccessNote] // CHECK:STDERR: return p; // CHECK:STDERR: ^~~~~~~~~ return p; diff --git a/toolchain/check/testdata/deduce/array.carbon b/toolchain/check/testdata/deduce/array.carbon index 63ae9cf40346d..598049b5ebcdc 100644 --- a/toolchain/check/testdata/deduce/array.carbon +++ b/toolchain/check/testdata/deduce/array.carbon @@ -69,7 +69,7 @@ fn G() -> C { // CHECK:STDERR: fail_bound_mismatch.carbon:[[@LINE+10]]:12: error: cannot implicitly convert from `[C; 3]` to `[C; 2]` [ImplicitAsConversionFailure] // CHECK:STDERR: return F(a); // CHECK:STDERR: ^ - // CHECK:STDERR: fail_bound_mismatch.carbon:[[@LINE+7]]:12: note: type `[C; 3]` does not implement interface `ImplicitAs([C; 2])` [MissingImplInMemberAccessNote] + // CHECK:STDERR: fail_bound_mismatch.carbon:[[@LINE+7]]:12: note: type `[C; 3]` does not implement interface `Core.ImplicitAs([C; 2])` [MissingImplInMemberAccessNote] // CHECK:STDERR: return F(a); // CHECK:STDERR: ^ // CHECK:STDERR: fail_bound_mismatch.carbon:[[@LINE-11]]:16: note: initializing function parameter [InCallToFunctionParam] diff --git a/toolchain/check/testdata/function/call/fail_param_type.carbon b/toolchain/check/testdata/function/call/fail_param_type.carbon index 8ff54f984fd4f..d8c62c916c500 100644 --- a/toolchain/check/testdata/function/call/fail_param_type.carbon +++ b/toolchain/check/testdata/function/call/fail_param_type.carbon @@ -14,7 +14,7 @@ fn F() { // CHECK:STDERR: fail_param_type.carbon:[[@LINE+9]]:5: error: cannot implicitly convert from `f64` to `i32` [ImplicitAsConversionFailure] // CHECK:STDERR: G(1.0); // CHECK:STDERR: ^~~ - // CHECK:STDERR: fail_param_type.carbon:[[@LINE+6]]:5: note: type `f64` does not implement interface `ImplicitAs(i32)` [MissingImplInMemberAccessNote] + // CHECK:STDERR: fail_param_type.carbon:[[@LINE+6]]:5: note: type `f64` does not implement interface `Core.ImplicitAs(i32)` [MissingImplInMemberAccessNote] // CHECK:STDERR: G(1.0); // CHECK:STDERR: ^~~ // CHECK:STDERR: fail_param_type.carbon:[[@LINE-9]]:6: note: initializing function parameter [InCallToFunctionParam] diff --git a/toolchain/check/testdata/function/call/fail_return_type_mismatch.carbon b/toolchain/check/testdata/function/call/fail_return_type_mismatch.carbon index a1ec025bcbdf3..57f653b1e173c 100644 --- a/toolchain/check/testdata/function/call/fail_return_type_mismatch.carbon +++ b/toolchain/check/testdata/function/call/fail_return_type_mismatch.carbon @@ -14,7 +14,7 @@ fn Run() { // CHECK:STDERR: fail_return_type_mismatch.carbon:[[@LINE+6]]:3: error: cannot implicitly convert from `f64` to `i32` [ImplicitAsConversionFailure] // CHECK:STDERR: var x: i32 = Foo(); // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~ - // CHECK:STDERR: fail_return_type_mismatch.carbon:[[@LINE+3]]:3: note: type `f64` does not implement interface `ImplicitAs(i32)` [MissingImplInMemberAccessNote] + // CHECK:STDERR: fail_return_type_mismatch.carbon:[[@LINE+3]]:3: note: type `f64` does not implement interface `Core.ImplicitAs(i32)` [MissingImplInMemberAccessNote] // CHECK:STDERR: var x: i32 = Foo(); // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~ var x: i32 = Foo(); diff --git a/toolchain/check/testdata/impl/fail_impl_bad_interface.carbon b/toolchain/check/testdata/impl/fail_impl_bad_interface.carbon index 4a165b8a0d60e..da50bddc369bd 100644 --- a/toolchain/check/testdata/impl/fail_impl_bad_interface.carbon +++ b/toolchain/check/testdata/impl/fail_impl_bad_interface.carbon @@ -15,7 +15,7 @@ library "[[@TEST_NAME]]"; // CHECK:STDERR: fail_impl_as_false.carbon:[[@LINE+7]]:13: error: cannot implicitly convert from `bool` to `type` [ImplicitAsConversionFailure] // CHECK:STDERR: impl i32 as false {} // CHECK:STDERR: ^~~~~ -// CHECK:STDERR: fail_impl_as_false.carbon:[[@LINE+4]]:13: note: type `bool` does not implement interface `ImplicitAs(type)` [MissingImplInMemberAccessNote] +// CHECK:STDERR: fail_impl_as_false.carbon:[[@LINE+4]]:13: note: type `bool` does not implement interface `Core.ImplicitAs(type)` [MissingImplInMemberAccessNote] // CHECK:STDERR: impl i32 as false {} // CHECK:STDERR: ^~~~~ // CHECK:STDERR: diff --git a/toolchain/check/testdata/impl/lookup/no_prelude/import.carbon b/toolchain/check/testdata/impl/lookup/no_prelude/import.carbon index 04484d724bceb..2a5267c67d6be 100644 --- a/toolchain/check/testdata/impl/lookup/no_prelude/import.carbon +++ b/toolchain/check/testdata/impl/lookup/no_prelude/import.carbon @@ -183,7 +183,7 @@ import HasExtraInterfaces; fn Test(c: HasExtraInterfaces.C(type)) { // This triggers the import of a bunch more interfaces, which reallocates the // interface ValueStore. Ensure that doesn't result in a use-after-free crash. - // CHECK:STDERR: fail_use_has_extra_interfaces.carbon:[[@LINE+3]]:3: error: cannot access member of interface `I` in type `C(type)` that does not implement that interface [MissingImplInMemberAccess] + // CHECK:STDERR: fail_use_has_extra_interfaces.carbon:[[@LINE+3]]:3: error: cannot access member of interface `HasExtraInterfaces.I` in type `HasExtraInterfaces.C(type)` that does not implement that interface [MissingImplInMemberAccess] // CHECK:STDERR: c.(HasExtraInterfaces.I.F)(); // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~ c.(HasExtraInterfaces.I.F)(); diff --git a/toolchain/check/testdata/index/fail_array_non_int_indexing.carbon b/toolchain/check/testdata/index/fail_array_non_int_indexing.carbon index 74829b071f384..b38d5cb614917 100644 --- a/toolchain/check/testdata/index/fail_array_non_int_indexing.carbon +++ b/toolchain/check/testdata/index/fail_array_non_int_indexing.carbon @@ -12,7 +12,7 @@ var a: [i32; 1] = (12,); // CHECK:STDERR: fail_array_non_int_indexing.carbon:[[@LINE+6]]:16: error: cannot implicitly convert from `f64` to `i32` [ImplicitAsConversionFailure] // CHECK:STDERR: var b: i32 = a[2.6]; // CHECK:STDERR: ^~~ -// CHECK:STDERR: fail_array_non_int_indexing.carbon:[[@LINE+3]]:16: note: type `f64` does not implement interface `ImplicitAs(i32)` [MissingImplInMemberAccessNote] +// CHECK:STDERR: fail_array_non_int_indexing.carbon:[[@LINE+3]]:16: note: type `f64` does not implement interface `Core.ImplicitAs(i32)` [MissingImplInMemberAccessNote] // CHECK:STDERR: var b: i32 = a[2.6]; // CHECK:STDERR: ^~~ var b: i32 = a[2.6]; diff --git a/toolchain/check/testdata/index/fail_negative_indexing.carbon b/toolchain/check/testdata/index/fail_negative_indexing.carbon index 6b64234e0b7e8..c62619df16cd0 100644 --- a/toolchain/check/testdata/index/fail_negative_indexing.carbon +++ b/toolchain/check/testdata/index/fail_negative_indexing.carbon @@ -9,7 +9,7 @@ // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/index/fail_negative_indexing.carbon var c: [i32; 2] = (42, 42); -// CHECK:STDERR: fail_negative_indexing.carbon:[[@LINE+3]]:16: error: cannot access member of interface `Negate` in type `Core.IntLiteral` that does not implement that interface [MissingImplInMemberAccess] +// CHECK:STDERR: fail_negative_indexing.carbon:[[@LINE+3]]:16: error: cannot access member of interface `Core.Negate` in type `Core.IntLiteral` that does not implement that interface [MissingImplInMemberAccess] // CHECK:STDERR: var d: i32 = c[-10]; // CHECK:STDERR: ^~~ var d: i32 = c[-10]; diff --git a/toolchain/check/testdata/interface/fail_assoc_const_bad_default.carbon b/toolchain/check/testdata/interface/fail_assoc_const_bad_default.carbon index 7c80f963419f4..6ceef63cff67a 100644 --- a/toolchain/check/testdata/interface/fail_assoc_const_bad_default.carbon +++ b/toolchain/check/testdata/interface/fail_assoc_const_bad_default.carbon @@ -12,7 +12,7 @@ interface I { // CHECK:STDERR: fail_assoc_const_bad_default.carbon:[[@LINE+6]]:3: error: cannot implicitly convert from `Core.IntLiteral` to `type` [ImplicitAsConversionFailure] // CHECK:STDERR: let T:! type = 42; // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~ - // CHECK:STDERR: fail_assoc_const_bad_default.carbon:[[@LINE+3]]:3: note: type `Core.IntLiteral` does not implement interface `ImplicitAs(type)` [MissingImplInMemberAccessNote] + // CHECK:STDERR: fail_assoc_const_bad_default.carbon:[[@LINE+3]]:3: note: type `Core.IntLiteral` does not implement interface `Core.ImplicitAs(type)` [MissingImplInMemberAccessNote] // CHECK:STDERR: let T:! type = 42; // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~ let T:! type = 42; diff --git a/toolchain/check/testdata/let/compile_time_bindings.carbon b/toolchain/check/testdata/let/compile_time_bindings.carbon index 70f9880b45cc6..65c530e99cc3a 100644 --- a/toolchain/check/testdata/let/compile_time_bindings.carbon +++ b/toolchain/check/testdata/let/compile_time_bindings.carbon @@ -110,7 +110,7 @@ interface I { // CHECK:STDERR: fail_return_in_interface.carbon:[[@LINE+7]]:13: error: cannot implicitly convert from `` to `type` [ImplicitAsConversionFailure] // CHECK:STDERR: fn F() -> T; // CHECK:STDERR: ^ - // CHECK:STDERR: fail_return_in_interface.carbon:[[@LINE+4]]:13: note: type `` does not implement interface `ImplicitAs(type)` [MissingImplInMemberAccessNote] + // CHECK:STDERR: fail_return_in_interface.carbon:[[@LINE+4]]:13: note: type `` does not implement interface `Core.ImplicitAs(type)` [MissingImplInMemberAccessNote] // CHECK:STDERR: fn F() -> T; // CHECK:STDERR: ^ // CHECK:STDERR: diff --git a/toolchain/check/testdata/let/fail_generic.carbon b/toolchain/check/testdata/let/fail_generic.carbon index 824702625ea0d..15f6673b9f638 100644 --- a/toolchain/check/testdata/let/fail_generic.carbon +++ b/toolchain/check/testdata/let/fail_generic.carbon @@ -14,7 +14,7 @@ fn F(a: i32) -> i32 { // CHECK:STDERR: fail_generic.carbon:[[@LINE+7]]:3: error: cannot implicitly convert from `Core.IntLiteral` to `T` [ImplicitAsConversionFailure] // CHECK:STDERR: let x: T = 5; // CHECK:STDERR: ^~~~~~~~~~~~~ - // CHECK:STDERR: fail_generic.carbon:[[@LINE+4]]:3: note: type `Core.IntLiteral` does not implement interface `ImplicitAs(T)` [MissingImplInMemberAccessNote] + // CHECK:STDERR: fail_generic.carbon:[[@LINE+4]]:3: note: type `Core.IntLiteral` does not implement interface `Core.ImplicitAs(T)` [MissingImplInMemberAccessNote] // CHECK:STDERR: let x: T = 5; // CHECK:STDERR: ^~~~~~~~~~~~~ // CHECK:STDERR: @@ -22,7 +22,7 @@ fn F(a: i32) -> i32 { // CHECK:STDERR: fail_generic.carbon:[[@LINE+6]]:3: error: cannot implicitly convert from `T` to `i32` [ImplicitAsConversionFailure] // CHECK:STDERR: return x; // CHECK:STDERR: ^~~~~~~~~ - // CHECK:STDERR: fail_generic.carbon:[[@LINE+3]]:3: note: type `T` does not implement interface `ImplicitAs(i32)` [MissingImplInMemberAccessNote] + // CHECK:STDERR: fail_generic.carbon:[[@LINE+3]]:3: note: type `T` does not implement interface `Core.ImplicitAs(i32)` [MissingImplInMemberAccessNote] // CHECK:STDERR: return x; // CHECK:STDERR: ^~~~~~~~~ return x; diff --git a/toolchain/check/testdata/namespace/imported_indirect.carbon b/toolchain/check/testdata/namespace/imported_indirect.carbon index 55f62d06d9df8..c059c78479039 100644 --- a/toolchain/check/testdata/namespace/imported_indirect.carbon +++ b/toolchain/check/testdata/namespace/imported_indirect.carbon @@ -14,6 +14,8 @@ package Same library "[[@TEST_NAME]]"; namespace A; +class A.C; + // --- b.carbon package Same library "[[@TEST_NAME]]"; @@ -21,6 +23,8 @@ import library "a"; namespace A.B; +fn F() -> A.C; + // --- c.carbon package Same library "[[@TEST_NAME]]"; @@ -42,8 +46,53 @@ import library "d"; var e: () = A.B.C.D(); +// --- fail_named_indirectly_same_package.carbon + +package Same library "[[@TEST_NAME]]"; + +import library "b"; + +// CHECK:STDERR: fail_named_indirectly_same_package.carbon:[[@LINE+13]]:10: error: function returns incomplete type `A.C` [IncompleteTypeInFunctionReturnType] +// CHECK:STDERR: fn G() { F(); } +// CHECK:STDERR: ^~~ +// CHECK:STDERR: fail_named_indirectly_same_package.carbon:[[@LINE-5]]:1: in import [InImport] +// CHECK:STDERR: b.carbon:3:1: in import [InImport] +// CHECK:STDERR: a.carbon:6:1: note: class was forward declared here [ClassForwardDeclaredHere] +// CHECK:STDERR: class A.C; +// CHECK:STDERR: ^~~~~~~~~~ +// CHECK:STDERR: fail_named_indirectly_same_package.carbon:[[@LINE-10]]:1: in import [InImport] +// CHECK:STDERR: b.carbon:7:8: note: return type declared here [IncompleteReturnTypeHere] +// CHECK:STDERR: fn F() -> A.C; +// CHECK:STDERR: ^~~~~~ +// CHECK:STDERR: +fn G() { F(); } + +// --- fail_named_indirectly_different_package.carbon + +package Other library "[[@TEST_NAME]]"; + +import Same library "b"; + +// CHECK:STDERR: fail_named_indirectly_different_package.carbon:[[@LINE+12]]:10: error: function returns incomplete type `Same.A.C` [IncompleteTypeInFunctionReturnType] +// CHECK:STDERR: fn G() { Same.F(); } +// CHECK:STDERR: ^~~~~~~~ +// CHECK:STDERR: fail_named_indirectly_different_package.carbon:[[@LINE-5]]:1: in import [InImport] +// CHECK:STDERR: b.carbon:3:1: in import [InImport] +// CHECK:STDERR: a.carbon:6:1: note: class was forward declared here [ClassForwardDeclaredHere] +// CHECK:STDERR: class A.C; +// CHECK:STDERR: ^~~~~~~~~~ +// CHECK:STDERR: fail_named_indirectly_different_package.carbon:[[@LINE-10]]:1: in import [InImport] +// CHECK:STDERR: b.carbon:7:8: note: return type declared here [IncompleteReturnTypeHere] +// CHECK:STDERR: fn F() -> A.C; +// CHECK:STDERR: ^~~~~~ +fn G() { Same.F(); } + // CHECK:STDOUT: --- a.carbon // CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %C: type = class_type @C [template] +// CHECK:STDOUT: } +// CHECK:STDOUT: // CHECK:STDOUT: imports { // CHECK:STDOUT: %Core: = namespace file.%Core.import, [template] { // CHECK:STDOUT: import Core//prelude @@ -57,16 +106,29 @@ var e: () = A.B.C.D(); // CHECK:STDOUT: .A = %A // CHECK:STDOUT: } // CHECK:STDOUT: %Core.import = import Core -// CHECK:STDOUT: %A: = namespace [template] {} +// CHECK:STDOUT: %A: = namespace [template] { +// CHECK:STDOUT: .C = %C.decl +// CHECK:STDOUT: } +// CHECK:STDOUT: %C.decl: type = class_decl @C [template = constants.%C] {} {} // CHECK:STDOUT: } // CHECK:STDOUT: +// CHECK:STDOUT: class @C; +// CHECK:STDOUT: // CHECK:STDOUT: --- b.carbon // CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %C: type = class_type @C [template] +// CHECK:STDOUT: %F.type: type = fn_type @F [template] +// CHECK:STDOUT: %F: %F.type = struct_value () [template] +// CHECK:STDOUT: } +// CHECK:STDOUT: // CHECK:STDOUT: imports { -// CHECK:STDOUT: %import_ref: = import_ref Same//a, A, loaded -// CHECK:STDOUT: %A: = namespace %import_ref, [template] { +// CHECK:STDOUT: %import_ref.1: = import_ref Same//a, A, loaded +// CHECK:STDOUT: %A: = namespace %import_ref.1, [template] { +// CHECK:STDOUT: .C = %import_ref.2 // CHECK:STDOUT: .B = file.%B // CHECK:STDOUT: } +// CHECK:STDOUT: %import_ref.2: type = import_ref Same//a, C, loaded [template = constants.%C] // CHECK:STDOUT: %Core: = namespace file.%Core.import, [template] { // CHECK:STDOUT: import Core//prelude // CHECK:STDOUT: import Core//prelude/... @@ -77,12 +139,26 @@ var e: () = A.B.C.D(); // CHECK:STDOUT: package: = namespace [template] { // CHECK:STDOUT: .A = imports.%A // CHECK:STDOUT: .Core = imports.%Core +// CHECK:STDOUT: .F = %F.decl // CHECK:STDOUT: } // CHECK:STDOUT: %Core.import = import Core // CHECK:STDOUT: %default.import = import // CHECK:STDOUT: %B: = namespace [template] {} +// CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [template = constants.%F] { +// CHECK:STDOUT: %return.patt: %C = return_slot_pattern +// CHECK:STDOUT: %return.param_patt: %C = out_param_pattern %return.patt, runtime_param0 +// CHECK:STDOUT: } { +// CHECK:STDOUT: %A.ref: = name_ref A, imports.%A [template = imports.%A] +// CHECK:STDOUT: %C.ref: type = name_ref C, imports.%import_ref.2 [template = constants.%C] +// CHECK:STDOUT: %return.param: ref %C = out_param runtime_param0 +// CHECK:STDOUT: %return: ref %C = return_slot %return.param +// CHECK:STDOUT: } // CHECK:STDOUT: } // CHECK:STDOUT: +// CHECK:STDOUT: class @C [from "a.carbon"]; +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F() -> %C; +// CHECK:STDOUT: // CHECK:STDOUT: --- c.carbon // CHECK:STDOUT: // CHECK:STDOUT: imports { @@ -90,6 +166,7 @@ var e: () = A.B.C.D(); // CHECK:STDOUT: %A: = namespace %import_ref.1, [template] { // CHECK:STDOUT: .B = %B // CHECK:STDOUT: } +// CHECK:STDOUT: %import_ref.3 = import_ref Same//b, F, unloaded // CHECK:STDOUT: %Core: = namespace file.%Core.import, [template] { // CHECK:STDOUT: import Core//prelude // CHECK:STDOUT: import Core//prelude/... @@ -99,6 +176,7 @@ var e: () = A.B.C.D(); // CHECK:STDOUT: file { // CHECK:STDOUT: package: = namespace [template] { // CHECK:STDOUT: .A = imports.%A +// CHECK:STDOUT: .F = imports.%import_ref.3 // CHECK:STDOUT: .Core = imports.%Core // CHECK:STDOUT: } // CHECK:STDOUT: %Core.import = import Core @@ -194,3 +272,93 @@ var e: () = A.B.C.D(); // CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: +// CHECK:STDOUT: --- fail_named_indirectly_same_package.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %G.type: type = fn_type @G [template] +// CHECK:STDOUT: %G: %G.type = struct_value () [template] +// CHECK:STDOUT: %F.type: type = fn_type @F [template] +// CHECK:STDOUT: %F: %F.type = struct_value () [template] +// CHECK:STDOUT: %C: type = class_type @C [template] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: imports { +// CHECK:STDOUT: %import_ref.1: = import_ref Same//b, A, loaded +// CHECK:STDOUT: %A: = namespace %import_ref.1, [template] { +// CHECK:STDOUT: .B = %B +// CHECK:STDOUT: } +// CHECK:STDOUT: %import_ref.3: %F.type = import_ref Same//b, F, loaded [template = constants.%F] +// CHECK:STDOUT: %Core: = namespace file.%Core.import, [template] { +// CHECK:STDOUT: import Core//prelude +// CHECK:STDOUT: import Core//prelude/... +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace [template] { +// CHECK:STDOUT: .A = imports.%A +// CHECK:STDOUT: .F = imports.%import_ref.3 +// CHECK:STDOUT: .Core = imports.%Core +// CHECK:STDOUT: .G = %G.decl +// CHECK:STDOUT: } +// CHECK:STDOUT: %Core.import = import Core +// CHECK:STDOUT: %default.import = import +// CHECK:STDOUT: %G.decl: %G.type = fn_decl @G [template = constants.%G] {} {} +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @C [from "b.carbon"]; +// CHECK:STDOUT: +// CHECK:STDOUT: fn @G() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %F.ref: %F.type = name_ref F, imports.%import_ref.3 [template = constants.%F] +// CHECK:STDOUT: %F.call: init = call %F.ref() +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F() -> %C [from "b.carbon"]; +// CHECK:STDOUT: +// CHECK:STDOUT: --- fail_named_indirectly_different_package.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %G.type: type = fn_type @G [template] +// CHECK:STDOUT: %G: %G.type = struct_value () [template] +// CHECK:STDOUT: %F.type: type = fn_type @F [template] +// CHECK:STDOUT: %F: %F.type = struct_value () [template] +// CHECK:STDOUT: %C: type = class_type @C [template] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: imports { +// CHECK:STDOUT: %Core: = namespace file.%Core.import, [template] { +// CHECK:STDOUT: import Core//prelude +// CHECK:STDOUT: import Core//prelude/... +// CHECK:STDOUT: } +// CHECK:STDOUT: %Same: = namespace file.%Same.import, [template] { +// CHECK:STDOUT: .F = %import_ref +// CHECK:STDOUT: import Same//b +// CHECK:STDOUT: } +// CHECK:STDOUT: %import_ref: %F.type = import_ref Same//b, F, loaded [template = constants.%F] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace [template] { +// CHECK:STDOUT: .Core = imports.%Core +// CHECK:STDOUT: .Same = imports.%Same +// CHECK:STDOUT: .G = %G.decl +// CHECK:STDOUT: } +// CHECK:STDOUT: %Core.import = import Core +// CHECK:STDOUT: %Same.import = import Same +// CHECK:STDOUT: %G.decl: %G.type = fn_decl @G [template = constants.%G] {} {} +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @C [from "b.carbon"]; +// CHECK:STDOUT: +// CHECK:STDOUT: fn @G() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %Same.ref: = name_ref Same, imports.%Same [template = imports.%Same] +// CHECK:STDOUT: %F.ref: %F.type = name_ref F, imports.%import_ref [template = constants.%F] +// CHECK:STDOUT: %F.call: init = call %F.ref() +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F() -> %C [from "b.carbon"]; +// CHECK:STDOUT: diff --git a/toolchain/check/testdata/operators/builtin/fail_type_mismatch.carbon b/toolchain/check/testdata/operators/builtin/fail_type_mismatch.carbon index 792ab5be51c0e..164949a70fa39 100644 --- a/toolchain/check/testdata/operators/builtin/fail_type_mismatch.carbon +++ b/toolchain/check/testdata/operators/builtin/fail_type_mismatch.carbon @@ -12,7 +12,7 @@ fn Main() { // CHECK:STDERR: fail_type_mismatch.carbon:[[@LINE+6]]:17: error: cannot implicitly convert from `Core.IntLiteral` to `bool` [ImplicitAsConversionFailure] // CHECK:STDERR: var x: bool = not 12; // CHECK:STDERR: ^~~~~~ - // CHECK:STDERR: fail_type_mismatch.carbon:[[@LINE+3]]:17: note: type `Core.IntLiteral` does not implement interface `ImplicitAs(bool)` [MissingImplInMemberAccessNote] + // CHECK:STDERR: fail_type_mismatch.carbon:[[@LINE+3]]:17: note: type `Core.IntLiteral` does not implement interface `Core.ImplicitAs(bool)` [MissingImplInMemberAccessNote] // CHECK:STDERR: var x: bool = not 12; // CHECK:STDERR: ^~~~~~ var x: bool = not 12; diff --git a/toolchain/check/testdata/operators/builtin/fail_type_mismatch_assignment.carbon b/toolchain/check/testdata/operators/builtin/fail_type_mismatch_assignment.carbon index b9159fa3ff905..84ff5fcc8835f 100644 --- a/toolchain/check/testdata/operators/builtin/fail_type_mismatch_assignment.carbon +++ b/toolchain/check/testdata/operators/builtin/fail_type_mismatch_assignment.carbon @@ -13,7 +13,7 @@ fn Main() { // CHECK:STDERR: fail_type_mismatch_assignment.carbon:[[@LINE+6]]:3: error: cannot implicitly convert from `f64` to `i32` [ImplicitAsConversionFailure] // CHECK:STDERR: a = 5.6; // CHECK:STDERR: ^~~~~~~ - // CHECK:STDERR: fail_type_mismatch_assignment.carbon:[[@LINE+3]]:3: note: type `f64` does not implement interface `ImplicitAs(i32)` [MissingImplInMemberAccessNote] + // CHECK:STDERR: fail_type_mismatch_assignment.carbon:[[@LINE+3]]:3: note: type `f64` does not implement interface `Core.ImplicitAs(i32)` [MissingImplInMemberAccessNote] // CHECK:STDERR: a = 5.6; // CHECK:STDERR: ^~~~~~~ a = 5.6; diff --git a/toolchain/check/testdata/operators/builtin/fail_type_mismatch_once.carbon b/toolchain/check/testdata/operators/builtin/fail_type_mismatch_once.carbon index fb16090423dcb..cffdd1230ffa9 100644 --- a/toolchain/check/testdata/operators/builtin/fail_type_mismatch_once.carbon +++ b/toolchain/check/testdata/operators/builtin/fail_type_mismatch_once.carbon @@ -11,11 +11,11 @@ fn Main() -> i32 { // The following line has two mismatches, but after the first, it shouldn't // keep erroring. - // CHECK:STDERR: fail_type_mismatch_once.carbon:[[@LINE+7]]:10: error: cannot access member of interface `Add` in type `Core.IntLiteral` that does not implement that interface [MissingImplInMemberAccess] + // CHECK:STDERR: fail_type_mismatch_once.carbon:[[@LINE+7]]:10: error: cannot access member of interface `Core.Add` in type `Core.IntLiteral` that does not implement that interface [MissingImplInMemberAccess] // CHECK:STDERR: return 12 + 3.4 + 12; // CHECK:STDERR: ^~~~~~~~ // CHECK:STDERR: - // CHECK:STDERR: fail_type_mismatch_once.carbon:[[@LINE+3]]:10: error: cannot access member of interface `Add` in type `` that does not implement that interface [MissingImplInMemberAccess] + // CHECK:STDERR: fail_type_mismatch_once.carbon:[[@LINE+3]]:10: error: cannot access member of interface `Core.Add` in type `` that does not implement that interface [MissingImplInMemberAccess] // CHECK:STDERR: return 12 + 3.4 + 12; // CHECK:STDERR: ^~~~~~~~~~~~~ return 12 + 3.4 + 12; diff --git a/toolchain/check/testdata/operators/builtin/fail_unimplemented_op.carbon b/toolchain/check/testdata/operators/builtin/fail_unimplemented_op.carbon index 3af3394a67679..365bb892370ab 100644 --- a/toolchain/check/testdata/operators/builtin/fail_unimplemented_op.carbon +++ b/toolchain/check/testdata/operators/builtin/fail_unimplemented_op.carbon @@ -9,7 +9,7 @@ // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/operators/builtin/fail_unimplemented_op.carbon fn Main() -> i32 { - // CHECK:STDERR: fail_unimplemented_op.carbon:[[@LINE+3]]:10: error: cannot access member of interface `Add` in type `Core.IntLiteral` that does not implement that interface [MissingImplInMemberAccess] + // CHECK:STDERR: fail_unimplemented_op.carbon:[[@LINE+3]]:10: error: cannot access member of interface `Core.Add` in type `Core.IntLiteral` that does not implement that interface [MissingImplInMemberAccess] // CHECK:STDERR: return 12 + 34; // CHECK:STDERR: ^~~~~~~ return 12 + 34; diff --git a/toolchain/check/testdata/operators/overloaded/eq.carbon b/toolchain/check/testdata/operators/overloaded/eq.carbon index f6d4e6d1666bc..14cfe5079c7a7 100644 --- a/toolchain/check/testdata/operators/overloaded/eq.carbon +++ b/toolchain/check/testdata/operators/overloaded/eq.carbon @@ -34,7 +34,7 @@ package FailNoImpl; class D {}; fn TestEqual(a: D, b: D) -> bool { - // CHECK:STDERR: fail_no_impl.carbon:[[@LINE+4]]:10: error: cannot access member of interface `Eq` in type `D` that does not implement that interface [MissingImplInMemberAccess] + // CHECK:STDERR: fail_no_impl.carbon:[[@LINE+4]]:10: error: cannot access member of interface `Core.Eq` in type `D` that does not implement that interface [MissingImplInMemberAccess] // CHECK:STDERR: return a == b; // CHECK:STDERR: ^~~~~~ // CHECK:STDERR: @@ -42,7 +42,7 @@ fn TestEqual(a: D, b: D) -> bool { } fn TestNotEqual(a: D, b: D) -> bool { - // CHECK:STDERR: fail_no_impl.carbon:[[@LINE+4]]:10: error: cannot access member of interface `Eq` in type `D` that does not implement that interface [MissingImplInMemberAccess] + // CHECK:STDERR: fail_no_impl.carbon:[[@LINE+4]]:10: error: cannot access member of interface `Core.Eq` in type `D` that does not implement that interface [MissingImplInMemberAccess] // CHECK:STDERR: return a != b; // CHECK:STDERR: ^~~~~~ // CHECK:STDERR: @@ -65,7 +65,7 @@ fn TestRhsBad(a: C, b: D) -> bool { // CHECK:STDERR: fail_no_impl_for_args.carbon:[[@LINE+10]]:15: error: cannot implicitly convert from `D` to `C` [ImplicitAsConversionFailure] // CHECK:STDERR: return a == b; // CHECK:STDERR: ^ - // CHECK:STDERR: fail_no_impl_for_args.carbon:[[@LINE+7]]:15: note: type `D` does not implement interface `ImplicitAs(C)` [MissingImplInMemberAccessNote] + // CHECK:STDERR: fail_no_impl_for_args.carbon:[[@LINE+7]]:15: note: type `D` does not implement interface `Core.ImplicitAs(C)` [MissingImplInMemberAccessNote] // CHECK:STDERR: return a == b; // CHECK:STDERR: ^ // CHECK:STDERR: fail_no_impl_for_args.carbon:[[@LINE-11]]:21: note: initializing function parameter [InCallToFunctionParam] @@ -76,7 +76,7 @@ fn TestRhsBad(a: C, b: D) -> bool { } fn TestLhsBad(a: D, b: C) -> bool { - // CHECK:STDERR: fail_no_impl_for_args.carbon:[[@LINE+3]]:10: error: cannot access member of interface `Eq` in type `D` that does not implement that interface [MissingImplInMemberAccess] + // CHECK:STDERR: fail_no_impl_for_args.carbon:[[@LINE+3]]:10: error: cannot access member of interface `Core.Eq` in type `D` that does not implement that interface [MissingImplInMemberAccess] // CHECK:STDERR: return a != b; // CHECK:STDERR: ^~~~~~ return a != b; diff --git a/toolchain/check/testdata/operators/overloaded/fail_no_impl.carbon b/toolchain/check/testdata/operators/overloaded/fail_no_impl.carbon index 8046e1bf4bf62..f76473272923c 100644 --- a/toolchain/check/testdata/operators/overloaded/fail_no_impl.carbon +++ b/toolchain/check/testdata/operators/overloaded/fail_no_impl.carbon @@ -13,7 +13,7 @@ package User; class C {}; fn TestUnary(a: C) -> C { - // CHECK:STDERR: fail_no_impl.carbon:[[@LINE+4]]:10: error: cannot access member of interface `Negate` in type `C` that does not implement that interface [MissingImplInMemberAccess] + // CHECK:STDERR: fail_no_impl.carbon:[[@LINE+4]]:10: error: cannot access member of interface `Core.Negate` in type `C` that does not implement that interface [MissingImplInMemberAccess] // CHECK:STDERR: return -a; // CHECK:STDERR: ^~ // CHECK:STDERR: @@ -21,7 +21,7 @@ fn TestUnary(a: C) -> C { } fn TestBinary(a: C, b: C) -> C { - // CHECK:STDERR: fail_no_impl.carbon:[[@LINE+4]]:10: error: cannot access member of interface `Add` in type `C` that does not implement that interface [MissingImplInMemberAccess] + // CHECK:STDERR: fail_no_impl.carbon:[[@LINE+4]]:10: error: cannot access member of interface `Core.Add` in type `C` that does not implement that interface [MissingImplInMemberAccess] // CHECK:STDERR: return a + b; // CHECK:STDERR: ^~~~~ // CHECK:STDERR: @@ -30,12 +30,12 @@ fn TestBinary(a: C, b: C) -> C { fn TestRef(b: C) { var a: C = {}; - // CHECK:STDERR: fail_no_impl.carbon:[[@LINE+4]]:3: error: cannot access member of interface `AddAssign` in type `C` that does not implement that interface [MissingImplInMemberAccess] + // CHECK:STDERR: fail_no_impl.carbon:[[@LINE+4]]:3: error: cannot access member of interface `Core.AddAssign` in type `C` that does not implement that interface [MissingImplInMemberAccess] // CHECK:STDERR: a += b; // CHECK:STDERR: ^~~~~~ // CHECK:STDERR: a += b; - // CHECK:STDERR: fail_no_impl.carbon:[[@LINE+3]]:3: error: cannot access member of interface `Inc` in type `C` that does not implement that interface [MissingImplInMemberAccess] + // CHECK:STDERR: fail_no_impl.carbon:[[@LINE+3]]:3: error: cannot access member of interface `Core.Inc` in type `C` that does not implement that interface [MissingImplInMemberAccess] // CHECK:STDERR: ++a; // CHECK:STDERR: ^~~ ++a; diff --git a/toolchain/check/testdata/operators/overloaded/fail_no_impl_for_arg.carbon b/toolchain/check/testdata/operators/overloaded/fail_no_impl_for_arg.carbon index 99f27df7ac3bd..6ea10283ce257 100644 --- a/toolchain/check/testdata/operators/overloaded/fail_no_impl_for_arg.carbon +++ b/toolchain/check/testdata/operators/overloaded/fail_no_impl_for_arg.carbon @@ -24,7 +24,7 @@ fn Test(a: C, b: D) -> C { // CHECK:STDERR: fail_no_impl_for_arg.carbon:[[@LINE+10]]:14: error: cannot implicitly convert from `D` to `C` [ImplicitAsConversionFailure] // CHECK:STDERR: return a + b; // CHECK:STDERR: ^ - // CHECK:STDERR: fail_no_impl_for_arg.carbon:[[@LINE+7]]:14: note: type `D` does not implement interface `ImplicitAs(C)` [MissingImplInMemberAccessNote] + // CHECK:STDERR: fail_no_impl_for_arg.carbon:[[@LINE+7]]:14: note: type `D` does not implement interface `Core.ImplicitAs(C)` [MissingImplInMemberAccessNote] // CHECK:STDERR: return a + b; // CHECK:STDERR: ^ // CHECK:STDERR: fail_no_impl_for_arg.carbon:[[@LINE-13]]:18: note: initializing function parameter [InCallToFunctionParam] @@ -39,7 +39,7 @@ fn TestAssign(b: D) { // CHECK:STDERR: fail_no_impl_for_arg.carbon:[[@LINE+9]]:8: error: cannot implicitly convert from `D` to `C` [ImplicitAsConversionFailure] // CHECK:STDERR: a += b; // CHECK:STDERR: ^ - // CHECK:STDERR: fail_no_impl_for_arg.carbon:[[@LINE+6]]:8: note: type `D` does not implement interface `ImplicitAs(C)` [MissingImplInMemberAccessNote] + // CHECK:STDERR: fail_no_impl_for_arg.carbon:[[@LINE+6]]:8: note: type `D` does not implement interface `Core.ImplicitAs(C)` [MissingImplInMemberAccessNote] // CHECK:STDERR: a += b; // CHECK:STDERR: ^ // CHECK:STDERR: fail_no_impl_for_arg.carbon:[[@LINE-25]]:24: note: initializing function parameter [InCallToFunctionParam] diff --git a/toolchain/check/testdata/operators/overloaded/index.carbon b/toolchain/check/testdata/operators/overloaded/index.carbon index b58034c271289..1bf1bcfde2c0c 100644 --- a/toolchain/check/testdata/operators/overloaded/index.carbon +++ b/toolchain/check/testdata/operators/overloaded/index.carbon @@ -57,7 +57,7 @@ let c: C = {}; // CHECK:STDERR: fail_invalid_subscript_type.carbon:[[@LINE+7]]:22: error: cannot implicitly convert from `Core.IntLiteral` to `SubscriptType` [ImplicitAsConversionFailure] // CHECK:STDERR: let x: ElementType = c[0]; // CHECK:STDERR: ^~~~ -// CHECK:STDERR: fail_invalid_subscript_type.carbon:[[@LINE+4]]:22: note: type `Core.IntLiteral` does not implement interface `ImplicitAs(SubscriptType)` [MissingImplInMemberAccessNote] +// CHECK:STDERR: fail_invalid_subscript_type.carbon:[[@LINE+4]]:22: note: type `Core.IntLiteral` does not implement interface `Core.ImplicitAs(SubscriptType)` [MissingImplInMemberAccessNote] // CHECK:STDERR: let x: ElementType = c[0]; // CHECK:STDERR: ^~~~ // CHECK:STDERR: diff --git a/toolchain/check/testdata/operators/overloaded/ordered.carbon b/toolchain/check/testdata/operators/overloaded/ordered.carbon index dbafbba67b5e9..b65479c0edc70 100644 --- a/toolchain/check/testdata/operators/overloaded/ordered.carbon +++ b/toolchain/check/testdata/operators/overloaded/ordered.carbon @@ -44,7 +44,7 @@ package FailNoImpl; class D {}; fn TestLess(a: D, b: D) -> bool { - // CHECK:STDERR: fail_no_impl.carbon:[[@LINE+4]]:10: error: cannot access member of interface `Ordered` in type `D` that does not implement that interface [MissingImplInMemberAccess] + // CHECK:STDERR: fail_no_impl.carbon:[[@LINE+4]]:10: error: cannot access member of interface `Core.Ordered` in type `D` that does not implement that interface [MissingImplInMemberAccess] // CHECK:STDERR: return a < b; // CHECK:STDERR: ^~~~~ // CHECK:STDERR: @@ -52,7 +52,7 @@ fn TestLess(a: D, b: D) -> bool { } fn TestLessEqual(a: D, b: D) -> bool { - // CHECK:STDERR: fail_no_impl.carbon:[[@LINE+4]]:10: error: cannot access member of interface `Ordered` in type `D` that does not implement that interface [MissingImplInMemberAccess] + // CHECK:STDERR: fail_no_impl.carbon:[[@LINE+4]]:10: error: cannot access member of interface `Core.Ordered` in type `D` that does not implement that interface [MissingImplInMemberAccess] // CHECK:STDERR: return a <= b; // CHECK:STDERR: ^~~~~~ // CHECK:STDERR: @@ -60,7 +60,7 @@ fn TestLessEqual(a: D, b: D) -> bool { } fn TestGreater(a: D, b: D) -> bool { - // CHECK:STDERR: fail_no_impl.carbon:[[@LINE+4]]:10: error: cannot access member of interface `Ordered` in type `D` that does not implement that interface [MissingImplInMemberAccess] + // CHECK:STDERR: fail_no_impl.carbon:[[@LINE+4]]:10: error: cannot access member of interface `Core.Ordered` in type `D` that does not implement that interface [MissingImplInMemberAccess] // CHECK:STDERR: return a > b; // CHECK:STDERR: ^~~~~ // CHECK:STDERR: @@ -68,7 +68,7 @@ fn TestGreater(a: D, b: D) -> bool { } fn TestGreaterEqual(a: D, b: D) -> bool { - // CHECK:STDERR: fail_no_impl.carbon:[[@LINE+3]]:10: error: cannot access member of interface `Ordered` in type `D` that does not implement that interface [MissingImplInMemberAccess] + // CHECK:STDERR: fail_no_impl.carbon:[[@LINE+3]]:10: error: cannot access member of interface `Core.Ordered` in type `D` that does not implement that interface [MissingImplInMemberAccess] // CHECK:STDERR: return a >= b; // CHECK:STDERR: ^~~~~~ return a >= b; diff --git a/toolchain/check/testdata/pointer/fail_type_mismatch.carbon b/toolchain/check/testdata/pointer/fail_type_mismatch.carbon index df39aa7761031..54e9aa51a3ca3 100644 --- a/toolchain/check/testdata/pointer/fail_type_mismatch.carbon +++ b/toolchain/check/testdata/pointer/fail_type_mismatch.carbon @@ -12,7 +12,7 @@ fn ConstMismatch(p: const {}*) -> const ({}*) { // CHECK:STDERR: fail_type_mismatch.carbon:[[@LINE+6]]:3: error: cannot implicitly convert from `const {}*` to `const ({}*)` [ImplicitAsConversionFailure] // CHECK:STDERR: return p; // CHECK:STDERR: ^~~~~~~~~ - // CHECK:STDERR: fail_type_mismatch.carbon:[[@LINE+3]]:3: note: type `const {}*` does not implement interface `ImplicitAs(const ({}*))` [MissingImplInMemberAccessNote] + // CHECK:STDERR: fail_type_mismatch.carbon:[[@LINE+3]]:3: note: type `const {}*` does not implement interface `Core.ImplicitAs(const ({}*))` [MissingImplInMemberAccessNote] // CHECK:STDERR: return p; // CHECK:STDERR: ^~~~~~~~~ return p; diff --git a/toolchain/check/testdata/return/fail_type_mismatch.carbon b/toolchain/check/testdata/return/fail_type_mismatch.carbon index 177171072ceee..912ed3a02c73c 100644 --- a/toolchain/check/testdata/return/fail_type_mismatch.carbon +++ b/toolchain/check/testdata/return/fail_type_mismatch.carbon @@ -12,7 +12,7 @@ fn Main() -> i32 { // CHECK:STDERR: fail_type_mismatch.carbon:[[@LINE+6]]:3: error: cannot implicitly convert from `f64` to `i32` [ImplicitAsConversionFailure] // CHECK:STDERR: return 1.0; // CHECK:STDERR: ^~~~~~~~~~~ - // CHECK:STDERR: fail_type_mismatch.carbon:[[@LINE+3]]:3: note: type `f64` does not implement interface `ImplicitAs(i32)` [MissingImplInMemberAccessNote] + // CHECK:STDERR: fail_type_mismatch.carbon:[[@LINE+3]]:3: note: type `f64` does not implement interface `Core.ImplicitAs(i32)` [MissingImplInMemberAccessNote] // CHECK:STDERR: return 1.0; // CHECK:STDERR: ^~~~~~~~~~~ return 1.0; diff --git a/toolchain/check/testdata/struct/fail_type_assign.carbon b/toolchain/check/testdata/struct/fail_type_assign.carbon index 20fb8f7d5cdcb..104603681e2dc 100644 --- a/toolchain/check/testdata/struct/fail_type_assign.carbon +++ b/toolchain/check/testdata/struct/fail_type_assign.carbon @@ -11,7 +11,7 @@ // CHECK:STDERR: fail_type_assign.carbon:[[@LINE+6]]:1: error: cannot implicitly convert from `type` to `{.a: i32}` [ImplicitAsConversionFailure] // CHECK:STDERR: var x: {.a: i32} = {.a: i32}; // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// CHECK:STDERR: fail_type_assign.carbon:[[@LINE+3]]:1: note: type `type` does not implement interface `ImplicitAs({.a: i32})` [MissingImplInMemberAccessNote] +// CHECK:STDERR: fail_type_assign.carbon:[[@LINE+3]]:1: note: type `type` does not implement interface `Core.ImplicitAs({.a: i32})` [MissingImplInMemberAccessNote] // CHECK:STDERR: var x: {.a: i32} = {.a: i32}; // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ var x: {.a: i32} = {.a: i32}; diff --git a/toolchain/check/testdata/struct/fail_value_as_type.carbon b/toolchain/check/testdata/struct/fail_value_as_type.carbon index 0283a5756e64e..0a5725a17446d 100644 --- a/toolchain/check/testdata/struct/fail_value_as_type.carbon +++ b/toolchain/check/testdata/struct/fail_value_as_type.carbon @@ -11,7 +11,7 @@ // CHECK:STDERR: fail_value_as_type.carbon:[[@LINE+6]]:8: error: cannot implicitly convert from `{.a: Core.IntLiteral}` to `type` [ImplicitAsConversionFailure] // CHECK:STDERR: var x: {.a = 1}; // CHECK:STDERR: ^~~~~~~~ -// CHECK:STDERR: fail_value_as_type.carbon:[[@LINE+3]]:8: note: type `{.a: Core.IntLiteral}` does not implement interface `ImplicitAs(type)` [MissingImplInMemberAccessNote] +// CHECK:STDERR: fail_value_as_type.carbon:[[@LINE+3]]:8: note: type `{.a: Core.IntLiteral}` does not implement interface `Core.ImplicitAs(type)` [MissingImplInMemberAccessNote] // CHECK:STDERR: var x: {.a = 1}; // CHECK:STDERR: ^~~~~~~~ var x: {.a = 1}; diff --git a/toolchain/check/testdata/struct/import.carbon b/toolchain/check/testdata/struct/import.carbon index 9c422c8f0f6d6..8a3eb2418143f 100644 --- a/toolchain/check/testdata/struct/import.carbon +++ b/toolchain/check/testdata/struct/import.carbon @@ -43,10 +43,10 @@ var c_bad: C({.c = 1, .d = 2}) = F(); // --- fail_bad_value.impl.carbon impl package Implicit; -// CHECK:STDERR: fail_bad_value.impl.carbon:[[@LINE+6]]:1: error: cannot implicitly convert from `C()` to `C()` [ImplicitAsConversionFailure] +// CHECK:STDERR: fail_bad_value.impl.carbon:[[@LINE+6]]:1: error: cannot implicitly convert from `C()` to `C()` [ImplicitAsConversionFailure] // CHECK:STDERR: var c_bad: C({.a = 3, .b = 4}) = F(); // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// CHECK:STDERR: fail_bad_value.impl.carbon:[[@LINE+3]]:1: note: type `C()` does not implement interface `ImplicitAs(C())` [MissingImplInMemberAccessNote] +// CHECK:STDERR: fail_bad_value.impl.carbon:[[@LINE+3]]:1: note: type `C()` does not implement interface `Core.ImplicitAs(C())` [MissingImplInMemberAccessNote] // CHECK:STDERR: var c_bad: C({.a = 3, .b = 4}) = F(); // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ var c_bad: C({.a = 3, .b = 4}) = F(); diff --git a/toolchain/check/testdata/tuple/access/fail_negative_indexing.carbon b/toolchain/check/testdata/tuple/access/fail_negative_indexing.carbon index 962fdb512d250..634fc9e1f20f6 100644 --- a/toolchain/check/testdata/tuple/access/fail_negative_indexing.carbon +++ b/toolchain/check/testdata/tuple/access/fail_negative_indexing.carbon @@ -9,7 +9,7 @@ // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/tuple/access/fail_negative_indexing.carbon var a: (i32, i32) = (12, 6); -// CHECK:STDERR: fail_negative_indexing.carbon:[[@LINE+3]]:17: error: cannot access member of interface `Negate` in type `Core.IntLiteral` that does not implement that interface [MissingImplInMemberAccess] +// CHECK:STDERR: fail_negative_indexing.carbon:[[@LINE+3]]:17: error: cannot access member of interface `Core.Negate` in type `Core.IntLiteral` that does not implement that interface [MissingImplInMemberAccess] // CHECK:STDERR: var b: i32 = a.(-10); // CHECK:STDERR: ^~~ var b: i32 = a.(-10); diff --git a/toolchain/check/testdata/tuple/access/fail_non_int_indexing.carbon b/toolchain/check/testdata/tuple/access/fail_non_int_indexing.carbon index 0ef8d327f8459..2b41b07cc0569 100644 --- a/toolchain/check/testdata/tuple/access/fail_non_int_indexing.carbon +++ b/toolchain/check/testdata/tuple/access/fail_non_int_indexing.carbon @@ -12,7 +12,7 @@ var a: (i32, i32) = (12, 6); // CHECK:STDERR: fail_non_int_indexing.carbon:[[@LINE+6]]:17: error: cannot implicitly convert from `f64` to `Core.IntLiteral` [ImplicitAsConversionFailure] // CHECK:STDERR: var b: i32 = a.(2.6); // CHECK:STDERR: ^~~ -// CHECK:STDERR: fail_non_int_indexing.carbon:[[@LINE+3]]:17: note: type `f64` does not implement interface `ImplicitAs(Core.IntLiteral)` [MissingImplInMemberAccessNote] +// CHECK:STDERR: fail_non_int_indexing.carbon:[[@LINE+3]]:17: note: type `f64` does not implement interface `Core.ImplicitAs(Core.IntLiteral)` [MissingImplInMemberAccessNote] // CHECK:STDERR: var b: i32 = a.(2.6); // CHECK:STDERR: ^~~ var b: i32 = a.(2.6); diff --git a/toolchain/check/testdata/tuple/fail_element_type_mismatch.carbon b/toolchain/check/testdata/tuple/fail_element_type_mismatch.carbon index 1164116045b29..d211e442a27ed 100644 --- a/toolchain/check/testdata/tuple/fail_element_type_mismatch.carbon +++ b/toolchain/check/testdata/tuple/fail_element_type_mismatch.carbon @@ -11,7 +11,7 @@ // CHECK:STDERR: fail_element_type_mismatch.carbon:[[@LINE+6]]:21: error: cannot implicitly convert from `f64` to `i32` [ImplicitAsConversionFailure] // CHECK:STDERR: var x: (i32, i32) = (2, 65.89); // CHECK:STDERR: ^~~~~~~~~~ -// CHECK:STDERR: fail_element_type_mismatch.carbon:[[@LINE+3]]:21: note: type `f64` does not implement interface `ImplicitAs(i32)` [MissingImplInMemberAccessNote] +// CHECK:STDERR: fail_element_type_mismatch.carbon:[[@LINE+3]]:21: note: type `f64` does not implement interface `Core.ImplicitAs(i32)` [MissingImplInMemberAccessNote] // CHECK:STDERR: var x: (i32, i32) = (2, 65.89); // CHECK:STDERR: ^~~~~~~~~~ var x: (i32, i32) = (2, 65.89); diff --git a/toolchain/check/testdata/tuple/fail_type_assign.carbon b/toolchain/check/testdata/tuple/fail_type_assign.carbon index 82eee3054189c..34ba3ef294c86 100644 --- a/toolchain/check/testdata/tuple/fail_type_assign.carbon +++ b/toolchain/check/testdata/tuple/fail_type_assign.carbon @@ -11,7 +11,7 @@ // CHECK:STDERR: fail_type_assign.carbon:[[@LINE+6]]:18: error: cannot implicitly convert from `type` to `i32` [ImplicitAsConversionFailure] // CHECK:STDERR: var x: (i32, ) = (i32, ); // CHECK:STDERR: ^~~~~~~ -// CHECK:STDERR: fail_type_assign.carbon:[[@LINE+3]]:18: note: type `type` does not implement interface `ImplicitAs(i32)` [MissingImplInMemberAccessNote] +// CHECK:STDERR: fail_type_assign.carbon:[[@LINE+3]]:18: note: type `type` does not implement interface `Core.ImplicitAs(i32)` [MissingImplInMemberAccessNote] // CHECK:STDERR: var x: (i32, ) = (i32, ); // CHECK:STDERR: ^~~~~~~ var x: (i32, ) = (i32, ); diff --git a/toolchain/check/testdata/tuple/fail_value_as_type.carbon b/toolchain/check/testdata/tuple/fail_value_as_type.carbon index 5f6c878dd8cde..ad24527852d64 100644 --- a/toolchain/check/testdata/tuple/fail_value_as_type.carbon +++ b/toolchain/check/testdata/tuple/fail_value_as_type.carbon @@ -11,7 +11,7 @@ // CHECK:STDERR: fail_value_as_type.carbon:[[@LINE+6]]:8: error: cannot implicitly convert from `Core.IntLiteral` to `type` [ImplicitAsConversionFailure] // CHECK:STDERR: var x: (1, ); // CHECK:STDERR: ^~~~~ -// CHECK:STDERR: fail_value_as_type.carbon:[[@LINE+3]]:8: note: type `Core.IntLiteral` does not implement interface `ImplicitAs(type)` [MissingImplInMemberAccessNote] +// CHECK:STDERR: fail_value_as_type.carbon:[[@LINE+3]]:8: note: type `Core.IntLiteral` does not implement interface `Core.ImplicitAs(type)` [MissingImplInMemberAccessNote] // CHECK:STDERR: var x: (1, ); // CHECK:STDERR: ^~~~~ var x: (1, ); diff --git a/toolchain/check/testdata/tuple/import.carbon b/toolchain/check/testdata/tuple/import.carbon index 2ea5c9c50ac38..91ca8ab9db3db 100644 --- a/toolchain/check/testdata/tuple/import.carbon +++ b/toolchain/check/testdata/tuple/import.carbon @@ -45,10 +45,10 @@ var c_bad: C((1, 2, 3)) = F(); impl package Implicit; -// CHECK:STDERR: fail_bad_value.impl.carbon:[[@LINE+6]]:1: error: cannot implicitly convert from `C()` to `C()` [ImplicitAsConversionFailure] +// CHECK:STDERR: fail_bad_value.impl.carbon:[[@LINE+6]]:1: error: cannot implicitly convert from `C()` to `C()` [ImplicitAsConversionFailure] // CHECK:STDERR: var c_bad: C((3, 4)) = F(); // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~ -// CHECK:STDERR: fail_bad_value.impl.carbon:[[@LINE+3]]:1: note: type `C()` does not implement interface `ImplicitAs(C())` [MissingImplInMemberAccessNote] +// CHECK:STDERR: fail_bad_value.impl.carbon:[[@LINE+3]]:1: note: type `C()` does not implement interface `Core.ImplicitAs(C())` [MissingImplInMemberAccessNote] // CHECK:STDERR: var c_bad: C((3, 4)) = F(); // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~ var c_bad: C((3, 4)) = F(); diff --git a/toolchain/check/testdata/var/fail_storage_is_literal.carbon b/toolchain/check/testdata/var/fail_storage_is_literal.carbon index 3e55f28c4fced..f799ed7a0b969 100644 --- a/toolchain/check/testdata/var/fail_storage_is_literal.carbon +++ b/toolchain/check/testdata/var/fail_storage_is_literal.carbon @@ -12,7 +12,7 @@ fn Main() { // CHECK:STDERR: fail_storage_is_literal.carbon:[[@LINE+6]]:10: error: cannot implicitly convert from `Core.IntLiteral` to `type` [ImplicitAsConversionFailure] // CHECK:STDERR: var x: 1 = 1; // CHECK:STDERR: ^ - // CHECK:STDERR: fail_storage_is_literal.carbon:[[@LINE+3]]:10: note: type `Core.IntLiteral` does not implement interface `ImplicitAs(type)` [MissingImplInMemberAccessNote] + // CHECK:STDERR: fail_storage_is_literal.carbon:[[@LINE+3]]:10: note: type `Core.IntLiteral` does not implement interface `Core.ImplicitAs(type)` [MissingImplInMemberAccessNote] // CHECK:STDERR: var x: 1 = 1; // CHECK:STDERR: ^ var x: 1 = 1; diff --git a/toolchain/check/testdata/where_expr/constraints.carbon b/toolchain/check/testdata/where_expr/constraints.carbon index a005cf801d235..da847fe511118 100644 --- a/toolchain/check/testdata/where_expr/constraints.carbon +++ b/toolchain/check/testdata/where_expr/constraints.carbon @@ -45,7 +45,7 @@ library "[[@TEST_NAME]]"; // CHECK:STDERR: fail_left_of_impls_non_type.carbon:[[@LINE+7]]:32: error: cannot implicitly convert from `Core.IntLiteral` to `type` [ImplicitAsConversionFailure] // CHECK:STDERR: fn NonTypeImpls(U:! type where 7 impls type); // CHECK:STDERR: ^ -// CHECK:STDERR: fail_left_of_impls_non_type.carbon:[[@LINE+4]]:32: note: type `Core.IntLiteral` does not implement interface `ImplicitAs(type)` [MissingImplInMemberAccessNote] +// CHECK:STDERR: fail_left_of_impls_non_type.carbon:[[@LINE+4]]:32: note: type `Core.IntLiteral` does not implement interface `Core.ImplicitAs(type)` [MissingImplInMemberAccessNote] // CHECK:STDERR: fn NonTypeImpls(U:! type where 7 impls type); // CHECK:STDERR: ^ // CHECK:STDERR: @@ -58,7 +58,7 @@ library "[[@TEST_NAME]]"; // CHECK:STDERR: fail_right_of_impls_non_type.carbon:[[@LINE+7]]:44: error: cannot implicitly convert from `Core.IntLiteral` to `type` [ImplicitAsConversionFailure] // CHECK:STDERR: fn ImplsNonType(U:! type where .Self impls 7); // CHECK:STDERR: ^ -// CHECK:STDERR: fail_right_of_impls_non_type.carbon:[[@LINE+4]]:44: note: type `Core.IntLiteral` does not implement interface `ImplicitAs(type)` [MissingImplInMemberAccessNote] +// CHECK:STDERR: fail_right_of_impls_non_type.carbon:[[@LINE+4]]:44: note: type `Core.IntLiteral` does not implement interface `Core.ImplicitAs(type)` [MissingImplInMemberAccessNote] // CHECK:STDERR: fn ImplsNonType(U:! type where .Self impls 7); // CHECK:STDERR: ^ // CHECK:STDERR: @@ -89,7 +89,7 @@ fn DoesNotImplI() { // CHECK:STDERR: fail_todo_enforce_constraint.carbon:[[@LINE+11]]:3: error: cannot implicitly convert from `type` to `J where...` [ImplicitAsConversionFailure] // CHECK:STDERR: Impls(C); // CHECK:STDERR: ^~~~~~~~ - // CHECK:STDERR: fail_todo_enforce_constraint.carbon:[[@LINE+8]]:3: note: type `type` does not implement interface `ImplicitAs(J where...)` [MissingImplInMemberAccessNote] + // CHECK:STDERR: fail_todo_enforce_constraint.carbon:[[@LINE+8]]:3: note: type `type` does not implement interface `Core.ImplicitAs(J where...)` [MissingImplInMemberAccessNote] // CHECK:STDERR: Impls(C); // CHECK:STDERR: ^~~~~~~~ // CHECK:STDERR: fail_todo_enforce_constraint.carbon:[[@LINE-14]]:1: in import [InImport] @@ -107,7 +107,7 @@ fn NotEmptyStruct() { // CHECK:STDERR: fail_todo_enforce_constraint.carbon:[[@LINE+9]]:3: error: cannot implicitly convert from `type` to `J where...` [ImplicitAsConversionFailure] // CHECK:STDERR: EmptyStruct(C); // CHECK:STDERR: ^~~~~~~~~~~~~~ - // CHECK:STDERR: fail_todo_enforce_constraint.carbon:[[@LINE+6]]:3: note: type `type` does not implement interface `ImplicitAs(J where...)` [MissingImplInMemberAccessNote] + // CHECK:STDERR: fail_todo_enforce_constraint.carbon:[[@LINE+6]]:3: note: type `type` does not implement interface `Core.ImplicitAs(J where...)` [MissingImplInMemberAccessNote] // CHECK:STDERR: EmptyStruct(C); // CHECK:STDERR: ^~~~~~~~~~~~~~ // CHECK:STDERR: fail_todo_enforce_constraint.carbon:[[@LINE-10]]:1: note: while deducing parameters of generic declared here [DeductionGenericHere] diff --git a/toolchain/check/testdata/where_expr/equal_rewrite.carbon b/toolchain/check/testdata/where_expr/equal_rewrite.carbon index 21d6e3d66dd1c..6725d2a6c34cc 100644 --- a/toolchain/check/testdata/where_expr/equal_rewrite.carbon +++ b/toolchain/check/testdata/where_expr/equal_rewrite.carbon @@ -76,7 +76,7 @@ fn WithBool(R:! O where .P = bool) { // CHECK:STDERR: fail_rewrites_mismatch_right.carbon:[[@LINE+10]]:3: error: cannot implicitly convert from `O where .(O.P) = bool` to `O where .(O.P) = i32` [ImplicitAsConversionFailure] // CHECK:STDERR: WithInteger(R); // CHECK:STDERR: ^~~~~~~~~~~~~~ - // CHECK:STDERR: fail_rewrites_mismatch_right.carbon:[[@LINE+7]]:3: note: type `O where .(O.P) = bool` does not implement interface `ImplicitAs(O where .(O.P) = i32)` [MissingImplInMemberAccessNote] + // CHECK:STDERR: fail_rewrites_mismatch_right.carbon:[[@LINE+7]]:3: note: type `O where .(O.P) = bool` does not implement interface `Core.ImplicitAs(O where .(O.P) = i32)` [MissingImplInMemberAccessNote] // CHECK:STDERR: WithInteger(R); // CHECK:STDERR: ^~~~~~~~~~~~~~ // CHECK:STDERR: fail_rewrites_mismatch_right.carbon:[[@LINE-9]]:1: note: while deducing parameters of generic declared here [DeductionGenericHere] @@ -101,7 +101,7 @@ fn WithU(W:! S where .U = ()) { // CHECK:STDERR: fail_rewrites_mismatch_left.carbon:[[@LINE+10]]:3: error: cannot implicitly convert from `S where .(S.U) = ()` to `S where .(S.T) = ()` [ImplicitAsConversionFailure] // CHECK:STDERR: WithT(W); // CHECK:STDERR: ^~~~~~~~ - // CHECK:STDERR: fail_rewrites_mismatch_left.carbon:[[@LINE+7]]:3: note: type `S where .(S.U) = ()` does not implement interface `ImplicitAs(S where .(S.T) = ())` [MissingImplInMemberAccessNote] + // CHECK:STDERR: fail_rewrites_mismatch_left.carbon:[[@LINE+7]]:3: note: type `S where .(S.U) = ()` does not implement interface `Core.ImplicitAs(S where .(S.T) = ())` [MissingImplInMemberAccessNote] // CHECK:STDERR: WithT(W); // CHECK:STDERR: ^~~~~~~~ // CHECK:STDERR: fail_rewrites_mismatch_left.carbon:[[@LINE-9]]:1: note: while deducing parameters of generic declared here [DeductionGenericHere] @@ -122,7 +122,7 @@ fn Calls() { // CHECK:STDERR: fail_import_rewrites.carbon:[[@LINE+11]]:3: error: cannot implicitly convert from `type` to `N where .(N.P) = {}` [ImplicitAsConversionFailure] // CHECK:STDERR: Equal(bool); // CHECK:STDERR: ^~~~~~~~~~~ - // CHECK:STDERR: fail_import_rewrites.carbon:[[@LINE+8]]:3: note: type `type` does not implement interface `ImplicitAs(N where .(N.P) = {})` [MissingImplInMemberAccessNote] + // CHECK:STDERR: fail_import_rewrites.carbon:[[@LINE+8]]:3: note: type `type` does not implement interface `Core.ImplicitAs(N where .(N.P) = {})` [MissingImplInMemberAccessNote] // CHECK:STDERR: Equal(bool); // CHECK:STDERR: ^~~~~~~~~~~ // CHECK:STDERR: fail_import_rewrites.carbon:[[@LINE-10]]:1: in import [InImport] @@ -135,7 +135,7 @@ fn Calls() { // CHECK:STDERR: fail_import_rewrites.carbon:[[@LINE+11]]:3: error: cannot implicitly convert from `type` to `A where .(A.C) = () and .(A.B) = bool` [ImplicitAsConversionFailure] // CHECK:STDERR: NestedRewrite(i32); // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~ - // CHECK:STDERR: fail_import_rewrites.carbon:[[@LINE+8]]:3: note: type `type` does not implement interface `ImplicitAs(A where .(A.C) = () and .(A.B) = bool)` [MissingImplInMemberAccessNote] + // CHECK:STDERR: fail_import_rewrites.carbon:[[@LINE+8]]:3: note: type `type` does not implement interface `Core.ImplicitAs(A where .(A.C) = () and .(A.B) = bool)` [MissingImplInMemberAccessNote] // CHECK:STDERR: NestedRewrite(i32); // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~ // CHECK:STDERR: fail_import_rewrites.carbon:[[@LINE-23]]:1: in import [InImport] @@ -158,7 +158,7 @@ interface I { // CHECK:STDERR: fail_check_rewrite_constraints.carbon:[[@LINE+7]]:46: error: cannot implicitly convert from `Core.IntLiteral` to `type` [ImplicitAsConversionFailure] // CHECK:STDERR: fn RewriteTypeMismatch(X:! I where .Member = 2); // CHECK:STDERR: ^ -// CHECK:STDERR: fail_check_rewrite_constraints.carbon:[[@LINE+4]]:46: note: type `Core.IntLiteral` does not implement interface `ImplicitAs(type)` [MissingImplInMemberAccessNote] +// CHECK:STDERR: fail_check_rewrite_constraints.carbon:[[@LINE+4]]:46: note: type `Core.IntLiteral` does not implement interface `Core.ImplicitAs(type)` [MissingImplInMemberAccessNote] // CHECK:STDERR: fn RewriteTypeMismatch(X:! I where .Member = 2); // CHECK:STDERR: ^ // CHECK:STDERR: @@ -175,7 +175,7 @@ impl D as A {} // CHECK:STDERR: fail_todo_let.carbon:[[@LINE+7]]:1: error: cannot implicitly convert from `type` to `type where...` [ImplicitAsConversionFailure] // CHECK:STDERR: let B: type where .Self impls A = D; // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// CHECK:STDERR: fail_todo_let.carbon:[[@LINE+4]]:1: note: type `type` does not implement interface `ImplicitAs(type where...)` [MissingImplInMemberAccessNote] +// CHECK:STDERR: fail_todo_let.carbon:[[@LINE+4]]:1: note: type `type` does not implement interface `Core.ImplicitAs(type where...)` [MissingImplInMemberAccessNote] // CHECK:STDERR: let B: type where .Self impls A = D; // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // CHECK:STDERR: @@ -195,7 +195,7 @@ interface E { // CHECK:STDERR: fail_type_does_not_implement_where.carbon:[[@LINE+7]]:1: error: cannot implicitly convert from `type` to `E where .(E.G) = () and .(E.F) = bool` [ImplicitAsConversionFailure] // CHECK:STDERR: let H: (E where .F = bool and .G = ()) = f64; // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// CHECK:STDERR: fail_type_does_not_implement_where.carbon:[[@LINE+4]]:1: note: type `type` does not implement interface `ImplicitAs(E where .(E.G) = () and .(E.F) = bool)` [MissingImplInMemberAccessNote] +// CHECK:STDERR: fail_type_does_not_implement_where.carbon:[[@LINE+4]]:1: note: type `type` does not implement interface `Core.ImplicitAs(E where .(E.G) = () and .(E.F) = bool)` [MissingImplInMemberAccessNote] // CHECK:STDERR: let H: (E where .F = bool and .G = ()) = f64; // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // CHECK:STDERR: @@ -204,7 +204,7 @@ let H: (E where .F = bool and .G = ()) = f64; // CHECK:STDERR: fail_type_does_not_implement_where.carbon:[[@LINE+7]]:1: error: cannot implicitly convert from `type` to `E where .(E.G) = i32 and .(E.F) = {}` [ImplicitAsConversionFailure] // CHECK:STDERR: let J: ((E where .F = {}) where .G = i32) = bool; // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// CHECK:STDERR: fail_type_does_not_implement_where.carbon:[[@LINE+4]]:1: note: type `type` does not implement interface `ImplicitAs(E where .(E.G) = i32 and .(E.F) = {})` [MissingImplInMemberAccessNote] +// CHECK:STDERR: fail_type_does_not_implement_where.carbon:[[@LINE+4]]:1: note: type `type` does not implement interface `Core.ImplicitAs(E where .(E.G) = i32 and .(E.F) = {})` [MissingImplInMemberAccessNote] // CHECK:STDERR: let J: ((E where .F = {}) where .G = i32) = bool; // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // CHECK:STDERR: @@ -213,7 +213,7 @@ let J: ((E where .F = {}) where .G = i32) = bool; // CHECK:STDERR: fail_type_does_not_implement_where.carbon:[[@LINE+6]]:1: error: cannot implicitly convert from `type` to `E where .(E.F) = .(E.G)` [ImplicitAsConversionFailure] // CHECK:STDERR: let K: (E where .F = .Self.G) = bool; // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// CHECK:STDERR: fail_type_does_not_implement_where.carbon:[[@LINE+3]]:1: note: type `type` does not implement interface `ImplicitAs(E where .(E.F) = .(E.G))` [MissingImplInMemberAccessNote] +// CHECK:STDERR: fail_type_does_not_implement_where.carbon:[[@LINE+3]]:1: note: type `type` does not implement interface `Core.ImplicitAs(E where .(E.F) = .(E.G))` [MissingImplInMemberAccessNote] // CHECK:STDERR: let K: (E where .F = .Self.G) = bool; // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ let K: (E where .F = .Self.G) = bool; diff --git a/toolchain/check/testdata/while/fail_bad_condition.carbon b/toolchain/check/testdata/while/fail_bad_condition.carbon index c02bcb402fd69..c8836c528c306 100644 --- a/toolchain/check/testdata/while/fail_bad_condition.carbon +++ b/toolchain/check/testdata/while/fail_bad_condition.carbon @@ -12,7 +12,7 @@ fn While() { // CHECK:STDERR: fail_bad_condition.carbon:[[@LINE+6]]:9: error: cannot implicitly convert from `String` to `bool` [ImplicitAsConversionFailure] // CHECK:STDERR: while ("Hello") {} // CHECK:STDERR: ^~~~~~~~~ - // CHECK:STDERR: fail_bad_condition.carbon:[[@LINE+3]]:9: note: type `String` does not implement interface `ImplicitAs(bool)` [MissingImplInMemberAccessNote] + // CHECK:STDERR: fail_bad_condition.carbon:[[@LINE+3]]:9: note: type `String` does not implement interface `Core.ImplicitAs(bool)` [MissingImplInMemberAccessNote] // CHECK:STDERR: while ("Hello") {} // CHECK:STDERR: ^~~~~~~~~ while ("Hello") {} diff --git a/toolchain/sem_ir/stringify_type.cpp b/toolchain/sem_ir/stringify_type.cpp index 9429bc3767659..7a83f22913fdc 100644 --- a/toolchain/sem_ir/stringify_type.cpp +++ b/toolchain/sem_ir/stringify_type.cpp @@ -6,6 +6,7 @@ #include "toolchain/base/kind_switch.h" #include "toolchain/sem_ir/entity_with_params_base.h" +#include "toolchain/sem_ir/ids.h" namespace Carbon::SemIR { @@ -65,6 +66,29 @@ class StepStack { auto PushNameId(NameId name_id) -> void { steps.push_back({.kind = Name, .name_id = name_id}); } + auto PushQualifiedName(NameScopeId name_scope_id, NameId name_id) -> void { + PushNameId(name_id); + while (name_scope_id.is_valid() && name_scope_id != NameScopeId::Package) { + const auto& name_scope = sem_ir->name_scopes().Get(name_scope_id); + // TODO: Decide how to print unnamed scopes. + if (name_scope.name_id().is_valid()) { + PushString("."); + // TODO: For a generic scope, pass a SpecificId to this function and + // include the relevant arguments. + PushNameId(name_scope.name_id()); + } + name_scope_id = name_scope.parent_scope_id(); + } + } + auto PushEntityName(const EntityWithParamsBase& entity, + SpecificId specific_id) -> void { + PushSpecificId(entity, specific_id); + PushQualifiedName(entity.parent_scope_id, entity.name_id); + } + auto PushEntityName(EntityNameId entity_name_id) -> void { + const auto& entity_name = sem_ir->entity_names().Get(entity_name_id); + PushQualifiedName(entity_name.parent_scope_id, entity_name.name_id); + } auto PushTypeId(TypeId type_id) -> void { PushInstId(sem_ir->types().GetInstId(type_id)); } @@ -174,14 +198,12 @@ auto StringifyTypeExpr(const SemIR::File& sem_ir, InstId outer_inst_id) case ExportDecl::Kind: { auto name_id = untyped_inst.As().entity_name_id; - out << sem_ir.names().GetFormatted( - sem_ir.entity_names().Get(name_id).name_id); + step_stack.PushEntityName(name_id); break; } case CARBON_KIND(ClassType inst): { const auto& class_info = sem_ir.classes().Get(inst.class_id); - out << sem_ir.names().GetFormatted(class_info.name_id); - step_stack.PushSpecificId(class_info, inst.specific_id); + step_stack.PushEntityName(class_info, inst.specific_id); break; } case CARBON_KIND(ConstType inst): { @@ -246,8 +268,7 @@ auto StringifyTypeExpr(const SemIR::File& sem_ir, InstId outer_inst_id) const auto& impls = facet_type_info.impls_constraints[index]; const auto& interface_info = sem_ir.interfaces().Get(impls.interface_id); - step_stack.PushSpecificId(interface_info, impls.specific_id); - step_stack.PushNameId(interface_info.name_id); + step_stack.PushEntityName(interface_info, impls.specific_id); if (index > 0) { step_stack.PushString(" & "); } @@ -293,8 +314,7 @@ auto StringifyTypeExpr(const SemIR::File& sem_ir, InstId outer_inst_id) } case CARBON_KIND(ImportRefUnloaded inst): { if (inst.entity_name_id.is_valid()) { - auto name_id = sem_ir.entity_names().Get(inst.entity_name_id).name_id; - out << sem_ir.names().GetFormatted(name_id); + step_stack.PushEntityName(inst.entity_name_id); } else { out << ""; } @@ -345,7 +365,7 @@ auto StringifyTypeExpr(const SemIR::File& sem_ir, InstId outer_inst_id) step_stack.PushInstId(entity_inst_id); } step_stack.PushString("."); - step_stack.PushNameId(interface.name_id); + step_stack.PushEntityName(interface, impls_constraint->specific_id); step_stack.PushString(".("); } else { step_stack.PushTypeId(witness_type_id); @@ -373,8 +393,9 @@ auto StringifyTypeExpr(const SemIR::File& sem_ir, InstId outer_inst_id) break; } case CARBON_KIND(Namespace inst): { - out << sem_ir.names().GetFormatted( - sem_ir.name_scopes().Get(inst.name_scope_id).name_id()); + const auto& name_scope = sem_ir.name_scopes().Get(inst.name_scope_id); + step_stack.PushQualifiedName(name_scope.parent_scope_id(), + name_scope.name_id()); break; } case CARBON_KIND(PointerType inst): { From 61c0a8b676e14b633348cd97828bb5340cbc5ec9 Mon Sep 17 00:00:00 2001 From: Jon Ross-Perkins Date: Wed, 11 Dec 2024 10:16:38 -0800 Subject: [PATCH 3/7] Make more use of llvm STLExtras (#4668) This is essentially the result of looking at `.begin()` uses. We also frequently do `std::shuffle`, but unfortunately STLExtras doesn't provide a wrapper for that. --- common/array_stack.h | 2 +- common/command_line.cpp | 7 ++--- common/hashing_test.cpp | 33 +++++++++------------- common/raw_hashtable_benchmark_helpers.cpp | 8 +++--- testing/base/source_gen.cpp | 6 ++-- testing/file_test/autoupdate.h | 4 +-- toolchain/check/deduce.cpp | 2 +- toolchain/check/impl_lookup.cpp | 12 ++++---- toolchain/check/scope_stack.cpp | 5 ++-- toolchain/lex/lex.cpp | 5 ++-- toolchain/lex/numeric_literal.cpp | 5 ++-- toolchain/lex/tokenized_buffer.cpp | 11 ++++---- toolchain/parse/extract.cpp | 2 +- toolchain/sem_ir/facet_type_info.cpp | 10 +++---- toolchain/testing/coverage_helper.h | 4 +-- 15 files changed, 52 insertions(+), 64 deletions(-) diff --git a/common/array_stack.h b/common/array_stack.h index 9021c8a4bdb53..8fdbbfe47259c 100644 --- a/common/array_stack.h +++ b/common/array_stack.h @@ -78,7 +78,7 @@ class ArrayStack { auto AppendToTop(llvm::ArrayRef values) -> void { CARBON_CHECK(!array_offsets_.empty(), "Must call PushArray before PushValues."); - values_.append(values.begin(), values.end()); + llvm::append_range(values_, values); } // Returns the current number of values in all arrays. diff --git a/common/command_line.cpp b/common/command_line.cpp index f17878ce716ce..95134bed0aa1d 100644 --- a/common/command_line.cpp +++ b/common/command_line.cpp @@ -1070,10 +1070,9 @@ auto Parser::FinalizeParsedOptions() -> ErrorOr { // Sort the missing arguments by name to provide a stable and deterministic // error message. We know there can't be duplicate names because these came // from a may keyed on the name, so this provides a total ordering. - std::sort(missing_options.begin(), missing_options.end(), - [](const Arg* lhs, const Arg* rhs) { - return lhs->info.name < rhs->info.name; - }); + llvm::sort(missing_options, [](const Arg* lhs, const Arg* rhs) { + return lhs->info.name < rhs->info.name; + }); std::string error_str = "required options not provided: "; llvm::raw_string_ostream error(error_str); diff --git a/common/hashing_test.cpp b/common/hashing_test.cpp index c332db8ccac31..df8231abd7433 100644 --- a/common/hashing_test.cpp +++ b/common/hashing_test.cpp @@ -592,9 +592,9 @@ auto FindBitRangeCollisions(llvm::ArrayRef> hashes) // Now we sort by the extracted bit sequence so we can efficiently scan for // colliding bit patterns. - std::sort( - bits_and_indices.begin(), bits_and_indices.end(), - [](const auto& lhs, const auto& rhs) { return lhs.bits < rhs.bits; }); + llvm::sort(bits_and_indices, [](const auto& lhs, const auto& rhs) { + return lhs.bits < rhs.bits; + }); // Scan the sorted bit sequences we've extracted looking for collisions. We // count the total collisions, but we also track the number of individual @@ -635,16 +635,15 @@ auto FindBitRangeCollisions(llvm::ArrayRef> hashes) } // Sort by collision count for each hash. - std::sort(bits_and_indices.begin(), bits_and_indices.end(), - [&](const auto& lhs, const auto& rhs) { - return collision_counts[collision_map[lhs.index]] < - collision_counts[collision_map[rhs.index]]; - }); + llvm::sort(bits_and_indices, [&](const auto& lhs, const auto& rhs) { + return collision_counts[collision_map[lhs.index]] < + collision_counts[collision_map[rhs.index]]; + }); // And compute the median and max. int median = collision_counts [collision_map[bits_and_indices[bits_and_indices.size() / 2].index]]; - int max = *std::max_element(collision_counts.begin(), collision_counts.end()); + int max = *llvm::max_element(collision_counts); CARBON_CHECK(max == collision_counts[collision_map[bits_and_indices.back().index]]); return {.total = total, .median = median, .max = max}; @@ -672,11 +671,9 @@ auto AllByteStringsHashedAndSorted() { hashes.push_back({HashValue(s, TestSeed), s}); } - std::sort(hashes.begin(), hashes.end(), - [](const HashedString& lhs, const HashedString& rhs) { - return static_cast(lhs.hash) < - static_cast(rhs.hash); - }); + llvm::sort(hashes, [](const HashedString& lhs, const HashedString& rhs) { + return static_cast(lhs.hash) < static_cast(rhs.hash); + }); CheckNoDuplicateValues(hashes); return hashes; @@ -832,11 +829,9 @@ struct SparseHashTest : ::testing::Test { } } - std::sort(hashes.begin(), hashes.end(), - [](const HashedString& lhs, const HashedString& rhs) { - return static_cast(lhs.hash) < - static_cast(rhs.hash); - }); + llvm::sort(hashes, [](const HashedString& lhs, const HashedString& rhs) { + return static_cast(lhs.hash) < static_cast(rhs.hash); + }); CheckNoDuplicateValues(hashes); return hashes; diff --git a/common/raw_hashtable_benchmark_helpers.cpp b/common/raw_hashtable_benchmark_helpers.cpp index 1199748b35fb5..5ba7cd13b6536 100644 --- a/common/raw_hashtable_benchmark_helpers.cpp +++ b/common/raw_hashtable_benchmark_helpers.cpp @@ -349,10 +349,10 @@ auto DumpHashStatistics(llvm::ArrayRef keys) -> void { grouped_key_indices[hash_index].push_back(i); } ssize_t max_group_index = - std::max_element(grouped_key_indices.begin(), grouped_key_indices.end(), - [](const auto& lhs, const auto& rhs) { - return lhs.size() < rhs.size(); - }) - + llvm::max_element(grouped_key_indices, + [](const auto& lhs, const auto& rhs) { + return lhs.size() < rhs.size(); + }) - grouped_key_indices.begin(); // If the max number of collisions on the index is less than or equal to the diff --git a/testing/base/source_gen.cpp b/testing/base/source_gen.cpp index 673225320d810..819dccc6e3c47 100644 --- a/testing/base/source_gen.cpp +++ b/testing/base/source_gen.cpp @@ -206,7 +206,7 @@ auto SourceGen::ClassGenState::BuildClassAndTypeNames( int num_declared_types = num_types * type_use_params.declared_types_weight / type_weight_sum; for ([[maybe_unused]] auto _ : llvm::seq(num_declared_types / num_classes)) { - type_names_.append(class_names_.begin(), class_names_.end()); + llvm::append_range(type_names_, class_names_); } // Now append the remainder number of class names. This is where the class // names being un-shuffled is essential. We're going to have one extra @@ -389,8 +389,8 @@ auto SourceGen::GetIdentifiers(int number, int min_length, int max_length, number, min_length, max_length, uniform, [this](int length, int length_count, llvm::SmallVectorImpl& dest) { - auto length_idents = GetSingleLengthIdentifiers(length, length_count); - dest.append(length_idents.begin(), length_idents.end()); + llvm::append_range(dest, + GetSingleLengthIdentifiers(length, length_count)); }); return idents; diff --git a/testing/file_test/autoupdate.h b/testing/file_test/autoupdate.h index f69f5ac9a5ad0..cc206e48739a5 100644 --- a/testing/file_test/autoupdate.h +++ b/testing/file_test/autoupdate.h @@ -58,8 +58,8 @@ class FileTestAutoupdater { // initialization. stdout_(BuildCheckLines(stdout, "STDOUT")), stderr_(BuildCheckLines(stderr, "STDERR")), - any_attached_stdout_lines_(std::any_of( - stdout_.lines.begin(), stdout_.lines.end(), + any_attached_stdout_lines_(llvm::any_of( + stdout_.lines, [&](const CheckLine& line) { return line.line_number() != -1; })), non_check_line_(non_check_lines_.begin()) { for (const auto& replacement : line_number_replacements_) { diff --git a/toolchain/check/deduce.cpp b/toolchain/check/deduce.cpp index ddb44d4816bf6..c4cfe8e8f016a 100644 --- a/toolchain/check/deduce.cpp +++ b/toolchain/check/deduce.cpp @@ -272,7 +272,7 @@ DeductionContext::DeductionContext(Context& context, SemIR::LocId loc_id, // to substitute them into the function declaration. auto args = context.inst_blocks().Get( context.specifics().Get(enclosing_specific_id).args_id); - std::copy(args.begin(), args.end(), result_arg_ids_.begin()); + llvm::copy(args, result_arg_ids_.begin()); // TODO: Subst is linear in the length of the substitutions list. Change // it so we can pass in an array mapping indexes to substitutions instead. diff --git a/toolchain/check/impl_lookup.cpp b/toolchain/check/impl_lookup.cpp index e0dc94187ccd3..53bc43686033f 100644 --- a/toolchain/check/impl_lookup.cpp +++ b/toolchain/check/impl_lookup.cpp @@ -38,8 +38,7 @@ static auto FindAssociatedImportIRs(Context& context, // Push the contents of an instruction block onto our worklist. auto push_block = [&](SemIR::InstBlockId block_id) { if (block_id.is_valid()) { - auto block = context.inst_blocks().Get(block_id); - worklist.append(block.begin(), block.end()); + llvm::append_range(worklist, context.inst_blocks().Get(block_id)); } }; @@ -102,11 +101,10 @@ static auto FindAssociatedImportIRs(Context& context, } // Deduplicate. - std::sort(result.begin(), result.end(), - [](SemIR::ImportIRId a, SemIR::ImportIRId b) { - return a.index < b.index; - }); - result.erase(std::unique(result.begin(), result.end()), result.end()); + llvm::sort(result, [](SemIR::ImportIRId a, SemIR::ImportIRId b) { + return a.index < b.index; + }); + result.erase(llvm::unique(result), result.end()); return result; } diff --git a/toolchain/check/scope_stack.cpp b/toolchain/check/scope_stack.cpp index cccdeeebfd698..019c30714c8cd 100644 --- a/toolchain/check/scope_stack.cpp +++ b/toolchain/check/scope_stack.cpp @@ -129,9 +129,8 @@ auto ScopeStack::LookupInLexicalScopes(SemIR::NameId name_id) // Find the first non-lexical scope that is within the scope of the lexical // lookup result. - auto* first_non_lexical_scope = std::lower_bound( - non_lexical_scope_stack_.begin(), non_lexical_scope_stack_.end(), - lexical_results.back().scope_index, + auto* first_non_lexical_scope = llvm::lower_bound( + non_lexical_scope_stack_, lexical_results.back().scope_index, [](const NonLexicalScope& scope, ScopeIndex index) { return scope.scope_index < index; }); diff --git a/toolchain/lex/lex.cpp b/toolchain/lex/lex.cpp index f11c2b8fce1b0..8cfbb3c2eea4e 100644 --- a/toolchain/lex/lex.cpp +++ b/toolchain/lex/lex.cpp @@ -1555,9 +1555,8 @@ auto Lexer::DiagnoseAndFixMismatchedBrackets() -> void { } // Find the innermost matching opening symbol. - auto opening_it = std::find_if( - open_groups_.rbegin(), open_groups_.rend(), - [&](TokenIndex opening_token) { + auto opening_it = llvm::find_if( + llvm::reverse(open_groups_), [&](TokenIndex opening_token) { return buffer_.GetTokenInfo(opening_token).kind().closing_symbol() == kind; }); diff --git a/toolchain/lex/numeric_literal.cpp b/toolchain/lex/numeric_literal.cpp index d75de7e8881aa..8a197bf8a1ae4 100644 --- a/toolchain/lex/numeric_literal.cpp +++ b/toolchain/lex/numeric_literal.cpp @@ -185,9 +185,8 @@ static auto ParseInt(llvm::StringRef digits, NumericLiteral::Radix radix, llvm::SmallString<32> cleaned; if (needs_cleaning) { cleaned.reserve(digits.size()); - std::remove_copy_if(digits.begin(), digits.end(), - std::back_inserter(cleaned), - [](char c) { return c == '_' || c == '.'; }); + llvm::copy_if(digits, std::back_inserter(cleaned), + [](char c) { return c != '_' && c != '.'; }); digits = cleaned; } diff --git a/toolchain/lex/tokenized_buffer.cpp b/toolchain/lex/tokenized_buffer.cpp index 6aec49a05cec7..e4a7652033438 100644 --- a/toolchain/lex/tokenized_buffer.cpp +++ b/toolchain/lex/tokenized_buffer.cpp @@ -317,10 +317,9 @@ auto TokenizedBuffer::PrintToken(llvm::raw_ostream& output_stream, auto TokenizedBuffer::FindLineIndex(int32_t byte_offset) const -> LineIndex { CARBON_DCHECK(!line_infos_.empty()); const auto* line_it = - std::partition_point(line_infos_.begin(), line_infos_.end(), - [byte_offset](LineInfo line_info) { - return line_info.start <= byte_offset; - }); + llvm::partition_point(line_infos_, [byte_offset](LineInfo line_info) { + return line_info.start <= byte_offset; + }); --line_it; // If this isn't the first line but it starts past the end of the source, then @@ -386,8 +385,8 @@ auto TokenizedBuffer::SourceBufferDiagnosticConverter::ConvertLoc( int32_t offset = loc - buffer_->source_->text().begin(); // Find the first line starting after the given location. - const auto* next_line_it = std::partition_point( - buffer_->line_infos_.begin(), buffer_->line_infos_.end(), + const auto* next_line_it = llvm::partition_point( + buffer_->line_infos_, [offset](const LineInfo& line) { return line.start <= offset; }); // Step back one line to find the line containing the given position. diff --git a/toolchain/parse/extract.cpp b/toolchain/parse/extract.cpp index 7106754d9fef7..ae4b87d0161dd 100644 --- a/toolchain/parse/extract.cpp +++ b/toolchain/parse/extract.cpp @@ -201,7 +201,7 @@ auto NodeExtractor::MatchesNodeIdOneOf( *trace_ << "\n"; } return false; - } else if (std::find(kinds.begin(), kinds.end(), node_kind) == kinds.end()) { + } else if (llvm::find(kinds, node_kind) == kinds.end()) { if (trace_) { *trace_ << "NodeIdOneOf error: wrong kind " << node_kind << ", expected "; trace_kinds(); diff --git a/toolchain/sem_ir/facet_type_info.cpp b/toolchain/sem_ir/facet_type_info.cpp index 667c3070a88b9..b132a75f8feba 100644 --- a/toolchain/sem_ir/facet_type_info.cpp +++ b/toolchain/sem_ir/facet_type_info.cpp @@ -7,14 +7,14 @@ namespace Carbon::SemIR { template -static auto SortAndDeduplicate(VecT* vec) -> void { - std::sort(vec->begin(), vec->end()); - vec->erase(std::unique(vec->begin(), vec->end()), vec->end()); +static auto SortAndDeduplicate(VecT& vec) -> void { + llvm::sort(vec); + vec.erase(llvm::unique(vec), vec.end()); } auto FacetTypeInfo::Canonicalize() -> void { - SortAndDeduplicate(&impls_constraints); - SortAndDeduplicate(&rewrite_constraints); + SortAndDeduplicate(impls_constraints); + SortAndDeduplicate(rewrite_constraints); } auto FacetTypeInfo::Print(llvm::raw_ostream& out) const -> void { diff --git a/toolchain/testing/coverage_helper.h b/toolchain/testing/coverage_helper.h index e91dc5792c999..135ddb72f7345 100644 --- a/toolchain/testing/coverage_helper.h +++ b/toolchain/testing/coverage_helper.h @@ -64,14 +64,14 @@ auto TestKindCoverage(const std::string& manifest_path, constexpr llvm::StringLiteral Bullet = "\n - "; - std::sort(missing_kinds.begin(), missing_kinds.end()); + llvm::sort(missing_kinds); EXPECT_TRUE(missing_kinds.empty()) << "Some kinds have no tests:" << Bullet << llvm::join(missing_kinds, Bullet); llvm::SmallVector unexpected_matches; covered_kinds.ForEach( [&](const std::string& match) { unexpected_matches.push_back(match); }); - std::sort(unexpected_matches.begin(), unexpected_matches.end()); + llvm::sort(unexpected_matches); EXPECT_TRUE(unexpected_matches.empty()) << "Matched things that aren't in the kind list:" << Bullet << llvm::join(unexpected_matches, Bullet); From 042ac394260c01aa1c1e401c1783519d5a8ce5cd Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 11 Dec 2024 10:53:58 -0800 Subject: [PATCH 4/7] Pacify CHECK failure on invalid code. (#4665) Found by fuzzer. --------- Co-authored-by: Jon Ross-Perkins --- toolchain/check/handle_impl.cpp | 9 ++- .../impl/no_prelude/error_recovery.carbon | 67 +++++++++++++++++++ 2 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 toolchain/check/testdata/impl/no_prelude/error_recovery.carbon diff --git a/toolchain/check/handle_impl.cpp b/toolchain/check/handle_impl.cpp index 6e9fd86789116..33ebb26de0999 100644 --- a/toolchain/check/handle_impl.cpp +++ b/toolchain/check/handle_impl.cpp @@ -202,12 +202,17 @@ static auto PopImplIntroducerAndParamsAsNameComponent( context.node_stack().PopWithNodeIdIf(); if (implicit_param_patterns_id) { - // Emit the `forall` match. This shouldn't produce any `Call` params, + // Emit the `forall` match. This shouldn't produce any valid `Call` params, // because `impl`s are never actually called at runtime. auto call_params_id = CalleePatternMatch(context, *implicit_param_patterns_id, SemIR::InstBlockId::Invalid, SemIR::InstId::Invalid); - CARBON_CHECK(call_params_id == SemIR::InstBlockId::Empty); + CARBON_CHECK(call_params_id == SemIR::InstBlockId::Empty || + llvm::all_of(context.inst_blocks().Get(call_params_id), + [](SemIR::InstId inst_id) { + return inst_id == + SemIR::ErrorInst::SingletonInstId; + })); } Parse::NodeId first_param_node_id = diff --git a/toolchain/check/testdata/impl/no_prelude/error_recovery.carbon b/toolchain/check/testdata/impl/no_prelude/error_recovery.carbon new file mode 100644 index 0000000000000..a0dbffd769ddd --- /dev/null +++ b/toolchain/check/testdata/impl/no_prelude/error_recovery.carbon @@ -0,0 +1,67 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/impl/no_prelude/error_recovery.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/impl/no_prelude/error_recovery.carbon + +// --- fail_fuzz_crash.carbon + +class C {} +interface I {} + +// CHECK:STDERR: fail_fuzz_crash.carbon:[[@LINE+3]]:14: error: parameters of generic types must be constant [GenericParamMustBeConstant] +// CHECK:STDERR: impl forall [T: type] C as I { } +// CHECK:STDERR: ^~~~~~~ +impl forall [T: type] C as I { } + +// CHECK:STDOUT: --- fail_fuzz_crash.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %C: type = class_type @C [template] +// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [template] +// CHECK:STDOUT: %complete_type: = complete_type_witness %empty_struct_type [template] +// CHECK:STDOUT: %I.type: type = facet_type <@I> [template] +// CHECK:STDOUT: %Self: %I.type = bind_symbolic_name Self, 0 [symbolic] +// CHECK:STDOUT: %interface: = interface_witness () [template] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace [template] { +// CHECK:STDOUT: .C = %C.decl +// CHECK:STDOUT: .I = %I.decl +// CHECK:STDOUT: } +// CHECK:STDOUT: %C.decl: type = class_decl @C [template = constants.%C] {} {} +// CHECK:STDOUT: %I.decl: type = interface_decl @I [template = constants.%I.type] {} {} +// CHECK:STDOUT: impl_decl @impl [template] {} { +// CHECK:STDOUT: %C.ref: type = name_ref C, file.%C.decl [template = constants.%C] +// CHECK:STDOUT: %I.ref: type = name_ref I, file.%I.decl [template = constants.%I.type] +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: interface @I { +// CHECK:STDOUT: %Self: %I.type = bind_symbolic_name Self, 0 [symbolic = constants.%Self] +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = %Self +// CHECK:STDOUT: witness = () +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: impl @impl: %C.ref as %I.ref { +// CHECK:STDOUT: %interface: = interface_witness () [template = constants.%interface] +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: witness = %interface +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @C { +// CHECK:STDOUT: %complete_type: = complete_type_witness %empty_struct_type [template = constants.%complete_type] +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = constants.%C +// CHECK:STDOUT: complete_type_witness = %complete_type +// CHECK:STDOUT: } +// CHECK:STDOUT: From 758b6c42ba0a610150fe96e95663eac552941c0e Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 11 Dec 2024 14:34:10 -0800 Subject: [PATCH 5/7] Produce a note indicating where the specific was used from if monomorphization fails. (#4662) Also fix a bug in `Context::GetClassType` that previously tried to complete the class type before returning it. That's not correct -- `GetCompleteTypeImpl` is only appropriate for cases where the type can trivially be completed and completing it can't fail -- and led to infinite recursion with this change because we would call `GetClassType` when producing a diagnostic if completing that class type failed. --- toolchain/check/check_unit.cpp | 5 +- toolchain/check/context.cpp | 63 ++++++----------- toolchain/check/decl_name_stack.cpp | 11 +-- toolchain/check/deduce.cpp | 2 +- toolchain/check/eval.cpp | 67 ++++++++++++++----- toolchain/check/eval.h | 3 +- toolchain/check/generic.cpp | 53 ++++++++++++--- toolchain/check/generic.h | 19 ++++-- toolchain/check/impl.cpp | 6 +- toolchain/check/impl_lookup.cpp | 2 +- toolchain/check/import_ref.cpp | 5 +- toolchain/check/subst.cpp | 4 +- .../function/generic/resolve_used.carbon | 3 + .../testdata/generic/complete_type.carbon | 26 ++++--- toolchain/diagnostics/diagnostic_kind.def | 1 + toolchain/sem_ir/stringify_type.cpp | 11 ++- 16 files changed, 179 insertions(+), 102 deletions(-) diff --git a/toolchain/check/check_unit.cpp b/toolchain/check/check_unit.cpp index 3d3e8d4bd8d64..54a5e5f9e8d76 100644 --- a/toolchain/check/check_unit.cpp +++ b/toolchain/check/check_unit.cpp @@ -402,7 +402,10 @@ auto CheckUnit::CheckRequiredDefinitions() -> void { CARBON_FATAL("TODO: Support interfaces in DiagnoseMissingDefinitions"); } case CARBON_KIND(SemIR::SpecificFunction specific_function): { - if (!ResolveSpecificDefinition(context_, + // TODO: Track a location for the use. In general we may want to track a + // list of enclosing locations if this was used from a generic. + SemIRLoc use_loc = decl_inst_id; + if (!ResolveSpecificDefinition(context_, use_loc, specific_function.specific_id)) { CARBON_DIAGNOSTIC(MissingGenericFunctionDefinition, Error, "use of undefined generic function"); diff --git a/toolchain/check/context.cpp b/toolchain/check/context.cpp index 5bb666cd0e401..adab63ebe771d 100644 --- a/toolchain/check/context.cpp +++ b/toolchain/check/context.cpp @@ -228,47 +228,21 @@ auto Context::DiagnoseNameNotFound(SemIRLoc loc, SemIR::NameId name_id) emitter_->Emit(loc, NameNotFound, name_id); } -// Given an instruction associated with a scope and a `SpecificId` for that -// scope, returns an instruction that describes the specific scope. -static auto GetInstForSpecificScope(Context& context, SemIR::InstId inst_id, - SemIR::SpecificId specific_id) - -> SemIR::InstId { - if (!specific_id.is_valid()) { - return inst_id; - } - auto inst = context.insts().Get(inst_id); - CARBON_KIND_SWITCH(inst) { - case CARBON_KIND(SemIR::ClassDecl class_decl): { - return context.types().GetInstId( - context.GetClassType(class_decl.class_id, specific_id)); - } - case CARBON_KIND(SemIR::InterfaceDecl interface_decl): { - return context.types().GetInstId( - context.GetInterfaceType(interface_decl.interface_id, specific_id)); - } - default: { - // Don't know how to form a specific for this generic scope. - // TODO: Handle more cases. - return SemIR::InstId::Invalid; - } - } -} - auto Context::DiagnoseMemberNameNotFound( SemIRLoc loc, SemIR::NameId name_id, llvm::ArrayRef lookup_scopes) -> void { if (lookup_scopes.size() == 1 && lookup_scopes.front().name_scope_id.is_valid()) { - const auto& scope = name_scopes().Get(lookup_scopes.front().name_scope_id); - if (auto specific_inst_id = GetInstForSpecificScope( - *this, scope.inst_id(), lookup_scopes.front().specific_id); - specific_inst_id.is_valid()) { - CARBON_DIAGNOSTIC(MemberNameNotFoundInScope, Error, - "member name `{0}` not found in {1}", SemIR::NameId, - InstIdAsType); - emitter_->Emit(loc, MemberNameNotFoundInScope, name_id, specific_inst_id); - return; - } + auto specific_id = lookup_scopes.front().specific_id; + auto scope_inst_id = + specific_id.is_valid() + ? GetInstForSpecific(*this, specific_id) + : name_scopes().Get(lookup_scopes.front().name_scope_id).inst_id(); + CARBON_DIAGNOSTIC(MemberNameNotFoundInScope, Error, + "member name `{0}` not found in {1}", SemIR::NameId, + InstIdAsType); + emitter_->Emit(loc, MemberNameNotFoundInScope, name_id, scope_inst_id); + return; } CARBON_DIAGNOSTIC(MemberNameNotFound, Error, "member name `{0}` not found", @@ -895,8 +869,9 @@ namespace { // complete. class TypeCompleter { public: - TypeCompleter(Context& context, Context::BuildDiagnosticFn diagnoser) - : context_(context), diagnoser_(diagnoser) {} + TypeCompleter(Context& context, SemIRLoc loc, + Context::BuildDiagnosticFn diagnoser) + : context_(context), loc_(loc), diagnoser_(diagnoser) {} // Attempts to complete the given type. Returns true if it is now complete, // false if it could not be completed. @@ -1009,7 +984,7 @@ class TypeCompleter { return false; } if (inst.specific_id.is_valid()) { - ResolveSpecificDefinition(context_, inst.specific_id); + ResolveSpecificDefinition(context_, loc_, inst.specific_id); } if (auto adapted_type_id = class_info.GetAdaptedType(context_.sem_ir(), inst.specific_id); @@ -1281,19 +1256,21 @@ class TypeCompleter { Context& context_; llvm::SmallVector work_list_; + SemIRLoc loc_; Context::BuildDiagnosticFn diagnoser_; }; } // namespace auto Context::TryToCompleteType(SemIR::TypeId type_id) -> bool { - return TypeCompleter(*this, nullptr).Complete(type_id); + // TODO: We need a location here in case we need to instantiate a class type. + return TypeCompleter(*this, SemIR::LocId::Invalid, nullptr).Complete(type_id); } auto Context::RequireCompleteType(SemIR::TypeId type_id, SemIR::LocId loc_id, BuildDiagnosticFn diagnoser) -> bool { CARBON_CHECK(diagnoser); - if (!TypeCompleter(*this, diagnoser).Complete(type_id)) { + if (!TypeCompleter(*this, loc_id, diagnoser).Complete(type_id)) { return false; } @@ -1358,7 +1335,7 @@ auto Context::RequireDefinedType(SemIR::TypeId type_id, SemIR::LocId loc_id, } if (interface.specific_id.is_valid()) { - ResolveSpecificDefinition(*this, interface.specific_id); + ResolveSpecificDefinition(*this, loc_id, interface.specific_id); } } // TODO: Finish facet type resolution. @@ -1443,7 +1420,7 @@ auto Context::GetSingletonType(SemIR::InstId singleton_id) -> SemIR::TypeId { auto Context::GetClassType(SemIR::ClassId class_id, SemIR::SpecificId specific_id) -> SemIR::TypeId { - return GetCompleteTypeImpl(*this, class_id, specific_id); + return GetTypeImpl(*this, class_id, specific_id); } auto Context::GetFunctionType(SemIR::FunctionId fn_id, diff --git a/toolchain/check/decl_name_stack.cpp b/toolchain/check/decl_name_stack.cpp index c8b795e6243c8..ae98f100ebfc0 100644 --- a/toolchain/check/decl_name_stack.cpp +++ b/toolchain/check/decl_name_stack.cpp @@ -115,7 +115,8 @@ auto DeclNameStack::Restore(SuspendedName sus) -> void { // Reattempt to resolve the definition of the specific. The generic might // have been defined after we suspended this scope. if (suspended_scope.entry.specific_id.is_valid()) { - ResolveSpecificDefinition(*context_, suspended_scope.entry.specific_id); + ResolveSpecificDefinition(*context_, sus.name_context.loc_id, + suspended_scope.entry.specific_id); } context_->scope_stack().Restore(std::move(suspended_scope)); @@ -192,7 +193,7 @@ auto DeclNameStack::LookupOrAddName(NameContext name_context, // `fn Class(T:! type).F(n: i32)` we will push the scope for `Class(T:! type)` // between the scope containing the declaration of `T` and the scope // containing the declaration of `n`. -static auto PushNameQualifierScope(Context& context, +static auto PushNameQualifierScope(Context& context, SemIRLoc loc, SemIR::InstId scope_inst_id, SemIR::NameScopeId scope_id, SemIR::SpecificId specific_id, @@ -203,7 +204,7 @@ static auto PushNameQualifierScope(Context& context, // When declaring a member of a generic, resolve the self specific. if (specific_id.is_valid()) { - ResolveSpecificDefinition(context, specific_id); + ResolveSpecificDefinition(context, loc, specific_id); } context.scope_stack().Push(scope_inst_id, scope_id, specific_id, has_error); @@ -229,8 +230,8 @@ auto DeclNameStack::ApplyNameQualifier(const NameComponent& name) -> void { // Resolve the qualifier as a scope and enter the new scope. auto [scope_id, specific_id] = ResolveAsScope(name_context, name); if (scope_id.is_valid()) { - PushNameQualifierScope(*context_, name_context.resolved_inst_id, scope_id, - specific_id, + PushNameQualifierScope(*context_, name_context.loc_id, + name_context.resolved_inst_id, scope_id, specific_id, context_->name_scopes().Get(scope_id).has_error()); name_context.parent_scope_id = scope_id; } else { diff --git a/toolchain/check/deduce.cpp b/toolchain/check/deduce.cpp index c4cfe8e8f016a..0ee531c9bcdc9 100644 --- a/toolchain/check/deduce.cpp +++ b/toolchain/check/deduce.cpp @@ -505,7 +505,7 @@ auto DeductionContext::MakeSpecific() -> SemIR::SpecificId { // TODO: Convert the deduced values to the types of the bindings. return Check::MakeSpecific( - context(), generic_id_, + context(), loc_id_, generic_id_, context().inst_blocks().AddCanonical(result_arg_ids_)); } diff --git a/toolchain/check/eval.cpp b/toolchain/check/eval.cpp index e195dca3d27b1..b70ac1d0dcd9b 100644 --- a/toolchain/check/eval.cpp +++ b/toolchain/check/eval.cpp @@ -31,13 +31,32 @@ struct SpecificEvalInfo { class EvalContext { public: explicit EvalContext( - Context& context, + Context& context, SemIRLoc fallback_loc, SemIR::SpecificId specific_id = SemIR::SpecificId::Invalid, std::optional specific_eval_info = std::nullopt) : context_(context), + fallback_loc_(fallback_loc), specific_id_(specific_id), specific_eval_info_(specific_eval_info) {} + // Gets the location to use for diagnostics if a better location is + // unavailable. + // TODO: This is also sometimes unavailable. + auto fallback_loc() const -> SemIRLoc { return fallback_loc_; } + + // Returns a location to use to point at an instruction in a diagnostic, given + // a list of instructions that might have an attached location. This is the + // location of the first instruction in the list that has a location if there + // is one, and otherwise the fallback location. + auto GetDiagnosticLoc(llvm::ArrayRef inst_ids) -> SemIRLoc { + for (auto inst_id : inst_ids) { + if (inst_id.is_valid() && context_.insts().GetLocId(inst_id).is_valid()) { + return inst_id; + } + } + return fallback_loc_; + } + // Gets the value of the specified compile-time binding in this context. // Returns `Invalid` if the value is not fixed in this context. auto GetCompileTimeBindValue(SemIR::CompileTimeBindIndex bind_index) @@ -161,6 +180,8 @@ class EvalContext { private: // The type-checking context in which we're performing evaluation. Context& context_; + // The location to use for diagnostics when a better location isn't available. + SemIRLoc fallback_loc_; // The specific that we are evaluating within. SemIR::SpecificId specific_id_; // If we are currently evaluating an eval block for `specific_id_`, @@ -419,7 +440,8 @@ static auto GetConstantValue(EvalContext& eval_context, if (args_id == specific.args_id) { return specific_id; } - return MakeSpecific(eval_context.context(), specific.generic_id, args_id); + return MakeSpecific(eval_context.context(), eval_context.fallback_loc(), + specific.generic_id, args_id); } // Like `GetConstantValue` but does a `FacetTypeId` -> `FacetTypeInfo` @@ -602,7 +624,7 @@ static auto PerformArrayIndex(EvalContext& eval_context, SemIR::ArrayIndex inst) "array index `{0}` is past the end of type {1}", TypedInt, SemIR::TypeId); eval_context.emitter().Emit( - inst.index_id, ArrayIndexOutOfBounds, + eval_context.GetDiagnosticLoc(inst.index_id), ArrayIndexOutOfBounds, {.type = index->type_id, .value = index_val}, aggregate_type_id); return SemIR::ErrorInst::SingletonConstantId; } @@ -1336,7 +1358,7 @@ static auto TryEvalInstInContext(EvalContext& eval_context, CARBON_DIAGNOSTIC(ArrayBoundNegative, Error, "array bound of {0} is negative", TypedInt); eval_context.emitter().Emit( - bound_id, ArrayBoundNegative, + eval_context.GetDiagnosticLoc(bound_id), ArrayBoundNegative, {.type = int_bound->type_id, .value = bound_val}); return false; } @@ -1344,7 +1366,7 @@ static auto TryEvalInstInContext(EvalContext& eval_context, CARBON_DIAGNOSTIC(ArrayBoundTooLarge, Error, "array bound of {0} is too large", TypedInt); eval_context.emitter().Emit( - bound_id, ArrayBoundTooLarge, + eval_context.GetDiagnosticLoc(bound_id), ArrayBoundTooLarge, {.type = int_bound->type_id, .value = bound_val}); return false; } @@ -1393,7 +1415,8 @@ static auto TryEvalInstInContext(EvalContext& eval_context, [&](SemIR::IntType result) { return ValidateIntType( eval_context.context(), - inst_id.is_valid() ? inst_id : int_type.bit_width_id, result); + eval_context.GetDiagnosticLoc({inst_id, int_type.bit_width_id}), + result); }, &SemIR::IntType::bit_width_id); } @@ -1405,7 +1428,9 @@ static auto TryEvalInstInContext(EvalContext& eval_context, eval_context, inst, [&](SemIR::FloatType result) { return ValidateFloatType(eval_context.context(), - float_type.bit_width_id, result); + eval_context.GetDiagnosticLoc( + {inst_id, float_type.bit_width_id}), + result); }, &SemIR::FloatType::bit_width_id); } @@ -1568,7 +1593,8 @@ static auto TryEvalInstInContext(EvalContext& eval_context, } case CARBON_KIND(SemIR::Call call): { - return MakeConstantForCall(eval_context, inst_id, call); + return MakeConstantForCall(eval_context, + eval_context.GetDiagnosticLoc(inst_id), call); } // TODO: These need special handling. @@ -1785,7 +1811,8 @@ static auto TryEvalInstInContext(EvalContext& eval_context, "{0} evaluates to incomplete type {1}", SemIR::TypeId, SemIR::TypeId); return eval_context.emitter().Build( - inst_id, IncompleteTypeInMonomorphization, + eval_context.GetDiagnosticLoc(inst_id), + IncompleteTypeInMonomorphization, require_complete.complete_type_id, complete_type_id); }); if (complete_type_id == SemIR::ErrorInst::SingletonTypeId) { @@ -1842,11 +1869,12 @@ static auto TryEvalInstInContext(EvalContext& eval_context, auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst) -> SemIR::ConstantId { - EvalContext eval_context(context); + EvalContext eval_context(context, inst_id); return TryEvalInstInContext(eval_context, inst_id, inst); } -auto TryEvalBlockForSpecific(Context& context, SemIR::SpecificId specific_id, +auto TryEvalBlockForSpecific(Context& context, SemIRLoc loc, + SemIR::SpecificId specific_id, SemIR::GenericInstIndex::Region region) -> SemIR::InstBlockId { auto generic_id = context.specifics().Get(specific_id).generic_id; @@ -1856,20 +1884,27 @@ auto TryEvalBlockForSpecific(Context& context, SemIR::SpecificId specific_id, llvm::SmallVector result; result.resize(eval_block.size(), SemIR::InstId::Invalid); - EvalContext eval_context(context, specific_id, + EvalContext eval_context(context, loc, specific_id, SpecificEvalInfo{ .region = region, .values = result, }); + DiagnosticAnnotationScope annotate_diagnostics( + &context.emitter(), [&](auto& builder) { + CARBON_DIAGNOSTIC(ResolvingSpecificHere, Note, "in {0} used here", + InstIdAsType); + if (loc.is_inst_id && !loc.inst_id.is_valid()) { + return; + } + builder.Note(loc, ResolvingSpecificHere, + GetInstForSpecific(context, specific_id)); + }); + for (auto [i, inst_id] : llvm::enumerate(eval_block)) { auto const_id = TryEvalInstInContext(eval_context, inst_id, context.insts().Get(inst_id)); result[i] = context.constant_values().GetInstId(const_id); - - // TODO: If this becomes possible through monomorphization failure, produce - // a diagnostic and put `SemIR::ErrorInst::SingletonInstId` in the table - // entry. CARBON_CHECK(result[i].is_valid()); } diff --git a/toolchain/check/eval.h b/toolchain/check/eval.h index d56a41ba4ff7c..5c444969eddbe 100644 --- a/toolchain/check/eval.h +++ b/toolchain/check/eval.h @@ -20,7 +20,8 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst) // Evaluates the eval block for a region of a specific. Produces a block // containing the evaluated constant values of the instructions in the eval // block. -auto TryEvalBlockForSpecific(Context& context, SemIR::SpecificId specific_id, +auto TryEvalBlockForSpecific(Context& context, SemIRLoc loc, + SemIR::SpecificId specific_id, SemIR::GenericInstIndex::Region region) -> SemIR::InstBlockId; diff --git a/toolchain/check/generic.cpp b/toolchain/check/generic.cpp index 7f2c297385174..ca37cc70d5cc8 100644 --- a/toolchain/check/generic.cpp +++ b/toolchain/check/generic.cpp @@ -5,11 +5,13 @@ #include "toolchain/check/generic.h" #include "common/map.h" +#include "toolchain/base/kind_switch.h" #include "toolchain/check/eval.h" #include "toolchain/check/generic_region_stack.h" #include "toolchain/check/subst.h" #include "toolchain/sem_ir/ids.h" #include "toolchain/sem_ir/inst.h" +#include "toolchain/sem_ir/typed_insts.h" namespace Carbon::Check { @@ -341,7 +343,7 @@ auto FinishGenericDecl(Context& context, SemIR::InstId decl_id) context.generic_region_stack().Pop(); context.generics().Get(generic_id).decl_block_id = decl_block_id; - auto self_specific_id = MakeSelfSpecific(context, generic_id); + auto self_specific_id = MakeSelfSpecific(context, decl_id, generic_id); context.generics().Get(generic_id).self_specific_id = self_specific_id; return generic_id; } @@ -371,15 +373,16 @@ auto FinishGenericDefinition(Context& context, SemIR::GenericId generic_id) context.generic_region_stack().Pop(); } -auto MakeSpecific(Context& context, SemIR::GenericId generic_id, +auto MakeSpecific(Context& context, SemIRLoc loc, SemIR::GenericId generic_id, SemIR::InstBlockId args_id) -> SemIR::SpecificId { auto specific_id = context.specifics().GetOrAdd(generic_id, args_id); // If this is the first time we've formed this specific, evaluate its decl // block to form information about the specific. if (!context.specifics().Get(specific_id).decl_block_id.is_valid()) { - auto decl_block_id = TryEvalBlockForSpecific( - context, specific_id, SemIR::GenericInstIndex::Region::Declaration); + auto decl_block_id = + TryEvalBlockForSpecific(context, loc, specific_id, + SemIR::GenericInstIndex::Region::Declaration); // Note that TryEvalBlockForSpecific may reallocate the list of specifics, // so re-lookup the specific here. context.specifics().Get(specific_id).decl_block_id = decl_block_id; @@ -388,8 +391,8 @@ auto MakeSpecific(Context& context, SemIR::GenericId generic_id, return specific_id; } -auto MakeSelfSpecific(Context& context, SemIR::GenericId generic_id) - -> SemIR::SpecificId { +auto MakeSelfSpecific(Context& context, SemIRLoc loc, + SemIR::GenericId generic_id) -> SemIR::SpecificId { if (!generic_id.is_valid()) { return SemIR::SpecificId::Invalid; } @@ -409,11 +412,11 @@ auto MakeSelfSpecific(Context& context, SemIR::GenericId generic_id) // TODO: This could be made more efficient. We don't need to perform // substitution here; we know we want identity mappings for all constants and // types. We could also consider not storing the mapping at all in this case. - return MakeSpecific(context, generic_id, args_id); + return MakeSpecific(context, loc, generic_id, args_id); } -auto ResolveSpecificDefinition(Context& context, SemIR::SpecificId specific_id) - -> bool { +auto ResolveSpecificDefinition(Context& context, SemIRLoc loc, + SemIR::SpecificId specific_id) -> bool { auto& specific = context.specifics().Get(specific_id); auto generic_id = specific.generic_id; CARBON_CHECK(generic_id.is_valid(), "Specific with no generic ID"); @@ -426,7 +429,7 @@ auto ResolveSpecificDefinition(Context& context, SemIR::SpecificId specific_id) return false; } auto definition_block_id = TryEvalBlockForSpecific( - context, specific_id, SemIR::GenericInstIndex::Region::Definition); + context, loc, specific_id, SemIR::GenericInstIndex::Region::Definition); // Note that TryEvalBlockForSpecific may reallocate the list of specifics, // so re-lookup the specific here. context.specifics().Get(specific_id).definition_block_id = @@ -435,4 +438,34 @@ auto ResolveSpecificDefinition(Context& context, SemIR::SpecificId specific_id) return true; } +auto GetInstForSpecific(Context& context, SemIR::SpecificId specific_id) + -> SemIR::InstId { + CARBON_CHECK(specific_id.is_valid()); + const auto& specific = context.specifics().Get(specific_id); + const auto& generic = context.generics().Get(specific.generic_id); + auto decl = context.insts().Get(generic.decl_id); + CARBON_KIND_SWITCH(decl) { + case CARBON_KIND(SemIR::ClassDecl class_decl): { + return context.types().GetInstId( + context.GetClassType(class_decl.class_id, specific_id)); + } + case CARBON_KIND(SemIR::InterfaceDecl interface_decl): { + return context.types().GetInstId( + context.GetInterfaceType(interface_decl.interface_id, specific_id)); + } + case SemIR::FunctionDecl::Kind: { + return context.constant_values().GetInstId( + TryEvalInst(context, SemIR::InstId::Invalid, + SemIR::SpecificFunction{ + .type_id = context.GetSingletonType( + SemIR::SpecificFunctionType::SingletonInstId), + .callee_id = generic.decl_id, + .specific_id = specific_id})); + } + default: { + CARBON_FATAL("Unknown kind for generic declaration {0}", decl); + } + } +} + } // namespace Carbon::Check diff --git a/toolchain/check/generic.h b/toolchain/check/generic.h index f9b45d00dbb4c..9813ae405c97d 100644 --- a/toolchain/check/generic.h +++ b/toolchain/check/generic.h @@ -49,29 +49,34 @@ auto RebuildGenericEvalBlock(Context& context, SemIR::GenericId generic_id, // declaration, but not the definition, of the generic. // // `args_id` should be a canonical instruction block referring to constants. -auto MakeSpecific(Context& context, SemIR::GenericId generic_id, +auto MakeSpecific(Context& context, SemIRLoc loc, SemIR::GenericId generic_id, SemIR::InstBlockId args_id) -> SemIR::SpecificId; // Builds a new specific if the given generic is valid. Otherwise returns an // invalid specific. -inline auto MakeSpecificIfGeneric(Context& context, SemIR::GenericId generic_id, +inline auto MakeSpecificIfGeneric(Context& context, SemIRLoc loc, + SemIR::GenericId generic_id, SemIR::InstBlockId args_id) -> SemIR::SpecificId { - return generic_id.is_valid() ? MakeSpecific(context, generic_id, args_id) + return generic_id.is_valid() ? MakeSpecific(context, loc, generic_id, args_id) : SemIR::SpecificId::Invalid; } // Builds the specific that describes how the generic should refer to itself. // For example, for a generic `G(T:! type)`, this is the specific `G(T)`. For an // invalid `generic_id`, returns an invalid specific ID. -auto MakeSelfSpecific(Context& context, SemIR::GenericId generic_id) - -> SemIR::SpecificId; +auto MakeSelfSpecific(Context& context, SemIRLoc loc, + SemIR::GenericId generic_id) -> SemIR::SpecificId; // Attempts to resolve the definition of the given specific, by evaluating the // eval block of the corresponding generic and storing a corresponding value // block in the specific. Returns false if a definition is not available. -auto ResolveSpecificDefinition(Context& context, SemIR::SpecificId specific_id) - -> bool; +auto ResolveSpecificDefinition(Context& context, SemIRLoc loc, + SemIR::SpecificId specific_id) -> bool; + +// Returns an instruction describing the entity named by the given specific. +auto GetInstForSpecific(Context& context, SemIR::SpecificId specific_id) + -> SemIR::InstId; } // namespace Carbon::Check diff --git a/toolchain/check/impl.cpp b/toolchain/check/impl.cpp index 75a8a504e3eac..7ca75fceb9577 100644 --- a/toolchain/check/impl.cpp +++ b/toolchain/check/impl.cpp @@ -33,7 +33,7 @@ static auto NoteAssociatedFunction(Context& context, // Gets the self specific of a generic declaration that is an interface member, // given a specific for an enclosing generic, plus a type to use as `Self`. static auto GetSelfSpecificForInterfaceMemberWithSelfType( - Context& context, SemIR::SpecificId enclosing_specific_id, + Context& context, SemIRLoc loc, SemIR::SpecificId enclosing_specific_id, SemIR::GenericId generic_id, SemIR::TypeId self_type_id) -> SemIR::SpecificId { const auto& generic = context.generics().Get(generic_id); @@ -84,7 +84,7 @@ static auto GetSelfSpecificForInterfaceMemberWithSelfType( } auto args_id = context.inst_blocks().AddCanonical(arg_ids); - return MakeSpecific(context, generic_id, args_id); + return MakeSpecific(context, loc, generic_id, args_id); } // Checks that `impl_function_id` is a valid implementation of the function @@ -115,7 +115,7 @@ static auto CheckAssociatedFunctionImplementation( // parameters. auto interface_function_specific_id = GetSelfSpecificForInterfaceMemberWithSelfType( - context, interface_function_type.specific_id, + context, impl_decl_id, interface_function_type.specific_id, context.functions() .Get(interface_function_type.function_id) .generic_id, diff --git a/toolchain/check/impl_lookup.cpp b/toolchain/check/impl_lookup.cpp index 53bc43686033f..197ea33ae3b15 100644 --- a/toolchain/check/impl_lookup.cpp +++ b/toolchain/check/impl_lookup.cpp @@ -160,7 +160,7 @@ auto LookupInterfaceWitness(Context& context, SemIR::LocId loc_id, if (specific_id.is_valid()) { // We need a definition of the specific `impl` so we can access its // witness. - ResolveSpecificDefinition(context, specific_id); + ResolveSpecificDefinition(context, loc_id, specific_id); } return context.constant_values().GetInstId( SemIR::GetConstantValueInSpecific(context.sem_ir(), specific_id, diff --git a/toolchain/check/import_ref.cpp b/toolchain/check/import_ref.cpp index b01b91d9b4493..33aa2d2728062 100644 --- a/toolchain/check/import_ref.cpp +++ b/toolchain/check/import_ref.cpp @@ -2714,8 +2714,9 @@ static auto FinishPendingGeneric(ImportRefResolver& resolver, SemIR::GenericInstIndex::Region::Declaration); resolver.local_generics().Get(pending.local_id).decl_block_id = decl_block_id; - auto self_specific_id = - MakeSelfSpecific(resolver.local_context(), pending.local_id); + auto local_decl_id = resolver.local_generics().Get(pending.local_id).decl_id; + auto self_specific_id = MakeSelfSpecific(resolver.local_context(), + local_decl_id, pending.local_id); resolver.local_generics().Get(pending.local_id).self_specific_id = self_specific_id; resolver.AddPendingSpecific({.import_id = import_generic.self_specific_id, diff --git a/toolchain/check/subst.cpp b/toolchain/check/subst.cpp index 61ca7d94b366d..5e96504885031 100644 --- a/toolchain/check/subst.cpp +++ b/toolchain/check/subst.cpp @@ -190,7 +190,9 @@ static auto PopOperand(Context& context, Worklist& worklist, SemIR::IdKind kind, auto args_id = PopOperand(context, worklist, SemIR::IdKind::For, specific.args_id.index); - return MakeSpecific(context, specific.generic_id, + // TODO: Provide a location here. + SemIRLoc loc = SemIR::InstId::Invalid; + return MakeSpecific(context, loc, specific.generic_id, SemIR::InstBlockId(args_id)) .index; } diff --git a/toolchain/check/testdata/function/generic/resolve_used.carbon b/toolchain/check/testdata/function/generic/resolve_used.carbon index 51b5a86e01e5e..b8ecc92ee0b71 100644 --- a/toolchain/check/testdata/function/generic/resolve_used.carbon +++ b/toolchain/check/testdata/function/generic/resolve_used.carbon @@ -24,6 +24,9 @@ fn ErrorIfNIsZero(N:! Core.IntLiteral()) { } fn CallNegative() { + // CHECK:STDERR: fail_todo_call_monomorphization_error.carbon:[[@LINE+3]]:3: note: in `ErrorIfNIsZero(0)` used here [ResolvingSpecificHere] + // CHECK:STDERR: ErrorIfNIsZero(0); + // CHECK:STDERR: ^~~~~~~~~~~~~~ ErrorIfNIsZero(0); } diff --git a/toolchain/check/testdata/generic/complete_type.carbon b/toolchain/check/testdata/generic/complete_type.carbon index 15cc795cfc947..b4b8da4eb211c 100644 --- a/toolchain/check/testdata/generic/complete_type.carbon +++ b/toolchain/check/testdata/generic/complete_type.carbon @@ -15,20 +15,23 @@ library "[[@TEST_NAME]]"; class B; class A(T:! type) { - // CHECK:STDERR: fail_incomplete_in_class.carbon:[[@LINE+7]]:10: error: `T` evaluates to incomplete type `B` [IncompleteTypeInMonomorphization] + // CHECK:STDERR: fail_incomplete_in_class.carbon:[[@LINE+6]]:10: error: `T` evaluates to incomplete type `B` [IncompleteTypeInMonomorphization] // CHECK:STDERR: var v: T; // CHECK:STDERR: ^ // CHECK:STDERR: fail_incomplete_in_class.carbon:[[@LINE-6]]:1: note: class was forward declared here [ClassForwardDeclaredHere] // CHECK:STDERR: class B; // CHECK:STDERR: ^~~~~~~~ - // CHECK:STDERR: var v: T; } +// CHECK:STDERR: fail_incomplete_in_class.carbon:[[@LINE+11]]:6: note: in `A(B)` used here [ResolvingSpecificHere] +// CHECK:STDERR: fn F(x: A(B)) {} +// CHECK:STDERR: ^~~~~~~ +// CHECK:STDERR: // CHECK:STDERR: fail_incomplete_in_class.carbon:[[@LINE+7]]:6: error: parameter has incomplete type `A(B)` in function definition [IncompleteTypeInFunctionParam] // CHECK:STDERR: fn F(x: A(B)) {} // CHECK:STDERR: ^~~~~~~ -// CHECK:STDERR: fail_incomplete_in_class.carbon:[[@LINE-16]]:1: note: class was forward declared here [ClassForwardDeclaredHere] +// CHECK:STDERR: fail_incomplete_in_class.carbon:[[@LINE-19]]:1: note: class was forward declared here [ClassForwardDeclaredHere] // CHECK:STDERR: class B; // CHECK:STDERR: ^~~~~~~~ // CHECK:STDERR: @@ -67,6 +70,9 @@ fn F(T:! type) { var v: T; } +// CHECK:STDERR: fail_incomplete_in_function_at_eof.carbon:[[@LINE+3]]:10: note: in `F(B)` used here [ResolvingSpecificHere] +// CHECK:STDERR: fn G() { F(B); } +// CHECK:STDERR: ^ fn G() { F(B); } // CHECK:STDOUT: --- fail_incomplete_in_class.carbon @@ -125,7 +131,7 @@ fn G() { F(B); } // CHECK:STDOUT: %x.param: %A.2 = value_param runtime_param0 // CHECK:STDOUT: %x: %A.2 = bind_name x, %x.param // CHECK:STDOUT: } -// CHECK:STDOUT: %B.decl.loc26: type = class_decl @B [template = constants.%B] {} {} +// CHECK:STDOUT: %B.decl.loc29: type = class_decl @B [template = constants.%B] {} {} // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: class @B { @@ -145,17 +151,17 @@ fn G() { F(B); } // CHECK:STDOUT: %A: type = class_type @A, @A(%T.loc6_9.2) [symbolic = %A (constants.%A.1)] // CHECK:STDOUT: %A.elem: type = unbound_element_type @A.%A (%A.1), @A.%T.loc6_9.2 (%T) [symbolic = %A.elem (constants.%A.elem.1)] // CHECK:STDOUT: %struct_type.v: type = struct_type {.v: @A.%T.loc6_9.2 (%T)} [symbolic = %struct_type.v (constants.%struct_type.v.1)] -// CHECK:STDOUT: %complete_type.loc15_1.2: = complete_type_witness @A.%struct_type.v (%struct_type.v.1) [symbolic = %complete_type.loc15_1.2 (constants.%complete_type.1)] +// CHECK:STDOUT: %complete_type.loc14_1.2: = complete_type_witness @A.%struct_type.v (%struct_type.v.1) [symbolic = %complete_type.loc14_1.2 (constants.%complete_type.1)] // CHECK:STDOUT: // CHECK:STDOUT: class { // CHECK:STDOUT: %T.ref: type = name_ref T, %T.loc6_9.1 [symbolic = %T.loc6_9.2 (constants.%T)] -// CHECK:STDOUT: %.loc14: @A.%A.elem (%A.elem.1) = field_decl v, element0 [template] -// CHECK:STDOUT: %complete_type.loc15_1.1: = complete_type_witness %struct_type.v.1 [symbolic = %complete_type.loc15_1.2 (constants.%complete_type.1)] +// CHECK:STDOUT: %.loc13: @A.%A.elem (%A.elem.1) = field_decl v, element0 [template] +// CHECK:STDOUT: %complete_type.loc14_1.1: = complete_type_witness %struct_type.v.1 [symbolic = %complete_type.loc14_1.2 (constants.%complete_type.1)] // CHECK:STDOUT: // CHECK:STDOUT: !members: // CHECK:STDOUT: .Self = constants.%A.1 -// CHECK:STDOUT: .v = %.loc14 -// CHECK:STDOUT: complete_type_witness = %complete_type.loc15_1.1 +// CHECK:STDOUT: .v = %.loc13 +// CHECK:STDOUT: complete_type_witness = %complete_type.loc14_1.1 // CHECK:STDOUT: } // CHECK:STDOUT: } // CHECK:STDOUT: @@ -183,7 +189,7 @@ fn G() { F(B); } // CHECK:STDOUT: %A => constants.%A.2 // CHECK:STDOUT: %A.elem => constants.%A.elem.2 // CHECK:STDOUT: %struct_type.v => constants.%struct_type.v.2 -// CHECK:STDOUT: %complete_type.loc15_1.2 => constants.%complete_type.2 +// CHECK:STDOUT: %complete_type.loc14_1.2 => constants.%complete_type.2 // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: --- incomplete_in_function.carbon diff --git a/toolchain/diagnostics/diagnostic_kind.def b/toolchain/diagnostics/diagnostic_kind.def index 738d43113d839..ceb0df8691386 100644 --- a/toolchain/diagnostics/diagnostic_kind.def +++ b/toolchain/diagnostics/diagnostic_kind.def @@ -141,6 +141,7 @@ CARBON_DIAGNOSTIC_KIND(SemanticsTodo) // Location context. CARBON_DIAGNOSTIC_KIND(InImport) +CARBON_DIAGNOSTIC_KIND(ResolvingSpecificHere) // Package/import checking diagnostics. CARBON_DIAGNOSTIC_KIND(IncorrectExtension) diff --git a/toolchain/sem_ir/stringify_type.cpp b/toolchain/sem_ir/stringify_type.cpp index 7a83f22913fdc..ca13382b65f91 100644 --- a/toolchain/sem_ir/stringify_type.cpp +++ b/toolchain/sem_ir/stringify_type.cpp @@ -403,6 +403,16 @@ auto StringifyTypeExpr(const SemIR::File& sem_ir, InstId outer_inst_id) step_stack.PushTypeId(inst.pointee_id); break; } + case CARBON_KIND(SpecificFunction inst): { + auto callee = SemIR::GetCalleeFunction(sem_ir, inst.callee_id); + if (callee.function_id.is_valid()) { + step_stack.PushEntityName(sem_ir.functions().Get(callee.function_id), + inst.specific_id); + } else { + step_stack.PushString(""); + } + break; + } case CARBON_KIND(StructType inst): { auto fields = sem_ir.struct_type_fields().Get(inst.fields_id); if (fields.empty()) { @@ -500,7 +510,6 @@ auto StringifyTypeExpr(const SemIR::File& sem_ir, InstId outer_inst_id) case ReturnSlot::Kind: case ReturnSlotPattern::Kind: case SpecificConstant::Kind: - case SpecificFunction::Kind: case SpliceBlock::Kind: case StringLiteral::Kind: case StructAccess::Kind: From 79ba184dabcda5003374df634a7481b639e00fdd Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 11 Dec 2024 16:44:07 -0800 Subject: [PATCH 6/7] Provide a location for monomorphization failures resulting from `TryToCompleteType`. (#4670) Almost all callers actually could never fail and nearly all of those already `CHECK`-failed on failure. Add a new overload for that case, and add a location parameter for the one remaining call. --- toolchain/check/context.cpp | 18 +- toolchain/check/context.h | 6 +- toolchain/check/convert.cpp | 15 +- toolchain/check/generic.cpp | 2 +- .../generic/complete_in_conversion.carbon | 216 ++++++++++++++++++ 5 files changed, 241 insertions(+), 16 deletions(-) create mode 100644 toolchain/check/testdata/class/generic/complete_in_conversion.carbon diff --git a/toolchain/check/context.cpp b/toolchain/check/context.cpp index adab63ebe771d..3b1c43c268f27 100644 --- a/toolchain/check/context.cpp +++ b/toolchain/check/context.cpp @@ -1261,9 +1261,15 @@ class TypeCompleter { }; } // namespace -auto Context::TryToCompleteType(SemIR::TypeId type_id) -> bool { - // TODO: We need a location here in case we need to instantiate a class type. - return TypeCompleter(*this, SemIR::LocId::Invalid, nullptr).Complete(type_id); +auto Context::TryToCompleteType(SemIR::TypeId type_id, SemIRLoc loc) -> bool { + return TypeCompleter(*this, loc, nullptr).Complete(type_id); +} + +auto Context::CompleteTypeOrCheckFail(SemIR::TypeId type_id) -> void { + bool complete = + TypeCompleter(*this, SemIR::LocId::Invalid, nullptr).Complete(type_id); + CARBON_CHECK(complete, "Expected {0} to be a complete type", + types().GetAsInst(type_id)); } auto Context::RequireCompleteType(SemIR::TypeId type_id, SemIR::LocId loc_id, @@ -1386,8 +1392,7 @@ template static auto GetCompleteTypeImpl(Context& context, EachArgT... each_arg) -> SemIR::TypeId { auto type_id = GetTypeImpl(context, each_arg...); - bool complete = context.TryToCompleteType(type_id); - CARBON_CHECK(complete, "Type completion should not fail"); + context.CompleteTypeOrCheckFail(type_id); return type_id; } @@ -1413,8 +1418,7 @@ auto Context::GetSingletonType(SemIR::InstId singleton_id) -> SemIR::TypeId { CARBON_CHECK(SemIR::IsSingletonInstId(singleton_id)); auto type_id = GetTypeIdForTypeInst(singleton_id); // To keep client code simpler, complete builtin types before returning them. - bool complete = TryToCompleteType(type_id); - CARBON_CHECK(complete, "Failed to complete builtin type"); + CompleteTypeOrCheckFail(type_id); return type_id; } diff --git a/toolchain/check/context.h b/toolchain/check/context.h index 2b3f4afdb7ff4..b602adbb53e86 100644 --- a/toolchain/check/context.h +++ b/toolchain/check/context.h @@ -344,7 +344,11 @@ class Context { // symbolic. // // Avoid calling this where possible, as it can lead to coherence issues. - auto TryToCompleteType(SemIR::TypeId type_id) -> bool; + // TODO: Remove the remaining call to this and delete this function. + auto TryToCompleteType(SemIR::TypeId type_id, SemIRLoc loc) -> bool; + + // Completes the type `type_id`. CHECK-fails if it can't be completed. + auto CompleteTypeOrCheckFail(SemIR::TypeId type_id) -> void; // Like `TryToCompleteType`, but for cases where it is an error for the type // to be incomplete. diff --git a/toolchain/check/convert.cpp b/toolchain/check/convert.cpp index 7f4141a6a0749..507548c3bccbc 100644 --- a/toolchain/check/convert.cpp +++ b/toolchain/check/convert.cpp @@ -596,13 +596,14 @@ using InheritancePath = // Computes the inheritance path from class `derived_id` to class `base_id`. // Returns nullopt if `derived_id` is not a class derived from `base_id`. -static auto ComputeInheritancePath(Context& context, SemIR::TypeId derived_id, +static auto ComputeInheritancePath(Context& context, SemIRLoc loc, + SemIR::TypeId derived_id, SemIR::TypeId base_id) -> std::optional { // We intend for NRVO to be applied to `result`. All `return` statements in // this function should `return result;`. std::optional result(std::in_place); - if (!context.TryToCompleteType(derived_id)) { + if (!context.TryToCompleteType(derived_id, loc)) { // TODO: Should we give an error here? If we don't, and there is an // inheritance path when the class is defined, we may have a coherence // problem. @@ -856,8 +857,8 @@ static auto PerformBuiltinConversion(Context& context, SemIR::LocId loc_id, } // An expression of type T converts to U if T is a class derived from U. - if (auto path = - ComputeInheritancePath(context, value_type_id, target.type_id); + if (auto path = ComputeInheritancePath(context, loc_id, value_type_id, + target.type_id); path && !path->empty()) { return ConvertDerivedToBase(context, loc_id, value_id, *path); } @@ -867,9 +868,9 @@ static auto PerformBuiltinConversion(Context& context, SemIR::LocId loc_id, if (auto target_pointer_type = target_type_inst.TryAs()) { if (auto src_pointer_type = sem_ir.types().TryGetAs(value_type_id)) { - if (auto path = - ComputeInheritancePath(context, src_pointer_type->pointee_id, - target_pointer_type->pointee_id); + if (auto path = ComputeInheritancePath(context, loc_id, + src_pointer_type->pointee_id, + target_pointer_type->pointee_id); path && !path->empty()) { return ConvertDerivedPointerToBasePointer( context, loc_id, *src_pointer_type, target.type_id, value_id, diff --git a/toolchain/check/generic.cpp b/toolchain/check/generic.cpp index ca37cc70d5cc8..e9abbba09dd2d 100644 --- a/toolchain/check/generic.cpp +++ b/toolchain/check/generic.cpp @@ -249,7 +249,7 @@ static auto MakeGenericEvalBlock(Context& context, SemIR::GenericId generic_id, // constraints on the generic rather than properties of the type. For now, // require the transformed type to be complete if the original was. if (context.types().IsComplete(inst.type_id())) { - context.TryToCompleteType(type_id); + context.CompleteTypeOrCheckFail(type_id); } inst.SetType(type_id); context.sem_ir().insts().Set(inst_id, inst); diff --git a/toolchain/check/testdata/class/generic/complete_in_conversion.carbon b/toolchain/check/testdata/class/generic/complete_in_conversion.carbon new file mode 100644 index 0000000000000..6ab05b239223f --- /dev/null +++ b/toolchain/check/testdata/class/generic/complete_in_conversion.carbon @@ -0,0 +1,216 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/class/generic/complete_in_conversion.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/class/generic/complete_in_conversion.carbon + +// --- fail_derived_to_base.carbon + +base class B {} + +class A(N:! i32) { + extend base: B; + + // CHECK:STDERR: fail_derived_to_base.carbon:[[@LINE+3]]:10: error: integer type width of 0 is not positive [IntWidthNotPositive] + // CHECK:STDERR: var n: Core.Int(N); + // CHECK:STDERR: ^~~~~~~~~~~ + var n: Core.Int(N); +} + +fn F(a: A(0)*) { + // CHECK:STDERR: fail_derived_to_base.carbon:[[@LINE+3]]:3: note: in `A(0)` used here [ResolvingSpecificHere] + // CHECK:STDERR: let b: B* = a; + // CHECK:STDERR: ^~~~~~~~~~~~~~ + let b: B* = a; +} + +// CHECK:STDOUT: --- fail_derived_to_base.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %B: type = class_type @B [template] +// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [template] +// CHECK:STDOUT: %complete_type.1: = complete_type_witness %empty_struct_type [template] +// CHECK:STDOUT: %int_32: Core.IntLiteral = int_value 32 [template] +// CHECK:STDOUT: %Int.type: type = fn_type @Int [template] +// CHECK:STDOUT: %Int: %Int.type = struct_value () [template] +// CHECK:STDOUT: %i32: type = int_type signed, %int_32 [template] +// CHECK:STDOUT: %N.1: %i32 = bind_symbolic_name N, 0 [symbolic] +// CHECK:STDOUT: %N.patt.1: %i32 = symbolic_binding_pattern N, 0 [symbolic] +// CHECK:STDOUT: %A.type: type = generic_class_type @A [template] +// CHECK:STDOUT: %A.generic: %A.type = struct_value () [template] +// CHECK:STDOUT: %A.1: type = class_type @A, @A(%N.1) [symbolic] +// CHECK:STDOUT: %A.elem.1: type = unbound_element_type %A.1, %B [symbolic] +// CHECK:STDOUT: %Convert.type.2: type = fn_type @Convert.1, @ImplicitAs(Core.IntLiteral) [template] +// CHECK:STDOUT: %Convert.type.13: type = fn_type @Convert.4, @impl.3(%int_32) [template] +// CHECK:STDOUT: %Convert.13: %Convert.type.13 = struct_value () [template] +// CHECK:STDOUT: %interface.9: = interface_witness (%Convert.13) [template] +// CHECK:STDOUT: %Convert.bound.1: = bound_method %N.1, %Convert.13 [symbolic] +// CHECK:STDOUT: %Convert.specific_fn.1: = specific_function %Convert.bound.1, @Convert.4(%int_32) [symbolic] +// CHECK:STDOUT: %int.convert_checked: init Core.IntLiteral = call %Convert.specific_fn.1(%N.1) [symbolic] +// CHECK:STDOUT: %iN.2: type = int_type signed, %int.convert_checked [symbolic] +// CHECK:STDOUT: %require_complete.7: = require_complete_type %iN.2 [symbolic] +// CHECK:STDOUT: %A.elem.2: type = unbound_element_type %A.1, %iN.2 [symbolic] +// CHECK:STDOUT: %struct_type.base.n: type = struct_type {.base: %B, .n: %iN.2} [symbolic] +// CHECK:STDOUT: %complete_type.3: = complete_type_witness %struct_type.base.n [symbolic] +// CHECK:STDOUT: %int_0.1: Core.IntLiteral = int_value 0 [template] +// CHECK:STDOUT: %Convert.type.14: type = fn_type @Convert.1, @ImplicitAs(%i32) [template] +// CHECK:STDOUT: %Convert.type.15: type = fn_type @Convert.2, @impl.1(%int_32) [template] +// CHECK:STDOUT: %Convert.15: %Convert.type.15 = struct_value () [template] +// CHECK:STDOUT: %interface.10: = interface_witness (%Convert.15) [template] +// CHECK:STDOUT: %Convert.bound.2: = bound_method %int_0.1, %Convert.15 [template] +// CHECK:STDOUT: %Convert.specific_fn.2: = specific_function %Convert.bound.2, @Convert.2(%int_32) [template] +// CHECK:STDOUT: %int_0.2: %i32 = int_value 0 [template] +// CHECK:STDOUT: %A.2: type = class_type @A, @A(%int_0.2) [template] +// CHECK:STDOUT: %ptr.2: type = ptr_type %A.2 [template] +// CHECK:STDOUT: %F.type: type = fn_type @F [template] +// CHECK:STDOUT: %F: %F.type = struct_value () [template] +// CHECK:STDOUT: %ptr.3: type = ptr_type %B [template] +// CHECK:STDOUT: %A.elem.3: type = unbound_element_type %A.2, %B [template] +// CHECK:STDOUT: %Convert.bound.3: = bound_method %int_0.2, %Convert.13 [template] +// CHECK:STDOUT: %Convert.specific_fn.3: = specific_function %Convert.bound.3, @Convert.4(%int_32) [template] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: imports { +// CHECK:STDOUT: %Core: = namespace file.%Core.import, [template] { +// CHECK:STDOUT: .Int = %import_ref.1 +// CHECK:STDOUT: .ImplicitAs = %import_ref.2 +// CHECK:STDOUT: import Core//prelude +// CHECK:STDOUT: import Core//prelude/... +// CHECK:STDOUT: } +// CHECK:STDOUT: %import_ref.1: %Int.type = import_ref Core//prelude/types, Int, loaded [template = constants.%Int] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace [template] { +// CHECK:STDOUT: .Core = imports.%Core +// CHECK:STDOUT: .B = %B.decl +// CHECK:STDOUT: .A = %A.decl +// CHECK:STDOUT: .F = %F.decl +// CHECK:STDOUT: } +// CHECK:STDOUT: %Core.import = import Core +// CHECK:STDOUT: %B.decl: type = class_decl @B [template = constants.%B] {} {} +// CHECK:STDOUT: %A.decl: %A.type = class_decl @A [template = constants.%A.generic] { +// CHECK:STDOUT: %N.patt.loc4_9.1: %i32 = symbolic_binding_pattern N, 0 [symbolic = %N.patt.loc4_9.2 (constants.%N.patt.1)] +// CHECK:STDOUT: %N.param_patt: %i32 = value_param_pattern %N.patt.loc4_9.1, runtime_param [symbolic = %N.patt.loc4_9.2 (constants.%N.patt.1)] +// CHECK:STDOUT: } { +// CHECK:STDOUT: %int_32: Core.IntLiteral = int_value 32 [template = constants.%int_32] +// CHECK:STDOUT: %int.make_type_signed.loc4: init type = call constants.%Int(%int_32) [template = constants.%i32] +// CHECK:STDOUT: %.loc4_13.1: type = value_of_initializer %int.make_type_signed.loc4 [template = constants.%i32] +// CHECK:STDOUT: %.loc4_13.2: type = converted %int.make_type_signed.loc4, %.loc4_13.1 [template = constants.%i32] +// CHECK:STDOUT: %N.param: %i32 = value_param runtime_param +// CHECK:STDOUT: %N.loc4_9.1: %i32 = bind_symbolic_name N, 0, %N.param [symbolic = %N.loc4_9.2 (constants.%N.1)] +// CHECK:STDOUT: } +// CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [template = constants.%F] { +// CHECK:STDOUT: %a.patt: %ptr.2 = binding_pattern a +// CHECK:STDOUT: %a.param_patt: %ptr.2 = value_param_pattern %a.patt, runtime_param0 +// CHECK:STDOUT: } { +// CHECK:STDOUT: %A.ref: %A.type = name_ref A, file.%A.decl [template = constants.%A.generic] +// CHECK:STDOUT: %int_0: Core.IntLiteral = int_value 0 [template = constants.%int_0.1] +// CHECK:STDOUT: %impl.elem0: %Convert.type.14 = interface_witness_access constants.%interface.10, element0 [template = constants.%Convert.15] +// CHECK:STDOUT: %Convert.bound: = bound_method %int_0, %impl.elem0 [template = constants.%Convert.bound.2] +// CHECK:STDOUT: %Convert.specific_fn: = specific_function %Convert.bound, @Convert.2(constants.%int_32) [template = constants.%Convert.specific_fn.2] +// CHECK:STDOUT: %int.convert_checked: init %i32 = call %Convert.specific_fn(%int_0) [template = constants.%int_0.2] +// CHECK:STDOUT: %.loc13_12.1: %i32 = value_of_initializer %int.convert_checked [template = constants.%int_0.2] +// CHECK:STDOUT: %.loc13_12.2: %i32 = converted %int_0, %.loc13_12.1 [template = constants.%int_0.2] +// CHECK:STDOUT: %A: type = class_type @A, @A(constants.%int_0.2) [template = constants.%A.2] +// CHECK:STDOUT: %ptr.loc13: type = ptr_type %A.2 [template = constants.%ptr.2] +// CHECK:STDOUT: %a.param: %ptr.2 = value_param runtime_param0 +// CHECK:STDOUT: %a: %ptr.2 = bind_name a, %a.param +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @B { +// CHECK:STDOUT: %complete_type: = complete_type_witness %empty_struct_type [template = constants.%complete_type.1] +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = constants.%B +// CHECK:STDOUT: complete_type_witness = %complete_type +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: generic class @A(%N.loc4_9.1: %i32) { +// CHECK:STDOUT: %N.loc4_9.2: %i32 = bind_symbolic_name N, 0 [symbolic = %N.loc4_9.2 (constants.%N.1)] +// CHECK:STDOUT: %N.patt.loc4_9.2: %i32 = symbolic_binding_pattern N, 0 [symbolic = %N.patt.loc4_9.2 (constants.%N.patt.1)] +// CHECK:STDOUT: +// CHECK:STDOUT: !definition: +// CHECK:STDOUT: %A: type = class_type @A, @A(%N.loc4_9.2) [symbolic = %A (constants.%A.1)] +// CHECK:STDOUT: %A.elem.loc5: type = unbound_element_type @A.%A (%A.1), %B [symbolic = %A.elem.loc5 (constants.%A.elem.1)] +// CHECK:STDOUT: %Convert.bound.loc10_19.2: = bound_method %N.loc4_9.2, constants.%Convert.13 [symbolic = %Convert.bound.loc10_19.2 (constants.%Convert.bound.1)] +// CHECK:STDOUT: %Convert.specific_fn.loc10_19.2: = specific_function %Convert.bound.loc10_19.2, @Convert.4(constants.%int_32) [symbolic = %Convert.specific_fn.loc10_19.2 (constants.%Convert.specific_fn.1)] +// CHECK:STDOUT: %int.convert_checked.loc10_19.2: init Core.IntLiteral = call %Convert.specific_fn.loc10_19.2(%N.loc4_9.2) [symbolic = %int.convert_checked.loc10_19.2 (constants.%int.convert_checked)] +// CHECK:STDOUT: %iN: type = int_type signed, %int.convert_checked.loc10_19.2 [symbolic = %iN (constants.%iN.2)] +// CHECK:STDOUT: %require_complete: = require_complete_type @A.%iN (%iN.2) [symbolic = %require_complete (constants.%require_complete.7)] +// CHECK:STDOUT: %A.elem.loc10: type = unbound_element_type @A.%A (%A.1), @A.%iN (%iN.2) [symbolic = %A.elem.loc10 (constants.%A.elem.2)] +// CHECK:STDOUT: %struct_type.base.n: type = struct_type {.base: %B, .n: @A.%iN (%iN.2)} [symbolic = %struct_type.base.n (constants.%struct_type.base.n)] +// CHECK:STDOUT: %complete_type.loc11_1.2: = complete_type_witness @A.%struct_type.base.n (%struct_type.base.n) [symbolic = %complete_type.loc11_1.2 (constants.%complete_type.3)] +// CHECK:STDOUT: +// CHECK:STDOUT: class { +// CHECK:STDOUT: %B.ref: type = name_ref B, file.%B.decl [template = constants.%B] +// CHECK:STDOUT: %.loc5: @A.%A.elem.loc5 (%A.elem.1) = base_decl %B.ref, element0 [template] +// CHECK:STDOUT: %Core.ref: = name_ref Core, imports.%Core [template = imports.%Core] +// CHECK:STDOUT: %Int.ref: %Int.type = name_ref Int, imports.%import_ref.1 [template = constants.%Int] +// CHECK:STDOUT: %N.ref: %i32 = name_ref N, %N.loc4_9.1 [symbolic = %N.loc4_9.2 (constants.%N.1)] +// CHECK:STDOUT: %impl.elem0: %Convert.type.2 = interface_witness_access constants.%interface.9, element0 [template = constants.%Convert.13] +// CHECK:STDOUT: %Convert.bound.loc10_19.1: = bound_method %N.ref, %impl.elem0 [symbolic = %Convert.bound.loc10_19.2 (constants.%Convert.bound.1)] +// CHECK:STDOUT: %Convert.specific_fn.loc10_19.1: = specific_function %Convert.bound.loc10_19.1, @Convert.4(constants.%int_32) [symbolic = %Convert.specific_fn.loc10_19.2 (constants.%Convert.specific_fn.1)] +// CHECK:STDOUT: %int.convert_checked.loc10_19.1: init Core.IntLiteral = call %Convert.specific_fn.loc10_19.1(%N.ref) [symbolic = %int.convert_checked.loc10_19.2 (constants.%int.convert_checked)] +// CHECK:STDOUT: %.loc10_19.1: Core.IntLiteral = value_of_initializer %int.convert_checked.loc10_19.1 [symbolic = %int.convert_checked.loc10_19.2 (constants.%int.convert_checked)] +// CHECK:STDOUT: %.loc10_19.2: Core.IntLiteral = converted %N.ref, %.loc10_19.1 [symbolic = %int.convert_checked.loc10_19.2 (constants.%int.convert_checked)] +// CHECK:STDOUT: %int.make_type_signed.loc10: init type = call %Int.ref(%.loc10_19.2) [symbolic = %iN (constants.%iN.2)] +// CHECK:STDOUT: %.loc10_20.1: type = value_of_initializer %int.make_type_signed.loc10 [symbolic = %iN (constants.%iN.2)] +// CHECK:STDOUT: %.loc10_20.2: type = converted %int.make_type_signed.loc10, %.loc10_20.1 [symbolic = %iN (constants.%iN.2)] +// CHECK:STDOUT: %.loc10_8: @A.%A.elem.loc10 (%A.elem.2) = field_decl n, element1 [template] +// CHECK:STDOUT: %complete_type.loc11_1.1: = complete_type_witness %struct_type.base.n [symbolic = %complete_type.loc11_1.2 (constants.%complete_type.3)] +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = constants.%A.1 +// CHECK:STDOUT: .base = %.loc5 +// CHECK:STDOUT: .n = %.loc10_8 +// CHECK:STDOUT: extend %B.ref +// CHECK:STDOUT: complete_type_witness = %complete_type.loc11_1.1 +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F(%a.param_patt: %ptr.2) { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %B.ref: type = name_ref B, file.%B.decl [template = constants.%B] +// CHECK:STDOUT: %ptr.loc17: type = ptr_type %B [template = constants.%ptr.3] +// CHECK:STDOUT: %a.ref: %ptr.2 = name_ref a, %a +// CHECK:STDOUT: %.loc17_16.1: ref %A.2 = deref %a.ref +// CHECK:STDOUT: %.loc17_16.2: ref %B = class_element_access %.loc17_16.1, element0 +// CHECK:STDOUT: %addr: %ptr.3 = addr_of %.loc17_16.2 +// CHECK:STDOUT: %.loc17_16.3: %ptr.3 = converted %a.ref, %addr +// CHECK:STDOUT: %b: %ptr.3 = bind_name b, %.loc17_16.3 +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: specific @A(constants.%N.1) { +// CHECK:STDOUT: %N.loc4_9.2 => constants.%N.1 +// CHECK:STDOUT: %N.patt.loc4_9.2 => constants.%N.1 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: specific @A(%N.loc4_9.2) { +// CHECK:STDOUT: %N.loc4_9.2 => constants.%N.1 +// CHECK:STDOUT: %N.patt.loc4_9.2 => constants.%N.1 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: specific @A(constants.%int_0.2) { +// CHECK:STDOUT: %N.loc4_9.2 => constants.%int_0.2 +// CHECK:STDOUT: %N.patt.loc4_9.2 => constants.%int_0.2 +// CHECK:STDOUT: +// CHECK:STDOUT: !definition: +// CHECK:STDOUT: %A => constants.%A.2 +// CHECK:STDOUT: %A.elem.loc5 => constants.%A.elem.3 +// CHECK:STDOUT: %Convert.bound.loc10_19.2 => constants.%Convert.bound.3 +// CHECK:STDOUT: %Convert.specific_fn.loc10_19.2 => constants.%Convert.specific_fn.3 +// CHECK:STDOUT: %int.convert_checked.loc10_19.2 => constants.%int_0.1 +// CHECK:STDOUT: %iN => +// CHECK:STDOUT: %require_complete => +// CHECK:STDOUT: %A.elem.loc10 => +// CHECK:STDOUT: %struct_type.base.n => +// CHECK:STDOUT: %complete_type.loc11_1.2 => +// CHECK:STDOUT: } +// CHECK:STDOUT: From 3ce0df67bb7a2e9503e9f06819c3f20708f914a5 Mon Sep 17 00:00:00 2001 From: Jon Ross-Perkins Date: Thu, 12 Dec 2024 12:51:02 -0800 Subject: [PATCH 7/7] Add `Dump` functions to Check, Parse, and Lex (#4669) - Provide `Check::Dump(context, arg)` and similar. - gdb and lldb should do contextual lookup, and `call Dump(*this, Lex::TokenIndex::Invalid)` has been tested with gdb. - Since this is only for debug, keeps the functions fully separated from code. - Uses alwayslink to ensure objects are correctly linked, even though there are no calls. - `-Wno-missing-prototypes` is needed when we don't have forward declarations. - Code is not linked in opt builds, using `#ifndef NDEBUG`. - This probably could be doing something in BUILD files with a `select()`, but the `#ifndef` seemed easier. This is based on #4620, but uses free functions instead of member functions. Co-authored-by: Dana Jansens --------- Co-authored-by: danakj --- common/ostream.h | 5 +- toolchain/check/BUILD | 20 ++++++++ toolchain/check/context.h | 9 +++- toolchain/check/dump.cpp | 78 ++++++++++++++++++++++++++++++ toolchain/docs/adding_features.md | 11 +++++ toolchain/lex/BUILD | 13 +++++ toolchain/lex/dump.cpp | 36 ++++++++++++++ toolchain/lex/dump.h | 34 +++++++++++++ toolchain/lex/tokenized_buffer.cpp | 8 +-- toolchain/lex/tokenized_buffer.h | 3 -- toolchain/parse/BUILD | 14 ++++++ toolchain/parse/context.cpp | 2 +- toolchain/parse/dump.cpp | 39 +++++++++++++++ toolchain/parse/dump.h | 35 ++++++++++++++ 14 files changed, 293 insertions(+), 14 deletions(-) create mode 100644 toolchain/check/dump.cpp create mode 100644 toolchain/lex/dump.cpp create mode 100644 toolchain/lex/dump.h create mode 100644 toolchain/parse/dump.cpp create mode 100644 toolchain/parse/dump.h diff --git a/common/ostream.h b/common/ostream.h index be984707af2d7..6ba004898875f 100644 --- a/common/ostream.h +++ b/common/ostream.h @@ -5,13 +5,14 @@ #ifndef CARBON_COMMON_OSTREAM_H_ #define CARBON_COMMON_OSTREAM_H_ +// Libraries should include this header instead of raw_ostream. + #include #include #include -#include "llvm/Support/raw_os_ostream.h" -// Libraries should include this header instead of raw_ostream. #include "llvm/Support/Compiler.h" +#include "llvm/Support/raw_os_ostream.h" #include "llvm/Support/raw_ostream.h" // IWYU pragma: export namespace Carbon { diff --git a/toolchain/check/BUILD b/toolchain/check/BUILD index e1cb268405438..213513bdbef18 100644 --- a/toolchain/check/BUILD +++ b/toolchain/check/BUILD @@ -92,6 +92,25 @@ cc_library( ], ) +cc_library( + name = "dump", + srcs = ["dump.cpp"], + # Contains Dump methods without a forward declaration. + copts = ["-Wno-missing-prototypes"], + deps = [ + ":context", + "//common:check", + "//common:ostream", + "//toolchain/lex:dump", + "//toolchain/lex:tokenized_buffer", + "//toolchain/parse:dump", + "//toolchain/parse:tree", + "//toolchain/sem_ir:file", + ], + # Always link dump methods. + alwayslink = 1, +) + cc_library( name = "check", srcs = [ @@ -111,6 +130,7 @@ cc_library( hdrs = ["check.h"], deps = [ ":context", + ":dump", ":impl", ":interface", ":pointer_dereference", diff --git a/toolchain/check/context.h b/toolchain/check/context.h index b602adbb53e86..500624f936a5d 100644 --- a/toolchain/check/context.h +++ b/toolchain/check/context.h @@ -495,10 +495,15 @@ class Context { } auto sem_ir() -> SemIR::File& { return *sem_ir_; } + auto sem_ir() const -> const SemIR::File& { return *sem_ir_; } - auto parse_tree() -> const Parse::Tree& { return sem_ir_->parse_tree(); } + auto parse_tree() const -> const Parse::Tree& { + return sem_ir_->parse_tree(); + } - auto tokens() -> const Lex::TokenizedBuffer& { return parse_tree().tokens(); } + auto tokens() const -> const Lex::TokenizedBuffer& { + return parse_tree().tokens(); + } auto node_stack() -> NodeStack& { return node_stack_; } diff --git a/toolchain/check/dump.cpp b/toolchain/check/dump.cpp new file mode 100644 index 0000000000000..d92306c78d518 --- /dev/null +++ b/toolchain/check/dump.cpp @@ -0,0 +1,78 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// This library contains functions to assist dumping objects to stderr during +// interactive debugging. Functions named `Dump` are intended for direct use by +// developers, and should use overload resolution to determine which will be +// invoked. The debugger should do namespace resolution automatically. For +// example: +// +// - lldb: `expr Dump(context, id)` +// - gdb: `call Dump(context, id)` +// +// The `DumpNoNewline` functions are helpers that exclude a trailing newline. +// They're intended to be composed by `Dump` function implementations. + +#ifndef NDEBUG + +#include "toolchain/lex/dump.h" + +#include "common/check.h" +#include "common/ostream.h" +#include "toolchain/check/context.h" +#include "toolchain/lex/tokenized_buffer.h" +#include "toolchain/parse/dump.h" +#include "toolchain/parse/tree.h" +#include "toolchain/sem_ir/file.h" + +namespace Carbon::Check { + +static auto DumpNoNewline(const Context& context, SemIR::LocId loc_id) -> void { + if (!loc_id.is_valid()) { + llvm::errs() << "LocId(invalid)"; + return; + } + + if (loc_id.is_node_id()) { + auto token = context.parse_tree().node_token(loc_id.node_id()); + auto line = context.tokens().GetLineNumber(token); + auto col = context.tokens().GetColumnNumber(token); + const char* implicit = loc_id.is_implicit() ? " implicit" : ""; + llvm::errs() << "LocId("; + llvm::errs().write_escaped(context.sem_ir().filename()); + llvm::errs() << ":" << line << ":" << col << implicit << ")"; + } else { + CARBON_CHECK(loc_id.is_import_ir_inst_id()); + + auto import_ir_id = context.sem_ir() + .import_ir_insts() + .Get(loc_id.import_ir_inst_id()) + .ir_id; + const auto* import_file = + context.sem_ir().import_irs().Get(import_ir_id).sem_ir; + llvm::errs() << "LocId(import from \""; + llvm::errs().write_escaped(import_file->filename()); + llvm::errs() << "\")"; + } +} + +LLVM_DUMP_METHOD auto Dump(const Context& context, Lex::TokenIndex token) + -> void { + Parse::Dump(context.parse_tree(), token); +} + +LLVM_DUMP_METHOD auto Dump(const Context& context, Parse::NodeId node_id) + -> void { + Parse::Dump(context.parse_tree(), node_id); +} + +LLVM_DUMP_METHOD auto Dump(const Context& context, SemIR::LocId loc_id) + -> void { + DumpNoNewline(context, loc_id); + llvm::errs() << '\n'; +} + +} // namespace Carbon::Check + +#endif // NDEBUG diff --git a/toolchain/docs/adding_features.md b/toolchain/docs/adding_features.md index 2eb1800895434..cf23795105dfe 100644 --- a/toolchain/docs/adding_features.md +++ b/toolchain/docs/adding_features.md @@ -23,6 +23,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Reviewing test deltas](#reviewing-test-deltas) - [Verbose output](#verbose-output) - [Stack traces](#stack-traces) + - [Dumping objects in interactive debuggers](#dumping-objects-in-interactive-debuggers) @@ -496,3 +497,13 @@ While the iterative processing pattern means function stack traces will have minimal context for how the current function is reached, we use LLVM's `PrettyStackTrace` to include details about the state stack. The state stack will be above the function stack in crash output. + +### Dumping objects in interactive debuggers + +We provide namespace-scoped `Dump` functions in several components, such as +[check/dump.cpp](/toolchain/check/dump.cpp). These `Dump` functions will print +contextual information about an object to stderr. The files contain details +regarding support. + +Objects which inherit from `Printable` also have `Dump` member functions, but +these will lack contextual information. diff --git a/toolchain/lex/BUILD b/toolchain/lex/BUILD index 08e2f0bb0d5f7..c68378ef81ccd 100644 --- a/toolchain/lex/BUILD +++ b/toolchain/lex/BUILD @@ -184,6 +184,7 @@ cc_library( hdrs = ["lex.h"], deps = [ ":character_set", + ":dump", ":helpers", ":numeric_literal", ":string_literal", @@ -198,6 +199,18 @@ cc_library( ], ) +cc_library( + name = "dump", + srcs = ["dump.cpp"], + hdrs = ["dump.h"], + deps = [ + ":tokenized_buffer", + "//common:ostream", + ], + # Always link dump methods. + alwayslink = 1, +) + cc_library( name = "token_index", hdrs = ["token_index.h"], diff --git a/toolchain/lex/dump.cpp b/toolchain/lex/dump.cpp new file mode 100644 index 0000000000000..d6e3532a6c9e6 --- /dev/null +++ b/toolchain/lex/dump.cpp @@ -0,0 +1,36 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef NDEBUG + +#include "toolchain/lex/dump.h" + +#include "common/ostream.h" + +namespace Carbon::Lex { + +auto DumpNoNewline(const TokenizedBuffer& tokens, TokenIndex token) -> void { + if (!token.is_valid()) { + llvm::errs() << "TokenIndex(invalid)"; + return; + } + + auto kind = tokens.GetKind(token); + auto line = tokens.GetLineNumber(token); + auto col = tokens.GetColumnNumber(token); + + llvm::errs() << "TokenIndex(kind: " << kind << ", loc: "; + llvm::errs().write_escaped(tokens.source().filename()); + llvm::errs() << ":" << line << ":" << col << ")"; +} + +LLVM_DUMP_METHOD auto Dump(const TokenizedBuffer& tokens, TokenIndex token) + -> void { + DumpNoNewline(tokens, token); + llvm::errs() << '\n'; +} + +} // namespace Carbon::Lex + +#endif // NDEBUG diff --git a/toolchain/lex/dump.h b/toolchain/lex/dump.h new file mode 100644 index 0000000000000..f38f5598b2713 --- /dev/null +++ b/toolchain/lex/dump.h @@ -0,0 +1,34 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// This library contains functions to assist dumping objects to stderr during +// interactive debugging. Functions named `Dump` are intended for direct use by +// developers, and should use overload resolution to determine which will be +// invoked. The debugger should do namespace resolution automatically. For +// example: +// +// - lldb: `expr Dump(tokens, id)` +// - gdb: `call Dump(tokens, id)` +// +// The `DumpNoNewline` functions are helpers that exclude a trailing newline. +// They're intended to be composed by `Dump` function implementations. + +#ifndef CARBON_TOOLCHAIN_LEX_DUMP_H_ +#define CARBON_TOOLCHAIN_LEX_DUMP_H_ + +#ifndef NDEBUG + +#include "toolchain/lex/tokenized_buffer.h" + +namespace Carbon::Lex { + +auto DumpNoNewline(const TokenizedBuffer& tokens, TokenIndex token) -> void; + +auto Dump(const TokenizedBuffer& tokens, TokenIndex token) -> void; + +} // namespace Carbon::Lex + +#endif // NDEBUG + +#endif // CARBON_TOOLCHAIN_LEX_DUMP_H_ diff --git a/toolchain/lex/tokenized_buffer.cpp b/toolchain/lex/tokenized_buffer.cpp index e4a7652033438..06597456cdeaf 100644 --- a/toolchain/lex/tokenized_buffer.cpp +++ b/toolchain/lex/tokenized_buffer.cpp @@ -25,7 +25,7 @@ auto TokenizedBuffer::GetLine(TokenIndex token) const -> LineIndex { } auto TokenizedBuffer::GetLineNumber(TokenIndex token) const -> int { - return GetLineNumber(GetLine(token)); + return GetLine(token).index + 1; } auto TokenizedBuffer::GetColumnNumber(TokenIndex token) const -> int { @@ -162,10 +162,6 @@ auto TokenizedBuffer::IsRecoveryToken(TokenIndex token) const -> bool { return recovery_tokens_[token.index]; } -auto TokenizedBuffer::GetLineNumber(LineIndex line) const -> int { - return line.index + 1; -} - auto TokenizedBuffer::GetNextLine(LineIndex line) const -> LineIndex { LineIndex next(line.index + 1); CARBON_DCHECK(static_cast(next.index) < line_infos_.size()); @@ -262,7 +258,7 @@ auto TokenizedBuffer::PrintToken(llvm::raw_ostream& output_stream, llvm::right_justify( llvm::formatv("'{0}'", token_info.kind().name()).str(), widths.kind + 2), - llvm::format_decimal(GetLineNumber(GetLine(token)), widths.line), + llvm::format_decimal(GetLineNumber(token), widths.line), llvm::format_decimal(GetColumnNumber(token), widths.column), llvm::format_decimal(GetIndentColumnNumber(line_index), widths.indent), token_text); diff --git a/toolchain/lex/tokenized_buffer.h b/toolchain/lex/tokenized_buffer.h index 1822a7c25823c..748e65f51e810 100644 --- a/toolchain/lex/tokenized_buffer.h +++ b/toolchain/lex/tokenized_buffer.h @@ -156,9 +156,6 @@ class TokenizedBuffer : public Printable { // For example, a closing paren inserted to match an unmatched paren. auto IsRecoveryToken(TokenIndex token) const -> bool; - // Returns the 1-based line number. - auto GetLineNumber(LineIndex line) const -> int; - // Returns the 1-based indentation column number. auto GetIndentColumnNumber(LineIndex line) const -> int; diff --git a/toolchain/parse/BUILD b/toolchain/parse/BUILD index 310ff296eeca1..24ee38f008373 100644 --- a/toolchain/parse/BUILD +++ b/toolchain/parse/BUILD @@ -88,6 +88,7 @@ cc_library( ], deps = [ ":context", + ":dump", ":node_kind", ":state", ":tree", @@ -102,6 +103,19 @@ cc_library( ], ) +cc_library( + name = "dump", + srcs = ["dump.cpp"], + hdrs = ["dump.h"], + deps = [ + ":tree", + "//common:ostream", + "//toolchain/lex:dump", + ], + # Always link dump methods. + alwayslink = 1, +) + cc_library( name = "state", srcs = ["state.cpp"], diff --git a/toolchain/parse/context.cpp b/toolchain/parse/context.cpp index dbcd5c6b14338..fd0b6384cb2a3 100644 --- a/toolchain/parse/context.cpp +++ b/toolchain/parse/context.cpp @@ -481,7 +481,7 @@ auto Context::PrintForStackDump(llvm::raw_ostream& output) const -> void { auto Context::PrintTokenForStackDump(llvm::raw_ostream& output, Lex::TokenIndex token) const -> void { - output << " @ " << tokens_->GetLineNumber(tokens_->GetLine(token)) << ":" + output << " @ " << tokens_->GetLineNumber(token) << ":" << tokens_->GetColumnNumber(token) << ": token " << token << " : " << tokens_->GetKind(token) << "\n"; } diff --git a/toolchain/parse/dump.cpp b/toolchain/parse/dump.cpp new file mode 100644 index 0000000000000..127990596e728 --- /dev/null +++ b/toolchain/parse/dump.cpp @@ -0,0 +1,39 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef NDEBUG + +#include "toolchain/parse/dump.h" + +#include "common/ostream.h" +#include "toolchain/lex/dump.h" + +namespace Carbon::Parse { + +auto DumpNoNewline(const Tree& tree, NodeId node_id) -> void { + if (!node_id.is_valid()) { + llvm::errs() << "NodeId(invalid)"; + return; + } + + auto kind = tree.node_kind(node_id); + auto token = tree.node_token(node_id); + + llvm::errs() << "NodeId(kind: " << kind << ", token: "; + Lex::DumpNoNewline(tree.tokens(), token); + llvm::errs() << ")"; +} + +LLVM_DUMP_METHOD auto Dump(const Tree& tree, Lex::TokenIndex token) -> void { + Lex::Dump(tree.tokens(), token); +} + +LLVM_DUMP_METHOD auto Dump(const Tree& tree, NodeId node_id) -> void { + DumpNoNewline(tree, node_id); + llvm::errs() << '\n'; +} + +} // namespace Carbon::Parse + +#endif // NDEBUG diff --git a/toolchain/parse/dump.h b/toolchain/parse/dump.h new file mode 100644 index 0000000000000..4c3d90377c3b4 --- /dev/null +++ b/toolchain/parse/dump.h @@ -0,0 +1,35 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// This library contains functions to assist dumping objects to stderr during +// interactive debugging. Functions named `Dump` are intended for direct use by +// developers, and should use overload resolution to determine which will be +// invoked. The debugger should do namespace resolution automatically. For +// example: +// +// - lldb: `expr Dump(tree, id)` +// - gdb: `call Dump(tree, id)` +// +// The `DumpNoNewline` functions are helpers that exclude a trailing newline. +// They're intended to be composed by `Dump` function implementations. + +#ifndef CARBON_TOOLCHAIN_PARSE_DUMP_H_ +#define CARBON_TOOLCHAIN_PARSE_DUMP_H_ + +#ifndef NDEBUG + +#include "toolchain/parse/tree.h" + +namespace Carbon::Parse { + +auto DumpNoNewline(const Tree& tree, NodeId node_id) -> void; + +auto Dump(const Tree& tree, Lex::TokenIndex token) -> void; +auto Dump(const Tree& tree, NodeId node_id) -> void; + +} // namespace Carbon::Parse + +#endif // NDEBUG + +#endif // CARBON_TOOLCHAIN_PARSE_DUMP_H_