@@ -1708,53 +1708,105 @@ pub(super) mod types {
17081708 }
17091709 }
17101710
1711- // args are truncated to 2 for compatibility (only when 2-5 args)
1712- if ( 3 ..=5 ) . contains ( & len) {
1711+ // args are truncated to 2 for compatibility (only when 2-5 args and filename is not None)
1712+ // truncation happens inside "if (filename && filename != Py_None)" block
1713+ let has_filename = exc. filename . to_owned ( ) . filter ( |f| !vm. is_none ( f) ) . is_some ( ) ;
1714+ if ( 3 ..=5 ) . contains ( & len) && has_filename {
17131715 new_args. args . truncate ( 2 ) ;
17141716 }
17151717 PyBaseException :: slot_init ( zelf, new_args, vm)
17161718 }
17171719
17181720 #[ pymethod]
17191721 fn __str__ ( exc : PyBaseExceptionRef , vm : & VirtualMachine ) -> PyResult < PyStrRef > {
1720- let args = exc. args ( ) ;
17211722 let obj = exc. as_object ( ) . to_owned ( ) ;
17221723
1723- let str = if args. len ( ) == 2 {
1724- // SAFETY: len() == 2 is checked so get_arg 1 or 2 won't panic
1725- let errno = exc. get_arg ( 0 ) . unwrap ( ) . str ( vm) ?;
1726- let msg = exc. get_arg ( 1 ) . unwrap ( ) . str ( vm) ?;
1727-
1728- // On Windows, use [WinError X] format when winerror is set
1729- #[ cfg( windows) ]
1730- let ( label, code) = match obj. get_attr ( "winerror" , vm) {
1731- Ok ( winerror) if !vm. is_none ( & winerror) => ( "WinError" , winerror. str ( vm) ?) ,
1732- _ => ( "Errno" , errno. clone ( ) ) ,
1733- } ;
1734- #[ cfg( not( windows) ) ]
1735- let ( label, code) = ( "Errno" , errno. clone ( ) ) ;
1724+ // Get OSError fields directly
1725+ let errno_field = obj. get_attr ( "errno" , vm) . ok ( ) . filter ( |v| !vm. is_none ( v) ) ;
1726+ let strerror = obj. get_attr ( "strerror" , vm) . ok ( ) . filter ( |v| !vm. is_none ( v) ) ;
1727+ let filename = obj. get_attr ( "filename" , vm) . ok ( ) . filter ( |v| !vm. is_none ( v) ) ;
1728+ let filename2 = obj
1729+ . get_attr ( "filename2" , vm)
1730+ . ok ( )
1731+ . filter ( |v| !vm. is_none ( v) ) ;
1732+ #[ cfg( windows) ]
1733+ let winerror = obj. get_attr ( "winerror" , vm) . ok ( ) . filter ( |v| !vm. is_none ( v) ) ;
17361734
1737- let s = match obj. get_attr ( "filename" , vm) {
1738- Ok ( filename) if !vm. is_none ( & filename) => match obj. get_attr ( "filename2" , vm) {
1739- Ok ( filename2) if !vm. is_none ( & filename2) => format ! (
1740- "[{} {}] {}: '{}' -> '{}'" ,
1741- label,
1735+ // Windows: winerror takes priority over errno
1736+ #[ cfg( windows) ]
1737+ if let Some ( ref win_err) = winerror {
1738+ let code = win_err. str ( vm) ?;
1739+ if let Some ( ref f) = filename {
1740+ let msg = strerror
1741+ . as_ref ( )
1742+ . map ( |s| s. str ( vm) )
1743+ . transpose ( ) ?
1744+ . map ( |s| s. to_string ( ) )
1745+ . unwrap_or_else ( || "None" . to_owned ( ) ) ;
1746+ if let Some ( ref f2) = filename2 {
1747+ return Ok ( vm. ctx . new_str ( format ! (
1748+ "[WinError {}] {}: '{}' -> '{}'" ,
17421749 code,
17431750 msg,
1744- filename. str ( vm) ?,
1745- filename2. str ( vm) ?
1746- ) ,
1747- _ => format ! ( "[{} {}] {}: '{}'" , label, code, msg, filename. str ( vm) ?) ,
1748- } ,
1749- _ => {
1750- format ! ( "[{label} {code}] {msg}" )
1751+ f. str ( vm) ?,
1752+ f2. str ( vm) ?
1753+ ) ) ) ;
17511754 }
1752- } ;
1753- vm. ctx . new_str ( s)
1754- } else {
1755- exc. __str__ ( vm)
1756- } ;
1757- Ok ( str)
1755+ return Ok ( vm. ctx . new_str ( format ! (
1756+ "[WinError {}] {}: '{}'" ,
1757+ code,
1758+ msg,
1759+ f. str ( vm) ?
1760+ ) ) ) ;
1761+ }
1762+ // winerror && strerror (no filename)
1763+ if let Some ( ref s) = strerror {
1764+ return Ok ( vm
1765+ . ctx
1766+ . new_str ( format ! ( "[WinError {}] {}" , code, s. str ( vm) ?) ) ) ;
1767+ }
1768+ }
1769+
1770+ // Non-Windows or fallback: use errno
1771+ if let Some ( ref f) = filename {
1772+ let errno_str = errno_field
1773+ . as_ref ( )
1774+ . map ( |e| e. str ( vm) )
1775+ . transpose ( ) ?
1776+ . map ( |s| s. to_string ( ) )
1777+ . unwrap_or_else ( || "None" . to_owned ( ) ) ;
1778+ let msg = strerror
1779+ . as_ref ( )
1780+ . map ( |s| s. str ( vm) )
1781+ . transpose ( ) ?
1782+ . map ( |s| s. to_string ( ) )
1783+ . unwrap_or_else ( || "None" . to_owned ( ) ) ;
1784+ if let Some ( ref f2) = filename2 {
1785+ return Ok ( vm. ctx . new_str ( format ! (
1786+ "[Errno {}] {}: '{}' -> '{}'" ,
1787+ errno_str,
1788+ msg,
1789+ f. str ( vm) ?,
1790+ f2. str ( vm) ?
1791+ ) ) ) ;
1792+ }
1793+ return Ok ( vm. ctx . new_str ( format ! (
1794+ "[Errno {}] {}: '{}'" ,
1795+ errno_str,
1796+ msg,
1797+ f. str ( vm) ?
1798+ ) ) ) ;
1799+ }
1800+
1801+ // errno && strerror (no filename)
1802+ if let ( Some ( e) , Some ( s) ) = ( & errno_field, & strerror) {
1803+ return Ok ( vm
1804+ . ctx
1805+ . new_str ( format ! ( "[Errno {}] {}" , e. str ( vm) ?, s. str ( vm) ?) ) ) ;
1806+ }
1807+
1808+ // fallback to BaseException.__str__
1809+ Ok ( exc. __str__ ( vm) )
17581810 }
17591811
17601812 #[ pymethod]
0 commit comments