Skip to content

Commit 291bfef

Browse files
committed
Use unavailability instead
1 parent 1e0148a commit 291bfef

13 files changed

+235
-62
lines changed

include/swift/AST/DiagnosticsParse.def

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -909,11 +909,8 @@ WARNING(parameter_unnamed_warn,none,
909909
ERROR(parameter_curry_syntax_removed,none,
910910
"cannot have more than one parameter list", ())
911911

912-
ERROR(unavailability_does_not_support_expressions,none,
913-
"availability equality only supports boolean literals", ())
914-
915-
ERROR(availability_cannot_be_positive_and_negative_at_same_time,none,
916-
"cannot have availability and unavailability in the same statement; result may cause impossible constraints", ())
912+
ERROR(availability_cannot_be_mixed,none,
913+
"#available and #unavailable cannot be in the same statement", ())
917914

918915
ERROR(initializer_as_typed_pattern,none,
919916
"unexpected initializer in pattern; did you mean to use '='?", ())
@@ -1204,8 +1201,8 @@ ERROR(invalid_float_literal_missing_leading_zero,none,
12041201
"'.%0' is not a valid floating point literal; it must be written '0.%0'",
12051202
(StringRef))
12061203
ERROR(availability_query_outside_if_stmt_guard, none,
1207-
"#available may only be used as condition of an 'if', 'guard'"
1208-
" or 'while' statement", ())
1204+
"%0 may only be used as condition of an 'if', 'guard'"
1205+
" or 'while' statement", (StringRef))
12091206

12101207
ERROR(empty_arg_label_underscore, none,
12111208
"an empty argument label is spelled with '_'", ())

include/swift/AST/Stmt.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -340,8 +340,7 @@ class alignas(8) PoundAvailableInfo final :
340340
/// This is filled in by Sema.
341341
VersionRange VariantAvailableRange;
342342

343-
/// Indicates if the expression is checking if something is **not** available,
344-
/// as represented by #available(...) == false.
343+
/// Indicates that the expression is checking if something is **not** available.
345344
bool isUnavailability;
346345

347346
PoundAvailableInfo(SourceLoc PoundLoc, SourceLoc LParenLoc,

include/swift/Parse/Parser.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1641,7 +1641,7 @@ class Parser {
16411641
StringRef &BindingKindStr);
16421642
ParserStatus parseStmtCondition(StmtCondition &Result, Diag<> ID,
16431643
StmtKind ParentKind);
1644-
ParserResult<PoundAvailableInfo> parseStmtConditionPoundAvailable(bool isUnavailability);
1644+
ParserResult<PoundAvailableInfo> parseStmtConditionPoundAvailable();
16451645
ParserResult<Stmt> parseStmtIf(LabeledStmtInfo LabelInfo,
16461646
bool IfWasImplicitlyInserted = false);
16471647
ParserResult<Stmt> parseStmtGuard();

lib/Parse/ParseExpr.cpp

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -218,9 +218,8 @@ ParserResult<Expr> Parser::parseExprSequence(Diag<> Message,
218218
// so the code is invalid. We get better recovery if we bail out from
219219
// this, because then we can produce a fixit to rewrite the && into a ,
220220
// if we're in a stmt-condition.
221-
// FIXME
222221
if (Tok.getText() == "&&" &&
223-
peekToken().isAny(tok::pound_available,
222+
peekToken().isAny(tok::pound_available, tok::pound_unavailable,
224223
tok::kw_let, tok::kw_var, tok::kw_case))
225224
goto done;
226225

@@ -1636,12 +1635,12 @@ ParserResult<Expr> Parser::parseExprPrimary(Diag<> ID, bool isExprBasic) {
16361635
case tok::l_square:
16371636
return parseExprCollection();
16381637

1639-
case tok::pound_available: {
1638+
case tok::pound_available:
1639+
case tok::pound_unavailable: {
16401640
// For better error recovery, parse but reject #available in an expr
16411641
// context.
1642-
// FIXME
1643-
diagnose(Tok.getLoc(), diag::availability_query_outside_if_stmt_guard);
1644-
auto res = parseStmtConditionPoundAvailable(false);
1642+
diagnose(Tok.getLoc(), diag::availability_query_outside_if_stmt_guard, Tok.getText());
1643+
auto res = parseStmtConditionPoundAvailable();
16451644
if (res.hasCodeCompletion())
16461645
return makeParserCodeCompletionStatus();
16471646
if (res.isParseError() || res.isNull())

lib/Parse/ParseStmt.cpp

Lines changed: 13 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1245,15 +1245,21 @@ static void validateAvailabilitySpecList(Parser &P,
12451245
Specs = RecognizedSpecs;
12461246
}
12471247

1248-
// #available(...) / !#available(...) / #available(...) == true/false
1249-
ParserResult<PoundAvailableInfo> Parser::parseStmtConditionPoundAvailable(bool isUnavailability) {
1248+
// #available(...)
1249+
// #unavailable(...)
1250+
ParserResult<PoundAvailableInfo> Parser::parseStmtConditionPoundAvailable() {
12501251
SyntaxParsingContext ConditonCtxt(SyntaxContext,
12511252
SyntaxKind::AvailabilityCondition);
1253+
SourceLoc PoundLoc;
12521254

1253-
if (isUnavailability) {
1254-
consumeToken(tok::exclaim_postfix);
1255+
bool isUnavailability;
1256+
if (Tok.is(tok::pound_available)) {
1257+
isUnavailability = false;
1258+
PoundLoc = consumeToken();
1259+
} else {
1260+
isUnavailability = true;
1261+
PoundLoc = consumeToken(tok::pound_unavailable);
12551262
}
1256-
SourceLoc PoundLoc = consumeToken(tok::pound_available);
12571263

12581264
if (!Tok.isFollowingLParen()) {
12591265
diagnose(Tok, diag::avail_query_expected_condition);
@@ -1287,22 +1293,6 @@ ParserResult<PoundAvailableInfo> Parser::parseStmtConditionPoundAvailable(bool i
12871293
diag::avail_query_expected_rparen, LParenLoc))
12881294
Status.setIsParseError();
12891295

1290-
// == true/false
1291-
if (Tok.isAnyOperator() && Tok.getText() == "==") {
1292-
consumeToken();
1293-
if (Tok.is(tok::kw_false)) {
1294-
// Treat double negation from
1295-
// !#available(...) == false
1296-
isUnavailability = !isUnavailability;
1297-
consumeToken();
1298-
} else if (Tok.is(tok::kw_true)) {
1299-
consumeToken();
1300-
} else {
1301-
diagnose(Tok, diag::unavailability_does_not_support_expressions);
1302-
Status.setIsParseError();
1303-
}
1304-
}
1305-
13061296
auto *result = PoundAvailableInfo::create(Context, PoundLoc, LParenLoc, Specs,
13071297
RParenLoc, isUnavailability);
13081298
return makeParserResult(Status, result);
@@ -1398,9 +1388,8 @@ Parser::parseStmtConditionElement(SmallVectorImpl<StmtConditionElement> &result,
13981388
ParserStatus Status;
13991389

14001390
// Parse a leading #available condition if present.
1401-
if (Tok.is(tok::pound_available) || (Tok.is(tok::exclaim_postfix) &&
1402-
peekToken().is(tok::pound_available))) {
1403-
auto res = parseStmtConditionPoundAvailable(Tok.is(tok::exclaim_postfix));
1391+
if (Tok.isAny(tok::pound_available, tok::pound_unavailable)) {
1392+
auto res = parseStmtConditionPoundAvailable();
14041393
if (res.isNull() || res.hasCodeCompletion()) {
14051394
Status |= res;
14061395
return Status;

lib/Sema/BuilderTransform.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1232,6 +1232,7 @@ class BuilderClosureRewriter
12321232
// have had the chance to adopt buildLimitedAvailability(), we'll upgrade
12331233
// this warning to an error.
12341234
if (auto availabilityCond = findAvailabilityCondition(ifStmt->getCond())) {
1235+
// FIXME: #unavailable
12351236
SourceLoc loc = availabilityCond->getStartLoc();
12361237
Type thenBodyType = solution.simplifyType(
12371238
solution.getType(target.captured.second[0]));

lib/Sema/TypeCheckAvailability.cpp

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -437,13 +437,17 @@ class TypeRefinementContextBuilder : private ASTWalker {
437437
// condition elements following it.
438438
auto *Query = Element.getAvailability();
439439

440-
// Prevent availability and unavailability from mixing, like
441-
// if #available(...) == false, #available(...)
442440
if (isRefiningUnavailability == None) {
443441
isRefiningUnavailability = Query->getIsUnavailability();
444442
} else if (isRefiningUnavailability != Query->getIsUnavailability()) {
443+
// Mixing availability with unavailability in the same statement will
444+
// cause the false flow's version range to be ambiguous. Report it.
445+
//
446+
// Technically we can support this by not refining ambiguous flows,
447+
// but there are currently no legitimate cases where one would have
448+
// to mix availability with unavailability.
445449
Context.Diags.diagnose(Query->getLoc(),
446-
diag::availability_cannot_be_positive_and_negative_at_same_time);
450+
diag::availability_cannot_be_mixed);
447451
break;
448452
}
449453

@@ -546,6 +550,7 @@ class TypeRefinementContextBuilder : private ASTWalker {
546550
++NestedCount;
547551
}
548552

553+
549554
Optional<AvailabilityContext> FalseRefinement = None;
550555
// The version range for the false branch should never have any versions
551556
// that weren't possible when the condition started evaluating.
@@ -569,7 +574,7 @@ class TypeRefinementContextBuilder : private ASTWalker {
569574
if (!isRefiningUnavailability.hasValue() || !isRefiningUnavailability.getValue()) {
570575
return std::make_pair(NestedTRC->getAvailabilityInfo(), FalseRefinement);
571576
} else {
572-
// If this is an unavailability check, simply invert the result.
577+
// If this is an unavailability check, invert the result.
573578
return std::make_pair(FalseRefinement, NestedTRC->getAvailabilityInfo());
574579
}
575580
}

test/Parse/availability_query.swift

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ let x = #available(OSX 10.51, *) // expected-error {{#available may only be use
1212

1313
(#available(OSX 10.51, *) ? 1 : 0) // expected-error {{#available may only be used as condition of an}}
1414

15+
if !#available(OSX 10.52, *) { // expected-error {{#available may only be used as condition of an}}
16+
}
17+
if let _ = Optional(5), !#available(OSX 10.52, *) { // expected-error {{#available may only be used as condition}}
18+
}
19+
1520
if #available(OSX 10.51, *) && #available(OSX 10.52, *) { // expected-error {{expected ',' joining parts of a multi-clause condition}} {{28-31=,}}
1621
}
1722

@@ -103,21 +108,3 @@ if let _ = Optional(42), #available(iOS 8.0, *) {}
103108
// Allow "macOS" as well.
104109
if #available(macOS 10.51, *) {
105110
}
106-
107-
// Negative availability.
108-
//if !#available(OSX 10.52, *) {
109-
//}
110-
//if let _ = Optional(5), !#available(OSX 10.52, *) {
111-
//}
112-
//if !#available(OSX 10.52, *), let _ = Optional(5) {
113-
//}
114-
if let _ = Optional(5), #available(OSX 10.52, *) == false {
115-
}
116-
if #available(OSX 10.52, *) == false, let _ = Optional(5) {
117-
}
118-
if #available(macOS 10.51, *) == false {
119-
}
120-
121-
// Allow verbose positive form as well.
122-
if #available(macOS 10.51, *) == true {
123-
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// RUN: %target-typecheck-verify-swift
2+
3+
// REQUIRES: OS=macosx
4+
5+
// This file is mostly an inverted version of availability_query.swift
6+
7+
if #unavailable(OSX 10.51, *) {
8+
}
9+
10+
// Disallow use as an expression.
11+
if (#unavailable(OSX 10.51, *)) {} // expected-error {{#unavailable may only be used as condition of an 'if', 'guard'}}
12+
13+
let x = #unavailable(OSX 10.51, *) // expected-error {{#unavailable may only be used as condition of}}
14+
15+
(#unavailable(OSX 10.51, *) ? 1 : 0) // expected-error {{#unavailable may only be used as condition of an}}
16+
17+
if !unavailable(OSX 10.52, *) { // expected-error {{#unavailable may only be used as condition of an}}
18+
}
19+
if let _ = Optional(5), !unavailable(OSX 10.52, *) { // expected-error {{#unavailable may only be used as condition}}
20+
}
21+
22+
if #unavailable(OSX 10.51, *) && #unavailable(OSX 10.52, *) { // expected-error {{expected ',' joining parts of a multi-clause condition}} {{30-33=,}}
23+
}
24+
25+
26+
if #unavailable { // expected-error {{expected availability condition}} expected-error {{closure expression is unused}} expected-error {{top-level statement cannot begin with a closure expression}} expected-note {{did you mean to use a 'do' statement?}} {{17-17=do }}
27+
}
28+
29+
if #unavailable( { // expected-error {{expected platform name}} expected-error {{expected ')'}} expected-note {{to match this opening '('}}
30+
}
31+
32+
if #unavailable() { // expected-error {{expected platform name}}
33+
}
34+
35+
if #unavailable(OSX { // expected-error {{expected version number}} expected-error {{expected ')'}} expected-note {{to match this opening '('}}
36+
}
37+
38+
if #unavailable(OSX) { // expected-error {{expected version number}}
39+
}
40+
41+
if #unavailable(OSX 10.51 { // expected-error {{expected ')'}} expected-note {{to match this opening '('}} expected-error {{must handle potential future platforms with '*'}} {{26-26=, *}}
42+
}
43+
44+
if #unavailable(iDishwasherOS 10.51) { // expected-warning {{unrecognized platform name 'iDishwasherOS'}}
45+
// expected-error@-1 {{must handle potential future platforms with '*'}}
46+
}
47+
48+
if #unavailable(iDishwasherOS 10.51, *) { // expected-warning {{unrecognized platform name 'iDishwasherOS'}}
49+
}
50+
51+
if #unavailable(OSX 10.51, OSX 10.52, *) { // expected-error {{version for 'macOS' already specified}}
52+
}
53+
54+
if #unavailable(OSX 10.52) { } // expected-error {{must handle potential future platforms with '*'}} {{26-26=, *}}
55+
56+
if #unavailable(OSX 10.51, iOS 8.0) { } // expected-error {{must handle potential future platforms with '*'}} {{35-35=, *}}
57+
58+
if #unavailable(iOS 8.0, *) {
59+
}
60+
61+
if #unavailable(iOSApplicationExtension, unavailable) { // expected-error 2{{expected version number}}
62+
}
63+
64+
// Want to make sure we can parse this. Perhaps we should not let this validate, though.
65+
if #unavailable(*) {
66+
}
67+
68+
if #unavailable(* { // expected-error {{expected ')' in availability query}} expected-note {{to match this opening '('}}
69+
}
70+
71+
// Multiple platforms
72+
if #unavailable(OSX 10.51, iOS 8.0, *) {
73+
}
74+
75+
76+
if #unavailable(OSX 10.51, { // expected-error {{expected platform name}} // expected-error {{expected ')'}} expected-note {{to match this opening '('}}
77+
}
78+
79+
if #unavailable(OSX 10.51,) { // expected-error {{expected platform name}}
80+
}
81+
82+
if #unavailable(OSX 10.51, iOS { // expected-error {{expected version number}} // expected-error {{expected ')'}} expected-note {{to match this opening '('}}
83+
}
84+
85+
if #unavailable(OSX 10.51, iOS 8.0, iDishwasherOS 10.51) { // expected-warning {{unrecognized platform name 'iDishwasherOS'}}
86+
// expected-error@-1 {{must handle potential future platforms with '*'}}
87+
}
88+
89+
if #unavailable(iDishwasherOS 10.51, OSX 10.51) { // expected-warning {{unrecognized platform name 'iDishwasherOS'}}
90+
// expected-error@-1 {{must handle potential future platforms with '*'}}
91+
}
92+
93+
if #unavailable(OSX 10.51 || iOS 8.0) {// expected-error {{'||' cannot be used in an availability condition}}
94+
}
95+
96+
// Emit Fix-It removing un-needed >=, for the moment.
97+
98+
if #unavailable(OSX >= 10.51, *) { // expected-error {{version comparison not needed}} {{21-24=}}
99+
}
100+
101+
// <rdar://problem/20904820> Following a "let" condition with #unavailable is incorrectly rejected
102+
103+
// Bool then #unavailable.
104+
if 1 != 2, #unavailable(iOS 8.0, *) {}
105+
106+
// Pattern then #unavailable(iOS 8.0, *) {
107+
if case 42 = 42, #unavailable(iOS 8.0, *) {}
108+
if let _ = Optional(42), #unavailable(iOS 8.0, *) {}
109+
110+
// Allow "macOS" as well.
111+
if #unavailable(macOS 10.51, *) {
112+
}
113+
114+
// Prevent availability and unavailability being present in the same statement.
115+
if #unavailable(macOS 10.51, *), #available(macOS 10.52, *) { // expected-error {{#available and #unavailable cannot be in the same statement}}
116+
}
117+
if #available(macOS 10.51, *), #unavailable(macOS 10.52, *) { // expected-error {{#available and #unavailable cannot be in the same statement}}
118+
}
119+
if #available(macOS 10.51, *), #available(macOS 10.55, *), #unavailable(macOS 10.53, *) { // expected-error {{#available and #unavailable cannot be in the same statement}}
120+
}
121+
if #unavailable(macOS 10.51, *), #unavailable(macOS 10.55, *), #available(macOS 10.53, *) { // expected-error {{#available and #unavailable cannot be in the same statement}}
122+
}
123+
if case 42 = 42, #available(macOS 10.51, *), #unavailable(macOS 10.52, *) { // expected-error {{#available and #unavailable cannot be in the same statement}}
124+
}
125+
if #available(macOS 10.51, *), case 42 = 42, #unavailable(macOS 10.52, *) { // expected-error {{#available and #unavailable cannot be in the same statement}}
126+
}
127+
128+
// Allow availabiility and unavailability to mix if they are not in the same statement.
129+
if #unavailable(macOS 11, *) {
130+
if #available(macOS 10, *) { }
131+
}
132+
if #available(macOS 10, *) {
133+
if #unavailable(macOS 11, *) { }
134+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// RUN: %target-typecheck-verify-swift -parse-as-library
2+
// REQUIRES: OS=macosx
3+
4+
@available(OSX 998.0, *)
5+
@discardableResult
6+
func foo() -> Int { return 0 }
7+
8+
@available(OSX 999.0, *)
9+
@discardableResult
10+
func bar() -> Int { return 0 }
11+
12+
// Verify that #unavailable is the opposite of #available.
13+
// expected-note@+1 *{{add @available attribute to enclosing global function}}
14+
func testUnavailable() {
15+
if #unavailable(OSX 998.0, *) {
16+
foo() // expected-error{{'foo()' is only available in macOS 998.0 or newer}}
17+
// expected-note@-1 {{add 'if #available' version check}}
18+
bar() // expected-error{{'bar()' is only available in macOS 999.0 or newer}}
19+
// expected-note@-1 {{add 'if #available' version check}}
20+
} else {
21+
foo()
22+
bar() // expected-error{{'bar()' is only available in macOS 999.0 or newer}}
23+
// expected-note@-1 {{add 'if #available' version check}}
24+
if #unavailable(OSX 999.0, *) {
25+
foo()
26+
bar() // expected-error{{'bar()' is only available in macOS 999.0 or newer}}
27+
// expected-note@-1 {{add 'if #available' version check}}
28+
} else {
29+
foo()
30+
bar()
31+
}
32+
}
33+
}
34+
35+
// Verify that #unavailable doesn't complain about useless specs.
36+
// expected-note@+1 *{{add @available attribute to enclosing global function}}
37+
func testUnavailableDoesntWarnUselessSpecs() {
38+
if #unavailable(OSX 998.0, *), #unavailable(OSX 999.0, *) {
39+
foo() // expected-error{{'foo()' is only available in macOS 998.0 or newer}}
40+
// expected-note@-1 {{add 'if #available' version check}}
41+
bar() // expected-error{{'bar()' is only available in macOS 999.0 or newer}}
42+
// expected-note@-1 {{add 'if #available' version check}}
43+
} else {
44+
foo()
45+
bar()
46+
}
47+
}
48+

0 commit comments

Comments
 (0)