As discussed around #3792, identify the import a diagnostic message came
from prior to the diagnostic message itself. This occurs during location
translation so that the logic can be central.
I'd considered associating the parse node with ImportRef instructions,
but I realized about halfway through that because I need to store the
ImportDirectiveId on the ImportIR for cross-package imports, it's there
for use in location translation without extra work. That saves a fair
amount of stringing it through declarations, as well as an oddity where
ImportRef instructions would have a node that didn't really represent
them.
This revamps the support for cross-package imports, making them look
more like a namespace. The planned model is mentioned on
[#toolchain](https://discord.com/channels/655572317891461132/655578254970716160/1217586076022210670).
This does not implement name lookup into the new namespace structure.
A few key changes in this PR (it's a little sprawling) are:
- Moves logic for adding package imports from context.* to import.*
- Remove SemIR::Import, which was the prior model. This is instead now a
SemIR::Namespace with the NameScope getting a new import_ir_scopes
field.
- Allow SemIR::Namespace to use Parse::ImportDirectiveId in addition to
the prior Parse::NamespaceId
- The import_ir_scopes field includes a NameScopeId so that as we
traverse to child namespaces, we can directly perform name lookup in the
other IR.
- is_closed_import now tracks whether a namespace comes from a different
package. This has a diagnostic implemented in decl_name_stack.
When a member access names an interface member, perform impl lookup to
find the impl and its corresponding member.
---------
Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
Add an instruction to hold the witness table, along with a corresponding
type to keep things simpler. Add `check/impl.{h,cpp}` to house the new
logic. No checking of impls against interfaces is performed yet.
---------
Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
`Self` is modeled as a `bind_symbolic_name` with no corresponding value,
for now at least. In the future it might make sense to model it as a new
kind of instruction, or as a `bind_symbolic_name` whose value is a
`param`, but for now we just want it to introduce a symbolic constant.
In order to convert a value like the `Self` of an interface to a type, a
new instruction `facet_type_access` is introduced. This notionally
accesses the "type" field within a facet, converting it from a pair of
(type, witness) into just the type.
When declaring an associated entity in an interface -- just associated
functions for now -- create an associated entity value and corresponding
type to represent a "slot in a witness table". Also track the list of
associated entities on the interface so that we will eventually be able
to check impls against them.
Associated entities are represented as the integer index of their slot
in a witness table.
---------
Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
By adding a constant to ClassDecl/InterfaceDecl, we're able to remove
name reference special-casing. Use TryEvalInst on the Decl to generate
the Type. For ClassDecl, then use the generated constant for
self_type_id.
Adds `BindAlias` with a hybrid of `BindName` and `NameRef` semantics. I
think it's slightly closer to `BindName` because it introduces a name,
so I'm going more in that direction. This also matches the need for
`bind_name_id` with imports on enclosing scopes.
Note, only things that look like a name reference are being allowed on
the RHS of `alias`. This includes builtins that look like name
references, such as `bool`, but not ones that turn into values
underneath, such as `false`.
- File::StringifyTypeExpr now has a case that hits the ImportRefUsed
TODO, so implementing that. I think the `static` approach will be
helpful in ensuring there aren't access bugs, particularly when future
support is added.
- `let` wasn't adding to exports because it doesn't use
`decl_name_stack` the way `var` does. This now adds to exports, but we
might want to unify logic for issues such as this.
- BuildImportRefUsedValueRepr is now called for another inst kind, and
it seemed like calling back to BuildValueRepr was the best way to
resolve this. I don't *think* that's going to cause recursion.
Collect the contents of an `impl` into a scope, and start doing very
basic checking for `impl` declarations and definitions.
This change adds two new `Id` types to the set of type that `NodeStack`
supports -- `ImplId` and `NameScopeId`.
---------
Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
Right now, ConstantValueStore defaults to having unknown values use
NotConstant. This generally works for the current IR, but with imports
we're expecting sparse entries which are generally unknown -- and
distinguishing between NotConstant and simply unset would be helpful. As
a consequence, add Invalid.
We discussed whether to simply have ConstantValueStore default to
Invalid going forward, or to make the default flexible. The upside to
the former is consistency, the upside to the latter is that it should
result in fewer Sets when operating on the current IR (which will more
frequently have known non-constant values). This PR offers both
approaches in separate commits, but I somewhat lean towards the latter
for fewer array resizes.
Note, a totally different approach would be to use a different class
(not ConstantValueStore) for imported IRs -- then the default of Invalid
versus NotConstant would be type-dependent. However, I expect we're
going to want to do at least somewhat consistent lookups, and using the
same ConstantValueStore for both cases allows avoiding a virtual
interface or templating. Also, I'm hoping to only maintain the
ConstantValueStore for an imported IR as part of Context (not File),
which would mean the SmallVector storage overhead is ephemeral,
mitigating one of the potential advantages of using a different type for
imported IRs.
Consume the components of the `impl` declaration, and set up scopes for
the child elements. We don't yet build a representation for the impl
itself.
Also, add an interface type value. This is necessary so that we have a
value for the expression on the right-hand side of `as` in an `impl`.
I'm basically just nudging down the path I think is right here. Adding a
small bit more support, but also more tests to capture cases that I
think will need to be verified as working.
With diagnostics like "Value of type `<function>` is not callable.",
that's because it expects a FunctionDecl but is instead finding a
ImportRefUsed. I'll need to work out the necessary support for a
callable function.
One more (hopefully last) rename on the Import instruction renaming.
I was kind of tempted to rename to just "IRId", since the IRs aren't all
imports. However, this felt easier to read, and a better choice than
CrossRef because it's more consistent with the other ways imports exist
in code. (even if IRs aren't all imports, most use-cases are derived
from imports)
Note though that import_irs may include IRs not just from direct
imports. Beyond the builtin IR, I'm thinking that for indirect imports,
or the prelude, we may end up adding them. e.g., so that constants can
be generated for indirect imports and still correspond to a directly
known IR, and for a given IR that's indirectly imported multiple times
to be deduplicated locally. I'm not there yet, I'm just mentioning this
to help give background for naming thoughts.
This is a bit of a cleanup; I probably should've just renamed CrossRef
instead of adding ImportRefUsed.
Adding `is_builtin` to InstId is more about providing a standard API for
the check, which I expect to add a little more of.
Shifts import tests to validate that the BuildValueRepr CHECK isn't
accidentally hit.
Recent runs of `clang-tidy` for me started showing more errors, and this
is a collection of changes to address them.
First, I've systematically applied the disabling tag to all C++ rules
under //explorer/... with `buildozer` so we don't spend time analyzing
this code or reporting errors from it. Not sure this was strictly
necessary, but it seemed like a nice consistency improvement.
Next, I disabled a buggy check for missing `default` cases in
`switch`es. It seems to get confused by the fancy conversions in our
`enum_base.h`. We don't miss much with this as the Clang compiler
warnings for `switch` catch most of our actual bugs. I also removed the
local disabling of this now that it is turned off centrally.
Lastly, I added error checking to two file descriptor manipulating calls
in the `file_test` infrastructure.
---------
Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
Builds on #3656.
Under the prior "lazy" model, we had been planning to copy instructions.
Under the current "unused+used" model, we're restricting that to
constants. I'm trimming back some of the ResolveIfImportRefUnused logic
because it was more appropriate for the former model.
Right now, I'm adding ImportRefUsed direct creation in import.cpp for
namespaces. I think I'll need to do something similar in
RseolveIfImportRefUnused... I'm still trying to think about how to
manage type information there (which needs to come in as an import
reference itself, and probably have some amount of deduplication before
forming a TypeId). So ImportRefUnused lacks a type because I'm hesitant
to aggressively load it, whereas ImportRefUsed should *always* have a
type but it's just an error while I think things through.
For reference, ImportRefUnused and ImportRefUsed are mainly split in
order to track the boolean "used" without making fundamental
modifications to Inst for bit packing (this effectively instead packs a
bit into InstKind). AnyImportRef currently excludes the type because
it's mainly for diagnostic printing at the moment. It could end up with
a TypeId that would end up Invalid for ImportRefUnused, though it could
also be that the TypeId is only accessed when using ImportRefUsed
explicitly.
This makes some changes to the formatter so that ImportRefUnused and
ImportRefUsed will both be labeled as "import_ref" with an "unused ->
used" argument change in textual IR, but is otherwise not changing
logic.
To avoid bouncing through `constant_values()` to determine whether a
type is symbolic or template, store the `ConstantId` on the `TypeInfo`
not just the `InstId`.
In addition to propagating the symbolic / template phase, this also
propagates whether a type contains an error, resulting in our no longer
producing types such as `<error>*` -- these now evaluate to simply
`<error>`. While this makes our types less precise after an error, it
also removes some follow-on diagnostics, so it seems to be an
improvement on the whole.
Remove the type canonicalization mechanism and instead rely on constant
canonicalization to deduplicate types.
Rename the `Canonicalize*Type` functions to reflect that they're no
longer performing canonicalization. Switch code that creates types due
to semantic checking, rather than due to source syntax, to directly
create type constants through evaluation rather than creating an
instruction and evaluating it to produce a separate constant
representation.
The mapping from `const (const T)` that was previously performed by type
canonicalization is now implemented in expression evaluation instead.
The value `<error>` is now treated as a constant value, with a special
property that an instruction involving `<error>` that could possibly be
constant evaluates to `<error>`. This helps avoid producing follow-on
errors when an error occurs as a subexpression of an expression, such as
a type, that is intended to be constant.
Rather than producing multiple constants with the same value, fold all
instances of a given constant to the same constant instruction.
A future PR will use this to replace the current type canonicalization
system.
The parse nodes are still tracked as part of the same value store
interface in order to ensure parity, but they're split out from Inst
itself in order to reduce the size of Inst -- the expectation is that
they don't need to be passed around quite as much.
This change doesn't actually reduce the passing very much, although
there are hints of it: AddInstAndPush doesn't typically need a separate
parse node from the one on the Inst itself, for example. In a couple
spots I changed code to rely a little more on the InstId until the
ParseNode is needed, but it's very low hanging fruit where done. I think
convert could do more to not eagerly fetch the parse node before its
use, but more cleanup felt it would be easier to handle separately. I'm
currently viewing this as making such cleanup _possible_ rather than
executing on it up-front.
But also, I want to make sure there's a consensus to head in this
direction before pulling the trigger. We speculated that this would
result in the parse node being passed around less, and I do think that's
the case, although it's a bit fuzzy in the change.
---------
Co-authored-by: Richard Smith <richard@metafoo.co.uk>
Form a side table with constant values for each instruction. Evaluation
is only supported for a few very simple kinds of instruction for now.
This is not observable outside of the SemIR output, because nothing
depends on expressions having a constant value phase yet.
Namespaces are copied, which means also adding their name to the
underlying instruction. It happened not to be done previously; the name
was only in name lookup.
Since the only import supported right now is the default import,
functionality is limited; in the future I'll need to deal with namespace
vs package conflicts.
Tests of namespace imports are under "namespace" -- I figured this would
be best for scaling as more instructions get support.
This also improves some debugging-related output that I was trying to
use while trying to build the support.
---------
Co-authored-by: Richard Smith <richard@metafoo.co.uk>
This change adds a `BindSymbolicName` instruction for generic bindings,
paralleling the existing `BindName`. A mechanism is also added to allow
both kinds of binding to be accessed uniformly, for convenience in the
case where the two different kinds of binding are treated the same.
Generic bindings of type `type` are allowed to be used as types,
although no operations are provided for such types. For now lowering
treats these types as empty structs, which seems like a reasonable
lowering for non-monomorphized unconstrained types.
This is a step towards adding enclosing scopes for imports. It creates
an indirection for all bind names.
We discussed specializing for bindings that are in function scope (i.e.,
not a useful enclosing scope for imports or diagnostics). However, the
thought is to go ahead with this singular approach for now, and only
change structure if it's a performance issues so that we have
incrementally fewer instructions to handle.
This adds a block for exported InstIds, rather than scanning the package
scope. This working down a path discussed last month, which we'll need
to add enclosing namespaces to the Inst in order to complete import of
something like `namespace NS; var NS.a;`
Exports could've been a separate `vector<InstId>` on `SemIR::File`, but
using an entry in `inst_blocks` felt more consistent.
The goal here is to make the representation more uniform so that we can
start adding different kinds of binding -- checked generic bindings and
template bindings -- across both function parameters and local `let`
declarations.
With this change, the entry in the parameter list for the function is
the name binding, not the Param itself, which has some ripple effects on
consumers of that list that want to access the parameter rather than the
binding. This is expected to change again when we start adding more of
the pattern matching SemIR, but this seems good enough for now.
Adds a `LazyImportRef` instruction. Versus `CrossRef`, this is intended
to represent an instruction which cannot be used directly, and must be
replaced when it comes up due to name lookup. The intent is to use this
to avoid recursive loading of imported IR instructions.
Note, under this model, when `ResolveIfLazyImportRef` is called, it
essentially needs to load both inst and type information to a sufficient
point where any further attempts would hit name lookup again. That will
probably be complex, and the current implementation is just touching the
surface of the issue. I was heading down this route because it would
mean we have a limited number of points that need to consider whether
they're going to talk about a `LazyImportRef`.
I'm considering whether `CrossRef` should be dropped in favor of more
specific `Builtin` special-casing, due to the divergence of desired
behaviors. This could mean dropping the `builtins` IR since it's not
looking useful right now.
Modify `NameScope` to track whether the scope is associated with a load
error. This is to handle cases where one or more imports failed, so we
do not want to issue warnings for related scopes.
The 0-size on `ValueStore` comes up due to the changes to `NameScope`,
which make it too large for the default handling. After discussion with
zygoloid, the thought was we might want to try reserving a roughly
correct value based on parse node counts, but the stack default wasn't
buying much.
Fixes a bug where the implicit import used the package name instead of
the invalid identifier.
BinaryOperatorAdd had been added early on as a proof-of-concept for
toolchain design for parsing -> checking flow. However, it doesn't
reflect the interface direction for operators, and now other portions of
the toolchain demonstrate the relevant logic. Instead, it's just a bit
of an outlier versus other instructions which have evolved over time.
Most of the calls to `StringifyType` already passed `true` for
`in_type_context`. Checking the rest, I found that every one of them was
already sufficiently clear that they were printing a type, or could be
made so with a very small change to the diagnostic text.
I was suggesting this because `FloatingPoint` is pretty long. `int` and
`float` should be familiar abbreviations. `unsigned` should be familiar
to developers too, but `UnsignedInt` still feels usefully clearer for
the additional chars.
This adds instructions so that we get printing. I may adjust the
instruction format a little further to add a type, but I think the basic
setup will remain.
Note this builds on #3414