Sema: Accept if #_hasSymbol() conditions in closure contexts.

Resolves rdar://100129165
This commit is contained in:
Allan Shortlidge
2022-09-27 11:20:26 -07:00
parent af276b77f5
commit 034e53ad20
7 changed files with 135 additions and 43 deletions

View File

@@ -1394,8 +1394,17 @@ public:
for (auto &elt : C) { for (auto &elt : C) {
switch (elt.getKind()) { switch (elt.getKind()) {
case StmtConditionElement::CK_Availability: case StmtConditionElement::CK_Availability:
case StmtConditionElement::CK_HasSymbol:
break; break;
case StmtConditionElement::CK_HasSymbol: {
auto E = elt.getHasSymbolInfo()->getSymbolExpr();
if (!E)
return true;
E = doIt(E);
if (!E)
return true;
elt.getHasSymbolInfo()->setSymbolExpr(E);
break;
}
case StmtConditionElement::CK_Boolean: { case StmtConditionElement::CK_Boolean: {
auto E = elt.getBoolean(); auto E = elt.getBoolean();
// Walk an expression condition normally. // Walk an expression condition normally.

View File

@@ -8923,9 +8923,23 @@ ExprWalker::rewriteTarget(SolutionApplicationTarget target) {
for (auto &condElement : *stmtCondition) { for (auto &condElement : *stmtCondition) {
switch (condElement.getKind()) { switch (condElement.getKind()) {
case StmtConditionElement::CK_Availability: case StmtConditionElement::CK_Availability:
case StmtConditionElement::CK_HasSymbol:
continue; continue;
case StmtConditionElement::CK_HasSymbol: {
ConstraintSystem &cs = solution.getConstraintSystem();
auto target = *cs.getSolutionApplicationTarget(&condElement);
auto resolvedTarget = rewriteTarget(target);
if (!resolvedTarget)
return None;
auto info = condElement.getHasSymbolInfo();
auto rewrittenExpr = resolvedTarget->getAsExpr();
info->setSymbolExpr(rewrittenExpr);
info->setReferencedDecl(
TypeChecker::getReferencedDeclForHasSymbolCondition(rewrittenExpr));
continue;
}
case StmtConditionElement::CK_Boolean: { case StmtConditionElement::CK_Boolean: {
auto condExpr = condElement.getBoolean(); auto condExpr = condElement.getBoolean();
auto finalCondExpr = condExpr->walk(*this); auto finalCondExpr = condExpr->walk(*this);

View File

@@ -4369,10 +4369,15 @@ bool ConstraintSystem::generateConstraints(StmtCondition condition,
continue; continue;
case StmtConditionElement::CK_HasSymbol: { case StmtConditionElement::CK_HasSymbol: {
ASTContext &ctx = getASTContext(); Expr *symbolExpr = condElement.getHasSymbolInfo()->getSymbolExpr();
ctx.Diags.diagnose(condElement.getStartLoc(), auto target = SolutionApplicationTarget(symbolExpr, dc, CTP_Unused,
diag::has_symbol_unsupported_in_closures); Type(), /*isDiscarded=*/false);
return true;
if (generateConstraints(target, FreeTypeVariableBinding::Disallow))
return true;
setSolutionApplicationTarget(&condElement, target);
continue;
} }
case StmtConditionElement::CK_Boolean: { case StmtConditionElement::CK_Boolean: {

View File

@@ -4302,6 +4302,50 @@ checkImplicitPromotionsInCondition(const StmtConditionElement &cond,
} }
} }
/// Perform MiscDiagnostics for the conditions belonging to a \c
/// LabeledConditionalStmt.
static void checkLabeledStmtConditions(ASTContext &ctx,
const LabeledConditionalStmt *stmt,
DeclContext *DC) {
for (auto elt : stmt->getCond()) {
// Check for implicit optional promotions in stmt-condition patterns.
checkImplicitPromotionsInCondition(elt, ctx);
switch (elt.getKind()) {
case StmtConditionElement::CK_Boolean:
case StmtConditionElement::CK_PatternBinding:
case StmtConditionElement::CK_Availability:
break;
case StmtConditionElement::CK_HasSymbol: {
auto info = elt.getHasSymbolInfo();
auto symbolExpr = info->getSymbolExpr();
if (!symbolExpr)
break;
if (!symbolExpr->getType())
break;
if (auto decl = info->getReferencedDecl().getDecl()) {
// `if #_hasSymbol(someStronglyLinkedSymbol)` is functionally a no-op
// and may indicate the developer has mis-identified the declaration
// they want to check (or forgot to import the module weakly).
if (!decl->isWeakImported(DC->getParentModule())) {
ctx.Diags.diagnose(symbolExpr->getLoc(),
diag::has_symbol_decl_must_be_weak,
decl->getDescriptiveKind(), decl->getName());
}
} else {
// Diagnose because we weren't able to interpret the expression as one
// that uniquely identifies a single declaration.
ctx.Diags.diagnose(symbolExpr->getLoc(), diag::has_symbol_invalid_expr);
}
break;
}
}
}
}
static void diagnoseUnintendedOptionalBehavior(const Expr *E, static void diagnoseUnintendedOptionalBehavior(const Expr *E,
const DeclContext *DC) { const DeclContext *DC) {
if (!E || isa<ErrorExpr>(E) || !E->getType()) if (!E || isa<ErrorExpr>(E) || !E->getType())
@@ -5290,11 +5334,9 @@ void swift::performStmtDiagnostics(const Stmt *S, DeclContext *DC) {
checkSwitch(ctx, switchStmt, DC); checkSwitch(ctx, switchStmt, DC);
checkStmtConditionTrailingClosure(ctx, S); checkStmtConditionTrailingClosure(ctx, S);
// Check for implicit optional promotions in stmt-condition patterns.
if (auto *lcs = dyn_cast<LabeledConditionalStmt>(S)) if (auto *lcs = dyn_cast<LabeledConditionalStmt>(S))
for (const auto &elt : lcs->getCond()) checkLabeledStmtConditions(ctx, lcs, DC);
checkImplicitPromotionsInCondition(elt, ctx);
if (!ctx.LangOpts.DisableAvailabilityChecking) if (!ctx.LangOpts.DisableAvailabilityChecking)
diagnoseStmtAvailability(S, const_cast<DeclContext*>(DC)); diagnoseStmtAvailability(S, const_cast<DeclContext*>(DC));

View File

@@ -448,8 +448,7 @@ static Expr *getDeclRefProvidingExpressionForHasSymbol(Expr *E) {
return E; return E;
} }
static ConcreteDeclRef ConcreteDeclRef TypeChecker::getReferencedDeclForHasSymbolCondition(Expr *E) {
getReferencedDeclForHasSymbolCondition(ASTContext &Context, Expr *E) {
// Match DotSelfExprs (e.g. `SomeStruct.self`) when the type is static. // Match DotSelfExprs (e.g. `SomeStruct.self`) when the type is static.
if (auto DSE = dyn_cast<DotSelfExpr>(E)) { if (auto DSE = dyn_cast<DotSelfExpr>(E)) {
if (DSE->isStaticallyDerivedMetatype()) if (DSE->isStaticallyDerivedMetatype())
@@ -461,7 +460,6 @@ getReferencedDeclForHasSymbolCondition(ASTContext &Context, Expr *E) {
return CDR; return CDR;
} }
Context.Diags.diagnose(E->getLoc(), diag::has_symbol_invalid_expr);
return ConcreteDeclRef(); return ConcreteDeclRef();
} }
@@ -506,20 +504,10 @@ bool TypeChecker::typeCheckStmtConditionElement(StmtConditionElement &elt,
auto exprTy = TypeChecker::typeCheckExpression(E, dc); auto exprTy = TypeChecker::typeCheckExpression(E, dc);
Info->setSymbolExpr(E); Info->setSymbolExpr(E);
if (!exprTy) if (exprTy)
return true; Info->setReferencedDecl(getReferencedDeclForHasSymbolCondition(E));
auto CDR = getReferencedDeclForHasSymbolCondition(Context, E); return !exprTy;
if (!CDR)
return true;
auto decl = CDR.getDecl();
if (!decl->isWeakImported(dc->getParentModule())) {
Context.Diags.diagnose(E->getLoc(), diag::has_symbol_decl_must_be_weak,
decl->getDescriptiveKind(), decl->getName());
}
Info->setReferencedDecl(CDR);
return false;
} }
if (auto E = elt.getBooleanOrNull()) { if (auto E = elt.getBooleanOrNull()) {

View File

@@ -441,6 +441,10 @@ Expr *substituteInputSugarTypeForResult(ApplyExpr *E);
bool typeCheckStmtConditionElement(StmtConditionElement &elt, bool &isFalsable, bool typeCheckStmtConditionElement(StmtConditionElement &elt, bool &isFalsable,
DeclContext *dc); DeclContext *dc);
/// Returns the unique decl ref identified by the expr according to the
/// requirements of the \c #_hasSymbol() condition type.
ConcreteDeclRef getReferencedDeclForHasSymbolCondition(Expr *E);
void typeCheckASTNode(ASTNode &node, DeclContext *DC, void typeCheckASTNode(ASTNode &node, DeclContext *DC,
bool LeaveBodyUnchecked = false); bool LeaveBodyUnchecked = false);

View File

@@ -93,6 +93,7 @@ func testNotWeakDeclDiagnostics(_ s: LocalStruct) {
} }
func testInvalidExpressionsDiagnostics() { func testInvalidExpressionsDiagnostics() {
if #_hasSymbol(unknownDecl) {} // expected-error {{cannot find 'unknownDecl' in scope}}
if #_hasSymbol(noArgFunc()) {} // expected-error {{#_hasSymbol condition must refer to a declaration}} if #_hasSymbol(noArgFunc()) {} // expected-error {{#_hasSymbol condition must refer to a declaration}}
if #_hasSymbol(global - 1) {} // expected-error {{#_hasSymbol condition must refer to a declaration}} if #_hasSymbol(global - 1) {} // expected-error {{#_hasSymbol condition must refer to a declaration}}
if #_hasSymbol(S.staticFunc()) {} // expected-error {{#_hasSymbol condition must refer to a declaration}} if #_hasSymbol(S.staticFunc()) {} // expected-error {{#_hasSymbol condition must refer to a declaration}}
@@ -101,15 +102,36 @@ func testInvalidExpressionsDiagnostics() {
if #_hasSymbol(1 as S) {} // expected-error {{cannot convert value of type 'Int' to type 'S' in coercion}} if #_hasSymbol(1 as S) {} // expected-error {{cannot convert value of type 'Int' to type 'S' in coercion}}
} }
func testMultiStatementClosure() { func testGuard() {
let _: () -> Void = { // expected-error {{unable to infer closure type in the current context}} guard #_hasSymbol(global) else { return }
if #_hasSymbol(global) {} // expected-error 2 {{#_hasSymbol is not supported in closures}} guard #_hasSymbol(unknownDecl) else { return } // expected-error {{cannot find 'unknownDecl' in scope}}
} guard #_hasSymbol(localFunc) else { return } // expected-warning {{global function 'localFunc()' is not a weakly linked declaration}}
}
let _: () -> Void = { // expected-error {{unable to infer closure type in the current context}}
if #_hasSymbol(global) {} // expected-error 2 {{#_hasSymbol is not supported in closures}} func testWhile() {
localFunc() while #_hasSymbol(global) { break }
} while #_hasSymbol(unknownDecl) { break } // expected-error {{cannot find 'unknownDecl' in scope}}
while #_hasSymbol(localFunc) { break } // expected-warning {{global function 'localFunc()' is not a weakly linked declaration}}
}
func doIt(_ closure: () -> ()) {
closure()
}
func testClosure() {
doIt { if #_hasSymbol(global) {} }
doIt { if #_hasSymbol(noArgFunc) {} }
doIt { if #_hasSymbol(ambiguousFunc as () -> Int) {} }
doIt { if #_hasSymbol(S.self) {} }
doIt { if #_hasSymbol(ambiguousFunc) {} } // expected-error {{ambiguous use of 'ambiguousFunc()'}}
doIt { if #_hasSymbol(localFunc) {} } // expected-warning {{global function 'localFunc()' is not a weakly linked declaration}}
doIt { if #_hasSymbol(unknownDecl) {} } // expected-error {{cannot find 'unknownDecl' in scope}}
doIt { if #_hasSymbol(noArgFunc()) {} } // expected-error {{#_hasSymbol condition must refer to a declaration}}
doIt { if #_hasSymbol(global - 1) {} } // expected-error {{#_hasSymbol condition must refer to a declaration}}
doIt { if #_hasSymbol(S.staticFunc()) {} } // expected-error {{#_hasSymbol condition must refer to a declaration}}
doIt { if #_hasSymbol(C.classFunc()) {} } // expected-error {{#_hasSymbol condition must refer to a declaration}}
doIt { if #_hasSymbol(1 as Int) {} } // expected-error {{#_hasSymbol condition must refer to a declaration}}
doIt { if #_hasSymbol(1 as S) {} } // expected-error {{cannot convert value of type 'Int' to type 'S' in coercion}}
} }
protocol View {} protocol View {}
@@ -120,15 +142,23 @@ protocol View {}
static func buildEither<Content>(second content: Content) -> Content where Content : View { fatalError() } static func buildEither<Content>(second content: Content) -> Content where Content : View { fatalError() }
} }
struct Image : View { struct Image : View {}
}
struct MyView { struct MyView {
@ViewBuilder var body: some View { let image = Image()
if #_hasSymbol(global) { // expected-error {{#_hasSymbol is not supported in closures}}
Image() @ViewBuilder var globalView: some View {
} else { if #_hasSymbol(global) { image }
Image() else { image }
} }
@ViewBuilder var ambiguousFuncView: some View {
if #_hasSymbol(ambiguousFunc) { image } // expected-error {{ambiguous use of 'ambiguousFunc()'}}
else { image }
}
@ViewBuilder var localFuncView: some View {
if #_hasSymbol(localFunc) { image } // expected-warning {{global function 'localFunc()' is not a weakly linked declaration}}
else { image }
} }
} }