Skip to content

Commit 7217eb3

Browse files
committed
[Performance Hints] Implement check for existential any
This commit introduces a performance hint check that warns on the use of existential any in variable declarations, function and closure parameters and return, and typealiases.
1 parent 25a1e42 commit 7217eb3

File tree

4 files changed

+502
-2
lines changed

4 files changed

+502
-2
lines changed

include/swift/AST/DiagnosticEngine.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ namespace swift {
4848
class FuncDecl;
4949
class SourceManager;
5050
class SourceFile;
51+
class ParamDecl;
52+
class AnyPattern;
5153

5254
/// Enumeration describing all of possible diagnostics.
5355
///

include/swift/AST/DiagnosticsSema.def

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8946,6 +8946,30 @@ GROUPED_WARNING(perf_hint_closure_returns_array,ReturnTypeImplicitCopy,DefaultIg
89468946
"Performance: closure returns a%select{ dictionary|n array}0, leading to implicit copies. "
89478947
"Consider using an 'inout' parameter instead.", (bool))
89488948

8949+
GROUPED_WARNING(perf_hint_param_has_existential,PerformanceHints,DefaultIgnore,
8950+
"Performance: %0 expects existential any, leading to heap allocation, reference counting, "
8951+
"and dynamic dispatch. Consider using generic constraints or concrete types instead.",
8952+
(const ParamDecl*))
8953+
GROUPED_WARNING(perf_hint_func_returns_existential,PerformanceHints,DefaultIgnore,
8954+
"Performance: %0 returns existential any, leading to heap allocation, reference counting, "
8955+
"and dynamic dispatch. Consider using generic constraints or concrete types instead.",
8956+
(const FuncDecl*))
8957+
GROUPED_WARNING(perf_hint_closure_returns_existential,PerformanceHints,DefaultIgnore,
8958+
"Performance: closure returns existential any, leading to heap allocation, reference counting, "
8959+
"and dynamic dispatch. Consider using generic constraints or concrete types instead.", ())
8960+
GROUPED_WARNING(perf_hint_var_uses_existential,PerformanceHints,DefaultIgnore,
8961+
"Performance: %0 uses existential any, leading to heap allocation, reference counting, "
8962+
"and dynamic dispatch. Consider using generic constraints or concrete types instead.",
8963+
(const VarDecl *))
8964+
GROUPED_WARNING(perf_hint_pat_uses_existential,PerformanceHints,DefaultIgnore,
8965+
"Performance: Ignored value uses existential any, leading to heap allocation, reference counting, "
8966+
"and dynamic dispatch. Consider using generic constraints or concrete types instead.",
8967+
())
8968+
GROUPED_WARNING(perf_hint_typealias_uses_existential,PerformanceHints,DefaultIgnore,
8969+
"Performance: %0 aliases existential any type, leading to heap allocation, reference counting, "
8970+
"and dynamic dispatch. Consider using generic constraints or concrete types instead.",
8971+
(const TypeAliasDecl *))
8972+
89498973
ERROR(unsafe_self_dependent_result_attr_on_invalid_decl,none,
89508974
"invalid use of @_unsafeSelfDependentResult", ())
89518975

lib/Sema/PerformanceHints.cpp

Lines changed: 132 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "swift/AST/Evaluator.h"
2626
#include "swift/AST/Expr.h"
2727
#include "swift/AST/TypeCheckRequests.h"
28+
#include "swift/AST/TypeVisitor.h"
2829

2930
using namespace swift;
3031

@@ -52,6 +53,88 @@ void checkImplicitCopyReturnType(const ClosureExpr *Closure,
5253
}
5354
}
5455

56+
class HasExistentialAnyType : public TypeVisitor<HasExistentialAnyType, bool> {
57+
public:
58+
static bool check(Type type) {
59+
return HasExistentialAnyType().visit(type->getCanonicalType());
60+
}
61+
62+
bool visitExistentialType(ExistentialType *ET) {
63+
return true;
64+
}
65+
66+
bool visitTupleType(TupleType *TT) {
67+
for (const auto &element : TT->getElements()) {
68+
if (visit(element.getType())) {
69+
return true;
70+
}
71+
}
72+
return false;
73+
}
74+
75+
bool visitBoundGenericType(BoundGenericType *BGT) {
76+
// Check generic arguments (e.g., Array<any Protocol>)
77+
for (Type arg : BGT->getGenericArgs()) {
78+
if (visit(arg)) {
79+
return true;
80+
}
81+
}
82+
return false;
83+
}
84+
85+
bool visitFunctionType(FunctionType *FT) {
86+
for (const auto &param : FT->getParams()) {
87+
if (visit(param.getPlainType()->getCanonicalType())) {
88+
return true;
89+
}
90+
}
91+
92+
return visit(FT->getResult()->getCanonicalType());
93+
}
94+
95+
bool visitType(TypeBase *T) {
96+
return false;
97+
}
98+
};
99+
100+
void checkExistentialAnyReturnType(FuncDecl *FD, DiagnosticEngine &Diags) {
101+
Type T = FD->getResultInterfaceType();
102+
103+
if (HasExistentialAnyType::check(T))
104+
Diags.diagnose(FD, diag::perf_hint_func_returns_existential, FD);
105+
}
106+
107+
void checkExistentialAnyReturnType(ClosureExpr *CE, DiagnosticEngine &Diags) {
108+
Type T = CE->getResultType();
109+
110+
if (HasExistentialAnyType::check(T))
111+
Diags.diagnose(CE->getLoc(), diag::perf_hint_closure_returns_existential);
112+
}
113+
114+
void checkExistentialAnyVarType(const VarDecl *VD, DiagnosticEngine &Diags) {
115+
Type T = VD->getInterfaceType();
116+
117+
if (HasExistentialAnyType::check(T))
118+
Diags.diagnose(VD, diag::perf_hint_var_uses_existential, VD);
119+
}
120+
121+
void checkExistentialAnyPatternType(const AnyPattern *AP,
122+
DiagnosticEngine &Diags) {
123+
Type T = AP->getType();
124+
125+
if (HasExistentialAnyType::check(T))
126+
Diags.diagnose(AP->getLoc(), diag::perf_hint_pat_uses_existential);
127+
}
128+
129+
void checkExistentialAnyTypeAlias(const TypeAliasDecl *TAD,
130+
DiagnosticEngine &Diags) {
131+
Type T = TAD->getUnderlyingType();
132+
133+
if (HasExistentialAnyType::check(T))
134+
Diags.diagnose(TAD->getLoc(), diag::perf_hint_typealias_uses_existential,
135+
TAD);
136+
}
137+
55138
/// Produce performance hint diagnostics for a SourceFile.
56139
class PerformanceHintDiagnosticWalker final : public ASTWalker {
57140
ASTContext &Ctx;
@@ -64,16 +147,63 @@ class PerformanceHintDiagnosticWalker final : public ASTWalker {
64147
SF->walk(Walker);
65148
}
66149

150+
PreWalkResult<Pattern *> walkToPatternPre(Pattern *P) override {
151+
if (P->isImplicit())
152+
return Action::SkipNode(P);
153+
154+
if (const AnyPattern *AP = dyn_cast<AnyPattern>(P)) {
155+
checkExistentialAnyPatternType(AP, Ctx.Diags);
156+
}
157+
158+
return Action::Continue(P);
159+
}
160+
67161
PreWalkResult<Expr *> walkToExprPre(Expr *E) override {
68-
if (auto Closure = dyn_cast<ClosureExpr>(E))
162+
if (E->isImplicit())
163+
return Action::SkipNode(E);
164+
165+
if (const ClosureExpr* Closure = dyn_cast<ClosureExpr>(E)) {
69166
checkImplicitCopyReturnType(Closure, Ctx.Diags);
167+
}
168+
169+
return Action::Continue(E);
170+
}
171+
172+
PostWalkResult<Expr *> walkToExprPost(Expr *E) override {
173+
assert(
174+
!E->isImplicit() &&
175+
"Traversing implicit expressions is disabled in the pre-walk visitor");
176+
177+
if (auto Closure = dyn_cast<ClosureExpr>(E)) {
178+
checkExistentialAnyReturnType(Closure, Ctx.Diags);
179+
}
70180

71181
return Action::Continue(E);
72182
}
73183

74184
PreWalkAction walkToDeclPre(Decl *D) override {
75-
if (auto *FD = dyn_cast<FuncDecl>(D))
185+
if (D->isImplicit())
186+
return Action::SkipNode();
187+
188+
if (const FuncDecl *FD = dyn_cast<FuncDecl>(D)) {
76189
checkImplicitCopyReturnType(FD, Ctx.Diags);
190+
} else if (const VarDecl *VD = dyn_cast<VarDecl>(D)) {
191+
checkExistentialAnyVarType(VD, Ctx.Diags);
192+
} else if (const TypeAliasDecl *TAD = dyn_cast<TypeAliasDecl>(D)) {
193+
checkExistentialAnyTypeAlias(TAD, Ctx.Diags);
194+
}
195+
196+
return Action::Continue();
197+
}
198+
199+
PostWalkAction walkToDeclPost(Decl *D) override {
200+
assert(
201+
!D->isImplicit() &&
202+
"Traversing implicit declarations is disabled in the pre-walk visitor");
203+
204+
if (auto *FD = dyn_cast<FuncDecl>(D)) {
205+
checkExistentialAnyReturnType(FD, Ctx.Diags);
206+
}
77207

78208
return Action::Continue();
79209
}

0 commit comments

Comments
 (0)