| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 7c4017b commit 3191eca
32 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -22,7 +22,7 @@ freeze-stdlib = ["stdlib", "rustpython-vm/freeze-stdlib", "rustpython-pylib?/fre | |||
| 22 | 22 | jit = ["rustpython-vm/jit"] | |
| 23 | 23 | threading = ["rustpython-vm/threading", "rustpython-stdlib/threading"] | |
| 24 | 24 | sqlite = ["rustpython-stdlib/sqlite"] | |
| 25 | - ssl = ["host_env"] | ||
| 25 | + ssl = ["host_env", "rustpython-stdlib?/ssl"] | ||
| 26 | 26 | ssl-rustls = ["ssl", "rustpython-stdlib/ssl-rustls"] | |
| 27 | 27 | ssl-rustls-aws-lc = ["ssl-rustls", "dep:rustls", "rustls/aws_lc_rs"] | |
| 28 | 28 | ssl-rustls-aws-lc-fips = ["ssl-rustls-aws-lc", "rustls/fips"] | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1183,7 +1183,6 @@ def __del__(self): | |||
| 1183 | 1183 | self.assertEqual(out.strip(), b"OK") | |
| 1184 | 1184 | self.assertIn(b"can't create new thread at interpreter shutdown", err) | |
| 1185 | 1185 | ||
| 1186 | - @unittest.expectedFailure # TODO: RUSTPYTHON | ||
| 1187 | 1186 | def test_join_daemon_thread_in_finalization(self): | |
| 1188 | 1187 | # gh-123940: Py_Finalize() prevents other threads from running Python | |
| 1189 | 1188 | # code, so join() can not succeed unless the thread is already done. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -208,6 +208,7 @@ impl NormalizedParseDiagnostic { | |||
| 208 | 208 | /// column. These are reconstructed by re-scanning after ruff's parse has already failed, so they | |
| 209 | 209 | /// carry CPython's wording rather than a translation of ruff's own error, and they never reach | |
| 210 | 210 | /// ruff — `NormalizedParseDiagnostic` and `CompileError` are the only things that consume one. | |
| 211 | + #[derive(Clone)] | ||
| 211 | 212 | struct CpythonDiagnostic { | |
| 212 | 213 | message: String, | |
| 213 | 214 | range: ruff_text_size::TextRange, | |
@@ -243,6 +244,52 @@ impl CpythonDiagnostic { | |||
| 243 | 244 | } | |
| 244 | 245 | } | |
| 245 | 246 | ||
| 247 | + /// Lexer-class failures outrank print hints. Decode and f-string diagnostics | ||
| 248 | + /// compete with lexer failures by offset, and an unterminated quote is only | ||
| 249 | + /// a fallback when no decode/f-string diagnostic exists. Print is considered | ||
| 250 | + /// against the final winner: a later 0x still beats print, but an f-string | ||
| 251 | + /// that replaced that 0x must not hide an earlier print. A decode diagnostic | ||
| 252 | + /// also suppresses an EOF unclosed opener. | ||
| 253 | + #[derive(Clone, Copy, PartialEq, Eq)] | ||
| 254 | + enum OverrideClass { | ||
| 255 | + Lexer, | ||
| 256 | + Decode, | ||
| 257 | + Print, | ||
| 258 | + } | ||
| 259 | + | ||
| 260 | + struct RankedOverride { | ||
| 261 | + diagnostic: CpythonDiagnostic, | ||
| 262 | + unclosed_bracket: bool, | ||
| 263 | + class: OverrideClass, | ||
| 264 | + } | ||
| 265 | + | ||
| 266 | + fn consider_override( | ||
| 267 | + best: &mut Option<RankedOverride>, | ||
| 268 | + diagnostic: CpythonDiagnostic, | ||
| 269 | + class: OverrideClass, | ||
| 270 | + ) { | ||
| 271 | + let unclosed_bracket = diagnostic.is_unclosed_bracket; | ||
| 272 | + consider_ranked(best, diagnostic, unclosed_bracket, class); | ||
| 273 | + } | ||
| 274 | + | ||
| 275 | + fn consider_ranked( | ||
| 276 | + best: &mut Option<RankedOverride>, | ||
| 277 | + diagnostic: CpythonDiagnostic, | ||
| 278 | + unclosed_bracket: bool, | ||
| 279 | + class: OverrideClass, | ||
| 280 | + ) { | ||
| 281 | + if best | ||
| 282 | + .as_ref() | ||
| 283 | + .is_none_or(|current| diagnostic.range.start() < current.diagnostic.range.start()) | ||
| 284 | + { | ||
| 285 | + *best = Some(RankedOverride { | ||
| 286 | + diagnostic, | ||
| 287 | + unclosed_bracket, | ||
| 288 | + class, | ||
| 289 | + }); | ||
| 290 | + } | ||
| 291 | + } | ||
| 292 | + | ||
| 246 | 293 | fn cpython_parse_diagnostic_override( | |
| 247 | 294 | error: &parser::ParseError, | |
| 248 | 295 | source_file: &SourceFile, | |
@@ -258,18 +305,80 @@ fn cpython_parse_diagnostic_override( | |||
| 258 | 305 | }; | |
| 259 | 306 | } | |
| 260 | 307 | ||
| 261 | - source_error!(invalid_number_literal_error(source_text)); | ||
| 262 | - source_error!(invalid_legacy_statement_error(source_text)); | ||
| 263 | - source_error!(incompatible_string_prefix_error(source_text)); | ||
| 264 | - source_error!(malformed_unicode_n_escape_error(source_text)); | ||
| 265 | - source_error!(non_printable_character_error(source_text)); | ||
| 266 | - source_error!(invalid_interpolated_string_error(source_text)); | ||
| 267 | - source_error!(mixed_tstring_literal_error(error, source_text)); | ||
| 268 | - | ||
| 269 | - if let Some(bracket) = bracket_syntax_error(source_text) { | ||
| 308 | + let mut earliest: Option<RankedOverride> = None; | ||
| 309 | + if let Some(diagnostic) = invalid_number_literal_error(source_text) { | ||
| 310 | + consider_override(&mut earliest, diagnostic, OverrideClass::Lexer); | ||
| 311 | + } | ||
| 312 | + if let Some(diagnostic) = incompatible_string_prefix_error(source_text) { | ||
| 313 | + consider_override(&mut earliest, diagnostic, OverrideClass::Lexer); | ||
| 314 | + } | ||
| 315 | + if let Some(diagnostic) = non_printable_character_error(source_text) { | ||
| 316 | + consider_override(&mut earliest, diagnostic, OverrideClass::Lexer); | ||
| 317 | + } | ||
| 318 | + let bracket = bracket_syntax_error(source_text); | ||
| 319 | + if let Some(bracket) = bracket.as_ref() { | ||
| 320 | + // Unclosed openers are reported at the opener and only become errors | ||
| 321 | + // at EOF. A later token-time diagnostic (invalid number, prefix, …) | ||
| 322 | + // must keep winning. Mismatched closers stay in the lexer-class | ||
| 323 | + // positional ranking. | ||
| 324 | + if !bracket.unclosed { | ||
| 325 | + consider_ranked( | ||
| 326 | + &mut earliest, | ||
| 327 | + bracket.diagnostic.clone(), | ||
| 328 | + false, | ||
| 329 | + OverrideClass::Lexer, | ||
| 330 | + ); | ||
| 331 | + } | ||
| 332 | + } | ||
| 333 | + let mut saw_decode = false; | ||
| 334 | + if let Some(diagnostic) = malformed_unicode_n_escape_error(source_text) { | ||
| 335 | + saw_decode = true; | ||
| 336 | + consider_override(&mut earliest, diagnostic, OverrideClass::Decode); | ||
| 337 | + } | ||
| 338 | + if let Some(diagnostic) = invalid_interpolated_string_error(source_text) { | ||
| 339 | + saw_decode = true; | ||
| 340 | + consider_override(&mut earliest, diagnostic, OverrideClass::Decode); | ||
| 341 | + } | ||
| 342 | + if let Some(diagnostic) = mixed_tstring_literal_error(error, source_text) { | ||
| 343 | + saw_decode = true; | ||
| 344 | + consider_override(&mut earliest, diagnostic, OverrideClass::Decode); | ||
| 345 | + } | ||
| 346 | + // A later ordinary unterminated quote is only a fallback. Format-spec | ||
| 347 | + // newlines and empty fields are decode diagnostics and must keep winning. | ||
| 348 | + let line_continuation = matches!( | ||
| 349 | + &error.error, | ||
| 350 | + parser::ParseErrorType::Lexical(parser::LexicalErrorType::LineContinuationError) | ||
| 351 | + ); | ||
| 352 | + if !saw_decode | ||
| 353 | + && !line_continuation | ||
| 354 | + && let Some(diagnostic) = unterminated_string_error(source_text, mode) | ||
| 355 | + { | ||
| 356 | + consider_override(&mut earliest, diagnostic, OverrideClass::Lexer); | ||
| 357 | + } | ||
| 358 | + if earliest | ||
| 359 | + .as_ref() | ||
| 360 | + .is_none_or(|current| current.class != OverrideClass::Lexer) | ||
| 361 | + && let Some(diagnostic) = invalid_legacy_statement_error(source_text) | ||
| 362 | + { | ||
| 363 | + consider_override(&mut earliest, diagnostic, OverrideClass::Print); | ||
| 364 | + } | ||
| 365 | + if !saw_decode | ||
| 366 | + && earliest | ||
| 367 | + .as_ref() | ||
| 368 | + .is_none_or(|current| current.class == OverrideClass::Print) | ||
| 369 | + && let Some(bracket) = bracket.filter(|bracket| bracket.unclosed) | ||
| 370 | + { | ||
| 371 | + consider_ranked( | ||
| 372 | + &mut earliest, | ||
| 373 | + bracket.diagnostic, | ||
| 374 | + true, | ||
| 375 | + OverrideClass::Lexer, | ||
| 376 | + ); | ||
| 377 | + } | ||
| 378 | + if let Some(override_diag) = earliest { | ||
| 270 | 379 | return Some( | |
| 271 | - NormalizedParseDiagnostic::other(source_file, bracket.diagnostic) | ||
| 272 | - .with_unclosed_bracket(bracket.unclosed), | ||
| 380 | + NormalizedParseDiagnostic::other(source_file, override_diag.diagnostic) | ||
| 381 | + .with_unclosed_bracket(override_diag.unclosed_bracket), | ||
| 273 | 382 | ); | |
| 274 | 383 | } | |
| 275 | 384 | ||
@@ -293,7 +402,9 @@ fn cpython_parse_diagnostic_override( | |||
| 293 | 402 | } | |
| 294 | 403 | let loc = source_location(source_file, error.location.start() + TextSize::from(1)); | |
| 295 | 404 | return Some(NormalizedParseDiagnostic::new( | |
| 296 | - error.error.clone(), | ||
| 405 | + parser::ParseErrorType::OtherError( | ||
| 406 | + "unexpected character after line continuation character".to_owned(), | ||
| 407 | + ), | ||
| 297 | 408 | loc, | |
| 298 | 409 | loc, | |
| 299 | 410 | )); | |
@@ -591,7 +702,11 @@ fn invalid_radix_literal_error( | |||
| 591 | 702 | let mut has_digit = false; | |
| 592 | 703 | loop { | |
| 593 | 704 | let Some(&byte) = bytes.get(index) else { | |
| 594 | - return Some((format!("invalid {kind} literal"), start + 1)); | ||
| 705 | + return if has_digit { | ||
| 706 | + None | ||
| 707 | + } else { | ||
| 708 | + Some((format!("invalid {kind} literal"), start + 1)) | ||
| 709 | + }; | ||
| 595 | 710 | }; | |
| 596 | 711 | if byte == b'_' { | |
| 597 | 712 | let Some(&next) = bytes.get(index + 1) else { | |
@@ -5876,6 +5991,7 @@ fn expected_opening_bracket(closing: char) -> char { | |||
| 5876 | 5991 | ||
| 5877 | 5992 | /// A bracket diagnostic, and whether it is an opener that was never closed. The caller needs | |
| 5878 | 5993 | /// that apart from the message because ruff reports the unclosed case as an EOF error. | |
| 5994 | + #[derive(Clone)] | ||
| 5879 | 5995 | struct BracketError { | |
| 5880 | 5996 | diagnostic: CpythonDiagnostic, | |
| 5881 | 5997 | unclosed: bool, | |
@@ -5932,6 +6048,24 @@ fn bracket_syntax_error(source: &str) -> Option<BracketError> { | |||
| 5932 | 6048 | continue; | |
| 5933 | 6049 | } | |
| 5934 | 6050 | ||
| 6051 | + if ch == '\\' { | ||
| 6052 | + match chars.get(index + 1).map(|(_, next)| *next) { | ||
| 6053 | + Some('\n' | '\r') => { | ||
| 6054 | + escape_next = true; | ||
| 6055 | + index += 1; | ||
| 6056 | + continue; | ||
| 6057 | + } | ||
| 6058 | + Some(_) => { | ||
| 6059 | + index += 2; | ||
| 6060 | + continue; | ||
| 6061 | + } | ||
| 6062 | + None => { | ||
| 6063 | + index += 1; | ||
| 6064 | + continue; | ||
| 6065 | + } | ||
| 6066 | + } | ||
| 6067 | + } | ||
| 6068 | + | ||
| 5935 | 6069 | if ch == '\'' || ch == '"' { | |
| 5936 | 6070 | is_raw_string = false; | |
| 5937 | 6071 | for look_back in 1..=2.min(index) { | |
@@ -7496,6 +7630,49 @@ mod tests { | |||
| 7496 | 7630 | ("fu''", "'u' and 'f' prefixes are incompatible"), | |
| 7497 | 7631 | ("fb''", "'b' and 'f' prefixes are incompatible"), | |
| 7498 | 7632 | ("ufr''", "'u' and 'r' prefixes are incompatible"), | |
| 7633 | + ( | ||
| 7634 | + "(]\nbu'x'", | ||
| 7635 | + "closing parenthesis ']' does not match opening parenthesis '('", | ||
| 7636 | + ), | ||
| 7637 | + ("0x\nbu'x'", "invalid hexadecimal literal"), | ||
| 7638 | + ("(0x", "invalid hexadecimal literal"), | ||
| 7639 | + ("print x; 0x", "invalid hexadecimal literal"), | ||
| 7640 | + ("exec x; 0x", "invalid hexadecimal literal"), | ||
| 7641 | + ( | ||
| 7642 | + "print x; 0x1", | ||
| 7643 | + "Missing parentheses in call to 'print'. Did you mean print(...)?", | ||
| 7644 | + ), | ||
| 7645 | + ( | ||
| 7646 | + "print x; (", | ||
| 7647 | + "Missing parentheses in call to 'print'. Did you mean print(...)?", | ||
| 7648 | + ), | ||
| 7649 | + ("print x; )", "unmatched ')'"), | ||
| 7650 | + ( | ||
| 7651 | + "( '\\N'", | ||
| 7652 | + "(unicode error) 'unicodeescape' codec can't decode bytes in position 0-1: malformed \\N character escape", | ||
| 7653 | + ), | ||
| 7654 | + ("(print x", "'(' was never closed"), | ||
| 7655 | + ( | ||
| 7656 | + "(print x; '\\N'", | ||
| 7657 | + "Missing parentheses in call to 'print'. Did you mean print(...)?", | ||
| 7658 | + ), | ||
| 7659 | + ("f'{x'; '", "f-string: expecting '}'"), | ||
| 7660 | + ("f'{x'; 0x", "f-string: expecting '}'"), | ||
| 7661 | + ( | ||
| 7662 | + "print x; f'{x'; 0x", | ||
| 7663 | + "Missing parentheses in call to 'print'. Did you mean print(...)?", | ||
| 7664 | + ), | ||
| 7665 | + ( | ||
| 7666 | + concat!("f'{1:", "d\n}'"), | ||
| 7667 | + "f-string: newlines are not allowed in format specifiers", | ||
| 7668 | + ), | ||
| 7669 | + ("f'{\n}'", "f-string: valid expression required before '}'"), | ||
| 7670 | + ( | ||
| 7671 | + "f'''\n{\n# only a comment\n}'''", | ||
| 7672 | + "f-string: valid expression required before '}'", | ||
| 7673 | + ), | ||
| 7674 | + ("{\\'a\\'}", "unexpected character after line continuation"), | ||
| 7675 | + ("\"\\\n\"(1 for c in I,\\\n\\", "'(' was never closed"), | ||
| 7499 | 7676 | ( | |
| 7500 | 7677 | r"'\N'", | |
| 7501 | 7678 | "(unicode error) 'unicodeescape' codec can't decode bytes in position 0-1: malformed \\N character escape", | |
@@ -7514,6 +7691,23 @@ mod tests { | |||
| 7514 | 7691 | } | |
| 7515 | 7692 | } | |
| 7516 | 7693 | ||
| 7694 | + #[test] | ||
| 7695 | + fn unclosed_fstring_field_keeps_the_unclosed_bracket_flag() { | ||
| 7696 | + let err = compile("f'{", Mode::Eval, "<interp>", CompileOpts::default()) | ||
| 7697 | + .expect_err("should not compile"); | ||
| 7698 | + let crate::CompileError::Parse(parse) = err else { | ||
| 7699 | + panic!("expected a parse error, got {err}"); | ||
| 7700 | + }; | ||
| 7701 | + assert!( | ||
| 7702 | + parse.is_unclosed_bracket, | ||
| 7703 | + "unclosed f-string field must stay incomplete, got {parse}" | ||
| 7704 | + ); | ||
| 7705 | + assert!( | ||
| 7706 | + parse.to_string().contains("'{' was never closed"), | ||
| 7707 | + "got {parse}" | ||
| 7708 | + ); | ||
| 7709 | + } | ||
| 7710 | + | ||
| 7517 | 7711 | #[test] | |
| 7518 | 7712 | fn interpolated_literals_do_not_take_the_escaped_quote_hint() { | |
| 7519 | 7713 | // Parser/lexer/lexer.c offers "perhaps you escaped the end quote?" from its plain-string | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -16,19 +16,19 @@ ssl = ["dep:rustls", "dep:rustls-pemfile", "dep:rustls-webpki", "dep:x509-parser | |||
| 16 | 16 | rustpython-wtf8 = { workspace = true } | |
| 17 | 17 | ||
| 18 | 18 | bitflags = { workspace = true } | |
| 19 | + der = { workspace = true, optional = true } | ||
| 19 | 20 | getrandom = { workspace = true } | |
| 20 | 21 | libc = { workspace = true } | |
| 21 | 22 | num-traits = { workspace = true } | |
| 22 | 23 | parking_lot = { workspace = true } | |
| 23 | 24 | paste = { workspace = true } | |
| 24 | - widestring = { workspace = true } | ||
| 25 | + pkcs8 = { workspace = true, features = ["encryption", "pkcs5", "pem"], optional = true } | ||
| 25 | 26 | rustls = { workspace = true, default-features = false, features = ["std", "tls12"], optional = true } | |
| 26 | 27 | rustls-pemfile = { workspace = true, optional = true } | |
| 27 | 28 | rustls-webpki = { workspace = true, optional = true } | |
| 28 | - x509-parser = { workspace = true, optional = true } | ||
| 29 | - der = { workspace = true, optional = true } | ||
| 30 | - pkcs8 = { workspace = true, features = ["encryption", "pkcs5", "pem"], optional = true } | ||
| 31 | 29 | sha2 = { workspace = true, optional = true } | |
| 30 | + widestring = { workspace = true } | ||
| 31 | + x509-parser = { workspace = true, optional = true } | ||
| 32 | 32 | ||
| 33 | 33 | [target.'cfg(unix)'.dependencies] | |
| 34 | 34 | nix = { workspace = true } | |
@@ -39,6 +39,11 @@ rustix = { workspace = true } | |||
| 39 | 39 | [target.'cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))'.dependencies] | |
| 40 | 40 | num_cpus = "1.17.0" | |
| 41 | 41 | ||
| 42 | + # rustls-pki-types::UnixTime::now is gated off wasm32-unknown-unknown unless | ||
| 43 | + # the `web` feature is on. Browser rustls and Charon/LLBC both need it. | ||
| 44 | + [target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi")))'.dependencies] | ||
| 45 | + rustls-pki-types = { version = "1", default-features = false, features = ["alloc", "std", "web"] } | ||
| 46 | + | ||
| 42 | 47 | [target.'cfg(not(any(target_os = "ios", target_os = "android", target_os = "windows", target_arch = "wasm32", target_os = "redox")))'.dependencies] | |
| 43 | 48 | mac_address = { workspace = true } | |
| 44 | 49 | ||
@@ -76,9 +81,11 @@ windows-sys = { workspace = true, features = [ | |||
| 76 | 81 | "Win32_Security", | |
| 77 | 82 | "Win32_Security_Authorization", | |
| 78 | 83 | "Win32_Storage_FileSystem", | |
| 84 | + "Win32_System_Com", | ||
| 79 | 85 | "Win32_System_Console", | |
| 80 | 86 | "Win32_System_Diagnostics_Debug", | |
| 81 | 87 | "Win32_System_Environment", | |
| 88 | + "Win32_System_Ole", | ||
| 82 | 89 | "Win32_System_IO", | |
| 83 | 90 | "Win32_System_Ioctl", | |
| 84 | 91 | "Win32_System_JobObjects", | |
@@ -106,3 +113,8 @@ cc = "1" | |||
| 106 | 113 | ||
| 107 | 114 | [lints] | |
| 108 | 115 | workspace = true | |
| 116 | + | ||
| 117 | + [package.metadata.cargo-shear] | ||
| 118 | + # Feature-unification only: enables rustls-pki-types `web` so UnixTime::now | ||
| 119 | + # exists on wasm32-unknown-unknown. No rust source imports this crate. | ||
| 120 | + ignored = ["rustls-pki-types"] | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments