Diagnose possible enum common-prefix mistakes

Clang Importer strips prefixes from enum and option set case names. The logic to do this computes a common prefix from the type name and all non-deprecated case names (to oversimplify), which means that adding, removing, or changing one case can change the prefix that is removed from *all* cases. This typically causes the prefix to become shorter, meaning that additional words are prepended to each existing case name.

Existing diagnostics make it look like the case has disappeared, when in fact it still exists under a different name. A little more information may help developers to figure out what happened.

Add a tailored diagnostic for this scenario which kicks in when (a) a missing member is diagnosed, (b) the base is an imported enum or option set’s metatype, and (c) an enum case or static property exists which has the name we attempted to look up as a suffix.

Fixes rdar://116251319.
This commit is contained in:
Becca Royal-Gordon
2023-09-29 16:15:09 -07:00
parent e23f26ac85
commit 78127ce5ee
7 changed files with 203 additions and 1 deletions

View File

@@ -215,6 +215,17 @@ void WordIterator::computePrevPosition() const {
PrevPositionValid = true;
}
bool camel_case::Words::hasWordStartingAt(unsigned targetPosition) const {
// Iterate over the words until we see one at or past targetPosition.
// FIXME: Is there a faster way to do this by looking at the characters around
// the position?
for (auto i = begin(); i != end() && i.getPosition() <= targetPosition; i++) {
if (i.getPosition() == targetPosition)
return true;
}
return false;
}
StringRef camel_case::getFirstWord(StringRef string) {
if (string.empty())
return "";
@@ -249,6 +260,30 @@ bool camel_case::startsWithIgnoreFirstCase(StringRef word1, StringRef word2) {
return word1.substr(1, word2.size() - 1) == word2.substr(1);
}
bool camel_case::hasWordSuffix(StringRef haystack, StringRef needle) {
// Is it even possible for one to be a suffix of the other?
if (needle.empty() || haystack.size() <= needle.size())
return false;
// Does haystack have a word boundary at the right position?
auto targetPosition = haystack.size() - needle.size();
if (!Words(haystack).hasWordStartingAt(targetPosition))
return false;
StringRef suffix = haystack.substr(targetPosition);
// Fast path: Without potentially copying the strings, do they match?
if (sameWordIgnoreFirstCase(suffix, needle))
return true;
// Flatten out leading initialisms. Do they match?
SmallString<32> suffixScratch, needleScratch;
auto suffixFlat = toLowercaseInitialisms(suffix, suffixScratch);
auto needleFlat = toLowercaseInitialisms(needle, needleScratch);
return suffixFlat == needleFlat;
}
StringRef camel_case::toLowercaseWord(StringRef string,
SmallVectorImpl<char> &scratch) {
if (string.empty())