mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
Improve Fix-It for if let x where x is a reference to an unsafe value
When we encounter unsafe code in `if let x`, we would produce a Fix-It
that would change it to the ill-formed `if let unsafe x`. Improve
tracking of the expressions that are synthesized for the right-hand
side of these conditions, so that we can produce a Fix-It that turns
this into the proper
if let x = unsafe x
Fixes rdar://147944243.
This commit is contained in:
@@ -668,7 +668,10 @@ public:
|
||||
recurse = asImpl().checkThrow(thr);
|
||||
} else if (auto forEach = dyn_cast<ForEachStmt>(S)) {
|
||||
recurse = asImpl().checkForEach(forEach);
|
||||
} else if (auto labeled = dyn_cast<LabeledConditionalStmt>(S)) {
|
||||
asImpl().noteLabeledConditionalStmt(labeled);
|
||||
}
|
||||
|
||||
if (!recurse)
|
||||
return Action::SkipNode(S);
|
||||
|
||||
@@ -690,6 +693,8 @@ public:
|
||||
}
|
||||
|
||||
void visitExprPre(Expr *expr) { asImpl().visitExprPre(expr); }
|
||||
|
||||
void noteLabeledConditionalStmt(LabeledConditionalStmt *stmt) { }
|
||||
};
|
||||
|
||||
/// A potential reason why something might have an effect.
|
||||
@@ -3416,6 +3421,10 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
|
||||
/// passed directly into an explicitly `@safe` function.
|
||||
llvm::DenseSet<const Expr *> assumedSafeArguments;
|
||||
|
||||
/// Keeps track of the expressions that were synthesized as initializers for
|
||||
/// the "if let x" shorthand syntax.
|
||||
llvm::SmallPtrSet<const Expr *, 4> synthesizedIfLetInitializers;
|
||||
|
||||
/// Tracks all of the uncovered uses of unsafe constructs based on their
|
||||
/// anchor expression, so we can emit diagnostics at the end.
|
||||
llvm::MapVector<Expr *, std::vector<UnsafeUse>> uncoveredUnsafeUses;
|
||||
@@ -4425,7 +4434,67 @@ private:
|
||||
Ctx.Diags.diagnose(E->getUnsafeLoc(), diag::no_unsafe_in_unsafe)
|
||||
.fixItRemove(E->getUnsafeLoc());
|
||||
}
|
||||
|
||||
|
||||
void noteLabeledConditionalStmt(LabeledConditionalStmt *stmt) {
|
||||
// Make a note of any initializers that are the synthesized right-hand side
|
||||
// for an "if let x".
|
||||
for (const auto &condition: stmt->getCond()) {
|
||||
switch (condition.getKind()) {
|
||||
case StmtConditionElement::CK_Availability:
|
||||
case StmtConditionElement::CK_Boolean:
|
||||
case StmtConditionElement::CK_HasSymbol:
|
||||
continue;
|
||||
|
||||
case StmtConditionElement::CK_PatternBinding:
|
||||
break;
|
||||
}
|
||||
|
||||
auto init = condition.getInitializer();
|
||||
if (!init)
|
||||
continue;
|
||||
|
||||
auto pattern = condition.getPattern();
|
||||
if (!pattern)
|
||||
continue;
|
||||
|
||||
auto optPattern = dyn_cast<OptionalSomePattern>(pattern);
|
||||
if (!optPattern)
|
||||
continue;
|
||||
|
||||
auto var = optPattern->getSubPattern()->getSingleVar();
|
||||
if (!var)
|
||||
continue;
|
||||
|
||||
// If the right-hand side has the same location as the variable, it was
|
||||
// synthesized.
|
||||
if (var->getLoc().isValid() &&
|
||||
var->getLoc() == init->getStartLoc() &&
|
||||
init->getStartLoc() == init->getEndLoc())
|
||||
synthesizedIfLetInitializers.insert(init);
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine whether this is the synthesized right-hand-side when we have
|
||||
/// expanded an "if let x" into its semantic equivalent, "if let x = x".
|
||||
VarDecl *isShorthandIfLetSyntax(const Expr *expr) const {
|
||||
// Check whether this is referencing a variable.
|
||||
VarDecl *var = nullptr;
|
||||
if (auto declRef = dyn_cast<DeclRefExpr>(expr)) {
|
||||
var = dyn_cast_or_null<VarDecl>(declRef->getDecl());
|
||||
} else if (auto memberRef = dyn_cast<MemberRefExpr>(expr)) {
|
||||
var = dyn_cast_or_null<VarDecl>(memberRef->getMember().getDecl());
|
||||
}
|
||||
|
||||
if (!var)
|
||||
return nullptr;
|
||||
|
||||
// If we identified this as one of the bindings, return the variable.
|
||||
if (synthesizedIfLetInitializers.contains(expr))
|
||||
return var;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::pair<SourceLoc, std::string>
|
||||
getFixItForUncoveredSite(const Expr *anchor, StringRef keyword) const {
|
||||
SourceLoc insertLoc = anchor->getStartLoc();
|
||||
@@ -4438,13 +4507,10 @@ private:
|
||||
insertLoc = tryExpr->getSubExpr()->getStartLoc();
|
||||
// Supply a tailored fixIt including the identifier if we are
|
||||
// looking at a shorthand optional binding.
|
||||
} else if (anchor->isImplicit()) {
|
||||
if (auto declRef = dyn_cast<DeclRefExpr>(anchor))
|
||||
if (auto var = dyn_cast_or_null<VarDecl>(declRef->getDecl())) {
|
||||
insertText = (" = " + keyword).str() + " " + var->getNameStr().str();
|
||||
insertLoc = Lexer::getLocForEndOfToken(Ctx.Diags.SourceMgr,
|
||||
anchor->getStartLoc());
|
||||
}
|
||||
} else if (auto var = isShorthandIfLetSyntax(anchor)) {
|
||||
insertText = (" = " + keyword).str() + " " + var->getNameStr().str();
|
||||
insertLoc = Lexer::getLocForEndOfToken(Ctx.Diags.SourceMgr,
|
||||
anchor->getStartLoc());
|
||||
}
|
||||
return std::make_pair(insertLoc, insertText);
|
||||
}
|
||||
|
||||
@@ -249,3 +249,22 @@ func testUnsafeLHS() {
|
||||
default: 0
|
||||
}
|
||||
}
|
||||
|
||||
@safe
|
||||
struct UnsafeWrapTest {
|
||||
@unsafe var pointer: UnsafeMutablePointer<Int>?
|
||||
|
||||
func test() {
|
||||
if let pointer { // expected-warning{{expression uses unsafe constructs but is not marked with 'unsafe'}}{{19-19= = unsafe pointer}}
|
||||
// expected-note@-1{{reference to unsafe property 'pointer'}}
|
||||
_ = unsafe pointer
|
||||
}
|
||||
}
|
||||
|
||||
func otherTest(pointer: UnsafeMutablePointer<Int>?) {
|
||||
if let pointer { // expected-warning{{expression uses unsafe constructs but is not marked with 'unsafe'}}{{19-19= = unsafe pointer}}
|
||||
// expected-note@-1{{reference to parameter 'pointer' involves unsafe type 'UnsafeMutablePointer<Int>}}
|
||||
_ = unsafe pointer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,7 +232,7 @@ func testAsyncExprWithoutAwait() async {
|
||||
if let result = result {} // expected-error {{expression is 'async' but is not marked with 'await'}} {{19-19=await }}
|
||||
// expected-warning@-1 {{value 'result' was defined but never used; consider replacing with boolean test}}
|
||||
// expected-note@-2 {{reference to async let 'result' is 'async'}}
|
||||
if let result {} // expected-error {{expression is 'async' but is not marked with 'await'}} {{10-10=await }}
|
||||
if let result {} // expected-error {{expression is 'async' but is not marked with 'await'}} {{16-16= = await result}}
|
||||
// expected-warning@-1 {{value 'result' was defined but never used; consider replacing with boolean test}}
|
||||
// expected-note@-2 {{reference to async let 'result' is 'async'}}
|
||||
let a = f("a") // expected-error {{expression is 'async' but is not marked with 'await'}} {{11-11=await }}
|
||||
|
||||
Reference in New Issue
Block a user