Skip to content

Prevent shadowing of unavailable member impls #79094

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 30 additions & 2 deletions lib/AST/NameLookup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,16 @@ static ConstructorComparison compareConstructors(ConstructorDecl *ctor1,
return ConstructorComparison::Same;
}

static bool isMemberImplementation(ValueDecl *VD) {
return VD->isObjCMemberImplementation();
}
static bool isMemberImplementation(OperatorDecl *VD) {
return false;
}
static bool isMemberImplementation(PrecedenceGroupDecl *VD) {
return false;
}

/// Given a set of declarations whose names and interface types have matched,
/// figure out which of these declarations have been shadowed by others.
template <typename T>
Expand All @@ -412,7 +422,8 @@ static void recordShadowedDeclsAfterTypeMatch(
for (unsigned firstIdx : indices(decls)) {
auto firstDecl = decls[firstIdx];
auto firstModule = firstDecl->getModuleContext();
bool firstTopLevel = firstDecl->getDeclContext()->isModuleScopeContext();
auto firstDC = firstDecl->getDeclContext();
bool firstTopLevel = firstDC->isModuleScopeContext();

auto name = firstDecl->getBaseName();

Expand Down Expand Up @@ -454,7 +465,8 @@ static void recordShadowedDeclsAfterTypeMatch(
// Determine whether one module takes precedence over another.
auto secondDecl = decls[secondIdx];
auto secondModule = secondDecl->getModuleContext();
bool secondTopLevel = secondDecl->getDeclContext()->isModuleScopeContext();
auto secondDC = secondDecl->getDeclContext();
bool secondTopLevel = secondDC->isModuleScopeContext();
bool secondPrivate = isPrivateImport(secondModule);

// For member types, we skip most of the below rules. Instead, we allow
Expand Down Expand Up @@ -538,6 +550,22 @@ static void recordShadowedDeclsAfterTypeMatch(
}
}

// Member implementations are usually filtered out by access control.
// They're sometimes visible in contexts that can directly access storage,
// though, and there they should shadow the matching imported declaration.
if (firstDC != secondDC
&& firstDC->getImplementedObjCContext() ==
secondDC->getImplementedObjCContext()) {
if (isMemberImplementation(firstDecl) && secondDecl->hasClangNode()) {
shadowed.insert(secondDecl);
continue;
}
if (isMemberImplementation(secondDecl) && firstDecl->hasClangNode()) {
shadowed.insert(firstDecl);
continue;
}
}

// Swift 4 compatibility hack: Don't shadow properties defined in
// extensions of generic types with properties defined elsewhere.
// This is due to the fact that in Swift 4, we only gave custom overload
Expand Down
10 changes: 10 additions & 0 deletions test/decl/ext/Inputs/objc_implementation_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,13 @@

@end

@interface ObjCPropertyTest : NSObject

@property (readonly) int prop1;
@property (readwrite) int prop2;

- (instancetype)init;

- (void)doSomething;

@end
30 changes: 30 additions & 0 deletions test/decl/ext/objc_implementation_direct_to_storage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// RUN: %target-typecheck-verify-swift -Xcc -fmodule-map-file=%S/Inputs/objc_implementation_private.modulemap -enable-experimental-feature ObjCImplementation -target %target-stable-abi-triple -debug-diagnostic-names
// REQUIRES: objc_interop
// REQUIRES: swift_feature_ObjCImplementation

import objc_implementation_internal

@available(*, unavailable)
@objc @implementation extension ObjCPropertyTest {
// FIXME: Shouldn't this be on the `@available` above?
// expected-note@+1 {{'prop1' has been explicitly marked unavailable here}}
let prop1: Int32

// expected-note@+1 2 {{'prop2' has been explicitly marked unavailable here}}
var prop2: Int32 {
didSet {
_ = prop2 // expected-error {{'prop2' is unavailable}}
}
}

override init() {
self.prop1 = 1 // expected-error {{'prop1' is unavailable}}
self.prop2 = 2 // expected-error {{'prop2' is unavailable}}
super.init()
}

func doSomething() {
_ = self.prop1
_ = self.prop2
}
}