diff --git a/toolchain/check/context.cpp b/toolchain/check/context.cpp index f7f9c9791bcc..d4f7dd01a53b 100644 --- a/toolchain/check/context.cpp +++ b/toolchain/check/context.cpp @@ -509,7 +509,7 @@ auto Context::AppendLookupScopesForConstant( return true; } if (auto base_as_class = base.TryAs()) { - TryToDefineType(GetTypeIdForTypeConstant(base_const_id), loc_id, [&] { + RequireDefinedType(GetTypeIdForTypeConstant(base_const_id), loc_id, [&] { CARBON_DIAGNOSTIC(QualifiedExprInIncompleteClassScope, Error, "member access into incomplete class {0}", InstIdAsType); @@ -522,7 +522,7 @@ auto Context::AppendLookupScopesForConstant( return true; } if (auto base_as_facet_type = base.TryAs()) { - TryToDefineType(GetTypeIdForTypeConstant(base_const_id), loc_id, [&] { + RequireDefinedType(GetTypeIdForTypeConstant(base_const_id), loc_id, [&] { CARBON_DIAGNOSTIC(QualifiedExprInUndefinedInterfaceScope, Error, "member access into undefined interface {0}", InstIdAsType); @@ -1279,16 +1279,21 @@ class TypeCompleter { }; } // namespace -auto Context::TryToCompleteType(SemIR::TypeId type_id, SemIR::LocId loc_id, - BuildDiagnosticFn diagnoser, - BuildDiagnosticFn abstract_diagnoser) -> bool { +auto Context::TryToCompleteType(SemIR::TypeId type_id) -> bool { + return TypeCompleter(*this, nullptr).Complete(type_id); +} + +auto Context::RequireCompleteType(SemIR::TypeId type_id, SemIR::LocId loc_id, + BuildDiagnosticFn diagnoser) -> bool { + CARBON_CHECK(diagnoser); + if (!TypeCompleter(*this, diagnoser).Complete(type_id)) { return false; } // For a symbolic type, create an instruction to require the corresponding // specific type to be complete. - if (diagnoser && type_id.AsConstantId().is_symbolic()) { + if (type_id.AsConstantId().is_symbolic()) { // TODO: Deduplicate these. AddInstInNoBlock(SemIR::LocIdAndInst( loc_id, @@ -1297,8 +1302,17 @@ auto Context::TryToCompleteType(SemIR::TypeId type_id, SemIR::LocId loc_id, .complete_type_id = type_id})); } - if (!abstract_diagnoser) { - return true; + 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)) { @@ -1320,9 +1334,9 @@ auto Context::TryToCompleteType(SemIR::TypeId type_id, SemIR::LocId loc_id, return true; } -auto Context::TryToDefineType(SemIR::TypeId type_id, SemIR::LocId loc_id, - BuildDiagnosticFn diagnoser) -> bool { - if (!TryToCompleteType(type_id, loc_id, diagnoser)) { +auto Context::RequireDefinedType(SemIR::TypeId type_id, SemIR::LocId loc_id, + BuildDiagnosticFn diagnoser) -> bool { + if (!RequireCompleteType(type_id, loc_id, diagnoser)) { return false; } diff --git a/toolchain/check/context.h b/toolchain/check/context.h index 42c76b1f15af..cd1a02e2a027 100644 --- a/toolchain/check/context.h +++ b/toolchain/check/context.h @@ -339,44 +339,58 @@ class Context { // 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. + // known object and value representations. Returns `true` if the type is + // symbolic. + // + // Avoid calling this where possible, as it can lead to coherence issues. + auto TryToCompleteType(SemIR::TypeId type_id) -> bool; + + // 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. // - // If `diagnoser` is provided, it is assumed to be an error for the type to be - // incomplete, and `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. + // `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. // - // Returns `true` if the type is symbolic. - auto TryToCompleteType(SemIR::TypeId type_id, SemIR::LocId loc_id, - BuildDiagnosticFn diagnoser, - BuildDiagnosticFn abstract_diagnoser = nullptr) - -> bool; - auto TryToCompleteType(SemIR::TypeId type_id) -> bool { - return TryToCompleteType(type_id, SemIR::LocId::Invalid, nullptr); + // 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; } - // Attempts to complete and define the type `type_id`. Returns `true` if the - // type is defined, or `false` if no definition is available. A defined type - // has known members. - // - // This is the same as `TryToCompleteType` except for interfaces, which are - // complete before they are fully defined. - auto TryToDefineType(SemIR::TypeId type_id, SemIR::LocId loc_id, - BuildDiagnosticFn diagnoser = nullptr) -> bool; - - // Returns the type `type_id` as a complete type, or produces an incomplete - // type error and returns an error type. This is a convenience wrapper around - // TryToCompleteType. `diagnoser` must not be null. - auto AsCompleteType(SemIR::TypeId type_id, SemIR::LocId loc_id, + // 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 = nullptr) - -> SemIR::TypeId { - return TryToCompleteType(type_id, loc_id, diagnoser, abstract_diagnoser) + BuildDiagnosticFn abstract_diagnoser) -> SemIR::TypeId { + return RequireConcreteType(type_id, loc_id, diagnoser, abstract_diagnoser) ? type_id : SemIR::ErrorInst::SingletonTypeId; } diff --git a/toolchain/check/convert.cpp b/toolchain/check/convert.cpp index 2658ca16bb5b..7f4141a6a074 100644 --- a/toolchain/check/convert.cpp +++ b/toolchain/check/convert.cpp @@ -966,8 +966,8 @@ auto Convert(Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id, return SemIR::ErrorInst::SingletonInstId; } - // We can only perform initialization for complete types. - if (!context.TryToCompleteType( + // We can only perform initialization for complete, non-abstract types. + if (!context.RequireConcreteType( target.type_id, loc_id, [&] { CARBON_CHECK(!target.is_initializer(), diff --git a/toolchain/check/function.cpp b/toolchain/check/function.cpp index 50ef2d6cc013..221d70143f60 100644 --- a/toolchain/check/function.cpp +++ b/toolchain/check/function.cpp @@ -90,9 +90,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.TryToCompleteType(return_info.type_id, loc_id, - diagnose_incomplete_return_type, - diagnose_abstract_return_type)) { + if (context.RequireConcreteType(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/handle_binding_pattern.cpp b/toolchain/check/handle_binding_pattern.cpp index 93ac910a3153..dba8505115c3 100644 --- a/toolchain/check/handle_binding_pattern.cpp +++ b/toolchain/check/handle_binding_pattern.cpp @@ -103,7 +103,7 @@ static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id, // A `var` declaration at class scope introduces a field. auto parent_class_decl = context.GetCurrentScopeAs(); - cast_type_id = context.AsCompleteType( + cast_type_id = context.AsConcreteType( cast_type_id, type_node, [&] { CARBON_DIAGNOSTIC(IncompleteTypeInVarDecl, Error, diff --git a/toolchain/check/handle_class.cpp b/toolchain/check/handle_class.cpp index b60ba5b577f1..c61337489696 100644 --- a/toolchain/check/handle_class.cpp +++ b/toolchain/check/handle_class.cpp @@ -384,7 +384,7 @@ 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.AsCompleteType( + adapted_type_id = context.AsConcreteType( adapted_type_id, node_id, [&] { CARBON_DIAGNOSTIC(IncompleteTypeInAdaptDecl, Error, diff --git a/toolchain/check/handle_function.cpp b/toolchain/check/handle_function.cpp index 9740be7dee3a..18677edf5271 100644 --- a/toolchain/check/handle_function.cpp +++ b/toolchain/check/handle_function.cpp @@ -349,7 +349,7 @@ static auto CheckFunctionDefinitionSignature(Context& context, } // The parameter types need to be complete. - context.TryToCompleteType( + context.RequireCompleteType( context.insts().GetAs(param_ref_id).type_id, context.insts().GetLocId(param_ref_id), [&] { CARBON_DIAGNOSTIC( diff --git a/toolchain/check/handle_impl.cpp b/toolchain/check/handle_impl.cpp index 861d15f76b07..6e9fd8678911 100644 --- a/toolchain/check/handle_impl.cpp +++ b/toolchain/check/handle_impl.cpp @@ -179,7 +179,7 @@ static auto ExtendImpl(Context& context, Parse::NodeId extend_node, parent_scope.set_has_error(); return; } - if (!context.TryToDefineType(constraint_id, node_id, [&] { + if (!context.RequireDefinedType(constraint_id, node_id, [&] { CARBON_DIAGNOSTIC( ExtendUndefinedInterface, Error, "`extend impl` requires a definition for facet type {0}", diff --git a/toolchain/check/impl.cpp b/toolchain/check/impl.cpp index 2af983185389..75a8a504e3ea 100644 --- a/toolchain/check/impl.cpp +++ b/toolchain/check/impl.cpp @@ -143,7 +143,7 @@ static auto BuildInterfaceWitness( // TODO: This is going to try and define all the interfaces for this facet // type, and so once we support impl of a facet type with more than one // interface, it might give the wrong name in the diagnostic. - if (!context.TryToDefineType( + if (!context.RequireDefinedType( facet_type_id, context.insts().GetLocId(impl.definition_id), [&] { CARBON_DIAGNOSTIC(ImplOfUndefinedInterface, Error, "implementation of undefined interface {0}", diff --git a/toolchain/check/member_access.cpp b/toolchain/check/member_access.cpp index 2128b9b80200..3686b515b3ab 100644 --- a/toolchain/check/member_access.cpp +++ b/toolchain/check/member_access.cpp @@ -425,7 +425,7 @@ 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.TryToCompleteType( + if (!context.RequireCompleteType( base_type_id, context.insts().GetLocId(base_id), [&] { CARBON_DIAGNOSTIC( IncompleteTypeInMemberAccess, Error,