Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 27 additions & 13 deletions lib/CppInterOp/CppInterOp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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;
Expand All @@ -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("");
Expand Down
30 changes: 27 additions & 3 deletions unittests/CppInterOp/FunctionReflectionTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand All @@ -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<Cpp::TemplateArgInfo> template_args = {C.IntTy.getAsOpaquePtr()};
Expand All @@ -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<Decl*> 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.";
Expand Down
Loading