diff --git a/toolchain/docs/check/associated_constant.md b/toolchain/docs/check/associated_constant.md index b1a789139c3b..7104900a7b92 100644 --- a/toolchain/docs/check/associated_constant.md +++ b/toolchain/docs/check/associated_constant.md @@ -28,16 +28,18 @@ _Note:_ This document only describes non-function associated constants. An associated constant is declared within an interface scope with the syntax: ```carbon -[MODIFIERS] let NAME:! TYPE [= INITIALIZER] ; +[MODIFIERS] let NAME: TYPE [= INITIALIZER] ; ``` Associated constants introduce a slot in the witness table for an interface that contains a value of type `TYPE`. -Associated constants are always generic entities, because they're always -parameterized at least by the `Self` type of the interface, as well as any other -enclosing generic parameters. Note that the interface itself is _not_ -parameterized by its `Self`. +Associated constants are declared within the interface-with-self generic, which +is parameterized by the `Self` type of the interface, and is nested within the +interface generic, which is parameterized by any other generic parameters of the +interface. Symbolic instructions within the declaration of an associated +constant, such as those used to compute its type, are part of the +interface-with-self generic. Associated constant entities are held in the `associated_constants` value store as objects of type `AssociatedConstant`. Each declaration of an associated @@ -50,80 +52,76 @@ be found. Because associated constants share the syntax of `let` declarations, a lot of the checking logic is also shared. This logic is in -[handle_let_and_var.cpp](/toolchain/check/handle_let_and_var.cpp). Associated -constant declaration handling proceeds as follows: +[handle_let_and_var.cpp](/toolchain/check/handle_let_and_var.cpp). The parser +produces distinct parse nodes for a `let` declaration in an interface scope, and +associated constant declaration handling proceeds as follows: 1. ```carbon - let NAME:! TYPE [= INITIALIZER] ; + let NAME: TYPE [= INITIALIZER] ; ^ ``` - `StartAssociatedConstant` is called at the start of an interface-scope `let` - declaration. This: + The handler for `AssociatedConstantIntroducer` is called at the start of + the declaration. This: -- Starts a generic declaration region. -- Pushes an instruction block to hold instructions within the declaration - of the constant. These form the body of the generic. + - Pushes an instruction block to hold instructions within the declaration + of the constant. + - Performs the same setup as for other `let` declarations, such as + starting a full pattern and an expression region for the type. 2. ```carbon - let NAME:! TYPE [= INITIALIZER] ; - ~~~~^~~~~~~ + let NAME: TYPE [= INITIALIZER] ; + ~~~~^~~~~~ ``` - Process the symbolic binding pattern. This is done in + Process the name and type. This is done by the handler for + `AssociatedConstantNameAndType` in [handle_binding_pattern.cpp](/toolchain/check/handle_binding_pattern.cpp), - which detects that we are at interface scope, and creates an - `AssociatedConstantDecl` and corresponding `AssociatedConstant` entity. This - binding is then produced as the instruction associated with the binding - pattern. - - _Note:_ This is somewhat unusual: usually, a pattern instruction would be - associated with a pattern parse node. + which creates an `AssociatedConstantDecl` and corresponding + `AssociatedConstant` entity. This instruction is then produced as the + pattern for the declaration. 3. ```carbon - let NAME:! TYPE ; - ^ - let NAME:! TYPE = INITIALIZER ; - ^ + let NAME: TYPE ; + ^ + let NAME: TYPE = INITIALIZER ; + ^ ``` - When we reach the end of the pattern in an interface-scope `let` binding, - either because we reached the `=` or because we reached the `;` and there - was no initializer, `EndAssociatedConstantDeclRegion` is called. This: + When we reach the end of the pattern, either because we reached the `=` or + because we reached the `;` and there was no initializer, the full pattern + is ended and `EndAssociatedConstantDeclRegion` is called. This: -- Ends the generic declaration region. -- Builds an `AssociatedEntity` object, reserving a slot in the interface's - witness table for the constant. -- Adds the associated constant to name lookup. - - _Note:_ The pattern might not be valid for an associated constant. In this - case, we won't have built an `AssociatedConstantDecl` in the previous step. - When this happens, we instead just discard the generic declaration region - and continue. The invalid pattern will be diagnosed later. + - Builds an `AssociatedEntity` object, reserving a slot in the + interface's witness table for the constant. + - Adds the associated constant to name lookup. 4. ```carbon - let NAME:! TYPE = INITIALIZER ; - ^ + let NAME: TYPE = INITIALIZER ; + ^ ``` - If there is an initializer, we start the generic definition region. + If there is an initializer, the handler for `AssociatedConstantInitializer` + starts processing it, in the same way as for other `let` declarations. 5. ```carbon - let NAME:! TYPE [= INITIALIZER] ; - ^ + let NAME: TYPE [= INITIALIZER] ; + ^ ``` - At the end of the declaration, `FinishAssociatedConstant` is called to - finalize the declaration. This: + At the end of the declaration, the handler for `AssociatedConstantDecl` + finalizes the declaration. This: -- Diagnoses if the pattern handling didn't create an - `AssociatedConstantDecl`. -- Finishes handling the initializer, if it's present: - - Converts the initializer to the type of the constant. - - Ends the generic definition region. -- Pops the inst block created by `StartAssociatedConstant` and attaches it - to the `AssociatedConstantDecl`. -- Adds the `AssociatedConstantDecl` to the enclosing inst block. + - If the pattern is an error, marks the interface-with-self scope as + having an error, and discards the instruction block. + - Otherwise: + - If there is an initializer, converts it to the type of the + constant, and stores the result as the `default_value_id` of the + `AssociatedConstant`. + - Pops the instruction block created in step 1 and attaches it to + the `AssociatedConstantDecl`. + - Adds the `AssociatedConstantDecl` to the enclosing instruction + block. ## Specifying rewrite constraints @@ -153,35 +151,43 @@ depends on how the member name was found. In `LookupMemberNameInScope`, if lookup for `y` in `x.y` finds an associated constant from interface `I`, then a witness is determined as follows: -- If the lookup scope is the type `T` of `x`, then: - - If `T` is a non-type facet, the witness for that facet is used. TODO: - That facet might not contain a witness for `I`. In that case we will - need to perform impl lookup for `T as I` instead. - - Otherwise, impl lookup for `T as I` is performed to find the witness. +- If the lookup scope is the type `T` of `x`, then `PerformImplLookup` is + called to perform impl lookup for `T as I`. - If the lookup scope is `x` itself, then: - - If `x` is a facet type or a namespace, impl lookup is not performed, and - the result is simply `y`. This happens for cases such as - `Interface.AssocConst`. - - Otherwise, `x` must be a type other than a facet type, and impl lookup - for `x as I` is performed to find the witness. + - If `x` is a namespace or a facet type other than `type`, impl lookup is + not performed, and the result is simply `y`. This happens for cases + such as `Interface.AssocConst`. + - Otherwise, `x` must be a type, and `PerformImplLookup` is called to + perform impl lookup for `x as I`. ### Compound member access In `PerformCompoundMemberAccess` for `x.(y)`, if `y` is an associated constant -then impl lookup is performed for `T as I`, where `T` is the type of `x` and `I` -is the interface in which `y` is declared to find the witness containing the -constant value. +from interface `I`, then `GetAssociatedValueImpl` converts `x` itself to a facet +value of type `I`, and performs impl lookup for `x as I` to find the witness +containing the constant value. The same logic is used by `GetAssociatedValue`, +which finds the value of an associated entity for a given type or facet without +going through member access syntax. + +_Note:_ This differs from the handling of associated functions that are +instance methods, for which impl lookup is performed for `T as I`, where `T` +is the type of `x`. ### Forming the constant value -Once the witness is determined, `AccessMemberOfImplWitness` is called to find -the value of the associated constant in the witness. In the case where an impl -lookup is needed, `PerformImplLookup` calls `AccessMemberOfImplWitness`, -otherwise it's called directly. +Once the witness is determined, a specific for the interface-with-self generic +is formed by `MakeSpecificWithInnerSelf` from the specific for the interface and +the self facet value. For simple member access, this happens in +`PerformImplLookup`, which then calls `AccessMemberOfImplWitness`. For compound +member access, this happens directly in `GetAssociatedValueImpl`. -`AccessMemberOfImplWitness` uses `GetTypeForSpecificAssociatedEntity` to form -the type of the constant. This substitutes both the generic arguments (if any) -for the interface and the `Self` type into the type of the associated constant. -Then, an `ImplWitnessAccess` instruction is created to extract the relevant slot -from the witness. Constant evaluation of this instruction reads the associated -constant from the witness table. +`GetTypeForSpecificAssociatedEntity` is then used to form the type of the +constant by substituting the interface-with-self specific into the type of the +associated constant. Then, an `ImplWitnessAccess` instruction is created to +extract the relevant slot from the witness. Constant evaluation of this +instruction reads the associated constant from the witness table. + +If the access appears within a `where` expression that has a rewrite constraint +for the same associated constant, the `ImplWitnessAccess` is wrapped in an +`ImplWitnessAccessSubstituted` instruction that also records the rewritten +value.