Skip to content

Commit 3b6d202

Browse files
zygoloiddanakj
andauthored
Implement support for mixed-access overload sets. (#6137)
This turns out to be quite important, as several important standard library types (such as `std::string`) have mixed-access overload sets for their constructors as an implementation detail. The overall approach here is: - Use the most permissive access to determine the access of the overload set itself. This affects whether name lookup finds the member name at all. - After overload resolution, re-check the access of the selected member, if it's protected or private. --------- Co-authored-by: Dana Jansens <[email protected]>
1 parent 83ba714 commit 3b6d202

File tree

11 files changed

+238
-332
lines changed

11 files changed

+238
-332
lines changed

toolchain/check/cpp/import.cpp

Lines changed: 35 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -508,18 +508,6 @@ static auto GetDeclContext(Context& context, SemIR::NameScopeId scope_id)
508508
context.clang_decls().Get(scope_clang_decl_context_id).key.decl);
509509
}
510510

511-
// Looks up for constructors in the class scope and returns the lookup result.
512-
static auto ClangConstructorLookup(Context& context,
513-
SemIR::NameScopeId scope_id)
514-
-> clang::DeclContextLookupResult {
515-
const SemIR::NameScope& scope = context.name_scopes().Get(scope_id);
516-
517-
clang::Sema& sema = context.clang_sema();
518-
clang::Decl* decl =
519-
context.clang_decls().Get(scope.clang_decl_context_id()).key.decl;
520-
return sema.LookupConstructors(cast<clang::CXXRecordDecl>(decl));
521-
}
522-
523511
// Returns true if the given Clang declaration is the implicit injected class
524512
// name within the class.
525513
static auto IsDeclInjectedClassName(Context& context,
@@ -2074,12 +2062,14 @@ static auto LookupBuiltinTypes(Context& context, SemIR::LocId loc_id,
20742062

20752063
auto ImportCppOverloadSet(Context& context, SemIR::NameScopeId scope_id,
20762064
SemIR::NameId name_id,
2077-
const clang::UnresolvedSet<4>& overload_set)
2065+
clang::CXXRecordDecl* naming_class,
2066+
clang::UnresolvedSet<4>&& overload_set)
20782067
-> SemIR::InstId {
20792068
SemIR::CppOverloadSetId overload_set_id = context.cpp_overload_sets().Add(
20802069
SemIR::CppOverloadSet{.name_id = name_id,
20812070
.parent_scope_id = scope_id,
2082-
.candidate_functions = overload_set});
2071+
.naming_class = naming_class,
2072+
.candidate_functions = std::move(overload_set)});
20832073

20842074
auto overload_set_inst_id =
20852075
// TODO: Add a location.
@@ -2093,65 +2083,65 @@ auto ImportCppOverloadSet(Context& context, SemIR::NameScopeId scope_id,
20932083
return overload_set_inst_id;
20942084
}
20952085

2096-
// Gets the access for an overloaded function set. Returns std::nullopt
2097-
// if the access is not the same for all functions in the overload set.
2098-
// TODO: Fix to support functions with different access levels.
2099-
static auto GetOverloadSetAccess(Context& context, SemIR::LocId loc_id,
2100-
const clang::UnresolvedSet<4>& overload_set)
2101-
-> std::optional<SemIR::AccessKind> {
2086+
// Gets the best access for an overloaded function set. This is the access that
2087+
// we use for the overload set as a whole. More fine-grained checking is done
2088+
// after overload resolution.
2089+
static auto GetOverloadSetAccess(const clang::UnresolvedSet<4>& overload_set)
2090+
-> SemIR::AccessKind {
21022091
clang::AccessSpecifier access = overload_set.begin().getAccess();
21032092
for (auto it = overload_set.begin() + 1; it != overload_set.end(); ++it) {
2104-
if (it.getAccess() != access) {
2105-
context.TODO(loc_id, "Unsupported: Overloaded set with mixed access");
2106-
return std::nullopt;
2093+
CARBON_CHECK(
2094+
(it.getAccess() == clang::AS_none) == (access == clang::AS_none),
2095+
"Unexpected mixture of members and non-members");
2096+
if (it.getAccess() < access) {
2097+
access = it->getAccess();
21072098
}
21082099
}
21092100
return MapAccess(access);
21102101
}
21112102

21122103
// Imports an overload set from Clang to Carbon and adds the name into the
21132104
// `NameScope`.
2114-
static auto ImportOverloadSetIntoScope(
2115-
Context& context, SemIR::LocId loc_id, SemIR::NameScopeId scope_id,
2116-
SemIR::NameId name_id, const clang::UnresolvedSet<4>& overload_set)
2105+
static auto ImportOverloadSetIntoScope(Context& context,
2106+
SemIR::NameScopeId scope_id,
2107+
SemIR::NameId name_id,
2108+
clang::CXXRecordDecl* naming_class,
2109+
clang::UnresolvedSet<4>&& overload_set)
21172110
-> SemIR::ScopeLookupResult {
2118-
std::optional<SemIR::AccessKind> access_kind =
2119-
GetOverloadSetAccess(context, loc_id, overload_set);
2120-
if (!access_kind.has_value()) {
2121-
return SemIR::ScopeLookupResult::MakeError();
2122-
}
2123-
2124-
SemIR::InstId inst_id =
2125-
ImportCppOverloadSet(context, scope_id, name_id, overload_set);
2126-
AddNameToScope(context, scope_id, name_id, access_kind.value(), inst_id);
2111+
SemIR::AccessKind access_kind = GetOverloadSetAccess(overload_set);
2112+
SemIR::InstId inst_id = ImportCppOverloadSet(
2113+
context, scope_id, name_id, naming_class, std::move(overload_set));
2114+
AddNameToScope(context, scope_id, name_id, access_kind, inst_id);
21272115
return SemIR::ScopeLookupResult::MakeWrappedLookupResult(inst_id,
2128-
access_kind.value());
2116+
access_kind);
21292117
}
21302118

21312119
// Imports the constructors for a given class name. The found constructors are
21322120
// imported as part of an overload set into the scope. Currently copy/move
21332121
// constructors are not imported.
2134-
static auto ImportConstructorsIntoScope(Context& context, SemIR::LocId loc_id,
2122+
static auto ImportConstructorsIntoScope(Context& context,
21352123
SemIR::NameScopeId scope_id,
21362124
SemIR::NameId name_id)
21372125
-> SemIR::ScopeLookupResult {
2126+
auto* naming_class =
2127+
cast<clang::CXXRecordDecl>(GetDeclContext(context, scope_id));
21382128
clang::DeclContextLookupResult constructors_lookup =
2139-
ClangConstructorLookup(context, scope_id);
2129+
context.clang_sema().LookupConstructors(naming_class);
21402130

21412131
clang::UnresolvedSet<4> overload_set;
21422132
for (auto* decl : constructors_lookup) {
21432133
auto info = clang::getConstructorInfo(decl);
21442134
if (!info.Constructor || info.Constructor->isCopyOrMoveConstructor()) {
21452135
continue;
21462136
}
2147-
overload_set.addDecl(info.FoundDecl, info.FoundDecl->getAccess());
2137+
overload_set.addDecl(info.FoundDecl, info.FoundDecl.getAccess());
21482138
}
21492139
if (overload_set.empty()) {
21502140
return SemIR::ScopeLookupResult::MakeNotFound();
21512141
}
21522142

2153-
return ImportOverloadSetIntoScope(context, loc_id, scope_id, name_id,
2154-
overload_set);
2143+
return ImportOverloadSetIntoScope(context, scope_id, name_id, naming_class,
2144+
std::move(overload_set));
21552145
}
21562146

21572147
// Imports a builtin type from Clang to Carbon and adds the name into the
@@ -2207,8 +2197,9 @@ auto ImportNameFromCpp(Context& context, SemIR::LocId loc_id,
22072197
lookup->getFoundDecl()->isFunctionOrFunctionTemplate())) {
22082198
clang::UnresolvedSet<4> overload_set;
22092199
overload_set.append(lookup->begin(), lookup->end());
2210-
return ImportOverloadSetIntoScope(context, loc_id, scope_id, name_id,
2211-
overload_set);
2200+
return ImportOverloadSetIntoScope(context, scope_id, name_id,
2201+
lookup->getNamingClass(),
2202+
std::move(overload_set));
22122203
}
22132204
if (!lookup->isSingleResult()) {
22142205
// Clang will diagnose ambiguous lookup results for us.
@@ -2224,7 +2215,7 @@ auto ImportNameFromCpp(Context& context, SemIR::LocId loc_id,
22242215
}
22252216
if (IsDeclInjectedClassName(context, scope_id, name_id,
22262217
lookup->getFoundDecl())) {
2227-
return ImportConstructorsIntoScope(context, loc_id, scope_id, name_id);
2218+
return ImportConstructorsIntoScope(context, scope_id, name_id);
22282219
}
22292220
auto key = SemIR::ClangDeclKey::ForNonFunctionDecl(lookup->getFoundDecl());
22302221
return ImportNameDeclIntoScope(context, loc_id, scope_id, name_id, key,

toolchain/check/cpp/import.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ auto ImportCppFunctionDecl(Context& context, SemIR::LocId loc_id,
3434
// Imports an overloaded function set from Clang to Carbon.
3535
auto ImportCppOverloadSet(Context& context, SemIR::NameScopeId scope_id,
3636
SemIR::NameId name_id,
37-
const clang::UnresolvedSet<4>& overload_set)
37+
clang::CXXRecordDecl* naming_class,
38+
clang::UnresolvedSet<4>&& overload_set)
3839
-> SemIR::InstId;
3940

4041
// Looks up the given name in the Clang AST generated when importing C++ code

toolchain/check/cpp/operators.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,8 @@ auto LookupCppOperator(Context& context, SemIR::LocId loc_id, Operator op,
206206
}
207207

208208
return ImportCppOverloadSet(context, SemIR::NameScopeId::None,
209-
SemIR::NameId::CppOperator, functions);
209+
SemIR::NameId::CppOperator,
210+
/*naming_class=*/nullptr, std::move(functions));
210211
}
211212

212213
} // namespace Carbon::Check

toolchain/check/cpp/overload_resolution.cpp

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,12 @@
1111
#include "toolchain/check/cpp/import.h"
1212
#include "toolchain/check/cpp/location.h"
1313
#include "toolchain/check/cpp/type_mapping.h"
14+
#include "toolchain/check/member_access.h"
15+
#include "toolchain/check/name_lookup.h"
1416
#include "toolchain/diagnostics/diagnostic_emitter.h"
1517
#include "toolchain/sem_ir/function.h"
1618
#include "toolchain/sem_ir/ids.h"
19+
#include "toolchain/sem_ir/name_scope.h"
1720
#include "toolchain/sem_ir/typed_insts.h"
1821

1922
namespace Carbon::Check {
@@ -79,6 +82,40 @@ static auto AddOverloadCandidataes(clang::Sema& sema,
7982
}
8083
}
8184

85+
// Checks whether a selected overload is accessible and diagnoses if not.
86+
static auto CheckOverloadAccess(Context& context, SemIR::LocId loc_id,
87+
const SemIR::CppOverloadSet& overload_set,
88+
clang::DeclAccessPair overload,
89+
SemIR::InstId overload_inst_id) -> void {
90+
SemIR::AccessKind member_access_kind;
91+
switch (overload->getAccess()) {
92+
case clang::AS_none:
93+
case clang::AS_public: {
94+
return;
95+
}
96+
97+
case clang::AS_protected: {
98+
member_access_kind = SemIR::AccessKind::Protected;
99+
break;
100+
}
101+
102+
case clang::AS_private: {
103+
member_access_kind = SemIR::AccessKind::Private;
104+
break;
105+
}
106+
}
107+
108+
auto name_scope_const_id = context.constant_values().Get(
109+
context.name_scopes().Get(overload_set.parent_scope_id).inst_id());
110+
SemIR::AccessKind allowed_access_kind =
111+
GetHighestAllowedAccess(context, loc_id, name_scope_const_id);
112+
CheckAccess(context, loc_id, SemIR::LocId(overload_inst_id),
113+
overload_set.name_id, member_access_kind,
114+
/*is_parent_access=*/false,
115+
{.constant_id = name_scope_const_id,
116+
.highest_allowed_access = allowed_access_kind});
117+
}
118+
82119
// Returns whether the decl is an operator member function.
83120
static auto IsOperatorMethodDecl(clang::Decl* decl) -> bool {
84121
auto* clang_method_decl = dyn_cast<clang::CXXMethodDecl>(decl);
@@ -136,12 +173,14 @@ static auto ResolveCalleeId(Context& context, SemIR::LocId loc_id,
136173
// TODO: Handle the cases when Function is null.
137174
CARBON_CHECK(best_viable_fn->Function);
138175
sema.MarkFunctionReferenced(loc, best_viable_fn->Function);
139-
SemIR::InstId result = ImportCppFunctionDecl(
176+
SemIR::InstId result_id = ImportCppFunctionDecl(
140177
context, loc_id, best_viable_fn->Function,
141178
// If this is an operator method, the first arg will be used as self.
142179
arg_exprs.size() -
143180
(IsOperatorMethodDecl(best_viable_fn->Function) ? 1 : 0));
144-
return result;
181+
CheckOverloadAccess(context, loc_id, overload_set,
182+
best_viable_fn->FoundDecl, result_id);
183+
return result_id;
145184
}
146185
case clang::OverloadingResult::OR_No_Viable_Function: {
147186
candidate_set.NoteCandidates(

toolchain/check/member_access.cpp

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,8 @@ static auto IsInstanceType(Context& context, SemIR::TypeId type_id) -> bool {
9696
return false;
9797
}
9898

99-
// Returns the highest allowed access. For example, if this returns `Protected`
100-
// then only `Public` and `Protected` accesses are allowed--not `Private`.
101-
static auto GetHighestAllowedAccess(Context& context, SemIR::LocId loc_id,
102-
SemIR::ConstantId name_scope_const_id)
99+
auto GetHighestAllowedAccess(Context& context, SemIR::LocId loc_id,
100+
SemIR::ConstantId name_scope_const_id)
103101
-> SemIR::AccessKind {
104102
SemIR::ScopeLookupResult lookup_result =
105103
LookupUnqualifiedName(context, loc_id, SemIR::NameId::SelfType,

toolchain/check/member_access.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@
1010

1111
namespace Carbon::Check {
1212

13+
// Returns the highest allowed access for members of `name_scope_const_id`. For
14+
// example, if this returns `Protected` then only `Public` and `Protected`
15+
// accesses are allowed -- not `Private`.
16+
auto GetHighestAllowedAccess(Context& context, SemIR::LocId loc_id,
17+
SemIR::ConstantId name_scope_const_id)
18+
-> SemIR::AccessKind;
19+
1320
// Creates SemIR to perform a member access with base expression `base_id` and
1421
// member name `name_id`. When `required`, failing to find the name is a
1522
// diagnosed error; otherwise, `None` is returned. Returns the result of the

toolchain/check/name_lookup.cpp

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ auto LookupNameInExactScope(Context& context, SemIR::LocId loc_id,
195195

196196
// Prints diagnostics on invalid qualified name access.
197197
static auto DiagnoseInvalidQualifiedNameAccess(
198-
Context& context, SemIR::LocId loc_id, SemIR::InstId scope_result_id,
198+
Context& context, SemIR::LocId loc_id, SemIR::LocId member_loc_id,
199199
SemIR::NameId name_id, SemIR::AccessKind access_kind, bool is_parent_access,
200200
AccessInfo access_info) -> void {
201201
auto class_type = context.insts().TryGetAs<SemIR::ClassType>(
@@ -231,7 +231,7 @@ static auto DiagnoseInvalidQualifiedNameAccess(
231231
context.emitter()
232232
.Build(loc_id, ClassInvalidMemberAccess,
233233
access_kind == SemIR::AccessKind::Private, name_id, parent_type_id)
234-
.Note(scope_result_id, ClassMemberDeclaration)
234+
.Note(member_loc_id, ClassMemberDeclaration)
235235
.Emit();
236236
}
237237

@@ -255,6 +255,17 @@ static auto IsAccessProhibited(std::optional<AccessInfo> access_info,
255255
}
256256
}
257257

258+
auto CheckAccess(Context& context, SemIR::LocId loc_id,
259+
SemIR::LocId member_loc_id, SemIR::NameId name_id,
260+
SemIR::AccessKind access_kind, bool is_parent_access,
261+
AccessInfo access_info) -> void {
262+
if (IsAccessProhibited(access_info, access_kind, is_parent_access)) {
263+
DiagnoseInvalidQualifiedNameAccess(context, loc_id, member_loc_id, name_id,
264+
access_kind, is_parent_access,
265+
access_info);
266+
}
267+
}
268+
258269
// Information regarding a prohibited access.
259270
struct ProhibitedAccessInfo {
260271
// The resulting inst of the lookup.
@@ -475,9 +486,9 @@ auto LookupQualifiedName(Context& context, SemIR::LocId loc_id,
475486

476487
// Note, `access_info` is guaranteed to have a value here, since
477488
// `prohibited_accesses` is non-empty.
478-
DiagnoseInvalidQualifiedNameAccess(context, loc_id, scope_result_id,
479-
name_id, access_kind,
480-
is_parent_access, *access_info);
489+
DiagnoseInvalidQualifiedNameAccess(
490+
context, loc_id, SemIR::LocId(scope_result_id), name_id,
491+
access_kind, is_parent_access, *access_info);
481492
}
482493
}
483494

toolchain/check/name_lookup.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,13 @@ auto LookupQualifiedName(Context& context, SemIR::LocId loc_id,
9999
auto LookupNameInCore(Context& context, SemIR::LocId loc_id,
100100
llvm::StringRef name) -> SemIR::InstId;
101101

102+
// Checks whether a name is accessible in the given access context. Produces a
103+
// diagnostic if not.
104+
auto CheckAccess(Context& context, SemIR::LocId loc_id,
105+
SemIR::LocId member_loc_id, SemIR::NameId name_id,
106+
SemIR::AccessKind access_kind, bool is_parent_access,
107+
AccessInfo access_info) -> void;
108+
102109
// Prints a diagnostic for a duplicate name.
103110
auto DiagnoseDuplicateName(Context& context, SemIR::NameId name_id,
104111
SemIR::LocId dup_def, SemIR::LocId prev_def) -> void;

0 commit comments

Comments
 (0)