From dbf79d52297723d744960a7a898d9b1d964e9e96 Mon Sep 17 00:00:00 2001 From: Nicholas Bishop Date: Fri, 18 Sep 2026 19:12:05 +0000 Subject: [PATCH] Fix accessing protected members from templates derived classes (#7780) When evaluating a deferred member access action, the scope stack cannot be relied on, so `LookupUnqualifiedName` cannot be used in `GetHighestAllowedAccess` to get the `Self` type. Instead, store the `Self` type in the `Context` when evaluating a method, and use that in `GetHighestAllowedAccess`. --- toolchain/check/context.h | 9 ++ toolchain/check/cpp/overload_resolution.cpp | 2 +- toolchain/check/decl_name_stack.cpp | 18 +++ toolchain/check/decl_name_stack.h | 3 + toolchain/check/eval.cpp | 23 ++- toolchain/check/member_access.cpp | 55 +++---- toolchain/check/member_access.h | 2 +- .../class/generic/member_access.carbon | 22 +++ .../generic/template/member_access.carbon | 140 ++++++++++++------ toolchain/check/testdata/impl/access.carbon | 129 ++++++++++++++++ 10 files changed, 323 insertions(+), 80 deletions(-) create mode 100644 toolchain/check/testdata/impl/access.carbon diff --git a/toolchain/check/context.h b/toolchain/check/context.h index 7a9960aca33c..5e2b46f1092b 100644 --- a/toolchain/check/context.h +++ b/toolchain/check/context.h @@ -352,6 +352,8 @@ class Context { auto core_identifiers() -> CoreIdentifierCache& { return core_identifiers_; } + auto access_context() -> SemIR::NameScopeId& { return access_context_; } + // -------------------------------------------------------------------------- // Directly expose SemIR::File data accessors for brevity in calls. // -------------------------------------------------------------------------- @@ -626,6 +628,13 @@ class Context { CoreIdentifierCache core_identifiers_; bool mangle_string_fingerprint_; + + // Scope for querying member access. For example, when checking a class + // method, this would be set to the scope of that method's class. + // + // This is updated by `DeclNameStack`. During monomorphization, it is updated + // by `TryEvalBlockForSpecific`. + SemIR::NameScopeId access_context_ = SemIR::NameScopeId::None; }; inline constexpr Context::FormExpr Context::FormExpr::Error = { diff --git a/toolchain/check/cpp/overload_resolution.cpp b/toolchain/check/cpp/overload_resolution.cpp index 4ff5bf30b9b5..ebf85082e8ab 100644 --- a/toolchain/check/cpp/overload_resolution.cpp +++ b/toolchain/check/cpp/overload_resolution.cpp @@ -122,7 +122,7 @@ auto CheckCppOverloadAccess( auto name_scope_const_id = context.constant_values().Get( context.name_scopes().Get(parent_scope_id).inst_id()); SemIR::AccessKind allowed_access_kind = - GetHighestAllowedAccess(context, loc_id, name_scope_const_id); + GetHighestAllowedAccess(context, name_scope_const_id); CheckAccess(context, loc_id, SemIR::LocId(overload_inst_id), function.name_id, member_access_kind, /*is_parent_access=*/false, diff --git a/toolchain/check/decl_name_stack.cpp b/toolchain/check/decl_name_stack.cpp index 13e8fea0943a..ac1284830424 100644 --- a/toolchain/check/decl_name_stack.cpp +++ b/toolchain/check/decl_name_stack.cpp @@ -13,6 +13,7 @@ #include "toolchain/check/merge.h" #include "toolchain/check/name_component.h" #include "toolchain/check/name_lookup.h" +#include "toolchain/check/type.h" #include "toolchain/check/type_completion.h" #include "toolchain/check/unused.h" #include "toolchain/diagnostics/diagnostic.h" @@ -65,6 +66,8 @@ auto DeclNameStack::PushScopeAndStartName() -> void { // Create a scope for any parameters introduced in this name. context_->scope_stack().PushForDeclName(); + + UpdateAccessContext(); } auto DeclNameStack::FinishName(const NameComponent& name) -> NameContext { @@ -93,6 +96,8 @@ auto DeclNameStack::PopScope(bool check_unused) -> void { context_->scope_stack().PopTo(decl_name_stack_.back().initial_scope_index, check_unused); decl_name_stack_.pop_back(); + + UpdateAccessContext(); } auto DeclNameStack::Suspend() -> SuspendedName { @@ -108,6 +113,9 @@ auto DeclNameStack::Suspend() -> SuspendedName { CARBON_CHECK(scope_stack.PeekIndex() == scope_index, "Scope index {0} does not enclose the current scope {1}", scope_index, scope_stack.PeekIndex()); + + UpdateAccessContext(); + return result; } @@ -131,6 +139,8 @@ auto DeclNameStack::Restore(SuspendedName&& sus) -> void { context_->scope_stack().Restore(std::move(suspended_scope)); } + + UpdateAccessContext(); } auto DeclNameStack::AddName(NameContext name_context, SemIR::InstId target_id, @@ -523,4 +533,12 @@ auto DeclNameStack::ResolveAsScope(const NameContext& name_context, } } +auto DeclNameStack::UpdateAccessContext() const -> void { + if (decl_name_stack_.empty()) { + context_->access_context() = SemIR::NameScopeId::None; + } else { + context_->access_context() = decl_name_stack_.back().parent_scope_id; + } +} + } // namespace Carbon::Check diff --git a/toolchain/check/decl_name_stack.h b/toolchain/check/decl_name_stack.h index 80bbd8808a2b..5c8c762509ae 100644 --- a/toolchain/check/decl_name_stack.h +++ b/toolchain/check/decl_name_stack.h @@ -279,6 +279,9 @@ class DeclNameStack { const NameComponent& name) const -> std::pair; + // Update `Context.access_context` to the current NameContext's parent scope. + auto UpdateAccessContext() const -> void; + // The linked context. Context* context_; diff --git a/toolchain/check/eval.cpp b/toolchain/check/eval.cpp index 75e121740ef7..f5efcc4caecb 100644 --- a/toolchain/check/eval.cpp +++ b/toolchain/check/eval.cpp @@ -12,6 +12,7 @@ #include "common/raw_string_ostream.h" #include "llvm/ADT/APFloat.h" #include "llvm/Support/ConvertUTF.h" +#include "llvm/Support/SaveAndRestore.h" #include "toolchain/base/canonical_value_store.h" #include "toolchain/base/int.h" #include "toolchain/base/kind_switch.h" @@ -3458,12 +3459,30 @@ auto TryEvalInstUnsafe(Context& context, SemIR::InstId inst_id, return TryEvalInstInContext(eval_context, inst_id, inst); } +// Update `context.access_context` to the type of the innermost enclosing type +// scope of the generic. +static auto SetAccessContext(Context& context, const SemIR::Generic& generic) { + auto function_decl = + context.insts().TryGetAs(generic.decl_id); + if (!function_decl || !function_decl->function_id.has_value()) { + return; + } + const auto& function = context.functions().Get(function_decl->function_id); + if (!function.parent_scope_id.has_value()) { + return; + } + + context.access_context() = function.parent_scope_id; +} + auto TryEvalBlockForSpecific(Context& context, SemIR::LocId loc_id, SemIR::SpecificId specific_id, SemIR::GenericInstIndex::Region region) -> void { auto generic_id = context.specifics().Get(specific_id).generic_id; - auto eval_block_id = context.generics().Get(generic_id).GetEvalBlock(region); + const auto& generic = context.generics().Get(generic_id); + auto eval_block_id = generic.GetEvalBlock(region); auto eval_block = context.inst_blocks().Get(eval_block_id); + llvm::SaveAndRestore access_context(context.access_context()); // Allocate the value block and store it back onto the specific, so that our // in-progress results are visible. @@ -3476,6 +3495,8 @@ auto TryEvalBlockForSpecific(Context& context, SemIR::LocId loc_id, } specific.SetValueBlock(region, value_block_id); + SetAccessContext(context, generic); + EvalContext eval_context(&context, loc_id, specific_id); Diagnostics::ContextScope diagnostic_context( diff --git a/toolchain/check/member_access.cpp b/toolchain/check/member_access.cpp index 07ca4aec5e51..1dbc1bca25e6 100644 --- a/toolchain/check/member_access.cpp +++ b/toolchain/check/member_access.cpp @@ -99,54 +99,41 @@ static auto IsInstanceType(Context& context, SemIR::TypeId type_id) -> bool { return false; } -auto GetHighestAllowedAccess(Context& context, SemIR::LocId loc_id, +auto GetHighestAllowedAccess(Context& context, SemIR::ConstantId name_scope_const_id) -> SemIR::AccessKind { - SemIR::ScopeLookupResult lookup_result = - LookupUnqualifiedName(context, loc_id, SemIR::NameId::SelfType, - /*required=*/false) - .scope_result; - CARBON_CHECK(!lookup_result.is_poisoned()); - if (!lookup_result.is_found()) { + SemIR::NameScopeId access_context_scope_id = context.access_context(); + if (!access_context_scope_id.has_value()) { return SemIR::AccessKind::Public; } - // TODO: Support other types for `Self`. - auto self_class_type = context.insts().TryGetAs( - lookup_result.target_inst_id()); - if (!self_class_type) { - return SemIR::AccessKind::Public; - } - - auto self_class_info = context.classes().Get(self_class_type->class_id); - // TODO: Support other types. if (auto class_type = context.constant_values().TryGetInstAs( name_scope_const_id)) { auto class_info = context.classes().Get(class_type->class_id); - if (self_class_info.self_type_id == class_info.self_type_id) { - return SemIR::AccessKind::Private; + // Check if private access is allowed. + while (access_context_scope_id.has_value()) { + if (class_info.scope_id == access_context_scope_id) { + return SemIR::AccessKind::Private; + } + + const auto& scope = context.name_scopes().Get(access_context_scope_id); + access_context_scope_id = scope.parent_scope_id(); } - // If the `type_id` of `Self` does not match with the one we're currently - // accessing, try checking if this class is of the parent type of `Self`. - if (auto base_type_id = self_class_info.GetBaseType( - context.sem_ir(), self_class_type->specific_id); - base_type_id.has_value()) { - if (context.types().GetConstantId(base_type_id) == name_scope_const_id) { - return SemIR::AccessKind::Protected; - } - // TODO: Also check whether this base class has a base class of its own. - } else if (auto adapt_type_id = self_class_info.GetAdaptedType( - context.sem_ir(), self_class_type->specific_id); - adapt_type_id.has_value()) { - if (context.types().GetConstantId(adapt_type_id) == name_scope_const_id) { - // TODO: Should we be allowed to access protected fields of a type we - // are adapting? The design doesn't allow this. + // Check if protected access is allowed. + access_context_scope_id = context.access_context(); + const auto& scope = context.name_scopes().Get(access_context_scope_id); + for (auto extended_scope_id : scope.extended_scopes()) { + auto const_id = context.constant_values().Get(extended_scope_id); + if (const_id == name_scope_const_id) { return SemIR::AccessKind::Protected; } + + // TODO: also check indirectly-extended scopes, as well as extended + // scopes of parent scopes of the access context. } } @@ -318,7 +305,7 @@ static auto LookupMemberNameInScope(Context& context, SemIR::LocId loc_id, AccessInfo access_info = { .constant_id = name_scope_const_id, .highest_allowed_access = - GetHighestAllowedAccess(context, loc_id, name_scope_const_id), + GetHighestAllowedAccess(context, name_scope_const_id), }; LookupResult result = LookupQualifiedName( context, loc_id, name_id, lookup_scopes, required, access_info); diff --git a/toolchain/check/member_access.h b/toolchain/check/member_access.h index 4a6bf54e7bc9..6c10b58bbaa7 100644 --- a/toolchain/check/member_access.h +++ b/toolchain/check/member_access.h @@ -13,7 +13,7 @@ namespace Carbon::Check { // Returns the highest allowed access for members of `name_scope_const_id`. For // example, if this returns `Protected` then only `Public` and `Protected` // accesses are allowed -- not `Private`. -auto GetHighestAllowedAccess(Context& context, SemIR::LocId loc_id, +auto GetHighestAllowedAccess(Context& context, SemIR::ConstantId name_scope_const_id) -> SemIR::AccessKind; diff --git a/toolchain/check/testdata/class/generic/member_access.carbon b/toolchain/check/testdata/class/generic/member_access.carbon index ca8f9d339a1e..40a76b82d1a2 100644 --- a/toolchain/check/testdata/class/generic/member_access.carbon +++ b/toolchain/check/testdata/class/generic/member_access.carbon @@ -62,6 +62,28 @@ fn StaticMemberFunctionCall(generic T: type) -> Class(T) { //@dump-sem-ir-end } +// --- access_private_method_after_specific_eval.carbon + +library "[[@TEST_NAME]]"; + +interface I(T: type) { + fn F(self, t: T); +} + +class A {} +class B {} + +impl A as I(B) { + fn F(unused self, unused b: B) {} +} + +class C { + private fn G(ref self) { + let a: A = {}; + a.(I(B).F)({}); + self.G(); + } +} // CHECK:STDOUT: --- member_access.carbon // CHECK:STDOUT: diff --git a/toolchain/check/testdata/generic/template/member_access.carbon b/toolchain/check/testdata/generic/template/member_access.carbon index 8d0dbae0440f..a91528ee21f1 100644 --- a/toolchain/check/testdata/generic/template/member_access.carbon +++ b/toolchain/check/testdata/generic/template/member_access.carbon @@ -2,7 +2,7 @@ // Exceptions. See /LICENSE for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // -// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/int.carbon +// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/full.carbon // // AUTOUPDATE // TIP: To test this file alone, run: @@ -85,7 +85,7 @@ fn Test(e: E) { F(e); } -// --- fail_todo_compound_member_access.carbon +// --- compound_member_access.carbon library "[[@TEST_NAME]]"; @@ -104,16 +104,30 @@ class D { } fn G(d: D) -> i32 { - // CHECK:STDERR: fail_todo_compound_member_access.carbon:[[@LINE+7]]:12: error: unable to monomorphize specific `F(D)` [ResolvingSpecificHere] - // CHECK:STDERR: return F(d); - // CHECK:STDERR: ^ - // CHECK:STDERR: fail_todo_compound_member_access.carbon:[[@LINE-12]]:3: note: cannot access member of interface `Core.ImplicitAs(i32)` in type `i32` that does not implement that interface [MissingImplInMemberAccess] - // CHECK:STDERR: return x.(C.n); - // CHECK:STDERR: ^~~~~~~~~~~~~~~ - // CHECK:STDERR: return F(d); } +// --- protected_member_access.carbon + +library "[[@TEST_NAME]]"; + +base class A { + protected var n: i32 = 1; +} + +class B(template T: type) { + extend base: A; + + fn F(self) -> i32 { + return self.n; + } +} + +fn G() { + let b: B(i32) = {.base = {}}; + b.F(); +} + // CHECK:STDOUT: --- simple_member_access.carbon // CHECK:STDOUT: // CHECK:STDOUT: constants { @@ -184,7 +198,7 @@ fn G(d: D) -> i32 { // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: imports { -// CHECK:STDOUT: %Core.import_ref.cd6: @Int.as.Copy.impl.%Int.as.Copy.impl.Op.type (%Int.as.Copy.impl.Op.type.ac8) = import_ref Core//prelude/parts/int, loc{{\d+_\d+}}, loaded [symbolic = @Int.as.Copy.impl.%Int.as.Copy.impl.Op (constants.%Int.as.Copy.impl.Op.5e0)] +// CHECK:STDOUT: %Core.import_ref.cd6: @Int.as.Copy.impl.%Int.as.Copy.impl.Op.type (%Int.as.Copy.impl.Op.type.ac8) = import_ref Core//prelude/types/int, loc{{\d+_\d+}}, loaded [symbolic = @Int.as.Copy.impl.%Int.as.Copy.impl.Op (constants.%Int.as.Copy.impl.Op.5e0)] // CHECK:STDOUT: %Copy.impl_witness_table.8d2 = impl_witness_table (%Core.import_ref.cd6), @Int.as.Copy.impl [concrete] // CHECK:STDOUT: } // CHECK:STDOUT: @@ -287,20 +301,21 @@ fn G(d: D) -> i32 { // CHECK:STDOUT: %.loc6_17.8 => invalid // CHECK:STDOUT: } // CHECK:STDOUT: -// CHECK:STDOUT: --- fail_todo_compound_member_access.carbon +// CHECK:STDOUT: --- compound_member_access.carbon // CHECK:STDOUT: // CHECK:STDOUT: constants { // CHECK:STDOUT: %C: type = class_type @C [concrete] // CHECK:STDOUT: %int_32: Core.IntLiteral = int_value 32 [concrete] +// CHECK:STDOUT: %N: Core.IntLiteral = symbolic_binding N, 0 [symbolic] // CHECK:STDOUT: %i32: type = class_type @Int, @Int(%int_32) [concrete] // CHECK:STDOUT: %pattern_type.6b6: type = pattern_type %i32 [concrete] // CHECK:STDOUT: %C.elem: type = unbound_element_type %C, %i32 [concrete] // CHECK:STDOUT: %type: type = facet_type [concrete] // CHECK:STDOUT: %.Self.frozen: %type = symbolic_binding .Self [symbolic_self] // CHECK:STDOUT: %pattern_type.98f: type = pattern_type type [concrete] -// CHECK:STDOUT: %T.patt: %pattern_type.98f = symbolic_binding_pattern T, 0, template [template] -// CHECK:STDOUT: %T: type = symbolic_binding T, 0, template [template] -// CHECK:STDOUT: %pattern_type.51d1c4.1: type = pattern_type %T [template] +// CHECK:STDOUT: %T.patt.d47011.1: %pattern_type.98f = symbolic_binding_pattern T, 0, template [template] +// CHECK:STDOUT: %T.67db0b.1: type = symbolic_binding T, 0, template [template] +// CHECK:STDOUT: %pattern_type.51d1c4.1: type = pattern_type %T.67db0b.1 [template] // CHECK:STDOUT: %x.param_patt.91d: %pattern_type.51d1c4.1 = value_param_pattern [template] // CHECK:STDOUT: %x.patt.260: %pattern_type.51d1c4.1 = wrapper_binding_pattern x, %x.param_patt.91d [template] // CHECK:STDOUT: %.795f: Core.Form = init_form %i32 [concrete] @@ -308,14 +323,15 @@ fn G(d: D) -> i32 { // CHECK:STDOUT: %return.patt.e1b: %pattern_type.6b6 = return_slot_pattern %return.param_patt.a9a, %i32 [concrete] // CHECK:STDOUT: %F.type: type = fn_type @F [concrete] // CHECK:STDOUT: %F: %F.type = struct_value () [concrete] -// CHECK:STDOUT: %require_complete.944: = require_complete_type %T [template] +// CHECK:STDOUT: %require_complete.944: = require_complete_type %T.67db0b.1 [template] // CHECK:STDOUT: %.525: type = type_of_inst @F.%.loc10_11.3 [template] // CHECK:STDOUT: %.ba9: %.525 = splice_inst @F.%.loc10_11.3 [template] // CHECK:STDOUT: %Dest: type = symbolic_binding Dest, 0 [symbolic] -// CHECK:STDOUT: %ImplicitAs.type.3aa: type = facet_type <@ImplicitAs, @ImplicitAs(%Dest)> [symbolic] -// CHECK:STDOUT: %Self.294: %ImplicitAs.type.3aa = symbolic_binding Self, 1 [symbolic] -// CHECK:STDOUT: %ImplicitAs.WithSelf.Convert.type.97e: type = fn_type @ImplicitAs.WithSelf.Convert, @ImplicitAs.WithSelf(%Dest, %Self.294) [symbolic] -// CHECK:STDOUT: %ImplicitAs.WithSelf.Convert.945: %ImplicitAs.WithSelf.Convert.type.97e = struct_value () [symbolic] +// CHECK:STDOUT: %ImplicitAs.type.3aa489.1: type = facet_type <@ImplicitAs, @ImplicitAs(%Dest)> [symbolic] +// CHECK:STDOUT: %Self.29426c.1: %ImplicitAs.type.3aa489.1 = symbolic_binding Self, 1 [symbolic] +// CHECK:STDOUT: %ImplicitAs.WithSelf.Convert.type.97ed63.1: type = fn_type @ImplicitAs.WithSelf.Convert, @ImplicitAs.WithSelf(%Dest, %Self.29426c.1) [symbolic] +// CHECK:STDOUT: %ImplicitAs.WithSelf.Convert.9453b4.1: %ImplicitAs.WithSelf.Convert.type.97ed63.1 = struct_value () [symbolic] +// CHECK:STDOUT: %ImplicitAs.type.914: type = facet_type <@ImplicitAs, @ImplicitAs(%i32)> [concrete] // CHECK:STDOUT: %ImplicitAs.assoc_type.e23: type = assoc_entity_type @ImplicitAs, @ImplicitAs(%i32) [concrete] // CHECK:STDOUT: %assoc0.368: %ImplicitAs.assoc_type.e23 = assoc_entity element0, imports.%Core.import_ref.cb2 [concrete] // CHECK:STDOUT: %.ba3: type = type_of_inst @F.%.loc10_17.7 [template] @@ -337,18 +353,56 @@ fn G(d: D) -> i32 { // CHECK:STDOUT: %.287: ref %i32 = class_element_access %.07e, element0 // CHECK:STDOUT: } // CHECK:STDOUT: } -// CHECK:STDOUT: %inst.splice_block.edd: = inst_value [concrete] { -// CHECK:STDOUT: %.432: = splice_block [concrete = ] {} +// CHECK:STDOUT: %To: Core.IntLiteral = symbolic_binding To, 0 [symbolic] +// CHECK:STDOUT: %Int.67af24.2: type = class_type @Int, @Int(%To) [symbolic] +// CHECK:STDOUT: %facet_type.037: type = facet_type <@IntFitsIn, @IntFitsIn(%Int.67af24.2) & @AnyInt> [symbolic] +// CHECK:STDOUT: %From.6a4: %facet_type.037 = symbolic_binding From, 1 [symbolic] +// CHECK:STDOUT: %From.as_type.as.ImplicitAs.impl.Convert.type.7fa: type = fn_type @From.as_type.as.ImplicitAs.impl.Convert, @From.as_type.as.ImplicitAs.impl(%To, %From.6a4) [symbolic] +// CHECK:STDOUT: %From.as_type.as.ImplicitAs.impl.Convert.643: %From.as_type.as.ImplicitAs.impl.Convert.type.7fa = struct_value () [symbolic] +// CHECK:STDOUT: %facet_type.138: type = facet_type <@IntFitsIn, @IntFitsIn(%i32) & @AnyInt> [concrete] +// CHECK:STDOUT: %custom_witness: = custom_witness (), @IntFitsIn, @IntFitsIn(%i32) [concrete] +// CHECK:STDOUT: %Int.as.AnyInt.impl.AsInt.type.f15: type = fn_type @Int.as.AnyInt.impl.AsInt, @Int.as.AnyInt.impl(%N) [symbolic] +// CHECK:STDOUT: %Int.as.AnyInt.impl.AsInt.d10: %Int.as.AnyInt.impl.AsInt.type.f15 = struct_value () [symbolic] +// CHECK:STDOUT: %AnyInt.impl_witness.48c: = impl_witness imports.%AnyInt.impl_witness_table, @Int.as.AnyInt.impl(%int_32) [concrete] +// CHECK:STDOUT: %facet_value: %facet_type.138 = facet_value %i32, (%custom_witness, %AnyInt.impl_witness.48c) [concrete] +// CHECK:STDOUT: %ImplicitAs.impl_witness.1d8: = impl_witness imports.%ImplicitAs.impl_witness_table.992, @From.as_type.as.ImplicitAs.impl(%int_32, %facet_value) [concrete] +// CHECK:STDOUT: %From.as_type.as.ImplicitAs.impl.Convert.type.9d6: type = fn_type @From.as_type.as.ImplicitAs.impl.Convert, @From.as_type.as.ImplicitAs.impl(%int_32, %facet_value) [concrete] +// CHECK:STDOUT: %From.as_type.as.ImplicitAs.impl.Convert.84a: %From.as_type.as.ImplicitAs.impl.Convert.type.9d6 = struct_value () [concrete] +// CHECK:STDOUT: %ImplicitAs.facet: %ImplicitAs.type.914 = facet_value %i32, (%ImplicitAs.impl_witness.1d8) [concrete] +// CHECK:STDOUT: %ImplicitAs.WithSelf.Convert.type.157: type = fn_type @ImplicitAs.WithSelf.Convert, @ImplicitAs.WithSelf(%i32, %ImplicitAs.facet) [concrete] +// CHECK:STDOUT: %.3fd: type = fn_type_with_self_type %ImplicitAs.WithSelf.Convert.type.157, %ImplicitAs.facet [concrete] +// CHECK:STDOUT: %inst.splice_block.2ae: = inst_value [concrete] { +// CHECK:STDOUT: %.2dc: = splice_block %bound_method.b08 { +// CHECK:STDOUT: %impl.elem0.6c4: %.3fd = impl_witness_access %ImplicitAs.impl_witness.1d8, element0 [concrete = %From.as_type.as.ImplicitAs.impl.Convert.84a] +// CHECK:STDOUT: %bound_method.b08: = bound_method %.99f, %impl.elem0.6c4 +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: %From.as_type.as.ImplicitAs.impl.Convert.specific_fn: = specific_function %From.as_type.as.ImplicitAs.impl.Convert.84a, @From.as_type.as.ImplicitAs.impl.Convert(%int_32, %facet_value) [concrete] +// CHECK:STDOUT: %inst.splice_block.3b5: = inst_value [concrete] { +// CHECK:STDOUT: %.fe6: init %i32 = splice_block %From.as_type.as.ImplicitAs.impl.Convert.call { +// CHECK:STDOUT: %specific_fn: = specific_function %impl.elem0.6c4, @From.as_type.as.ImplicitAs.impl.Convert(%int_32, %facet_value) [concrete = %From.as_type.as.ImplicitAs.impl.Convert.specific_fn] +// CHECK:STDOUT: %bound_method.dab: = bound_method %.99f, %specific_fn +// CHECK:STDOUT: %.1ba: %i32 = acquire_value %.99f +// CHECK:STDOUT: %From.as_type.as.ImplicitAs.impl.Convert.call: init %i32 = call %bound_method.dab(%.1ba) +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: %inst.specific_inst: = inst_value [concrete] { +// CHECK:STDOUT: %.ec7: init %i32 = specific_inst @F.%.loc10_17.5, @F(%D) // CHECK:STDOUT: } // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: imports { -// CHECK:STDOUT: %Core.import_ref.cb2: @ImplicitAs.WithSelf.%ImplicitAs.WithSelf.Convert.type (%ImplicitAs.WithSelf.Convert.type.97e) = import_ref Core//prelude/parts/as, loc{{\d+_\d+}}, loaded [symbolic = @ImplicitAs.WithSelf.%ImplicitAs.WithSelf.Convert (constants.%ImplicitAs.WithSelf.Convert.945)] +// CHECK:STDOUT: %Core.import_ref.cb2: @ImplicitAs.WithSelf.%ImplicitAs.WithSelf.Convert.type (%ImplicitAs.WithSelf.Convert.type.97ed63.1) = import_ref Core//prelude/operators/as, loc{{\d+_\d+}}, loaded [symbolic = @ImplicitAs.WithSelf.%ImplicitAs.WithSelf.Convert (constants.%ImplicitAs.WithSelf.Convert.9453b4.1)] +// CHECK:STDOUT: %Core.import_ref.2a4: @From.as_type.as.ImplicitAs.impl.%From.as_type.as.ImplicitAs.impl.Convert.type (%From.as_type.as.ImplicitAs.impl.Convert.type.7fa) = import_ref Core//prelude/types/int, loc{{\d+_\d+}}, loaded [symbolic = @From.as_type.as.ImplicitAs.impl.%From.as_type.as.ImplicitAs.impl.Convert (constants.%From.as_type.as.ImplicitAs.impl.Convert.643)] +// CHECK:STDOUT: %ImplicitAs.impl_witness_table.992 = impl_witness_table (%Core.import_ref.2a4), @From.as_type.as.ImplicitAs.impl [concrete] +// CHECK:STDOUT: %Core.import_ref.47b: Core.IntLiteral = import_ref Core//prelude/types/int, loc{{\d+_\d+}}, loaded [symbolic = @Int.as.AnyInt.impl.%N (constants.%N)] +// CHECK:STDOUT: %Core.import_ref.49b: @Int.as.AnyInt.impl.%Int.as.AnyInt.impl.AsInt.type (%Int.as.AnyInt.impl.AsInt.type.f15) = import_ref Core//prelude/types/int, loc{{\d+_\d+}}, loaded [symbolic = @Int.as.AnyInt.impl.%Int.as.AnyInt.impl.AsInt (constants.%Int.as.AnyInt.impl.AsInt.d10)] +// CHECK:STDOUT: %AnyInt.impl_witness_table = impl_witness_table (%Core.import_ref.47b, %Core.import_ref.49b), @Int.as.AnyInt.impl [concrete] // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: file { // CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [concrete = constants.%F] { -// CHECK:STDOUT: %T.patt.loc9_16.1: %pattern_type.98f = symbolic_binding_pattern T, 0, template [template = %T.patt.loc9_16.2 (constants.%T.patt)] +// CHECK:STDOUT: %T.patt.loc9_16.1: %pattern_type.98f = symbolic_binding_pattern T, 0, template [template = %T.patt.loc9_16.2 (constants.%T.patt.d47011.1)] // CHECK:STDOUT: %x.param_patt.loc9_25.1: @F.%pattern_type (%pattern_type.51d1c4.1) = value_param_pattern [template = %x.param_patt.loc9_25.2 (constants.%x.param_patt.91d)] // CHECK:STDOUT: %x.patt.loc9_25.1: @F.%pattern_type (%pattern_type.51d1c4.1) = wrapper_binding_pattern x, %x.param_patt.loc9_25.1 [template = %x.patt.loc9_25.2 (constants.%x.patt.260)] // CHECK:STDOUT: %return.param_patt: %pattern_type.6b6 = out_param_pattern [concrete = constants.%return.param_patt.a9a] @@ -360,18 +414,18 @@ fn G(d: D) -> i32 { // CHECK:STDOUT: %.Self.frozen: %type = symbolic_binding .Self [symbolic_self = constants.%.Self.frozen] // CHECK:STDOUT: %.loc9_18.2: type = type_literal type [concrete = type] // CHECK:STDOUT: } -// CHECK:STDOUT: %T.loc9_16.2: type = symbolic_binding T, 0, template [template = %T.loc9_16.1 (constants.%T)] -// CHECK:STDOUT: %x.param: @F.%T.loc9_16.1 (%T) = value_param call_param0 -// CHECK:STDOUT: %T.ref: type = name_ref T, %T.loc9_16.2 [template = %T.loc9_16.1 (constants.%T)] -// CHECK:STDOUT: %x: @F.%T.loc9_16.1 (%T) = wrapper_binding x, %x.param +// CHECK:STDOUT: %T.loc9_16.2: type = symbolic_binding T, 0, template [template = %T.loc9_16.1 (constants.%T.67db0b.1)] +// CHECK:STDOUT: %x.param: @F.%T.loc9_16.1 (%T.67db0b.1) = value_param call_param0 +// CHECK:STDOUT: %T.ref: type = name_ref T, %T.loc9_16.2 [template = %T.loc9_16.1 (constants.%T.67db0b.1)] +// CHECK:STDOUT: %x: @F.%T.loc9_16.1 (%T.67db0b.1) = wrapper_binding x, %x.param // CHECK:STDOUT: %return.param: ref %i32 = out_param call_param1 // CHECK:STDOUT: %return: ref %i32 = return_slot %return.param // CHECK:STDOUT: } // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: generic fn @F(%T.loc9_16.2: type) { -// CHECK:STDOUT: %T.patt.loc9_16.2: %pattern_type.98f = symbolic_binding_pattern T, 0, template [template = %T.patt.loc9_16.2 (constants.%T.patt)] -// CHECK:STDOUT: %T.loc9_16.1: type = symbolic_binding T, 0, template [template = %T.loc9_16.1 (constants.%T)] +// CHECK:STDOUT: %T.patt.loc9_16.2: %pattern_type.98f = symbolic_binding_pattern T, 0, template [template = %T.patt.loc9_16.2 (constants.%T.patt.d47011.1)] +// CHECK:STDOUT: %T.loc9_16.1: type = symbolic_binding T, 0, template [template = %T.loc9_16.1 (constants.%T.67db0b.1)] // CHECK:STDOUT: %pattern_type: type = pattern_type %T.loc9_16.1 [template = %pattern_type (constants.%pattern_type.51d1c4.1)] // CHECK:STDOUT: %x.param_patt.loc9_25.2: @F.%pattern_type (%pattern_type.51d1c4.1) = value_param_pattern [template = %x.param_patt.loc9_25.2 (constants.%x.param_patt.91d)] // CHECK:STDOUT: %x.patt.loc9_25.2: @F.%pattern_type (%pattern_type.51d1c4.1) = wrapper_binding_pattern x, %x.param_patt.loc9_25.2 [template = %x.patt.loc9_25.2 (constants.%x.patt.260)] @@ -390,9 +444,9 @@ fn G(d: D) -> i32 { // CHECK:STDOUT: %.loc10_17.13: = convert_to_category_action %.loc10_17.5, element10 [template] // CHECK:STDOUT: %.loc10_17.14: %i32 = splice_inst %.loc10_17.13 [template = %.loc10_17.14 (constants.%.fc4)] // CHECK:STDOUT: -// CHECK:STDOUT: fn(%x.param: @F.%T.loc9_16.1 (%T)) -> out %return.param: %i32 { +// CHECK:STDOUT: fn(%x.param: @F.%T.loc9_16.1 (%T.67db0b.1)) -> out %return.param: %i32 { // CHECK:STDOUT: !entry: -// CHECK:STDOUT: %x.ref: @F.%T.loc9_16.1 (%T) = name_ref x, %x +// CHECK:STDOUT: %x.ref: @F.%T.loc9_16.1 (%T.67db0b.1) = name_ref x, %x // CHECK:STDOUT: %C.ref: type = name_ref C, file.%C.decl [concrete = constants.%C] // CHECK:STDOUT: %n.ref: %C.elem = name_ref n, @C.%field_decl [concrete = @C.%field_decl] // CHECK:STDOUT: %.loc10_11.1: type = type_of_inst %.loc10_11.3 [template = %.loc10_11.4 (constants.%.525)] @@ -407,16 +461,16 @@ fn G(d: D) -> i32 { // CHECK:STDOUT: } // CHECK:STDOUT: } // CHECK:STDOUT: -// CHECK:STDOUT: specific @F(constants.%T) { -// CHECK:STDOUT: %T.patt.loc9_16.2 => constants.%T.patt -// CHECK:STDOUT: %T.loc9_16.1 => constants.%T +// CHECK:STDOUT: specific @F(constants.%T.67db0b.1) { +// CHECK:STDOUT: %T.patt.loc9_16.2 => constants.%T.patt.d47011.1 +// CHECK:STDOUT: %T.loc9_16.1 => constants.%T.67db0b.1 // CHECK:STDOUT: %pattern_type => constants.%pattern_type.51d1c4.1 // CHECK:STDOUT: %x.param_patt.loc9_25.2 => constants.%x.param_patt.91d // CHECK:STDOUT: %x.patt.loc9_25.2 => constants.%x.patt.260 // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: specific @F(constants.%D) { -// CHECK:STDOUT: %T.patt.loc9_16.2 => constants.%T.patt +// CHECK:STDOUT: %T.patt.loc9_16.2 => constants.%T.patt.d47011.1 // CHECK:STDOUT: %T.loc9_16.1 => constants.%D // CHECK:STDOUT: %pattern_type => constants.%pattern_type.d8d // CHECK:STDOUT: %x.param_patt.loc9_25.2 => constants.%x.param_patt.23b @@ -427,13 +481,13 @@ fn G(d: D) -> i32 { // CHECK:STDOUT: %.loc10_11.3 => constants.%inst.splice_block.c9b // CHECK:STDOUT: %.loc10_11.4 => constants.%i32 // CHECK:STDOUT: %.loc10_11.5 => invalid -// CHECK:STDOUT: %.loc10_17.7 => constants.%inst.splice_block.edd -// CHECK:STDOUT: %.loc10_17.8 => -// CHECK:STDOUT: %.loc10_17.9 => -// CHECK:STDOUT: %.loc10_17.10 => -// CHECK:STDOUT: %.loc10_17.11 => -// CHECK:STDOUT: %.loc10_17.12 => -// CHECK:STDOUT: %.loc10_17.13 => -// CHECK:STDOUT: %.loc10_17.14 => +// CHECK:STDOUT: %.loc10_17.7 => constants.%inst.splice_block.2ae +// CHECK:STDOUT: %.loc10_17.8 => +// CHECK:STDOUT: %.loc10_17.9 => invalid +// CHECK:STDOUT: %.loc10_17.10 => constants.%inst.splice_block.3b5 +// CHECK:STDOUT: %.loc10_17.11 => constants.%i32 +// CHECK:STDOUT: %.loc10_17.12 => invalid +// CHECK:STDOUT: %.loc10_17.13 => constants.%inst.specific_inst +// CHECK:STDOUT: %.loc10_17.14 => invalid // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/impl/access.carbon b/toolchain/check/testdata/impl/access.carbon new file mode 100644 index 000000000000..f83c6da9be15 --- /dev/null +++ b/toolchain/check/testdata/impl/access.carbon @@ -0,0 +1,129 @@ +// 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-FILE: toolchain/testing/testdata/min_prelude/int.carbon +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/impl/access.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/impl/access.carbon + +// --- shared.carbon +library "[[@TEST_NAME]]"; + +interface I { fn Get(self: Self) -> i32; } +interface J { fn Get(self: Self) -> i32; } +interface K(T: type) { fn Get(t: T) -> i32; } + +// --- impl_in_class_valid_access.carbon +library "[[@TEST_NAME]]"; +import library "shared"; + +class A { + private var n: i32; + impl A as I { + fn Get(self: Self) -> i32 { + return self.n; + } + } + + impl as K(A) { + fn Get(t: A) -> i32 { + return t.n; + } + } +} + +// --- fail_impl_in_other_class1.carbon +library "[[@TEST_NAME]]"; +import library "shared"; + +class B { + private var m: i32; +} + +class A { + private var n: i32; + + impl B as I { + fn Get(self: B) -> i32 { + // CHECK:STDERR: fail_impl_in_other_class1.carbon:[[@LINE+7]]:20: error: cannot access private member `m` of type `B` [ClassInvalidMemberAccess] + // CHECK:STDERR: return self.m; + // CHECK:STDERR: ^~~~~~ + // CHECK:STDERR: fail_impl_in_other_class1.carbon:[[@LINE-11]]:17: note: declared here [ClassMemberDeclaration] + // CHECK:STDERR: private var m: i32; + // CHECK:STDERR: ^~~~~~ + // CHECK:STDERR: + return self.m; + } + } +} + +// --- fail_impl_in_other_class2.carbon +library "[[@TEST_NAME]]"; +import library "shared"; + +class B { + private var m: i32; +} + +class A { + private var n: i32; + + impl as K(B) { + fn Get(t: B) -> i32 { + // CHECK:STDERR: fail_impl_in_other_class2.carbon:[[@LINE+7]]:20: error: cannot access private member `m` of type `B` [ClassInvalidMemberAccess] + // CHECK:STDERR: return t.m; + // CHECK:STDERR: ^~~ + // CHECK:STDERR: fail_impl_in_other_class2.carbon:[[@LINE-11]]:17: note: declared here [ClassMemberDeclaration] + // CHECK:STDERR: private var m: i32; + // CHECK:STDERR: ^~~~~~ + // CHECK:STDERR: + return t.m; + } + } +} + +// --- fail_impl_outside_class1.carbon +library "[[@TEST_NAME]]"; +import library "shared"; + +class A { + private var n: i32; +} + +impl A as J { + fn Get(self: Self) -> i32 { + // CHECK:STDERR: fail_impl_outside_class1.carbon:[[@LINE+7]]:16: error: cannot access private member `n` of type `A` [ClassInvalidMemberAccess] + // CHECK:STDERR: return self.n; + // CHECK:STDERR: ^~~~~~ + // CHECK:STDERR: fail_impl_outside_class1.carbon:[[@LINE-8]]:17: note: declared here [ClassMemberDeclaration] + // CHECK:STDERR: private var n: i32; + // CHECK:STDERR: ^~~~~~ + // CHECK:STDERR: + return self.n; + } +} + +// --- fail_impl_outside_class2.carbon +library "[[@TEST_NAME]]"; +import library "shared"; + +class B { + private var m: i32; +} + +impl B as J { + fn Get(self: B) -> i32 { + // CHECK:STDERR: fail_impl_outside_class2.carbon:[[@LINE+7]]:16: error: cannot access private member `m` of type `B` [ClassInvalidMemberAccess] + // CHECK:STDERR: return self.m; + // CHECK:STDERR: ^~~~~~ + // CHECK:STDERR: fail_impl_outside_class2.carbon:[[@LINE-8]]:17: note: declared here [ClassMemberDeclaration] + // CHECK:STDERR: private var m: i32; + // CHECK:STDERR: ^~~~~~ + // CHECK:STDERR: + return self.m; + } +}