Skip to content

Commit 2e1c37f

Browse files
committed
[ASTScope] Allow try in unfolded sequence to cover following elements
Rather than fixing-up in the parser, adjust the ASTScope logic such that a `try` element in a SequenceExpr is considered as covering all elements to the right of it. Cases where this isn't true are invalid, and will be diagnosed during sequence folding. e.g: ``` 0 * try foo() + bar() _ = try foo() ~~~ bar() // Assuming `~~~` has lower precedence than `=` ``` This ensures we correctly handle `try` in assignment sequences, and allows ASTGen to get the behavior for free. rdar://132872235
1 parent d958c0e commit 2e1c37f

File tree

6 files changed

+125
-54
lines changed

6 files changed

+125
-54
lines changed

include/swift/AST/ASTScope.h

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1871,16 +1871,21 @@ class BraceStmtScope final : public AbstractStmtScope {
18711871
class TryScope final : public ASTScopeImpl {
18721872
public:
18731873
AnyTryExpr *const expr;
1874-
TryScope(AnyTryExpr *e)
1875-
: ASTScopeImpl(ScopeKind::Try), expr(e) {}
1874+
1875+
/// The end location of the scope. This may be past the TryExpr for
1876+
/// cases where the `try` is at the top-level of an unfolded SequenceExpr. In
1877+
/// such cases, the `try` covers all elements to the right.
1878+
SourceLoc endLoc;
1879+
1880+
TryScope(AnyTryExpr *e, SourceLoc endLoc)
1881+
: ASTScopeImpl(ScopeKind::Try), expr(e), endLoc(endLoc) {
1882+
ASSERT(endLoc.isValid());
1883+
}
18761884
virtual ~TryScope() {}
18771885

18781886
protected:
18791887
ASTScopeImpl *expandSpecifically(ScopeCreator &scopeCreator) override;
18801888

1881-
private:
1882-
void expandAScopeThatDoesNotCreateANewInsertionPoint(ScopeCreator &);
1883-
18841889
public:
18851890
SourceRange
18861891
getSourceRangeOfThisASTNode(bool omitAssertions = false) const override;

lib/AST/ASTScopeCreation.cpp

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -134,10 +134,39 @@ class ScopeCreator final : public ASTAllocated<ScopeCreator> {
134134

135135
// If we have a try/try!/try?, we need to add a scope for it
136136
if (auto anyTry = dyn_cast<AnyTryExpr>(E)) {
137-
scopeCreator.constructExpandAndInsert<TryScope>(parent, anyTry);
137+
auto *scope = scopeCreator.constructExpandAndInsert<TryScope>(
138+
parent, anyTry, anyTry->getEndLoc());
139+
scopeCreator.addExprToScopeTree(anyTry->getSubExpr(), scope);
138140
return Action::SkipNode(E);
139141
}
140142

143+
// If we have an unfolded SequenceExpr, make sure any `try` covers all
144+
// the following elements in the sequence. It's possible it doesn't
145+
// end up covering some of the following elements in the folded tree,
146+
// e.g `0 * try foo() + bar()` and `_ = try foo() ^ bar()` where `^` is
147+
// lower precedence than `=`, but those cases are invalid and will be
148+
// diagnosed during sequence folding.
149+
if (auto *seqExpr = dyn_cast<SequenceExpr>(E)) {
150+
if (!seqExpr->getFoldedExpr()) {
151+
auto *scope = parent;
152+
for (auto *elt : seqExpr->getElements()) {
153+
// Make sure we look through any always-left-folded expr,
154+
// including e.g `await` and `unsafe`.
155+
while (auto *subExpr = elt->getAlwaysLeftFoldedSubExpr()) {
156+
// Only `try` current receives a scope.
157+
if (auto *ATE = dyn_cast<AnyTryExpr>(elt)) {
158+
scope = scopeCreator.constructExpandAndInsert<TryScope>(
159+
scope, ATE, seqExpr->getEndLoc());
160+
}
161+
elt = subExpr;
162+
}
163+
scopeCreator.addExprToScopeTree(elt, scope);
164+
}
165+
// Already walked.
166+
return Action::SkipNode(E);
167+
}
168+
}
169+
141170
return Action::Continue(E);
142171
}
143172
PreWalkResult<Stmt *> walkToStmtPre(Stmt *S) override {
@@ -802,11 +831,11 @@ NO_NEW_INSERTION_POINT(MacroDefinitionScope)
802831
NO_NEW_INSERTION_POINT(MacroExpansionDeclScope)
803832
NO_NEW_INSERTION_POINT(SwitchStmtScope)
804833
NO_NEW_INSERTION_POINT(WhileStmtScope)
805-
NO_NEW_INSERTION_POINT(TryScope)
806834

807835
NO_EXPANSION(GenericParamScope)
808836
NO_EXPANSION(SpecializeAttributeScope)
809837
NO_EXPANSION(DifferentiableAttributeScope)
838+
NO_EXPANSION(TryScope)
810839

811840
#undef CREATES_NEW_INSERTION_POINT
812841
#undef NO_NEW_INSERTION_POINT
@@ -1433,11 +1462,6 @@ IterableTypeBodyPortion::insertionPointForDeferredExpansion(
14331462
return s->getParent().get();
14341463
}
14351464

1436-
void TryScope::expandAScopeThatDoesNotCreateANewInsertionPoint(
1437-
ScopeCreator &scopeCreator) {
1438-
scopeCreator.addToScopeTree(expr->getSubExpr(), this);
1439-
}
1440-
14411465
#pragma mark verification
14421466

14431467
void ast_scope::simple_display(llvm::raw_ostream &out,

lib/AST/ASTScopeSourceRange.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -410,5 +410,5 @@ SourceLoc ast_scope::extractNearestSourceLoc(
410410

411411
SourceRange TryScope::getSourceRangeOfThisASTNode(
412412
const bool omitAssertions) const {
413-
return expr->getSourceRange();
413+
return SourceRange(expr->getStartLoc(), endLoc);
414414
}

lib/ASTGen/Sources/ASTGen/Exprs.swift

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1126,18 +1126,6 @@ extension ASTGenVisitor {
11261126
var elements: [BridgedExpr] = []
11271127
elements.reserveCapacity(node.elements.count)
11281128

1129-
// If the left-most sequence expr is a 'try', hoist it up to turn
1130-
// '(try x) + y' into 'try (x + y)'. This is necessary to do in the
1131-
// ASTGen because 'try' nodes are represented in the ASTScope tree
1132-
// to look up catch nodes. The scope tree must be syntactic because
1133-
// it's constructed before sequence folding happens during preCheckExpr.
1134-
// Otherwise, catch node lookup would find the incorrect catch node for
1135-
// 'try x + y' at the source location for 'y'.
1136-
//
1137-
// 'try' has restrictions for where it can appear within a sequence
1138-
// expr. This is still diagnosed in TypeChecker::foldSequence.
1139-
let firstTryExprSyntax = node.elements.first?.as(TryExprSyntax.self)
1140-
11411129
var iter = node.elements.makeIterator()
11421130
while let node = iter.next() {
11431131
switch node.as(ExprSyntaxEnum.self) {
@@ -1164,24 +1152,14 @@ extension ASTGenVisitor {
11641152
case .unresolvedTernaryExpr(let node):
11651153
elements.append(self.generate(unresolvedTernaryExpr: node).asExpr)
11661154
default:
1167-
if let firstTryExprSyntax, node.id == firstTryExprSyntax.id {
1168-
elements.append(self.generate(expr: firstTryExprSyntax.expression))
1169-
} else {
1170-
elements.append(self.generate(expr: node))
1171-
}
1155+
elements.append(self.generate(expr: node))
11721156
}
11731157
}
11741158

1175-
let seqExpr = BridgedSequenceExpr.createParsed(
1159+
return BridgedSequenceExpr.createParsed(
11761160
self.ctx,
11771161
exprs: elements.lazy.bridgedArray(in: self)
11781162
).asExpr
1179-
1180-
if let firstTryExprSyntax {
1181-
return self.generate(tryExpr: firstTryExprSyntax, overridingSubExpr: seqExpr)
1182-
} else {
1183-
return seqExpr
1184-
}
11851163
}
11861164

11871165
func generate(subscriptCallExpr node: SubscriptCallExprSyntax, postfixIfConfigBaseExpr: BridgedExpr? = nil) -> BridgedSubscriptExpr {

lib/Parse/ParseExpr.cpp

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -370,23 +370,6 @@ ParserResult<Expr> Parser::parseExprSequence(Diag<> Message,
370370
if (SequencedExprs.size() == 1)
371371
return makeParserResult(SequenceStatus, SequencedExprs[0]);
372372

373-
// If the left-most sequence expr is a 'try', hoist it up to turn
374-
// '(try x) + y' into 'try (x + y)'. This is necessary to do in the
375-
// parser because 'try' nodes are represented in the ASTScope tree
376-
// to look up catch nodes. The scope tree must be syntactic because
377-
// it's constructed before sequence folding happens during preCheckExpr.
378-
// Otherwise, catch node lookup would find the incorrect catch node for
379-
// 'try x + y' at the source location for 'y'.
380-
//
381-
// 'try' has restrictions for where it can appear within a sequence
382-
// expr. This is still diagnosed in TypeChecker::foldSequence.
383-
if (auto *tryEval = dyn_cast<AnyTryExpr>(SequencedExprs[0])) {
384-
SequencedExprs[0] = tryEval->getSubExpr();
385-
auto *sequence = SequenceExpr::create(Context, SequencedExprs);
386-
tryEval->setSubExpr(sequence);
387-
return makeParserResult(SequenceStatus, tryEval);
388-
}
389-
390373
return makeParserResult(SequenceStatus,
391374
SequenceExpr::create(Context, SequencedExprs));
392375
}

test/stmt/typed_throws.swift

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,8 @@ func takesThrowingAutoclosure(_: @autoclosure () throws(MyError) -> Int) {}
316316
func takesNonThrowingAutoclosure(_: @autoclosure () throws(Never) -> Int) {}
317317

318318
func getInt() throws -> Int { 0 }
319+
func getIntAsync() async throws -> Int { 0 }
320+
func getBool() throws -> Bool { true }
319321

320322
func throwingAutoclosures() {
321323
takesThrowingAutoclosure(try getInt())
@@ -337,3 +339,82 @@ func noThrow() throws(Never) {
337339
// expected-error@-1 {{thrown expression type 'MyError' cannot be converted to error type 'Never'}}
338340
} catch {}
339341
}
342+
343+
precedencegroup LowerThanAssignment {
344+
lowerThan: AssignmentPrecedence
345+
}
346+
infix operator ~~~ : LowerThanAssignment
347+
func ~~~ <T, U> (lhs: T, rhs: U) {}
348+
349+
func testSequenceExpr() async throws(Never) {
350+
// Make sure the `try` here covers both calls in the ASTScope tree.
351+
try! getInt() + getInt() // expected-warning {{result of operator '+' is unused}}
352+
try! _ = getInt() + getInt()
353+
_ = try! getInt() + getInt()
354+
_ = try! getInt() + (getInt(), 0).0
355+
356+
_ = try try! getInt() + getInt()
357+
// expected-warning@-1 {{no calls to throwing functions occur within 'try' expression}}
358+
359+
_ = await try! getIntAsync() + getIntAsync()
360+
// expected-warning@-1 {{'try' must precede 'await'}}
361+
362+
_ = unsafe await try! getIntAsync() + getIntAsync()
363+
// expected-warning@-1 {{'try' must precede 'await'}}
364+
365+
_ = try unsafe await try! getIntAsync() + getIntAsync()
366+
// expected-warning@-1 {{'try' must precede 'await'}}
367+
// expected-warning@-2 {{no calls to throwing functions occur within 'try' expression}}
368+
369+
try _ = (try! getInt()) + getInt()
370+
// expected-error@-1:29 {{thrown expression type 'any Error' cannot be converted to error type 'Never'}}
371+
372+
// `try` on the condition covers both branches.
373+
_ = try! getBool() ? getInt() : getInt()
374+
375+
// `try` on "then" branch doesn't cover else.
376+
try _ = getBool() ? try! getInt() : getInt()
377+
// expected-error@-1:11 {{thrown expression type 'any Error' cannot be converted to error type 'Never'}}
378+
// expected-error@-2:39 {{thrown expression type 'any Error' cannot be converted to error type 'Never'}}
379+
380+
// The `try` here covers everything, even if unassignable.
381+
try! getBool() ? getInt() : getInt() = getInt()
382+
// expected-error@-1 {{result of conditional operator '? :' is never mutable}}
383+
384+
// Same here.
385+
try! getBool() ? getInt() : getInt() ~~~ getInt()
386+
387+
try _ = getInt() + try! getInt()
388+
// expected-error@-1 {{thrown expression type 'any Error' cannot be converted to error type 'Never'}}
389+
// expected-error@-2 {{'try!' cannot appear to the right of a non-assignment operator}}
390+
391+
// The ASTScope for `try` here covers both, but isn't covered in the folded
392+
// expression. This is illegal anyway.
393+
_ = 0 + try! getInt() + getInt()
394+
// expected-error@-1 {{'try!' cannot appear to the right of a non-assignment operator}}
395+
// expected-error@-2:27 {{call can throw but is not marked with 'try'}}
396+
// expected-note@-3:27 3{{did you mean}}
397+
398+
try _ = 0 + try! getInt() + getInt()
399+
// expected-error@-1 {{'try!' cannot appear to the right of a non-assignment operator}}
400+
401+
// The ASTScope for `try` here covers both, but isn't covered in the folded
402+
// expression due `~~~` having a lower precedence than assignment. This is
403+
// illegal anyway.
404+
_ = try! getInt() ~~~ getInt()
405+
// expected-error@-1 {{'try!' following assignment operator does not cover everything to its right}}
406+
// expected-error@-2:25 {{call can throw but is not marked with 'try'}}
407+
// expected-note@-3:25 3{{did you mean}}
408+
409+
try _ = try! getInt() ~~~ getInt()
410+
// expected-error@-1 {{'try!' following assignment operator does not cover everything to its right}}
411+
412+
// Same here.
413+
true ? 0 : try! getInt() ~~~ getInt()
414+
// expected-error@-1 {{'try!' following conditional operator does not cover everything to its right}}
415+
// expected-error@-2:32 {{call can throw but is not marked with 'try'}}
416+
// expected-note@-3:32 3{{did you mean}}
417+
418+
try true ? 0 : try! getInt() ~~~ getInt()
419+
// expected-error@-1 {{'try!' following conditional operator does not cover everything to its right}}
420+
}

0 commit comments

Comments
 (0)