Skip to content

Commit 13a5c2d

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 d06d126 commit 13a5c2d

File tree

5 files changed

+521
-4
lines changed

5 files changed

+521
-4
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/DiagnosticGroups.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ GROUP(WeakMutability, "weak-mutability")
8282
GROUP(PerformanceHints, "performance-hints")
8383
GROUP(ReturnTypeImplicitCopy, "return-type-implicit-copy")
8484
GROUP_LINK(PerformanceHints, ReturnTypeImplicitCopy)
85+
GROUP(ExistentialAnyType, "existential-any-type")
86+
GROUP_LINK(PerformanceHints, ExistentialAnyType)
8587

8688
#define UNDEFINE_DIAGNOSTIC_GROUPS_MACROS
8789
#include "swift/AST/DefineDiagnosticGroupsMacros.h"

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_expects_existential_any,ExistentialAnyType,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_any,ExistentialAnyType,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_any,ExistentialAnyType,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_any,ExistentialAnyType,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_any_pattern_uses_existential_any,ExistentialAnyType,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_any,ExistentialAnyType,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: 148 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,27 @@
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

3132
bool swift::performanceHintDiagnosticsEnabled(ASTContext &ctx) {
32-
return !ctx.Diags.isIgnoredDiagnostic(diag::perf_hint_closure_returns_array.ID) ||
33-
!ctx.Diags.isIgnoredDiagnostic(diag::perf_hint_function_returns_array.ID);
33+
return !ctx.Diags.isIgnoredDiagnostic(
34+
diag::perf_hint_closure_returns_array.ID) ||
35+
!ctx.Diags.isIgnoredDiagnostic(
36+
diag::perf_hint_function_returns_array.ID) ||
37+
!ctx.Diags.isIgnoredDiagnostic(
38+
diag::perf_hint_param_expects_existential_any.ID) ||
39+
!ctx.Diags.isIgnoredDiagnostic(
40+
diag::perf_hint_func_returns_existential_any.ID) ||
41+
!ctx.Diags.isIgnoredDiagnostic(
42+
diag::perf_hint_closure_returns_existential_any.ID) ||
43+
!ctx.Diags.isIgnoredDiagnostic(
44+
diag::perf_hint_var_uses_existential_any.ID) ||
45+
!ctx.Diags.isIgnoredDiagnostic(
46+
diag::perf_hint_any_pattern_uses_existential_any.ID) ||
47+
!ctx.Diags.isIgnoredDiagnostic(
48+
diag::perf_hint_typealias_uses_existential_any.ID);
3449
}
3550

3651
namespace {
@@ -52,6 +67,88 @@ void checkImplicitCopyReturnType(const ClosureExpr *Closure,
5267
}
5368
}
5469

70+
class CheckExistentialAny : public TypeVisitor<CheckExistentialAny, bool> {
71+
public:
72+
static bool inType(Type type) {
73+
return CheckExistentialAny().visit(type->getCanonicalType());
74+
}
75+
76+
static void inFunctionReturnType(FuncDecl *FD, DiagnosticEngine &Diags) {
77+
Type T = FD->getResultInterfaceType();
78+
79+
if (inType(T))
80+
Diags.diagnose(FD, diag::perf_hint_func_returns_existential_any, FD);
81+
}
82+
83+
static void inClosureReturnType(ClosureExpr *CE, DiagnosticEngine &Diags) {
84+
Type T = CE->getResultType();
85+
86+
if (inType(T))
87+
Diags.diagnose(CE->getLoc(),
88+
diag::perf_hint_closure_returns_existential_any);
89+
}
90+
91+
static void inVariableType(const VarDecl *VD, DiagnosticEngine &Diags) {
92+
Type T = VD->getInterfaceType();
93+
94+
if (inType(T))
95+
Diags.diagnose(VD, diag::perf_hint_var_uses_existential_any, VD);
96+
}
97+
98+
static void inPatternType(const AnyPattern *AP, DiagnosticEngine &Diags) {
99+
Type T = AP->getType();
100+
101+
if (inType(T))
102+
Diags.diagnose(AP->getLoc(),
103+
diag::perf_hint_any_pattern_uses_existential_any);
104+
}
105+
106+
static void inTypeAlias(const TypeAliasDecl *TAD, DiagnosticEngine &Diags) {
107+
Type T = TAD->getUnderlyingType();
108+
109+
if (inType(T))
110+
Diags.diagnose(TAD->getLoc(),
111+
diag::perf_hint_typealias_uses_existential_any, TAD);
112+
}
113+
114+
bool visitExistentialType(ExistentialType *ET) {
115+
return true;
116+
}
117+
118+
bool visitTupleType(TupleType *TT) {
119+
for (const auto &element : TT->getElements()) {
120+
if (visit(element.getType())) {
121+
return true;
122+
}
123+
}
124+
return false;
125+
}
126+
127+
bool visitBoundGenericType(BoundGenericType *BGT) {
128+
// Check generic arguments (e.g., Array<any Protocol>)
129+
for (Type arg : BGT->getGenericArgs()) {
130+
if (visit(arg)) {
131+
return true;
132+
}
133+
}
134+
return false;
135+
}
136+
137+
bool visitFunctionType(FunctionType *FT) {
138+
for (const auto &param : FT->getParams()) {
139+
if (visit(param.getPlainType()->getCanonicalType())) {
140+
return true;
141+
}
142+
}
143+
144+
return visit(FT->getResult()->getCanonicalType());
145+
}
146+
147+
bool visitType(TypeBase *T) {
148+
return false;
149+
}
150+
};
151+
55152
/// Produce performance hint diagnostics for a SourceFile.
56153
class PerformanceHintDiagnosticWalker final : public ASTWalker {
57154
ASTContext &Ctx;
@@ -64,16 +161,63 @@ class PerformanceHintDiagnosticWalker final : public ASTWalker {
64161
SF->walk(Walker);
65162
}
66163

164+
PreWalkResult<Pattern *> walkToPatternPre(Pattern *P) override {
165+
if (P->isImplicit())
166+
return Action::SkipNode(P);
167+
168+
if (const AnyPattern *AP = dyn_cast<AnyPattern>(P)) {
169+
CheckExistentialAny::inPatternType(AP, Ctx.Diags);
170+
}
171+
172+
return Action::Continue(P);
173+
}
174+
67175
PreWalkResult<Expr *> walkToExprPre(Expr *E) override {
68-
if (auto Closure = dyn_cast<ClosureExpr>(E))
176+
if (E->isImplicit())
177+
return Action::SkipNode(E);
178+
179+
if (const ClosureExpr* Closure = dyn_cast<ClosureExpr>(E)) {
69180
checkImplicitCopyReturnType(Closure, Ctx.Diags);
181+
}
182+
183+
return Action::Continue(E);
184+
}
185+
186+
PostWalkResult<Expr *> walkToExprPost(Expr *E) override {
187+
assert(
188+
!E->isImplicit() &&
189+
"Traversing implicit expressions is disabled in the pre-walk visitor");
190+
191+
if (auto Closure = dyn_cast<ClosureExpr>(E)) {
192+
CheckExistentialAny::inClosureReturnType(Closure, Ctx.Diags);
193+
}
70194

71195
return Action::Continue(E);
72196
}
73197

74198
PreWalkAction walkToDeclPre(Decl *D) override {
75-
if (auto *FD = dyn_cast<FuncDecl>(D))
199+
if (D->isImplicit())
200+
return Action::SkipNode();
201+
202+
if (const FuncDecl *FD = dyn_cast<FuncDecl>(D)) {
76203
checkImplicitCopyReturnType(FD, Ctx.Diags);
204+
} else if (const VarDecl *VD = dyn_cast<VarDecl>(D)) {
205+
CheckExistentialAny::inVariableType(VD, Ctx.Diags);
206+
} else if (const TypeAliasDecl *TAD = dyn_cast<TypeAliasDecl>(D)) {
207+
CheckExistentialAny::inTypeAlias(TAD, Ctx.Diags);
208+
}
209+
210+
return Action::Continue();
211+
}
212+
213+
PostWalkAction walkToDeclPost(Decl *D) override {
214+
assert(
215+
!D->isImplicit() &&
216+
"Traversing implicit declarations is disabled in the pre-walk visitor");
217+
218+
if (auto *FD = dyn_cast<FuncDecl>(D)) {
219+
CheckExistentialAny::inFunctionReturnType(FD, Ctx.Diags);
220+
}
77221

78222
return Action::Continue();
79223
}

0 commit comments

Comments
 (0)