@@ -19,12 +19,9 @@ namespace {
1919 return s.substr (first, s.find_last_not_of (whitespace) - first + 1 );
2020 }
2121
22- // `as<T>` dereferences its argument, and every location attribute is null
23- // until an info tuple sets it.
24- template <typename T> const T *attribute_as (PyObject *attribute)
25- {
26- return attribute ? as<T>(attribute) : nullptr ;
27- }
22+ // The location attributes are user-writable, so `lineno` and `offset` can
23+ // be any int; format them without the range-asserting conversions.
24+ std::string int_to_string (const PyInteger &value) { return value.as_big_int ().get_str (); }
2825}// namespace
2926
3027SyntaxError *SyntaxError::create (PyTuple *args)
@@ -60,9 +57,15 @@ SyntaxError *SyntaxError::create(std::string message, SyntaxErrorLocation locati
6057 return error;
6158}
6259
63- SyntaxError::SyntaxError (PyType *type) : Exception(type) {}
60+ // Every attribute reads as None until `__init__` sets it, as in CPython.
61+ SyntaxError::SyntaxError (PyType *type)
62+ : Exception(type), m_msg(py_none()), m_filename(py_none()), m_text(py_none()),
63+ m_lineno (py_none()), m_offset(py_none())
64+ {}
6465
65- SyntaxError::SyntaxError (PyTuple *args) : Exception(types::BuiltinTypes::the().syntax_error(), args)
66+ SyntaxError::SyntaxError (PyTuple *args)
67+ : Exception(types::BuiltinTypes::the().syntax_error(), args), m_msg(py_none()),
68+ m_filename(py_none()), m_text(py_none()), m_lineno(py_none()), m_offset(py_none())
6669{}
6770
6871PyResult<PyObject *> SyntaxError::__new__ (const PyType *type, PyTuple *args, PyDict *kwargs)
@@ -105,14 +108,11 @@ PyResult<int32_t> SyntaxError::__init__(PyTuple *args, PyDict *kwargs)
105108
106109PyResult<PyObject *> SyntaxError::__str__ () const
107110{
108- std::string msg{ " None" };
109- if (m_msg) {
110- auto str = m_msg->str ();
111- if (str.is_err ()) { return str; }
112- msg = str.unwrap ()->to_string ();
113- }
114- const auto *filename = attribute_as<PyString>(m_filename);
115- const auto *lineno = attribute_as<PyInteger>(m_lineno);
111+ auto msg_ = m_msg->str ();
112+ if (msg_.is_err ()) { return msg_; }
113+ const auto msg = msg_.unwrap ()->to_string ();
114+ const auto *filename = as<PyString>(m_filename);
115+ const auto *lineno = as<PyInteger>(m_lineno);
116116 if (!filename && !lineno) { return PyString::create (msg); }
117117 std::string basename;
118118 if (filename) {
@@ -122,28 +122,36 @@ PyResult<PyObject *> SyntaxError::__str__() const
122122 }
123123 if (filename && lineno) {
124124 return PyString::create (
125- std::format (" {} ({}, line {})" , msg, basename, lineno-> as_size_t ( )));
125+ std::format (" {} ({}, line {})" , msg, basename, int_to_string (*lineno )));
126126 }
127127 if (filename) { return PyString::create (std::format (" {} ({})" , msg, basename)); }
128- return PyString::create (std::format (" {} (line {})" , msg, lineno-> as_size_t ( )));
128+ return PyString::create (std::format (" {} (line {})" , msg, int_to_string (*lineno )));
129129}
130130
131131std::string SyntaxError::format_exception_only () const
132132{
133133 std::ostringstream out;
134134
135- const auto *lineno = attribute_as <PyInteger>(m_lineno);
135+ const auto *lineno = as <PyInteger>(m_lineno);
136136 if (lineno) {
137- const auto *filename = attribute_as <PyString>(m_filename);
137+ const auto *filename = as <PyString>(m_filename);
138138 out << std::format (" File \" {}\" , line {}\n " ,
139139 filename ? filename->value () : std::string{ " <string>" },
140- lineno-> as_size_t ( ));
141- if (const auto *text = attribute_as <PyString>(m_text)) {
140+ int_to_string (*lineno ));
141+ if (const auto *text = as <PyString>(m_text)) {
142142 const std::string_view line{ text->value () };
143143 if (const auto trimmed = strip (line); !trimmed.empty ()) {
144144 out << " " << trimmed << " \n " ;
145- if (const auto *offset = attribute_as<PyInteger>(m_offset)) {
146- const auto column = std::min (line.size (), offset->as_size_t ());
145+ // `offset` is 1-based and may be `line.size() + 1`, i.e. just
146+ // past the last character; a negative one means "no column".
147+ const auto *offset = as<PyInteger>(m_offset);
148+ const auto value = offset ? offset->as_big_int () : BigIntType{ -1 };
149+ // gmpxx's comparison operators aren't reachable through the module
150+ // interface; this is what the `mpz_sgn` macro expands to.
151+ if (value.get_mpz_t ()->_mp_size >= 0 ) {
152+ const auto column = value.fits_ulong_p ()
153+ ? std::min (line.size () + 1 , value.get_ui ())
154+ : line.size () + 1 ;
147155 auto prefix = line.substr (0 , column > 0 ? column - 1 : 0 );
148156 if (const auto first = prefix.find_first_not_of (whitespace);
149157 first != std::string_view::npos) {
@@ -164,7 +172,7 @@ std::string SyntaxError::format_exception_only() const
164172 }
165173
166174 std::string msg{ " <no detail available>" };
167- if (m_msg && m_msg != py_none ()) {
175+ if (m_msg != py_none ()) {
168176 if (auto str = m_msg->str (); str.is_ok ()) { msg = str.unwrap ()->to_string (); }
169177 }
170178 out << std::format (" {}: {}\n " , type ()->name (), msg);
@@ -180,11 +188,11 @@ PyType *SyntaxError::static_type() const
180188void SyntaxError::visit_graph (Visitor &visitor)
181189{
182190 Exception::visit_graph (visitor);
183- if (m_msg) visitor.visit (*m_msg);
184- if (m_filename) visitor.visit (*m_filename);
185- if (m_text) visitor.visit (*m_text);
186- if (m_lineno) visitor.visit (*m_lineno);
187- if (m_offset) visitor.visit (*m_offset);
191+ visitor.visit (*m_msg);
192+ visitor.visit (*m_filename);
193+ visitor.visit (*m_text);
194+ visitor.visit (*m_lineno);
195+ visitor.visit (*m_offset);
188196}
189197
190198namespace {
0 commit comments