diff --git a/include/swift/AST/DiagnosticEngine.h b/include/swift/AST/DiagnosticEngine.h index 8f04b6f982eaf..f9ab405b51f4f 100644 --- a/include/swift/AST/DiagnosticEngine.h +++ b/include/swift/AST/DiagnosticEngine.h @@ -48,6 +48,8 @@ namespace swift { class FuncDecl; class SourceManager; class SourceFile; + class ParamDecl; + class AnyPattern; /// Enumeration describing all of possible diagnostics. /// diff --git a/include/swift/AST/DiagnosticGroups.def b/include/swift/AST/DiagnosticGroups.def index 99091eeb5cc40..4f55336d1ed97 100644 --- a/include/swift/AST/DiagnosticGroups.def +++ b/include/swift/AST/DiagnosticGroups.def @@ -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" diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index e00d8a8dcff9a..31b11a228cea5 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -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", ()) diff --git a/lib/Sema/PerformanceHints.cpp b/lib/Sema/PerformanceHints.cpp index c3f90665df8ee..2798caab79577 100644 --- a/lib/Sema/PerformanceHints.cpp +++ b/lib/Sema/PerformanceHints.cpp @@ -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(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 @@ class PerformanceHintDiagnosticWalker final : public ASTWalker { SF->walk(Walker); } + PreWalkResult walkToPatternPre(Pattern *P) override { + if (P->isImplicit()) + return Action::SkipNode(P); + + return Action::Continue(P); + } + + PostWalkResult walkToPatternPost(Pattern *P) override { + assert(!P->isImplicit() && + "Traversing implicit patterns is disabled in the pre-walk visitor"); + + if (const AnyPattern *AP = dyn_cast(P)) { + checkExistentialInPatternType(AP, Ctx.Diags); + } + + return Action::Continue(P); + } + PreWalkResult walkToExprPre(Expr *E) override { - if (auto Closure = dyn_cast(E)) - checkImplicitCopyReturnType(Closure, Ctx.Diags); + if (E->isImplicit()) + return Action::SkipNode(E); + + return Action::Continue(E); + } + + PostWalkResult walkToExprPost(Expr *E) override { + assert( + !E->isImplicit() && + "Traversing implicit expressions is disabled in the pre-walk visitor"); + + if (const ClosureExpr *CE = dyn_cast(E)) { + checkImplicitCopyReturnType(CE, Ctx.Diags); + checkExistentialInClosureReturnType(CE, Ctx.Diags); + } return Action::Continue(E); } PreWalkAction walkToDeclPre(Decl *D) override { - if (auto *FD = dyn_cast(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(D)) { checkImplicitCopyReturnType(FD, Ctx.Diags); + checkExistentialInFunctionReturnType(FD, Ctx.Diags); + } else if (const VarDecl *VD = dyn_cast(D)) { + checkExistentialInVariableType(VD, Ctx.Diags); + } else if (const TypeAliasDecl *TAD = dyn_cast(D)) { + checkExistentialInTypeAlias(TAD, Ctx.Diags); + } return Action::Continue(); } diff --git a/test/PerformanceHints/existential-any.swift b/test/PerformanceHints/existential-any.swift new file mode 100644 index 0000000000000..9fa147a8b0b1f --- /dev/null +++ b/test/PerformanceHints/existential-any.swift @@ -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 { + 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 = Container // 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 = .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(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 { + struct Inner {} + var i = Inner() +} + +func f() -> Outer.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().i +}