Skip to content

Commit 60fe6aa

Browse files
committed
[cxx-interop] Add a "nonmutating" swift_attr attribute
Previously, the clang importer marked all const methods as mutating whenever a C++ record had mutable fields. This change allows overriding this behavior by using the "nonmutating" swift_attr attribute. Fixes SR-15907.
1 parent 54e82a0 commit 60fe6aa

File tree

5 files changed

+42
-13
lines changed

5 files changed

+42
-13
lines changed

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 *

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,15 @@ 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+
1829
#endif // TEST_INTEROP_CXX_CLASS_INPUTS_MUTABILITY_ANNOTATIONS_H

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,10 @@
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: var a: Int32
13+
// CHECK: var b: Int32
14+
// CHECK: }
Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
1-
// RUN: %target-typecheck-verify-swift -I %S/Inputs -enable-experimental-cxx-interop
1+
// RUN: %target-typecheck-verify-swift -I %S/Inputs -enable-experimental-cxx-interop \
2+
// RUN: -verify -verify-additional-file %S/Inputs/mutability-annotations.h -verify-ignore-unknown
23

34
import MutabilityAnnotations
45

56
let obj = HasConstMethodAnnotatedAsMutating(a: 42) // expected-note {{change 'let' to 'var' to make it mutable}}
67
let i = obj.annotatedMutating() // expected-error {{cannot use mutating member on immutable value: 'obj' is a 'let' constant}}
8+
9+
let objWMutableProperty = HasMutableProperty(a: 42, b: 21) // expected-note {{change 'let' to 'var' to make it mutable}}
10+
// expected-note@-1 {{change 'let' to 'var' to make it mutable}}
11+
12+
let _ = objWMutableProperty.annotatedNonMutating()
13+
let _ = objWMutableProperty.noAnnotation() // expected-error {{cannot use mutating member on immutable value: 'objWMutableProperty' is a 'let' constant}}

0 commit comments

Comments
 (0)