diff --git a/toolchain/check/check.cpp b/toolchain/check/check.cpp index 86339b81ae1e..25837f0635f5 100644 --- a/toolchain/check/check.cpp +++ b/toolchain/check/check.cpp @@ -16,6 +16,7 @@ #include "toolchain/check/function.h" #include "toolchain/check/handle.h" #include "toolchain/check/import.h" +#include "toolchain/check/import_ref.h" #include "toolchain/diagnostics/diagnostic.h" #include "toolchain/diagnostics/diagnostic_emitter.h" #include "toolchain/lex/token_kind.h" @@ -755,6 +756,10 @@ static auto CheckParseTree( InitPackageScopeAndImports(context, unit_info); + // Import all impls declared in imports. + // TODO: Do this selectively when we see an impl query. + ImportImpls(context); + if (!ProcessNodeIds(context, vlog_stream, unit_info.err_tracker)) { context.sem_ir().set_has_errors(true); return; diff --git a/toolchain/check/import_ref.cpp b/toolchain/check/import_ref.cpp index d7fb08a2d91d..4509bcb10f20 100644 --- a/toolchain/check/import_ref.cpp +++ b/toolchain/check/import_ref.cpp @@ -301,7 +301,11 @@ class ImportRefResolver { if (!inst_id.is_valid()) { // Map scopes that aren't associated with an instruction to invalid // scopes. For now, such scopes aren't used, and we don't have a good way - // to rmmap them. + // to remap them. + return SemIR::NameScopeId::Invalid; + } + if (import_ir_.insts().Is(inst_id)) { + // TODO: Import the scope for an `impl` definition. return SemIR::NameScopeId::Invalid; } auto const_id = GetLocalConstantId(inst_id); @@ -409,6 +413,9 @@ class ImportRefResolver { case SemIR::InstKind::InterfaceDecl: return TryResolveTypedInst(inst.As(), const_id); + case SemIR::InstKind::InterfaceWitness: + return TryResolveTypedInst(inst.As()); + case SemIR::InstKind::InterfaceType: return TryResolveTypedInst(inst.As()); @@ -834,6 +841,31 @@ class ImportRefResolver { return {interface_const_id}; } + auto TryResolveTypedInst(SemIR::InterfaceWitness inst) -> ResolveResult { + auto initial_work = work_stack_.size(); + llvm::SmallVector elements; + auto import_elements = import_ir_.inst_blocks().Get(inst.elements_id); + elements.reserve(import_elements.size()); + for (auto import_elem_id : import_elements) { + if (auto const_id = GetLocalConstantId(import_elem_id); + const_id.is_valid()) { + elements.push_back(const_id.inst_id()); + } + } + if (HasNewWork(initial_work)) { + return ResolveResult::Retry(); + } + CARBON_CHECK(elements.size() == import_elements.size()) + << "Failed to import an element without adding new work."; + + auto elements_id = context_.inst_blocks().Add(elements); + return {TryEvalInst( + context_, SemIR::InstId::Invalid, + SemIR::InterfaceWitness{ + context_.GetBuiltinType(SemIR::BuiltinKind::WitnessType), + elements_id})}; + } + auto TryResolveTypedInst(SemIR::PointerType inst) -> ResolveResult { auto initial_work = work_stack_.size(); CARBON_CHECK(inst.type_id == SemIR::TypeId::TypeType); @@ -987,4 +1019,44 @@ auto LoadImportRef(Context& context, SemIR::InstId inst_id, SemIRLoc loc) } } +// Imports the impl `import_impl_id` from the imported IR `import_ir`. +static auto ImportImpl(Context& context, SemIR::ImportIRId import_ir_id, + const SemIR::File& import_ir, + SemIR::ImplId import_impl_id) -> void { + // Resolve the imported impl to a local impl ID. + ImportRefResolver resolver(context, import_ir_id); + const auto& import_impl = import_ir.impls().Get(import_impl_id); + auto self_id = resolver.ResolveType(import_impl.self_id); + auto constraint_id = resolver.ResolveType(import_impl.constraint_id); + + // Import the definition if the impl is defined. + // TODO: Do we need to check for multiple definitions? + auto impl_id = context.impls().LookupOrAdd(self_id, constraint_id); + if (import_impl.is_defined()) { + // TODO: Create a scope for the `impl` if necessary. + // TODO: Consider importing the definition_id. + + auto& impl = context.impls().Get(impl_id); + impl.witness_id = context.AddImportRef( + {.ir_id = import_ir_id, .inst_id = import_impl.witness_id}); + } +} + +// TODO: This doesn't belong in this file. Consider moving the import resolver +// and this file elsewhere. +auto ImportImpls(Context& context) -> void { + for (auto [import_index, import_ir] : + llvm::enumerate(context.import_irs().array_ref())) { + if (!import_ir.sem_ir) { + continue; + } + + auto import_ir_id = SemIR::ImportIRId(import_index); + for (auto impl_index : llvm::seq(import_ir.sem_ir->impls().size())) { + auto impl_id = SemIR::ImplId(impl_index); + ImportImpl(context, import_ir_id, *import_ir.sem_ir, impl_id); + } + } +} + } // namespace Carbon::Check diff --git a/toolchain/check/import_ref.h b/toolchain/check/import_ref.h index 413c1623ef93..3c8f0e333213 100644 --- a/toolchain/check/import_ref.h +++ b/toolchain/check/import_ref.h @@ -15,6 +15,9 @@ namespace Carbon::Check { auto LoadImportRef(Context& context, SemIR::InstId inst_id, SemIRLoc loc) -> void; +// Load all impls declared in IRs imported into this context. +auto ImportImpls(Context& context) -> void; + } // namespace Carbon::Check #endif // CARBON_TOOLCHAIN_CHECK_IMPORT_REF_H_ diff --git a/toolchain/check/member_access.cpp b/toolchain/check/member_access.cpp index 172ba06f0a2f..deb34c5c71a1 100644 --- a/toolchain/check/member_access.cpp +++ b/toolchain/check/member_access.cpp @@ -5,6 +5,7 @@ #include "llvm/ADT/STLExtras.h" #include "toolchain/check/context.h" #include "toolchain/check/convert.h" +#include "toolchain/check/import_ref.h" #include "toolchain/check/subst.h" #include "toolchain/diagnostics/diagnostic_emitter.h" #include "toolchain/sem_ir/inst.h" @@ -120,7 +121,7 @@ static auto ScopeNeedsImplLookup(Context& context, // Given a type and an interface, searches for an impl that describes how that // type implements that interface, and returns the corresponding witness. // Returns an invalid InstId if no matching impl is found. -static auto LookupInterfaceWitness(Context& context, +static auto LookupInterfaceWitness(Context& context, SemIRLoc loc, SemIR::ConstantId type_const_id, SemIR::InterfaceId interface_id) -> SemIR::InstId { @@ -141,7 +142,11 @@ static auto LookupInterfaceWitness(Context& context, if (interface_type->interface_id != interface_id) { continue; } - // TODO: Diagnose if the impl isn't defined yet? + if (!impl.witness_id.is_valid()) { + // TODO: Diagnose if the impl isn't defined yet? + return SemIR::InstId::Invalid; + } + LoadImportRef(context, impl.witness_id, loc); return impl.witness_id; } return SemIR::InstId::Invalid; @@ -154,8 +159,8 @@ static auto PerformImplLookup(Context& context, Parse::NodeId node_id, SemIR::AssociatedEntityType assoc_type, SemIR::InstId member_id) -> SemIR::InstId { auto& interface = context.interfaces().Get(assoc_type.interface_id); - auto witness_id = - LookupInterfaceWitness(context, type_const_id, assoc_type.interface_id); + auto witness_id = LookupInterfaceWitness(context, node_id, type_const_id, + assoc_type.interface_id); if (!witness_id.is_valid()) { CARBON_DIAGNOSTIC(MissingImplInMemberAccess, Error, "Cannot access member of interface {0} in type {1} " diff --git a/toolchain/check/testdata/impl/lookup/import.carbon b/toolchain/check/testdata/impl/lookup/import.carbon new file mode 100644 index 000000000000..c211781f708e --- /dev/null +++ b/toolchain/check/testdata/impl/lookup/import.carbon @@ -0,0 +1,148 @@ +// 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 +// +// AUTOUPDATE + +// --- impl.carbon + +package Impl api; + +interface HasF { + fn F(); +} + +class C {} + +impl C as HasF { + fn F() {} +} + +// --- use.carbon + +import Impl; + +fn G(c: Impl.C) { + c.(Impl.HasF.F)(); +} + +// CHECK:STDOUT: --- impl.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %.1: type = interface_type @HasF [template] +// CHECK:STDOUT: %.2: type = assoc_entity_type @HasF, [template] +// CHECK:STDOUT: %.3: in HasF> = assoc_entity element0, @HasF.%F [template] +// CHECK:STDOUT: %C: type = class_type @C [template] +// CHECK:STDOUT: %.4: type = struct_type {} [template] +// CHECK:STDOUT: %.5: = interface_witness (@impl.%F) [template] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace [template] { +// CHECK:STDOUT: .HasF = %HasF.decl +// CHECK:STDOUT: .C = %C.decl +// CHECK:STDOUT: } +// CHECK:STDOUT: %HasF.decl: type = interface_decl @HasF [template = constants.%.1] {} +// CHECK:STDOUT: %C.decl: type = class_decl @C [template = constants.%C] {} +// CHECK:STDOUT: impl_decl @impl { +// CHECK:STDOUT: %C.ref: type = name_ref C, %C.decl [template = constants.%C] +// CHECK:STDOUT: %HasF.ref: type = name_ref HasF, %HasF.decl [template = constants.%.1] +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: interface @HasF { +// CHECK:STDOUT: %Self: HasF = bind_symbolic_name Self [symbolic] +// CHECK:STDOUT: %F: = fn_decl @F.1 [template] {} +// CHECK:STDOUT: %.loc5: in HasF> = assoc_entity element0, %F [template = constants.%.3] +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = %Self +// CHECK:STDOUT: .F = %.loc5 +// CHECK:STDOUT: witness = (%F) +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: impl @impl: C as HasF { +// CHECK:STDOUT: %F: = fn_decl @F.2 [template] {} +// CHECK:STDOUT: %.1: = interface_witness (%F) [template = constants.%.5] +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .F = %F +// CHECK:STDOUT: witness = %.1 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @C { +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = constants.%C +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F.1(); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F.2() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: --- use.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %C: type = class_type @C [template] +// CHECK:STDOUT: %.1: type = struct_type {} [template] +// CHECK:STDOUT: %.2: type = interface_type @HasF [template] +// CHECK:STDOUT: %.3: type = tuple_type () [template] +// CHECK:STDOUT: %.4: type = ptr_type {} [template] +// CHECK:STDOUT: %.5: type = assoc_entity_type @HasF, [template] +// CHECK:STDOUT: %.6: in HasF> = assoc_entity element0, file.%import_ref.8 [template] +// CHECK:STDOUT: %.7: = interface_witness () [template] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace [template] { +// CHECK:STDOUT: .Impl = %Impl +// CHECK:STDOUT: .G = %G +// CHECK:STDOUT: } +// CHECK:STDOUT: %Impl: = namespace [template] {} +// CHECK:STDOUT: %C.decl: invalid = class_decl @C [template = constants.%C] {} +// CHECK:STDOUT: %import_ref.1 = import_ref ir2, inst+9, unloaded +// CHECK:STDOUT: %HasF.decl: invalid = interface_decl @HasF [template = constants.%.2] {} +// CHECK:STDOUT: %import_ref.2: in HasF> = import_ref ir2, inst+6, loc_20 [template = constants.%.6] +// CHECK:STDOUT: %import_ref.3 = import_ref ir2, inst+3, unloaded +// CHECK:STDOUT: %import_ref.4 = import_ref ir2, inst+4, unloaded +// CHECK:STDOUT: %import_ref.5: = import_ref ir2, inst+15, loc_22 [template = constants.%.7] +// CHECK:STDOUT: %import_ref.6: type = import_ref ir2, inst+8, loc_10 [template = constants.%C] +// CHECK:STDOUT: %G: = fn_decl @G [template] { +// CHECK:STDOUT: %Impl.ref: = name_ref Impl, %Impl [template = %Impl] +// CHECK:STDOUT: %C.ref: type = name_ref C, %import_ref.6 [template = constants.%C] +// CHECK:STDOUT: %c.loc4_6.1: C = param c +// CHECK:STDOUT: @G.%c: C = bind_name c, %c.loc4_6.1 +// CHECK:STDOUT: } +// CHECK:STDOUT: %import_ref.7: type = import_ref ir2, inst+1, loc_18 [template = constants.%.2] +// CHECK:STDOUT: %import_ref.8 = import_ref ir2, inst+4, unloaded +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: interface @HasF { +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .F = file.%import_ref.2 +// CHECK:STDOUT: .Self = file.%import_ref.3 +// CHECK:STDOUT: witness = (file.%import_ref.4) +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: impl @impl: C as HasF; +// CHECK:STDOUT: +// CHECK:STDOUT: class @C { +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = file.%import_ref.1 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @G(%c: C) { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %c.ref: C = name_ref c, %c +// CHECK:STDOUT: %Impl.ref: = name_ref Impl, file.%Impl [template = file.%Impl] +// CHECK:STDOUT: %HasF.ref: type = name_ref HasF, file.%import_ref.7 [template = constants.%.2] +// CHECK:STDOUT: %F.ref: in HasF> = name_ref F, file.%import_ref.2 [template = constants.%.6] +// CHECK:STDOUT: %.1: = interface_witness_access file.%import_ref.5, element0 [template = ] +// CHECK:STDOUT: %.loc5: init () = call %.1() +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F(); +// CHECK:STDOUT: