// 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/custom_witness.h" #include "toolchain/base/kind_switch.h" #include "toolchain/check/facet_type.h" #include "toolchain/check/function.h" #include "toolchain/check/generic.h" #include "toolchain/check/impl.h" #include "toolchain/check/impl_lookup.h" #include "toolchain/check/import_ref.h" #include "toolchain/check/inst.h" #include "toolchain/check/name_lookup.h" #include "toolchain/check/type.h" #include "toolchain/check/type_completion.h" #include "toolchain/sem_ir/associated_constant.h" #include "toolchain/sem_ir/builtin_function_kind.h" #include "toolchain/sem_ir/constant.h" #include "toolchain/sem_ir/ids.h" #include "toolchain/sem_ir/type_info.h" #include "toolchain/sem_ir/typed_insts.h" namespace Carbon::Check { // Given a value whose type `IsFacetTypeOrError`, returns the corresponding // type. static auto GetFacetAsType(Context& context, SemIR::ConstantId facet_or_type_const_id) -> SemIR::TypeId { auto facet_or_type_id = context.constant_values().GetInstId(facet_or_type_const_id); auto type_type_id = context.insts().Get(facet_or_type_id).type_id(); CARBON_CHECK(context.types().IsFacetTypeOrError(type_type_id)); if (context.types().Is(type_type_id)) { // It's a facet; access its type. facet_or_type_id = context.types().GetTypeInstId( GetFacetAccessType(context, facet_or_type_id)); } return context.types().GetTypeIdForTypeInstId(facet_or_type_id); } // Returns a manufactured `Copy.Op` function with the `self` parameter typed // to `self_type_id`. static auto MakeCopyOpFunction(Context& context, SemIR::LocId loc_id, SemIR::TypeId self_type_id, SemIR::NameScopeId parent_scope_id) -> SemIR::InstId { auto name_id = context.core_identifiers().AddNameId(CoreIdentifier::Op); auto [decl_id, function_id] = MakeGeneratedFunctionDecl(context, loc_id, {.parent_scope_id = parent_scope_id, .name_id = name_id, .self_type_id = self_type_id, .self_kind = ParamPatternKind::Value, .return_type_id = self_type_id}); auto& function = context.functions().Get(function_id); function.SetCoreWitness(SemIR::BuiltinFunctionKind::PrimitiveCopy); return decl_id; } // Returns the body for `Destroy.Op`. This will return `None` if using the // builtin `NoOp` is appropriate. // Returns a FacetType that contains only the query interface. static auto GetFacetTypeForQuerySpecificInterface( Context& context, SemIR::LocId loc_id, SemIR::SpecificInterfaceId query_specific_interface_id) -> SemIR::ConstantId { const auto query_specific_interface = context.specific_interfaces().Get(query_specific_interface_id); // The Self facet will have type FacetType, for the query interface. auto const_id = EvalOrAddInst( context, loc_id, FacetTypeFromInterface(context, query_specific_interface.interface_id, query_specific_interface.specific_id)); return const_id; } // Starts a block for lookup-related instructions, and returns the `FacetType` // for lookups in `HasWitnessForRepeatedField`. static auto PrepareForHasWitness( Context& context, SemIR::LocId loc_id, SemIR::SpecificInterfaceId query_specific_interface_id) -> SemIR::ConstantId { context.inst_block_stack().Push(); StartGenericDecl(context); return GetFacetTypeForQuerySpecificInterface(context, loc_id, query_specific_interface_id); } // Cleans up state `PrepareForHasWitness`. static auto CleanupAfterHasWitness(Context& context) -> void { DiscardGenericDecl(context); context.inst_block_stack().PopAndDiscard(); } // Returns true if `type_inst_id` has a witness for the query interface, which // comes from `PrepareForHasWitness`. static auto HasWitnessForRepeatedField( Context& context, SemIR::LocId loc_id, SemIR::InstId type_inst_id, SemIR::ConstantId query_facet_type_const_id) -> bool { auto type_const_id = context.constant_values().Get(type_inst_id); auto block_or_err = LookupImplWitness(context, loc_id, type_const_id, query_facet_type_const_id); return block_or_err.has_value(); } // The format for `Destroy.Op`. enum class DestroyFormat { NoDestroy, Trivial, NonTrivial, }; // Similar to `HasWitnessForRepeatedField`, but for cases where there's only one // field, this can handle the call to `PrepareForHasWitness`. static auto HasWitnessForOneField( Context& context, SemIR::LocId loc_id, SemIR::InstId field_inst_id, SemIR::SpecificInterfaceId query_specific_interface_id) -> DestroyFormat { auto query_facet_type_const_id = PrepareForHasWitness(context, loc_id, query_specific_interface_id); auto has_witness = HasWitnessForRepeatedField(context, loc_id, field_inst_id, query_facet_type_const_id); CleanupAfterHasWitness(context); return has_witness ? DestroyFormat::NonTrivial : DestroyFormat::NoDestroy; } // Returns true if `class_type` should impl `Destroy`. static auto CanDestroyClass( Context& context, SemIR::LocId loc_id, SemIR::ClassType class_type, const SemIR::CompleteTypeInfo& complete_info, SemIR::SpecificInterfaceId query_specific_interface_id, bool is_partial) -> DestroyFormat { // Abstract classes can't be destroyed. if (!is_partial && complete_info.IsAbstract()) { return DestroyFormat::NoDestroy; } auto class_info = context.classes().Get(class_type.class_id); // `LookupCppImpl` handles C++ types. if (context.name_scopes().Get(class_info.scope_id).is_cpp_scope()) { return DestroyFormat::NoDestroy; } auto object_repr_id = class_info.GetObjectRepr(context.sem_ir(), class_type.specific_id); return HasWitnessForOneField(context, loc_id, context.types().GetTypeInstId(object_repr_id), query_specific_interface_id); } // Returns true if the `Self` should impl `Destroy`. This will recurse into impl // lookup of `Destroy` for members, similar to `where .Self.members each impls // Destroy`. static auto CanDestroyType( Context& context, SemIR::LocId loc_id, SemIR::ConstantId query_self_const_id, SemIR::SpecificInterfaceId query_specific_interface_id) -> DestroyFormat { auto query_specific_interface = context.specific_interfaces().Get(query_specific_interface_id); auto destroy_interface_id = query_specific_interface.interface_id; auto inst_id = context.constant_values().GetInstId( GetCanonicalFacetOrTypeValue(context, query_self_const_id)); auto inst = context.insts().Get(inst_id); // For facet values, look if the FacetType provides the same. if (auto facet_type = context.types().TryGetAs(inst.type_id())) { const auto& info = context.facet_types().Get(facet_type->facet_type_id); for (auto interface : info.extend_constraints) { if (interface.interface_id == destroy_interface_id) { return DestroyFormat::Trivial; } } return DestroyFormat::NoDestroy; } // Incomplete types can not be destroyed. auto type_id = context.types().GetTypeIdForTypeInstId(inst_id); if (!TryToCompleteType(context, type_id, loc_id)) { return DestroyFormat::NoDestroy; } CARBON_KIND_SWITCH(inst) { case SemIR::ImplWitnessAccess::Kind: case SemIR::SymbolicBinding::Kind: { // A symbolic facet of type `type`. Such symbolic values can't be // destroyed. return DestroyFormat::NoDestroy; } case CARBON_KIND(SemIR::ArrayType array_type): { // A zero element array is always trivially destructible. if (auto int_bound = context.sem_ir().GetZExtIntValue(array_type.bound_id); !int_bound || *int_bound == 0) { return DestroyFormat::Trivial; } // Verify the element can be destroyed. return HasWitnessForOneField(context, loc_id, array_type.element_type_inst_id, query_specific_interface_id); } case SemIR::Call::Kind: // TODO: These seem like they shouldn't be getting directly queried for // destroy. The use is in a test that was TODO before this TODO. return DestroyFormat::NoDestroy; case CARBON_KIND(SemIR::ClassType class_type): { return CanDestroyClass(context, loc_id, class_type, context.types().GetCompleteTypeInfo(type_id), query_specific_interface_id, /*is_partial=*/false); } case CARBON_KIND(SemIR::ConstType const_type): { return HasWitnessForOneField(context, loc_id, const_type.inner_id, query_specific_interface_id); } case CARBON_KIND(SemIR::MaybeUnformedType maybe_unformed_type): { return HasWitnessForOneField(context, loc_id, maybe_unformed_type.inner_id, query_specific_interface_id); } case CARBON_KIND(SemIR::PartialType partial_type): { // In contrast with something like `const`, need to treat the inner // class differently based on the `partial` modifier. auto class_type = context.insts().GetAs(partial_type.inner_id); return CanDestroyClass(context, loc_id, class_type, context.types().GetCompleteTypeInfo(type_id), query_specific_interface_id, /*is_partial=*/true); } case CARBON_KIND(SemIR::StructType struct_type): { auto fields = context.struct_type_fields().Get(struct_type.fields_id); if (fields.empty()) { return DestroyFormat::Trivial; } auto query_facet_type_const_id = PrepareForHasWitness(context, loc_id, query_specific_interface_id); bool has_witness = true; for (const auto& field : fields) { if (!HasWitnessForRepeatedField(context, loc_id, field.type_inst_id, query_facet_type_const_id)) { has_witness = false; break; } } CleanupAfterHasWitness(context); return has_witness ? DestroyFormat::NonTrivial : DestroyFormat::NoDestroy; } case CARBON_KIND(SemIR::TupleType tuple_type): { auto block = context.inst_blocks().Get(tuple_type.type_elements_id); if (block.empty()) { return DestroyFormat::Trivial; } auto query_facet_type_const_id = PrepareForHasWitness(context, loc_id, query_specific_interface_id); bool has_witness = true; for (const auto& element_id : block) { if (!HasWitnessForRepeatedField(context, loc_id, element_id, query_facet_type_const_id)) { has_witness = false; break; } } CleanupAfterHasWitness(context); return has_witness ? DestroyFormat::NonTrivial : DestroyFormat::NoDestroy; } case SemIR::BoolType::Kind: case SemIR::FacetType::Kind: case SemIR::FloatType::Kind: case SemIR::IntLiteralType::Kind: case SemIR::IntType::Kind: case SemIR::PointerType::Kind: case SemIR::TypeType::Kind: // Trivially destructible. return DestroyFormat::Trivial; default: CARBON_FATAL("Unexpected type for CanDestroyType: {0}", inst.kind()); } } // Returns the body for `Destroy.Op`. // // TODO: This is a placeholder still not actually destroying things, intended to // maintain mostly-consistent behavior with current logic while working. That // also means using `self`. static auto MakeDestroyOpBody(Context& context, SemIR::LocId loc_id, SemIR::TypeId self_type_id, SemIR::InstId self_param_id) -> SemIR::InstBlockId { context.inst_block_stack().Push(); auto inst = context.types().GetAsInst(self_type_id); CARBON_KIND_SWITCH(inst) { case SemIR::ArrayType::Kind: case SemIR::ClassType::Kind: case SemIR::ConstType::Kind: case SemIR::MaybeUnformedType::Kind: case SemIR::PartialType::Kind: case SemIR::StructType::Kind: case SemIR::TupleType::Kind: (void)self_param_id; // TODO: Implement destruction of the type. break; default: CARBON_FATAL("Unexpected type for MakeDestroyOpBody: {0}", inst); } AddInst(context, loc_id, SemIR::Return{}); return context.inst_block_stack().Pop(); } // Returns a manufactured `Destroy.Op` function with the `self` parameter typed // to `self_type_id`. static auto MakeDestroyOpFunction(Context& context, SemIR::LocId loc_id, SemIR::TypeId self_type_id, SemIR::NameScopeId parent_scope_id, DestroyFormat format) -> SemIR::InstId { auto name_id = context.core_identifiers().AddNameId(CoreIdentifier::Op); auto [decl_id, function_id] = MakeGeneratedFunctionDecl(context, loc_id, {.parent_scope_id = parent_scope_id, .name_id = name_id, .self_type_id = self_type_id, .self_kind = ParamPatternKind::Ref}); auto& function = context.functions().Get(function_id); if (format == DestroyFormat::Trivial) { function.SetCoreWitness(SemIR::BuiltinFunctionKind::NoOp); } else { CARBON_CHECK(format == DestroyFormat::NonTrivial); function.SetCoreWitness(SemIR::BuiltinFunctionKind::None); auto body_id = MakeDestroyOpBody(context, loc_id, self_type_id, function.self_param_id); function.body_block_ids.push_back(body_id); } return decl_id; } static auto MakeCustomWitnessConstantInst( Context& context, SemIR::LocId loc_id, SemIR::SpecificInterfaceId query_specific_interface_id, SemIR::InstBlockId associated_entities_block_id) -> SemIR::InstId { // The witness is a CustomWitness of the query interface with a table that // contains each associated entity. auto const_id = EvalOrAddInst( context, loc_id, {.type_id = GetSingletonType(context, SemIR::WitnessType::TypeInstId), .elements_id = associated_entities_block_id, .query_specific_interface_id = query_specific_interface_id}); return context.constant_values().GetInstId(const_id); } struct TypesForSelfFacet { // A FacetType that contains only the query interface. SemIR::TypeId facet_type_for_query_specific_interface; // The query self as a type, which involves a conversion if it was a facet. SemIR::TypeId query_self_as_type_id; }; static auto GetTypesForSelfFacet( Context& context, SemIR::LocId loc_id, SemIR::ConstantId query_self_const_id, SemIR::SpecificInterfaceId query_specific_interface_id) -> TypesForSelfFacet { // The Self facet will have type FacetType, for the query interface. auto facet_type_for_query_specific_interface = context.types().GetTypeIdForTypeConstantId( GetFacetTypeForQuerySpecificInterface(context, loc_id, query_specific_interface_id)); // The Self facet needs to point to a type value. If it's not one already, // convert to type. auto query_self_as_type_id = GetFacetAsType(context, query_self_const_id); return {facet_type_for_query_specific_interface, query_self_as_type_id}; } // Build a new facet from the query self, using a CustomWitness for the query // interface with an entry for each associated entity so far. static auto MakeSelfFacetWithCustomWitness( Context& context, SemIR::LocId loc_id, TypesForSelfFacet query_types, SemIR::SpecificInterfaceId query_specific_interface_id, SemIR::InstBlockId associated_entities_block_id) -> SemIR::ConstantId { // We are building a facet value for a single interface, so the witness block // is a single witness for that interface. auto witnesses_block_id = context.inst_blocks().Add({MakeCustomWitnessConstantInst( context, loc_id, query_specific_interface_id, associated_entities_block_id)}); return EvalOrAddInst( context, loc_id, {.type_id = query_types.facet_type_for_query_specific_interface, .type_inst_id = context.types().GetTypeInstId(query_types.query_self_as_type_id), .witnesses_block_id = witnesses_block_id}); } auto BuildCustomWitness(Context& context, SemIR::LocId loc_id, SemIR::ConstantId query_self_const_id, SemIR::SpecificInterfaceId query_specific_interface_id, llvm::ArrayRef values) -> SemIR::InstId { const auto query_specific_interface = context.specific_interfaces().Get(query_specific_interface_id); const auto& interface = context.interfaces().Get(query_specific_interface.interface_id); auto assoc_entities = context.inst_blocks().GetOrEmpty(interface.associated_entities_id); if (assoc_entities.size() != values.size()) { context.TODO(loc_id, ("Unsupported definition of interface " + context.names().GetFormatted(interface.name_id)) .str()); return SemIR::ErrorInst::InstId; } auto query_types_for_self_facet = GetTypesForSelfFacet( context, loc_id, query_self_const_id, query_specific_interface_id); // The values that will go in the witness table. llvm::SmallVector entries; auto interface_with_self_specific_id = SemIR::SpecificId::None; enum class AssociatedEntityState { None, AssociatedConstant, AssociatedFunction }; auto associated_entity_state = AssociatedEntityState::None; // Build a witness with the current contents of the witness table. Each // specific interface has at most two witness tables: an optional table // for associated constants and a table for associated functions. // // We need to separate associated constants and associated functions since // associated entities can't depend on other entities in their own witness // table. To ensure that we don't end up with n^2 witness tables, and so // that we don't need to loop over the associated entities more than once, // we require that interfaces with a custom witness specify all of their // associated constants before their associated functions. Interfaces with // custom witness tables are hardcoded in the compiler, so this // restriction doesn't impact whether or not a type is definable. auto update_interface_with_self_specific_id = [&](SemIR::InstId assoc_entity_id) { auto decl_id = context.constant_values().GetConstantInstId(assoc_entity_id); CARBON_CHECK(decl_id.has_value(), "Non-constant associated entity"); auto decl = context.insts().Get(decl_id); auto new_associated_entity_state = decl.kind() == SemIR::AssociatedConstantDecl::Kind ? AssociatedEntityState::AssociatedConstant : AssociatedEntityState::AssociatedFunction; CARBON_CHECK(new_associated_entity_state >= associated_entity_state, "Implementation restriction: associated constants must be " "defined before associated functions"); if (associated_entity_state < new_associated_entity_state) { auto self_facet = MakeSelfFacetWithCustomWitness( context, loc_id, query_types_for_self_facet, query_specific_interface_id, context.inst_blocks().Add(entries)); interface_with_self_specific_id = MakeSpecificWithInnerSelf( context, loc_id, interface.generic_id, interface.generic_with_self_id, query_specific_interface.specific_id, self_facet); associated_entity_state = new_associated_entity_state; } }; // Fill in the witness table. for (const auto& [assoc_entity_id, value_id] : llvm::zip_equal(assoc_entities, values)) { LoadImportRef(context, assoc_entity_id); update_interface_with_self_specific_id(assoc_entity_id); CARBON_CHECK( !context.specifics().Get(interface_with_self_specific_id).HasError()); auto decl_id = context.constant_values().GetInstId(SemIR::GetConstantValueInSpecific( context.sem_ir(), interface_with_self_specific_id, assoc_entity_id)); CARBON_CHECK(decl_id.has_value(), "Non-constant associated entity"); auto decl = context.insts().Get(decl_id); CARBON_KIND_SWITCH(decl) { case CARBON_KIND(SemIR::StructValue struct_value): { if (struct_value.type_id == SemIR::ErrorInst::TypeId) { return SemIR::ErrorInst::InstId; } // TODO: If a thunk is needed, this will build a different value each // time it's called, so we won't properly deduplicate repeated // witnesses. entries.push_back(CheckAssociatedFunctionImplementation( context, context.types().GetAs(struct_value.type_id), query_specific_interface.specific_id, value_id, /*defer_thunk_definition=*/false)); break; } case CARBON_KIND(SemIR::AssociatedConstantDecl decl): { if (decl.type_id == SemIR::ErrorInst::TypeId) { return SemIR::ErrorInst::InstId; } // TODO: remove once we have a test-case for all associated constants. // Special-case the ones we want to support in this if-statement, until // we're able to account for everything. if (decl.type_id != SemIR::TypeType::TypeId) { context.TODO(loc_id, "Associated constant of type other than `TypeType` in " "synthesized impl"); return SemIR::ErrorInst::InstId; } auto type_id = context.insts().Get(value_id).type_id(); CARBON_CHECK(type_id == SemIR::TypeType::TypeId || type_id == SemIR::ErrorInst::TypeId); auto impl_witness_associated_constant = AddInst( context, loc_id, {.type_id = type_id, .inst_id = value_id}); entries.push_back(impl_witness_associated_constant); break; } default: CARBON_CHECK(decl_id == SemIR::ErrorInst::InstId, "Unexpected kind of associated entity {0}", decl); return SemIR::ErrorInst::InstId; } } return MakeCustomWitnessConstantInst(context, loc_id, query_specific_interface_id, context.inst_blocks().Add(entries)); } auto AsCoreIdentifier(SemIR::CoreInterface core_interface) -> CoreIdentifier { using SemIR::CoreInterface; switch (core_interface) { case CoreInterface::AddAssignWith: return CoreIdentifier::AddAssignWith; case CoreInterface::AddWith: return CoreIdentifier::AddWith; case CoreInterface::Copy: return CoreIdentifier::Copy; case CoreInterface::CppUnsafeDeref: return CoreIdentifier::CppUnsafeDeref; case CoreInterface::Dec: return CoreIdentifier::Dec; case CoreInterface::Default: return CoreIdentifier::Default; case CoreInterface::Destroy: return CoreIdentifier::Destroy; case CoreInterface::DivAssignWith: return CoreIdentifier::DivAssignWith; case CoreInterface::DivWith: return CoreIdentifier::DivWith; case CoreInterface::Inc: return CoreIdentifier::Inc; case CoreInterface::IntFitsIn: return CoreIdentifier::IntFitsIn; case CoreInterface::ModAssignWith: return CoreIdentifier::ModAssignWith; case CoreInterface::ModWith: return CoreIdentifier::ModWith; case CoreInterface::MulAssignWith: return CoreIdentifier::MulAssignWith; case CoreInterface::MulWith: return CoreIdentifier::MulWith; case CoreInterface::Negate: return CoreIdentifier::Negate; case CoreInterface::SubAssignWith: return CoreIdentifier::SubAssignWith; case CoreInterface::SubWith: return CoreIdentifier::SubWith; case CoreInterface::Unknown: CARBON_FATAL("{0} doesn't have a `CoreIdentifier` mapping", core_interface); } } auto GetCoreInterface(Context& context, SemIR::InterfaceId interface_id) -> SemIR::CoreInterface { const auto& interface = context.interfaces().Get(interface_id); if (!context.name_scopes().IsCorePackage(interface.parent_scope_id) || !interface.name_id.AsIdentifierId().has_value()) { return SemIR::CoreInterface::Unknown; } return interface.core_interface; } auto BuildPrimitiveCopyWitness( Context& context, SemIR::LocId loc_id, SemIR::NameScopeId parent_scope_id, SemIR::ConstantId query_self_const_id, SemIR::SpecificInterfaceId query_specific_interface_id) -> SemIR::InstId { auto self_type_id = GetFacetAsType(context, query_self_const_id); auto op_id = MakeCopyOpFunction(context, loc_id, self_type_id, parent_scope_id); return BuildCustomWitness(context, loc_id, query_self_const_id, query_specific_interface_id, {op_id}); } static auto MakeDestroyWitness( Context& context, SemIR::LocId loc_id, SemIR::ConstantId query_self_const_id, SemIR::SpecificInterfaceId query_specific_interface_id, bool build_witness) -> std::optional { auto format = CanDestroyType(context, loc_id, query_self_const_id, query_specific_interface_id); if (format == DestroyFormat::NoDestroy) { return std::nullopt; } if (!build_witness || query_self_const_id.is_symbolic()) { // The type can be destroyed, but we shouldn't make a witness right now. return SemIR::InstId::None; } // Mark functions with the interface's scope as a hint to mangling. This // does not add them to the scope. auto query_specific_interface = context.specific_interfaces().Get(query_specific_interface_id); auto parent_scope_id = context.interfaces() .Get(query_specific_interface.interface_id) .scope_without_self_id; auto self_type_id = GetFacetAsType(context, query_self_const_id); auto op_id = MakeDestroyOpFunction(context, loc_id, self_type_id, parent_scope_id, format); return BuildCustomWitness(context, loc_id, query_self_const_id, query_specific_interface_id, {op_id}); } static auto MakeIntFitsInWitness( Context& context, SemIR::LocId loc_id, SemIR::ConstantId query_self_const_id, SemIR::SpecificInterfaceId query_specific_interface_id, bool build_witness) -> std::optional { auto query_specific_interface = context.specific_interfaces().Get(query_specific_interface_id); auto args_id = query_specific_interface.specific_id; if (!args_id.has_value()) { return std::nullopt; } auto args_block_id = context.specifics().Get(args_id).args_id; auto args_block = context.inst_blocks().Get(args_block_id); if (args_block.size() != 1) { return std::nullopt; } auto dest_const_id = context.constant_values().Get(args_block[0]); if (!dest_const_id.is_constant()) { return std::nullopt; } auto src_type_id = GetFacetAsType(context, query_self_const_id); auto dest_type_id = GetFacetAsType(context, dest_const_id); auto context_fn = [](DiagnosticContextBuilder& /*builder*/) -> void {}; if (!RequireCompleteType(context, src_type_id, loc_id, context_fn) || !RequireCompleteType(context, dest_type_id, loc_id, context_fn)) { return std::nullopt; } auto src_info = context.types().TryGetIntTypeInfo(src_type_id); auto dest_info = context.types().TryGetIntTypeInfo(dest_type_id); if (!src_info || !dest_info) { return std::nullopt; } // If the bit width is unknown (e.g., due to symbolic evaluation), we cannot // determine whether it fits yet. if (src_info->bit_width == IntId::None || dest_info->bit_width == IntId::None) { return std::nullopt; } const auto& src_width = context.ints().Get(src_info->bit_width); const auto& dest_width = context.ints().Get(dest_info->bit_width); bool fits = false; if (src_info->is_signed && !dest_info->is_signed) { // Signed -> unsigned: would truncate the sign bit. fits = false; } else if (src_info->is_signed == dest_info->is_signed) { // Signed -> signed or unsigned -> unsigned: allow widening or preserving // width. fits = src_width.sle(dest_width); } else { // Unsigned -> signed: strict widening required. fits = src_width.slt(dest_width); } if (!fits) { return std::nullopt; } if (!build_witness) { return SemIR::InstId::None; } return BuildCustomWitness(context, loc_id, query_self_const_id, query_specific_interface_id, {}); } auto LookupCustomWitness(Context& context, SemIR::LocId loc_id, SemIR::CoreInterface core_interface, SemIR::ConstantId query_self_const_id, SemIR::SpecificInterfaceId query_specific_interface_id, bool build_witness) -> std::optional { switch (core_interface) { case SemIR::CoreInterface::Destroy: return MakeDestroyWitness(context, loc_id, query_self_const_id, query_specific_interface_id, build_witness); case SemIR::CoreInterface::IntFitsIn: return MakeIntFitsInWitness(context, loc_id, query_self_const_id, query_specific_interface_id, build_witness); case SemIR::CoreInterface::AddAssignWith: case SemIR::CoreInterface::AddWith: case SemIR::CoreInterface::Copy: case SemIR::CoreInterface::CppUnsafeDeref: case SemIR::CoreInterface::Dec: case SemIR::CoreInterface::Default: case SemIR::CoreInterface::DivAssignWith: case SemIR::CoreInterface::DivWith: case SemIR::CoreInterface::Inc: case SemIR::CoreInterface::ModAssignWith: case SemIR::CoreInterface::ModWith: case SemIR::CoreInterface::MulAssignWith: case SemIR::CoreInterface::MulWith: case SemIR::CoreInterface::Negate: case SemIR::CoreInterface::SubAssignWith: case SemIR::CoreInterface::SubWith: case SemIR::CoreInterface::Unknown: // TODO: Handle more interfaces, particularly copy, move, and conversion. return std::nullopt; } } } // namespace Carbon::Check