Skip to content

Commit 189b6aa

Browse files
author
Emery Conrad
committed
Keep symbolic floating defaults in GetFunctionArgDefault
The printed default of a floating-typed parameter need not be a numeric literal (double ratio = kDefaultRatio, arrow's pool = default_memory_pool() style); std::stod then throws std::invalid_argument into exception-free LLVM code and the process terminates. Parse with strtod non-throwingly (full consumption + ERANGE checked) and keep the printed spelling when the default is not a plain literal; literals keep the precision normalization the code exists for. Co-developed-with-the-help-of: Claude Code (Fable 5, human in the loop)
1 parent 3103be9 commit 189b6aa

2 files changed

Lines changed: 41 additions & 4 deletions

File tree

lib/CppInterOp/CppInterOp.cpp

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@
103103
#include <algorithm>
104104
#include <array>
105105
#include <cassert>
106+
#include <cerrno>
106107
#include <cstddef>
107108
#include <cstdint>
108109
#include <cstdio>
@@ -5502,10 +5503,21 @@ std::string GetFunctionArgDefault(ConstFuncRef func, size_t param_index) {
55025503
if (PI->getType()->isFloatingType()) {
55035504
if (!Result.empty() && Result.back() == '.')
55045505
return INTEROP_RETURN(Result);
5505-
auto DefaultArgValue = std::stod(Result);
5506-
std::ostringstream oss;
5507-
oss << DefaultArgValue;
5508-
Result = oss.str();
5506+
// The printed default need not be a numeric literal (an identifier
5507+
// such as `kDefaultRatio`, or a call such as `quiet_NaN()`); std::stod
5508+
// would throw std::invalid_argument, and this library is built
5509+
// without exception support, so the throw terminates the process.
5510+
// Parse non-throwingly and keep the printed spelling when the default
5511+
// is not a plain literal.
5512+
errno = 0;
5513+
char* ParseEnd = nullptr;
5514+
double DefaultArgValue = std::strtod(Result.c_str(), &ParseEnd);
5515+
if (ParseEnd != Result.c_str() && ParseEnd != nullptr &&
5516+
*ParseEnd == '\0' && errno != ERANGE) {
5517+
std::ostringstream oss;
5518+
oss << DefaultArgValue;
5519+
Result = oss.str();
5520+
}
55095521
}
55105522
return INTEROP_RETURN(Result);
55115523
}

unittests/CppInterOp/FunctionReflectionTest.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2903,6 +2903,31 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionArgDefault) {
29032903
EXPECT_EQ(Cpp::GetFunctionArgDefault(fn, 1), "S()");
29042904
}
29052905

2906+
TYPED_TEST(CPPINTEROP_TEST_MODE,
2907+
FunctionReflection_GetFunctionArgDefaultSymbolic) {
2908+
std::vector<Decl*> Decls;
2909+
std::string code = R"(
2910+
constexpr double kDefaultRatio = 0.5;
2911+
double default_ratio();
2912+
double scaled(double ratio = kDefaultRatio);
2913+
double rescaled(double ratio = default_ratio());
2914+
double inverted(double ratio = 2.0 / kDefaultRatio);
2915+
double pi_ish(double p = 3.14);
2916+
)";
2917+
2918+
GetAllTopLevelDecls(code, Decls);
2919+
2920+
// A floating-typed default need not be a numeric literal. Formatting a
2921+
// symbolic default must not throw (std::stod would terminate the
2922+
// exception-free build) and must preserve the printed spelling.
2923+
EXPECT_EQ(Cpp::GetFunctionArgDefault(Decls[2], 0), "kDefaultRatio");
2924+
EXPECT_EQ(Cpp::GetFunctionArgDefault(Decls[3], 0), "default_ratio()");
2925+
// Starts with a valid literal but has trailing text: spelling preserved.
2926+
EXPECT_EQ(Cpp::GetFunctionArgDefault(Decls[4], 0), "2. / kDefaultRatio");
2927+
// Plain literals keep the precision normalization.
2928+
EXPECT_EQ(Cpp::GetFunctionArgDefault(Decls[5], 0), "3.14");
2929+
}
2930+
29062931
TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_Construct) {
29072932
#ifdef _WIN32
29082933
GTEST_SKIP() << "Disabled on Windows. Needs fixing.";

0 commit comments

Comments
 (0)