Skip to content

Commit d31bad4

Browse files
committed
[Parse] Add fixit for targetEnvironment(simulator)
1 parent d3613b8 commit d31bad4

File tree

3 files changed

+127
-3
lines changed

3 files changed

+127
-3
lines changed

include/swift/AST/DiagnosticsParse.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1503,6 +1503,9 @@ ERROR(empty_version_string,none,
15031503
WARNING(unknown_platform_condition_argument,none,
15041504
"unknown %0 for build configuration '%1'",
15051505
(StringRef, StringRef))
1506+
WARNING(likely_simulator_platform_condition,none,
1507+
"plaform condition appears to be testing for simulator environment",
1508+
())
15061509

15071510
//------------------------------------------------------------------------------
15081511
// Availability query parsing diagnostics

lib/Parse/ParseIfConfig.cpp

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,8 +547,97 @@ static bool isVersionIfConfigCondition(Expr *Condition) {
547547
return IsVersionIfConfigCondition().visit(Condition);
548548
}
549549

550+
/// Get the identifier string from an \c Expr if it's an
551+
/// \c UnresolvedDeclRefExpr, otherwise the empty string.
552+
static StringRef getDeclRefStr(Expr *E) {
553+
if (auto *UDRE = cast<UnresolvedDeclRefExpr>(E)) {
554+
return UDRE->getName().getBaseIdentifier().str();
555+
}
556+
return "";
557+
}
558+
559+
static bool isPlatformConditionDisjunction(Expr *E, PlatformConditionKind Kind,
560+
ArrayRef<StringRef> Vals) {
561+
if (auto *Or = dyn_cast<BinaryExpr>(E)) {
562+
if (getDeclRefStr(Or->getFn()) == "||") {
563+
auto Args = Or->getArg()->getElements();
564+
return (isPlatformConditionDisjunction(Args[0], Kind, Vals) &&
565+
isPlatformConditionDisjunction(Args[1], Kind, Vals));
566+
}
567+
} else if (auto *P = dyn_cast<ParenExpr>(E)) {
568+
return isPlatformConditionDisjunction(P->getSubExpr(), Kind, Vals);
569+
} else if (auto *C = dyn_cast<CallExpr>(E)) {
570+
if (getPlatformConditionKind(getDeclRefStr(C->getFn())) != Kind)
571+
return false;
572+
if (auto *ArgP = dyn_cast<ParenExpr>(C->getArg())) {
573+
if (auto *Arg = ArgP->getSubExpr()) {
574+
auto ArgStr = getDeclRefStr(Arg);
575+
for (auto V : Vals) {
576+
if (ArgStr == V)
577+
return true;
578+
}
579+
}
580+
}
581+
}
582+
return false;
583+
}
584+
585+
// Search for the first occurrence of a _likely_ (but not definite) implicit
586+
// simulator-environment platform condition, or negation thereof. This is
587+
// defined as any logical conjunction of one or more os() platform conditions
588+
// _strictly_ from the set {iOS, tvOS, watchOS} and one or more arch() platform
589+
// conditions _strictly_ from the set {i386, x86_64}.
590+
//
591+
// These are (at the time of writing) defined as de-facto simulators in
592+
// Platform.cpp, and if a user is testing them they're _likely_ looking for
593+
// simulator-ness indirectly. If there is anything else in the condition aside
594+
// from these conditions (or the negation of such a conjunction), we
595+
// conservatively assume the user is testing something other than
596+
// simulator-ness.
597+
static Expr *findAnyLikelySimulatorEnvironmentTest(Expr *Condition) {
598+
599+
if (!Condition)
600+
return nullptr;
601+
602+
if (auto *N = dyn_cast<PrefixUnaryExpr>(Condition)) {
603+
return findAnyLikelySimulatorEnvironmentTest(N->getArg());
604+
} else if (auto *P = dyn_cast<ParenExpr>(Condition)) {
605+
return findAnyLikelySimulatorEnvironmentTest(P->getSubExpr());
606+
}
607+
608+
// We assume the user is writing the condition in CNF -- say (os(iOS) ||
609+
// os(tvOS)) && (arch(i386) || arch(x86_64)) -- rather than DNF, as the former
610+
// is exponentially more terse, and these conditions are already quite
611+
// unwieldy. If field evidence shows people using other variants, possibly add
612+
// them here.
613+
614+
auto isSimulatorPlatformOSTest = [](Expr *E) -> bool {
615+
return isPlatformConditionDisjunction(
616+
E, PlatformConditionKind::OS, {"iOS", "tvOS", "watchOS"});
617+
};
618+
619+
auto isSimulatorPlatformArchTest = [](Expr *E) -> bool {
620+
return isPlatformConditionDisjunction(
621+
E, PlatformConditionKind::Arch, {"i386", "x86_64"});
622+
};
623+
624+
if (auto *And = dyn_cast<BinaryExpr>(Condition)) {
625+
if (getDeclRefStr(And->getFn()) == "&&") {
626+
auto Args = And->getArg()->getElements();
627+
if ((isSimulatorPlatformOSTest(Args[0]) &&
628+
isSimulatorPlatformArchTest(Args[1])) ||
629+
(isSimulatorPlatformOSTest(Args[1]) &&
630+
isSimulatorPlatformArchTest(Args[0]))) {
631+
return And;
632+
}
633+
}
634+
}
635+
return nullptr;
636+
}
637+
550638
} // end anonymous namespace
551639

640+
552641
/// Parse and populate a #if ... #endif directive.
553642
/// Delegate callback function to parse elements in the blocks.
554643
ParserResult<IfConfigDecl> Parser::parseIfConfig(
@@ -597,6 +686,13 @@ ParserResult<IfConfigDecl> Parser::parseIfConfig(
597686
diag::extra_tokens_conditional_compilation_directive);
598687
}
599688

689+
if (Expr *Test = findAnyLikelySimulatorEnvironmentTest(Condition)) {
690+
diagnose(Test->getLoc(),
691+
diag::likely_simulator_platform_condition)
692+
.fixItReplace(Test->getSourceRange(),
693+
"targetEnvironment(simulator)");
694+
}
695+
600696
// Parse elements
601697
SmallVector<ASTNode, 16> Elements;
602698
if (isActive || !isVersionCondition) {

test/Parse/ConditionalCompilation/simulatorTargetEnv.swift

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
// RUN: %swift -typecheck %s -verify -target x86_64-apple-ios7.0 -parse-stdlib
2-
// RUN: %swift -typecheck %s -verify -target x86_64-unknown-linux-simulator -parse-stdlib
3-
// RUN: %swift-ide-test -test-input-complete -source-filename=%s -target x86_64-apple-ios7.0
1+
// RUN: %swift -swift-version 4 -typecheck %s -verify -target x86_64-apple-ios7.0 -parse-stdlib
2+
// RUN: %swift -swift-version 4 -typecheck %s -verify -target x86_64-unknown-linux-simulator -parse-stdlib
3+
// RUN: %swift-ide-test -swift-version 4 -test-input-complete -source-filename=%s -target x86_64-apple-ios7.0
44

55
#if !targetEnvironment(simulator)
66
// This block should not parse.
@@ -12,3 +12,28 @@ class C {}
1212
var x = C()
1313
#endif
1414
var y = x
15+
16+
#if os(iOS) && arch(i386)
17+
// expected-warning @-1 {{plaform condition appears to be testing for simulator environment}} {{5-26=targetEnvironment(simulator)}}
18+
class C1 {}
19+
#endif
20+
21+
#if arch(i386) && os(iOS)
22+
// expected-warning @-1 {{plaform condition appears to be testing for simulator environment}} {{5-26=targetEnvironment(simulator)}}
23+
class C2 {}
24+
#endif
25+
26+
#if arch(i386) && (os(iOS) || os(watchOS))
27+
// expected-warning @-1 {{plaform condition appears to be testing for simulator environment}} {{5-43=targetEnvironment(simulator)}}
28+
class C3 {}
29+
#endif
30+
31+
#if (arch(x86_64) || arch(i386)) && (os(iOS) || os(watchOS) || os(tvOS))
32+
// expected-warning @-1 {{plaform condition appears to be testing for simulator environment}} {{5-73=targetEnvironment(simulator)}}
33+
class C4 {}
34+
#endif
35+
36+
#if !(arch(x86_64) && os(tvOS))
37+
// expected-warning @-1 {{plaform condition appears to be testing for simulator environment}} {{7-31=targetEnvironment(simulator)}}
38+
class C5 {}
39+
#endif

0 commit comments

Comments
 (0)