[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
returns, and typealiases.
This commit is contained in:
Aviral Goel
2025-10-02 13:07:07 -07:00
parent d06d12632b
commit 1cf692f116
5 changed files with 388 additions and 5 deletions

View File

@@ -48,6 +48,8 @@ namespace swift {
class FuncDecl;
class SourceManager;
class SourceFile;
class ParamDecl;
class AnyPattern;
/// Enumeration describing all of possible diagnostics.
///

View File

@@ -82,6 +82,8 @@ GROUP(WeakMutability, "weak-mutability")
GROUP(PerformanceHints, "performance-hints")
GROUP(ReturnTypeImplicitCopy, "return-type-implicit-copy")
GROUP_LINK(PerformanceHints, ReturnTypeImplicitCopy)
GROUP(ExistentialType, "existential-type")
GROUP_LINK(PerformanceHints, ExistentialType)
#define UNDEFINE_DIAGNOSTIC_GROUPS_MACROS
#include "swift/AST/DefineDiagnosticGroupsMacros.h"

View File

@@ -8946,6 +8946,30 @@ GROUPED_WARNING(perf_hint_closure_returns_array,ReturnTypeImplicitCopy,DefaultIg
"Performance: closure returns a%select{ dictionary|n array}0, leading to implicit copies. "
"Consider using an 'inout' parameter instead.", (bool))
GROUPED_WARNING(perf_hint_param_expects_existential,ExistentialType,DefaultIgnore,
"Performance: %0 expects an existential, leading to heap allocation, reference counting, "
"and dynamic dispatch. Consider using generic constraints or concrete types instead.",
(const ParamDecl*))
GROUPED_WARNING(perf_hint_func_returns_existential,ExistentialType,DefaultIgnore,
"Performance: %0 returns an existential, leading to heap allocation, reference counting, "
"and dynamic dispatch. Consider using generic constraints or concrete types instead.",
(const FuncDecl*))
GROUPED_WARNING(perf_hint_closure_returns_existential,ExistentialType,DefaultIgnore,
"Performance: closure returns an existential, leading to heap allocation, reference counting, "
"and dynamic dispatch. Consider using generic constraints or concrete types instead.", ())
GROUPED_WARNING(perf_hint_var_uses_existential,ExistentialType,DefaultIgnore,
"Performance: %0 uses an existential, leading to heap allocation, reference counting, "
"and dynamic dispatch. Consider using generic constraints or concrete types instead.",
(const VarDecl *))
GROUPED_WARNING(perf_hint_any_pattern_uses_existential,ExistentialType,DefaultIgnore,
"Performance: declaration uses an existential, leading to heap allocation, reference counting, "
"and dynamic dispatch. Consider using generic constraints or concrete types instead.",
())
GROUPED_WARNING(perf_hint_typealias_uses_existential,ExistentialType,DefaultIgnore,
"Performance: %0 aliases an existential type, leading to heap allocation, reference counting, "
"and dynamic dispatch. Consider using generic constraints or concrete types instead.",
(const TypeAliasDecl *))
ERROR(unsafe_self_dependent_result_attr_on_invalid_decl,none,
"invalid use of @_unsafeSelfDependentResult", ())

View File

@@ -25,12 +25,27 @@
#include "swift/AST/Evaluator.h"
#include "swift/AST/Expr.h"
#include "swift/AST/TypeCheckRequests.h"
#include "swift/AST/TypeVisitor.h"
using namespace swift;
bool swift::performanceHintDiagnosticsEnabled(ASTContext &ctx) {
return !ctx.Diags.isIgnoredDiagnostic(diag::perf_hint_closure_returns_array.ID) ||
!ctx.Diags.isIgnoredDiagnostic(diag::perf_hint_function_returns_array.ID);
return !ctx.Diags.isIgnoredDiagnostic(
diag::perf_hint_closure_returns_array.ID) ||
!ctx.Diags.isIgnoredDiagnostic(
diag::perf_hint_function_returns_array.ID) ||
!ctx.Diags.isIgnoredDiagnostic(
diag::perf_hint_param_expects_existential.ID) ||
!ctx.Diags.isIgnoredDiagnostic(
diag::perf_hint_func_returns_existential.ID) ||
!ctx.Diags.isIgnoredDiagnostic(
diag::perf_hint_closure_returns_existential.ID) ||
!ctx.Diags.isIgnoredDiagnostic(
diag::perf_hint_var_uses_existential.ID) ||
!ctx.Diags.isIgnoredDiagnostic(
diag::perf_hint_any_pattern_uses_existential.ID) ||
!ctx.Diags.isIgnoredDiagnostic(
diag::perf_hint_typealias_uses_existential.ID);
}
namespace {
@@ -52,6 +67,52 @@ void checkImplicitCopyReturnType(const ClosureExpr *Closure,
}
}
bool hasExistentialAnyInType(Type T) {
return T->getCanonicalType().findIf(
[](CanType CT) { return isa<ExistentialType>(CT); });
}
void checkExistentialInFunctionReturnType(const FuncDecl *FD,
DiagnosticEngine &Diags) {
Type T = FD->getResultInterfaceType();
if (hasExistentialAnyInType(T))
Diags.diagnose(FD, diag::perf_hint_func_returns_existential, FD);
}
void checkExistentialInClosureReturnType(const ClosureExpr *CE,
DiagnosticEngine &Diags) {
Type T = CE->getResultType();
if (hasExistentialAnyInType(T))
Diags.diagnose(CE->getLoc(), diag::perf_hint_closure_returns_existential);
}
void checkExistentialInVariableType(const VarDecl *VD,
DiagnosticEngine &Diags) {
Type T = VD->getInterfaceType();
if (hasExistentialAnyInType(T))
Diags.diagnose(VD, diag::perf_hint_var_uses_existential, VD);
}
void checkExistentialInPatternType(const AnyPattern *AP,
DiagnosticEngine &Diags) {
Type T = AP->getType();
if (hasExistentialAnyInType(T))
Diags.diagnose(AP->getLoc(), diag::perf_hint_any_pattern_uses_existential);
}
void checkExistentialInTypeAlias(const TypeAliasDecl *TAD,
DiagnosticEngine &Diags) {
Type T = TAD->getUnderlyingType();
if (hasExistentialAnyInType(T))
Diags.diagnose(TAD->getLoc(), diag::perf_hint_typealias_uses_existential,
TAD);
}
/// Produce performance hint diagnostics for a SourceFile.
class PerformanceHintDiagnosticWalker final : public ASTWalker {
ASTContext &Ctx;
@@ -64,16 +125,64 @@ public:
SF->walk(Walker);
}
PreWalkResult<Pattern *> walkToPatternPre(Pattern *P) override {
if (P->isImplicit())
return Action::SkipNode(P);
return Action::Continue(P);
}
PostWalkResult<Pattern *> walkToPatternPost(Pattern *P) override {
assert(!P->isImplicit() &&
"Traversing implicit patterns is disabled in the pre-walk visitor");
if (const AnyPattern *AP = dyn_cast<AnyPattern>(P)) {
checkExistentialInPatternType(AP, Ctx.Diags);
}
return Action::Continue(P);
}
PreWalkResult<Expr *> walkToExprPre(Expr *E) override {
if (auto Closure = dyn_cast<ClosureExpr>(E))
checkImplicitCopyReturnType(Closure, Ctx.Diags);
if (E->isImplicit())
return Action::SkipNode(E);
return Action::Continue(E);
}
PostWalkResult<Expr *> walkToExprPost(Expr *E) override {
assert(
!E->isImplicit() &&
"Traversing implicit expressions is disabled in the pre-walk visitor");
if (const ClosureExpr *CE = dyn_cast<ClosureExpr>(E)) {
checkImplicitCopyReturnType(CE, Ctx.Diags);
checkExistentialInClosureReturnType(CE, Ctx.Diags);
}
return Action::Continue(E);
}
PreWalkAction walkToDeclPre(Decl *D) override {
if (auto *FD = dyn_cast<FuncDecl>(D))
if (D->isImplicit())
return Action::SkipNode();
return Action::Continue();
}
PostWalkAction walkToDeclPost(Decl *D) override {
assert(
!D->isImplicit() &&
"Traversing implicit declarations is disabled in the pre-walk visitor");
if (const FuncDecl *FD = dyn_cast<FuncDecl>(D)) {
checkImplicitCopyReturnType(FD, Ctx.Diags);
checkExistentialInFunctionReturnType(FD, Ctx.Diags);
} else if (const VarDecl *VD = dyn_cast<VarDecl>(D)) {
checkExistentialInVariableType(VD, Ctx.Diags);
} else if (const TypeAliasDecl *TAD = dyn_cast<TypeAliasDecl>(D)) {
checkExistentialInTypeAlias(TAD, Ctx.Diags);
}
return Action::Continue();
}

View File

@@ -0,0 +1,246 @@
// RUN: %target-typecheck-verify-swift -Werror ExistentialType
protocol Animal {
var Name: String { get }
}
struct Tiger: Animal {
let Name: String = "Tiger"
}
struct Panda: Animal {
let Name: String = "Panda"
}
struct AnimalError: Error {
let reason: String
init(reason: String) {
self.reason = reason
}
}
struct Container<T> {
let value: T
}
protocol Person {
}
////////////////////////////////////////////////////////////////////////////////
// Typealias
///////////////////////////////////////////////////////////////////////////////
typealias AnyAnimal = any Animal // expected-error {{Performance: 'AnyAnimal' aliases an existential type, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
typealias AnimalContainer<T> = Container<any Animal> // expected-error {{Performance: 'AnimalContainer' aliases an existential type, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
typealias AnimalHandler = (any Animal) -> (any Animal)? // expected-error {{Performance: 'AnimalHandler' aliases an existential type, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
////////////////////////////////////////////////////////////////////////////////
// Function Return Type
///////////////////////////////////////////////////////////////////////////////
// Regular
func returnAnimal1() -> any Animal { return Tiger() } // expected-error {{Performance: 'returnAnimal1()' returns an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
// Optional
func returnAnimal2() -> (any Animal)? { // expected-error {{Performance: 'returnAnimal2()' returns an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
let n = Int.random(in: 1...100)
return (n <= 50) ? nil : Tiger()
}
// Throwing
func returnAnimal3() throws -> any Animal { // expected-error {{Performance: 'returnAnimal3()' returns an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
let n = Int.random(in: 1...100)
if n <= 50 {
throw AnimalError(reason: "All animals are extinct.")
} else {
return Tiger()
}
}
// Async
func returnAnimal4() async -> any Animal {} // expected-error {{Performance: 'returnAnimal4()' returns an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
////////////////////////////////////////////////////////////////////////////////
// Function Parameter Type
///////////////////////////////////////////////////////////////////////////////
// Regular parameters
func animalParam1(_ animal: any Animal) {} // expected-error {{Performance: 'animal' uses an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead}}
// Multiple parameters
func animalParam2(
_ animal: any Animal, // expected-error {{Performance: 'animal' uses an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
to other: any Animal // expected-error {{Performance: 'other' uses an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
) {}
// Variadic parameters
func animalParam3(_ animals: any Animal...) {} // expected-error {{Performance: 'animals' uses an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
// In-out parameters
func animalParam4(_ animal: inout any Animal) {} // expected-error {{Performance: 'animal' uses an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
////////////////////////////////////////////////////////////////////////////////
// Protocol
////////////////////////////////////////////////////////////////////////////////
protocol AnimalShelter {
var animal: (any Animal)? { // expected-error {{Performance: 'animal' uses an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
get // expected-error {{Performance: getter for 'animal' returns an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
}
func admit(_ animal: any Animal) // expected-error {{Performance: 'animal' uses an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
func releaseAnimal() -> (any Animal)? // expected-error {{Performance: 'releaseAnimal()' returns an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
subscript(id: String) -> (any Animal)? { get } // expected-error {{Performance: getter for 'subscript(_:)' returns an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
}
////////////////////////////////////////////////////////////////////////////////
// Constructor
////////////////////////////////////////////////////////////////////////////////
class Zoo {
var animals: [any Animal] // expected-error {{Performance: 'animals' uses an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
init(with animal: any Animal) { // expected-error {{Performance: 'animal' uses an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
self.animals = [animal]
}
init(animals: [any Animal]) { // expected-error {{Performance: 'animals' uses an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
self.animals = animals
}
}
////////////////////////////////////////////////////////////////////////////////
// Compound Types
////////////////////////////////////////////////////////////////////////////////
func testCompoundTypes() {
let animals1: [any Animal] = [] // expected-error {{Performance: 'animals1' uses an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
print(type(of: animals1))
let animals2: [any Animal] = [] // expected-error {{Performance: 'animals2' uses an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
print(type(of: animals2))
let animals3: [String: any Animal] = [:] // expected-error {{Performance: 'animals3' uses an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
print(type(of: animals3))
let animals4: [String: any Animal] = [:] // expected-error {{Performance: 'animals4' uses an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
print(type(of: animals4))
let animals5: (any Animal)? = nil // expected-error {{Performance: 'animals5' uses an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
print(type(of: animals5))
let animals6: (any Animal)? = nil // expected-error {{Performance: 'animals6' uses an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
print(type(of: animals6))
let animals7: Result<any Animal, Error> = .success(Tiger()) // expected-error {{Performance: 'animals7' uses an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
print(type(of: animals7))
let container = Container<any Animal>(value: Tiger()) // expected-error {{Performance: 'container' uses an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
print(type(of: container))
}
////////////////////////////////////////////////////////////////////////////////
// Tuple
////////////////////////////////////////////////////////////////////////////////
func tupleTest() {
let _ = ([Tiger() as any Animal], [Panda() as any Animal]) // expected-error {{Performance: declaration uses an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
let (
animalsA, // expected-error {{Performance: 'animalsA' uses an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
animalsB // expected-error {{Performance: 'animalsB' uses an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
) = ([Tiger() as any Animal], [Panda() as any Animal])
print(type(of: animalsA))
print(type(of: animalsB))
let (
_, // expected-error {{Performance: declaration uses an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
animalsC // expected-error {{Performance: 'animalsC' uses an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
) = ([Tiger() as any Animal], [Panda() as any Animal])
print(type(of: animalsC))
let (_, _) = ([Tiger() as any Animal], [Panda() as any Animal]) // expected-error 2 {{Performance: declaration uses an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
let tuple: (animal1: any Animal, animal2: any Animal) = (Tiger(), Panda()) // expected-error {{Performance: 'tuple' uses an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
print(type(of: tuple))
}
////////////////////////////////////////////////////////////////////////////////
// Closure
////////////////////////////////////////////////////////////////////////////////
func closureTest() {
// Closure parameter type
let handler: (any Animal) -> Void = { // expected-error {{Performance: 'handler' uses an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
animal in print(type(of: animal)) // expected-error {{'animal' uses an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
}
handler(Tiger())
// Closure return type
let factory: () -> any Animal = { Tiger() } // expected-error {{Performance: 'factory' uses an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}} expected-error {{Performance: closure returns an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
print(type(of: factory()))
// Both parameter and return types
let transformer: (any Animal) -> any Animal = { $0 } // expected-error {{Performance: 'transformer' uses an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}} expected-error {{Performance: closure returns an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
print(type(of: transformer(Tiger())))
// Escaping closures
var handlers: [(any Animal) -> Void] = [] // expected-error {{Performance: 'handlers' uses an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
func registerHandler(with handler: @escaping (any Animal) -> Void) { // expected-error {{Performance: 'handler' uses an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
handlers.append(handler)
}
// Autoclosure
func registerHandler2(animalThunk: @autoclosure () -> any Animal) { // expected-error {{Performance: 'animalThunk' uses an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
handlers[0](animalThunk())
}
}
////////////////////////////////////////////////////////////////////////////////
// Type casting
///////////////////////////////////////////////////////////////////////////////
protocol A {
}
protocol A1: A {
}
protocol A2: A {
}
struct S1: A1 {
}
struct S2: A2 {
}
func testTypeCasting() {
let randomNumber = Int.random(in: 1...100)
let value: any A = randomNumber <= 50 ? S1() : S2() // expected-error {{Performance: 'value' uses an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
let a1 = value as? any A1 // expected-error {{Performance: 'a1' uses an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
print(type(of: a1))
let a2 = value as! any A2 // expected-error {{Performance: 'a2' uses an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
print(type(of: a2))
}
////////////////////////////////////////////////////////////////////////////////
// Enum
///////////////////////////////////////////////////////////////////////////////
enum PetOwnership {
case owned(
pet: any Animal, // expected-error {{Performance: 'pet' uses an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
owner: any Person) // expected-error {{Performance: 'owner' uses an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
case stray(any Animal) // expected-error {{Performance: parameter uses an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
case multiple([any Animal]) // expected-error {{Performance: parameter uses an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
}
////////////////////////////////////////////////////////////////////////////////
// An existential in parent type
///////////////////////////////////////////////////////////////////////////////
struct Outer<T> {
struct Inner {}
var i = Inner()
}
func f() -> Outer<any Animal>.Inner { // expected-error {{Performance: 'f()' returns an existential, leading to heap allocation, reference counting, and dynamic dispatch. Consider using generic constraints or concrete types instead.}}
return Outer<any Animal>().i
}