1515// ===----------------------------------------------------------------------===//
1616
1717#include " MiscDiagnostics.h"
18- #include " TypeChecker .h"
18+ #include " ConstraintSystem .h"
1919#include " TypeCheckAvailability.h"
20+ #include " TypeChecker.h"
2021#include " swift/AST/ASTWalker.h"
2122#include " swift/AST/NameLookup.h"
2223#include " swift/AST/NameLookupRequests.h"
3435
3536#define DEBUG_TYPE " Sema"
3637using namespace swift ;
38+ using namespace constraints ;
3739
3840// / Return true if this expression is an implicit promotion from T to T?.
3941static Expr *isImplicitPromotionToOptional (Expr *E) {
@@ -4438,6 +4440,131 @@ static void diagnoseExplicitUseOfLazyVariableStorage(const Expr *E,
44384440 const_cast <Expr *>(E)->walk (Walker);
44394441}
44404442
4443+ static void diagnoseComparisonWithNaN (const Expr *E, const DeclContext *DC) {
4444+ class ComparisonWithNaNFinder : public ASTWalker {
4445+ const ASTContext &C;
4446+ const DeclContext *DC;
4447+
4448+ public:
4449+ ComparisonWithNaNFinder (const DeclContext *dc)
4450+ : C(dc->getASTContext ()), DC(dc) {}
4451+
4452+ void tryDiagnoseComparisonWithNaN (BinaryExpr *BE) {
4453+ ValueDecl *comparisonDecl = nullptr ;
4454+
4455+ // Comparison functions like == or <= take two arguments.
4456+ if (BE->getArg ()->getNumElements () != 2 ) {
4457+ return ;
4458+ }
4459+
4460+ // Dig out the function declaration.
4461+ if (auto Fn = BE->getFn ()) {
4462+ if (auto DSCE = dyn_cast<DotSyntaxCallExpr>(Fn)) {
4463+ comparisonDecl = DSCE->getCalledValue ();
4464+ } else {
4465+ comparisonDecl = BE->getCalledValue ();
4466+ }
4467+ }
4468+
4469+ // Bail out if it isn't a function.
4470+ if (!comparisonDecl || !isa<FuncDecl>(comparisonDecl)) {
4471+ return ;
4472+ }
4473+
4474+ // We're only interested in comparison functions like == or <=.
4475+ auto comparisonDeclName = comparisonDecl->getBaseIdentifier ();
4476+ if (!comparisonDeclName.isStandardComparisonOperator ()) {
4477+ return ;
4478+ }
4479+
4480+ auto firstArg = BE->getArg ()->getElement (0 );
4481+ auto secondArg = BE->getArg ()->getElement (1 );
4482+
4483+ // Both arguments must conform to FloatingPoint protocol.
4484+ if (!conformsToKnownProtocol (const_cast <DeclContext *>(DC),
4485+ firstArg->getType (),
4486+ KnownProtocolKind::FloatingPoint) ||
4487+ !conformsToKnownProtocol (const_cast <DeclContext *>(DC),
4488+ secondArg->getType (),
4489+ KnownProtocolKind::FloatingPoint)) {
4490+ return ;
4491+ }
4492+
4493+ // Convenience utility to extract argument decl.
4494+ auto extractArgumentDecl = [&](Expr *arg) -> ValueDecl * {
4495+ if (auto DRE = dyn_cast<DeclRefExpr>(arg)) {
4496+ return DRE->getDecl ();
4497+ } else if (auto MRE = dyn_cast<MemberRefExpr>(arg)) {
4498+ return MRE->getMember ().getDecl ();
4499+ }
4500+ return nullptr ;
4501+ };
4502+
4503+ // Dig out the declarations for the arguments.
4504+ auto *firstVal = extractArgumentDecl (firstArg);
4505+ auto *secondVal = extractArgumentDecl (secondArg);
4506+
4507+ // If we can't find declarations for both arguments, bail out,
4508+ // because one of them has to be '.nan'.
4509+ if (!firstArg && !secondArg) {
4510+ return ;
4511+ }
4512+
4513+ // Convenience utility to check if this is a 'nan' variable.
4514+ auto isNanDecl = [&](ValueDecl *VD) {
4515+ return VD && isa<VarDecl>(VD) && VD->getBaseIdentifier ().is (" nan" );
4516+ };
4517+
4518+ // Diagnose comparison with '.nan'.
4519+ //
4520+ // If the comparison is done using '<=', '<', '==', '>', '>=', then
4521+ // the result is always false. If the comparison is done using '!=',
4522+ // then the result is always true.
4523+ //
4524+ // Emit a different diagnostic which doesn't mention using '.isNaN' if
4525+ // the comparison isn't done using '==' or '!=' or if both sides are
4526+ // '.nan'.
4527+ if (isNanDecl (firstVal) && isNanDecl (secondVal)) {
4528+ C.Diags .diagnose (BE->getLoc (), diag::nan_comparison_both_nan,
4529+ comparisonDeclName.str (), comparisonDeclName.is (" !=" ));
4530+ } else if (isNanDecl (firstVal) || isNanDecl (secondVal)) {
4531+ if (comparisonDeclName.is (" ==" ) || comparisonDeclName.is (" !=" )) {
4532+ auto exprStr =
4533+ C.SourceMgr
4534+ .extractText (Lexer::getCharSourceRangeFromSourceRange (
4535+ C.SourceMgr , firstArg->getSourceRange ()))
4536+ .str ();
4537+ auto prefix = exprStr;
4538+ if (comparisonDeclName.is (" !=" )) {
4539+ prefix = " !" + prefix;
4540+ }
4541+ C.Diags .diagnose (BE->getLoc (), diag::nan_comparison,
4542+ comparisonDeclName, comparisonDeclName.is (" !=" ),
4543+ prefix, exprStr);
4544+ } else {
4545+ C.Diags .diagnose (BE->getLoc (), diag::nan_comparison_without_isnan,
4546+ comparisonDeclName, comparisonDeclName.is (" !=" ));
4547+ }
4548+ }
4549+ }
4550+
4551+ std::pair<bool , Expr *> walkToExprPre (Expr *E) override {
4552+ if (!E || isa<ErrorExpr>(E) || !E->getType ())
4553+ return {false , E};
4554+
4555+ if (auto *BE = dyn_cast<BinaryExpr>(E)) {
4556+ tryDiagnoseComparisonWithNaN (BE);
4557+ return {false , E};
4558+ }
4559+
4560+ return {true , E};
4561+ }
4562+ };
4563+
4564+ ComparisonWithNaNFinder Walker (DC);
4565+ const_cast <Expr *>(E)->walk (Walker);
4566+ }
4567+
44414568// ===----------------------------------------------------------------------===//
44424569// High-level entry points.
44434570// ===----------------------------------------------------------------------===//
@@ -4454,6 +4581,7 @@ void swift::performSyntacticExprDiagnostics(const Expr *E,
44544581 diagnoseUnintendedOptionalBehavior (E, DC);
44554582 maybeDiagnoseCallToKeyValueObserveMethod (E, DC);
44564583 diagnoseExplicitUseOfLazyVariableStorage (E, DC);
4584+ diagnoseComparisonWithNaN (E, DC);
44574585 if (!ctx.isSwiftVersionAtLeast (5 ))
44584586 diagnoseDeprecatedWritableKeyPath (E, DC);
44594587 if (!ctx.LangOpts .DisableAvailabilityChecking )
0 commit comments