Skip to content

Commit d71b844

Browse files
authored
Add a writeup for how associated constants are processed. (#4856)
Also fix a bug found when working through this.
1 parent a7de3b8 commit d71b844

File tree

6 files changed

+303
-5
lines changed

6 files changed

+303
-5
lines changed

toolchain/check/handle_let_and_var.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,9 @@ static auto FinishAssociatedConstant(Context& context, Parse::LetDeclId node_id,
300300
context.name_scopes()
301301
.Get(context.interfaces().Get(interface_id).scope_id)
302302
.set_has_error();
303+
if (decl_info.init_id.has_value()) {
304+
DiscardGenericDecl(context);
305+
}
303306
context.inst_block_stack().Pop();
304307
return;
305308
}

toolchain/check/testdata/interface/no_prelude/fail_assoc_const_not_binding.carbon

Lines changed: 103 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,90 @@
88
// TIP: To dump output, run:
99
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interface/no_prelude/fail_assoc_const_not_binding.carbon
1010

11+
// --- fail_tuple_pattern.carbon
12+
13+
library "[[@TEST_NAME]]";
14+
1115
interface I {
12-
// CHECK:STDERR: fail_assoc_const_not_binding.carbon:[[@LINE+4]]:7: error: semantics TODO: `tuple pattern in let/var` [SemanticsTodo]
16+
// CHECK:STDERR: fail_tuple_pattern.carbon:[[@LINE+4]]:7: error: semantics TODO: `tuple pattern in let/var` [SemanticsTodo]
1317
// CHECK:STDERR: let (T:! type, U:! type);
1418
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~
1519
// CHECK:STDERR:
1620
let (T:! type, U:! type);
1721
}
1822

19-
// CHECK:STDOUT: --- fail_assoc_const_not_binding.carbon
23+
// --- fail_tuple_pattern_with_default.carbon
24+
25+
library "[[@TEST_NAME]]";
26+
27+
interface I {
28+
// CHECK:STDERR: fail_tuple_pattern_with_default.carbon:[[@LINE+4]]:15: error: semantics TODO: `tuple pattern in let/var` [SemanticsTodo]
29+
// CHECK:STDERR: default let (T:! type, U:! type) = ({}, {});
30+
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~
31+
// CHECK:STDERR:
32+
default let (T:! type, U:! type) = ({}, {});
33+
}
34+
35+
// --- fail_var_pattern.carbon
36+
37+
library "[[@TEST_NAME]]";
38+
39+
interface I {
40+
// CHECK:STDERR: fail_var_pattern.carbon:[[@LINE+8]]:7: error: expected name in binding pattern [ExpectedBindingPattern]
41+
// CHECK:STDERR: let var T:! type;
42+
// CHECK:STDERR: ^~~
43+
// CHECK:STDERR:
44+
// CHECK:STDERR: fail_var_pattern.carbon:[[@LINE+4]]:7: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo]
45+
// CHECK:STDERR: let var T:! type;
46+
// CHECK:STDERR: ^~~
47+
// CHECK:STDERR:
48+
let var T:! type;
49+
}
50+
51+
// --- fail_var_pattern_with_default.carbon
52+
53+
library "[[@TEST_NAME]]";
54+
55+
interface I {
56+
// CHECK:STDERR: fail_var_pattern_with_default.carbon:[[@LINE+8]]:15: error: expected name in binding pattern [ExpectedBindingPattern]
57+
// CHECK:STDERR: default let var T:! type = {};
58+
// CHECK:STDERR: ^~~
59+
// CHECK:STDERR:
60+
// CHECK:STDERR: fail_var_pattern_with_default.carbon:[[@LINE+4]]:15: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo]
61+
// CHECK:STDERR: default let var T:! type = {};
62+
// CHECK:STDERR: ^~~
63+
// CHECK:STDERR:
64+
default let var T:! type = {};
65+
}
66+
67+
// CHECK:STDOUT: --- fail_tuple_pattern.carbon
68+
// CHECK:STDOUT:
69+
// CHECK:STDOUT: constants {
70+
// CHECK:STDOUT: %I.type: type = facet_type <@I> [template]
71+
// CHECK:STDOUT: %Self: %I.type = bind_symbolic_name Self, 0 [symbolic]
72+
// CHECK:STDOUT: }
73+
// CHECK:STDOUT:
74+
// CHECK:STDOUT: file {
75+
// CHECK:STDOUT: package: <namespace> = namespace [template] {
76+
// CHECK:STDOUT: .I = %I.decl
77+
// CHECK:STDOUT: }
78+
// CHECK:STDOUT: %I.decl: type = interface_decl @I [template = constants.%I.type] {} {}
79+
// CHECK:STDOUT: }
80+
// CHECK:STDOUT:
81+
// CHECK:STDOUT: interface @I {
82+
// CHECK:STDOUT: %Self: %I.type = bind_symbolic_name Self, 0 [symbolic = constants.%Self]
83+
// CHECK:STDOUT:
84+
// CHECK:STDOUT: !members:
85+
// CHECK:STDOUT: .Self = %Self
86+
// CHECK:STDOUT: has_error
87+
// CHECK:STDOUT: witness = ()
88+
// CHECK:STDOUT: }
89+
// CHECK:STDOUT:
90+
// CHECK:STDOUT: assoc_const @T T:! type;
91+
// CHECK:STDOUT:
92+
// CHECK:STDOUT: assoc_const @U U:! type;
93+
// CHECK:STDOUT:
94+
// CHECK:STDOUT: --- fail_tuple_pattern_with_default.carbon
2095
// CHECK:STDOUT:
2196
// CHECK:STDOUT: constants {
2297
// CHECK:STDOUT: %I.type: type = facet_type <@I> [template]
@@ -43,3 +118,29 @@ interface I {
43118
// CHECK:STDOUT:
44119
// CHECK:STDOUT: assoc_const @U U:! type;
45120
// CHECK:STDOUT:
121+
// CHECK:STDOUT: --- fail_var_pattern.carbon
122+
// CHECK:STDOUT:
123+
// CHECK:STDOUT: constants {
124+
// CHECK:STDOUT: }
125+
// CHECK:STDOUT:
126+
// CHECK:STDOUT: file {}
127+
// CHECK:STDOUT:
128+
// CHECK:STDOUT: interface @I {
129+
// CHECK:STDOUT: !members:
130+
// CHECK:STDOUT: .Self = <unexpected>.inst15
131+
// CHECK:STDOUT: witness = invalid
132+
// CHECK:STDOUT: }
133+
// CHECK:STDOUT:
134+
// CHECK:STDOUT: --- fail_var_pattern_with_default.carbon
135+
// CHECK:STDOUT:
136+
// CHECK:STDOUT: constants {
137+
// CHECK:STDOUT: }
138+
// CHECK:STDOUT:
139+
// CHECK:STDOUT: file {}
140+
// CHECK:STDOUT:
141+
// CHECK:STDOUT: interface @I {
142+
// CHECK:STDOUT: !members:
143+
// CHECK:STDOUT: .Self = <unexpected>.inst15
144+
// CHECK:STDOUT: witness = invalid
145+
// CHECK:STDOUT: }
146+
// CHECK:STDOUT:

toolchain/docs/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ The main components are:
4444
[Lex::TokenizedBuffer](/toolchain/lex/tokenized_buffer.h).
4545
3. [Parse](parse.md): Transform a TokenizedBuffer into a
4646
[Parse::Tree](/toolchain/parse/tree.h).
47-
4. [Check](check.md): Transform a Tree to produce
47+
4. [Check](check): Transform a Tree to produce
4848
[SemIR::File](/toolchain/sem_ir/file.h).
4949
5. [Lower](lower.md): Transform the SemIR to an
5050
[LLVM Module](https://llvm.org/doxygen/classllvm_1_1Module.html).

toolchain/docs/check.md renamed to toolchain/docs/check/README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
1111
## Table of contents
1212

1313
- [Overview](#overview)
14+
- [Subtopics](#subtopics)
1415
- [Postorder processing](#postorder-processing)
1516
- [Key IR concepts](#key-ir-concepts)
1617
- [Instruction operands](#instruction-operands)
@@ -47,6 +48,12 @@ or SemIR. This will look closer to a series of instructions, in preparation for
4748
transformation to LLVM IR. Semantic analysis and type checking occurs during the
4849
production of SemIR. It also does any validation that requires context.
4950

51+
## Subtopics
52+
53+
Some particular topics have their own documentation:
54+
55+
- [Associated constants](associated_constant.md)
56+
5057
## Postorder processing
5158

5259
The checking step is oriented on postorder processing on the `Parse::Tree` to
@@ -82,7 +89,7 @@ instruction, and `SemIR::PointerType` represents a pointer type instruction.
8289
Each instruction class has up to four public data members describing the
8390
instruction, as described in
8491
[sem_ir/typed_insts.h](/toolchain/sem_ir/typed_insts.h) (also see
85-
[adding features for Check](adding_features.md#check)):
92+
[adding features for Check](/toolchain/docs/adding_features.md#check)):
8693

8794
- An `InstKind kind;` member if the instruction has a `Kinds` constant making
8895
it a shorthand for multiple individual instructions.
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
# Associated constants
2+
3+
<!--
4+
Part of the Carbon Language project, under the Apache License v2.0 with LLVM
5+
Exceptions. See /LICENSE for license information.
6+
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7+
-->
8+
9+
<!-- toc -->
10+
11+
## Table of contents
12+
13+
- [Overview](#overview)
14+
- [Declaration checking](#declaration-checking)
15+
- [Specifying rewrite constraints](#specifying-rewrite-constraints)
16+
- [Definition of associated constant values](#definition-of-associated-constant-values)
17+
- [Use of associated constants](#use-of-associated-constants)
18+
- [Simple member access](#simple-member-access)
19+
- [Compound member access](#compound-member-access)
20+
- [Forming the constant value](#forming-the-constant-value)
21+
22+
<!-- tocstop -->
23+
24+
## Overview
25+
26+
_Note:_ This document only describes non-function associated constants.
27+
28+
An associated constant is declared within an interface scope with the syntax:
29+
30+
```carbon
31+
[MODIFIERS] let NAME:! TYPE [= INITIALIZER] ;
32+
```
33+
34+
Associated constants introduce a slot in the witness table for an interface that
35+
contains a value of type `TYPE`.
36+
37+
Associated constants are always generic entities, because they're always
38+
parameterized at least by the `Self` type of the interface, as well as any other
39+
enclosing generic parameters. Note that the interface itself is _not_
40+
parameterized by its `Self`.
41+
42+
Associated constant entities are held in the `associated_constants` value store
43+
as objects of type `AssociatedConstant`. Each declaration of an associated
44+
constant is modeled by an `AssociatedConstantDecl` instruction. Each such
45+
instruction is then wrapped in an `AssociatedEntity` instruction which
46+
represents the slot within an interface witness where the constant's value can
47+
be found.
48+
49+
## Declaration checking
50+
51+
Because associated constants share the syntax of `let` declarations, a lot of
52+
the checking logic is also shared. This logic is in
53+
[handle_let_and_var.cpp](/toolchain/check/handle_let_and_var.cpp). Associated
54+
constant declaration handling proceeds as follows:
55+
56+
1. ```carbon
57+
let NAME:! TYPE [= INITIALIZER] ;
58+
^
59+
```
60+
61+
`StartAssociatedConstant` is called at the start of an interface-scope `let`
62+
declaration. This:
63+
64+
- Starts a generic declaration region.
65+
- Pushes an instruction block to hold instructions within the declaration
66+
of the constant. These form the body of the generic.
67+
68+
2. ```carbon
69+
let NAME:! TYPE [= INITIALIZER] ;
70+
~~~~^~~~~~~
71+
```
72+
73+
Process the symbolic binding pattern. This is done in
74+
[handle_binding_pattern.cpp](/toolchain/check/handle_binding_pattern.cpp),
75+
which detects that we are at interface scope, and creates an
76+
`AssociatedConstantDecl` and corresponding `AssociatedConstant` entity. This
77+
binding is then produced as the instruction associated with the binding
78+
pattern.
79+
80+
_Note:_ This is somewhat unusual: usually, a pattern instruction would be
81+
associated with a pattern parse node.
82+
83+
3. ```carbon
84+
let NAME:! TYPE ;
85+
^
86+
let NAME:! TYPE = INITIALIZER ;
87+
^
88+
```
89+
90+
When we reach the end of the pattern in an interface-scope `let` binding,
91+
either because we reached the `=` or because we reached the `;` and there
92+
was no initializer, `EndAssociatedConstantDeclRegion` is called. This:
93+
94+
- Ends the generic declaration region.
95+
- Builds an `AssociatedEntity` object, reserving a slot in the interface's
96+
witness table for the constant.
97+
- Adds the associated constant to name lookup.
98+
99+
_Note:_ The pattern might not be valid for an associated constant. In this
100+
case, we won't have built an `AssociatedConstantDecl` in the previous step.
101+
When this happens, we instead just discard the generic declaration region
102+
and continue. The invalid pattern will be diagnosed later.
103+
104+
4. ```carbon
105+
let NAME:! TYPE = INITIALIZER ;
106+
^
107+
```
108+
109+
If there is an initializer, we start the generic definition region.
110+
111+
5. ```carbon
112+
let NAME:! TYPE [= INITIALIZER] ;
113+
^
114+
```
115+
116+
At the end of the declaration, `FinishAssociatedConstant` is called to
117+
finalize the declaration. This:
118+
119+
- Diagnoses if the pattern handling didn't create an
120+
`AssociatedConstantDecl`.
121+
- Finishes handling the initializer, if it's present:
122+
- Converts the initializer to the type of the constant.
123+
- Ends the generic definition region.
124+
- Pops the inst block created by `StartAssociatedConstant` and attaches it
125+
to the `AssociatedConstantDecl`.
126+
- Adds the `AssociatedConstantDecl` to the enclosing inst block.
127+
128+
## Specifying rewrite constraints
129+
130+
TODO: Fill this out. In particular, note that we do not convert the rewrite to
131+
the type of the associated constant as part of forming a `where` expression if
132+
the constant's type is symbolic, and instead defer that until the facet type is
133+
resolved.
134+
135+
## Definition of associated constant values
136+
137+
Associated constant values are stored into witness tables as part of impl
138+
processing in [impl.cpp](/toolchain/check/impl.cpp).
139+
140+
TODO: Fill this out once the new model is implemented.
141+
142+
## Use of associated constants
143+
144+
The work to handle uses of associated constants starts in
145+
[member_access.cpp](/toolchain/check/member_access.cpp).
146+
147+
When an `AssociatedEntity` is the member in a member access, impl lookup is
148+
performed to find the corresponding impl witness. The self type in impl lookup
149+
depends on how the member name was found.
150+
151+
### Simple member access
152+
153+
In `LookupMemberNameInScope`, if lookup for `y` in `x.y` finds an associated
154+
constant from interface `I`, then a witness is determined as follows:
155+
156+
- If the lookup scope is the type `T` of `x`, then:
157+
- If `T` is a non-type facet, the witness for that facet is used. TODO:
158+
That facet might not contain a witness for `I`. In that case we will
159+
need to perform impl lookup for `T as I` instead.
160+
- Otherwise, impl lookup for `T as I` is performed to find the witness.
161+
- If the lookup scope is `x` itself, then:
162+
- If `x` is a facet type or a namespace, impl lookup is not performed, and
163+
the result is simply `y`. This happens for cases such as
164+
`Interface.AssocConst`.
165+
- Otherwise, `x` must be a type other than a facet type, and impl lookup
166+
for `x as I` is performed to find the witness.
167+
168+
### Compound member access
169+
170+
In `PerformCompoundMemberAccess` for `x.(y)`, if `y` is an associated constant
171+
then impl lookup is performed for `T as I`, where `T` is the type of `x` and `I`
172+
is the interface in which `y` is declared to find the witness containing the
173+
constant value.
174+
175+
### Forming the constant value
176+
177+
Once the witness is determined, `AccessMemberOfImplWitness` is called to find
178+
the value of the associated constant in the witness. In the case where an impl
179+
lookup is needed, `PerformImplLookup` calls `AccessMemberOfImplWitness`,
180+
otherwise it's called directly.
181+
182+
`AccessMemberOfImplWitness` uses `GetTypeForSpecificAssociatedEntity` to form
183+
the type of the constant. This substitutes both the generic arguments (if any)
184+
for the interface and the `Self` type into the type of the associated constant.
185+
Then, an `ImplWitnessAccess` instruction is created to extract the relevant slot
186+
from the witness. Constant evaluation of this instruction reads the associated
187+
constant from the witness table.

toolchain/docs/parse.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ structures, but it may still be helpful for tools such as syntax highlighters or
4949
refactoring tools.
5050

5151
In general, we favor doing the checking for whether something is allowed _in a
52-
particular context_ in [the check stage](check.md) instead of the parse stage,
52+
particular context_ in [the check stage](check) instead of the parse stage,
5353
unless the context is very local. This is for a few reasons:
5454

5555
- We anticipate that the parse stage will be used to operate on invalid code

0 commit comments

Comments
 (0)