diff --git a/lib/CppInterOp/CppInterOp.cpp b/lib/CppInterOp/CppInterOp.cpp index 893f295ee..9d7aff3ef 100644 --- a/lib/CppInterOp/CppInterOp.cpp +++ b/lib/CppInterOp/CppInterOp.cpp @@ -68,6 +68,7 @@ #include "clang/Basic/Version.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Interpreter/Interpreter.h" +#include "clang/Lex/Lexer.h" #include "clang/Sema/Lookup.h" #include "clang/Sema/Overload.h" #include "clang/Sema/Ownership.h" @@ -5485,6 +5486,32 @@ std::string GetFunctionArgDefault(ConstFuncRef func, size_t param_index) { PI = (FD->getTemplatedDecl())->getNonObjectParameter(param_index); if (PI->hasDefaultArg()) { + // Render the default as written: read its source range, the way clang's + // own code completion formats default arguments (GetDefaultValueString + // in SemaCodeComplete). Pretty-printing the AST instead reproduces + // floating literals at representation precision ("3.1400000000000001" + // for a written 3.14), and normalizing that through std::stod terminated + // the exception-free build on symbolic defaults such as + // `double ratio = kDefaultRatio`. + Sema& S = getSema(); + CharSourceRange SrcRange = + CharSourceRange::getTokenRange(PI->getDefaultArgRange()); + bool Invalid = SrcRange.isInvalid(); + if (!Invalid) { + llvm::StringRef SrcText = Lexer::getSourceText( + SrcRange, S.getSourceManager(), S.getLangOpts(), &Invalid); + if (!Invalid) { + // The lexed range sometimes includes the leading '=' (see the FIXME + // in SemaCodeComplete's GetDefaultValueString); keep only the value. + SrcText = SrcText.ltrim(); + SrcText.consume_front("="); + SrcText = SrcText.ltrim(); + if (!SrcText.empty()) + return INTEROP_RETURN(SrcText.str()); + } + } + + // Fallback for defaults with no usable source range: print the AST. std::string Result; llvm::raw_string_ostream OS(Result); const Expr* DefaultArgExpr = nullptr; @@ -5494,19 +5521,6 @@ std::string GetFunctionArgDefault(ConstFuncRef func, size_t param_index) { else DefaultArgExpr = PI->getDefaultArg(); DefaultArgExpr->printPretty(OS, nullptr, PrintingPolicy(LangOptions())); - - // FIXME: Floats are printed in clang with the precision of their underlying - // representation and not as written. This is a deficiency in the printing - // mechanism of clang which we require extra work to mitigate. For example - // float PI = 3.14 is printed as 3.1400000000000001 - if (PI->getType()->isFloatingType()) { - if (!Result.empty() && Result.back() == '.') - return INTEROP_RETURN(Result); - auto DefaultArgValue = std::stod(Result); - std::ostringstream oss; - oss << DefaultArgValue; - Result = oss.str(); - } return INTEROP_RETURN(Result); } return INTEROP_RETURN(""); diff --git a/unittests/CppInterOp/FunctionReflectionTest.cpp b/unittests/CppInterOp/FunctionReflectionTest.cpp index 5c5a4e5fd..2b44f77bd 100644 --- a/unittests/CppInterOp/FunctionReflectionTest.cpp +++ b/unittests/CppInterOp/FunctionReflectionTest.cpp @@ -2870,10 +2870,10 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionArgDefault) { GetAllTopLevelDecls(code, Decls); EXPECT_EQ(Cpp::GetFunctionArgDefault(Decls[0], 0), ""); - EXPECT_EQ(Cpp::GetFunctionArgDefault(Decls[0], 1), "4."); + EXPECT_EQ(Cpp::GetFunctionArgDefault(Decls[0], 1), "4.0"); EXPECT_EQ(Cpp::GetFunctionArgDefault(Decls[0], 2), "\"default\""); EXPECT_EQ(Cpp::GetFunctionArgDefault(Decls[0], 3), "\'c\'"); - EXPECT_EQ(Cpp::GetFunctionArgDefault(Decls[1], 0), "0."); + EXPECT_EQ(Cpp::GetFunctionArgDefault(Decls[1], 0), "0.0"); EXPECT_EQ(Cpp::GetFunctionArgDefault(Decls[1], 1), "3.123"); EXPECT_EQ(Cpp::GetFunctionArgDefault(Decls[1], 2), "34126"); @@ -2888,7 +2888,7 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionArgDefault) { EXPECT_EQ(Cpp::GetFunctionArgDefault(Decls[4], 0), ""); EXPECT_EQ(Cpp::GetFunctionArgDefault(Decls[4], 1), ""); EXPECT_EQ(Cpp::GetFunctionArgDefault(Decls[4], 2), "\'a\'"); - EXPECT_EQ(Cpp::GetFunctionArgDefault(Decls[4], 3), "0."); + EXPECT_EQ(Cpp::GetFunctionArgDefault(Decls[4], 3), "0.0"); ASTContext& C = Interp->getCI()->getASTContext(); std::vector template_args = {C.IntTy.getAsOpaquePtr()}; @@ -2903,6 +2903,30 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionArgDefault) { EXPECT_EQ(Cpp::GetFunctionArgDefault(fn, 1), "S()"); } +TYPED_TEST(CPPINTEROP_TEST_MODE, + FunctionReflection_GetFunctionArgDefaultSymbolic) { + std::vector Decls; + std::string code = R"( + constexpr double kDefaultRatio = 0.5; + double default_ratio(); + double scaled(double ratio = kDefaultRatio); + double rescaled(double ratio = default_ratio()); + double inverted(double ratio = 2.0 / kDefaultRatio); + double pi_ish(double p = 3.14); + )"; + + GetAllTopLevelDecls(code, Decls); + + // A floating-typed default need not be a numeric literal. Formatting a + // symbolic default must not crash (the removed std::stod normalization + // terminated the exception-free build) and must render it as written. + EXPECT_EQ(Cpp::GetFunctionArgDefault(Decls[2], 0), "kDefaultRatio"); + EXPECT_EQ(Cpp::GetFunctionArgDefault(Decls[3], 0), "default_ratio()"); + EXPECT_EQ(Cpp::GetFunctionArgDefault(Decls[4], 0), "2.0 / kDefaultRatio"); + // Literals render exactly as written, not at representation precision. + EXPECT_EQ(Cpp::GetFunctionArgDefault(Decls[5], 0), "3.14"); +} + TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_Construct) { #ifdef _WIN32 GTEST_SKIP() << "Disabled on Windows. Needs fixing.";