From 0a55081c5d0add0d6d461d565e4f6c1b339d33be Mon Sep 17 00:00:00 2001 From: Jon Ross-Perkins Date: Tue, 11 Feb 2025 10:51:46 -0800 Subject: [PATCH] Move TypeCompleter and closely related helper functions to their own file (#4922) context.cpp is getting large, so I'm looking at a few ways to cut out clusters of functions. This felt like a logical cluster of functions to move to their own file. Note I have two commits in this PR: one moving the functionality to a new file, and one specifically changing TypeCompleter to use out-of-line function implementations. This is to assist reviewability. --- toolchain/check/BUILD | 2 + toolchain/check/context.cpp | 547 +------------------ toolchain/check/context.h | 69 +-- toolchain/check/convert.cpp | 7 +- toolchain/check/eval.cpp | 6 +- toolchain/check/function.cpp | 7 +- toolchain/check/generic.cpp | 3 +- toolchain/check/handle_binding_pattern.cpp | 15 +- toolchain/check/handle_class.cpp | 7 +- toolchain/check/handle_function.cpp | 5 +- toolchain/check/impl.cpp | 20 +- toolchain/check/member_access.cpp | 5 +- toolchain/check/type_completion.cpp | 602 +++++++++++++++++++++ toolchain/check/type_completion.h | 78 +++ 14 files changed, 746 insertions(+), 627 deletions(-) create mode 100644 toolchain/check/type_completion.cpp create mode 100644 toolchain/check/type_completion.h diff --git a/toolchain/check/BUILD b/toolchain/check/BUILD index cf10e10b3abec..e1de557278548 100644 --- a/toolchain/check/BUILD +++ b/toolchain/check/BUILD @@ -39,6 +39,7 @@ cc_library( "pattern_match.cpp", "return.cpp", "subst.cpp", + "type_completion.cpp", ], hdrs = [ "call.h", @@ -70,6 +71,7 @@ cc_library( "pending_block.h", "return.h", "subst.h", + "type_completion.h", ], deps = [ ":node_stack", diff --git a/toolchain/check/context.cpp b/toolchain/check/context.cpp index bd1eb879f6c5b..b38695c1b0e13 100644 --- a/toolchain/check/context.cpp +++ b/toolchain/check/context.cpp @@ -11,7 +11,6 @@ #include "common/check.h" #include "common/vlog.h" #include "llvm/ADT/Sequence.h" -#include "toolchain/base/kind_switch.h" #include "toolchain/check/decl_name_stack.h" #include "toolchain/check/eval.h" #include "toolchain/check/generic.h" @@ -20,6 +19,7 @@ #include "toolchain/check/import_ref.h" #include "toolchain/check/inst_block_stack.h" #include "toolchain/check/merge.h" +#include "toolchain/check/type_completion.h" #include "toolchain/diagnostics/diagnostic_emitter.h" #include "toolchain/diagnostics/format_providers.h" #include "toolchain/lex/tokenized_buffer.h" @@ -532,26 +532,28 @@ auto Context::AppendLookupScopesForConstant( return true; } if (auto base_as_class = base.TryAs()) { - RequireDefinedType(GetTypeIdForTypeConstant(base_const_id), loc_id, [&] { - CARBON_DIAGNOSTIC(QualifiedExprInIncompleteClassScope, Error, - "member access into incomplete class {0}", - InstIdAsType); - return emitter().Build(loc_id, QualifiedExprInIncompleteClassScope, - base_id); - }); + RequireDefinedType( + *this, GetTypeIdForTypeConstant(base_const_id), loc_id, [&] { + CARBON_DIAGNOSTIC(QualifiedExprInIncompleteClassScope, Error, + "member access into incomplete class {0}", + InstIdAsType); + return emitter().Build(loc_id, QualifiedExprInIncompleteClassScope, + base_id); + }); auto& class_info = classes().Get(base_as_class->class_id); scopes->push_back(LookupScope{.name_scope_id = class_info.scope_id, .specific_id = base_as_class->specific_id}); return true; } if (auto base_as_facet_type = base.TryAs()) { - RequireDefinedType(GetTypeIdForTypeConstant(base_const_id), loc_id, [&] { - CARBON_DIAGNOSTIC(QualifiedExprInUndefinedInterfaceScope, Error, - "member access into undefined interface {0}", - InstIdAsType); - return emitter().Build(loc_id, QualifiedExprInUndefinedInterfaceScope, - base_id); - }); + RequireDefinedType( + *this, GetTypeIdForTypeConstant(base_const_id), loc_id, [&] { + CARBON_DIAGNOSTIC(QualifiedExprInUndefinedInterfaceScope, Error, + "member access into undefined interface {0}", + InstIdAsType); + return emitter().Build(loc_id, QualifiedExprInUndefinedInterfaceScope, + base_id); + }); const auto& facet_type_info = facet_types().Get(base_as_facet_type->facet_type_id); for (auto interface : facet_type_info.impls_constraints) { @@ -964,517 +966,6 @@ auto Context::Finalize() -> void { global_init_.Finalize(); } -namespace { -// Worklist-based type completion mechanism. -// -// When attempting to complete a type, we may find other types that also need to -// be completed: types nested within that type, and the value representation of -// the type. In order to complete a type without recursing arbitrarily deeply, -// we use a worklist of tasks: -// -// - An `AddNestedIncompleteTypes` step adds a task for all incomplete types -// nested within a type to the work list. -// - A `BuildValueRepr` step computes the value representation for a -// type, once all of its nested types are complete, and marks the type as -// complete. -class TypeCompleter { - public: - 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. - auto Complete(SemIR::TypeId type_id) -> bool { - Push(type_id); - while (!work_list_.empty()) { - if (!ProcessStep()) { - return false; - } - } - return true; - } - - private: - // Adds `type_id` to the work list, if it's not already complete. - auto Push(SemIR::TypeId type_id) -> void { - if (!context_.types().IsComplete(type_id)) { - work_list_.push_back( - {.type_id = type_id, .phase = Phase::AddNestedIncompleteTypes}); - } - } - - // Runs the next step. - auto ProcessStep() -> bool { - auto [type_id, phase] = work_list_.back(); - - // We might have enqueued the same type more than once. Just skip the - // type if it's already complete. - if (context_.types().IsComplete(type_id)) { - work_list_.pop_back(); - return true; - } - - auto inst_id = context_.types().GetInstId(type_id); - auto inst = context_.insts().Get(inst_id); - auto old_work_list_size = work_list_.size(); - - switch (phase) { - case Phase::AddNestedIncompleteTypes: - if (!AddNestedIncompleteTypes(inst)) { - return false; - } - CARBON_CHECK(work_list_.size() >= old_work_list_size, - "AddNestedIncompleteTypes should not remove work items"); - work_list_[old_work_list_size - 1].phase = Phase::BuildValueRepr; - break; - - case Phase::BuildValueRepr: { - auto value_rep = BuildValueRepr(type_id, inst); - context_.types().SetValueRepr(type_id, value_rep); - CARBON_CHECK(old_work_list_size == work_list_.size(), - "BuildValueRepr should not change work items"); - work_list_.pop_back(); - - // Also complete the value representation type, if necessary. This - // should never fail: the value representation shouldn't require any - // additional nested types to be complete. - if (!context_.types().IsComplete(value_rep.type_id)) { - work_list_.push_back( - {.type_id = value_rep.type_id, .phase = Phase::BuildValueRepr}); - } - // For a pointer representation, the pointee also needs to be complete. - if (value_rep.kind == SemIR::ValueRepr::Pointer) { - if (value_rep.type_id == SemIR::ErrorInst::SingletonTypeId) { - break; - } - auto pointee_type_id = - context_.sem_ir().GetPointeeType(value_rep.type_id); - if (!context_.types().IsComplete(pointee_type_id)) { - work_list_.push_back( - {.type_id = pointee_type_id, .phase = Phase::BuildValueRepr}); - } - } - break; - } - } - - return true; - } - - // Adds any types nested within `type_inst` that need to be complete for - // `type_inst` to be complete to our work list. - auto AddNestedIncompleteTypes(SemIR::Inst type_inst) -> bool { - CARBON_KIND_SWITCH(type_inst) { - case CARBON_KIND(SemIR::ArrayType inst): { - Push(inst.element_type_id); - break; - } - case CARBON_KIND(SemIR::StructType inst): { - for (auto field : context_.struct_type_fields().Get(inst.fields_id)) { - Push(field.type_id); - } - break; - } - case CARBON_KIND(SemIR::TupleType inst): { - for (auto element_type_id : - context_.type_blocks().Get(inst.elements_id)) { - Push(element_type_id); - } - break; - } - case CARBON_KIND(SemIR::ClassType inst): { - auto& class_info = context_.classes().Get(inst.class_id); - if (!class_info.is_defined()) { - if (diagnoser_) { - auto builder = diagnoser_(); - context_.NoteIncompleteClass(inst.class_id, builder); - builder.Emit(); - } - return false; - } - if (inst.specific_id.has_value()) { - ResolveSpecificDefinition(context_, loc_, inst.specific_id); - } - if (auto adapted_type_id = - class_info.GetAdaptedType(context_.sem_ir(), inst.specific_id); - adapted_type_id.has_value()) { - Push(adapted_type_id); - } else { - Push(class_info.GetObjectRepr(context_.sem_ir(), inst.specific_id)); - } - break; - } - case CARBON_KIND(SemIR::ConstType inst): { - Push(inst.inner_id); - break; - } - default: - break; - } - - return true; - } - - // Makes an empty value representation, which is used for types that have no - // state, such as empty structs and tuples. - auto MakeEmptyValueRepr() const -> SemIR::ValueRepr { - return {.kind = SemIR::ValueRepr::None, - .type_id = context_.GetTupleType({})}; - } - - // Makes a value representation that uses pass-by-copy, copying the given - // type. - auto MakeCopyValueRepr(SemIR::TypeId rep_id, - SemIR::ValueRepr::AggregateKind aggregate_kind = - SemIR::ValueRepr::NotAggregate) const - -> SemIR::ValueRepr { - return {.kind = SemIR::ValueRepr::Copy, - .aggregate_kind = aggregate_kind, - .type_id = rep_id}; - } - - // Makes a value representation that uses pass-by-address with the given - // pointee type. - auto MakePointerValueRepr(SemIR::TypeId pointee_id, - SemIR::ValueRepr::AggregateKind aggregate_kind = - SemIR::ValueRepr::NotAggregate) const - -> SemIR::ValueRepr { - // TODO: Should we add `const` qualification to `pointee_id`? - return {.kind = SemIR::ValueRepr::Pointer, - .aggregate_kind = aggregate_kind, - .type_id = context_.GetPointerType(pointee_id)}; - } - - // Gets the value representation of a nested type, which should already be - // complete. - auto GetNestedValueRepr(SemIR::TypeId nested_type_id) const { - CARBON_CHECK(context_.types().IsComplete(nested_type_id), - "Nested type should already be complete"); - auto value_rep = context_.types().GetValueRepr(nested_type_id); - CARBON_CHECK(value_rep.kind != SemIR::ValueRepr::Unknown, - "Complete type should have a value representation"); - return value_rep; - } - - template - requires(InstT::Kind.template IsAnyOf< - SemIR::AutoType, SemIR::BoolType, SemIR::BoundMethodType, - SemIR::ErrorInst, SemIR::IntLiteralType, SemIR::LegacyFloatType, - SemIR::NamespaceType, SemIR::SpecificFunctionType, SemIR::TypeType, - SemIR::VtableType, SemIR::WitnessType>()) - auto BuildValueReprForInst(SemIR::TypeId type_id, InstT /*inst*/) const - -> SemIR::ValueRepr { - return MakeCopyValueRepr(type_id); - } - - auto BuildValueReprForInst(SemIR::TypeId type_id, - SemIR::StringType /*inst*/) const - -> SemIR::ValueRepr { - // TODO: Decide on string value semantics. This should probably be a - // custom value representation carrying a pointer and size or - // similar. - return MakePointerValueRepr(type_id); - } - - auto BuildStructOrTupleValueRepr(size_t num_elements, - SemIR::TypeId elementwise_rep, - bool same_as_object_rep) const - -> SemIR::ValueRepr { - SemIR::ValueRepr::AggregateKind aggregate_kind = - same_as_object_rep ? SemIR::ValueRepr::ValueAndObjectAggregate - : SemIR::ValueRepr::ValueAggregate; - - if (num_elements == 1) { - // The value representation for a struct or tuple with a single element - // is a struct or tuple containing the value representation of the - // element. - // TODO: Consider doing the same whenever `elementwise_rep` is - // sufficiently small. - return MakeCopyValueRepr(elementwise_rep, aggregate_kind); - } - // For a struct or tuple with multiple fields, we use a pointer - // to the elementwise value representation. - return MakePointerValueRepr(elementwise_rep, aggregate_kind); - } - - auto BuildValueReprForInst(SemIR::TypeId type_id, - SemIR::StructType struct_type) const - -> SemIR::ValueRepr { - auto fields = context_.struct_type_fields().Get(struct_type.fields_id); - if (fields.empty()) { - return MakeEmptyValueRepr(); - } - - // Find the value representation for each field, and construct a struct - // of value representations. - llvm::SmallVector value_rep_fields; - value_rep_fields.reserve(fields.size()); - bool same_as_object_rep = true; - for (auto field : fields) { - auto field_value_rep = GetNestedValueRepr(field.type_id); - if (!field_value_rep.IsCopyOfObjectRepr(context_.sem_ir(), - field.type_id)) { - same_as_object_rep = false; - field.type_id = field_value_rep.type_id; - } - value_rep_fields.push_back(field); - } - - auto value_rep = - same_as_object_rep - ? type_id - : context_.GetStructType( - context_.struct_type_fields().AddCanonical(value_rep_fields)); - return BuildStructOrTupleValueRepr(fields.size(), value_rep, - same_as_object_rep); - } - - auto BuildValueReprForInst(SemIR::TypeId type_id, - SemIR::TupleType tuple_type) const - -> SemIR::ValueRepr { - // TODO: Share more code with structs. - auto elements = context_.type_blocks().Get(tuple_type.elements_id); - if (elements.empty()) { - return MakeEmptyValueRepr(); - } - - // Find the value representation for each element, and construct a tuple - // of value representations. - llvm::SmallVector value_rep_elements; - value_rep_elements.reserve(elements.size()); - bool same_as_object_rep = true; - for (auto element_type_id : elements) { - auto element_value_rep = GetNestedValueRepr(element_type_id); - if (!element_value_rep.IsCopyOfObjectRepr(context_.sem_ir(), - element_type_id)) { - same_as_object_rep = false; - } - value_rep_elements.push_back(element_value_rep.type_id); - } - - auto value_rep = same_as_object_rep - ? type_id - : context_.GetTupleType(value_rep_elements); - return BuildStructOrTupleValueRepr(elements.size(), value_rep, - same_as_object_rep); - } - - auto BuildValueReprForInst(SemIR::TypeId type_id, - SemIR::ArrayType /*inst*/) const - -> SemIR::ValueRepr { - // For arrays, it's convenient to always use a pointer representation, - // even when the array has zero or one element, in order to support - // indexing. - return MakePointerValueRepr(type_id, SemIR::ValueRepr::ObjectAggregate); - } - - auto BuildValueReprForInst(SemIR::TypeId /*type_id*/, - SemIR::ClassType inst) const -> SemIR::ValueRepr { - auto& class_info = context_.classes().Get(inst.class_id); - // The value representation of an adapter is the value representation of - // its adapted type. - if (auto adapted_type_id = - class_info.GetAdaptedType(context_.sem_ir(), inst.specific_id); - adapted_type_id.has_value()) { - return GetNestedValueRepr(adapted_type_id); - } - // Otherwise, the value representation for a class is a pointer to the - // object representation. - // TODO: Support customized value representations for classes. - // TODO: Pick a better value representation when possible. - return MakePointerValueRepr( - class_info.GetObjectRepr(context_.sem_ir(), inst.specific_id), - SemIR::ValueRepr::ObjectAggregate); - } - - template - requires(InstT::Kind.template IsAnyOf< - SemIR::AssociatedEntityType, SemIR::FacetAccessType, - SemIR::FacetType, SemIR::FunctionType, - SemIR::FunctionTypeWithSelfType, SemIR::GenericClassType, - SemIR::GenericInterfaceType, SemIR::UnboundElementType, - SemIR::WhereExpr>()) - auto BuildValueReprForInst(SemIR::TypeId /*type_id*/, InstT /*inst*/) const - -> SemIR::ValueRepr { - // These types have no runtime operations, so we use an empty value - // representation. - // - // TODO: There is information we could model here: - // - For an interface, we could use a witness. - // - For an associated entity, we could use an index into the witness. - // - For an unbound element, we could use an index or offset. - return MakeEmptyValueRepr(); - } - - template - requires(InstT::Kind.template IsAnyOf()) - auto BuildValueReprForInst(SemIR::TypeId type_id, InstT /*inst*/) const - -> SemIR::ValueRepr { - // For symbolic types, we arbitrarily pick a copy representation. - return MakeCopyValueRepr(type_id); - } - - template - requires(InstT::Kind.template IsAnyOf()) - auto BuildValueReprForInst(SemIR::TypeId type_id, InstT /*inst*/) const - -> SemIR::ValueRepr { - return MakeCopyValueRepr(type_id); - } - - auto BuildValueReprForInst(SemIR::TypeId /*type_id*/, - SemIR::ConstType inst) const -> SemIR::ValueRepr { - // The value representation of `const T` is the same as that of `T`. - // Objects are not modifiable through their value representations. - return GetNestedValueRepr(inst.inner_id); - } - - template - requires(InstT::Kind.is_type() == SemIR::InstIsType::Never) - auto BuildValueReprForInst(SemIR::TypeId /*type_id*/, InstT inst) const - -> SemIR::ValueRepr { - CARBON_FATAL("Type refers to non-type inst {0}", inst); - } - - // Builds and returns the value representation for the given type. All nested - // types, as found by AddNestedIncompleteTypes, are known to be complete. - auto BuildValueRepr(SemIR::TypeId type_id, SemIR::Inst inst) const - -> SemIR::ValueRepr { - // Use overload resolution to select the implementation, producing compile - // errors when BuildValueReprForInst isn't defined for a given instruction. - CARBON_KIND_SWITCH(inst) { -#define CARBON_SEM_IR_INST_KIND(Name) \ - case CARBON_KIND(SemIR::Name typed_inst): { \ - return BuildValueReprForInst(type_id, typed_inst); \ - } -#include "toolchain/sem_ir/inst_kind.def" - } - } - - enum class Phase : int8_t { - // The next step is to add nested types to the list of types to complete. - AddNestedIncompleteTypes, - // The next step is to build the value representation for the type. - BuildValueRepr, - }; - - struct WorkItem { - SemIR::TypeId type_id; - Phase phase; - }; - - Context& context_; - llvm::SmallVector work_list_; - SemIRLoc loc_; - Context::BuildDiagnosticFn diagnoser_; -}; -} // namespace - -auto Context::TryToCompleteType(SemIR::TypeId type_id, SemIRLoc loc, - BuildDiagnosticFn diagnoser) -> bool { - return TypeCompleter(*this, loc, diagnoser).Complete(type_id); -} - -auto Context::CompleteTypeOrCheckFail(SemIR::TypeId type_id) -> void { - bool complete = - TypeCompleter(*this, SemIR::LocId::None, 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, - BuildDiagnosticFn diagnoser) -> bool { - CARBON_CHECK(diagnoser); - - if (!TypeCompleter(*this, loc_id, diagnoser).Complete(type_id)) { - return false; - } - - // For a symbolic type, create an instruction to require the corresponding - // specific type to be complete. - if (type_id.AsConstantId().is_symbolic()) { - // TODO: Deduplicate these. - AddInstInNoBlock(SemIR::LocIdAndInst( - loc_id, - SemIR::RequireCompleteType{ - .type_id = GetSingletonType(SemIR::WitnessType::SingletonInstId), - .complete_type_id = type_id})); - } - - return true; -} - -auto Context::RequireConcreteType(SemIR::TypeId type_id, SemIR::LocId loc_id, - BuildDiagnosticFn diagnoser, - BuildDiagnosticFn abstract_diagnoser) - -> bool { - CARBON_CHECK(abstract_diagnoser); - - if (!RequireCompleteType(type_id, loc_id, diagnoser)) { - return false; - } - - if (auto class_type = types().TryGetAs(type_id)) { - auto& class_info = classes().Get(class_type->class_id); - if (class_info.inheritance_kind != - SemIR::Class::InheritanceKind::Abstract) { - return true; - } - - auto builder = abstract_diagnoser(); - if (!builder) { - return false; - } - NoteAbstractClass(class_type->class_id, builder); - builder.Emit(); - return false; - } - - return true; -} - -auto Context::RequireDefinedType(SemIR::TypeId type_id, SemIR::LocId loc_id, - BuildDiagnosticFn diagnoser) -> bool { - if (!RequireCompleteType(type_id, loc_id, diagnoser)) { - return false; - } - - if (auto facet_type = types().TryGetAs(type_id)) { - const auto& facet_type_info = facet_types().Get(facet_type->facet_type_id); - for (auto interface : facet_type_info.impls_constraints) { - auto interface_id = interface.interface_id; - if (!interfaces().Get(interface_id).is_defined()) { - auto builder = diagnoser(); - NoteUndefinedInterface(interface_id, builder); - builder.Emit(); - return false; - } - - if (interface.specific_id.has_value()) { - ResolveSpecificDefinition(*this, loc_id, interface.specific_id); - } - } - // TODO: Finish facet type resolution. - // - // Note that we will need Self to be passed into facet type resolution. - // The `.Self` of a facet type created by `where` will then be bound to the - // provided self type. - // - // For example, in `T:! X where ...`, we will bind the `.Self` of the - // `where` facet type to `T`, and in `(X where ...) where ...`, we will bind - // the inner `.Self` to the outer `.Self`. - // - // If the facet type contains a rewrite, we may have deferred converting the - // rewritten value to the type of the associated constant. That conversion - // should also be performed as part of resolution, and may depend on the - // Self type. - } - - return true; -} - auto Context::GetTypeIdForTypeConstant(SemIR::ConstantId constant_id) -> SemIR::TypeId { CARBON_CHECK(constant_id.is_constant(), @@ -1517,7 +1008,7 @@ template static auto GetCompleteTypeImpl(Context& context, EachArgT... each_arg) -> SemIR::TypeId { auto type_id = GetTypeImpl(context, each_arg...); - context.CompleteTypeOrCheckFail(type_id); + CompleteTypeOrCheckFail(context, type_id); return type_id; } @@ -1541,7 +1032,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. - CompleteTypeOrCheckFail(type_id); + CompleteTypeOrCheckFail(*this, type_id); return type_id; } diff --git a/toolchain/check/context.h b/toolchain/check/context.h index 01d7a0cbb40c3..9ba23c25d29ae 100644 --- a/toolchain/check/context.h +++ b/toolchain/check/context.h @@ -65,11 +65,11 @@ class Context { public: using DiagnosticEmitter = Carbon::DiagnosticEmitter; using DiagnosticBuilder = DiagnosticEmitter::DiagnosticBuilder; + // A function that forms a diagnostic for some kind of problem. The // DiagnosticBuilder is returned rather than emitted so that the caller can // add contextual notes as appropriate. - using BuildDiagnosticFn = - llvm::function_refContext::DiagnosticBuilder>; + using BuildDiagnosticFn = llvm::function_refDiagnosticBuilder>; // Stores references for work. explicit Context(DiagnosticEmitter* emitter, @@ -374,71 +374,6 @@ class Context { return GetTypeIdForTypeConstant(constant_values().Get(inst_id)); } - // Attempts to complete the type `type_id`. Returns `true` if the type is - // complete, or `false` if it could not be completed. A complete type has - // known object and value representations. Returns `true` if the type is - // symbolic. - // - // Avoid calling this where possible, as it can lead to coherence issues. - // However, it's important that we use it during monomorphization, where we - // don't want to trigger a request for more monomorphization. - // TODO: Remove the other call to this function. - auto TryToCompleteType(SemIR::TypeId type_id, SemIRLoc loc, - BuildDiagnosticFn diagnoser = nullptr) -> 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. - // - // If the type is not complete, `diagnoser` is invoked to diagnose the issue, - // if a `diagnoser` is provided. The builder it returns will be annotated to - // describe the reason why the type is not complete. - // - // `diagnoser` should build an error diagnostic. If `type_id` is dependent, - // the completeness of the type will be enforced during monomorphization, and - // `loc_id` is used as the location for a diagnostic produced at that time. - auto RequireCompleteType(SemIR::TypeId type_id, SemIR::LocId loc_id, - BuildDiagnosticFn diagnoser) -> bool; - - // Like `RequireCompleteType`, but also require the type to not be an abstract - // class type. If it is, `abstract_diagnoser` is used to diagnose the problem, - // and this function returns false. - auto RequireConcreteType(SemIR::TypeId type_id, SemIR::LocId loc_id, - BuildDiagnosticFn diagnoser, - BuildDiagnosticFn abstract_diagnoser) -> bool; - - // Like `RequireCompleteType`, but also require the type to be defined. A - // defined type has known members. If the type is not defined, `diagnoser` is - // used to diagnose the problem, and this function returns false. - // - // This is the same as `RequireCompleteType` except for facet types, which are - // complete before they are fully defined. - auto RequireDefinedType(SemIR::TypeId type_id, SemIR::LocId loc_id, - BuildDiagnosticFn diagnoser) -> bool; - - // Returns the type `type_id` if it is a complete type, or produces an - // incomplete type error and returns an error type. This is a convenience - // wrapper around `RequireCompleteType`. - auto AsCompleteType(SemIR::TypeId type_id, SemIR::LocId loc_id, - BuildDiagnosticFn diagnoser) -> SemIR::TypeId { - return RequireCompleteType(type_id, loc_id, diagnoser) - ? type_id - : SemIR::ErrorInst::SingletonTypeId; - } - - // Returns the type `type_id` if it is a concrete type, or produces an - // incomplete or abstract type error and returns an error type. This is a - // convenience wrapper around `RequireConcreteType`. - auto AsConcreteType(SemIR::TypeId type_id, SemIR::LocId loc_id, - BuildDiagnosticFn diagnoser, - BuildDiagnosticFn abstract_diagnoser) -> SemIR::TypeId { - return RequireConcreteType(type_id, loc_id, diagnoser, abstract_diagnoser) - ? type_id - : SemIR::ErrorInst::SingletonTypeId; - } - // Returns whether `type_id` represents a facet type. auto IsFacetType(SemIR::TypeId type_id) -> bool { return type_id == SemIR::TypeType::SingletonTypeId || diff --git a/toolchain/check/convert.cpp b/toolchain/check/convert.cpp index 5e6bdc02e7422..b7f7c9d620787 100644 --- a/toolchain/check/convert.cpp +++ b/toolchain/check/convert.cpp @@ -16,6 +16,7 @@ #include "toolchain/check/impl_lookup.h" #include "toolchain/check/operator.h" #include "toolchain/check/pattern_match.h" +#include "toolchain/check/type_completion.h" #include "toolchain/diagnostics/format_providers.h" #include "toolchain/sem_ir/copy_on_write_block.h" #include "toolchain/sem_ir/file.h" @@ -617,7 +618,7 @@ static auto ComputeInheritancePath(Context& context, SemIRLoc loc, // 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, loc)) { + if (!TryToCompleteType(context, 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. @@ -1078,8 +1079,8 @@ auto Convert(Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id, } // We can only perform initialization for complete, non-abstract types. - if (!context.RequireConcreteType( - target.type_id, loc_id, + if (!RequireConcreteType( + context, target.type_id, loc_id, [&] { CARBON_CHECK(!target.is_initializer(), "Initialization of incomplete types is expected to be " diff --git a/toolchain/check/eval.cpp b/toolchain/check/eval.cpp index a7bb994c56ea1..bea162b24a25e 100644 --- a/toolchain/check/eval.cpp +++ b/toolchain/check/eval.cpp @@ -8,6 +8,7 @@ #include "toolchain/check/diagnostic_helpers.h" #include "toolchain/check/generic.h" #include "toolchain/check/import_ref.h" +#include "toolchain/check/type_completion.h" #include "toolchain/diagnostics/diagnostic_emitter.h" #include "toolchain/diagnostics/format_providers.h" #include "toolchain/sem_ir/builtin_function_kind.h" @@ -2078,8 +2079,9 @@ static auto TryEvalInstInContext(EvalContext& eval_context, // If the type is a template constant, require it to be complete now. if (phase == Phase::Template) { - if (!eval_context.context().TryToCompleteType( - complete_type_id, eval_context.GetDiagnosticLoc(inst_id), [&] { + if (!TryToCompleteType( + eval_context.context(), complete_type_id, + eval_context.GetDiagnosticLoc(inst_id), [&] { CARBON_DIAGNOSTIC(IncompleteTypeInMonomorphization, Error, "{0} evaluates to incomplete type {1}", SemIR::TypeId, SemIR::TypeId); diff --git a/toolchain/check/function.cpp b/toolchain/check/function.cpp index 246ca91884923..901e4e8fd9475 100644 --- a/toolchain/check/function.cpp +++ b/toolchain/check/function.cpp @@ -5,6 +5,7 @@ #include "toolchain/check/function.h" #include "toolchain/check/merge.h" +#include "toolchain/check/type_completion.h" #include "toolchain/sem_ir/ids.h" namespace Carbon::Check { @@ -96,9 +97,9 @@ auto CheckFunctionReturnType(Context& context, SemIR::LocId loc_id, // TODO: Consider suppressing the diagnostic if we've already diagnosed a // definition or call to this function. - if (context.RequireConcreteType(return_info.type_id, loc_id, - diagnose_incomplete_return_type, - diagnose_abstract_return_type)) { + if (RequireConcreteType(context, return_info.type_id, loc_id, + diagnose_incomplete_return_type, + diagnose_abstract_return_type)) { return_info = SemIR::ReturnTypeInfo::ForFunction(context.sem_ir(), function, specific_id); } diff --git a/toolchain/check/generic.cpp b/toolchain/check/generic.cpp index 98312aa74f932..153e0fe87f96e 100644 --- a/toolchain/check/generic.cpp +++ b/toolchain/check/generic.cpp @@ -9,6 +9,7 @@ #include "toolchain/check/eval.h" #include "toolchain/check/generic_region_stack.h" #include "toolchain/check/subst.h" +#include "toolchain/check/type_completion.h" #include "toolchain/sem_ir/generic.h" #include "toolchain/sem_ir/ids.h" #include "toolchain/sem_ir/inst.h" @@ -255,7 +256,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.CompleteTypeOrCheckFail(type_id); + CompleteTypeOrCheckFail(context, type_id); } inst.SetType(type_id); context.sem_ir().insts().Set(inst_id, inst); diff --git a/toolchain/check/handle_binding_pattern.cpp b/toolchain/check/handle_binding_pattern.cpp index ca76b2889c4e7..25004ac3b8bc1 100644 --- a/toolchain/check/handle_binding_pattern.cpp +++ b/toolchain/check/handle_binding_pattern.cpp @@ -7,6 +7,7 @@ #include "toolchain/check/handle.h" #include "toolchain/check/interface.h" #include "toolchain/check/return.h" +#include "toolchain/check/type_completion.h" #include "toolchain/diagnostics/format_providers.h" #include "toolchain/sem_ir/ids.h" #include "toolchain/sem_ir/inst.h" @@ -112,8 +113,8 @@ static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id, if (auto parent_class_decl = context.GetCurrentScopeAs(); parent_class_decl.has_value() && node_kind == Parse::NodeKind::VarBindingPattern) { - cast_type_id = context.AsConcreteType( - cast_type_id, type_node, + cast_type_id = AsConcreteType( + context, cast_type_id, type_node, [&] { CARBON_DIAGNOSTIC(IncompleteTypeInFieldDecl, Error, "field has incomplete type {0}", SemIR::TypeId); @@ -152,7 +153,7 @@ static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id, if (auto parent_interface_decl = context.GetCurrentScopeAs(); parent_interface_decl.has_value() && is_generic) { - cast_type_id = context.AsCompleteType(cast_type_id, type_node, [&] { + cast_type_id = AsCompleteType(context, cast_type_id, type_node, [&] { CARBON_DIAGNOSTIC(IncompleteTypeInAssociatedDecl, Error, "associated constant has incomplete type {0}", SemIR::TypeId); @@ -252,8 +253,8 @@ static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id, cast_type_inst_id); }; if (node_kind == Parse::NodeKind::VarBindingPattern) { - cast_type_id = context.AsConcreteType( - cast_type_id, type_node, incomplete_diagnoser, [&] { + cast_type_id = AsConcreteType( + context, cast_type_id, type_node, incomplete_diagnoser, [&] { CARBON_DIAGNOSTIC( AbstractTypeInVarPattern, Error, "binding pattern has abstract type {0} in `var` " @@ -263,8 +264,8 @@ static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id, type_node, AbstractTypeInVarPattern, cast_type_id); }); } else { - cast_type_id = context.AsCompleteType(cast_type_id, type_node, - incomplete_diagnoser); + cast_type_id = AsCompleteType(context, cast_type_id, type_node, + incomplete_diagnoser); } auto binding_pattern_id = make_binding_pattern(); if (node_kind == Parse::NodeKind::VarBindingPattern) { diff --git a/toolchain/check/handle_class.cpp b/toolchain/check/handle_class.cpp index 7d8e0a4e945b1..24e1e1b75c6ce 100644 --- a/toolchain/check/handle_class.cpp +++ b/toolchain/check/handle_class.cpp @@ -14,6 +14,7 @@ #include "toolchain/check/merge.h" #include "toolchain/check/modifiers.h" #include "toolchain/check/name_component.h" +#include "toolchain/check/type_completion.h" #include "toolchain/parse/node_ids.h" #include "toolchain/sem_ir/function.h" #include "toolchain/sem_ir/ids.h" @@ -390,8 +391,8 @@ auto HandleParseNode(Context& context, Parse::AdaptDeclId node_id) -> bool { auto [adapted_inst_id, adapted_type_id] = ExprAsType(context, node_id, adapted_type_expr_id); - adapted_type_id = context.AsConcreteType( - adapted_type_id, node_id, + adapted_type_id = AsConcreteType( + context, adapted_type_id, node_id, [&] { CARBON_DIAGNOSTIC(IncompleteTypeInAdaptDecl, Error, "adapted type {0} is an incomplete type", @@ -463,7 +464,7 @@ static auto CheckBaseType(Context& context, Parse::NodeId node_id, SemIR::InstId base_expr_id) -> BaseInfo { auto [base_type_inst_id, base_type_id] = ExprAsType(context, node_id, base_expr_id); - base_type_id = context.AsCompleteType(base_type_id, node_id, [&] { + base_type_id = AsCompleteType(context, base_type_id, node_id, [&] { CARBON_DIAGNOSTIC(IncompleteTypeInBaseDecl, Error, "base {0} is an incomplete type", InstIdAsType); return context.emitter().Build(node_id, IncompleteTypeInBaseDecl, diff --git a/toolchain/check/handle_function.cpp b/toolchain/check/handle_function.cpp index 146297e888060..35dfdb587c605 100644 --- a/toolchain/check/handle_function.cpp +++ b/toolchain/check/handle_function.cpp @@ -16,6 +16,7 @@ #include "toolchain/check/merge.h" #include "toolchain/check/modifiers.h" #include "toolchain/check/name_component.h" +#include "toolchain/check/type_completion.h" #include "toolchain/sem_ir/builtin_function_kind.h" #include "toolchain/sem_ir/entry_point.h" #include "toolchain/sem_ir/function.h" @@ -375,8 +376,8 @@ static auto CheckFunctionDefinitionSignature(Context& context, } // The parameter types need to be complete. - context.RequireCompleteType( - context.insts().GetAs(param_ref_id).type_id, + RequireCompleteType( + context, context.insts().GetAs(param_ref_id).type_id, context.insts().GetLocId(param_ref_id), [&] { CARBON_DIAGNOSTIC( IncompleteTypeInFunctionParam, Error, diff --git a/toolchain/check/impl.cpp b/toolchain/check/impl.cpp index 23e88d9dad617..3a5b37a58ad9a 100644 --- a/toolchain/check/impl.cpp +++ b/toolchain/check/impl.cpp @@ -12,6 +12,7 @@ #include "toolchain/check/generic.h" #include "toolchain/check/import_ref.h" #include "toolchain/check/interface.h" +#include "toolchain/check/type_completion.h" #include "toolchain/diagnostics/diagnostic_emitter.h" #include "toolchain/sem_ir/generic.h" #include "toolchain/sem_ir/ids.h" @@ -107,15 +108,16 @@ auto ImplWitnessForDeclaration(Context& context, const SemIR::Impl& impl) context.interfaces().Get(interface_type->interface_id); // TODO: This should be done as part of facet type resolution. - if (!context.RequireDefinedType( - facet_type_id, context.insts().GetLocId(impl.latest_decl_id()), [&] { - CARBON_DIAGNOSTIC(ImplOfUndefinedInterface, Error, - "implementation of undefined interface {0}", - SemIR::NameId); - return context.emitter().Build(impl.latest_decl_id(), - ImplOfUndefinedInterface, - interface.name_id); - })) { + if (!RequireDefinedType(context, facet_type_id, + context.insts().GetLocId(impl.latest_decl_id()), [&] { + CARBON_DIAGNOSTIC( + ImplOfUndefinedInterface, Error, + "implementation of undefined interface {0}", + SemIR::NameId); + return context.emitter().Build( + impl.latest_decl_id(), ImplOfUndefinedInterface, + interface.name_id); + })) { return SemIR::ErrorInst::SingletonInstId; } diff --git a/toolchain/check/member_access.cpp b/toolchain/check/member_access.cpp index 9fd5995c2adcd..89418a0f6e990 100644 --- a/toolchain/check/member_access.cpp +++ b/toolchain/check/member_access.cpp @@ -13,6 +13,7 @@ #include "toolchain/check/impl_lookup.h" #include "toolchain/check/import_ref.h" #include "toolchain/check/interface.h" +#include "toolchain/check/type_completion.h" #include "toolchain/diagnostics/diagnostic_emitter.h" #include "toolchain/sem_ir/function.h" #include "toolchain/sem_ir/generic.h" @@ -445,8 +446,8 @@ auto PerformMemberAccess(Context& context, SemIR::LocId loc_id, // If the base isn't a scope, it must have a complete type. auto base_type_id = context.insts().Get(base_id).type_id(); - if (!context.RequireCompleteType( - base_type_id, context.insts().GetLocId(base_id), [&] { + if (!RequireCompleteType( + context, base_type_id, context.insts().GetLocId(base_id), [&] { CARBON_DIAGNOSTIC( IncompleteTypeInMemberAccess, Error, "member access into object of incomplete type {0}", diff --git a/toolchain/check/type_completion.cpp b/toolchain/check/type_completion.cpp new file mode 100644 index 0000000000000..22b7167a89bc2 --- /dev/null +++ b/toolchain/check/type_completion.cpp @@ -0,0 +1,602 @@ +// 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 + +#include "toolchain/check/type_completion.h" + +#include "llvm/ADT/SmallVector.h" +#include "toolchain/base/kind_switch.h" +#include "toolchain/check/generic.h" + +namespace Carbon::Check { + +namespace { +// Worklist-based type completion mechanism. +// +// When attempting to complete a type, we may find other types that also need to +// be completed: types nested within that type, and the value representation of +// the type. In order to complete a type without recursing arbitrarily deeply, +// we use a worklist of tasks: +// +// - An `AddNestedIncompleteTypes` step adds a task for all incomplete types +// nested within a type to the work list. +// - A `BuildValueRepr` step computes the value representation for a +// type, once all of its nested types are complete, and marks the type as +// complete. +class TypeCompleter { + public: + 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. + auto Complete(SemIR::TypeId type_id) -> bool; + + private: + enum class Phase : int8_t { + // The next step is to add nested types to the list of types to complete. + AddNestedIncompleteTypes, + // The next step is to build the value representation for the type. + BuildValueRepr, + }; + + struct WorkItem { + SemIR::TypeId type_id; + Phase phase; + }; + + // Adds `type_id` to the work list, if it's not already complete. + auto Push(SemIR::TypeId type_id) -> void; + + // Runs the next step. + auto ProcessStep() -> bool; + + // Adds any types nested within `type_inst` that need to be complete for + // `type_inst` to be complete to our work list. + auto AddNestedIncompleteTypes(SemIR::Inst type_inst) -> bool; + + // Makes an empty value representation, which is used for types that have no + // state, such as empty structs and tuples. + auto MakeEmptyValueRepr() const -> SemIR::ValueRepr; + + // Makes a value representation that uses pass-by-copy, copying the given + // type. + auto MakeCopyValueRepr(SemIR::TypeId rep_id, + SemIR::ValueRepr::AggregateKind aggregate_kind = + SemIR::ValueRepr::NotAggregate) const + -> SemIR::ValueRepr; + + // Makes a value representation that uses pass-by-address with the given + // pointee type. + auto MakePointerValueRepr(SemIR::TypeId pointee_id, + SemIR::ValueRepr::AggregateKind aggregate_kind = + SemIR::ValueRepr::NotAggregate) const + -> SemIR::ValueRepr; + + // Gets the value representation of a nested type, which should already be + // complete. + auto GetNestedValueRepr(SemIR::TypeId nested_type_id) const + -> SemIR::ValueRepr; + + template + requires(InstT::Kind.template IsAnyOf< + SemIR::AutoType, SemIR::BoolType, SemIR::BoundMethodType, + SemIR::ErrorInst, SemIR::IntLiteralType, SemIR::LegacyFloatType, + SemIR::NamespaceType, SemIR::SpecificFunctionType, SemIR::TypeType, + SemIR::VtableType, SemIR::WitnessType>()) + auto BuildValueReprForInst(SemIR::TypeId type_id, InstT /*inst*/) const + -> SemIR::ValueRepr { + return MakeCopyValueRepr(type_id); + } + + auto BuildValueReprForInst(SemIR::TypeId type_id, + SemIR::StringType /*inst*/) const + -> SemIR::ValueRepr; + + auto BuildStructOrTupleValueRepr(size_t num_elements, + SemIR::TypeId elementwise_rep, + bool same_as_object_rep) const + -> SemIR::ValueRepr; + + auto BuildValueReprForInst(SemIR::TypeId type_id, + SemIR::StructType struct_type) const + -> SemIR::ValueRepr; + + auto BuildValueReprForInst(SemIR::TypeId type_id, + SemIR::TupleType tuple_type) const + -> SemIR::ValueRepr; + + auto BuildValueReprForInst(SemIR::TypeId type_id, + SemIR::ArrayType /*inst*/) const + -> SemIR::ValueRepr; + + auto BuildValueReprForInst(SemIR::TypeId /*type_id*/, + SemIR::ClassType inst) const -> SemIR::ValueRepr; + + template + requires(InstT::Kind.template IsAnyOf< + SemIR::AssociatedEntityType, SemIR::FacetAccessType, + SemIR::FacetType, SemIR::FunctionType, + SemIR::FunctionTypeWithSelfType, SemIR::GenericClassType, + SemIR::GenericInterfaceType, SemIR::UnboundElementType, + SemIR::WhereExpr>()) + auto BuildValueReprForInst(SemIR::TypeId /*type_id*/, InstT /*inst*/) const + -> SemIR::ValueRepr { + // These types have no runtime operations, so we use an empty value + // representation. + // + // TODO: There is information we could model here: + // - For an interface, we could use a witness. + // - For an associated entity, we could use an index into the witness. + // - For an unbound element, we could use an index or offset. + return MakeEmptyValueRepr(); + } + + template + requires(InstT::Kind.template IsAnyOf()) + auto BuildValueReprForInst(SemIR::TypeId type_id, InstT /*inst*/) const + -> SemIR::ValueRepr { + // For symbolic types, we arbitrarily pick a copy representation. + return MakeCopyValueRepr(type_id); + } + + template + requires(InstT::Kind.template IsAnyOf()) + auto BuildValueReprForInst(SemIR::TypeId type_id, InstT /*inst*/) const + -> SemIR::ValueRepr { + return MakeCopyValueRepr(type_id); + } + + auto BuildValueReprForInst(SemIR::TypeId /*type_id*/, + SemIR::ConstType inst) const -> SemIR::ValueRepr; + + template + requires(InstT::Kind.is_type() == SemIR::InstIsType::Never) + auto BuildValueReprForInst(SemIR::TypeId /*type_id*/, InstT inst) const + -> SemIR::ValueRepr { + CARBON_FATAL("Type refers to non-type inst {0}", inst); + } + + // Builds and returns the value representation for the given type. All nested + // types, as found by AddNestedIncompleteTypes, are known to be complete. + auto BuildValueRepr(SemIR::TypeId type_id, SemIR::Inst inst) const + -> SemIR::ValueRepr; + + Context& context_; + llvm::SmallVector work_list_; + SemIRLoc loc_; + Context::BuildDiagnosticFn diagnoser_; +}; +} // namespace + +auto TypeCompleter::Complete(SemIR::TypeId type_id) -> bool { + Push(type_id); + while (!work_list_.empty()) { + if (!ProcessStep()) { + return false; + } + } + return true; +} + +auto TypeCompleter::Push(SemIR::TypeId type_id) -> void { + if (!context_.types().IsComplete(type_id)) { + work_list_.push_back( + {.type_id = type_id, .phase = Phase::AddNestedIncompleteTypes}); + } +} + +auto TypeCompleter::ProcessStep() -> bool { + auto [type_id, phase] = work_list_.back(); + + // We might have enqueued the same type more than once. Just skip the + // type if it's already complete. + if (context_.types().IsComplete(type_id)) { + work_list_.pop_back(); + return true; + } + + auto inst_id = context_.types().GetInstId(type_id); + auto inst = context_.insts().Get(inst_id); + auto old_work_list_size = work_list_.size(); + + switch (phase) { + case Phase::AddNestedIncompleteTypes: + if (!AddNestedIncompleteTypes(inst)) { + return false; + } + CARBON_CHECK(work_list_.size() >= old_work_list_size, + "AddNestedIncompleteTypes should not remove work items"); + work_list_[old_work_list_size - 1].phase = Phase::BuildValueRepr; + break; + + case Phase::BuildValueRepr: { + auto value_rep = BuildValueRepr(type_id, inst); + context_.types().SetValueRepr(type_id, value_rep); + CARBON_CHECK(old_work_list_size == work_list_.size(), + "BuildValueRepr should not change work items"); + work_list_.pop_back(); + + // Also complete the value representation type, if necessary. This + // should never fail: the value representation shouldn't require any + // additional nested types to be complete. + if (!context_.types().IsComplete(value_rep.type_id)) { + work_list_.push_back( + {.type_id = value_rep.type_id, .phase = Phase::BuildValueRepr}); + } + // For a pointer representation, the pointee also needs to be complete. + if (value_rep.kind == SemIR::ValueRepr::Pointer) { + if (value_rep.type_id == SemIR::ErrorInst::SingletonTypeId) { + break; + } + auto pointee_type_id = + context_.sem_ir().GetPointeeType(value_rep.type_id); + if (!context_.types().IsComplete(pointee_type_id)) { + work_list_.push_back( + {.type_id = pointee_type_id, .phase = Phase::BuildValueRepr}); + } + } + break; + } + } + + return true; +} + +auto TypeCompleter::AddNestedIncompleteTypes(SemIR::Inst type_inst) -> bool { + CARBON_KIND_SWITCH(type_inst) { + case CARBON_KIND(SemIR::ArrayType inst): { + Push(inst.element_type_id); + break; + } + case CARBON_KIND(SemIR::StructType inst): { + for (auto field : context_.struct_type_fields().Get(inst.fields_id)) { + Push(field.type_id); + } + break; + } + case CARBON_KIND(SemIR::TupleType inst): { + for (auto element_type_id : + context_.type_blocks().Get(inst.elements_id)) { + Push(element_type_id); + } + break; + } + case CARBON_KIND(SemIR::ClassType inst): { + auto& class_info = context_.classes().Get(inst.class_id); + if (!class_info.is_defined()) { + if (diagnoser_) { + auto builder = diagnoser_(); + context_.NoteIncompleteClass(inst.class_id, builder); + builder.Emit(); + } + return false; + } + if (inst.specific_id.has_value()) { + ResolveSpecificDefinition(context_, loc_, inst.specific_id); + } + if (auto adapted_type_id = + class_info.GetAdaptedType(context_.sem_ir(), inst.specific_id); + adapted_type_id.has_value()) { + Push(adapted_type_id); + } else { + Push(class_info.GetObjectRepr(context_.sem_ir(), inst.specific_id)); + } + break; + } + case CARBON_KIND(SemIR::ConstType inst): { + Push(inst.inner_id); + break; + } + default: + break; + } + + return true; +} + +auto TypeCompleter::MakeEmptyValueRepr() const -> SemIR::ValueRepr { + return {.kind = SemIR::ValueRepr::None, .type_id = context_.GetTupleType({})}; +} + +auto TypeCompleter::MakeCopyValueRepr( + SemIR::TypeId rep_id, SemIR::ValueRepr::AggregateKind aggregate_kind) const + -> SemIR::ValueRepr { + return {.kind = SemIR::ValueRepr::Copy, + .aggregate_kind = aggregate_kind, + .type_id = rep_id}; +} + +auto TypeCompleter::MakePointerValueRepr( + SemIR::TypeId pointee_id, + SemIR::ValueRepr::AggregateKind aggregate_kind) const -> SemIR::ValueRepr { + // TODO: Should we add `const` qualification to `pointee_id`? + return {.kind = SemIR::ValueRepr::Pointer, + .aggregate_kind = aggregate_kind, + .type_id = context_.GetPointerType(pointee_id)}; +} + +auto TypeCompleter::GetNestedValueRepr(SemIR::TypeId nested_type_id) const + -> SemIR::ValueRepr { + CARBON_CHECK(context_.types().IsComplete(nested_type_id), + "Nested type should already be complete"); + auto value_rep = context_.types().GetValueRepr(nested_type_id); + CARBON_CHECK(value_rep.kind != SemIR::ValueRepr::Unknown, + "Complete type should have a value representation"); + return value_rep; +} + +auto TypeCompleter::BuildValueReprForInst(SemIR::TypeId type_id, + SemIR::StringType /*inst*/) const + -> SemIR::ValueRepr { + // TODO: Decide on string value semantics. This should probably be a + // custom value representation carrying a pointer and size or + // similar. + return MakePointerValueRepr(type_id); +} + +auto TypeCompleter::BuildStructOrTupleValueRepr(size_t num_elements, + SemIR::TypeId elementwise_rep, + bool same_as_object_rep) const + -> SemIR::ValueRepr { + SemIR::ValueRepr::AggregateKind aggregate_kind = + same_as_object_rep ? SemIR::ValueRepr::ValueAndObjectAggregate + : SemIR::ValueRepr::ValueAggregate; + + if (num_elements == 1) { + // The value representation for a struct or tuple with a single element + // is a struct or tuple containing the value representation of the + // element. + // TODO: Consider doing the same whenever `elementwise_rep` is + // sufficiently small. + return MakeCopyValueRepr(elementwise_rep, aggregate_kind); + } + // For a struct or tuple with multiple fields, we use a pointer + // to the elementwise value representation. + return MakePointerValueRepr(elementwise_rep, aggregate_kind); +} + +auto TypeCompleter::BuildValueReprForInst(SemIR::TypeId type_id, + SemIR::StructType struct_type) const + -> SemIR::ValueRepr { + auto fields = context_.struct_type_fields().Get(struct_type.fields_id); + if (fields.empty()) { + return MakeEmptyValueRepr(); + } + + // Find the value representation for each field, and construct a struct + // of value representations. + llvm::SmallVector value_rep_fields; + value_rep_fields.reserve(fields.size()); + bool same_as_object_rep = true; + for (auto field : fields) { + auto field_value_rep = GetNestedValueRepr(field.type_id); + if (!field_value_rep.IsCopyOfObjectRepr(context_.sem_ir(), field.type_id)) { + same_as_object_rep = false; + field.type_id = field_value_rep.type_id; + } + value_rep_fields.push_back(field); + } + + auto value_rep = + same_as_object_rep + ? type_id + : context_.GetStructType( + context_.struct_type_fields().AddCanonical(value_rep_fields)); + return BuildStructOrTupleValueRepr(fields.size(), value_rep, + same_as_object_rep); +} + +auto TypeCompleter::BuildValueReprForInst(SemIR::TypeId type_id, + SemIR::TupleType tuple_type) const + -> SemIR::ValueRepr { + // TODO: Share more code with structs. + auto elements = context_.type_blocks().Get(tuple_type.elements_id); + if (elements.empty()) { + return MakeEmptyValueRepr(); + } + + // Find the value representation for each element, and construct a tuple + // of value representations. + llvm::SmallVector value_rep_elements; + value_rep_elements.reserve(elements.size()); + bool same_as_object_rep = true; + for (auto element_type_id : elements) { + auto element_value_rep = GetNestedValueRepr(element_type_id); + if (!element_value_rep.IsCopyOfObjectRepr(context_.sem_ir(), + element_type_id)) { + same_as_object_rep = false; + } + value_rep_elements.push_back(element_value_rep.type_id); + } + + auto value_rep = + same_as_object_rep ? type_id : context_.GetTupleType(value_rep_elements); + return BuildStructOrTupleValueRepr(elements.size(), value_rep, + same_as_object_rep); +} + +auto TypeCompleter::BuildValueReprForInst(SemIR::TypeId type_id, + SemIR::ArrayType /*inst*/) const + -> SemIR::ValueRepr { + // For arrays, it's convenient to always use a pointer representation, + // even when the array has zero or one element, in order to support + // indexing. + return MakePointerValueRepr(type_id, SemIR::ValueRepr::ObjectAggregate); +} + +auto TypeCompleter::BuildValueReprForInst(SemIR::TypeId /*type_id*/, + SemIR::ClassType inst) const + -> SemIR::ValueRepr { + auto& class_info = context_.classes().Get(inst.class_id); + // The value representation of an adapter is the value representation of + // its adapted type. + if (auto adapted_type_id = + class_info.GetAdaptedType(context_.sem_ir(), inst.specific_id); + adapted_type_id.has_value()) { + return GetNestedValueRepr(adapted_type_id); + } + // Otherwise, the value representation for a class is a pointer to the + // object representation. + // TODO: Support customized value representations for classes. + // TODO: Pick a better value representation when possible. + return MakePointerValueRepr( + class_info.GetObjectRepr(context_.sem_ir(), inst.specific_id), + SemIR::ValueRepr::ObjectAggregate); +} + +auto TypeCompleter::BuildValueReprForInst(SemIR::TypeId /*type_id*/, + SemIR::ConstType inst) const + -> SemIR::ValueRepr { + // The value representation of `const T` is the same as that of `T`. + // Objects are not modifiable through their value representations. + return GetNestedValueRepr(inst.inner_id); +} + +// Builds and returns the value representation for the given type. All nested +// types, as found by AddNestedIncompleteTypes, are known to be complete. +auto TypeCompleter::BuildValueRepr(SemIR::TypeId type_id, + SemIR::Inst inst) const -> SemIR::ValueRepr { + // Use overload resolution to select the implementation, producing compile + // errors when BuildValueReprForInst isn't defined for a given instruction. + CARBON_KIND_SWITCH(inst) { +#define CARBON_SEM_IR_INST_KIND(Name) \ + case CARBON_KIND(SemIR::Name typed_inst): { \ + return BuildValueReprForInst(type_id, typed_inst); \ + } +#include "toolchain/sem_ir/inst_kind.def" + } +} + +auto TryToCompleteType(Context& context, SemIR::TypeId type_id, SemIRLoc loc, + Context::BuildDiagnosticFn diagnoser) -> bool { + return TypeCompleter(context, loc, diagnoser).Complete(type_id); +} + +auto CompleteTypeOrCheckFail(Context& context, SemIR::TypeId type_id) -> void { + bool complete = + TypeCompleter(context, SemIR::LocId::None, nullptr).Complete(type_id); + CARBON_CHECK(complete, "Expected {0} to be a complete type", + context.types().GetAsInst(type_id)); +} + +auto RequireCompleteType(Context& context, SemIR::TypeId type_id, + SemIR::LocId loc_id, + Context::BuildDiagnosticFn diagnoser) -> bool { + CARBON_CHECK(diagnoser); + + if (!TypeCompleter(context, loc_id, diagnoser).Complete(type_id)) { + return false; + } + + // For a symbolic type, create an instruction to require the corresponding + // specific type to be complete. + if (type_id.AsConstantId().is_symbolic()) { + // TODO: Deduplicate these. + context.AddInstInNoBlock(SemIR::LocIdAndInst( + loc_id, + SemIR::RequireCompleteType{.type_id = context.GetSingletonType( + SemIR::WitnessType::SingletonInstId), + .complete_type_id = type_id})); + } + + return true; +} + +auto RequireConcreteType(Context& context, SemIR::TypeId type_id, + SemIR::LocId loc_id, + Context::BuildDiagnosticFn diagnoser, + Context::BuildDiagnosticFn abstract_diagnoser) + -> bool { + CARBON_CHECK(abstract_diagnoser); + + if (!RequireCompleteType(context, type_id, loc_id, diagnoser)) { + return false; + } + + if (auto class_type = context.types().TryGetAs(type_id)) { + auto& class_info = context.classes().Get(class_type->class_id); + if (class_info.inheritance_kind != + SemIR::Class::InheritanceKind::Abstract) { + return true; + } + + auto builder = abstract_diagnoser(); + if (!builder) { + return false; + } + context.NoteAbstractClass(class_type->class_id, builder); + builder.Emit(); + return false; + } + + return true; +} + +auto RequireDefinedType(Context& context, SemIR::TypeId type_id, + SemIR::LocId loc_id, + Context::BuildDiagnosticFn diagnoser) -> bool { + if (!RequireCompleteType(context, type_id, loc_id, diagnoser)) { + return false; + } + + if (auto facet_type = context.types().TryGetAs(type_id)) { + const auto& facet_type_info = + context.facet_types().Get(facet_type->facet_type_id); + for (auto interface : facet_type_info.impls_constraints) { + auto interface_id = interface.interface_id; + if (!context.interfaces().Get(interface_id).is_defined()) { + auto builder = diagnoser(); + context.NoteUndefinedInterface(interface_id, builder); + builder.Emit(); + return false; + } + + if (interface.specific_id.has_value()) { + ResolveSpecificDefinition(context, loc_id, interface.specific_id); + } + } + // TODO: Finish facet type resolution. + // + // Note that we will need Self to be passed into facet type resolution. + // The `.Self` of a facet type created by `where` will then be bound to the + // provided self type. + // + // For example, in `T:! X where ...`, we will bind the `.Self` of the + // `where` facet type to `T`, and in `(X where ...) where ...`, we will bind + // the inner `.Self` to the outer `.Self`. + // + // If the facet type contains a rewrite, we may have deferred converting the + // rewritten value to the type of the associated constant. That conversion + // should also be performed as part of resolution, and may depend on the + // Self type. + } + + return true; +} + +auto AsCompleteType(Context& context, SemIR::TypeId type_id, + SemIR::LocId loc_id, Context::BuildDiagnosticFn diagnoser) + -> SemIR::TypeId { + return RequireCompleteType(context, type_id, loc_id, diagnoser) + ? type_id + : SemIR::ErrorInst::SingletonTypeId; +} + +// Returns the type `type_id` if it is a concrete type, or produces an +// incomplete or abstract type error and returns an error type. This is a +// convenience wrapper around `RequireConcreteType`. +auto AsConcreteType(Context& context, SemIR::TypeId type_id, + SemIR::LocId loc_id, Context::BuildDiagnosticFn diagnoser, + Context::BuildDiagnosticFn abstract_diagnoser) + -> SemIR::TypeId { + return RequireConcreteType(context, type_id, loc_id, diagnoser, + abstract_diagnoser) + ? type_id + : SemIR::ErrorInst::SingletonTypeId; +} + +} // namespace Carbon::Check diff --git a/toolchain/check/type_completion.h b/toolchain/check/type_completion.h new file mode 100644 index 0000000000000..191c63d0abaea --- /dev/null +++ b/toolchain/check/type_completion.h @@ -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 + +#ifndef CARBON_TOOLCHAIN_CHECK_TYPE_COMPLETION_H_ +#define CARBON_TOOLCHAIN_CHECK_TYPE_COMPLETION_H_ + +#include "toolchain/check/context.h" +#include "toolchain/check/diagnostic_helpers.h" +#include "toolchain/sem_ir/ids.h" + +namespace Carbon::Check { + +// Attempts to complete the type `type_id`. Returns `true` if the type is +// complete, or `false` if it could not be completed. A complete type has +// known object and value representations. Returns `true` if the type is +// symbolic. +// +// Avoid calling this where possible, as it can lead to coherence issues. +// However, it's important that we use it during monomorphization, where we +// don't want to trigger a request for more monomorphization. +// TODO: Remove the other call to this function. +auto TryToCompleteType(Context& context, SemIR::TypeId type_id, SemIRLoc loc, + Context::BuildDiagnosticFn diagnoser = nullptr) -> bool; + +// Completes the type `type_id`. CHECK-fails if it can't be completed. +auto CompleteTypeOrCheckFail(Context& context, SemIR::TypeId type_id) -> void; + +// Like `TryToCompleteType`, but for cases where it is an error for the type +// to be incomplete. +// +// If the type is not complete, `diagnoser` is invoked to diagnose the issue, +// if a `diagnoser` is provided. The builder it returns will be annotated to +// describe the reason why the type is not complete. +// +// `diagnoser` should build an error diagnostic. If `type_id` is dependent, +// the completeness of the type will be enforced during monomorphization, and +// `loc_id` is used as the location for a diagnostic produced at that time. +auto RequireCompleteType(Context& context, SemIR::TypeId type_id, + SemIR::LocId loc_id, + Context::BuildDiagnosticFn diagnoser) -> bool; + +// Like `RequireCompleteType`, but also require the type to not be an abstract +// class type. If it is, `abstract_diagnoser` is used to diagnose the problem, +// and this function returns false. +auto RequireConcreteType(Context& context, SemIR::TypeId type_id, + SemIR::LocId loc_id, + Context::BuildDiagnosticFn diagnoser, + Context::BuildDiagnosticFn abstract_diagnoser) -> bool; + +// Like `RequireCompleteType`, but also require the type to be defined. A +// defined type has known members. If the type is not defined, `diagnoser` is +// used to diagnose the problem, and this function returns false. +// +// This is the same as `RequireCompleteType` except for facet types, which are +// complete before they are fully defined. +auto RequireDefinedType(Context& context, SemIR::TypeId type_id, + SemIR::LocId loc_id, + Context::BuildDiagnosticFn diagnoser) -> bool; + +// Returns the type `type_id` if it is a complete type, or produces an +// incomplete type error and returns an error type. This is a convenience +// wrapper around `RequireCompleteType`. +auto AsCompleteType(Context& context, SemIR::TypeId type_id, + SemIR::LocId loc_id, Context::BuildDiagnosticFn diagnoser) + -> SemIR::TypeId; + +// Returns the type `type_id` if it is a concrete type, or produces an +// incomplete or abstract type error and returns an error type. This is a +// convenience wrapper around `RequireConcreteType`. +auto AsConcreteType(Context& context, SemIR::TypeId type_id, + SemIR::LocId loc_id, Context::BuildDiagnosticFn diagnoser, + Context::BuildDiagnosticFn abstract_diagnoser) + -> SemIR::TypeId; + +} // namespace Carbon::Check + +#endif // CARBON_TOOLCHAIN_CHECK_TYPE_COMPLETION_H_