Files
swift-mirror/lib/Sema/PerformanceHints.cpp
Aviral Goel 6a3ca6d246 [Performance Hints] Disable performance hints if typechecker skips some function bodies
If `SkipFunctionBodies` is not `None`, functions that are inlinable or not used
for the result are not typechecked. The absence of type information crashes the
performance hint analyzer. This change prevents the analyzer from running in
these cases.
2025-10-31 16:32:33 -07:00

161 lines
5.2 KiB
C++

//===--------------------- PerformanceHints.cpp ---------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// This files implements the various checks/lints intended to provide
// opt-in guidance for hidden costs in performance-critical Swift code.
//
//===----------------------------------------------------------------------===//
#include "MiscDiagnostics.h"
#include "swift/AST/ASTContext.h"
#include "swift/AST/ASTWalker.h"
#include "swift/AST/Decl.h"
#include "swift/AST/DiagnosticsFrontend.h"
#include "swift/AST/DiagnosticGroups.h"
#include "swift/AST/DiagnosticsSema.h"
#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.isIgnoredDiagnosticGroupTree(DiagGroupID::PerformanceHints) &&
(ctx.TypeCheckerOpts.SkipFunctionBodies == FunctionBodySkipping::None);
}
namespace {
void checkImplicitCopyReturnType(const FuncDecl *FD, DiagnosticEngine &Diags) {
auto ReturnType = FD->getResultInterfaceType();
if (ReturnType->isArray() || ReturnType->isDictionary()) {
Diags.diagnose(FD->getLoc(), diag::perf_hint_function_returns_array, FD,
ReturnType->isArray());
}
}
void checkImplicitCopyReturnType(const ClosureExpr *Closure,
DiagnosticEngine &Diags) {
auto ReturnType = Closure->getResultType();
if (ReturnType->isArray() || ReturnType->isDictionary()) {
Diags.diagnose(Closure->getLoc(), diag::perf_hint_closure_returns_array,
ReturnType->isArray());
}
}
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;
public:
PerformanceHintDiagnosticWalker(ASTContext &Ctx) : Ctx(Ctx) {}
static void check(SourceFile *SF) {
auto Walker = PerformanceHintDiagnosticWalker(SF->getASTContext());
SF->walk(Walker);
}
PostWalkResult<Pattern *> walkToPatternPost(Pattern *P) override {
if (P->isImplicit())
return Action::Continue(P);
if (const AnyPattern *AP = dyn_cast<AnyPattern>(P)) {
checkExistentialInPatternType(AP, Ctx.Diags);
}
return Action::Continue(P);
}
PostWalkResult<Expr *> walkToExprPost(Expr *E) override {
if (E->isImplicit())
return Action::Continue(E);
if (const ClosureExpr *CE = dyn_cast<ClosureExpr>(E)) {
checkImplicitCopyReturnType(CE, Ctx.Diags);
checkExistentialInClosureReturnType(CE, Ctx.Diags);
}
return Action::Continue(E);
}
PostWalkAction walkToDeclPost(Decl *D) override {
if (D->isImplicit())
return Action::Continue();
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();
}
};
} // namespace
evaluator::SideEffect EmitPerformanceHints::evaluate(Evaluator &evaluator,
SourceFile *SF) const {
PerformanceHintDiagnosticWalker::check(SF);
return {};
}