mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-05 22:02:55 +01:00
Factor out an impl declaration helper function (#5851)
In trying to have types implicitly define `impl Self as Destroy`, I'm wanting to use standard impl declaration support. For example, this should produce more consistent errors if someone writes code that would conflict with the generated impl. I'm also concerned, with the complexity involved, that I'd get something wrong if I tried to write a divergent implementation. I'm only factoring out the start of the declaration. Right now the finishing portion seems much simpler and lower risk to duplicate; I may also factor it out separately. But either way, I think `StartImplDecl` here is high churn risk due to its size (`CheckConstraintIsInterface` I also expect to be used).
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "toolchain/base/kind_switch.h"
|
||||
#include "toolchain/check/context.h"
|
||||
#include "toolchain/check/deduce.h"
|
||||
#include "toolchain/check/eval.h"
|
||||
#include "toolchain/check/facet_type.h"
|
||||
#include "toolchain/check/function.h"
|
||||
@@ -13,6 +14,7 @@
|
||||
#include "toolchain/check/import_ref.h"
|
||||
#include "toolchain/check/inst.h"
|
||||
#include "toolchain/check/interface.h"
|
||||
#include "toolchain/check/merge.h"
|
||||
#include "toolchain/check/name_lookup.h"
|
||||
#include "toolchain/check/thunk.h"
|
||||
#include "toolchain/check/type.h"
|
||||
@@ -279,4 +281,362 @@ auto IsImplEffectivelyFinal(Context& context, const SemIR::Impl& impl) -> bool {
|
||||
context.constant_values().Get(impl.constraint_id).is_concrete());
|
||||
}
|
||||
|
||||
auto CheckConstraintIsInterface(Context& context, SemIR::InstId impl_decl_id,
|
||||
SemIR::TypeInstId constraint_id)
|
||||
-> SemIR::SpecificInterface {
|
||||
auto facet_type_id = context.types().GetTypeIdForTypeInstId(constraint_id);
|
||||
if (facet_type_id == SemIR::ErrorInst::TypeId) {
|
||||
return SemIR::SpecificInterface::None;
|
||||
}
|
||||
auto facet_type = context.types().TryGetAs<SemIR::FacetType>(facet_type_id);
|
||||
if (!facet_type) {
|
||||
CARBON_DIAGNOSTIC(ImplAsNonFacetType, Error, "impl as non-facet type {0}",
|
||||
InstIdAsType);
|
||||
context.emitter().Emit(impl_decl_id, ImplAsNonFacetType, constraint_id);
|
||||
return SemIR::SpecificInterface::None;
|
||||
}
|
||||
|
||||
auto identified_id = RequireIdentifiedFacetType(context, *facet_type);
|
||||
const auto& identified = context.identified_facet_types().Get(identified_id);
|
||||
if (!identified.is_valid_impl_as_target()) {
|
||||
CARBON_DIAGNOSTIC(ImplOfNotOneInterface, Error,
|
||||
"impl as {0} interfaces, expected 1", int);
|
||||
context.emitter().Emit(impl_decl_id, ImplOfNotOneInterface,
|
||||
identified.num_interfaces_to_impl());
|
||||
return SemIR::SpecificInterface::None;
|
||||
}
|
||||
return identified.impl_as_target_interface();
|
||||
}
|
||||
|
||||
// Returns true if impl redeclaration parameters match.
|
||||
static auto CheckImplRedeclParamsMatch(Context& context, SemIR::Impl& new_impl,
|
||||
SemIR::ImplId prev_impl_id) -> bool {
|
||||
auto& prev_impl = context.impls().Get(prev_impl_id);
|
||||
|
||||
// If the parameters aren't the same, then this is not a redeclaration of this
|
||||
// `impl`. Keep looking for a prior declaration without issuing a diagnostic.
|
||||
if (!CheckRedeclParamsMatch(context, DeclParams(new_impl),
|
||||
DeclParams(prev_impl), SemIR::SpecificId::None,
|
||||
/*diagnose=*/false, /*check_syntax=*/true,
|
||||
/*check_self=*/true)) {
|
||||
// NOLINTNEXTLINE(readability-simplify-boolean-expr)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns whether an impl can be redeclared. For example, defined impls
|
||||
// cannot be redeclared.
|
||||
static auto IsValidImplRedecl(Context& context, SemIR::Impl& new_impl,
|
||||
SemIR::ImplId prev_impl_id) -> bool {
|
||||
auto& prev_impl = context.impls().Get(prev_impl_id);
|
||||
|
||||
// TODO: Following #3763, disallow redeclarations in different scopes.
|
||||
|
||||
// Following #4672, disallowing defining non-extern declarations in another
|
||||
// file.
|
||||
if (auto import_ref =
|
||||
context.insts().TryGetAs<SemIR::AnyImportRef>(prev_impl.self_id)) {
|
||||
// TODO: Handle extern.
|
||||
CARBON_DIAGNOSTIC(RedeclImportedImpl, Error,
|
||||
"redeclaration of imported impl");
|
||||
// TODO: Note imported declaration
|
||||
context.emitter().Emit(new_impl.latest_decl_id(), RedeclImportedImpl);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (prev_impl.has_definition_started()) {
|
||||
// Impls aren't merged in order to avoid generic region lookup into a
|
||||
// mismatching table.
|
||||
CARBON_DIAGNOSTIC(ImplRedefinition, Error,
|
||||
"redefinition of `impl {0} as {1}`", InstIdAsRawType,
|
||||
InstIdAsRawType);
|
||||
CARBON_DIAGNOSTIC(ImplPreviousDefinition, Note,
|
||||
"previous definition was here");
|
||||
context.emitter()
|
||||
.Build(new_impl.latest_decl_id(), ImplRedefinition, new_impl.self_id,
|
||||
new_impl.constraint_id)
|
||||
.Note(prev_impl.definition_id, ImplPreviousDefinition)
|
||||
.Emit();
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Only allow redeclaration in a match_first/impl_priority block.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static auto DiagnoseExtendImplOutsideClass(Context& context,
|
||||
SemIR::LocId loc_id) -> void {
|
||||
CARBON_DIAGNOSTIC(ExtendImplOutsideClass, Error,
|
||||
"`extend impl` can only be used in a class");
|
||||
context.emitter().Emit(loc_id, ExtendImplOutsideClass);
|
||||
}
|
||||
|
||||
// If the specified name scope corresponds to a class, returns the corresponding
|
||||
// class declaration.
|
||||
// TODO: Should this be somewhere more central?
|
||||
static auto TryAsClassScope(Context& context, SemIR::NameScopeId scope_id)
|
||||
-> std::optional<SemIR::ClassDecl> {
|
||||
if (!scope_id.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
auto& scope = context.name_scopes().Get(scope_id);
|
||||
if (!scope.inst_id().has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return context.insts().TryGetAs<SemIR::ClassDecl>(scope.inst_id());
|
||||
}
|
||||
|
||||
auto GetImplDefaultSelfType(Context& context) -> SemIR::TypeId {
|
||||
auto parent_scope_id = context.decl_name_stack().PeekParentScopeId();
|
||||
|
||||
if (auto class_decl = TryAsClassScope(context, parent_scope_id)) {
|
||||
return context.classes().Get(class_decl->class_id).self_type_id;
|
||||
}
|
||||
|
||||
// TODO: This is also valid in a mixin.
|
||||
|
||||
return SemIR::TypeId::None;
|
||||
}
|
||||
|
||||
// Process an `extend impl` declaration by extending the impl scope with the
|
||||
// `impl`'s scope.
|
||||
static auto ExtendImpl(Context& context, Parse::NodeId extend_node,
|
||||
SemIR::LocId loc_id, SemIR::ImplId impl_id,
|
||||
Parse::NodeId self_type_node_id,
|
||||
SemIR::TypeId self_type_id,
|
||||
SemIR::LocId implicit_params_loc_id,
|
||||
SemIR::TypeInstId constraint_type_inst_id,
|
||||
SemIR::TypeId constraint_type_id) -> bool {
|
||||
auto parent_scope_id = context.decl_name_stack().PeekParentScopeId();
|
||||
if (!parent_scope_id.has_value()) {
|
||||
DiagnoseExtendImplOutsideClass(context, loc_id);
|
||||
return false;
|
||||
}
|
||||
// TODO: This is also valid in a mixin.
|
||||
if (!TryAsClassScope(context, parent_scope_id)) {
|
||||
DiagnoseExtendImplOutsideClass(context, loc_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& parent_scope = context.name_scopes().Get(parent_scope_id);
|
||||
|
||||
if (implicit_params_loc_id.has_value()) {
|
||||
CARBON_DIAGNOSTIC(ExtendImplForall, Error,
|
||||
"cannot `extend` a parameterized `impl`");
|
||||
context.emitter().Emit(extend_node, ExtendImplForall);
|
||||
parent_scope.set_has_error();
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& impl = context.impls().Get(impl_id);
|
||||
|
||||
if (context.parse_tree().node_kind(self_type_node_id) ==
|
||||
Parse::NodeKind::TypeImplAs) {
|
||||
CARBON_DIAGNOSTIC(ExtendImplSelfAs, Error,
|
||||
"cannot `extend` an `impl` with an explicit self type");
|
||||
auto diag = context.emitter().Build(extend_node, ExtendImplSelfAs);
|
||||
|
||||
// If the explicit self type is not the default, just bail out.
|
||||
if (self_type_id != GetImplDefaultSelfType(context)) {
|
||||
diag.Emit();
|
||||
parent_scope.set_has_error();
|
||||
return false;
|
||||
}
|
||||
|
||||
// The explicit self type is the same as the default self type, so suggest
|
||||
// removing it and recover as if it were not present.
|
||||
if (auto self_as =
|
||||
context.parse_tree_and_subtrees().ExtractAs<Parse::TypeImplAs>(
|
||||
self_type_node_id)) {
|
||||
CARBON_DIAGNOSTIC(ExtendImplSelfAsDefault, Note,
|
||||
"remove the explicit `Self` type here");
|
||||
diag.Note(self_as->type_expr, ExtendImplSelfAsDefault);
|
||||
}
|
||||
diag.Emit();
|
||||
}
|
||||
|
||||
if (impl.witness_id == SemIR::ErrorInst::InstId) {
|
||||
parent_scope.set_has_error();
|
||||
} else {
|
||||
bool is_complete = RequireCompleteType(
|
||||
context, constraint_type_id, SemIR::LocId(constraint_type_inst_id),
|
||||
[&] {
|
||||
CARBON_DIAGNOSTIC(ExtendImplAsIncomplete, Error,
|
||||
"`extend impl as` incomplete facet type {0}",
|
||||
InstIdAsType);
|
||||
return context.emitter().Build(impl.latest_decl_id(),
|
||||
ExtendImplAsIncomplete,
|
||||
constraint_type_inst_id);
|
||||
});
|
||||
if (!is_complete) {
|
||||
parent_scope.set_has_error();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
parent_scope.AddExtendedScope(constraint_type_inst_id);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Diagnoses when an impl has an unused binding.
|
||||
static auto DiagnoseUnusedGenericBinding(Context& context, SemIR::LocId loc_id,
|
||||
SemIR::LocId implicit_params_loc_id,
|
||||
SemIR::ImplId impl_id) -> void {
|
||||
auto deduced_specific_id = SemIR::SpecificId::None;
|
||||
|
||||
auto& impl = context.impls().Get(impl_id);
|
||||
if (!impl.generic_id.has_value() ||
|
||||
impl.witness_id == SemIR::ErrorInst::InstId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Deduce has side effects in the semir by generating `Converted`
|
||||
// instructions which we will not use here. We should stop generating
|
||||
// those when deducing for impl lookup, but for now we discard them by
|
||||
// pushing an InstBlock on the stack and dropping it right after.
|
||||
context.inst_block_stack().Push();
|
||||
deduced_specific_id = DeduceImplArguments(
|
||||
context, loc_id, impl, context.constant_values().Get(impl.self_id),
|
||||
impl.interface.specific_id);
|
||||
context.inst_block_stack().PopAndDiscard();
|
||||
|
||||
if (deduced_specific_id.has_value()) {
|
||||
// Deduction succeeded, all bindings were used.
|
||||
return;
|
||||
}
|
||||
|
||||
CARBON_DIAGNOSTIC(ImplUnusedBinding, Error,
|
||||
"`impl` with unused generic binding");
|
||||
// TODO: This location may be incorrect, the binding may be inherited
|
||||
// from an outer declaration. It would be nice to get the particular
|
||||
// binding that was undeducible back from DeduceImplArguments here and
|
||||
// use that.
|
||||
auto diag_loc_id =
|
||||
implicit_params_loc_id.has_value() ? implicit_params_loc_id : loc_id;
|
||||
context.emitter().Emit(diag_loc_id, ImplUnusedBinding);
|
||||
// Don't try to match the impl at all, save us work and possible future
|
||||
// diagnostics.
|
||||
FillImplWitnessWithErrors(context, context.impls().Get(impl_id));
|
||||
}
|
||||
|
||||
auto StartImplDecl(Context& context, SemIR::LocId loc_id,
|
||||
SemIR::LocId implicit_params_loc_id, SemIR::Impl impl,
|
||||
bool is_definition,
|
||||
std::optional<ExtendImplDecl> extend_impl)
|
||||
-> std::pair<SemIR::ImplId, SemIR::InstId> {
|
||||
auto impl_id = SemIR::ImplId::None;
|
||||
|
||||
// Add the impl declaration.
|
||||
auto lookup_bucket_ref = context.impls().GetOrAddLookupBucket(impl);
|
||||
// TODO: Detect two impl declarations with the same self type and interface,
|
||||
// and issue an error if they don't match.
|
||||
for (auto prev_impl_id : lookup_bucket_ref) {
|
||||
if (CheckImplRedeclParamsMatch(context, impl, prev_impl_id)) {
|
||||
if (IsValidImplRedecl(context, impl, prev_impl_id)) {
|
||||
impl_id = prev_impl_id;
|
||||
} else {
|
||||
// IsValidImplRedecl() has issued a diagnostic, avoid generating more
|
||||
// diagnostics for this declaration.
|
||||
impl.witness_id = SemIR::ErrorInst::InstId;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new impl if this isn't a valid redeclaration.
|
||||
if (!impl_id.has_value()) {
|
||||
impl.generic_id = BuildGeneric(context, impl.latest_decl_id());
|
||||
if (impl.witness_id != SemIR::ErrorInst::InstId) {
|
||||
if (impl.interface.interface_id.has_value()) {
|
||||
impl.witness_id =
|
||||
ImplWitnessForDeclaration(context, impl, is_definition);
|
||||
} else {
|
||||
impl.witness_id = SemIR::ErrorInst::InstId;
|
||||
// TODO: We might also want to mark that the name scope for the impl has
|
||||
// an error -- at least once we start making name lookups within the
|
||||
// impl also look into the facet (eg, so you can name associated
|
||||
// constants from within the impl).
|
||||
}
|
||||
}
|
||||
FinishGenericDecl(context, SemIR::LocId(impl.latest_decl_id()),
|
||||
impl.generic_id);
|
||||
// From here on, use the `Impl` from the `ImplStore` instead of `impl`
|
||||
// in order to make and see any changes to the `Impl`.
|
||||
impl_id = context.impls().Add(impl);
|
||||
lookup_bucket_ref.push_back(impl_id);
|
||||
|
||||
AssignImplIdInWitness(context, impl_id, impl.witness_id);
|
||||
|
||||
// Looking to see if there are any generic bindings on the `impl`
|
||||
// declaration that are not deducible. If so, and the `impl` does not
|
||||
// actually use all its generic bindings, and will never be matched. This
|
||||
// should be diagnossed to the user.
|
||||
bool has_error_in_implicit_pattern = false;
|
||||
if (impl.implicit_param_patterns_id.has_value()) {
|
||||
for (auto inst_id :
|
||||
context.inst_blocks().Get(impl.implicit_param_patterns_id)) {
|
||||
if (inst_id == SemIR::ErrorInst::InstId) {
|
||||
has_error_in_implicit_pattern = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!has_error_in_implicit_pattern) {
|
||||
DiagnoseUnusedGenericBinding(context, loc_id, implicit_params_loc_id,
|
||||
impl_id);
|
||||
}
|
||||
} else {
|
||||
auto& stored_impl = context.impls().Get(impl_id);
|
||||
FinishGenericRedecl(context, stored_impl.generic_id);
|
||||
}
|
||||
|
||||
// Write the impl ID into the ImplDecl.
|
||||
auto impl_decl =
|
||||
context.insts().GetAs<SemIR::ImplDecl>(impl.first_owning_decl_id);
|
||||
CARBON_CHECK(!impl_decl.impl_id.has_value());
|
||||
impl_decl.impl_id = impl_id;
|
||||
ReplaceInstBeforeConstantUse(context, impl.first_owning_decl_id, impl_decl);
|
||||
|
||||
// For an `extend impl` declaration, mark the impl as extending this `impl`.
|
||||
if (extend_impl) {
|
||||
auto& stored_impl_info = context.impls().Get(impl_decl.impl_id);
|
||||
auto self_type_id =
|
||||
context.types().GetTypeIdForTypeInstId(stored_impl_info.self_id);
|
||||
if (self_type_id != SemIR::ErrorInst::TypeId) {
|
||||
auto constraint_id = impl.constraint_id;
|
||||
if (stored_impl_info.generic_id.has_value()) {
|
||||
constraint_id = AddTypeInst<SemIR::SpecificConstant>(
|
||||
context, SemIR::LocId(constraint_id),
|
||||
{.type_id = SemIR::TypeType::TypeId,
|
||||
.inst_id = constraint_id,
|
||||
.specific_id = context.generics().GetSelfSpecific(
|
||||
stored_impl_info.generic_id)});
|
||||
}
|
||||
if (!ExtendImpl(context, extend_impl->extend_node_id, loc_id,
|
||||
impl_decl.impl_id, extend_impl->self_type_node_id,
|
||||
self_type_id, implicit_params_loc_id, constraint_id,
|
||||
extend_impl->constraint_type_id)) {
|
||||
// Don't allow the invalid impl to be used.
|
||||
FillImplWitnessWithErrors(context, stored_impl_info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Impl definitions are required in the same file as the declaration. We skip
|
||||
// this requirement if we've already issued an invalid redeclaration error, or
|
||||
// there is an error that would prevent the impl from being legal to define.
|
||||
if (!is_definition) {
|
||||
auto& stored_impl = context.impls().Get(impl_id);
|
||||
if (stored_impl.witness_id != SemIR::ErrorInst::InstId) {
|
||||
context.definitions_required_by_decl().push_back(
|
||||
stored_impl.latest_decl_id());
|
||||
}
|
||||
}
|
||||
|
||||
return {impl_id, impl.latest_decl_id()};
|
||||
}
|
||||
|
||||
} // namespace Carbon::Check
|
||||
|
||||
Reference in New Issue
Block a user