Skip to content

Commit 52e2b3d

Browse files
[Parse] Suggest platform correction based on known platforms edit distance
1 parent a6a486c commit 52e2b3d

10 files changed

+87
-10
lines changed

include/swift/AST/PlatformKind.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,10 @@ StringRef platformString(PlatformKind platform);
4141
/// or None if such a platform kind does not exist.
4242
Optional<PlatformKind> platformFromString(StringRef Name);
4343

44-
/// Returns a valid platform string if the candidate string would be a valid
45-
/// platform string if its case were adjusted (e.g. "macos" -> "macOS").
46-
Optional<StringRef> caseCorrectedPlatformString(StringRef candidate);
44+
/// Returns a valid platform string that is closest to the candidate string
45+
/// based on edit distance. Returns \c None if the closest valid platform
46+
/// distance is not within a minimum threshold.
47+
Optional<StringRef> closestCorrectedPlatformString(StringRef candidate);
4748

4849
/// Returns a human-readable version of the platform name as a string, suitable
4950
/// for emission in diagnostics (e.g., "macOS").

lib/AST/PlatformKind.cpp

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,28 @@ Optional<PlatformKind> swift::platformFromString(StringRef Name) {
5858
.Default(Optional<PlatformKind>());
5959
}
6060

61-
Optional<StringRef> swift::caseCorrectedPlatformString(StringRef candidate) {
61+
Optional<StringRef> swift::closestCorrectedPlatformString(StringRef candidate) {
62+
auto lowerCasedCandidate = candidate.lower();
63+
auto lowerCasedCandidateRef = StringRef(lowerCasedCandidate);
64+
auto minDistance = std::numeric_limits<unsigned int>::max();
65+
Optional<StringRef> result = None;
6266
#define AVAILABILITY_PLATFORM(X, PrettyName) \
63-
if (candidate.compare_insensitive(#X) == 0) { \
64-
return StringRef(#X); \
67+
{ \
68+
auto platform = StringRef(#X); \
69+
auto distance = lowerCasedCandidateRef.edit_distance(platform.lower()); \
70+
if (distance == 0) { \
71+
return platform; \
72+
} \
73+
if (distance < minDistance) { \
74+
minDistance = distance; \
75+
result = platform; \
76+
} \
6577
}
6678
#include "swift/AST/PlatformKinds.def"
67-
return None;
79+
// If the most similar platform distance is greater than this threshold,
80+
// it's not similar enough to be suggested as correction.
81+
const unsigned int distanceThreshold = 5;
82+
return (minDistance < distanceThreshold) ? result : None;
6883
}
6984

7085
static bool isApplicationExtensionPlatform(PlatformKind Platform) {

lib/Parse/ParseDecl.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -567,7 +567,7 @@ ParserResult<AvailableAttr> Parser::parseExtendedAvailabilitySpecList(
567567
if (AnyArgumentInvalid)
568568
return nullptr;
569569
if (!PlatformKind.hasValue()) {
570-
if (auto CorrectedPlatform = caseCorrectedPlatformString(Platform)) {
570+
if (auto CorrectedPlatform = closestCorrectedPlatformString(Platform)) {
571571
diagnose(PlatformLoc, diag::attr_availability_suggest_platform, Platform,
572572
AttrName, *CorrectedPlatform)
573573
.fixItReplace(SourceRange(PlatformLoc), *CorrectedPlatform);
@@ -1829,7 +1829,7 @@ ParserStatus Parser::parsePlatformVersionInList(StringRef AttrName,
18291829
consumeToken();
18301830

18311831
if (!MaybePlatform.hasValue()) {
1832-
if (auto correctedPlatform = caseCorrectedPlatformString(platformText)) {
1832+
if (auto correctedPlatform = closestCorrectedPlatformString(platformText)) {
18331833
diagnose(PlatformLoc, diag::attr_availability_suggest_platform,
18341834
platformText, AttrName, *correctedPlatform)
18351835
.fixItReplace(SourceRange(PlatformLoc), *correctedPlatform);

lib/Parse/ParseExpr.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3953,7 +3953,7 @@ Parser::parsePlatformVersionConstraintSpec() {
39533953

39543954
if (!Platform.hasValue() || Platform.getValue() == PlatformKind::none) {
39553955
if (auto CorrectedPlatform =
3956-
caseCorrectedPlatformString(PlatformIdentifier.str())) {
3956+
closestCorrectedPlatformString(PlatformIdentifier.str())) {
39573957
diagnose(PlatformLoc, diag::avail_query_suggest_platform_name,
39583958
PlatformIdentifier, *CorrectedPlatform)
39593959
.fixItReplace(PlatformLoc, *CorrectedPlatform);

test/Parse/availability_query.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,15 @@ if #available(iDishwasherOS 10.51, *) { // expected-warning {{unrecognized platf
4949
if #available(macos 10.51, *) { // expected-warning {{unrecognized platform name 'macos'; did you mean 'macOS'?}} {{15-20=macOS}}
5050
}
5151

52+
if #available(mscos 10.51, *) { // expected-warning {{unrecognized platform name 'mscos'; did you mean 'macOS'?}} {{15-20=macOS}}
53+
}
54+
55+
if #available(macoss 10.51, *) { // expected-warning {{unrecognized platform name 'macoss'; did you mean 'macOS'?}} {{15-21=macOS}}
56+
}
57+
58+
if #available(mac 10.51, *) { // expected-warning {{unrecognized platform name 'mac'; did you mean 'macOS'?}} {{15-18=macOS}}
59+
}
60+
5261
if #available(OSX 10.51, OSX 10.52, *) { // expected-error {{version for 'macOS' already specified}}
5362
}
5463

test/Parse/availability_query_unavailability.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,15 @@ if #unavailable(iDishwasherOS 10.51) { // expected-warning {{unrecognized platfo
4646
if #unavailable(macos 10.51) { // expected-warning {{unrecognized platform name 'macos'; did you mean 'macOS'?}} {{17-22=macOS}}
4747
}
4848

49+
if #unavailable(mscos 10.51) { // expected-warning {{unrecognized platform name 'mscos'; did you mean 'macOS'?}} {{17-22=macOS}}
50+
}
51+
52+
if #unavailable(macoss 10.51) { // expected-warning {{unrecognized platform name 'macoss'; did you mean 'macOS'?}} {{17-23=macOS}}
53+
}
54+
55+
if #unavailable(mac 10.51) { // expected-warning {{unrecognized platform name 'mac'; did you mean 'macOS'?}} {{17-20=macOS}}
56+
}
57+
4958
if #unavailable(OSX 10.51, OSX 10.52) { // expected-error {{version for 'macOS' already specified}}
5059
}
5160

test/Sema/diag_originally_definedin.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,26 @@ public func macroUnknown() {}
3131
// expected-error@-1 {{expected at least one platform version in '@_originallyDefinedIn' attribute}}
3232
public func incorrectPlatformCase() {}
3333

34+
@available(macOS 10.9, *)
35+
@_originallyDefinedIn(module: "original", mscos 10.13) // expected-warning {{unknown platform 'mscos' for attribute '@_originallyDefinedIn'; did you mean 'macOS'?}} {{43-48=macOS}}
36+
// expected-error@-1 {{expected at least one platform version in '@_originallyDefinedIn' attribute}}
37+
public func incorrectPlatformSimilar1() {}
38+
39+
@available(macOS 10.9, *)
40+
@_originallyDefinedIn(module: "original", macoss 10.13) // expected-warning {{unknown platform 'macoss' for attribute '@_originallyDefinedIn'; did you mean 'macOS'?}} {{43-49=macOS}}
41+
// expected-error@-1 {{expected at least one platform version in '@_originallyDefinedIn' attribute}}
42+
public func incorrectPlatformSimilar2() {}
43+
44+
@available(macOS 10.9, *)
45+
@_originallyDefinedIn(module: "original", mac 10.13) // expected-warning {{unknown platform 'mac' for attribute '@_originallyDefinedIn'; did you mean 'macOS'?}} {{43-46=macOS}}
46+
// expected-error@-1 {{expected at least one platform version in '@_originallyDefinedIn' attribute}}
47+
public func incorrectPlatformSimilar3() {}
48+
49+
@available(macOS 10.9, *)
50+
@_originallyDefinedIn(module: "original", notValid 10.13) // expected-warning {{unknown platform 'notValid' for attribute '@_originallyDefinedIn'}} {{none}}
51+
// expected-error@-1 {{expected at least one platform version in '@_originallyDefinedIn' attribute}}
52+
public func incorrectPlatformNotSimilar() {}
53+
3454
@available(macOS 10.9, *)
3555
@_originallyDefinedIn(module: "original", swift 5.1) // expected-warning {{unknown platform 'swift' for attribute '@_originallyDefinedIn'}}
3656
// expected-error@-1 {{expected at least one platform version in '@_originallyDefinedIn' attribute}}

test/attr/attr_availability.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,18 @@ func unavailable_bad_platform() {}
2323
@available(macos, unavailable) // expected-warning {{unknown platform 'macos' for attribute 'available'; did you mean 'macOS'?}} {{12-17=macOS}}
2424
func incorrect_platform_case() {}
2525

26+
@available(mscos, unavailable) // expected-warning {{unknown platform 'mscos' for attribute 'available'; did you mean 'macOS'?}} {{12-17=macOS}}
27+
func incorrect_platform_similar1() {}
28+
29+
@available(macoss, unavailable) // expected-warning {{unknown platform 'macoss' for attribute 'available'; did you mean 'macOS'?}} {{12-18=macOS}}
30+
func incorrect_platform_similar2() {}
31+
32+
@available(mac, unavailable) // expected-warning {{unknown platform 'mac' for attribute 'available'; did you mean 'macOS'?}} {{12-15=macOS}}
33+
func incorrect_platform_similar3() {}
34+
35+
@available(notValid, unavailable) // expected-warning {{unknown platform 'notValid' for attribute 'available'}} {{none}}
36+
func incorrect_platform_not_similar() {}
37+
2638
// Handle unknown platform.
2739
@available(HAL9000, unavailable) // expected-warning {{unknown platform 'HAL9000'}}
2840
func availabilityUnknownPlatform() {}
@@ -234,6 +246,10 @@ func shortFormWithTwoUnrecognizedPlatforms() {
234246
func shortFormWithTwoPlatformsIncorrectCase() {
235247
}
236248

249+
@available(os 8.0, *)
250+
// expected-warning@-1 {{unrecognized platform name 'os'; did you mean 'iOS'?}} {{12-14=iOS}}
251+
func iosIsClosestThanMacOS() {}
252+
237253
// Make sure that even after the parser hits an unrecognized
238254
// platform it validates the availability.
239255
@available(iOS 8.0, iDishwasherOS 22.0, iOS 9.0, *)

test/attr/attr_backDeploy.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,10 @@ public func transparentFunc() {}
257257
@_backDeploy(before: macos 12.0, iOS 15.0) // expected-warning {{unknown platform 'macos' for attribute '@_backDeploy'; did you mean 'macOS'?}} {{22-27=macOS}}
258258
public func incorrectPlatformCaseFunc() {}
259259

260+
@available(macOS 11.0, iOS 14.0, *)
261+
@_backDeploy(before: mscos 12.0, iOS 15.0) // expected-warning {{unknown platform 'mscos' for attribute '@_backDeploy'; did you mean 'macOS'?}} {{22-27=macOS}}
262+
public func incorrectPlatformSimilarFunc() {}
263+
260264
@available(macOS 11.0, *)
261265
@_backDeploy(before: macOS 12.0, unknownOS 1.0) // expected-warning {{unknown platform 'unknownOS' for attribute '@_backDeploy'}}
262266
public func unknownOSFunc() {}

test/attr/spi_available.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,6 @@ public class SPIClass4 {} // expected-warning {{symbols that are @_spi_available
1414

1515
@_spi_available(macos 10.15, *) // expected-warning {{unrecognized platform name 'macos'; did you mean 'macOS'?}} {{17-22=macOS}}
1616
public class SPIClass5 {}
17+
18+
@_spi_available(mscos 10.15, *) // expected-warning {{unrecognized platform name 'mscos'; did you mean 'macOS'?}} {{17-22=macOS}}
19+
public class SPIClass6 {}

0 commit comments

Comments
 (0)