Skip to content

Commit 4167e2f

Browse files
bro4allOmar Habra
andauthored
[cxx-interop] Computed properties from getters and setters (#40842)
* fixing setters * adding tests * Removing extras * fixing tests * Better naming for properties * Cleanup * Add tests * Clang format it * more refactoring and some more tests * More tests and more fixes * Updating tests: fixing other tests to work with new property importing * Fix the two asserts. Move things around. Remove createImported. Move CXXMethodBridging to it's own header. * General updates: Fixing tests, adding radars to follow issues that cna be a started issues on the project. Also Factoring out MethodBridging. * Fixing Comments left on the PR: General formatting. * Fixing tests, and general updates for formatting * removing extras and passing this on swift. Co-authored-by: Omar Habra <[email protected]>
1 parent 4c33aab commit 4167e2f

File tree

8 files changed

+923
-62
lines changed

8 files changed

+923
-62
lines changed
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
#ifndef SWIFT_CXXMETHODBRIDGING_H
2+
#define SWIFT_CXXMETHODBRIDGING_H
3+
4+
#include "swift/AST/Decl.h"
5+
#include "clang/AST/DeclCXX.h"
6+
#include "llvm/ADT/StringRef.h"
7+
8+
#include <string>
9+
namespace swift {
10+
struct CXXMethodBridging {
11+
enum class Kind { unkown, getter, setter, subscript };
12+
13+
enum class NameKind { unkown, snake, lower, camel, title };
14+
15+
CXXMethodBridging(const clang::CXXMethodDecl *method) : method(method) {}
16+
17+
Kind classify() {
18+
if (nameIsBlacklist())
19+
return Kind::unkown;
20+
21+
// this should be handled as snake case. See: rdar://89453010
22+
// case. In the future we could
23+
// import these too, though.
24+
auto nameKind = classifyNameKind();
25+
if (nameKind != NameKind::title && nameKind != NameKind::camel &&
26+
nameKind != NameKind::lower)
27+
return Kind::unkown;
28+
29+
if (getClangName().startswith_insensitive("set")) {
30+
// Setters only have one parameter.
31+
if (method->getNumParams() != 1)
32+
return Kind::unkown;
33+
34+
// rdar://89453106 (We need to handle imported properties that return a
35+
// reference)
36+
if (method->getParamDecl(0)->getType()->isReferenceType())
37+
return Kind::unkown;
38+
39+
return Kind::setter;
40+
}
41+
42+
// Getters and subscripts cannot return void.
43+
if (method->getReturnType()->isVoidType())
44+
return Kind::unkown;
45+
46+
if (getClangName().startswith_insensitive("get")) {
47+
// Getters cannot take arguments.
48+
if (method->getNumParams() != 0)
49+
return Kind::unkown;
50+
51+
// rdar://89453106 (We need to handle imported properties that return a
52+
// reference)
53+
if (method->getReturnType()->isReferenceType())
54+
return Kind::unkown;
55+
56+
return Kind::getter;
57+
}
58+
59+
// rdar://89453187 (Add subscripts clarification to CXXMethod Bridging to
60+
// clean up importDecl)
61+
return Kind::unkown;
62+
}
63+
64+
NameKind classifyNameKind() {
65+
bool allLower = llvm::all_of(getClangName(), islower);
66+
67+
if (getClangName().empty())
68+
return NameKind::unkown;
69+
70+
if (getClangName().contains('_'))
71+
return allLower ? NameKind::snake : NameKind::unkown;
72+
73+
if (allLower)
74+
return NameKind::lower;
75+
76+
return islower(getClangName().front()) ? NameKind::camel : NameKind::title;
77+
}
78+
79+
llvm::StringRef getClangName() {
80+
if (!method->getDeclName().isIdentifier())
81+
return "";
82+
83+
return method->getName();
84+
}
85+
86+
// this should be handled as snake case. See: rdar://89453010
87+
std::string importNameAsCamelCaseName() {
88+
std::string output;
89+
auto kind = classify();
90+
if (kind == Kind::getter || kind == Kind::setter) {
91+
output = getClangName().drop_front(3).str();
92+
} else {
93+
output = getClangName().str();
94+
}
95+
96+
if (output.empty())
97+
return output;
98+
99+
// No work to do.
100+
if (classifyNameKind() == NameKind::lower)
101+
return output;
102+
103+
// The first character is always lowercase.
104+
output.front() = std::tolower(output.front());
105+
106+
// We already lowercased the first element, so start at one. Look at the
107+
// current element and the next one. To handle cases like UTF8String, start
108+
// making all the uppercase characters lower, until we see an upper case
109+
// character followed by a lower case character (i.e., "St").
110+
for (size_t i = 1; i < output.size(); i++) {
111+
size_t next = i + 1;
112+
113+
// If we see two upper case characters (or an upper case character and a
114+
// number) make the current character lower case.
115+
if (std::isupper(output[i]) &&
116+
(std::isupper(output[next]) || std::isdigit(output[next]))) {
117+
output[i] = std::tolower(output[i]);
118+
// If we found an upper case character followed by a lower case
119+
// character, we went far enough. We're done.
120+
} else if (std::isupper(output[i]) && std::islower(output[next])) {
121+
break;
122+
// If we got to the end of the string, we're done.
123+
} else if (std::isupper(output[i]) && next + 1 > output.size()) {
124+
output[i] = std::tolower(output[i]);
125+
break;
126+
}
127+
}
128+
129+
return output;
130+
}
131+
132+
std::string importNameAsTitleCaseName() {
133+
auto output = importNameAsCamelCaseName();
134+
output.front() = std::toupper(output.front());
135+
return output;
136+
}
137+
138+
private:
139+
const clang::CXXMethodDecl *method = nullptr;
140+
141+
bool nameIsBlacklist() {
142+
auto loweredName = getClangName().lower();
143+
// Names that start with "get" or "set" but aren't getters or setters.
144+
return loweredName == "getter" || loweredName == "setter" ||
145+
loweredName == "get" || loweredName == "set";
146+
}
147+
};
148+
} // namespace swift
149+
#endif // SWIFT_CXXMETHODBRIDGING_H

0 commit comments

Comments
 (0)