Skip to content

Commit 3664f59

Browse files
authored
Merge pull request #42372 from skrtks/clang-nonmutating-attribute
Clang nonmutating attribute
2 parents 4bf0581 + cd48565 commit 3664f59

File tree

8 files changed

+137
-22
lines changed

8 files changed

+137
-22
lines changed

include/swift/AST/DiagnosticsClangImporter.def

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,15 @@ WARNING(import_multiple_mainactor_attr,none,
106106
"this attribute for global actor '%0' is invalid; the declaration already has attribute for global actor '%1'",
107107
(StringRef, StringRef))
108108

109+
WARNING(contradicting_mutation_attrs,none,
110+
"attribute 'nonmutating' is ignored when combined with attribute 'mutating'", ())
111+
112+
WARNING(nonmutating_without_const,none,
113+
"attribute 'nonmutating' has no effect on non-const method", ())
114+
115+
WARNING(nonmutating_without_mutable_fields,none,
116+
"attribute 'nonmutating' has no effect without any mutable fields", ())
117+
109118
ERROR(module_map_not_found, none, "module map file '%0' not found", (StringRef))
110119

111120
NOTE(macro_not_imported_unsupported_operator, none, "operator not supported in macro arithmetic", ())

include/swift/ClangImporter/ClangImporter.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,8 @@ class ClangImporter final : public ClangModuleLoader {
516516

517517
bool isCXXMethodMutating(const clang::CXXMethodDecl *method) override;
518518

519+
bool isAnnotatedWith(const clang::CXXMethodDecl *method, StringRef attr);
520+
519521
/// Find the lookup table that corresponds to the given Clang module.
520522
///
521523
/// \param clangModule The module, or null to indicate that we're talking

lib/ClangImporter/ClangImporter.cpp

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5656,18 +5656,20 @@ ClangImporter::getCXXFunctionTemplateSpecialization(SubstitutionMap subst,
56565656

56575657
bool ClangImporter::isCXXMethodMutating(const clang::CXXMethodDecl *method) {
56585658
return isa<clang::CXXConstructorDecl>(method) || !method->isConst() ||
5659-
// method->getParent()->hasMutableFields() ||
5660-
// FIXME(rdar://91961524): figure out a way to handle mutable fields
5661-
// without breaking classes from the C++ standard library (e.g.
5662-
// `std::string` which has a mutable member in old libstdc++ version
5663-
// used on CentOS 7)
5664-
(method->hasAttrs() &&
5665-
llvm::any_of(method->getAttrs(), [](clang::Attr *a) {
5666-
if (auto swiftAttr = dyn_cast<clang::SwiftAttrAttr>(a)) {
5667-
return swiftAttr->getAttribute() == "mutating";
5668-
}
5669-
return false;
5670-
}));
5659+
(method->getParent()->hasMutableFields() &&
5660+
!isAnnotatedWith(method, "nonmutating")) ||
5661+
isAnnotatedWith(method, "mutating");
5662+
}
5663+
5664+
bool ClangImporter::isAnnotatedWith(const clang::CXXMethodDecl *method,
5665+
StringRef attr) {
5666+
return method->hasAttrs() &&
5667+
llvm::any_of(method->getAttrs(), [attr](clang::Attr *a) {
5668+
if (auto swiftAttr = dyn_cast<clang::SwiftAttrAttr>(a)) {
5669+
return swiftAttr->getAttribute() == attr;
5670+
}
5671+
return false;
5672+
});
56715673
}
56725674

56735675
SwiftLookupTable *

lib/ClangImporter/ImportDecl.cpp

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8768,12 +8768,13 @@ SourceFile &ClangImporter::Implementation::getClangSwiftAttrSourceFile(
87688768
}
87698769

87708770
bool swift::importer::isMainActorAttr(const clang::SwiftAttrAttr *swiftAttr) {
8771-
if (swiftAttr->getAttribute() == "@MainActor" ||
8772-
swiftAttr->getAttribute() == "@UIActor") {
8773-
return true;
8774-
}
8771+
return swiftAttr->getAttribute() == "@MainActor" ||
8772+
swiftAttr->getAttribute() == "@UIActor";
8773+
}
87758774

8776-
return false;
8775+
bool swift::importer::isMutabilityAttr(const clang::SwiftAttrAttr *swiftAttr) {
8776+
return swiftAttr->getAttribute() == "mutating" ||
8777+
swiftAttr->getAttribute() == "nonmutating";
87778778
}
87788779

87798780
void
@@ -8792,7 +8793,8 @@ ClangImporter::Implementation::importSwiftAttrAttributes(Decl *MappedDecl) {
87928793
if (maybeDefinition.getValue())
87938794
ClangDecl = cast<clang::NamedDecl>(maybeDefinition.getValue());
87948795

8795-
Optional<const clang::SwiftAttrAttr *> SeenMainActorAttr;
8796+
Optional<const clang::SwiftAttrAttr *> seenMainActorAttr;
8797+
Optional<const clang::SwiftAttrAttr *> seenMutabilityAttr;
87968798
PatternBindingInitializer *initContext = nullptr;
87978799

87988800
auto importAttrsFromDecl = [&](const clang::NamedDecl *ClangDecl) {
@@ -8803,26 +8805,63 @@ ClangImporter::Implementation::importSwiftAttrAttributes(Decl *MappedDecl) {
88038805
// FIXME: Hard-code @MainActor and @UIActor, because we don't have a
88048806
// point at which to do name lookup for imported entities.
88058807
if (isMainActorAttr(swiftAttr)) {
8806-
if (SeenMainActorAttr) {
8808+
if (seenMainActorAttr) {
88078809
// Cannot add main actor annotation twice. We'll keep the first
88088810
// one and raise a warning about the duplicate.
88098811
HeaderLoc attrLoc(swiftAttr->getLocation());
88108812
diagnose(attrLoc, diag::import_multiple_mainactor_attr,
88118813
swiftAttr->getAttribute(),
8812-
SeenMainActorAttr.getValue()->getAttribute());
8814+
seenMainActorAttr.getValue()->getAttribute());
88138815
continue;
88148816
}
88158817

88168818
if (Type mainActorType = SwiftContext.getMainActorType()) {
88178819
auto typeExpr = TypeExpr::createImplicit(mainActorType, SwiftContext);
88188820
auto attr = CustomAttr::create(SwiftContext, SourceLoc(), typeExpr);
88198821
MappedDecl->getAttrs().add(attr);
8820-
SeenMainActorAttr = swiftAttr;
8822+
seenMainActorAttr = swiftAttr;
88218823
}
88228824

88238825
continue;
88248826
}
88258827

8828+
if (isMutabilityAttr(swiftAttr)) {
8829+
8830+
// Check if 'nonmutating' attr is applicable
8831+
if (swiftAttr->getAttribute() == "nonmutating") {
8832+
if (auto *method = dyn_cast<clang::CXXMethodDecl>(ClangDecl)) {
8833+
if (!method->isConst()) {
8834+
diagnose(HeaderLoc(swiftAttr->getLocation()),
8835+
diag::nonmutating_without_const);
8836+
}
8837+
if (!method->getParent()->hasMutableFields()) {
8838+
diagnose(HeaderLoc(swiftAttr->getLocation()),
8839+
diag::nonmutating_without_mutable_fields);
8840+
}
8841+
}
8842+
}
8843+
8844+
// Check for contradicting mutability attr
8845+
if (seenMutabilityAttr) {
8846+
StringRef seenAttribute =
8847+
seenMutabilityAttr.getValue()->getAttribute();
8848+
if ((seenAttribute == "nonmutating" &&
8849+
swiftAttr->getAttribute() == "mutating") ||
8850+
(seenAttribute == "mutating" &&
8851+
swiftAttr->getAttribute() == "nonmutating")) {
8852+
const clang::SwiftAttrAttr *nonmutatingAttr =
8853+
seenAttribute == "nonmutating" ? seenMutabilityAttr.getValue()
8854+
: swiftAttr;
8855+
8856+
diagnose(HeaderLoc(nonmutatingAttr->getLocation()),
8857+
diag::contradicting_mutation_attrs);
8858+
continue;
8859+
}
8860+
}
8861+
8862+
seenMutabilityAttr = swiftAttr;
8863+
}
8864+
88268865
// Hard-code @actorIndependent, until Objective-C clients start
88278866
// using nonisolated.
88288867
if (swiftAttr->getAttribute() == "@actorIndependent") {

lib/ClangImporter/ImporterImpl.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1849,6 +1849,9 @@ class SwiftNameLookupExtension : public clang::ModuleFileExtension {
18491849
/// actor.
18501850
bool isMainActorAttr(const clang::SwiftAttrAttr *swiftAttr);
18511851

1852+
/// Determines whether the given swift_attr controls mutability
1853+
bool isMutabilityAttr(const clang::SwiftAttrAttr *swiftAttr);
1854+
18521855
/// Apply an attribute to a function type.
18531856
static inline Type applyToFunctionType(
18541857
Type type, llvm::function_ref<ASTExtInfo(ASTExtInfo)> transform) {

test/Interop/Cxx/class/Inputs/mutability-annotations.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,37 @@ struct HasConstMethodAnnotatedAsMutating {
1515
}
1616
};
1717

18+
struct HasMutableProperty {
19+
mutable int a;
20+
int b;
21+
22+
int annotatedNonMutating() const __attribute__((__swift_attr__("nonmutating"))) {
23+
return b;
24+
}
25+
26+
int noAnnotation() const { return b; }
27+
28+
// expected-warning@+1 {{attribute 'nonmutating' is ignored when combined with attribute 'mutating'}}
29+
int contradictingAnnotations() const __attribute__((__swift_attr__("nonmutating"))) __attribute__((__swift_attr__("mutating"))) {
30+
return b;
31+
}
32+
33+
int duplicateAnnotations() const __attribute__((__swift_attr__("nonmutating"))) __attribute__((__swift_attr__("nonmutating"))) {
34+
return b;
35+
}
36+
};
37+
38+
struct NoMutableProperty {
39+
int a;
40+
41+
// expected-warning@+1 {{attribute 'nonmutating' has no effect without any mutable fields}}
42+
int isConst() const __attribute__((__swift_attr__("nonmutating"))) {
43+
return a;
44+
}
45+
46+
// expected-warning@+2 {{attribute 'nonmutating' has no effect without any mutable fields}}
47+
// expected-warning@+1 {{attribute 'nonmutating' has no effect on non-const method}}
48+
int nonConst() __attribute__((__swift_attr__("nonmutating"))) { return a; }
49+
};
50+
1851
#endif // TEST_INTEROP_CXX_CLASS_INPUTS_MUTABILITY_ANNOTATIONS_H

test/Interop/Cxx/class/mutability-annotations-module-interface.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,18 @@
55
// CHECK: mutating func annotatedMutatingWithOtherAttrs() -> Int32
66
// CHECK: var a: Int32
77
// CHECK: }
8+
9+
// CHECK: struct HasMutableProperty {
10+
// CHECK: func annotatedNonMutating() -> Int32
11+
// CHECK: mutating func noAnnotation() -> Int32
12+
// CHECK: mutating func contradictingAnnotations() -> Int32
13+
// CHECK: func duplicateAnnotations() -> Int32
14+
// CHECK: var a: Int32
15+
// CHECK: var b: Int32
16+
// CHECK: }
17+
18+
// CHECK: struct NoMutableProperty {
19+
// CHECK: func isConst() -> Int32
20+
// CHECK: mutating func nonConst() -> Int32
21+
// CHECK: var a: Int32
22+
// CHECK: }
Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
1-
// RUN: %target-typecheck-verify-swift -I %S/Inputs -enable-experimental-cxx-interop
1+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck -I %S/Inputs %s -enable-experimental-cxx-interop -verify -verify-additional-file %S/Inputs/mutability-annotations.h
22

33
import MutabilityAnnotations
44

55
let obj = HasConstMethodAnnotatedAsMutating(a: 42) // expected-note {{change 'let' to 'var' to make it mutable}}
66
let i = obj.annotatedMutating() // expected-error {{cannot use mutating member on immutable value: 'obj' is a 'let' constant}}
7+
8+
let objWMutableProperty = HasMutableProperty(a: 42, b: 21) // expected-note {{change 'let' to 'var' to make it mutable}}
9+
// expected-note@-1 {{change 'let' to 'var' to make it mutable}}
10+
11+
let _ = objWMutableProperty.annotatedNonMutating()
12+
let _ = objWMutableProperty.noAnnotation() // expected-error {{cannot use mutating member on immutable value: 'objWMutableProperty' is a 'let' constant}}
13+
let _ = objWMutableProperty.contradictingAnnotations() // expected-error {{cannot use mutating member on immutable value: 'objWMutableProperty' is a 'let' constant}}
14+
let _ = objWMutableProperty.duplicateAnnotations()
15+
16+
let objWithoutMutableProperty = NoMutableProperty(a: 42) // expected-note {{change 'let' to 'var' to make it mutable}}
17+
let _ = objWithoutMutableProperty.isConst()
18+
let _ = objWithoutMutableProperty.nonConst() // expected-error {{cannot use mutating member on immutable value: 'objWithoutMutableProperty' is a 'let' constant}}

0 commit comments

Comments
 (0)