Skip to content

Commit ba46170

Browse files
committed
Implement type checking for the #_hasSymbol directive. The directive takes an expression which must resolve at compile time to a single concrete declaration. There are a few kinds of expressions which we intend to accept:
- Unapplied functions: `if #_hasSymbol(foo(_:))` - Member references: `if #_hasSymbol(a.x)` - `.self` expressions on static metatypes: `if #_hasSymbol(SomeEnum.self)` - Dot syntax call expressions with unapplied functions: `if #_hasSymbol(SomeType.init(_:)` Additionally, we diagnose when the concretely referenced declaration is not weak linked since this likely indicates that the programmer misidentified the declaration they wish to check or the build is not configured in a way such that the check will be meaningful. Resolves rdar://99826340
1 parent fa994e7 commit ba46170

File tree

6 files changed

+267
-11
lines changed

6 files changed

+267
-11
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6577,5 +6577,15 @@ ERROR(type_wrapper_ignored_on_static_properties,none,
65776577
ERROR(type_wrapper_ignored_on_lazy_properties,none,
65786578
"%0 must not be used on lazy properties", (DeclAttribute))
65796579

6580+
//------------------------------------------------------------------------------
6581+
// MARK: #_hasSymbol
6582+
//------------------------------------------------------------------------------
6583+
6584+
WARNING(has_symbol_decl_must_be_weak,none,
6585+
"%0 %1 is not a weakly linked declaration",
6586+
(DescriptiveDeclKind, DeclName))
6587+
ERROR(has_symbol_invalid_expr,none,
6588+
"#_hasSymbol condition must refer to a declaration", ())
6589+
65806590
#define UNDEFINE_DIAGNOSTIC_MACROS
65816591
#include "DefineDiagnosticMacros.h"

include/swift/AST/Stmt.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -397,15 +397,16 @@ class alignas(8) PoundAvailableInfo final :
397397
///
398398
class PoundHasSymbolInfo final : public ASTAllocated<PoundHasSymbolInfo> {
399399
Expr *SymbolExpr;
400+
ConcreteDeclRef ReferencedDecl;
400401

401402
SourceLoc PoundLoc;
402403
SourceLoc LParenLoc;
403404
SourceLoc RParenLoc;
404405

405406
PoundHasSymbolInfo(SourceLoc PoundLoc, SourceLoc LParenLoc, Expr *SymbolExpr,
406407
SourceLoc RParenLoc)
407-
: SymbolExpr(SymbolExpr), PoundLoc(PoundLoc), LParenLoc(LParenLoc),
408-
RParenLoc(RParenLoc){};
408+
: SymbolExpr(SymbolExpr), ReferencedDecl(), PoundLoc(PoundLoc),
409+
LParenLoc(LParenLoc), RParenLoc(RParenLoc){};
409410

410411
public:
411412
static PoundHasSymbolInfo *create(ASTContext &Ctx, SourceLoc PoundLoc,
@@ -415,6 +416,9 @@ class PoundHasSymbolInfo final : public ASTAllocated<PoundHasSymbolInfo> {
415416
Expr *getSymbolExpr() const { return SymbolExpr; }
416417
void setSymbolExpr(Expr *E) { SymbolExpr = E; }
417418

419+
ConcreteDeclRef getReferencedDecl() { return ReferencedDecl; }
420+
void setReferencedDecl(ConcreteDeclRef CDR) { ReferencedDecl = CDR; }
421+
418422
SourceLoc getLParenLoc() const { return LParenLoc; }
419423
SourceLoc getRParenLoc() const { return RParenLoc; }
420424
SourceLoc getStartLoc() const { return PoundLoc; }

lib/Sema/TypeCheckStmt.cpp

Lines changed: 66 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,52 @@ LabeledStmt *swift::findBreakOrContinueStmtTarget(
419419
return nullptr;
420420
}
421421

422+
static Expr *getDeclRefProvidingExpressionForHasSymbol(Expr *E) {
423+
// Strip coercions, which are necessary in source to disambiguate overloaded
424+
// functions or generic functions, e.g.
425+
//
426+
// if #_hasSymbol(foo as () -> ()) { ... }
427+
//
428+
if (auto CE = dyn_cast<CoerceExpr>(E))
429+
return getDeclRefProvidingExpressionForHasSymbol(CE->getSubExpr());
430+
431+
// Unwrap curry thunks which are injected into the AST to wrap some forms of
432+
// unapplied method references, e.g.
433+
//
434+
// if #_hasSymbol(SomeStruct.foo(_:)) { ... }
435+
//
436+
if (auto ACE = dyn_cast<AutoClosureExpr>(E))
437+
return getDeclRefProvidingExpressionForHasSymbol(
438+
ACE->getUnwrappedCurryThunkExpr());
439+
440+
// Drill into the function expression for a DotSyntaxCallExpr. These sometimes
441+
// wrap or are wrapped by an AutoClosureExpr.
442+
//
443+
// if #_hasSymbol(someStruct.foo(_:)) { ... }
444+
//
445+
if (auto DSCE = dyn_cast<DotSyntaxCallExpr>(E))
446+
return getDeclRefProvidingExpressionForHasSymbol(DSCE->getFn());
447+
448+
return E;
449+
}
450+
451+
static ConcreteDeclRef
452+
getReferencedDeclForHasSymbolCondition(ASTContext &Context, Expr *E) {
453+
// Match DotSelfExprs (e.g. `SomeStruct.self`) when the type is static.
454+
if (auto DSE = dyn_cast<DotSelfExpr>(E)) {
455+
if (DSE->isStaticallyDerivedMetatype())
456+
return DSE->getType()->getMetatypeInstanceType()->getAnyNominal();
457+
}
458+
459+
if (auto declRefExpr = getDeclRefProvidingExpressionForHasSymbol(E)) {
460+
if (auto CDR = declRefExpr->getReferencedDecl())
461+
return CDR;
462+
}
463+
464+
Context.Diags.diagnose(E->getLoc(), diag::has_symbol_invalid_expr);
465+
return ConcreteDeclRef();
466+
}
467+
422468
bool TypeChecker::typeCheckStmtConditionElement(StmtConditionElement &elt,
423469
bool &isFalsable,
424470
DeclContext *dc) {
@@ -450,15 +496,29 @@ bool TypeChecker::typeCheckStmtConditionElement(StmtConditionElement &elt,
450496

451497
// Typecheck a #_hasSymbol condition.
452498
if (elt.getKind() == StmtConditionElement::CK_HasSymbol) {
499+
isFalsable = true;
500+
453501
auto Info = elt.getHasSymbolInfo();
454502
auto E = Info->getSymbolExpr();
455-
if (E) {
456-
// FIXME: Implement #_hasSymbol typechecking.
457-
(void)TypeChecker::typeCheckExpression(E, dc);
458-
Info->setSymbolExpr(E);
459-
}
460-
isFalsable = true;
503+
if (!E)
504+
return false;
461505

506+
auto exprTy = TypeChecker::typeCheckExpression(E, dc);
507+
Info->setSymbolExpr(E);
508+
509+
if (!exprTy)
510+
return true;
511+
512+
auto CDR = getReferencedDeclForHasSymbolCondition(Context, E);
513+
if (!CDR)
514+
return true;
515+
516+
auto decl = CDR.getDecl();
517+
if (!decl->isWeakImported(dc->getParentModule())) {
518+
Context.Diags.diagnose(E->getLoc(), diag::has_symbol_decl_must_be_weak,
519+
decl->getDescriptiveKind(), decl->getName());
520+
}
521+
Info->setReferencedDecl(CDR);
462522
return false;
463523
}
464524

test/Parse/has_symbol.swift

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
1-
// RUN: %target-typecheck-verify-swift
1+
// RUN: %empty-directory(%t)
2+
// RUN: split-file %s %t
3+
// RUN: %target-swift-frontend -emit-module -emit-module-path %t/Library.swiftmodule -parse-as-library %t/Library.swift -enable-library-evolution
4+
// RUN: %target-swift-frontend -typecheck -verify %t/Client.swift -I %t
25

3-
func foo() {}
4-
func bar() {}
6+
// UNSUPPORTED: OS=windows-msvc
7+
8+
//--- Library.swift
9+
10+
public func foo() {}
11+
public func bar() {}
12+
13+
//--- Client.swift
14+
15+
@_weakLinked import Library
516

617
if #_hasSymbol(foo) {}
718
if #_hasSymbol(foo), #_hasSymbol(bar) {}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
public let global: Int = 0
2+
3+
public func noArgFunc() {}
4+
public func function(with argument: Int) {}
5+
public func throwingFunc() throws {}
6+
public func ambiguousFunc() {}
7+
public func ambiguousFunc() -> Int { return 0 }
8+
public func genericFunc<T: P>(_ t: T) {}
9+
10+
public protocol P {
11+
func requirement()
12+
func requirementWithDefaultImpl()
13+
}
14+
15+
extension P {
16+
public func requirementWithDefaultImpl() {}
17+
}
18+
19+
public struct S {
20+
public static var staticMember: Int = 0
21+
public static func staticFunc() {}
22+
23+
public var member: Int
24+
25+
public init(member: Int) {
26+
self.member = member
27+
}
28+
public func noArgsMethod() {}
29+
public func method(with argument: Int) {}
30+
public func genericFunc<T: P>(_ t: T) {}
31+
}
32+
33+
extension S: P {
34+
public func requirement() {}
35+
}
36+
37+
public struct GenericS<T: P> {
38+
public var member: T
39+
40+
public init(member: T) {
41+
self.member = member
42+
}
43+
public func noArgsMethod() {}
44+
public func method(with argument: T) {}
45+
}
46+
47+
public class C {
48+
public static var staticMember: Int = 0
49+
public class func classFunc() {}
50+
51+
public var member: Int
52+
53+
public init(member: Int) {
54+
self.member = member
55+
}
56+
public func noArgsMethod() {}
57+
public func method(with argument: Int) {}
58+
}
59+
60+
extension C: P {
61+
public func requirement() {}
62+
}
63+
64+
public enum E {
65+
case basicCase
66+
case payloadCase(_: S)
67+
68+
public func method() {}
69+
}

test/Sema/has_symbol.swift

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend -emit-module -emit-module-path %t/has_symbol_helper.swiftmodule -parse-as-library %S/Inputs/has_symbol_helper.swift -enable-library-evolution
3+
// RUN: %target-typecheck-verify-swift -I %t
4+
5+
// UNSUPPORTED: OS=windows-msvc
6+
7+
@_weakLinked import has_symbol_helper
8+
9+
func testGlobalFunctions() {
10+
if #_hasSymbol(noArgFunc) {}
11+
if #_hasSymbol(function(with:)) {}
12+
if #_hasSymbol(throwingFunc) {}
13+
if #_hasSymbol(ambiguousFunc) {} // expected-error {{ambiguous use of 'ambiguousFunc()'}}
14+
if #_hasSymbol(ambiguousFunc as () -> Int) {}
15+
if #_hasSymbol(genericFunc(_:) as (S) -> Void) {}
16+
}
17+
18+
func testVars() {
19+
if #_hasSymbol(global) {}
20+
if #_hasSymbol(global as Int) {}
21+
}
22+
23+
func testStruct(_ s: S) {
24+
if #_hasSymbol(s.member) {}
25+
if #_hasSymbol(s.noArgsMethod) {}
26+
if #_hasSymbol(s.method(with:)) {}
27+
if #_hasSymbol(s.genericFunc(_:)) {} // expected-error {{generic parameter 'T' could not be inferred}}
28+
if #_hasSymbol(s.genericFunc(_:) as (S) -> ()) {}
29+
if #_hasSymbol(s.requirement) {}
30+
if #_hasSymbol(s.requirementWithDefaultImpl) {}
31+
32+
if #_hasSymbol(S.staticFunc) {}
33+
if #_hasSymbol(S.staticMember) {}
34+
if #_hasSymbol(S.init(member:)) {}
35+
}
36+
37+
func testGenericStruct(_ s: GenericS<S>) {
38+
if #_hasSymbol(s.member) {}
39+
if #_hasSymbol(s.noArgsMethod) {}
40+
if #_hasSymbol(s.method(with:)) {}
41+
}
42+
43+
func testClass(_ c: C) {
44+
if #_hasSymbol(c.member) {}
45+
if #_hasSymbol(c.noArgsMethod) {}
46+
if #_hasSymbol(c.method(with:)) {}
47+
if #_hasSymbol(c.requirement) {}
48+
if #_hasSymbol(c.requirementWithDefaultImpl) {}
49+
50+
if #_hasSymbol(C.classFunc) {}
51+
if #_hasSymbol(C.staticMember) {}
52+
if #_hasSymbol(C.init(member:)) {}
53+
}
54+
55+
func testEnum(_ e: E) {
56+
if #_hasSymbol(E.basicCase) {}
57+
if #_hasSymbol(E.payloadCase) {}
58+
if #_hasSymbol(E.payloadCase(_:)) {}
59+
if #_hasSymbol(e.method) {}
60+
}
61+
62+
func testMetatypes() {
63+
if #_hasSymbol(P.self) {}
64+
if #_hasSymbol(S.self) {}
65+
if #_hasSymbol(GenericS.self) {} // expected-error {{generic parameter 'T' could not be inferred}} expected-note {{explicitly specify the generic arguments to fix this issue}}
66+
if #_hasSymbol(GenericS<S>.self) {}
67+
if #_hasSymbol(C.self) {}
68+
if #_hasSymbol(E.self) {}
69+
}
70+
71+
var localGlobal: Int = 0
72+
73+
func localFunc() {}
74+
75+
struct LocalStruct {
76+
var member: Int = 0
77+
}
78+
79+
protocol LocalProtocol {}
80+
81+
enum LocalEnum {
82+
case a
83+
}
84+
85+
func testNotWeakDeclDiagnostics(_ s: LocalStruct) {
86+
if #_hasSymbol(localFunc) {} // expected-warning {{global function 'localFunc()' is not a weakly linked declaration}}
87+
if #_hasSymbol(localGlobal) {} // expected-warning {{var 'localGlobal' is not a weakly linked declaration}}
88+
if #_hasSymbol(s) {} // expected-warning {{parameter 's' is not a weakly linked declaration}}
89+
if #_hasSymbol(s.member) {} // expected-warning {{property 'member' is not a weakly linked declaration}}
90+
if #_hasSymbol(LocalEnum.a) {} // expected-warning {{enum case 'a' is not a weakly linked declaration}}
91+
if #_hasSymbol(LocalStruct.self) {} // expected-warning {{struct 'LocalStruct' is not a weakly linked declaration}}
92+
if #_hasSymbol(LocalProtocol.self) {} // expected-warning {{protocol 'LocalProtocol' is not a weakly linked declaration}}
93+
}
94+
95+
func testInvalidExpressionsDiagnostics() {
96+
if #_hasSymbol(noArgFunc()) {} // expected-error {{#_hasSymbol condition must refer to a declaration}}
97+
if #_hasSymbol(global - 1) {} // expected-error {{#_hasSymbol condition must refer to a declaration}}
98+
if #_hasSymbol(S.staticFunc()) {} // expected-error {{#_hasSymbol condition must refer to a declaration}}
99+
if #_hasSymbol(C.classFunc()) {} // expected-error {{#_hasSymbol condition must refer to a declaration}}
100+
if #_hasSymbol(1 as Int) {} // expected-error {{#_hasSymbol condition must refer to a declaration}}
101+
if #_hasSymbol(1 as S) {} // expected-error {{cannot convert value of type 'Int' to type 'S' in coercion}}
102+
}

0 commit comments

Comments
 (0)