diff --git a/Cargo.lock b/Cargo.lock index c95acc34a3e20..e62276662874f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -89,18 +89,18 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-svg" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b9ec8c976eada1b0f9747a3d7cc4eae3bef10613e443746e7487f26c872fde" +checksum = "e22d9f3dea8bbda97c75bd0f0203e23f1e190d6d6f27a40e10063946dc4d4362" dependencies = [ "anstyle", "anstyle-lossy", @@ -111,13 +111,13 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "3.0.10" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -172,11 +172,22 @@ dependencies = [ "tempfile", ] +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "attribute-derive" -version = "0.10.3" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0053e96dd3bec5b4879c23a138d6ef26f2cb936c9cdc96274ac2b9ed44b5bb54" +checksum = "05832cdddc8f2650cc2cc187cc2e952b8c133a48eb055f35211f61ee81502d77" dependencies = [ "attribute-derive-macro", "derive-where", @@ -188,9 +199,9 @@ dependencies = [ [[package]] name = "attribute-derive-macro" -version = "0.10.3" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "463b53ad0fd5b460af4b1915fe045ff4d946d025fb6c4dc3337752eaa980f71b" +checksum = "0a7cdbbd4bd005c5d3e2e9c885e6fa575db4f4a3572335b974d8db853b6beb61" dependencies = [ "collection_literals", "interpolator", @@ -234,26 +245,6 @@ dependencies = [ "virtue", ] -[[package]] -name = "bindgen" -version = "0.72.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" -dependencies = [ - "bitflags 2.10.0", - "cexpr", - "clang-sys", - "itertools 0.13.0", - "log", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn", -] - [[package]] name = "bitflags" version = "1.3.2" @@ -360,9 +351,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.38" +version = "1.2.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80f41ae168f955c12fb8960b057d70d0ca153fb83182b57d86380443527be7e9" +checksum = "cd405d82c84ff7f35739f175f67d8b9fb7687a0e84ccdc78bd3568839827cf07" dependencies = [ "find-msvc-tools", "jobserver", @@ -370,20 +361,11 @@ dependencies = [ "shlex", ] -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] - [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" @@ -399,7 +381,7 @@ checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" dependencies = [ "iana-time-zone", "num-traits", - "windows-link 0.2.0", + "windows-link", ] [[package]] @@ -429,17 +411,6 @@ dependencies = [ "half", ] -[[package]] -name = "clang-sys" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" -dependencies = [ - "glob", - "libc", - "libloading", -] - [[package]] name = "clap" version = "4.5.53" @@ -465,9 +436,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.58" +version = "4.5.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75bf0b32ad2e152de789bb635ea4d3078f6b838ad7974143e99b99f45a04af4a" +checksum = "39615915e2ece2550c0149addac32fb5bd312c657f43845bb9088cb9c8a7c992" dependencies = [ "clap", ] @@ -485,9 +456,9 @@ dependencies = [ [[package]] name = "clap_complete_nushell" -version = "4.5.8" +version = "4.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a0c951694691e65bf9d421d597d68416c22de9632e884c28412cb8cd8b73dce" +checksum = "685bc86fd34b7467e0532a4f8435ab107960d69a243785ef0275e571b35b641a" dependencies = [ "clap", "clap_complete", @@ -507,9 +478,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "clearscreen" @@ -526,28 +497,27 @@ dependencies = [ [[package]] name = "codspeed" -version = "4.0.4" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0f62ea8934802f8b374bf691eea524c3aa444d7014f604dd4182a3667b69510" +checksum = "c3b847e05a34be5c38f3f2a5052178a3bd32e6b5702f3ea775efde95c483a539" dependencies = [ "anyhow", - "bindgen", "cc", "colored 2.2.0", + "getrandom 0.2.16", "glob", "libc", "nix 0.30.1", "serde", "serde_json", "statrs", - "uuid", ] [[package]] name = "codspeed-criterion-compat" -version = "4.0.4" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87efbc015fc0ff1b2001cd87df01c442824de677e01a77230bf091534687abb" +checksum = "30a0e2a53beb18dec493ec133f226e0d35e8bb2fdc638ccf7351696eabee416c" dependencies = [ "clap", "codspeed", @@ -558,9 +528,9 @@ dependencies = [ [[package]] name = "codspeed-criterion-compat-walltime" -version = "4.0.4" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae5713ace440123bb4f1f78dd068d46872cb8548bfe61f752e7b2ad2c06d7f00" +checksum = "1f652d6e6d40bba0f5c244744db94d92a26b7f083df18692df88fb0772f1c793" dependencies = [ "anes", "cast", @@ -583,9 +553,9 @@ dependencies = [ [[package]] name = "codspeed-divan-compat" -version = "4.0.4" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b4214b974f8f5206497153e89db90274e623f06b00bf4b9143eeb7735d975d" +checksum = "f0f0e9fe5eaa39995ec35e46407f7154346cc25bd1300c64c21636f3d00cb2cc" dependencies = [ "clap", "codspeed", @@ -596,9 +566,9 @@ dependencies = [ [[package]] name = "codspeed-divan-compat-macros" -version = "4.0.4" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a53f34a16cb70ce4fd9ad57e1db016f0718e434f34179ca652006443b9a39967" +checksum = "88c8babf2a40fd2206a2e030cf020d0d58144cd56e1dc408bfba02cdefb08b4f" dependencies = [ "divan-macros", "itertools 0.14.0", @@ -610,9 +580,9 @@ dependencies = [ [[package]] name = "codspeed-divan-compat-walltime" -version = "4.0.4" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a5099050c8948dce488b8eaa2e68dc5cf571cb8f9fce99aaaecbdddb940bcd" +checksum = "7f26092328e12a36704ffc552f379c6405dd94d3149970b79b22d371717c2aae" dependencies = [ "cfg-if", "clap", @@ -625,9 +595,9 @@ dependencies = [ [[package]] name = "collection_literals" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b3f65b8fb8e88ba339f7d23a390fe1b0896217da05e2a66c584c9b29a91df8" +checksum = "2550f75b8cfac212855f6b1885455df8eaee8fe8e246b647d69146142e016084" [[package]] name = "colorchoice" @@ -697,7 +667,7 @@ dependencies = [ "libc", "once_cell", "unicode-width", - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -855,9 +825,9 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "typenum", @@ -877,9 +847,9 @@ dependencies = [ [[package]] name = "csv-core" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d" +checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" dependencies = [ "memchr", ] @@ -892,7 +862,7 @@ checksum = "73736a89c4aff73035ba2ed2e565061954da00d4970fc9ac25dcc85a2a20d790" dependencies = [ "dispatch2", "nix 0.30.1", - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -1016,7 +986,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1055,9 +1025,9 @@ dependencies = [ [[package]] name = "doc-comment" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +checksum = "780955b8b195a21ab8e4ac6b60dd1dbdcec1dc6c51c0617964b08c81785e12c9" [[package]] name = "drop_bomb" @@ -1108,7 +1078,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -1135,7 +1105,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de48cc4d1c1d97a20fd819def54b890cadde72ed3ad0c614822a0a433361be96" dependencies = [ "cfg-if", - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -1167,15 +1137,15 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.2" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" [[package]] name = "flate2" -version = "1.1.2" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" dependencies = [ "crc32fast", "miniz_oxide", @@ -1326,12 +1296,13 @@ dependencies = [ [[package]] name = "half" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", + "zerocopy", ] [[package]] @@ -1416,9 +1387,9 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" dependencies = [ "displaydoc", "potential_utf", @@ -1429,9 +1400,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" dependencies = [ "displaydoc", "litemap", @@ -1442,11 +1413,10 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" dependencies = [ - "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", @@ -1457,42 +1427,38 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" dependencies = [ - "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", - "potential_utf", "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" +checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" [[package]] name = "icu_provider" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" dependencies = [ "displaydoc", "icu_locale_core", - "stable_deref_trait", - "tinystr", "writeable", "yoke", "zerofrom", @@ -1693,13 +1659,13 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.16" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1714,9 +1680,9 @@ dependencies = [ [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itertools" @@ -1763,7 +1729,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde_core", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -1810,9 +1776,9 @@ checksum = "a037eddb7d28de1d0fc42411f501b53b75838d313908078d6698d064f3029b24" [[package]] name = "js-sys" -version = "0.3.82" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" dependencies = [ "once_cell", "wasm-bindgen", @@ -1876,14 +1842,10 @@ dependencies = [ ] [[package]] -name = "libloading" -version = "0.8.9" +name = "libm" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" -dependencies = [ - "cfg-if", - "windows-link 0.2.0", -] +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libmimalloc-sys" @@ -1926,17 +1888,16 @@ checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "litemap" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" [[package]] name = "lock_api" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] @@ -2071,18 +2032,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", + "simd-adler32", ] [[package]] name = "mio" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" dependencies = [ "libc", "log", "wasi", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2093,9 +2055,9 @@ checksum = "308d96db8debc727c3fd9744aac51751243420e46edf401010908da7f8d5e57c" [[package]] name = "newtype-uuid" -version = "1.2.4" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17d82edb1c8a6c20c238747ae7aae9181133e766bc92cd2556fdd764407d0d1" +checksum = "5c012d14ef788ab066a347d19e3dda699916c92293b05b85ba2c76b8c82d2830" dependencies = [ "uuid", ] @@ -2166,11 +2128,11 @@ checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" [[package]] name = "nu-ansi-term" -version = "0.50.1" +version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -2180,6 +2142,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -2215,9 +2178,9 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "once_cell_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "oorandom" @@ -2244,12 +2207,12 @@ dependencies = [ [[package]] name = "os_pipe" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db335f4760b14ead6290116f2427bf33a14d4f0617d49f78a246de10c1831224" +checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2263,9 +2226,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -2273,15 +2236,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -2390,20 +2353,19 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.2" +version = "2.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e0a3a33733faeaf8651dfee72dd0f388f0c8e5ad496a3478fa5a922f49cfa8" +checksum = "cbcfd20a6d4eeba40179f05735784ad32bdaef05ce8e8af05f180d45bb3e7e22" dependencies = [ "memchr", - "thiserror 2.0.17", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.8.2" +version = "2.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc58706f770acb1dbd0973e6530a3cff4746fb721207feb3a8a6064cd0b6c663" +checksum = "51f72981ade67b1ca6adc26ec221be9f463f2b5839c7508998daa17c23d94d7f" dependencies = [ "pest", "pest_generator", @@ -2411,9 +2373,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.2" +version = "2.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d4f36811dfe07f7b8573462465d5cb8965fffc2e71ae377a33aecf14c2c9a2f" +checksum = "dee9efd8cdb50d719a80088b76f81aec7c41ed6d522ee750178f83883d271625" dependencies = [ "pest", "pest_meta", @@ -2424,9 +2386,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.8.2" +version = "2.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42919b05089acbd0a5dcd5405fb304d17d1053847b81163d09c4ad18ce8e8420" +checksum = "bf1d70880e76bdc13ba52eafa6239ce793d85c8e43896507e43dd8984ff05b82" dependencies = [ "pest", "sha2", @@ -2499,9 +2461,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" dependencies = [ "zerovec", ] @@ -2552,16 +2514,6 @@ dependencies = [ "yansi", ] -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn", -] - [[package]] name = "proc-macro-crate" version = "3.4.0" @@ -2591,6 +2543,69 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "pyo3" +version = "0.23.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7778bffd85cf38175ac1f545509665d0b9b92a198ca7941f131f85f7a4f9a872" +dependencies = [ + "cfg-if", + "indoc", + "libc", + "memoffset", + "once_cell", + "portable-atomic", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.23.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f6cbe86ef3bf18998d9df6e0f3fc1050a8c5efa409bf712e661a4366e010fb" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.23.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9f1b4c431c0bb1c8fb0a338709859eed0d030ff6daa34368d3b152a63dfdd8d" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.23.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc2201328f63c4710f68abdf653c89d8dbc2858b88c5d88b0ff38a75288a9da" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.23.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fca6726ad0f3da9c9de093d6f116a93c1a38e417ed73bf138472cf4064f72028" +dependencies = [ + "heck", + "proc-macro2", + "pyo3-build-config", + "quote", + "syn", +] + [[package]] name = "pyproject-toml" version = "0.13.7" @@ -2773,9 +2788,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.17" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags 2.10.0", ] @@ -2836,15 +2851,15 @@ dependencies = [ [[package]] name = "regex-lite" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943f41321c63ef1c92fd763bfe054d2668f7f225a5c29f0105903dc2fc04ba30" +checksum = "8d942b98df5e658f56f20d592c7f868833fe38115e65c33003d8cd224b0155da" [[package]] name = "regex-syntax" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "ron" @@ -3140,6 +3155,8 @@ dependencies = [ "natord", "path-absolutize", "pep440_rs", + "pyo3", + "pyo3-build-config", "pyproject-toml", "regex", "ruff_cache", @@ -3160,6 +3177,7 @@ dependencies = [ "ruff_text_size", "rustc-hash", "schemars", + "scopeguard", "serde", "serde_json", "similar", @@ -3570,7 +3588,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -3636,9 +3654,9 @@ dependencies = [ [[package]] name = "schemars" -version = "1.0.5" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1317c3bf3e7df961da95b0a56a172a02abead31276215a0497241a7624b487ce" +checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289" dependencies = [ "dyn-clone", "ref-cast", @@ -3649,9 +3667,9 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "1.0.5" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f760a6150d45dd66ec044983c124595ae76912e77ed0b44124cb3e415cce5d9" +checksum = "301858a4023d78debd2353c7426dc486001bddc91ae31a76fb1f55132f7e2633" dependencies = [ "proc-macro2", "quote", @@ -3767,9 +3785,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.15.1" +version = "3.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa66c845eee442168b2c8134fec70ac50dc20e760769c8ba0ad1319ca1959b04" +checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" dependencies = [ "serde_core", "serde_with_macros", @@ -3777,9 +3795,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.15.1" +version = "3.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91a903660542fced4e99881aa481bdbaec1634568ee02e0b8bd57c64cb38955" +checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" dependencies = [ "darling", "proc-macro2", @@ -3822,6 +3840,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "similar" version = "2.7.0" @@ -3871,9 +3895,9 @@ dependencies = [ [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "static_assertions" @@ -3961,6 +3985,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + [[package]] name = "tempfile" version = "3.23.0" @@ -3971,7 +4001,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -4130,9 +4160,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" dependencies = [ "displaydoc", "zerovec", @@ -4189,9 +4219,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.23.6" +version = "0.23.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3effe7c0e86fdff4f69cdd2ccc1b96f933e24811c5441d44904e8683e27184b" +checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" dependencies = [ "indexmap", "toml_datetime", @@ -4228,9 +4258,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", @@ -4239,9 +4269,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" dependencies = [ "once_cell", "valuable", @@ -4626,9 +4656,9 @@ checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" [[package]] name = "typenum" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "ucd-trie" @@ -4692,9 +4722,9 @@ checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-normalization" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" dependencies = [ "tinyvec", ] @@ -4727,11 +4757,17 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "unindent" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" + [[package]] name = "unit-prefix" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "323402cff2dd658f39ca17c789b502021b3f18707c91cdf22e3838e1b4023817" +checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3" [[package]] name = "unscanny" @@ -4765,9 +4801,9 @@ checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" [[package]] name = "utf8-width" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" +checksum = "1292c0d970b54115d14f2492fe0170adf21d68a1de108eebc51c1df4f346a091" [[package]] name = "utf8_iter" @@ -4813,9 +4849,9 @@ checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "version-ranges" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8d079415ceb2be83fc355adbadafe401307d5c309c7e6ade6638e6f9f42f42d" +checksum = "3595ffe225639f1e0fd8d7269dcc05d2fbfea93cfac2fea367daf1adb60aae91" dependencies = [ "smallvec", ] @@ -4898,9 +4934,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" dependencies = [ "cfg-if", "once_cell", @@ -4911,9 +4947,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.55" +version = "0.4.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" +checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" dependencies = [ "cfg-if", "js-sys", @@ -4924,9 +4960,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4934,9 +4970,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" dependencies = [ "bumpalo", "proc-macro2", @@ -4947,21 +4983,29 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" dependencies = [ "unicode-ident", ] [[package]] name = "wasm-bindgen-test" -version = "0.3.55" +version = "0.3.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfc379bfb624eb59050b509c13e77b4eb53150c350db69628141abce842f2373" +checksum = "25e90e66d265d3a1efc0e72a54809ab90b9c0c515915c67cdf658689d2c22c6c" dependencies = [ + "async-trait", + "cast", "js-sys", + "libm", "minicov", + "nu-ansi-term", + "num-traits", + "oorandom", + "serde", + "serde_json", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test-macro", @@ -4969,9 +5013,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.55" +version = "0.3.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "085b2df989e1e6f9620c1311df6c996e83fe16f57792b272ce1e024ac16a90f1" +checksum = "7150335716dce6028bead2b848e72f47b45e7b9422f64cccdc23bedca89affc1" dependencies = [ "proc-macro2", "quote", @@ -4980,9 +5024,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.82" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" dependencies = [ "js-sys", "wasm-bindgen", @@ -5024,27 +5068,27 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "windows-core" -version = "0.62.0" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57fe7168f7de578d2d8a05b07fd61870d2e73b4020e9f49aa00da8471723497c" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", - "windows-link 0.2.0", + "windows-link", "windows-result", "windows-strings", ] [[package]] name = "windows-implement" -version = "0.60.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", @@ -5053,9 +5097,9 @@ dependencies = [ [[package]] name = "windows-interface" -version = "0.59.1" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", @@ -5064,41 +5108,26 @@ dependencies = [ [[package]] name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - -[[package]] -name = "windows-link" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-result" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link 0.2.0", + "windows-link", ] [[package]] name = "windows-strings" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda" -dependencies = [ - "windows-link 0.2.0", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -5116,16 +5145,16 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.3", + "windows-targets 0.53.5", ] [[package]] name = "windows-sys" -version = "0.61.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link 0.2.0", + "windows-link", ] [[package]] @@ -5146,19 +5175,19 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.3" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link 0.1.3", - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] [[package]] @@ -5169,9 +5198,9 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" @@ -5181,9 +5210,9 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" @@ -5193,9 +5222,9 @@ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" @@ -5205,9 +5234,9 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" @@ -5217,9 +5246,9 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" @@ -5229,9 +5258,9 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" @@ -5241,9 +5270,9 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" @@ -5253,15 +5282,15 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" dependencies = [ "memchr", ] @@ -5280,9 +5309,9 @@ checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "writeable" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] name = "wyz" @@ -5301,11 +5330,10 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "yoke" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" dependencies = [ - "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -5313,9 +5341,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", @@ -5325,18 +5353,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.27" +version = "0.8.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +checksum = "4ea879c944afe8a2b25fef16bb4ba234f47c694565e97383b36f3a878219065c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.27" +version = "0.8.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +checksum = "cf955aa904d6040f70dc8e9384444cb1030aed272ba3cb09bbc4ab9e7c1f34f5" dependencies = [ "proc-macro2", "quote", @@ -5366,9 +5394,9 @@ dependencies = [ [[package]] name = "zerotrie" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" dependencies = [ "displaydoc", "yoke", @@ -5377,9 +5405,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.4" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" dependencies = [ "yoke", "zerofrom", @@ -5388,9 +5416,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", diff --git a/crates/ruff/Cargo.toml b/crates/ruff/Cargo.toml index 07d7975a4327f..da89720b70be2 100644 --- a/crates/ruff/Cargo.toml +++ b/crates/ruff/Cargo.toml @@ -91,3 +91,10 @@ tikv-jemallocator = { workspace = true } [lints] workspace = true + +[features] +default = [] +ext-lint = [ + "ruff_linter/ext-lint", + "ruff_workspace/ext-lint", +] diff --git a/crates/ruff/src/args.rs b/crates/ruff/src/args.rs index 370186a0b49e2..ca35cc0ae7ebe 100644 --- a/crates/ruff/src/args.rs +++ b/crates/ruff/src/args.rs @@ -28,7 +28,7 @@ use ruff_options_metadata::{OptionEntry, OptionsMetadata}; use ruff_python_ast as ast; use ruff_source_file::{LineIndex, OneIndexed, PositionEncoding}; use ruff_text_size::TextRange; -use ruff_workspace::configuration::{Configuration, RuleSelection}; +use ruff_workspace::configuration::{Configuration, ExternalRuleSelection, RuleSelection}; use ruff_workspace::options::{Options, PycodestyleOptions}; use ruff_workspace::resolver::ConfigurationTransformer; use rustc_hash::FxHashMap; @@ -469,6 +469,53 @@ pub struct CheckCommand { conflicts_with = "watch", )] pub show_settings: bool, + /// List configured external AST linters and exit. + #[arg( + long, + help_heading = "External linter options", + conflicts_with = "add_noqa" + )] + pub list_external_linters: bool, + /// Restrict linting to the given external linter IDs. + #[arg( + long = "select-external", + value_name = "LINTER", + action = clap::ArgAction::Append, + help_heading = "External linter options", + )] + pub select_external: Vec, + /// Enable additional external linter IDs or rule codes without replacing existing selections. + #[arg( + long = "extend-select-external", + value_name = "LINTER", + action = clap::ArgAction::Append, + help_heading = "External linter options", + )] + pub extend_select_external: Vec, + /// Disable the given external linter IDs or rule codes. + #[arg( + long = "ignore-external", + value_name = "LINTER", + action = clap::ArgAction::Append, + help_heading = "External linter options", + )] + pub ignore_external: Vec, + /// Disable additional external linter IDs or rule codes without replacing existing ignores. + #[arg( + long = "extend-ignore-external", + value_name = "LINTER", + action = clap::ArgAction::Append, + help_heading = "External linter options", + )] + pub extend_ignore_external: Vec, + /// Validate external linter definitions without running lint checks. + #[arg( + long = "verify-external-linters", + help_heading = "External linter options", + conflicts_with = "add_noqa", + conflicts_with = "list_external_linters" + )] + pub verify_external_linters: bool, } #[derive(Clone, Debug, clap::Parser)] @@ -666,6 +713,14 @@ impl ConfigArguments { self.config_file.as_deref() } + pub(crate) fn has_cli_external_selection(&self) -> bool { + self.per_flag_overrides + .select_external + .as_ref() + .is_some_and(|selection| !selection.is_empty()) + || !self.per_flag_overrides.extend_select_external.is_empty() + } + fn from_cli_arguments( global_options: GlobalConfigArgs, per_flag_overrides: ExplicitConfigOverrides, @@ -737,6 +792,70 @@ impl CheckCommand { self, global_options: GlobalConfigArgs, ) -> anyhow::Result<(CheckArguments, ConfigArguments)> { + if let Some(invalid) = self + .select_external + .iter() + .find(|selector| is_builtin_rule_selector(selector)) + { + anyhow::bail!( + "Internal rule `{invalid}` cannot be enabled with `--select-external`; use `--select` instead." + ); + } + if let Some(selector) = self.select.as_ref().and_then(|selectors| { + selectors.iter().find_map(|selector| { + if let RuleSelector::External { code } = selector { + Some(code.as_ref().to_string()) + } else { + None + } + }) + }) { + anyhow::bail!( + "External rule `{selector}` cannot be enabled with `--select`; use `--select-external` instead." + ); + } + if let Some(selector) = self.extend_select.as_ref().and_then(|selectors| { + selectors.iter().find_map(|selector| { + if let RuleSelector::External { code } = selector { + Some(code.as_ref().to_string()) + } else { + None + } + }) + }) { + anyhow::bail!( + "External rule `{selector}` cannot be enabled with `--extend-select`; use `--extend-select-external` instead." + ); + } + if let Some(invalid) = self + .extend_select_external + .iter() + .find(|selector| is_builtin_rule_selector(selector)) + { + anyhow::bail!( + "Internal rule `{invalid}` cannot be enabled with `--extend-select-external`; use `--extend-select` instead." + ); + } + if let Some(invalid) = self + .ignore_external + .iter() + .chain(self.extend_ignore_external.iter()) + .find(|selector| is_builtin_rule_selector(selector)) + { + anyhow::bail!( + "Internal rule `{invalid}` cannot be disabled with `--ignore-external`; use `--ignore` instead." + ); + } + + let select_external_override = if self.select_external.is_empty() { + None + } else { + Some(self.select_external.clone()) + }; + let extend_select_external_override = self.extend_select_external.clone(); + let ignore_external_override = self.ignore_external.clone(); + let extend_ignore_external_override = self.extend_ignore_external.clone(); + let check_arguments = CheckArguments { add_noqa: self.add_noqa, diff: self.diff, @@ -748,6 +867,12 @@ impl CheckCommand { output_file: self.output_file, show_files: self.show_files, show_settings: self.show_settings, + list_external_linters: self.list_external_linters, + select_external: self.select_external, + extend_select_external: self.extend_select_external, + ignore_external: self.ignore_external, + extend_ignore_external: self.extend_ignore_external, + verify_external_linters: self.verify_external_linters, statistics: self.statistics, stdin_filename: self.stdin_filename, watch: self.watch, @@ -781,6 +906,10 @@ impl CheckCommand { output_format: self.output_format, show_fixes: resolve_bool_arg(self.show_fixes, self.no_show_fixes), extension: self.extension, + select_external: select_external_override, + extend_select_external: extend_select_external_override, + ignore_external: ignore_external_override, + extend_ignore_external: extend_ignore_external_override, ..ExplicitConfigOverrides::default() }; @@ -1083,6 +1212,12 @@ pub struct CheckArguments { pub output_file: Option, pub show_files: bool, pub show_settings: bool, + pub list_external_linters: bool, + pub select_external: Vec, + pub extend_select_external: Vec, + pub ignore_external: Vec, + pub extend_ignore_external: Vec, + pub verify_external_linters: bool, pub statistics: bool, pub stdin_filename: Option, pub watch: bool, @@ -1347,6 +1482,10 @@ struct ExplicitConfigOverrides { detect_string_imports: Option, string_imports_min_dots: Option, type_checking_imports: Option, + select_external: Option>, + extend_select_external: Vec, + ignore_external: Vec, + extend_ignore_external: Vec, } impl ConfigurationTransformer for ExplicitConfigOverrides { @@ -1440,11 +1579,33 @@ impl ConfigurationTransformer for ExplicitConfigOverrides { if let Some(type_checking_imports) = &self.type_checking_imports { config.analyze.type_checking_imports = Some(*type_checking_imports); } + if self.select_external.is_some() + || !self.extend_select_external.is_empty() + || !self.ignore_external.is_empty() + || !self.extend_ignore_external.is_empty() + { + config + .lint + .external_rule_selections + .push(ExternalRuleSelection { + select: self.select_external.clone(), + extend_select: self.extend_select_external.clone(), + ignore: self.ignore_external.clone(), + extend_ignore: self.extend_ignore_external.clone(), + }); + } config } } +fn is_builtin_rule_selector(selector: &str) -> bool { + matches!( + RuleSelector::from_str(selector), + Ok(RuleSelector::Linter(_) | RuleSelector::Prefix { .. } | RuleSelector::Rule { .. }) + ) +} + /// Convert a list of `PatternPrefixPair` structs to `PerFileIgnore`. pub fn collect_per_file_ignores(pairs: Vec) -> Vec { let mut per_file_ignores: FxHashMap> = FxHashMap::default(); diff --git a/crates/ruff/src/commands/check.rs b/crates/ruff/src/commands/check.rs index dcdd0f9b18c19..887e30dbfa2d3 100644 --- a/crates/ruff/src/commands/check.rs +++ b/crates/ruff/src/commands/check.rs @@ -21,6 +21,7 @@ use ruff_linter::settings::{LinterSettings, flags}; use ruff_linter::{IOError, Violation, fs, warn_user_once}; use ruff_source_file::SourceFileBuilder; use ruff_text_size::TextRange; +use ruff_workspace::Settings; use ruff_workspace::resolver::{ PyprojectConfig, ResolvedFile, match_exclusion, python_files_in_path, }; @@ -28,6 +29,7 @@ use ruff_workspace::resolver::{ use crate::args::ConfigArguments; use crate::cache::{Cache, PackageCacheMap, PackageCaches}; use crate::diagnostics::Diagnostics; +use crate::{apply_external_linter_selection_to_settings, compute_external_selection_state}; /// Run the linter over a collection of files. pub(crate) fn check( @@ -41,6 +43,20 @@ pub(crate) fn check( ) -> Result { // Collect all the Python files to check. let start = Instant::now(); + let apply_external_selection = |settings: &mut Settings| -> Result<()> { + let state = compute_external_selection_state( + &settings.linter.selected_external, + &settings.linter.ignored_external, + &[], + &[], + &[], + &[], + ); + settings.linter.selected_external = state.effective.iter().cloned().collect(); + settings.linter.ignored_external = state.ignored.iter().cloned().collect(); + apply_external_linter_selection_to_settings(settings, &state.effective, &state.ignored)?; + Ok(()) + }; let (paths, resolver) = python_files_in_path(files, pyproject_config, config_arguments)?; debug!("Identified files to lint in: {:?}", start.elapsed()); @@ -49,6 +65,19 @@ pub(crate) fn check( return Ok(Diagnostics::default()); } + let resolver = resolver.transform_settings(apply_external_selection)?; + let selection_from_cli = config_arguments.has_cli_external_selection(); + let any_external_selection = selection_from_cli + || resolver + .settings() + .any(|settings| !settings.linter.selected_external.is_empty()); + let any_external_registry = resolver + .settings() + .any(|settings| settings.linter.external_ast.is_some()); + if any_external_selection && !any_external_registry { + anyhow::bail!("No external AST linters are configured in this workspace."); + } + // Discover the package root for each Python file. let package_roots = resolver.package_roots( &paths diff --git a/crates/ruff/src/commands/format.rs b/crates/ruff/src/commands/format.rs index 0e245efa8c2ec..57d34512dafb5 100644 --- a/crates/ruff/src/commands/format.rs +++ b/crates/ruff/src/commands/format.rs @@ -596,7 +596,7 @@ impl<'a> FormatResults<'a> { .iter() .map(Diagnostic::from) .chain(self.to_diagnostics(&mut notebook_index)) - .sorted_unstable_by(Diagnostic::ruff_start_ordering) + .sorted_by(Diagnostic::ruff_start_ordering) .collect(); let context = EmitterContext::new(¬ebook_index); diff --git a/crates/ruff/src/external.rs b/crates/ruff/src/external.rs new file mode 100644 index 0000000000000..ec1325bb99fa2 --- /dev/null +++ b/crates/ruff/src/external.rs @@ -0,0 +1,298 @@ +use std::io::{self, Write}; + +use anyhow::Result; +use ruff_linter::external::ExternalLintRegistry; +use ruff_linter::external::ast::rule::{ExternalAstLinter, ExternalAstRule}; +use ruff_linter::registry::Rule; +use ruff_workspace::Settings; +use rustc_hash::FxHashSet; + +#[derive(Debug)] +pub(crate) struct ExternalSelectionState { + pub ignored: FxHashSet, + pub effective: FxHashSet, +} + +pub(crate) fn compute_external_selection_state( + base_selected: &[String], + base_ignored: &[String], + cli_select: &[String], + cli_extend_select: &[String], + cli_ignore: &[String], + cli_extend_ignore: &[String], +) -> ExternalSelectionState { + let mut selected: FxHashSet = if cli_select.is_empty() { + base_selected.iter().cloned().collect() + } else { + FxHashSet::default() + }; + selected.extend(cli_select.iter().cloned()); + selected.extend(cli_extend_select.iter().cloned()); + + let mut ignored: FxHashSet = base_ignored.iter().cloned().collect(); + ignored.extend(cli_ignore.iter().cloned()); + ignored.extend(cli_extend_ignore.iter().cloned()); + + let effective = selected + .iter() + .filter(|code| !ignored.contains(*code)) + .cloned() + .collect(); + + ExternalSelectionState { ignored, effective } +} + +pub(crate) fn apply_external_linter_selection_to_settings( + settings: &mut Settings, + selected: &FxHashSet, + ignored: &FxHashSet, +) -> Result { + let linter = &mut settings.linter; + + if selected.is_empty() { + if linter.rules.enabled(Rule::ExternalLinter) { + linter.rules.disable(Rule::ExternalLinter); + } + linter.selected_external.clear(); + linter.external_ast = None; + return Ok(true); + } + + if !linter.rules.enabled(Rule::ExternalLinter) { + linter.selected_external.clear(); + linter.external_ast = None; + return Ok(false); + } + + if let Some(registry) = linter.external_ast.take() { + let selection = select_external_linters(®istry, selected, ignored); + if !selection.missing.is_empty() { + anyhow::bail!( + "Unknown external linter or rule selector(s): {}", + selection.missing.join(", ") + ); + } + + let mut filtered = ExternalLintRegistry::new(); + for matched in &selection.matches { + filtered.insert_linter(matched.clone_selected())?; + } + + linter.selected_external = selected.iter().cloned().collect(); + let codes: Vec = filtered + .iter_enabled_rules() + .map(|rule| rule.code.as_str().to_string()) + .collect(); + + if filtered.is_empty() { + linter.rules.disable(Rule::ExternalLinter); + linter.external_ast = None; + linter.selected_external.clear(); + } else { + linter.rules.enable(Rule::ExternalLinter, false); + linter.external_ast = Some(filtered); + let external_codes = &mut linter.external; + for code in codes { + if !external_codes.iter().any(|existing| existing == &code) { + external_codes.push(code); + } + } + } + + Ok(true) + } else { + Ok(false) + } +} + +pub(crate) fn select_external_linters<'a>( + registry: &'a ExternalLintRegistry, + selected: &FxHashSet, + ignored: &FxHashSet, +) -> SelectedExternalLinters<'a> { + let mut matches = Vec::new(); + let mut missing = Vec::new(); + + let enabled_linters: Vec<&'a ExternalAstLinter> = registry + .linters() + .iter() + .filter(|linter| linter.enabled) + .collect(); + + if selected.is_empty() { + matches.extend( + enabled_linters + .iter() + .copied() + .map(SelectedExternalLinter::all_rules), + ); + return SelectedExternalLinters { matches, missing }; + } + + let mut satisfied: FxHashSet<&'a str> = FxHashSet::default(); + let mut available_linter_ids: FxHashSet<&'a str> = FxHashSet::default(); + + for linter in &enabled_linters { + available_linter_ids.insert(linter.id.as_str()); + } + + for linter in enabled_linters { + let selected_linter = selected.contains(linter.id.as_str()); + + if selected_linter && ignored.is_empty() { + matches.push(SelectedExternalLinter::all_rules(linter)); + satisfied.insert(linter.id.as_str()); + continue; + } + + let included: Vec<_> = linter + .rules + .iter() + .filter(|rule| !ignored.contains(rule.code.as_str())) + .collect(); + + if selected_linter { + if included.is_empty() { + missing.push(linter.id.clone()); + continue; + } + + satisfied.insert(linter.id.as_str()); + for rule in &included { + if selected.contains(rule.code.as_str()) { + satisfied.insert(rule.code.as_str()); + } + } + + matches.push(SelectedExternalLinter::subset(linter, included)); + continue; + } + + let matched_rules: Vec<_> = included + .iter() + .copied() + .filter(|rule| selected.contains(rule.code.as_str())) + .collect(); + + if matched_rules.is_empty() { + continue; + } + + for rule in &matched_rules { + satisfied.insert(rule.code.as_str()); + } + + matches.push(SelectedExternalLinter::subset(linter, matched_rules)); + } + + for selector in selected { + let selector = selector.as_str(); + if ignored.contains(selector) || satisfied.contains(selector) { + continue; + } + + if available_linter_ids.contains(selector) { + continue; + } + + if registry.find_rule_by_code(selector).is_some() { + continue; + } + + missing.push(selector.to_string()); + } + + SelectedExternalLinters { matches, missing } +} + +#[derive(Debug)] +pub(crate) struct SelectedExternalLinters<'a> { + pub matches: Vec>, + pub missing: Vec, +} + +pub(crate) fn print_external_linters( + registry: &ExternalLintRegistry, + linters: &[SelectedExternalLinter<'_>], + mut writer: impl Write, +) -> io::Result<()> { + match (registry.is_empty(), linters.is_empty()) { + (true, _) => writeln!(writer, "No external AST linters configured.")?, + (false, true) => writeln!(writer, "No matching external AST linters found.")?, + (false, false) => { + for selected in linters { + selected.print(&mut writer)?; + } + } + } + Ok(()) +} + +#[derive(Debug)] +pub(crate) struct SelectedExternalLinter<'a> { + linter: &'a ExternalAstLinter, + selection: SelectedRules<'a>, +} + +impl<'a> SelectedExternalLinter<'a> { + fn all_rules(linter: &'a ExternalAstLinter) -> Self { + Self { + linter, + selection: SelectedRules::All, + } + } + + fn subset(linter: &'a ExternalAstLinter, rules: Vec<&'a ExternalAstRule>) -> Self { + debug_assert!(!rules.is_empty()); + Self { + linter, + selection: SelectedRules::Subset(rules), + } + } + + fn clone_selected(&self) -> ExternalAstLinter { + match &self.selection { + SelectedRules::All => self.linter.clone(), + SelectedRules::Subset(rules) => ExternalAstLinter { + id: self.linter.id.clone(), + name: self.linter.name.clone(), + description: self.linter.description.clone(), + enabled: self.linter.enabled, + rules: rules.iter().map(|&rule| rule.clone()).collect(), + }, + } + } + + fn print(&self, writer: &mut impl Write) -> io::Result<()> { + match &self.selection { + SelectedRules::All => write!(writer, "{}", self.linter), + SelectedRules::Subset(rules) => { + writeln!( + writer, + "{}{}", + self.linter.id, + if self.linter.enabled { + "" + } else { + " (disabled)" + } + )?; + writeln!(writer, " name: {}", self.linter.name)?; + if let Some(description) = &self.linter.description { + writeln!(writer, " description: {description}")?; + } + writeln!(writer, " rules:")?; + for rule in rules { + writeln!(writer, " - {} ({})", rule.code.as_str(), rule.name)?; + } + writeln!(writer) + } + } + } +} + +#[derive(Debug)] +enum SelectedRules<'a> { + All, + Subset(Vec<&'a ExternalAstRule>), +} diff --git a/crates/ruff/src/lib.rs b/crates/ruff/src/lib.rs index 3ea0d94fadcad..47a4b64d9103c 100644 --- a/crates/ruff/src/lib.rs +++ b/crates/ruff/src/lib.rs @@ -14,26 +14,35 @@ use notify::{RecursiveMode, Watcher, recommended_watcher}; use args::{GlobalConfigArgs, ServerCommand}; use ruff_db::diagnostic::{Diagnostic, Severity}; +use ruff_linter::external::ExternalLintRegistry; use ruff_linter::logging::{LogLevel, set_up_logging}; use ruff_linter::settings::flags::FixMode; use ruff_linter::settings::types::OutputFormat; use ruff_linter::{fs, warn_user, warn_user_once}; use ruff_workspace::Settings; +use ruff_workspace::resolver::PyprojectConfig; +use rustc_hash::FxHashSet; -use crate::args::{ - AnalyzeCommand, AnalyzeGraphCommand, Args, CheckCommand, Command, FormatCommand, +use crate::{ + args::{AnalyzeCommand, AnalyzeGraphCommand, Args, CheckCommand, Command, FormatCommand}, + printer::{Flags as PrinterFlags, Printer}, }; -use crate::printer::{Flags as PrinterFlags, Printer}; pub mod args; mod cache; mod commands; mod diagnostics; +mod external; mod printer; pub mod resolve; mod stdin; mod version; +pub(crate) use external::{ + apply_external_linter_selection_to_settings, compute_external_selection_state, + print_external_linters, select_external_linters, +}; + #[derive(Copy, Clone)] pub enum ExitStatus { /// Linting was successful and there were no linting errors. @@ -125,6 +134,14 @@ fn resolve_default_files(files: Vec, is_stdin: bool) -> Vec { } } +fn apply_external_linter_selection( + pyproject_config: &mut PyprojectConfig, + selected: &FxHashSet, + ignored: &FxHashSet, +) -> Result { + apply_external_linter_selection_to_settings(&mut pyproject_config.settings, selected, ignored) +} + pub fn run( Args { command, @@ -238,7 +255,7 @@ pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result = match cli.output_file { Some(path) if !cli.watch => { @@ -256,6 +273,48 @@ pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result Result Result ExternalLintRegistry { + let mut registry = ExternalLintRegistry::new(); + + let rules = vec![ + ExternalAstRule::new( + ExternalRuleCode::new("EXT001").unwrap(), + "FirstRule", + None::<&str>, + vec![AstTarget::Stmt(StmtKind::FunctionDef)], + ExternalRuleScript::file( + PathBuf::from("ext001.py"), + "def check_stmt(node, ctx):\n pass\n", + ), + None, + ), + ExternalAstRule::new( + ExternalRuleCode::new("EXT002").unwrap(), + "SecondRule", + None::<&str>, + vec![AstTarget::Stmt(StmtKind::FunctionDef)], + ExternalRuleScript::file( + PathBuf::from("ext002.py"), + "def check_stmt(node, ctx):\n pass\n", + ), + None, + ), + ]; + + let linter = ExternalAstLinter::new("demo", "Demo", None::<&str>, true, rules); + registry.insert_linter(linter).unwrap(); + registry + } + + #[test] + fn selecting_linter_respects_ignored_rule_codes() { + let registry = make_registry(); + + let mut settings = Settings::default(); + settings.linter.external_ast = Some(registry); + settings.linter.selected_external = vec!["demo".to_string()]; + settings.linter.ignored_external = vec!["EXT002".to_string()]; + settings + .linter + .rules + .enable(ruff_linter::registry::Rule::ExternalLinter, false); + + let selected: FxHashSet = + settings.linter.selected_external.iter().cloned().collect(); + let ignored: FxHashSet = settings.linter.ignored_external.iter().cloned().collect(); + apply_external_linter_selection_to_settings(&mut settings, &selected, &ignored).unwrap(); + + let filtered = settings + .linter + .external_ast + .expect("external registry should remain configured"); + assert!( + filtered.find_rule_by_code("EXT002").is_none(), + "ignored rule code should be excluded when selecting the entire linter" + ); + assert!( + filtered.find_rule_by_code("EXT001").is_some(), + "other rules should remain enabled" + ); + } +} + #[cfg(test)] mod test_file_change_detector { use std::path::PathBuf; diff --git a/crates/ruff/tests/cli/ext_lint.rs b/crates/ruff/tests/cli/ext_lint.rs new file mode 100644 index 0000000000000..1a377cf33ec38 --- /dev/null +++ b/crates/ruff/tests/cli/ext_lint.rs @@ -0,0 +1,275 @@ +use anyhow::Result; + +use crate::CliTest; + +#[test] +fn external_ast_reports_all_parse_errors() -> Result<()> { + let test = CliTest::new()?; + test.write_file( + "lint/external/alpha.toml", + r#" +name = "Alpha" + +[[rule]] +code = "AXX001" +name = "AlphaRule" +targets = ["stmt:FunctionDef"] +script = "rules/alpha.py" +"#, + )?; + test.write_file( + "lint/external/beta.toml", + r#" +name = "Beta" + +[[rule]] +code = "BXX001" +name = "BetaRule" +targets = ["stmt:FunctionDef"] +script = "rules/beta.py" +"#, + )?; + test.write_file("lint/external/rules/alpha.py", "def alpha(:\n")?; + test.write_file("lint/external/rules/beta.py", "def beta(:\n")?; + + let config = format!( + r#" +[lint.external-ast.alpha] +path = "{}" + +[lint.external-ast.beta] +path = "{}" +"#, + test.root().join("lint/external/alpha.toml").display(), + test.root().join("lint/external/beta.toml").display(), + ); + test.write_file("ruff.toml", &config)?; + + let output = test + .command() + .args(["check", "--config", "ruff.toml", "."]) + .output()?; + + assert!( + !output.status.success(), + "command unexpectedly succeeded: {}", + String::from_utf8_lossy(&output.stderr) + ); + let stderr = std::str::from_utf8(&output.stderr)?; + assert!( + stderr.contains("alpha"), + "stderr missing alpha error: {stderr}" + ); + assert!( + stderr.contains("beta"), + "stderr missing beta error: {stderr}" + ); + Ok(()) +} + +#[test] +fn external_ast_executes_rules() -> Result<()> { + let test = CliTest::new()?; + test.write_file( + "lint/external/demo.toml", + r#" +name = "Demo" + +[[rule]] +code = "EXT001" +name = "ExampleRule" +targets = ["stmt:FunctionDef"] +script = "rules/example.py" +"#, + )?; + test.write_file( + "lint/external/rules/example.py", + r#" +def check_stmt(node, ctx): + if node["_kind"] == "FunctionDef": + ctx.report("hello from script") +"#, + )?; + let linter_path = test.root().join("lint/external/demo.toml"); + let config = format!( + r#" +[lint.external-ast.demo] +path = "{}" +"#, + linter_path.display() + ); + test.write_file("ruff.toml", &config)?; + test.write_file( + "src/example.py", + r#" +def demo(value=0): + return value +"#, + )?; + + let output = test + .command() + .args([ + "check", + "--config", + "ruff.toml", + "--select", + "RUF300", + "--select-external", + "EXT001", + "src/example.py", + ]) + .output()?; + assert!( + !output.status.success(), + "command unexpectedly succeeded: {}", + String::from_utf8_lossy(&output.stderr) + ); + let stdout = String::from_utf8_lossy(&output.stdout); + assert!(stdout.contains("EXT001"), "stdout missing EXT001: {stdout}"); + assert!( + stdout.contains("hello from script"), + "stdout missing message: {stdout}" + ); + Ok(()) +} + +#[test] +fn external_ast_respects_noqa() -> Result<()> { + let test = CliTest::new()?; + test.write_file( + "lint/external/demo.toml", + r#" +name = "Demo" + +[[rule]] +code = "EXT001" +name = "ExampleRule" +targets = ["stmt:FunctionDef"] +script = "rules/example.py" +"#, + )?; + test.write_file( + "lint/external/rules/example.py", + r#" +def check_stmt(node, ctx): + if node["_kind"] == "FunctionDef": + ctx.report("hello from script") +"#, + )?; + let linter_path = test.root().join("lint/external/demo.toml"); + let config = format!( + r#" +[lint.external-ast.demo] +path = "{}" +"#, + linter_path.display() + ); + test.write_file("ruff.toml", &config)?; + test.write_file( + "src/example.py", + r#" +def demo(value=0): # noqa: EXT001 + return value +"#, + )?; + + let output = test + .command() + .args([ + "check", + "--config", + "ruff.toml", + "--select", + "RUF300", + "--select-external", + "EXT001", + "src/example.py", + ]) + .output()?; + assert!( + output.status.success(), + "command unexpectedly failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + let stdout = String::from_utf8_lossy(&output.stdout); + assert!( + !stdout.contains("EXT001"), + "stdout unexpectedly reported EXT001: {stdout}" + ); + Ok(()) +} + +#[test] +fn external_logging_linter_reports_interpolation() -> Result<()> { + let test = CliTest::new()?; + test.write_file( + "lint/external/logging.toml", + r#" +name = "Logging" + +[[rule]] +code = "EXT801" +name = "LoggingInterpolation" +targets = ["expr:Call"] +call_callee_regex = "(?i).*log.*\\.(debug|info|warning|warn|error|exception|critical|fatal)$" +script = "logging/logging_interpolation.py" +"#, + )?; + test.write_file( + "lint/external/logging/logging_interpolation.py", + include_str!("../fixtures/external/logging_linter.py"), + )?; + let linter_path = test.root().join("lint/external/logging.toml"); + let config = format!( + r#" +[lint.external-ast.logging] +path = "{}" +"#, + linter_path.display() + ); + test.write_file("ruff.toml", &config)?; + test.write_file( + "src/logging_cases.py", + include_str!("../fixtures/external/logging_cases.py"), + )?; + + let output = test + .command() + .args([ + "check", + "--config", + "ruff.toml", + "--select", + "RUF300", + "--select-external", + "EXT801", + "src/logging_cases.py", + ]) + .output()?; + assert!( + !output.status.success(), + "command unexpectedly succeeded: {}", + String::from_utf8_lossy(&output.stderr) + ); + let stdout = String::from_utf8_lossy(&output.stdout); + let hits: Vec<_> = stdout + .lines() + .filter(|line| line.contains("EXT801")) + .collect(); + assert!( + !hits.is_empty(), + "stdout missing EXT801 logging violations: {stdout}" + ); + assert_eq!( + hits.len(), + 3, + "expected three logging interpolation diagnostics: {stdout}" + ); + assert!( + hits.iter() + .all(|line| line.contains("LoggingInterpolation: Logging message")), + "stdout missing logging interpolation message: {stdout}" + ); + Ok(()) +} diff --git a/crates/ruff/tests/cli/lint.rs b/crates/ruff/tests/cli/lint.rs index 25500ed346c4f..46ea9a98db321 100644 --- a/crates/ruff/tests/cli/lint.rs +++ b/crates/ruff/tests/cli/lint.rs @@ -21,6 +21,44 @@ impl CliTest { } } +fn toml_path(path: &std::path::Path) -> String { + // Escape backslashes so Windows paths stay valid inside TOML strings. + path.to_string_lossy().replace('\\', "\\\\") +} + +fn write_demo_external_linter(test: &CliTest) -> Result { + test.write_file( + "lint/external/demo.toml", + r#" +name = "Demo External Linter" +description = "Shows how to configure external AST linters" + +[[rule]] +code = "EXT001" +name = "ExampleRule" +summary = "Provides illustrative coverage" +targets = ["stmt:FunctionDef"] +script = "rules/example.py" + +[[rule]] +code = "EXT002" +name = "AnotherRule" +summary = "Provides illustrative coverage" +targets = ["stmt:FunctionDef"] +script = "rules/example.py" +"#, + )?; + test.write_file( + "lint/external/rules/example.py", + r#" +def check_stmt(node, ctx): + if node["_kind"] == "FunctionDef": + ctx.report("external lint fired") +"#, + )?; + Ok(test.root().join("lint/external/demo.toml")) +} + #[test] fn top_level_options() -> Result<()> { let test = CliTest::new()?; @@ -168,6 +206,787 @@ inline-quotes = "single" Ok(()) } +#[test] +fn external_ast_linter_listing() -> Result<()> { + let test = CliTest::new()?; + test.write_file( + "lint/external/demo.toml", + r#" +name = "Demo External Linter" +description = "Shows how to configure external AST linters" + +[[rule]] +code = "EXT001" +name = "ExampleRule" +summary = "Provides illustrative coverage" +targets = ["stmt:FunctionDef"] +script = "rules/example.py" + +[[rule]] +code = "EXT002" +name = "AnotherRule" +summary = "Demonstrates code-based selection" +targets = ["stmt:FunctionDef"] +script = "rules/example.py" +"#, + )?; + test.write_file( + "lint/external/rules/example.py", + r#" +def check_stmt(node, ctx): + pass + +def check_expr(node, ctx): + pass +"#, + )?; + let linter_path = test.root().join("lint/external/demo.toml"); + let config = format!( + r#" +[lint.external-ast.demo] +path = "{}" + +[lint] +select-external = ["EXT001"] +"#, + toml_path(&linter_path) + ); + test.write_file("ruff.toml", &config)?; + + let output = test + .command() + .args(["check", "--config", "ruff.toml", "--list-external-linters"]) + .output()?; + + assert!( + output.status.success(), + "command failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + let stdout = std::str::from_utf8(&output.stdout)?; + assert!(stdout.contains("demo")); + assert!(stdout.contains("Demo External Linter")); + assert!(stdout.contains("EXT001")); + assert!(stdout.contains("ExampleRule")); + + Ok(()) +} + +#[test] +fn external_ast_linter_listing_filtered_by_code() -> Result<()> { + let test = CliTest::new()?; + test.write_file( + "lint/external/demo.toml", + r#" +name = "Demo External Linter" +description = "Shows how to configure external AST linters" + +[[rule]] +code = "EXT001" +name = "ExampleRule" +summary = "Provides illustrative coverage" +targets = ["stmt:FunctionDef"] +script = "rules/example.py" + +[[rule]] +code = "EXT002" +name = "AnotherRule" +summary = "Demonstrates code-based selection" +targets = ["stmt:FunctionDef"] +script = "rules/example.py" +"#, + )?; + test.write_file( + "lint/external/rules/example.py", + r#" +def check_stmt(node, ctx): + pass + +def check_expr(node, ctx): + pass +"#, + )?; + let linter_path = test.root().join("lint/external/demo.toml"); + let config = format!( + r#" +[lint.external-ast.demo] +path = "{}" +"#, + toml_path(&linter_path) + ); + test.write_file("ruff.toml", &config)?; + + let output = test + .command() + .args([ + "check", + "--config", + "ruff.toml", + "--list-external-linters", + "--select-external", + "EXT002", + ]) + .output()?; + + assert!( + output.status.success(), + "command failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + let stdout = std::str::from_utf8(&output.stdout)?; + assert!(stdout.contains("EXT002")); + assert!(!stdout.contains("EXT001")); + + Ok(()) +} + +#[test] +fn external_ast_requires_explicit_selection() -> Result<()> { + let test = CliTest::new()?; + test.write_file( + "lint/external/demo.toml", + r#" +name = "Demo External Linter" +description = "Shows how to configure external AST linters" + +[[rule]] +code = "EXT001" +name = "ExampleRule" +summary = "Provides illustrative coverage" +targets = ["stmt:FunctionDef"] +script = "rules/example.py" +"#, + )?; + test.write_file( + "lint/external/rules/example.py", + r#" +def check_stmt(node, ctx): + # placeholder script body + pass +"#, + )?; + let linter_path = test.root().join("lint/external/demo.toml"); + let config = format!( + r#" +[lint.external-ast.demo] +path = "{}" +"#, + toml_path(&linter_path) + ); + test.write_file("ruff.toml", &config)?; + + let output = test + .command() + .args(["check", "--config", "ruff.toml", "--show-settings"]) + .output()?; + assert!( + output.status.success(), + "command failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + let stdout = std::str::from_utf8(&output.stdout)?; + assert!( + !stdout.contains("external-linter (RUF300)"), + "Expected external-linter to be disabled without an explicit selection" + ); + + let config_with_select = format!( + r#" +[lint] +extend-select = ["RUF300"] +select-external = ["EXT001"] + +[lint.external-ast.demo] +path = "{}" +"#, + toml_path(&linter_path) + ); + test.write_file("ruff.toml", &config_with_select)?; + let output = test + .command() + .args(["check", "--config", "ruff.toml", "--show-settings"]) + .output()?; + assert!( + output.status.success(), + "command failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + let stdout = std::str::from_utf8(&output.stdout)?; + assert!( + stdout.contains("external-linter (RUF300)"), + "Expected external-linter to be enabled when configured via lint.select-external" + ); + + Ok(()) +} + +#[test] +fn external_ast_select_external_disabled_rule_errors() -> Result<()> { + let test = CliTest::new()?; + let linter_path = { + test.write_file( + "lint/external/disabled.toml", + r#" +enabled = false +name = "Disabled External Linter" + +[[rule]] +code = "EXTDIS001" +name = "DisabledRule" +summary = "Rule intentionally disabled" +targets = ["stmt:FunctionDef"] +script = "rules/disabled.py" +"#, + )?; + test.write_file( + "lint/external/rules/disabled.py", + r#" +def check_stmt(node, ctx): + ctx.report("should not fire") +"#, + )?; + test.root().join("lint/external/disabled.toml") + }; + let config = format!( + r#" +[lint.external-ast.disabled] +path = "{}" + +[lint] +select = ["RUF300"] +select-external = ["EXTDIS001"] +"#, + toml_path(&linter_path) + ); + test.write_file("ruff.toml", &config)?; + test.write_file("example.py", "def foo():\n pass\n")?; + + let output = test + .command() + .args(["check", "--config", "ruff.toml", "example.py"]) + .output()?; + + assert!( + !output.status.success(), + "command unexpectedly succeeded: {}", + String::from_utf8_lossy(&output.stderr) + ); + let stderr = std::str::from_utf8(&output.stderr)?; + assert!( + stderr.contains("Unknown external linter or rule selector(s): EXTDIS001"), + "stderr missing warning about disabled external selection: {stderr}" + ); + + Ok(()) +} + +#[test] +fn external_ast_ignore_external_cli() -> Result<()> { + let test = CliTest::new()?; + let linter_path = write_demo_external_linter(&test)?; + let config = format!( + r#" +[lint] +select-external = ["EXT001"] + +[lint.external-ast.demo] +path = "{}" +"#, + toml_path(&linter_path) + ); + test.write_file("ruff.toml", &config)?; + test.write_file( + "example.py", + r#" +def demo(): + return 1 +"#, + )?; + + let output = test + .command() + .args([ + "check", + "--config", + "ruff.toml", + "--select", + "RUF300", + "--extend-select-external", + "EXT002", + "--ignore-external", + "EXT002", + "--list-external-linters", + "example.py", + ]) + .output()?; + assert!( + output.status.success(), + "command failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + let stdout = String::from_utf8_lossy(&output.stdout); + assert!(stdout.contains("EXT001"), "stdout missing EXT001: {stdout}"); + assert!( + !stdout.contains("EXT002"), + "stdout unexpectedly included EXT002: {stdout}" + ); + Ok(()) +} + +#[test] +fn external_ast_select_external_overrides_config() -> Result<()> { + let test = CliTest::new()?; + let linter_path = write_demo_external_linter(&test)?; + let config = format!( + r#" +[lint] +select-external = ["EXT001"] + +[lint.external-ast.demo] +path = "{}" +"#, + toml_path(&linter_path) + ); + test.write_file("ruff.toml", &config)?; + test.write_file("example.py", "def demo():\n return 1\n")?; + + let output = test + .command() + .args([ + "check", + "--config", + "ruff.toml", + "--select", + "RUF300", + "--select-external", + "EXT002", + "--list-external-linters", + "example.py", + ]) + .output()?; + assert!( + output.status.success(), + "command failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + let stdout = String::from_utf8_lossy(&output.stdout); + assert!(stdout.contains("EXT002"), "stdout missing EXT002: {stdout}"); + assert!( + !stdout.contains("EXT001"), + "stdout unexpectedly included EXT001: {stdout}" + ); + Ok(()) +} + +#[test] +fn cli_select_disables_configured_external_linters() -> Result<()> { + let test = CliTest::new()?; + let linter_path = write_demo_external_linter(&test)?; + let config = format!( + r#" +[lint] +select = ["RUF300"] +select-external = ["EXT001"] + +[lint.external-ast.demo] +path = "{}" +"#, + toml_path(&linter_path) + ); + test.write_file("ruff.toml", &config)?; + test.write_file("example.py", "def demo():\n return 1\n")?; + + let output = test + .command() + .args(["check", "--config", "ruff.toml", "--show-settings"]) + .output()?; + assert!( + output.status.success(), + "command failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + let stdout = String::from_utf8_lossy(&output.stdout); + assert!( + stdout.contains("external-linter (RUF300)"), + "Expected external linters to be enabled via configuration" + ); + + let output = test + .command() + .args([ + "check", + "--config", + "ruff.toml", + "--select", + "G004", + "--show-settings", + ]) + .output()?; + assert!( + output.status.success(), + "command failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + let stdout = String::from_utf8_lossy(&output.stdout); + assert!( + !stdout.contains("external-linter (RUF300)"), + "Expected `--select` to clear configured external linters" + ); + + let output = test + .command() + .args([ + "check", + "--config", + "ruff.toml", + "--select", + "G004", + "example.py", + ]) + .output()?; + assert!( + output.status.success(), + "command failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + let stdout = String::from_utf8_lossy(&output.stdout); + assert!( + !stdout.contains("EXT001"), + "Expected external linter diagnostics to be suppressed: {stdout}" + ); + + Ok(()) +} + +#[test] +fn external_ast_select_external_overrides_nested_config() -> Result<()> { + let test = CliTest::new()?; + let linter_path = write_demo_external_linter(&test)?; + let config = format!( + r#" +[lint] +select-external = ["EXT001", "EXT002"] + +[lint.external-ast.demo] +path = "{}" +"#, + toml_path(&linter_path) + ); + test.write_file("ruff.toml", &config)?; + test.write_file("nested/ruff.toml", r#"extend = "../ruff.toml""#)?; + test.write_file( + "nested/example.py", + r#" +def demo(): + return 1 +"#, + )?; + + let output = test + .command() + .args([ + "check", + "--select", + "RUF300", + "--select-external", + "EXT002", + "--list-external-linters", + "nested/example.py", + ]) + .output()?; + assert!( + output.status.success(), + "command failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + let stdout = String::from_utf8_lossy(&output.stdout); + assert!(stdout.contains("EXT002"), "stdout missing EXT002: {stdout}"); + assert!( + !stdout.contains("EXT001"), + "stdout unexpectedly included EXT001: {stdout}" + ); + + Ok(()) +} + +#[test] +fn select_external_cli_allows_nested_registry() -> Result<()> { + let test = CliTest::new()?; + let linter_path = write_demo_external_linter(&test)?; + + let nested_config = format!( + r#" +[lint.external-ast.demo] +path = "{}" +"#, + toml_path(&linter_path) + ); + test.write_file("nested/ruff.toml", &nested_config)?; + test.write_file("example.py", "def root():\n return 1\n")?; + test.write_file("nested/example.py", "def nested():\n return 1\n")?; + + let output = test + .command() + .args([ + "check", + "--select", + "RUF300", + "--select-external", + "EXT001", + "example.py", + "nested/example.py", + ]) + .output()?; + if cfg!(feature = "ext-lint") { + assert!( + !output.status.success(), + "expected EXT001 diagnostics but command succeeded: {}", + String::from_utf8_lossy(&output.stderr) + ); + let stdout = String::from_utf8_lossy(&output.stdout); + assert!( + stdout.contains("EXT001"), + "stdout missing EXT001 diagnostics: {stdout}" + ); + } else { + assert!( + output.status.success(), + "command failed unexpectedly without ext-lint: {}", + String::from_utf8_lossy(&output.stderr) + ); + } + + Ok(()) +} + +#[test] +fn select_external_cli_errors_when_no_registries() -> Result<()> { + let test = CliTest::new()?; + test.write_file("example.py", "def root():\n return 1\n")?; + + let output = test + .command() + .args([ + "check", + "--select", + "RUF300", + "--select-external", + "EXT001", + "example.py", + ]) + .output()?; + assert!( + !output.status.success(), + "command unexpectedly succeeded: {}", + String::from_utf8_lossy(&output.stderr) + ); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + stderr.contains("No external AST linters are configured in this workspace."), + "stderr missing missing-registry error: {stderr}" + ); + + Ok(()) +} + +#[test] +fn external_ast_ignore_external_config() -> Result<()> { + let test = CliTest::new()?; + let linter_path = write_demo_external_linter(&test)?; + let config = format!( + r#" +[lint] +select-external = ["EXT001"] +extend-select-external = ["EXT002"] +ignore-external = ["EXT002"] + +[lint.external-ast.demo] +path = "{}" +"#, + toml_path(&linter_path) + ); + test.write_file("ruff.toml", &config)?; + test.write_file( + "example.py", + r#" +def demo(): + return 1 +"#, + )?; + + let output = test + .command() + .args([ + "check", + "--config", + "ruff.toml", + "--select", + "RUF300", + "--list-external-linters", + "example.py", + ]) + .output()?; + assert!( + output.status.success(), + "command failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + let stdout = String::from_utf8_lossy(&output.stdout); + assert!(stdout.contains("EXT001"), "stdout missing EXT001: {stdout}"); + assert!( + !stdout.contains("EXT002"), + "stdout unexpectedly included EXT002: {stdout}" + ); + Ok(()) +} + +#[test] +fn external_ast_ignore_external_nested_config() -> Result<()> { + let test = CliTest::new()?; + let linter_path = write_demo_external_linter(&test)?; + let config = format!( + r#" +[lint] +select-external = ["EXT001", "EXT002"] +ignore-external = ["EXT002"] + +[lint.external-ast.demo] +path = "{}" +"#, + toml_path(&linter_path) + ); + test.write_file("ruff.toml", &config)?; + test.write_file( + "pkg/pyproject.toml", + r#" +[tool.ruff] +line-length = 88 +"#, + )?; + test.write_file( + "pkg/example.py", + r#" +def demo(): + return 1 +"#, + )?; + + let output = test + .command() + .args([ + "check", + "--config", + "ruff.toml", + "--select", + "RUF300", + "--list-external-linters", + "pkg/example.py", + ]) + .output()?; + assert!( + output.status.success(), + "command failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + let stdout = String::from_utf8_lossy(&output.stdout); + assert!( + stdout.contains("EXT001"), + "stdout missing surviving EXT001 listing: {stdout}" + ); + assert!( + !stdout.contains("EXT002"), + "stdout unexpectedly included ignored EXT002 listing: {stdout}" + ); + + Ok(()) +} + +#[test] +fn select_rejects_external_rules() -> Result<()> { + let test = CliTest::new()?; + + let output = test + .command() + .args(["check", "--isolated", "--select", "EXT001"]) + .output()?; + assert!(!output.status.success(), "command unexpectedly succeeded"); + let stderr = std::str::from_utf8(&output.stderr)?; + assert!( + stderr.contains("cannot be enabled with `--select`"), + "expected failure when selecting external rule via --select" + ); + + Ok(()) +} + +#[test] +fn select_external_rejects_internal_rules() -> Result<()> { + let test = CliTest::new()?; + + let output = test + .command() + .args(["check", "--isolated", "--select-external", "F401"]) + .output()?; + assert!(!output.status.success(), "command unexpectedly succeeded"); + let stderr = std::str::from_utf8(&output.stderr)?; + assert!( + stderr.contains("cannot be enabled with `--select-external`"), + "expected failure when selecting internal rules via --select-external" + ); + + Ok(()) +} + +#[test] +fn config_select_rejects_external_rules() -> Result<()> { + let test = CliTest::new()?; + test.write_file( + "ruff.toml", + r#" +[lint] +select = ["EXT001"] +"#, + )?; + + let output = test + .command() + .args(["check", "--config", "ruff.toml"]) + .output()?; + assert!(!output.status.success(), "command unexpectedly succeeded"); + let stderr = std::str::from_utf8(&output.stderr)?; + assert!( + stderr.contains("lint.select-external"), + "expected parse failure when selecting external rule via configuration" + ); + + Ok(()) +} + +#[test] +fn config_select_external_rejects_internal_rules() -> Result<()> { + let test = CliTest::new()?; + test.write_file( + "ruff.toml", + r#" +[lint] +select-external = ["F401"] +"#, + )?; + + let output = test + .command() + .args(["check", "--config", "ruff.toml"]) + .output()?; + assert!(!output.status.success(), "command unexpectedly succeeded"); + let stderr = std::str::from_utf8(&output.stderr)?; + assert!( + stderr.contains("cannot be enabled via `lint.select-external`"), + "expected lint.select-external failure when selecting internal rule" + ); + + Ok(()) +} + #[test] fn exclude() -> Result<()> { let case = CliTest::new()?; @@ -775,7 +1594,7 @@ fn valid_toml_but_nonexistent_option_provided_via_config_argument() { Could not parse the supplied argument as a `ruff.toml` configuration option: - Unknown rule selector: `F481` + Unknown rule selector: `F481`. External rule selectors must be provided via `lint.select-external`. For more information, try '--help'. "); @@ -907,6 +1726,10 @@ fn value_given_to_table_key_is_not_inline_table_2() { - `lint.dummy-variable-rgx` - `lint.extend-ignore` - `lint.extend-select` + - `lint.select-external` + - `lint.extend-select-external` + - `lint.ignore-external` + - `lint.extend-ignore-external` - `lint.extend-fixable` - `lint.external` - `lint.fixable` diff --git a/crates/ruff/tests/cli/main.rs b/crates/ruff/tests/cli/main.rs index 879035f7bb264..092f445973cdb 100644 --- a/crates/ruff/tests/cli/main.rs +++ b/crates/ruff/tests/cli/main.rs @@ -16,6 +16,8 @@ use std::{ use tempfile::TempDir; mod analyze_graph; +#[cfg(feature = "ext-lint")] +mod ext_lint; mod format; mod lint; diff --git a/crates/ruff/tests/fixtures/external/logging_cases.py b/crates/ruff/tests/fixtures/external/logging_cases.py new file mode 100644 index 0000000000000..0bbd7aefbca2b --- /dev/null +++ b/crates/ruff/tests/fixtures/external/logging_cases.py @@ -0,0 +1,11 @@ +from __future__ import annotations + +import logging + + +def interpolate(logger, extra): + logger.info("static value: %s", extra) # OK + logger.info(f"f-string value: {extra}") # violation + logger.warning("percent style %s" % extra) # violation # noqa: UP031 + logging.error("format {}".format(extra)) # violation # noqa: UP032 + logging.debug("plain literal") # OK diff --git a/crates/ruff/tests/fixtures/external/logging_linter.py b/crates/ruff/tests/fixtures/external/logging_linter.py new file mode 100644 index 0000000000000..ca79ef2a28460 --- /dev/null +++ b/crates/ruff/tests/fixtures/external/logging_linter.py @@ -0,0 +1,66 @@ +from __future__ import annotations + +import ruff_external as ast + +MESSAGE = ( + "Logging message should be a string literal with parameterized arguments; " + "avoid runtime string interpolation. (RLI001)" +) + + +def _iter_positional_args(arguments): + for argument in arguments.args: + if _is_unpack_expr(argument): + continue + yield argument + + +def _iter_keyword_args(arguments): + for keyword in arguments.keywords: + name = keyword.arg + if name is None: + continue + yield name, keyword.value + + +def _find_message_argument(arguments): + for argument in _iter_positional_args(arguments): + return argument + + for name, value in _iter_keyword_args(arguments): + if isinstance(name, str) and name.lower() == "msg": + return value + + return None + + +def _is_unpack_expr(node): + return isinstance(node, ast.StarredExpr) + + +def _is_dynamic_message(argument): + if isinstance(argument, ast.StringLiteralExpr): + return False + + if isinstance(argument, (ast.FStringExpr, ast.BinOpExpr)): + return True + + if isinstance(argument, ast.CallExpr): + callee = argument.function_text + if isinstance(callee, str) and callee.lower().endswith(".format"): + return True + + return False + + +def check_expr(node, ctx): + arguments = node.arguments + if arguments is None: + return + + message_argument = _find_message_argument(arguments) + if message_argument is None: + return + + if _is_dynamic_message(message_argument): + ctx.report(MESSAGE, message_argument._span) diff --git a/crates/ruff_linter/Cargo.toml b/crates/ruff_linter/Cargo.toml index 94f1ea961bd59..dc2c0a6a8647c 100644 --- a/crates/ruff_linter/Cargo.toml +++ b/crates/ruff_linter/Cargo.toml @@ -56,11 +56,13 @@ pep440_rs = { workspace = true } pyproject-toml = { workspace = true } regex = { workspace = true } rustc-hash = { workspace = true } +pyo3 = { version = "0.23.0", optional = true, features = ["macros"] } schemars = { workspace = true, optional = true } serde = { workspace = true } serde_json = { workspace = true } similar = { workspace = true } smallvec = { workspace = true } +scopeguard = "1.2.0" strum = { workspace = true } strum_macros = { workspace = true } thiserror = { workspace = true } @@ -78,11 +80,15 @@ test-case = { workspace = true } colored = { workspace = true, features = ["no-color"] } tempfile = { workspace = true } +[build-dependencies] +pyo3-build-config = { version = "0.23.5", features = ["resolve-config"] } + [features] default = [] schemars = ["dep:schemars", "ruff_python_ast/schemars"] # Enables rules for internal integration tests test-rules = [] +ext-lint = ["dep:pyo3"] [lints] workspace = true diff --git a/crates/ruff_linter/build.rs b/crates/ruff_linter/build.rs new file mode 100644 index 0000000000000..c3fbb3d831525 --- /dev/null +++ b/crates/ruff_linter/build.rs @@ -0,0 +1,19 @@ +use pyo3_build_config::{self, BuildFlag}; +use std::env; + +fn main() { + println!("cargo:rerun-if-env-changed=PYO3_PYTHON"); + + if env::var("CARGO_FEATURE_EXT_LINT").is_err() { + return; + } + + println!("cargo:rustc-check-cfg=cfg(Py_GIL_DISABLED)"); + if pyo3_build_config::get() + .build_flags + .0 + .contains(&BuildFlag::Py_GIL_DISABLED) + { + println!("cargo:rustc-cfg=Py_GIL_DISABLED"); + } +} diff --git a/crates/ruff_linter/resources/ruff_external/__init__.pyi b/crates/ruff_linter/resources/ruff_external/__init__.pyi new file mode 100644 index 0000000000000..bde5177104ff0 --- /dev/null +++ b/crates/ruff_linter/resources/ruff_external/__init__.pyi @@ -0,0 +1,41 @@ +from __future__ import annotations + +from typing import Any, Callable, Optional, Tuple + +Span = Tuple[int, int] +Reporter = Callable[[str, Optional[Span]], None] + +__all__ = ["Context", "Node", "RawNode"] + + +class Node: + _kind: str + _span: Span + _text: str + _repr: str + node_id: int + + def __init__(self, kind: str, span: Span, text: str, repr_value: str, node_id: int) -> None: ... + + def __getitem__(self, key: str) -> Any: ... + + def get(self, key: str, default: Any = ...) -> Any: ... + + +class RawNode(Node): + pass + + +class Context: + code: str + name: str + _report: Reporter + + def __init__(self, code: str, name: str, reporter: Reporter) -> None: ... + + def report(self, message: str, span: Optional[Span] = ...) -> None: ... + +from .nodes import * +from .nodes import __all__ as _node_all + +__all__ = __all__ + _node_all diff --git a/crates/ruff_linter/resources/ruff_external/nodes.pyi b/crates/ruff_linter/resources/ruff_external/nodes.pyi new file mode 100644 index 0000000000000..75a86274d9313 --- /dev/null +++ b/crates/ruff_linter/resources/ruff_external/nodes.pyi @@ -0,0 +1,377 @@ +# This file is auto-generated by crates/ruff_python_ast/generate.py +from __future__ import annotations + +from typing import Optional, Sequence + +from . import Node + +__all__ = ['Module', 'Expression', 'FunctionDefStmt', 'ClassDefStmt', 'ReturnStmt', 'DeleteStmt', 'TypeAliasStmt', 'AssignStmt', 'AugAssignStmt', 'AnnAssignStmt', 'ForStmt', 'WhileStmt', 'IfStmt', 'WithStmt', 'MatchStmt', 'RaiseStmt', 'TryStmt', 'AssertStmt', 'ImportStmt', 'ImportFromStmt', 'GlobalStmt', 'NonlocalStmt', 'ExprStmt', 'PassStmt', 'BreakStmt', 'ContinueStmt', 'IpyEscapeCommandStmt', 'BoolOpExpr', 'NamedExpr', 'BinOpExpr', 'UnaryOpExpr', 'LambdaExpr', 'IfExpr', 'DictExpr', 'SetExpr', 'ListCompExpr', 'SetCompExpr', 'DictCompExpr', 'GeneratorExpr', 'AwaitExpr', 'YieldExpr', 'YieldFromExpr', 'CompareExpr', 'CallExpr', 'FStringExpr', 'TStringExpr', 'StringLiteralExpr', 'BytesLiteralExpr', 'NumberLiteralExpr', 'BooleanLiteralExpr', 'NoneLiteralExpr', 'EllipsisLiteralExpr', 'AttributeExpr', 'SubscriptExpr', 'StarredExpr', 'NameExpr', 'ListExpr', 'TupleExpr', 'SliceExpr', 'IpyEscapeCommandExpr', 'ExceptHandler', 'InterpolatedElement', 'InterpolatedStringLiteralElement', 'MatchValuePattern', 'MatchSingletonPattern', 'MatchSequencePattern', 'MatchMappingPattern', 'MatchClassPattern', 'MatchStarPattern', 'MatchAsPattern', 'MatchOrPattern', 'TypeVar', 'TypeVarTuple', 'ParamSpec', 'InterpolatedStringFormatSpec', 'PatternArguments', 'PatternKeyword', 'Comprehension', 'Parameters', 'Parameter', 'ParameterWithDefault', 'Alias', 'WithItem', 'MatchCase', 'Decorator', 'ElifElseClause', 'TypeParams', 'FString', 'TString', 'StringLiteral', 'BytesLiteral', 'Identifier', 'Arguments', 'Keyword'] + +class Module(Node): + body: Sequence[Node] + +class Expression(Node): + body: Node + +class FunctionDefStmt(Node): + is_async: bool + decorator_list: Sequence[Decorator] + name: str + type_params: Optional[TypeParams] + parameters: Parameters + returns: Optional[Node] + body: Sequence[Node] + +class ClassDefStmt(Node): + decorator_list: Sequence[Decorator] + name: str + type_params: Optional[TypeParams] + arguments: Optional[Arguments] + body: Sequence[Node] + +class ReturnStmt(Node): + value: Optional[Node] + +class DeleteStmt(Node): + targets: Sequence[Node] + +class TypeAliasStmt(Node): + name: Node + type_params: Optional[TypeParams] + value: Node + +class AssignStmt(Node): + targets: Sequence[Node] + value: Node + +class AugAssignStmt(Node): + target: Node + op: str + value: Node + +class AnnAssignStmt(Node): + target: Node + annotation: Node + value: Optional[Node] + simple: bool + +class ForStmt(Node): + is_async: bool + target: Node + iter: Node + body: Sequence[Node] + orelse: Sequence[Node] + +class WhileStmt(Node): + test: Node + body: Sequence[Node] + orelse: Sequence[Node] + +class IfStmt(Node): + test: Node + body: Sequence[Node] + elif_else_clauses: Sequence[ElifElseClause] + +class WithStmt(Node): + is_async: bool + items: Sequence[WithItem] + body: Sequence[Node] + +class MatchStmt(Node): + subject: Node + cases: Sequence[MatchCase] + +class RaiseStmt(Node): + exc: Optional[Node] + cause: Optional[Node] + +class TryStmt(Node): + body: Sequence[Node] + handlers: Sequence[Node] + orelse: Sequence[Node] + finalbody: Sequence[Node] + is_star: bool + +class AssertStmt(Node): + test: Node + msg: Optional[Node] + +class ImportStmt(Node): + names: Sequence[Alias] + +class ImportFromStmt(Node): + module: Optional[str] + names: Sequence[Alias] + level: int + +class GlobalStmt(Node): + names: Sequence[str] + +class NonlocalStmt(Node): + names: Sequence[str] + +class ExprStmt(Node): + value: Node + +class PassStmt(Node): + pass + +class BreakStmt(Node): + pass + +class ContinueStmt(Node): + pass + +class IpyEscapeCommandStmt(Node): + kind: str + value: str + +class BoolOpExpr(Node): + op: str + values: Sequence[Node] + +class NamedExpr(Node): + target: Node + value: Node + +class BinOpExpr(Node): + left: Node + op: str + right: Node + +class UnaryOpExpr(Node): + op: str + operand: Node + +class LambdaExpr(Node): + parameters: Optional[Parameters] + body: Node + +class IfExpr(Node): + test: Node + body: Node + orelse: Node + +class DictExpr(Node): + items: Sequence[Node] + +class SetExpr(Node): + elts: Sequence[Node] + +class ListCompExpr(Node): + elt: Node + generators: Sequence[Comprehension] + +class SetCompExpr(Node): + elt: Node + generators: Sequence[Comprehension] + +class DictCompExpr(Node): + key: Node + value: Node + generators: Sequence[Comprehension] + +class GeneratorExpr(Node): + elt: Node + generators: Sequence[Comprehension] + parenthesized: bool + +class AwaitExpr(Node): + value: Node + +class YieldExpr(Node): + value: Optional[Node] + +class YieldFromExpr(Node): + value: Node + +class CompareExpr(Node): + left: Node + ops: Sequence[str] + comparators: Sequence[Node] + +class CallExpr(Node): + func: Node + arguments: Arguments + callee: Optional[str] + function_text: Optional[str] + function_kind: Optional[str] + +class FStringExpr(Node): + value: str + +class TStringExpr(Node): + value: str + +class StringLiteralExpr(Node): + value: str + +class BytesLiteralExpr(Node): + value: str + +class NumberLiteralExpr(Node): + value: float + +class BooleanLiteralExpr(Node): + value: bool + +class NoneLiteralExpr(Node): + pass + +class EllipsisLiteralExpr(Node): + pass + +class AttributeExpr(Node): + value: Node + attr: str + ctx: str + +class SubscriptExpr(Node): + value: Node + slice: Node + ctx: str + +class StarredExpr(Node): + value: Node + ctx: str + +class NameExpr(Node): + id: str + ctx: str + +class ListExpr(Node): + elts: Sequence[Node] + ctx: str + +class TupleExpr(Node): + elts: Sequence[Node] + ctx: str + parenthesized: bool + +class SliceExpr(Node): + lower: Optional[Node] + upper: Optional[Node] + step: Optional[Node] + +class IpyEscapeCommandExpr(Node): + kind: str + value: str + +class ExceptHandler(Node): + pass + +class InterpolatedElement(Node): + pass + +class InterpolatedStringLiteralElement(Node): + pass + +class MatchValuePattern(Node): + value: Node + +class MatchSingletonPattern(Node): + value: str + +class MatchSequencePattern(Node): + patterns: Sequence[Node] + +class MatchMappingPattern(Node): + keys: Sequence[Node] + patterns: Sequence[Node] + rest: Optional[str] + +class MatchClassPattern(Node): + cls: Node + arguments: PatternArguments + +class MatchStarPattern(Node): + name: Optional[str] + +class MatchAsPattern(Node): + pattern: Optional[Node] + name: Optional[str] + +class MatchOrPattern(Node): + patterns: Sequence[Node] + +class TypeVar(Node): + name: str + bound: Optional[Node] + default: Optional[Node] + +class TypeVarTuple(Node): + name: str + default: Optional[Node] + +class ParamSpec(Node): + name: str + default: Optional[Node] + +class InterpolatedStringFormatSpec(Node): + pass + +class PatternArguments(Node): + pass + +class PatternKeyword(Node): + pass + +class Comprehension(Node): + pass + +class Parameters(Node): + posonlyargs: Sequence[ParameterWithDefault] + args: Sequence[ParameterWithDefault] + vararg: Optional[Parameter] + kwonlyargs: Sequence[ParameterWithDefault] + kwarg: Optional[Parameter] + +class Parameter(Node): + name: str + annotation: Optional[Node] + +class ParameterWithDefault(Node): + parameter: Parameter + default: Optional[Node] + +class Alias(Node): + pass + +class WithItem(Node): + pass + +class MatchCase(Node): + pass + +class Decorator(Node): + pass + +class ElifElseClause(Node): + pass + +class TypeParams(Node): + pass + +class FString(Node): + pass + +class TString(Node): + pass + +class StringLiteral(Node): + pass + +class BytesLiteral(Node): + pass + +class Identifier(Node): + pass + +class Arguments(Node): + args: Sequence[Node] + keywords: Sequence[Keyword] + +class Keyword(Node): + arg: Optional[str] + value: Node + diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index 5f7f459ba98c6..598a4171e7198 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -24,6 +24,7 @@ use std::cell::RefCell; use std::path::Path; +use crate::external::ExternalLintRuntimeHandle; use itertools::Itertools; use log::debug; use rustc_hash::{FxHashMap, FxHashSet}; @@ -242,6 +243,7 @@ pub(crate) struct Checker<'a> { /// Errors collected by the `semantic_checker`. semantic_errors: RefCell>, context: &'a LintContext<'a>, + external_runtime: Option, } impl<'a> Checker<'a> { @@ -263,6 +265,7 @@ impl<'a> Checker<'a> { notebook_index: Option<&'a NotebookIndex>, target_version: TargetVersion, context: &'a LintContext<'a>, + external_runtime: Option, ) -> Self { let semantic = SemanticModel::new(&settings.typing_modules, path, module); Self { @@ -291,10 +294,18 @@ impl<'a> Checker<'a> { semantic_checker: SemanticSyntaxChecker::new(), semantic_errors: RefCell::default(), context, + external_runtime, } } } +fn external_runtime_from_settings(settings: &LinterSettings) -> Option { + settings + .external_ast + .as_ref() + .map(|registry| ExternalLintRuntimeHandle::new(registry.clone())) +} + impl<'a> Checker<'a> { /// Return `true` if a [`Rule`] is disabled by a `noqa` directive. pub(crate) fn rule_is_ignored(&self, code: Rule, offset: TextSize) -> bool { @@ -435,6 +446,11 @@ impl<'a> Checker<'a> { } } + #[cfg_attr(not(feature = "ext-lint"), allow(dead_code))] + pub(crate) fn owned_source_file(&self) -> SourceFile { + self.context.owned_source_file() + } + /// The [`Locator`] for the current file, which enables extraction of source code from byte /// offsets. pub(crate) const fn locator(&self) -> &'a Locator<'a> { @@ -1535,6 +1551,9 @@ impl<'a> Visitor<'a> for Checker<'a> { // Step 4: Analysis analyze::statement(stmt, self); + if let Some(runtime) = &self.external_runtime { + runtime.run_on_stmt(self, stmt); + } self.semantic.flags = flags_snapshot; self.semantic.pop_node(); @@ -2147,6 +2166,9 @@ impl<'a> Visitor<'a> for Checker<'a> { self.semantic.flags = flags_snapshot; analyze::expression(expr, self); + if let Some(runtime) = &self.external_runtime { + runtime.run_on_expr(self, expr); + } self.semantic.pop_node(); } @@ -3238,6 +3260,11 @@ pub(crate) fn check_ast( }; let allocator = typed_arena::Arena::new(); + let external_runtime = if context.is_rule_enabled(Rule::ExternalLinter) { + external_runtime_from_settings(settings) + } else { + None + }; let mut checker = Checker::new( parsed, &allocator, @@ -3255,10 +3282,24 @@ pub(crate) fn check_ast( notebook_index, target_version, context, + external_runtime, ); checker.bind_builtins(); + let external_runtime = checker.external_runtime.clone(); + if let Some(runtime) = external_runtime { + runtime.run_in_session(|| run_checker(&mut checker, parsed)); + } else { + run_checker(&mut checker, parsed); + } + + let Checker { + semantic_errors, .. + } = checker; - // Iterate over the AST. + semantic_errors.into_inner() +} + +fn run_checker<'a>(checker: &mut Checker<'a>, parsed: &'a Parsed) { checker.visit_module(parsed.suite()); checker.visit_body(parsed.suite()); @@ -3269,22 +3310,16 @@ pub(crate) fn check_ast( checker.visit_exports(); // Check docstrings, bindings, and unresolved references. - analyze::deferred_lambdas(&mut checker); - analyze::deferred_for_loops(&mut checker); - analyze::definitions(&mut checker); - analyze::bindings(&checker); - analyze::unresolved_references(&checker); + analyze::deferred_lambdas(checker); + analyze::deferred_for_loops(checker); + analyze::definitions(checker); + analyze::bindings(checker); + analyze::unresolved_references(checker); // Reset the scope to module-level, and check all consumed scopes. checker.semantic.scope_id = ScopeId::global(); checker.analyze.scopes.push(ScopeId::global()); - analyze::deferred_scopes(&checker); - - let Checker { - semantic_errors, .. - } = checker; - - semantic_errors.into_inner() + analyze::deferred_scopes(checker); } /// A type for collecting diagnostics in a given file. @@ -3377,6 +3412,10 @@ impl<'a> LintContext<'a> { (self.diagnostics.into_inner(), self.source_file) } + pub(crate) fn owned_source_file(&self) -> SourceFile { + self.source_file.clone() + } + #[inline] pub(crate) fn as_mut_vec(&mut self) -> &mut Vec { self.diagnostics.get_mut() diff --git a/crates/ruff_linter/src/checkers/noqa.rs b/crates/ruff_linter/src/checkers/noqa.rs index 7cf58a5def343..ee87989bf2732 100644 --- a/crates/ruff_linter/src/checkers/noqa.rs +++ b/crates/ruff_linter/src/checkers/noqa.rs @@ -5,6 +5,8 @@ use std::path::Path; use itertools::Itertools; use rustc_hash::FxHashSet; +use ruff_db::diagnostic::SecondaryCode; + use ruff_python_trivia::CommentRanges; use ruff_text_size::{Ranged, TextRange}; @@ -77,7 +79,7 @@ pub(crate) fn check_noqa( { let suppressed = match &directive_line.directive { Directive::All(_) => { - let Ok(rule) = Rule::from_code(code) else { + let Some(rule) = resolve_rule_for_noqa(code, settings) else { debug_assert!(false, "Invalid secondary code `{code}`"); continue; }; @@ -87,7 +89,7 @@ pub(crate) fn check_noqa( } Directive::Codes(directive) => { if directive.includes(code) { - let Ok(rule) = Rule::from_code(code) else { + let Some(rule) = resolve_rule_for_noqa(code, settings) else { debug_assert!(false, "Invalid secondary code `{code}`"); continue; }; @@ -258,3 +260,19 @@ pub(crate) fn check_noqa( ignored_diagnostics.sort_unstable(); ignored_diagnostics } + +fn resolve_rule_for_noqa(code: &SecondaryCode, settings: &LinterSettings) -> Option { + if let Ok(rule) = Rule::from_code(code.as_str()) { + return Some(rule); + } + + if settings + .external + .iter() + .any(|external| code.as_str().starts_with(external)) + { + return Some(Rule::ExternalLinter); + } + + None +} diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index ebec5f4acc09d..db46fde2b619a 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -1065,6 +1065,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Ruff, "102") => rules::ruff::rules::InvalidRuleCode, (Ruff, "200") => rules::ruff::rules::InvalidPyprojectToml, + (Ruff, "300") => rules::ruff::rules::ExternalLinter, #[cfg(any(feature = "test-rules", test))] (Ruff, "900") => rules::ruff::rules::StableTestRule, #[cfg(any(feature = "test-rules", test))] @@ -1092,7 +1093,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { #[cfg(any(feature = "test-rules", test))] (Ruff, "990") => rules::ruff::rules::PanicyTestRule, - // flake8-django (Flake8Django, "001") => rules::flake8_django::rules::DjangoNullableModelStringField, (Flake8Django, "003") => rules::flake8_django::rules::DjangoLocalsInRenderFunction, diff --git a/crates/ruff_linter/src/external/ast/definition.rs b/crates/ruff_linter/src/external/ast/definition.rs new file mode 100644 index 0000000000000..26b4e6565cf42 --- /dev/null +++ b/crates/ruff_linter/src/external/ast/definition.rs @@ -0,0 +1,26 @@ +use serde::Deserialize; + +use crate::external::ast::rule::ExternalAstRuleSpec; + +fn default_true() -> bool { + true +} + +#[derive(Debug, Clone, Deserialize)] +pub struct ExternalAstLinterFile { + #[serde(default = "default_true")] + pub enabled: bool, + #[serde(default)] + pub name: Option, + #[serde(default)] + pub description: Option, + #[serde(default)] + #[serde(rename = "rule")] + pub rules: Vec, +} + +#[allow(dead_code)] +pub fn _assert_specs_send_sync() { + fn assert_send_sync() {} + assert_send_sync::(); +} diff --git a/crates/ruff_linter/src/external/ast/loader.rs b/crates/ruff_linter/src/external/ast/loader.rs new file mode 100644 index 0000000000000..8cbebc48803ca --- /dev/null +++ b/crates/ruff_linter/src/external/ast/loader.rs @@ -0,0 +1,420 @@ +use std::collections::HashSet; +use std::fs; +use std::path::{Path, PathBuf}; + +use crate::external::PyprojectExternalLinterEntry; +use crate::external::ast::definition::ExternalAstLinterFile; +use crate::external::ast::registry::ExternalLintRegistry; +use crate::external::ast::rule::{ + CallCalleeMatcher, ExternalAstLinter, ExternalAstRule, ExternalAstRuleSpec, ExternalRuleCode, + ExternalRuleCodeError, ExternalRuleScript, +}; +use crate::external::ast::target::{AstTarget, AstTargetSpec, ExprKind}; +use crate::external::error::ExternalLinterError; + +pub fn load_linter_into_registry( + registry: &mut ExternalLintRegistry, + id: &str, + entry: &PyprojectExternalLinterEntry, +) -> Result<(), ExternalLinterError> { + let linter = load_linter_from_entry(id, entry)?; + registry.insert_linter(linter) +} + +pub fn load_linter_from_entry( + id: &str, + entry: &PyprojectExternalLinterEntry, +) -> Result { + let definition = load_definition_file(&entry.toml_path)?; + build_linter(id, entry, &definition) +} + +fn load_definition_file(path: &Path) -> Result { + let contents = fs::read_to_string(path).map_err(|source| ExternalLinterError::Io { + path: path.to_path_buf(), + source, + })?; + toml::from_str(&contents).map_err(|source| ExternalLinterError::Parse { + path: path.to_path_buf(), + source, + }) +} + +fn build_linter( + id: &str, + entry: &PyprojectExternalLinterEntry, + linter_file: &ExternalAstLinterFile, +) -> Result { + if linter_file.rules.is_empty() { + return Err(ExternalLinterError::EmptyLinter { id: id.to_string() }); + } + + let resolved_dir = entry + .toml_path + .parent() + .map(Path::to_path_buf) + .unwrap_or_else(|| PathBuf::from(".")); + + let mut codes = HashSet::new(); + let mut rules = Vec::with_capacity(linter_file.rules.len()); + + for rule_spec in &linter_file.rules { + let rule = build_rule(id, &resolved_dir, rule_spec)?; + + if !codes.insert(rule.code.as_str().to_string()) { + return Err(ExternalLinterError::DuplicateRule { + linter: id.to_string(), + code: rule.code.as_str().to_string(), + }); + } + + rules.push(rule); + } + + let linter = ExternalAstLinter::new( + id, + linter_file.name.clone().unwrap_or_else(|| id.to_string()), + linter_file.description.clone(), + entry.enabled && linter_file.enabled, + rules, + ); + + Ok(linter) +} + +fn build_rule( + linter_id: &str, + base_dir: &Path, + spec: &ExternalAstRuleSpec, +) -> Result { + let code = ExternalRuleCode::new(&spec.code).map_err(|error| match error { + ExternalRuleCodeError::Empty | ExternalRuleCodeError::InvalidCharacters(_) => { + ExternalLinterError::InvalidRuleCode { + linter: linter_id.to_string(), + code: spec.code.clone(), + } + } + })?; + + if spec.targets.is_empty() { + return Err(ExternalLinterError::MissingTargets { + linter: linter_id.to_string(), + rule: spec.name.clone(), + }); + } + + let mut resolved_targets = Vec::with_capacity(spec.targets.len()); + for target in &spec.targets { + let parsed = parse_target(target).map_err(|source| ExternalLinterError::UnknownTarget { + linter: linter_id.to_string(), + rule: spec.name.clone(), + target: target.raw().to_string(), + source, + })?; + resolved_targets.push(parsed); + } + + let script = resolve_script(linter_id, &spec.name, base_dir, &spec.script)?; + let call_callee = if let Some(pattern) = spec.call_callee_regex.as_ref() { + if !resolved_targets + .iter() + .any(|target| matches!(target, AstTarget::Expr(ExprKind::Call))) + { + return Err(ExternalLinterError::CallCalleeRegexWithoutCallTarget { + linter: linter_id.to_string(), + rule: spec.name.clone(), + }); + } + + Some(CallCalleeMatcher::new(pattern.clone()).map_err(|source| { + ExternalLinterError::InvalidCallCalleeRegex { + linter: linter_id.to_string(), + rule: spec.name.clone(), + pattern: pattern.clone(), + source, + } + })?) + } else { + None + }; + + Ok(ExternalAstRule::new( + code, + spec.name.clone(), + spec.summary.clone(), + resolved_targets, + script, + call_callee, + )) +} + +fn resolve_script( + linter_id: &str, + rule_name: &str, + base_dir: &Path, + script_path: &Path, +) -> Result { + let resolved = if script_path.is_absolute() { + script_path.to_path_buf() + } else { + base_dir.join(script_path) + }; + let contents = + fs::read_to_string(&resolved).map_err(|source| ExternalLinterError::ScriptIo { + linter: linter_id.to_string(), + rule: rule_name.to_string(), + path: resolved.clone(), + source, + })?; + if contents.trim().is_empty() { + return Err(ExternalLinterError::MissingScriptBody { + linter: linter_id.to_string(), + rule: rule_name.to_string(), + }); + } + Ok(ExternalRuleScript::file(resolved, contents)) +} + +fn parse_target( + spec: &AstTargetSpec, +) -> Result { + spec.parse() +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::external::ast::target::{ExprKind, StmtKind}; + use anyhow::Result; + use std::path::Path; + use tempfile::tempdir; + + fn write(path: &Path, contents: &str) -> Result<()> { + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent)?; + } + std::fs::write(path, contents)?; + Ok(()) + } + + #[test] + fn load_linter_from_entry_resolves_relative_paths() -> Result<()> { + let temp = tempdir()?; + let linter_path = temp.path().join("linters/my_linter.toml"); + let script_path = temp.path().join("linters/rules/example.py"); + let call_script_path = temp.path().join("linters/rules/call.py"); + + write( + &script_path, + r#" +def check(): + # placeholder body + pass +"#, + )?; + + write( + &call_script_path, + r#" +def check(): + pass +"#, + )?; + + write( + &linter_path, + r#" +name = "Example External Linter" +description = "Demonstrates external AST configuration" + +[[rule]] +code = "EXT001" +name = "ExampleRule" +summary = "Flags demo targets" +targets = ["stmt:FunctionDef"] +script = "rules/example.py" + +[[rule]] +code = "EXT100" +name = "CallRule" +targets = ["expr:Call"] +call-callee-regex = "^logging\\." +script = "rules/call.py" +"#, + )?; + + let entry = PyprojectExternalLinterEntry { + toml_path: linter_path, + enabled: true, + }; + + let linter = load_linter_from_entry("example", &entry)?; + assert!(linter.enabled); + assert_eq!(linter.id.as_str(), "example"); + assert_eq!(linter.name.as_str(), "Example External Linter"); + assert_eq!( + linter.description.as_deref(), + Some("Demonstrates external AST configuration") + ); + assert_eq!(linter.rules.len(), 2); + + let example_rule = &linter.rules[0]; + assert_eq!(example_rule.code.as_str(), "EXT001"); + assert_eq!(example_rule.name.as_str(), "ExampleRule"); + assert_eq!(example_rule.summary.as_deref(), Some("Flags demo targets")); + assert_eq!(example_rule.targets.len(), 1); + assert_eq!( + example_rule.targets[0], + AstTarget::Stmt(StmtKind::FunctionDef) + ); + assert_eq!(example_rule.script.path(), script_path.as_path()); + assert!(example_rule.script.body().contains("placeholder body")); + + let call_rule = &linter.rules[1]; + assert_eq!(call_rule.code.as_str(), "EXT100"); + assert_eq!(call_rule.name.as_str(), "CallRule"); + assert_eq!(call_rule.targets[0], AstTarget::Expr(ExprKind::Call)); + let call_callee = call_rule + .call_callee() + .expect("expected call callee matcher to be present"); + assert_eq!(call_callee.pattern(), "^logging\\."); + assert!(call_callee.regex().is_match("logging.info")); + + Ok(()) + } + + #[test] + fn load_linter_rejects_call_regex_without_call_target() -> Result<()> { + let temp = tempdir()?; + let linter_path = temp.path().join("linters/invalid-call.toml"); + let script_path = temp.path().join("linters/rules/invalid.py"); + + write( + &script_path, + r#" +def check(): + pass +"#, + )?; + + write( + &linter_path, + r#" +[[rule]] +code = "EXT101" +name = "InvalidCallRule" +targets = ["expr:Name"] +call-callee-regex = "^logging\\." +script = "rules/invalid.py" +"#, + )?; + + let entry = PyprojectExternalLinterEntry { + toml_path: linter_path, + enabled: true, + }; + + let err = load_linter_from_entry("invalid-call", &entry).unwrap_err(); + let ExternalLinterError::CallCalleeRegexWithoutCallTarget { linter, rule } = err else { + panic!("expected call regex without target error"); + }; + assert_eq!(linter, "invalid-call"); + assert_eq!(rule, "InvalidCallRule"); + + Ok(()) + } + + #[test] + fn load_linter_rejects_invalid_call_regex() -> Result<()> { + let temp = tempdir()?; + let linter_path = temp.path().join("linters/bad-regex.toml"); + let script_path = temp.path().join("linters/rules/bad.py"); + + write( + &script_path, + r#" +def check(): + pass +"#, + )?; + + write( + &linter_path, + r#" +[[rule]] +code = "EXT102" +name = "BadRegexRule" +targets = ["expr:Call"] +call-callee-regex = "[" +script = "rules/bad.py" +"#, + )?; + + let entry = PyprojectExternalLinterEntry { + toml_path: linter_path, + enabled: true, + }; + + let err = load_linter_from_entry("bad-regex", &entry).unwrap_err(); + let ExternalLinterError::InvalidCallCalleeRegex { + linter, + rule, + pattern, + .. + } = err + else { + panic!("expected invalid call regex error"); + }; + assert_eq!(linter, "bad-regex"); + assert_eq!(rule, "BadRegexRule"); + assert_eq!(pattern, "["); + + Ok(()) + } + + #[test] + fn load_linter_into_registry_marks_disabled_linters() -> Result<()> { + let temp = tempdir()?; + let linter_path = temp.path().join("linters/disabled.toml"); + let script_path = temp.path().join("linters/rules/unused.py"); + + write( + &script_path, + r#" +def check(): + pass +"#, + )?; + + write( + &linter_path, + r#" +enabled = false + +[[rule]] +code = "EXT002" +name = "DisabledRule" +targets = ["stmt:Expr"] +script = "rules/unused.py" +"#, + )?; + + let entry = PyprojectExternalLinterEntry { + toml_path: linter_path, + enabled: true, + }; + + let mut registry = ExternalLintRegistry::new(); + load_linter_into_registry(&mut registry, "disabled", &entry)?; + + assert_eq!(registry.linters().len(), 1); + + let linter = ®istry.linters()[0]; + assert!(!linter.enabled); + + // Disabled linters should not be discoverable by rule code lookup. + assert!(registry.find_rule_by_code("EXT002").is_none()); + + Ok(()) + } +} diff --git a/crates/ruff_linter/src/external/ast/mod.rs b/crates/ruff_linter/src/external/ast/mod.rs new file mode 100644 index 0000000000000..ef6bf10b6163d --- /dev/null +++ b/crates/ruff_linter/src/external/ast/mod.rs @@ -0,0 +1,6 @@ +pub mod definition; +pub mod loader; +pub(crate) mod python; +pub mod registry; +pub mod rule; +pub mod target; diff --git a/crates/ruff_linter/src/external/ast/python.rs b/crates/ruff_linter/src/external/ast/python.rs new file mode 100644 index 0000000000000..cb4fa5235f6f4 --- /dev/null +++ b/crates/ruff_linter/src/external/ast/python.rs @@ -0,0 +1,740 @@ +#![cfg(feature = "ext-lint")] +#![cfg_attr(not(test), allow(dead_code))] + +use self::generated::GENERATED_EXPORTS; +use self::projection::{ProjectionMode, project_typed_node}; +use self::store::{AstStoreHandle, current_store}; +use crate::Locator; +use crate::external::ast::target::{ExprKind, StmtKind}; +use pyo3::IntoPyObject; +use pyo3::prelude::*; +use pyo3::types::{PyBool, PyDict, PyModule, PyString, PyTuple}; +use pyo3::{Bound, PyClassInitializer, PyObject}; +use ruff_python_ast::name::UnqualifiedName; +use ruff_python_ast::{AnyNodeRef, Expr, ExprCall, HasNodeIndex, Stmt}; +use ruff_text_size::{Ranged, TextRange}; + +pub(crate) fn span_tuple(py: Python<'_>, range: TextRange) -> PyResult { + Ok( + PyTuple::new(py, [range.start().to_u32(), range.end().to_u32()])? + .into_any() + .unbind(), + ) +} + +#[derive(Debug)] +pub(crate) struct ProjectionTypes; + +pub(crate) static PROJECTION_TYPES: ProjectionTypes = ProjectionTypes; + +pub(crate) type ProjectionTypesRef = &'static ProjectionTypes; + +#[derive(Debug)] +#[allow(dead_code)] +pub(crate) struct ModuleTypes { + pub context: Py, + pub projection: ProjectionTypesRef, +} + +pub(crate) fn load_module_types(py: Python<'_>) -> PyResult { + let module = PyModule::new(py, "ruff_external")?; + module.setattr("__file__", "ruff_external/__init__.py")?; + ruff_external(py, &module)?; + let sys = PyModule::import(py, "sys")?; + let modules_obj = sys.getattr("modules")?; + let modules = modules_obj.downcast::()?; + modules.set_item("ruff_external", &module)?; + + let context = module.getattr("Context")?.unbind(); + + let projection = &PROJECTION_TYPES; + + Ok(ModuleTypes { + context, + projection, + }) +} + +pub(crate) fn expr_to_python( + py: Python<'_>, + locator: &Locator<'_>, + expr: &Expr, + types: ProjectionTypesRef, +) -> PyResult { + project_or_raw(py, locator, AnyNodeRef::from(expr), types, || { + let store = current_store(); + let node_id = ensure_node_id(expr, &store); + let kind = ExprKind::from(expr).as_str().to_string(); + let range = expr.range(); + let text = locator.slice(range); + let repr = format!("{expr:?}"); + build_raw_node(py, kind, range, text.to_string(), repr, node_id, store) + }) +} + +pub(crate) fn stmt_to_python( + py: Python<'_>, + locator: &Locator<'_>, + stmt: &Stmt, + types: ProjectionTypesRef, +) -> PyResult { + project_or_raw(py, locator, AnyNodeRef::from(stmt), types, || { + let store = current_store(); + let node_id = ensure_node_id(stmt, &store); + let kind = StmtKind::from(stmt).as_str().to_string(); + let range = stmt.range(); + let text = locator.slice(range); + let repr = format!("{stmt:?}"); + build_raw_node(py, kind, range, text.to_string(), repr, node_id, store) + }) +} + +pub(crate) fn node_to_python( + py: Python<'_>, + locator: &Locator<'_>, + node: AnyNodeRef<'_>, + types: ProjectionTypesRef, +) -> PyResult { + project_or_raw(py, locator, node, types, || { + let store = current_store(); + let node_id = ensure_node_id(&node, &store); + let range = node.range(); + let text = locator.slice(range).to_string(); + let repr = format!("{node:?}"); + let kind = format!("{:?}", node.kind()); + + build_raw_node(py, kind, range, text, repr, node_id, store) + }) +} + +fn build_raw_node( + py: Python<'_>, + kind: String, + range: TextRange, + text: String, + repr: String, + node_id: u32, + store: AstStoreHandle, +) -> PyResult { + RawNode::new_instance(py, kind, span_tuple(py, range)?, text, repr, node_id, store) +} + +fn project_or_raw( + py: Python<'_>, + locator: &Locator<'_>, + node: AnyNodeRef<'_>, + types: ProjectionTypesRef, + fallback: F, +) -> PyResult +where + F: FnOnce() -> PyResult, +{ + if let Some(typed) = project_typed_node(py, locator, node, ProjectionMode::Typed, types)? { + return Ok(typed); + } + + fallback() +} + +fn optional_str(py: Python<'_>, value: Option<&str>) -> PyObject { + match value { + Some(value) => py_string(py, value), + None => py_none(py), + } +} + +fn py_string(py: Python<'_>, value: &str) -> PyObject { + PyString::new(py, value).into_any().unbind() +} + +fn py_bool(py: Python<'_>, value: bool) -> PyObject { + PyBool::new(py, value).to_owned().into_any().unbind() +} + +fn py_int(py: Python<'_>, value: u32) -> PyObject { + value + .into_pyobject(py) + .expect("u32 to PyObject") + .into_any() + .unbind() +} + +fn py_none(py: Python<'_>) -> PyObject { + py.None() +} + +fn ensure_node_id(node: &impl HasNodeIndex, store: &AstStoreHandle) -> u32 { + store.assign_id(node.node_index()) +} + +fn extract_callee(locator: &Locator<'_>, range: TextRange, call: &ExprCall) -> Option { + UnqualifiedName::from_expr(call.func.as_ref()) + .map(|name| name.to_string()) + .or_else(|| { + let text = locator.slice(range); + let trimmed = text.trim_start(); + trimmed + .find('(') + .map(|index| trimmed[..index].trim()) + .filter(|callee| !callee.is_empty()) + .map(ToOwned::to_owned) + }) +} + +#[pymodule(gil_used = false)] +pub(crate) fn ruff_external(_py: Python<'_>, module: &Bound<'_, PyModule>) -> PyResult<()> { + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + generated::add_generated_classes(module)?; + let mut exports = vec!["Context", "Node", "RawNode"]; + exports.extend_from_slice(GENERATED_EXPORTS); + let exports = PyTuple::new(module.py(), exports)?; + module.add("__all__", exports)?; + Ok(()) +} + +mod generated; +mod projection; +pub(crate) mod source; +pub(crate) mod store; + +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct RawNode; + +#[pymethods] +impl RawNode {} + +impl RawNode { + fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + ) -> PyResult { + let node = bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store); + let initializer = PyClassInitializer::from(node).add_subclass(RawNode); + Ok(Py::new(py, initializer)?.into_any()) + } +} + +mod bindings { + #![allow(clippy::used_underscore_binding)] + + use super::store::AstStoreHandle; + use super::{py_int, py_none, py_string}; + use pyo3::exceptions::PyKeyError; + use pyo3::prelude::*; + use pyo3::types::PyAnyMethods; + + #[pyclass(module = "ruff_external", unsendable, subclass)] + pub(crate) struct Node { + #[pyo3(get)] + _kind: String, + #[pyo3(get)] + _span: PyObject, + #[pyo3(get)] + _text: String, + #[pyo3(get)] + _repr: String, + #[pyo3(get, name = "node_id")] + py_id: u32, + #[allow(dead_code)] + store: AstStoreHandle, + } + + #[pymethods] + impl Node { + #[new] + #[allow(clippy::too_many_arguments)] + #[pyo3( + signature = ( + kind, + span, + text, + repr_value, + node_id, + ) + )] + fn new( + _py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + ) -> Self { + Self::new_inner( + _py, + kind, + span, + text, + repr_value, + node_id, + AstStoreHandle::new(), + ) + } + + fn __repr__(&self, py: Python<'_>) -> PyResult { + let span_repr: String = self._span.bind(py).repr()?.extract()?; + Ok(format!("Node(kind={:?}, span={})", self._kind, span_repr)) + } + + fn __getitem__(&self, py: Python<'_>, key: &str) -> PyResult { + attribute_lookup(py, key, self).ok_or_else(|| PyKeyError::new_err(key.to_string())) + } + + #[pyo3(signature = (key, default=None))] + fn get(&self, py: Python<'_>, key: &str, default: Option) -> PyObject { + attribute_lookup(py, key, self) + .unwrap_or_else(|| default.unwrap_or_else(|| py_none(py))) + } + } + + impl Node { + pub(crate) fn new_inner( + _py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + ) -> Self { + Self { + _kind: kind, + _span: span, + _text: text, + _repr: repr_value, + py_id: node_id, + store, + } + } + + pub(crate) const fn store(&self) -> &AstStoreHandle { + &self.store + } + + pub(crate) const fn node_id(&self) -> u32 { + self.py_id + } + } + + fn attribute_lookup(py: Python<'_>, key: &str, node: &Node) -> Option { + match key { + "_kind" => Some(py_string(py, &node._kind)), + "_span" => Some(node._span.clone_ref(py)), + "_text" => Some(py_string(py, &node._text)), + "_repr" => Some(py_string(py, &node._repr)), + "node_id" => Some(py_int(py, node.py_id)), + _ => None, + } + } + + #[pyclass(module = "ruff_external", unsendable)] + pub(crate) struct Context { + #[pyo3(get)] + code: String, + #[pyo3(get)] + name: String, + #[pyo3(get)] + _report: Py, + } + + #[pymethods] + impl Context { + #[new] + fn new(code: String, name: String, reporter: Py) -> Self { + Self { + code, + name, + _report: reporter, + } + } + + #[pyo3(signature = (message, span=None))] + fn report(&self, py: Python<'_>, message: &str, span: Option<(u32, u32)>) -> PyResult<()> { + self._report.bind(py).call1((message, span))?; + Ok(()) + } + + fn __repr__(&self) -> String { + format!("Context(code={:?}, name={:?})", self.code, self.name) + } + } +} + +#[cfg(test)] +mod tests { + use super::source::with_source_file; + use super::store::{AstStoreHandle, with_store}; + use super::*; + use anyhow::anyhow; + use pyo3::types::{PyAnyMethods, PyTuple as PyTupleType, PyTuple}; + use ruff_python_ast::{Expr, Stmt}; + use ruff_python_parser::{parse_expression, parse_module}; + use ruff_source_file::SourceFileBuilder; + use ruff_text_size::{TextRange, TextSize}; + + fn with_python_fixture( + source: &str, + f: impl FnOnce(Python<'_>, ProjectionTypesRef, &Locator<'_>) -> R, + ) -> R { + pyo3::prepare_freethreaded_python(); + Python::with_gil(|py| { + let module_types = load_module_types(py).expect("module types"); + let projection = module_types.projection; + let locator = Locator::new(source); + let source_file = SourceFileBuilder::new("test.py", source).finish(); + with_store(AstStoreHandle::new(), || { + with_source_file(&source_file, || f(py, projection, &locator)) + }) + }) + } + + fn with_python_fixture_result( + source: &str, + f: impl FnOnce(Python<'_>, ProjectionTypesRef, &Locator<'_>) -> anyhow::Result, + ) -> anyhow::Result { + pyo3::prepare_freethreaded_python(); + Python::with_gil(|py| { + let module_types = load_module_types(py).expect("module types"); + let projection = module_types.projection; + let locator = Locator::new(source); + let source_file = SourceFileBuilder::new("test.py", source).finish(); + with_store(AstStoreHandle::new(), || { + with_source_file(&source_file, || f(py, projection, &locator)) + }) + }) + } + + #[test] + fn call_expr_arguments_are_cached() { + with_python_fixture("foo(value)", |py, projection, locator| { + let parsed = parse_expression(locator.contents()).expect("parse expression"); + let mod_expr = parsed.into_syntax(); + let expr = *mod_expr.body; + + let py_call = expr_to_python(py, locator, &expr, projection).expect("convert call"); + let call_bound = py_call.bind(py); + let first = call_bound.getattr("arguments").expect("first arguments"); + let second = call_bound.getattr("arguments").expect("second arguments"); + + assert_eq!( + first.as_ptr(), + second.as_ptr(), + "lazy loader should cache the PyTuple instance" + ); + + let args = first.getattr("args").expect("Arguments.args attribute"); + let args_tuple = args + .downcast::() + .expect("Arguments.args should be a tuple"); + assert_eq!(args_tuple.len(), 1); + }); + } + + #[test] + fn if_expr_test_is_eager() { + with_python_fixture("value if cond else fallback", |py, projection, locator| { + let parsed = parse_expression(locator.contents()).expect("parse expression"); + let mod_expr = parsed.into_syntax(); + let expr = *mod_expr.body; + let Expr::If(_) = expr else { + panic!("expected IfExpr") + }; + + let py_if = expr_to_python(py, locator, &expr, projection).expect("convert if expr"); + + let if_bound = py_if.bind(py); + let first = if_bound.getattr("test").expect("first test attribute"); + let second = if_bound.getattr("test").expect("second test attribute"); + + assert_eq!( + first.as_ptr(), + second.as_ptr(), + "eager field should be cached" + ); + }); + } + + #[test] + fn call_projection_includes_callee() -> anyhow::Result<()> { + with_python_fixture_result("logging.info('x')", |py, projection, locator| { + let expr = { + let parsed = ruff_python_parser::parse_expression(locator.contents())?; + *parsed.into_syntax().body + }; + let node = expr_to_python(py, locator, &expr, projection)?; + let node = node.bind(py); + let callee: String = node.getattr("callee")?.extract()?; + assert_eq!(callee, "logging.info"); + Ok(()) + }) + } + + #[test] + fn call_projection_includes_arguments() -> anyhow::Result<()> { + with_python_fixture_result( + "logging.info('static', msg='template {}'.format(value))", + |py, projection, locator| { + let expr = { + let parsed = ruff_python_parser::parse_expression(locator.contents())?; + *parsed.into_syntax().body + }; + + let node = expr_to_python(py, locator, &expr, projection)?; + let node = node.bind(py); + + let function_text: String = node.getattr("function_text")?.extract()?; + assert_eq!(function_text, "logging.info"); + + let arguments = node.getattr("arguments")?; + let args_obj = arguments.getattr("args")?; + let args = args_obj + .downcast::() + .map_err(|err| anyhow!(err.to_string()))?; + assert_eq!(args.len(), 1); + + let first = args.get_item(0)?; + let first_kind: String = first.getattr("_kind")?.extract()?; + assert_eq!(first_kind, "StringLiteral"); + + let keywords_obj = arguments.getattr("keywords")?; + let keywords = keywords_obj + .downcast::() + .map_err(|err| anyhow!(err.to_string()))?; + assert_eq!(keywords.len(), 1); + + let keyword = keywords.get_item(0)?; + let name = keyword.getattr("arg")?; + let name: String = name.extract()?; + assert_eq!(name, "msg"); + + let call_function_text: String = keyword + .getattr("value")? + .getattr("function_text")? + .extract()?; + assert!( + call_function_text.ends_with(".format"), + "unexpected call_function_text: {call_function_text}" + ); + + Ok(()) + }, + ) + } + + #[test] + fn stmt_projection_reports_kind() -> anyhow::Result<()> { + with_python_fixture_result("pass\n", |py, projection, locator| { + let stmt = ruff_python_ast::Stmt::Pass(ruff_python_ast::StmtPass { + node_index: ruff_python_ast::AtomicNodeIndex::NONE, + range: TextRange::new(TextSize::new(0), TextSize::new(4)), + }); + + let node = stmt_to_python(py, locator, &stmt, projection)?; + let node = node.bind(py); + let kind: String = node.getattr("_kind")?.extract()?; + assert_eq!(kind, "Pass"); + Ok(()) + }) + } + + #[test] + fn function_def_projects_decorators() -> anyhow::Result<()> { + with_python_fixture_result( + "@decorator\ndef func():\n pass\n", + |py, projection, locator| { + let parsed = parse_module(locator.contents())?; + let module = parsed.into_syntax(); + let stmt = module + .body + .first() + .ok_or_else(|| anyhow!("missing statement"))?; + let Stmt::FunctionDef(_) = stmt else { + return Err(anyhow!("expected FunctionDef")); + }; + + let node = stmt_to_python(py, locator, stmt, projection)?; + let node = node.bind(py); + let decorators = node.getattr("decorator_list")?; + let decorators = decorators + .downcast::() + .map_err(|err| anyhow!(err.to_string()))?; + assert_eq!(decorators.len(), 1); + Ok(()) + }, + ) + } + + #[test] + fn with_stmt_projects_items() -> anyhow::Result<()> { + with_python_fixture_result( + "with open('x') as f:\n pass\n", + |py, projection, locator| { + let parsed = parse_module(locator.contents())?; + let module = parsed.into_syntax(); + let stmt = module + .body + .first() + .ok_or_else(|| anyhow!("missing statement"))?; + let Stmt::With(_) = stmt else { + return Err(anyhow!("expected With")); + }; + + let node = stmt_to_python(py, locator, stmt, projection)?; + let node = node.bind(py); + let items = node.getattr("items")?; + let items = items + .downcast::() + .map_err(|err| anyhow!(err.to_string()))?; + assert_eq!(items.len(), 1); + Ok(()) + }, + ) + } + + #[test] + fn import_from_projects_names_and_level() -> anyhow::Result<()> { + with_python_fixture_result("from os import path as p\n", |py, projection, locator| { + let parsed = parse_module(locator.contents())?; + let module = parsed.into_syntax(); + let stmt = module + .body + .first() + .ok_or_else(|| anyhow!("missing statement"))?; + let Stmt::ImportFrom(_) = stmt else { + return Err(anyhow!("expected ImportFrom")); + }; + + let node = stmt_to_python(py, locator, stmt, projection)?; + let node = node.bind(py); + let names = node.getattr("names")?; + let names = names + .downcast::() + .map_err(|err| anyhow!(err.to_string()))?; + assert_eq!(names.len(), 1); + + let level: u32 = node.getattr("level")?.extract()?; + assert_eq!(level, 0); + Ok(()) + }) + } + + #[test] + fn try_stmt_projects_handlers() -> anyhow::Result<()> { + with_python_fixture_result( + "try:\n pass\nexcept Exception:\n pass\n", + |py, projection, locator| { + let parsed = parse_module(locator.contents())?; + let module = parsed.into_syntax(); + let stmt = module + .body + .first() + .ok_or_else(|| anyhow!("missing statement"))?; + let Stmt::Try(_) = stmt else { + return Err(anyhow!("expected Try")); + }; + + let node = stmt_to_python(py, locator, stmt, projection)?; + let node = node.bind(py); + let handlers = node.getattr("handlers")?; + let handlers = handlers + .downcast::() + .map_err(|err| anyhow!(err.to_string()))?; + assert_eq!(handlers.len(), 1); + Ok(()) + }, + ) + } + + #[test] + fn comprehension_projects_generators() -> anyhow::Result<()> { + with_python_fixture_result("[x for x in values]", |py, projection, locator| { + let parsed = parse_expression(locator.contents())?; + let expr = parsed.into_syntax().body; + + let node = expr_to_python(py, locator, &expr, projection)?; + let node = node.bind(py); + let generators = node.getattr("generators")?; + let generators = generators + .downcast::() + .map_err(|err| anyhow!(err.to_string()))?; + assert_eq!(generators.len(), 1); + Ok(()) + }) + } + + #[test] + fn for_stmt_projects_is_async() -> anyhow::Result<()> { + with_python_fixture_result( + "async for x in y:\n pass\n", + |py, projection, locator| { + let parsed = parse_module(locator.contents())?; + let module = parsed.into_syntax(); + let stmt = module + .body + .first() + .ok_or_else(|| anyhow!("missing statement"))?; + let Stmt::For(_) = stmt else { + return Err(anyhow!("expected For")); + }; + + let node = stmt_to_python(py, locator, stmt, projection)?; + let node = node.bind(py); + let is_async: bool = node.getattr("is_async")?.extract()?; + assert!(is_async); + Ok(()) + }, + ) + } + + #[test] + fn aug_assign_projects_op() -> anyhow::Result<()> { + with_python_fixture_result("x += 1\n", |py, projection, locator| { + let parsed = parse_module(locator.contents())?; + let module = parsed.into_syntax(); + let stmt = module + .body + .first() + .ok_or_else(|| anyhow!("missing statement"))?; + let Stmt::AugAssign(_) = stmt else { + return Err(anyhow!("expected AugAssign")); + }; + + let node = stmt_to_python(py, locator, stmt, projection)?; + let node = node.bind(py); + let op: String = node.getattr("op")?.extract()?; + assert_eq!(op, "+"); + Ok(()) + }) + } + + #[test] + fn global_stmt_projects_names() -> anyhow::Result<()> { + with_python_fixture_result("global a, b\n", |py, projection, locator| { + let parsed = parse_module(locator.contents())?; + let module = parsed.into_syntax(); + let stmt = module + .body + .first() + .ok_or_else(|| anyhow!("missing statement"))?; + let Stmt::Global(_) = stmt else { + return Err(anyhow!("expected Global")); + }; + + let node = stmt_to_python(py, locator, stmt, projection)?; + let node = node.bind(py); + let names = node.getattr("names")?; + let names = names + .downcast::() + .map_err(|err| anyhow!(err.to_string()))?; + assert_eq!(names.len(), 2); + Ok(()) + }) + } +} diff --git a/crates/ruff_linter/src/external/ast/python/generated.rs b/crates/ruff_linter/src/external/ast/python/generated.rs new file mode 100644 index 0000000000000..8fb4d64f2e3bd --- /dev/null +++ b/crates/ruff_linter/src/external/ast/python/generated.rs @@ -0,0 +1,7919 @@ +// This file is auto-generated by crates/ruff_python_ast/generate.py + +#![allow(clippy::unnecessary_wraps)] + +use pyo3::exceptions::PyRuntimeError; + +use pyo3::PyClassInitializer; +use pyo3::prelude::*; +use pyo3::types::{PyModule, PyTuple}; + +use ruff_python_ast::AnyNodeRef; + +use super::ProjectionTypesRef; +use super::bindings; +use super::source::SourceFileHandle; +use super::store::AstStoreHandle; +use super::{ + expr_to_python, node_to_python, optional_str, py_int, py_none, py_string, stmt_to_python, +}; +fn cache_value(slot: &mut Option, py: Python<'_>, value: PyObject) -> PyResult { + *slot = Some(value.clone_ref(py)); + Ok(value) +} + +fn map_option( + py: Python<'_>, + value: Option, + mapper: impl FnOnce(T) -> PyResult, +) -> PyResult { + match value { + Some(value) => mapper(value), + None => Ok(py_none(py)), + } +} + +fn map_tuple(py: Python<'_>, iter: I, mut mapper: F) -> PyResult +where + I: IntoIterator, + F: FnMut(I::Item) -> PyResult, +{ + let mut values = Vec::new(); + for value in iter { + values.push(mapper(value)?); + } + Ok(PyTuple::new(py, values)?.into_any().unbind()) +} + +fn get_ast(store: &AstStoreHandle, id: u32) -> PyResult<&T> +where + T: 'static, +{ + store + .get::(id) + .map_err(|err| PyRuntimeError::new_err(err.to_string())) +} +pub(crate) const GENERATED_EXPORTS: &[&str] = &[ + "Module", + "Expression", + "FunctionDefStmt", + "ClassDefStmt", + "ReturnStmt", + "DeleteStmt", + "TypeAliasStmt", + "AssignStmt", + "AugAssignStmt", + "AnnAssignStmt", + "ForStmt", + "WhileStmt", + "IfStmt", + "WithStmt", + "MatchStmt", + "RaiseStmt", + "TryStmt", + "AssertStmt", + "ImportStmt", + "ImportFromStmt", + "GlobalStmt", + "NonlocalStmt", + "ExprStmt", + "PassStmt", + "BreakStmt", + "ContinueStmt", + "IpyEscapeCommandStmt", + "BoolOpExpr", + "NamedExpr", + "BinOpExpr", + "UnaryOpExpr", + "LambdaExpr", + "IfExpr", + "DictExpr", + "SetExpr", + "ListCompExpr", + "SetCompExpr", + "DictCompExpr", + "GeneratorExpr", + "AwaitExpr", + "YieldExpr", + "YieldFromExpr", + "CompareExpr", + "CallExpr", + "FStringExpr", + "TStringExpr", + "StringLiteralExpr", + "BytesLiteralExpr", + "NumberLiteralExpr", + "BooleanLiteralExpr", + "NoneLiteralExpr", + "EllipsisLiteralExpr", + "AttributeExpr", + "SubscriptExpr", + "StarredExpr", + "NameExpr", + "ListExpr", + "TupleExpr", + "SliceExpr", + "IpyEscapeCommandExpr", + "ExceptHandler", + "InterpolatedElement", + "InterpolatedStringLiteralElement", + "MatchValuePattern", + "MatchSingletonPattern", + "MatchSequencePattern", + "MatchMappingPattern", + "MatchClassPattern", + "MatchStarPattern", + "MatchAsPattern", + "MatchOrPattern", + "TypeVar", + "TypeVarTuple", + "ParamSpec", + "InterpolatedStringFormatSpec", + "PatternArguments", + "PatternKeyword", + "Comprehension", + "Parameters", + "Parameter", + "ParameterWithDefault", + "Alias", + "WithItem", + "MatchCase", + "Decorator", + "ElifElseClause", + "TypeParams", + "FString", + "TString", + "StringLiteral", + "BytesLiteral", + "Identifier", + "Arguments", + "Keyword", +]; +pub(crate) fn add_generated_classes(module: &Bound<'_, PyModule>) -> PyResult<()> { + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + module.add_class::()?; + Ok(()) +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct Module { + body: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl Module { + #[getter] + fn body(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.body { + return Ok(value.clone_ref(this.py())); + } + Self::load_body(this) + } + fn load_body(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.body.iter(), |value| { + stmt_to_python(py, &locator, value, this.projection) + })?; + cache_value(&mut this.body, py, value) + } +} +impl Module { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + body: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct Expression { + body: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl Expression { + #[getter] + fn body(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.body { + return Ok(value.clone_ref(this.py())); + } + Self::load_body(this) + } + fn load_body(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = expr_to_python(py, &locator, &ast.body, this.projection)?; + cache_value(&mut this.body, py, value) + } +} +impl Expression { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + body: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct FunctionDefStmt { + is_async: Option, + decorator_list: Option, + name: Option, + type_params: Option, + parameters: Option, + returns: Option, + body: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl FunctionDefStmt { + #[getter] + fn is_async(&self, py: Python<'_>) -> PyResult { + Ok(self.is_async.as_ref().unwrap().clone_ref(py)) + } + #[getter] + fn decorator_list(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.decorator_list { + return Ok(value.clone_ref(this.py())); + } + Self::load_decorator_list(this) + } + #[getter] + fn name(&self, py: Python<'_>) -> PyResult { + Ok(self.name.as_ref().unwrap().clone_ref(py)) + } + #[getter] + fn type_params(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.type_params { + return Ok(value.clone_ref(this.py())); + } + Self::load_type_params(this) + } + #[getter] + fn parameters(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.parameters { + return Ok(value.clone_ref(this.py())); + } + Self::load_parameters(this) + } + #[getter] + fn returns(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.returns { + return Ok(value.clone_ref(this.py())); + } + Self::load_returns(this) + } + #[getter] + fn body(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.body { + return Ok(value.clone_ref(this.py())); + } + Self::load_body(this) + } + fn load_decorator_list(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.decorator_list.iter(), |value| { + node_to_python(py, &locator, AnyNodeRef::from(value), this.projection) + })?; + cache_value(&mut this.decorator_list, py, value) + } + fn load_type_params(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_option(py, ast.type_params.as_ref(), |value| { + node_to_python( + py, + &locator, + AnyNodeRef::from(value.as_ref()), + this.projection, + ) + })?; + cache_value(&mut this.type_params, py, value) + } + fn load_parameters(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = node_to_python( + py, + &locator, + AnyNodeRef::from(ast.parameters.as_ref()), + this.projection, + )?; + cache_value(&mut this.parameters, py, value) + } + fn load_returns(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_option(py, ast.returns.as_ref(), |value| { + expr_to_python(py, &locator, value, this.projection) + })?; + cache_value(&mut this.returns, py, value) + } + fn load_body(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.body.iter(), |value| { + stmt_to_python(py, &locator, value, this.projection) + })?; + cache_value(&mut this.body, py, value) + } +} +impl FunctionDefStmt { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + is_async: PyObject, + name: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + is_async: Some(is_async), + decorator_list: None, + name: Some(name), + type_params: None, + parameters: None, + returns: None, + body: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + is_async: PyObject, + name: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, is_async, name, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct ClassDefStmt { + decorator_list: Option, + name: Option, + type_params: Option, + arguments: Option, + body: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl ClassDefStmt { + #[getter] + fn decorator_list(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.decorator_list { + return Ok(value.clone_ref(this.py())); + } + Self::load_decorator_list(this) + } + #[getter] + fn name(&self, py: Python<'_>) -> PyResult { + Ok(self.name.as_ref().unwrap().clone_ref(py)) + } + #[getter] + fn type_params(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.type_params { + return Ok(value.clone_ref(this.py())); + } + Self::load_type_params(this) + } + #[getter] + fn arguments(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.arguments { + return Ok(value.clone_ref(this.py())); + } + Self::load_arguments(this) + } + #[getter] + fn body(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.body { + return Ok(value.clone_ref(this.py())); + } + Self::load_body(this) + } + fn load_decorator_list(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.decorator_list.iter(), |value| { + node_to_python(py, &locator, AnyNodeRef::from(value), this.projection) + })?; + cache_value(&mut this.decorator_list, py, value) + } + fn load_type_params(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_option(py, ast.type_params.as_ref(), |value| { + node_to_python( + py, + &locator, + AnyNodeRef::from(value.as_ref()), + this.projection, + ) + })?; + cache_value(&mut this.type_params, py, value) + } + fn load_arguments(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_option(py, ast.arguments.as_ref(), |value| { + node_to_python( + py, + &locator, + AnyNodeRef::from(value.as_ref()), + this.projection, + ) + })?; + cache_value(&mut this.arguments, py, value) + } + fn load_body(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.body.iter(), |value| { + stmt_to_python(py, &locator, value, this.projection) + })?; + cache_value(&mut this.body, py, value) + } +} +impl ClassDefStmt { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + name: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + decorator_list: None, + name: Some(name), + type_params: None, + arguments: None, + body: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + name: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, name, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct ReturnStmt { + value: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl ReturnStmt { + #[getter] + fn value(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.value { + return Ok(value.clone_ref(this.py())); + } + Self::load_value(this) + } + fn load_value(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_option(py, ast.value.as_ref(), |value| { + expr_to_python(py, &locator, value, this.projection) + })?; + cache_value(&mut this.value, py, value) + } +} +impl ReturnStmt { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + value: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct DeleteStmt { + targets: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl DeleteStmt { + #[getter] + fn targets(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.targets { + return Ok(value.clone_ref(this.py())); + } + Self::load_targets(this) + } + fn load_targets(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.targets.iter(), |value| { + expr_to_python(py, &locator, value, this.projection) + })?; + cache_value(&mut this.targets, py, value) + } +} +impl DeleteStmt { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + targets: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct TypeAliasStmt { + name: Option, + type_params: Option, + value: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl TypeAliasStmt { + #[getter] + fn name(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.name { + return Ok(value.clone_ref(this.py())); + } + Self::load_name(this) + } + #[getter] + fn type_params(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.type_params { + return Ok(value.clone_ref(this.py())); + } + Self::load_type_params(this) + } + #[getter] + fn value(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.value { + return Ok(value.clone_ref(this.py())); + } + Self::load_value(this) + } + fn load_name(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = expr_to_python(py, &locator, &ast.name, this.projection)?; + cache_value(&mut this.name, py, value) + } + fn load_type_params(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_option(py, ast.type_params.as_ref(), |value| { + node_to_python( + py, + &locator, + AnyNodeRef::from(value.as_ref()), + this.projection, + ) + })?; + cache_value(&mut this.type_params, py, value) + } + fn load_value(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = expr_to_python(py, &locator, &ast.value, this.projection)?; + cache_value(&mut this.value, py, value) + } +} +impl TypeAliasStmt { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + name: None, + type_params: None, + value: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct AssignStmt { + targets: Option, + value: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl AssignStmt { + #[getter] + fn targets(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.targets { + return Ok(value.clone_ref(this.py())); + } + Self::load_targets(this) + } + #[getter] + fn value(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.value { + return Ok(value.clone_ref(this.py())); + } + Self::load_value(this) + } + fn load_targets(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.targets.iter(), |value| { + expr_to_python(py, &locator, value, this.projection) + })?; + cache_value(&mut this.targets, py, value) + } + fn load_value(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = expr_to_python(py, &locator, &ast.value, this.projection)?; + cache_value(&mut this.value, py, value) + } +} +impl AssignStmt { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + targets: None, + value: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct AugAssignStmt { + target: Option, + op: Option, + value: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl AugAssignStmt { + #[getter] + fn target(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.target { + return Ok(value.clone_ref(this.py())); + } + Self::load_target(this) + } + #[getter] + fn op(&self, py: Python<'_>) -> PyResult { + Ok(self.op.as_ref().unwrap().clone_ref(py)) + } + #[getter] + fn value(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.value { + return Ok(value.clone_ref(this.py())); + } + Self::load_value(this) + } + fn load_target(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = expr_to_python(py, &locator, &ast.target, this.projection)?; + cache_value(&mut this.target, py, value) + } + fn load_value(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = expr_to_python(py, &locator, &ast.value, this.projection)?; + cache_value(&mut this.value, py, value) + } +} +impl AugAssignStmt { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + op: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + target: None, + op: Some(op), + value: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + op: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, op, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct AnnAssignStmt { + target: Option, + annotation: Option, + value: Option, + simple: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl AnnAssignStmt { + #[getter] + fn target(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.target { + return Ok(value.clone_ref(this.py())); + } + Self::load_target(this) + } + #[getter] + fn annotation(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.annotation { + return Ok(value.clone_ref(this.py())); + } + Self::load_annotation(this) + } + #[getter] + fn value(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.value { + return Ok(value.clone_ref(this.py())); + } + Self::load_value(this) + } + #[getter] + fn simple(&self, py: Python<'_>) -> PyResult { + Ok(self.simple.as_ref().unwrap().clone_ref(py)) + } + fn load_target(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = expr_to_python(py, &locator, &ast.target, this.projection)?; + cache_value(&mut this.target, py, value) + } + fn load_annotation(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = expr_to_python(py, &locator, &ast.annotation, this.projection)?; + cache_value(&mut this.annotation, py, value) + } + fn load_value(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_option(py, ast.value.as_ref(), |value| { + expr_to_python(py, &locator, value, this.projection) + })?; + cache_value(&mut this.value, py, value) + } +} +impl AnnAssignStmt { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + simple: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + target: None, + annotation: None, + value: None, + simple: Some(simple), + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + simple: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, simple, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct ForStmt { + is_async: Option, + target: Option, + iter: Option, + body: Option, + orelse: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl ForStmt { + #[getter] + fn is_async(&self, py: Python<'_>) -> PyResult { + Ok(self.is_async.as_ref().unwrap().clone_ref(py)) + } + #[getter] + fn target(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.target { + return Ok(value.clone_ref(this.py())); + } + Self::load_target(this) + } + #[allow(clippy::iter_not_returning_iterator)] + #[getter] + fn iter(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.iter { + return Ok(value.clone_ref(this.py())); + } + Self::load_iter(this) + } + #[getter] + fn body(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.body { + return Ok(value.clone_ref(this.py())); + } + Self::load_body(this) + } + #[getter] + fn orelse(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.orelse { + return Ok(value.clone_ref(this.py())); + } + Self::load_orelse(this) + } + fn load_target(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = expr_to_python(py, &locator, &ast.target, this.projection)?; + cache_value(&mut this.target, py, value) + } + fn load_iter(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = expr_to_python(py, &locator, &ast.iter, this.projection)?; + cache_value(&mut this.iter, py, value) + } + fn load_body(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.body.iter(), |value| { + stmt_to_python(py, &locator, value, this.projection) + })?; + cache_value(&mut this.body, py, value) + } + fn load_orelse(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.orelse.iter(), |value| { + stmt_to_python(py, &locator, value, this.projection) + })?; + cache_value(&mut this.orelse, py, value) + } +} +impl ForStmt { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + is_async: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + is_async: Some(is_async), + target: None, + iter: None, + body: None, + orelse: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + is_async: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, is_async, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct WhileStmt { + test: Option, + body: Option, + orelse: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl WhileStmt { + #[getter] + fn test(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.test { + return Ok(value.clone_ref(this.py())); + } + Self::load_test(this) + } + #[getter] + fn body(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.body { + return Ok(value.clone_ref(this.py())); + } + Self::load_body(this) + } + #[getter] + fn orelse(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.orelse { + return Ok(value.clone_ref(this.py())); + } + Self::load_orelse(this) + } + fn load_test(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = expr_to_python(py, &locator, &ast.test, this.projection)?; + cache_value(&mut this.test, py, value) + } + fn load_body(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.body.iter(), |value| { + stmt_to_python(py, &locator, value, this.projection) + })?; + cache_value(&mut this.body, py, value) + } + fn load_orelse(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.orelse.iter(), |value| { + stmt_to_python(py, &locator, value, this.projection) + })?; + cache_value(&mut this.orelse, py, value) + } +} +impl WhileStmt { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + test: None, + body: None, + orelse: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct IfStmt { + test: Option, + body: Option, + elif_else_clauses: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl IfStmt { + #[getter] + fn test(&self, py: Python<'_>) -> PyResult { + Ok(self.test.as_ref().unwrap().clone_ref(py)) + } + #[getter] + fn body(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.body { + return Ok(value.clone_ref(this.py())); + } + Self::load_body(this) + } + #[getter] + fn elif_else_clauses(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.elif_else_clauses { + return Ok(value.clone_ref(this.py())); + } + Self::load_elif_else_clauses(this) + } + fn load_body(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.body.iter(), |value| { + stmt_to_python(py, &locator, value, this.projection) + })?; + cache_value(&mut this.body, py, value) + } + fn load_elif_else_clauses(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.elif_else_clauses.iter(), |value| { + node_to_python(py, &locator, AnyNodeRef::from(value), this.projection) + })?; + cache_value(&mut this.elif_else_clauses, py, value) + } +} +impl IfStmt { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + test: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + test: Some(test), + body: None, + elif_else_clauses: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + test: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, test, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct WithStmt { + is_async: Option, + items: Option, + body: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl WithStmt { + #[getter] + fn is_async(&self, py: Python<'_>) -> PyResult { + Ok(self.is_async.as_ref().unwrap().clone_ref(py)) + } + #[getter] + fn items(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.items { + return Ok(value.clone_ref(this.py())); + } + Self::load_items(this) + } + #[getter] + fn body(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.body { + return Ok(value.clone_ref(this.py())); + } + Self::load_body(this) + } + fn load_items(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.items.iter(), |value| { + node_to_python(py, &locator, AnyNodeRef::from(value), this.projection) + })?; + cache_value(&mut this.items, py, value) + } + fn load_body(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.body.iter(), |value| { + stmt_to_python(py, &locator, value, this.projection) + })?; + cache_value(&mut this.body, py, value) + } +} +impl WithStmt { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + is_async: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + is_async: Some(is_async), + items: None, + body: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + is_async: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, is_async, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct MatchStmt { + subject: Option, + cases: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl MatchStmt { + #[getter] + fn subject(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.subject { + return Ok(value.clone_ref(this.py())); + } + Self::load_subject(this) + } + #[getter] + fn cases(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.cases { + return Ok(value.clone_ref(this.py())); + } + Self::load_cases(this) + } + fn load_subject(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = expr_to_python(py, &locator, &ast.subject, this.projection)?; + cache_value(&mut this.subject, py, value) + } + fn load_cases(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.cases.iter(), |value| { + node_to_python(py, &locator, AnyNodeRef::from(value), this.projection) + })?; + cache_value(&mut this.cases, py, value) + } +} +impl MatchStmt { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + subject: None, + cases: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct RaiseStmt { + exc: Option, + cause: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl RaiseStmt { + #[getter] + fn exc(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.exc { + return Ok(value.clone_ref(this.py())); + } + Self::load_exc(this) + } + #[getter] + fn cause(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.cause { + return Ok(value.clone_ref(this.py())); + } + Self::load_cause(this) + } + fn load_exc(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_option(py, ast.exc.as_ref(), |value| { + expr_to_python(py, &locator, value, this.projection) + })?; + cache_value(&mut this.exc, py, value) + } + fn load_cause(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_option(py, ast.cause.as_ref(), |value| { + expr_to_python(py, &locator, value, this.projection) + })?; + cache_value(&mut this.cause, py, value) + } +} +impl RaiseStmt { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + exc: None, + cause: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct TryStmt { + body: Option, + handlers: Option, + orelse: Option, + finalbody: Option, + is_star: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl TryStmt { + #[getter] + fn body(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.body { + return Ok(value.clone_ref(this.py())); + } + Self::load_body(this) + } + #[getter] + fn handlers(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.handlers { + return Ok(value.clone_ref(this.py())); + } + Self::load_handlers(this) + } + #[getter] + fn orelse(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.orelse { + return Ok(value.clone_ref(this.py())); + } + Self::load_orelse(this) + } + #[getter] + fn finalbody(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.finalbody { + return Ok(value.clone_ref(this.py())); + } + Self::load_finalbody(this) + } + #[getter] + fn is_star(&self, py: Python<'_>) -> PyResult { + Ok(self.is_star.as_ref().unwrap().clone_ref(py)) + } + fn load_body(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.body.iter(), |value| { + stmt_to_python(py, &locator, value, this.projection) + })?; + cache_value(&mut this.body, py, value) + } + fn load_handlers(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.handlers.iter(), |value| { + node_to_python(py, &locator, AnyNodeRef::from(value), this.projection) + })?; + cache_value(&mut this.handlers, py, value) + } + fn load_orelse(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.orelse.iter(), |value| { + stmt_to_python(py, &locator, value, this.projection) + })?; + cache_value(&mut this.orelse, py, value) + } + fn load_finalbody(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.finalbody.iter(), |value| { + stmt_to_python(py, &locator, value, this.projection) + })?; + cache_value(&mut this.finalbody, py, value) + } +} +impl TryStmt { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + is_star: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + body: None, + handlers: None, + orelse: None, + finalbody: None, + is_star: Some(is_star), + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + is_star: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, is_star, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct AssertStmt { + test: Option, + msg: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl AssertStmt { + #[getter] + fn test(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.test { + return Ok(value.clone_ref(this.py())); + } + Self::load_test(this) + } + #[getter] + fn msg(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.msg { + return Ok(value.clone_ref(this.py())); + } + Self::load_msg(this) + } + fn load_test(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = expr_to_python(py, &locator, &ast.test, this.projection)?; + cache_value(&mut this.test, py, value) + } + fn load_msg(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_option(py, ast.msg.as_ref(), |value| { + expr_to_python(py, &locator, value, this.projection) + })?; + cache_value(&mut this.msg, py, value) + } +} +impl AssertStmt { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + test: None, + msg: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct ImportStmt { + names: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl ImportStmt { + #[getter] + fn names(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.names { + return Ok(value.clone_ref(this.py())); + } + Self::load_names(this) + } + fn load_names(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.names.iter(), |value| { + node_to_python(py, &locator, AnyNodeRef::from(value), this.projection) + })?; + cache_value(&mut this.names, py, value) + } +} +impl ImportStmt { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + names: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct ImportFromStmt { + module: Option, + names: Option, + level: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl ImportFromStmt { + #[getter] + fn module(&self, py: Python<'_>) -> PyResult { + Ok(self.module.as_ref().unwrap().clone_ref(py)) + } + #[getter] + fn names(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.names { + return Ok(value.clone_ref(this.py())); + } + Self::load_names(this) + } + #[getter] + fn level(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.level { + return Ok(value.clone_ref(this.py())); + } + Self::load_level(this) + } + fn load_names(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.names.iter(), |value| { + node_to_python(py, &locator, AnyNodeRef::from(value), this.projection) + })?; + cache_value(&mut this.names, py, value) + } + fn load_level(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let ast = get_ast::(&store, node_id)?; + let value = py_int(py, ast.level); + cache_value(&mut this.level, py, value) + } +} +impl ImportFromStmt { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + module: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + module: Some(module), + names: None, + level: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + module: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, module, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct GlobalStmt { + names: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl GlobalStmt { + #[getter] + fn names(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.names { + return Ok(value.clone_ref(this.py())); + } + Self::load_names(this) + } + fn load_names(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.names.iter(), |value| { + Ok(py_string(py, value.as_str())) + })?; + cache_value(&mut this.names, py, value) + } +} +impl GlobalStmt { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + names: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct NonlocalStmt { + names: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl NonlocalStmt { + #[getter] + fn names(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.names { + return Ok(value.clone_ref(this.py())); + } + Self::load_names(this) + } + fn load_names(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.names.iter(), |value| { + Ok(py_string(py, value.as_str())) + })?; + cache_value(&mut this.names, py, value) + } +} +impl NonlocalStmt { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + names: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct ExprStmt { + value: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl ExprStmt { + #[getter] + fn value(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.value { + return Ok(value.clone_ref(this.py())); + } + Self::load_value(this) + } + fn load_value(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = expr_to_python(py, &locator, &ast.value, this.projection)?; + cache_value(&mut this.value, py, value) + } +} +impl ExprStmt { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + value: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct PassStmt { + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl PassStmt {} +impl PassStmt { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct BreakStmt { + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl BreakStmt {} +impl BreakStmt { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct ContinueStmt { + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl ContinueStmt {} +impl ContinueStmt { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct IpyEscapeCommandStmt { + kind: Option, + value: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl IpyEscapeCommandStmt { + #[getter] + fn kind(&self, py: Python<'_>) -> PyResult { + Ok(self.kind.as_ref().unwrap().clone_ref(py)) + } + #[getter] + fn value(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.value { + return Ok(value.clone_ref(this.py())); + } + Self::load_value(this) + } + fn load_value(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let ast = get_ast::(&store, node_id)?; + let value = py_string(py, &format!("{value:?}", value = ast.value)); + cache_value(&mut this.value, py, value) + } +} +impl IpyEscapeCommandStmt { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + kind_field: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + kind: Some(kind_field), + value: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + kind_field: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, kind_field, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct BoolOpExpr { + op: Option, + values: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl BoolOpExpr { + #[getter] + fn op(&self, py: Python<'_>) -> PyResult { + Ok(self.op.as_ref().unwrap().clone_ref(py)) + } + #[getter] + fn values(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.values { + return Ok(value.clone_ref(this.py())); + } + Self::load_values(this) + } + fn load_values(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.values.iter(), |value| { + expr_to_python(py, &locator, value, this.projection) + })?; + cache_value(&mut this.values, py, value) + } +} +impl BoolOpExpr { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + op: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + op: Some(op), + values: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + op: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, op, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct NamedExpr { + target: Option, + value: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl NamedExpr { + #[getter] + fn target(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.target { + return Ok(value.clone_ref(this.py())); + } + Self::load_target(this) + } + #[getter] + fn value(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.value { + return Ok(value.clone_ref(this.py())); + } + Self::load_value(this) + } + fn load_target(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = expr_to_python(py, &locator, &ast.target, this.projection)?; + cache_value(&mut this.target, py, value) + } + fn load_value(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = expr_to_python(py, &locator, &ast.value, this.projection)?; + cache_value(&mut this.value, py, value) + } +} +impl NamedExpr { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + target: None, + value: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct BinOpExpr { + left: Option, + op: Option, + right: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl BinOpExpr { + #[getter] + fn left(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.left { + return Ok(value.clone_ref(this.py())); + } + Self::load_left(this) + } + #[getter] + fn op(&self, py: Python<'_>) -> PyResult { + Ok(self.op.as_ref().unwrap().clone_ref(py)) + } + #[getter] + fn right(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.right { + return Ok(value.clone_ref(this.py())); + } + Self::load_right(this) + } + fn load_left(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = expr_to_python(py, &locator, &ast.left, this.projection)?; + cache_value(&mut this.left, py, value) + } + fn load_right(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = expr_to_python(py, &locator, &ast.right, this.projection)?; + cache_value(&mut this.right, py, value) + } +} +impl BinOpExpr { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + op: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + left: None, + op: Some(op), + right: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + op: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, op, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct UnaryOpExpr { + op: Option, + operand: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl UnaryOpExpr { + #[getter] + fn op(&self, py: Python<'_>) -> PyResult { + Ok(self.op.as_ref().unwrap().clone_ref(py)) + } + #[getter] + fn operand(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.operand { + return Ok(value.clone_ref(this.py())); + } + Self::load_operand(this) + } + fn load_operand(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = expr_to_python(py, &locator, &ast.operand, this.projection)?; + cache_value(&mut this.operand, py, value) + } +} +impl UnaryOpExpr { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + op: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + op: Some(op), + operand: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + op: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, op, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct LambdaExpr { + parameters: Option, + body: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl LambdaExpr { + #[getter] + fn parameters(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.parameters { + return Ok(value.clone_ref(this.py())); + } + Self::load_parameters(this) + } + #[getter] + fn body(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.body { + return Ok(value.clone_ref(this.py())); + } + Self::load_body(this) + } + fn load_parameters(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_option(py, ast.parameters.as_ref(), |value| { + node_to_python( + py, + &locator, + AnyNodeRef::from(value.as_ref()), + this.projection, + ) + })?; + cache_value(&mut this.parameters, py, value) + } + fn load_body(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = expr_to_python(py, &locator, &ast.body, this.projection)?; + cache_value(&mut this.body, py, value) + } +} +impl LambdaExpr { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + parameters: None, + body: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct IfExpr { + test: Option, + body: Option, + orelse: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl IfExpr { + #[getter] + fn test(&self, py: Python<'_>) -> PyResult { + Ok(self.test.as_ref().unwrap().clone_ref(py)) + } + #[getter] + fn body(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.body { + return Ok(value.clone_ref(this.py())); + } + Self::load_body(this) + } + #[getter] + fn orelse(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.orelse { + return Ok(value.clone_ref(this.py())); + } + Self::load_orelse(this) + } + fn load_body(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = expr_to_python(py, &locator, &ast.body, this.projection)?; + cache_value(&mut this.body, py, value) + } + fn load_orelse(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = expr_to_python(py, &locator, &ast.orelse, this.projection)?; + cache_value(&mut this.orelse, py, value) + } +} +impl IfExpr { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + test: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + test: Some(test), + body: None, + orelse: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + test: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, test, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct DictExpr { + items: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl DictExpr { + #[getter] + fn items(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.items { + return Ok(value.clone_ref(this.py())); + } + Self::load_items(this) + } + fn load_items(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.items.iter(), |item| { + let key = map_option(py, item.key.as_ref(), |key| { + expr_to_python(py, &locator, key, this.projection) + })?; + let value = expr_to_python(py, &locator, &item.value, this.projection)?; + PyTuple::new(py, [key, value]).map(|tuple| tuple.into_any().unbind()) + })?; + cache_value(&mut this.items, py, value) + } +} +impl DictExpr { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + items: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct SetExpr { + elts: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl SetExpr { + #[getter] + fn elts(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.elts { + return Ok(value.clone_ref(this.py())); + } + Self::load_elts(this) + } + fn load_elts(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.elts.iter(), |value| { + expr_to_python(py, &locator, value, this.projection) + })?; + cache_value(&mut this.elts, py, value) + } +} +impl SetExpr { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + elts: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct ListCompExpr { + elt: Option, + generators: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl ListCompExpr { + #[getter] + fn elt(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.elt { + return Ok(value.clone_ref(this.py())); + } + Self::load_elt(this) + } + #[getter] + fn generators(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.generators { + return Ok(value.clone_ref(this.py())); + } + Self::load_generators(this) + } + fn load_elt(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = expr_to_python(py, &locator, &ast.elt, this.projection)?; + cache_value(&mut this.elt, py, value) + } + fn load_generators(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.generators.iter(), |value| { + node_to_python(py, &locator, AnyNodeRef::from(value), this.projection) + })?; + cache_value(&mut this.generators, py, value) + } +} +impl ListCompExpr { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + elt: None, + generators: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct SetCompExpr { + elt: Option, + generators: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl SetCompExpr { + #[getter] + fn elt(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.elt { + return Ok(value.clone_ref(this.py())); + } + Self::load_elt(this) + } + #[getter] + fn generators(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.generators { + return Ok(value.clone_ref(this.py())); + } + Self::load_generators(this) + } + fn load_elt(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = expr_to_python(py, &locator, &ast.elt, this.projection)?; + cache_value(&mut this.elt, py, value) + } + fn load_generators(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.generators.iter(), |value| { + node_to_python(py, &locator, AnyNodeRef::from(value), this.projection) + })?; + cache_value(&mut this.generators, py, value) + } +} +impl SetCompExpr { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + elt: None, + generators: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct DictCompExpr { + key: Option, + value: Option, + generators: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl DictCompExpr { + #[getter] + fn key(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.key { + return Ok(value.clone_ref(this.py())); + } + Self::load_key(this) + } + #[getter] + fn value(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.value { + return Ok(value.clone_ref(this.py())); + } + Self::load_value(this) + } + #[getter] + fn generators(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.generators { + return Ok(value.clone_ref(this.py())); + } + Self::load_generators(this) + } + fn load_key(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = expr_to_python(py, &locator, &ast.key, this.projection)?; + cache_value(&mut this.key, py, value) + } + fn load_value(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = expr_to_python(py, &locator, &ast.value, this.projection)?; + cache_value(&mut this.value, py, value) + } + fn load_generators(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.generators.iter(), |value| { + node_to_python(py, &locator, AnyNodeRef::from(value), this.projection) + })?; + cache_value(&mut this.generators, py, value) + } +} +impl DictCompExpr { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + key: None, + value: None, + generators: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct GeneratorExpr { + elt: Option, + generators: Option, + parenthesized: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl GeneratorExpr { + #[getter] + fn elt(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.elt { + return Ok(value.clone_ref(this.py())); + } + Self::load_elt(this) + } + #[getter] + fn generators(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.generators { + return Ok(value.clone_ref(this.py())); + } + Self::load_generators(this) + } + #[getter] + fn parenthesized(&self, py: Python<'_>) -> PyResult { + Ok(self.parenthesized.as_ref().unwrap().clone_ref(py)) + } + fn load_elt(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = expr_to_python(py, &locator, &ast.elt, this.projection)?; + cache_value(&mut this.elt, py, value) + } + fn load_generators(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.generators.iter(), |value| { + node_to_python(py, &locator, AnyNodeRef::from(value), this.projection) + })?; + cache_value(&mut this.generators, py, value) + } +} +impl GeneratorExpr { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + parenthesized: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + elt: None, + generators: None, + parenthesized: Some(parenthesized), + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + parenthesized: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, + kind, + span, + text, + repr_value, + node_id, + store, + parenthesized, + locator, + projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct AwaitExpr { + value: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl AwaitExpr { + #[getter] + fn value(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.value { + return Ok(value.clone_ref(this.py())); + } + Self::load_value(this) + } + fn load_value(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = expr_to_python(py, &locator, &ast.value, this.projection)?; + cache_value(&mut this.value, py, value) + } +} +impl AwaitExpr { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + value: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct YieldExpr { + value: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl YieldExpr { + #[getter] + fn value(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.value { + return Ok(value.clone_ref(this.py())); + } + Self::load_value(this) + } + fn load_value(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_option(py, ast.value.as_ref(), |value| { + expr_to_python(py, &locator, value, this.projection) + })?; + cache_value(&mut this.value, py, value) + } +} +impl YieldExpr { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + value: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct YieldFromExpr { + value: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl YieldFromExpr { + #[getter] + fn value(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.value { + return Ok(value.clone_ref(this.py())); + } + Self::load_value(this) + } + fn load_value(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = expr_to_python(py, &locator, &ast.value, this.projection)?; + cache_value(&mut this.value, py, value) + } +} +impl YieldFromExpr { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + value: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct CompareExpr { + left: Option, + ops: Option, + comparators: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl CompareExpr { + #[getter] + fn left(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.left { + return Ok(value.clone_ref(this.py())); + } + Self::load_left(this) + } + #[getter] + fn ops(&self, py: Python<'_>) -> PyResult { + Ok(self.ops.as_ref().unwrap().clone_ref(py)) + } + #[getter] + fn comparators(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.comparators { + return Ok(value.clone_ref(this.py())); + } + Self::load_comparators(this) + } + fn load_left(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = expr_to_python(py, &locator, &ast.left, this.projection)?; + cache_value(&mut this.left, py, value) + } + fn load_comparators(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.comparators.iter(), |value| { + expr_to_python(py, &locator, value, this.projection) + })?; + cache_value(&mut this.comparators, py, value) + } +} +impl CompareExpr { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + ops: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + left: None, + ops: Some(ops), + comparators: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + ops: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, ops, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct CallExpr { + func: Option, + arguments: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, + callee: Option, + function_text: Option, + function_kind: Option, +} +#[pymethods] +impl CallExpr { + #[getter] + fn func(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.func { + return Ok(value.clone_ref(this.py())); + } + Self::load_func(this) + } + #[getter] + fn arguments(&self, py: Python<'_>) -> PyResult { + Ok(self.arguments.as_ref().unwrap().clone_ref(py)) + } + fn load_func(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = expr_to_python(py, &locator, &ast.func, this.projection)?; + cache_value(&mut this.func, py, value) + } + #[getter] + fn callee(&self, py: Python<'_>) -> PyResult { + Ok(optional_str(py, self.callee.as_deref())) + } + #[getter] + fn function_text(&self, py: Python<'_>) -> PyResult { + Ok(optional_str(py, self.function_text.as_deref())) + } + #[getter] + fn function_kind(&self, py: Python<'_>) -> PyResult { + Ok(optional_str(py, self.function_kind.as_deref())) + } +} +impl CallExpr { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + arguments: PyObject, + callee: Option, + function_text: Option, + function_kind: Option, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + func: None, + arguments: Some(arguments), + locator, + projection, + callee, + function_text, + function_kind, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + arguments: PyObject, + callee: Option, + function_text: Option, + function_kind: Option, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, + kind, + span, + text, + repr_value, + node_id, + store, + arguments, + callee, + function_text, + function_kind, + locator, + projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct FStringExpr { + value: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl FStringExpr { + #[getter] + fn value(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.value { + return Ok(value.clone_ref(this.py())); + } + Self::load_value(this) + } + fn load_value(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let ast = get_ast::(&store, node_id)?; + let value = py_string(py, &format!("{value:?}", value = ast.value)); + cache_value(&mut this.value, py, value) + } +} +impl FStringExpr { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + value: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct TStringExpr { + value: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl TStringExpr { + #[getter] + fn value(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.value { + return Ok(value.clone_ref(this.py())); + } + Self::load_value(this) + } + fn load_value(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let ast = get_ast::(&store, node_id)?; + let value = py_string(py, &format!("{value:?}", value = ast.value)); + cache_value(&mut this.value, py, value) + } +} +impl TStringExpr { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + value: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct StringLiteralExpr { + value: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl StringLiteralExpr { + #[getter] + fn value(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.value { + return Ok(value.clone_ref(this.py())); + } + Self::load_value(this) + } + fn load_value(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let ast = get_ast::(&store, node_id)?; + let value = py_string(py, &format!("{value:?}", value = ast.value)); + cache_value(&mut this.value, py, value) + } +} +impl StringLiteralExpr { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + value: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct BytesLiteralExpr { + value: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl BytesLiteralExpr { + #[getter] + fn value(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.value { + return Ok(value.clone_ref(this.py())); + } + Self::load_value(this) + } + fn load_value(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let ast = get_ast::(&store, node_id)?; + let value = py_string(py, &format!("{value:?}", value = ast.value)); + cache_value(&mut this.value, py, value) + } +} +impl BytesLiteralExpr { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + value: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct NumberLiteralExpr { + value: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl NumberLiteralExpr { + #[getter] + fn value(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.value { + return Ok(value.clone_ref(this.py())); + } + Self::load_value(this) + } + fn load_value(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let value = py_none(py); + cache_value(&mut this.value, py, value) + } +} +impl NumberLiteralExpr { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + value: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct BooleanLiteralExpr { + value: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl BooleanLiteralExpr { + #[getter] + fn value(&self, py: Python<'_>) -> PyResult { + Ok(self.value.as_ref().unwrap().clone_ref(py)) + } +} +impl BooleanLiteralExpr { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + value: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + value: Some(value), + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + value: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, value, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct NoneLiteralExpr { + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl NoneLiteralExpr {} +impl NoneLiteralExpr { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct EllipsisLiteralExpr { + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl EllipsisLiteralExpr {} +impl EllipsisLiteralExpr { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct AttributeExpr { + value: Option, + attr: Option, + ctx: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl AttributeExpr { + #[getter] + fn value(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.value { + return Ok(value.clone_ref(this.py())); + } + Self::load_value(this) + } + #[getter] + fn attr(&self, py: Python<'_>) -> PyResult { + Ok(self.attr.as_ref().unwrap().clone_ref(py)) + } + #[getter] + fn ctx(&self, py: Python<'_>) -> PyResult { + Ok(self.ctx.as_ref().unwrap().clone_ref(py)) + } + fn load_value(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = expr_to_python(py, &locator, &ast.value, this.projection)?; + cache_value(&mut this.value, py, value) + } +} +impl AttributeExpr { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + attr: PyObject, + ctx: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + value: None, + attr: Some(attr), + ctx: Some(ctx), + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + attr: PyObject, + ctx: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, attr, ctx, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct SubscriptExpr { + value: Option, + slice: Option, + ctx: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl SubscriptExpr { + #[getter] + fn value(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.value { + return Ok(value.clone_ref(this.py())); + } + Self::load_value(this) + } + #[getter] + fn slice(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.slice { + return Ok(value.clone_ref(this.py())); + } + Self::load_slice(this) + } + #[getter] + fn ctx(&self, py: Python<'_>) -> PyResult { + Ok(self.ctx.as_ref().unwrap().clone_ref(py)) + } + fn load_value(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = expr_to_python(py, &locator, &ast.value, this.projection)?; + cache_value(&mut this.value, py, value) + } + fn load_slice(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = expr_to_python(py, &locator, &ast.slice, this.projection)?; + cache_value(&mut this.slice, py, value) + } +} +impl SubscriptExpr { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + ctx: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + value: None, + slice: None, + ctx: Some(ctx), + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + ctx: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, ctx, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct StarredExpr { + value: Option, + ctx: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl StarredExpr { + #[getter] + fn value(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.value { + return Ok(value.clone_ref(this.py())); + } + Self::load_value(this) + } + #[getter] + fn ctx(&self, py: Python<'_>) -> PyResult { + Ok(self.ctx.as_ref().unwrap().clone_ref(py)) + } + fn load_value(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = expr_to_python(py, &locator, &ast.value, this.projection)?; + cache_value(&mut this.value, py, value) + } +} +impl StarredExpr { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + ctx: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + value: None, + ctx: Some(ctx), + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + ctx: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, ctx, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct NameExpr { + id: Option, + ctx: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl NameExpr { + #[getter] + fn id(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.id { + return Ok(value.clone_ref(this.py())); + } + Self::load_id(this) + } + #[getter] + fn ctx(&self, py: Python<'_>) -> PyResult { + Ok(self.ctx.as_ref().unwrap().clone_ref(py)) + } + fn load_id(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let ast = get_ast::(&store, node_id)?; + let value = py_string(py, &format!("{value:?}", value = ast.id)); + cache_value(&mut this.id, py, value) + } +} +impl NameExpr { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + ctx: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + id: None, + ctx: Some(ctx), + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + ctx: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, ctx, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct ListExpr { + elts: Option, + ctx: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl ListExpr { + #[getter] + fn elts(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.elts { + return Ok(value.clone_ref(this.py())); + } + Self::load_elts(this) + } + #[getter] + fn ctx(&self, py: Python<'_>) -> PyResult { + Ok(self.ctx.as_ref().unwrap().clone_ref(py)) + } + fn load_elts(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.elts.iter(), |value| { + expr_to_python(py, &locator, value, this.projection) + })?; + cache_value(&mut this.elts, py, value) + } +} +impl ListExpr { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + ctx: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + elts: None, + ctx: Some(ctx), + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + ctx: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, ctx, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct TupleExpr { + elts: Option, + ctx: Option, + parenthesized: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl TupleExpr { + #[getter] + fn elts(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.elts { + return Ok(value.clone_ref(this.py())); + } + Self::load_elts(this) + } + #[getter] + fn ctx(&self, py: Python<'_>) -> PyResult { + Ok(self.ctx.as_ref().unwrap().clone_ref(py)) + } + #[getter] + fn parenthesized(&self, py: Python<'_>) -> PyResult { + Ok(self.parenthesized.as_ref().unwrap().clone_ref(py)) + } + fn load_elts(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.elts.iter(), |value| { + expr_to_python(py, &locator, value, this.projection) + })?; + cache_value(&mut this.elts, py, value) + } +} +impl TupleExpr { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + ctx: PyObject, + parenthesized: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + elts: None, + ctx: Some(ctx), + parenthesized: Some(parenthesized), + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + ctx: PyObject, + parenthesized: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, + kind, + span, + text, + repr_value, + node_id, + store, + ctx, + parenthesized, + locator, + projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct SliceExpr { + lower: Option, + upper: Option, + step: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl SliceExpr { + #[getter] + fn lower(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.lower { + return Ok(value.clone_ref(this.py())); + } + Self::load_lower(this) + } + #[getter] + fn upper(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.upper { + return Ok(value.clone_ref(this.py())); + } + Self::load_upper(this) + } + #[getter] + fn step(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.step { + return Ok(value.clone_ref(this.py())); + } + Self::load_step(this) + } + fn load_lower(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_option(py, ast.lower.as_ref(), |value| { + expr_to_python(py, &locator, value, this.projection) + })?; + cache_value(&mut this.lower, py, value) + } + fn load_upper(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_option(py, ast.upper.as_ref(), |value| { + expr_to_python(py, &locator, value, this.projection) + })?; + cache_value(&mut this.upper, py, value) + } + fn load_step(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_option(py, ast.step.as_ref(), |value| { + expr_to_python(py, &locator, value, this.projection) + })?; + cache_value(&mut this.step, py, value) + } +} +impl SliceExpr { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + lower: None, + upper: None, + step: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct IpyEscapeCommandExpr { + kind: Option, + value: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl IpyEscapeCommandExpr { + #[getter] + fn kind(&self, py: Python<'_>) -> PyResult { + Ok(self.kind.as_ref().unwrap().clone_ref(py)) + } + #[getter] + fn value(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.value { + return Ok(value.clone_ref(this.py())); + } + Self::load_value(this) + } + fn load_value(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let ast = get_ast::(&store, node_id)?; + let value = py_string(py, &format!("{value:?}", value = ast.value)); + cache_value(&mut this.value, py, value) + } +} +impl IpyEscapeCommandExpr { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + kind_field: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + kind: Some(kind_field), + value: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + kind_field: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, kind_field, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct ExceptHandler { + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl ExceptHandler {} +impl ExceptHandler { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct InterpolatedElement { + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl InterpolatedElement {} +impl InterpolatedElement { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct InterpolatedStringLiteralElement { + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl InterpolatedStringLiteralElement {} +impl InterpolatedStringLiteralElement { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct MatchValuePattern { + value: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl MatchValuePattern { + #[getter] + fn value(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.value { + return Ok(value.clone_ref(this.py())); + } + Self::load_value(this) + } + fn load_value(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = expr_to_python(py, &locator, &ast.value, this.projection)?; + cache_value(&mut this.value, py, value) + } +} +impl MatchValuePattern { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + value: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct MatchSingletonPattern { + value: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl MatchSingletonPattern { + #[getter] + fn value(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.value { + return Ok(value.clone_ref(this.py())); + } + Self::load_value(this) + } + fn load_value(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let value = py_none(py); + cache_value(&mut this.value, py, value) + } +} +impl MatchSingletonPattern { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + value: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct MatchSequencePattern { + patterns: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl MatchSequencePattern { + #[getter] + fn patterns(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.patterns { + return Ok(value.clone_ref(this.py())); + } + Self::load_patterns(this) + } + fn load_patterns(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.patterns.iter(), |value| { + node_to_python(py, &locator, AnyNodeRef::from(value), this.projection) + })?; + cache_value(&mut this.patterns, py, value) + } +} +impl MatchSequencePattern { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + patterns: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct MatchMappingPattern { + keys: Option, + patterns: Option, + rest: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl MatchMappingPattern { + #[getter] + fn keys(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.keys { + return Ok(value.clone_ref(this.py())); + } + Self::load_keys(this) + } + #[getter] + fn patterns(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.patterns { + return Ok(value.clone_ref(this.py())); + } + Self::load_patterns(this) + } + #[getter] + fn rest(&self, py: Python<'_>) -> PyResult { + Ok(self.rest.as_ref().unwrap().clone_ref(py)) + } + fn load_keys(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.keys.iter(), |value| { + expr_to_python(py, &locator, value, this.projection) + })?; + cache_value(&mut this.keys, py, value) + } + fn load_patterns(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.patterns.iter(), |value| { + node_to_python(py, &locator, AnyNodeRef::from(value), this.projection) + })?; + cache_value(&mut this.patterns, py, value) + } +} +impl MatchMappingPattern { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + rest: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + keys: None, + patterns: None, + rest: Some(rest), + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + rest: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, rest, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct MatchClassPattern { + cls: Option, + arguments: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl MatchClassPattern { + #[getter] + fn cls(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.cls { + return Ok(value.clone_ref(this.py())); + } + Self::load_cls(this) + } + #[getter] + fn arguments(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.arguments { + return Ok(value.clone_ref(this.py())); + } + Self::load_arguments(this) + } + fn load_cls(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = expr_to_python(py, &locator, &ast.cls, this.projection)?; + cache_value(&mut this.cls, py, value) + } + fn load_arguments(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = node_to_python( + py, + &locator, + AnyNodeRef::from(&ast.arguments), + this.projection, + )?; + cache_value(&mut this.arguments, py, value) + } +} +impl MatchClassPattern { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + cls: None, + arguments: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct MatchStarPattern { + name: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl MatchStarPattern { + #[getter] + fn name(&self, py: Python<'_>) -> PyResult { + Ok(self.name.as_ref().unwrap().clone_ref(py)) + } +} +impl MatchStarPattern { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + name: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + name: Some(name), + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + name: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, name, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct MatchAsPattern { + pattern: Option, + name: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl MatchAsPattern { + #[getter] + fn pattern(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.pattern { + return Ok(value.clone_ref(this.py())); + } + Self::load_pattern(this) + } + #[getter] + fn name(&self, py: Python<'_>) -> PyResult { + Ok(self.name.as_ref().unwrap().clone_ref(py)) + } + fn load_pattern(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_option(py, ast.pattern.as_ref(), |value| { + node_to_python( + py, + &locator, + AnyNodeRef::from(value.as_ref()), + this.projection, + ) + })?; + cache_value(&mut this.pattern, py, value) + } +} +impl MatchAsPattern { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + name: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + pattern: None, + name: Some(name), + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + name: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, name, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct MatchOrPattern { + patterns: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl MatchOrPattern { + #[getter] + fn patterns(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.patterns { + return Ok(value.clone_ref(this.py())); + } + Self::load_patterns(this) + } + fn load_patterns(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.patterns.iter(), |value| { + node_to_python(py, &locator, AnyNodeRef::from(value), this.projection) + })?; + cache_value(&mut this.patterns, py, value) + } +} +impl MatchOrPattern { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + patterns: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct TypeVar { + name: Option, + bound: Option, + default: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl TypeVar { + #[getter] + fn name(&self, py: Python<'_>) -> PyResult { + Ok(self.name.as_ref().unwrap().clone_ref(py)) + } + #[getter] + fn bound(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.bound { + return Ok(value.clone_ref(this.py())); + } + Self::load_bound(this) + } + #[getter] + fn default(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.default { + return Ok(value.clone_ref(this.py())); + } + Self::load_default(this) + } + fn load_bound(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_option(py, ast.bound.as_ref(), |value| { + expr_to_python(py, &locator, value, this.projection) + })?; + cache_value(&mut this.bound, py, value) + } + fn load_default(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_option(py, ast.default.as_ref(), |value| { + expr_to_python(py, &locator, value, this.projection) + })?; + cache_value(&mut this.default, py, value) + } +} +impl TypeVar { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + name: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + name: Some(name), + bound: None, + default: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + name: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, name, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct TypeVarTuple { + name: Option, + default: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl TypeVarTuple { + #[getter] + fn name(&self, py: Python<'_>) -> PyResult { + Ok(self.name.as_ref().unwrap().clone_ref(py)) + } + #[getter] + fn default(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.default { + return Ok(value.clone_ref(this.py())); + } + Self::load_default(this) + } + fn load_default(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_option(py, ast.default.as_ref(), |value| { + expr_to_python(py, &locator, value, this.projection) + })?; + cache_value(&mut this.default, py, value) + } +} +impl TypeVarTuple { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + name: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + name: Some(name), + default: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + name: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, name, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct ParamSpec { + name: Option, + default: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl ParamSpec { + #[getter] + fn name(&self, py: Python<'_>) -> PyResult { + Ok(self.name.as_ref().unwrap().clone_ref(py)) + } + #[getter] + fn default(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.default { + return Ok(value.clone_ref(this.py())); + } + Self::load_default(this) + } + fn load_default(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_option(py, ast.default.as_ref(), |value| { + expr_to_python(py, &locator, value, this.projection) + })?; + cache_value(&mut this.default, py, value) + } +} +impl ParamSpec { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + name: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + name: Some(name), + default: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + name: PyObject, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, name, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct InterpolatedStringFormatSpec { + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl InterpolatedStringFormatSpec {} +impl InterpolatedStringFormatSpec { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct PatternArguments { + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl PatternArguments {} +impl PatternArguments { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct PatternKeyword { + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl PatternKeyword {} +impl PatternKeyword { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct Comprehension { + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl Comprehension {} +impl Comprehension { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct Parameters { + posonlyargs: Option, + args: Option, + vararg: Option, + kwonlyargs: Option, + kwarg: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl Parameters { + #[getter] + fn posonlyargs(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.posonlyargs { + return Ok(value.clone_ref(this.py())); + } + Self::load_posonlyargs(this) + } + #[getter] + fn args(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.args { + return Ok(value.clone_ref(this.py())); + } + Self::load_args(this) + } + #[getter] + fn vararg(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.vararg { + return Ok(value.clone_ref(this.py())); + } + Self::load_vararg(this) + } + #[getter] + fn kwonlyargs(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.kwonlyargs { + return Ok(value.clone_ref(this.py())); + } + Self::load_kwonlyargs(this) + } + #[getter] + fn kwarg(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.kwarg { + return Ok(value.clone_ref(this.py())); + } + Self::load_kwarg(this) + } + fn load_posonlyargs(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.posonlyargs.iter(), |value| { + node_to_python(py, &locator, AnyNodeRef::from(value), this.projection) + })?; + cache_value(&mut this.posonlyargs, py, value) + } + fn load_args(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.args.iter(), |value| { + node_to_python(py, &locator, AnyNodeRef::from(value), this.projection) + })?; + cache_value(&mut this.args, py, value) + } + fn load_vararg(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_option(py, ast.vararg.as_ref(), |value| { + node_to_python( + py, + &locator, + AnyNodeRef::from(value.as_ref()), + this.projection, + ) + })?; + cache_value(&mut this.vararg, py, value) + } + fn load_kwonlyargs(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.kwonlyargs.iter(), |value| { + node_to_python(py, &locator, AnyNodeRef::from(value), this.projection) + })?; + cache_value(&mut this.kwonlyargs, py, value) + } + fn load_kwarg(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_option(py, ast.kwarg.as_ref(), |value| { + node_to_python( + py, + &locator, + AnyNodeRef::from(value.as_ref()), + this.projection, + ) + })?; + cache_value(&mut this.kwarg, py, value) + } +} +impl Parameters { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + posonlyargs: None, + args: None, + vararg: None, + kwonlyargs: None, + kwarg: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct Parameter { + name: Option, + annotation: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl Parameter { + #[getter] + fn name(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.name { + return Ok(value.clone_ref(this.py())); + } + Self::load_name(this) + } + #[getter] + fn annotation(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.annotation { + return Ok(value.clone_ref(this.py())); + } + Self::load_annotation(this) + } + fn load_name(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let ast = get_ast::(&store, node_id)?; + let value = py_string(py, ast.name.as_str()); + cache_value(&mut this.name, py, value) + } + fn load_annotation(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_option(py, ast.annotation.as_ref(), |value| { + expr_to_python(py, &locator, value, this.projection) + })?; + cache_value(&mut this.annotation, py, value) + } +} +impl Parameter { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + name: None, + annotation: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct ParameterWithDefault { + parameter: Option, + default: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl ParameterWithDefault { + #[getter] + fn parameter(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.parameter { + return Ok(value.clone_ref(this.py())); + } + Self::load_parameter(this) + } + #[getter] + fn default(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.default { + return Ok(value.clone_ref(this.py())); + } + Self::load_default(this) + } + fn load_parameter(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = node_to_python( + py, + &locator, + AnyNodeRef::from(&ast.parameter), + this.projection, + )?; + cache_value(&mut this.parameter, py, value) + } + fn load_default(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_option(py, ast.default.as_ref(), |value| { + expr_to_python(py, &locator, value, this.projection) + })?; + cache_value(&mut this.default, py, value) + } +} +impl ParameterWithDefault { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + parameter: None, + default: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct Alias { + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl Alias {} +impl Alias { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct WithItem { + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl WithItem {} +impl WithItem { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct MatchCase { + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl MatchCase {} +impl MatchCase { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct Decorator { + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl Decorator {} +impl Decorator { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct ElifElseClause { + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl ElifElseClause {} +impl ElifElseClause { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct TypeParams { + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl TypeParams {} +impl TypeParams { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct FString { + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl FString {} +impl FString { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct TString { + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl TString {} +impl TString { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct StringLiteral { + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl StringLiteral {} +impl StringLiteral { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct BytesLiteral { + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl BytesLiteral {} +impl BytesLiteral { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct Identifier { + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl Identifier {} +impl Identifier { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct Arguments { + args: Option, + keywords: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl Arguments { + #[getter] + fn args(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.args { + return Ok(value.clone_ref(this.py())); + } + Self::load_args(this) + } + #[getter] + fn keywords(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.keywords { + return Ok(value.clone_ref(this.py())); + } + Self::load_keywords(this) + } + fn load_args(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.args.iter(), |value| { + expr_to_python(py, &locator, value, this.projection) + })?; + cache_value(&mut this.args, py, value) + } + fn load_keywords(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = map_tuple(py, ast.keywords.iter(), |value| { + node_to_python(py, &locator, AnyNodeRef::from(value), this.projection) + })?; + cache_value(&mut this.keywords, py, value) + } +} +impl Arguments { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + args: None, + keywords: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} +#[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] +pub(crate) struct Keyword { + arg: Option, + value: Option, + #[allow(dead_code)] + locator: SourceFileHandle, + #[allow(dead_code)] + projection: ProjectionTypesRef, +} +#[pymethods] +impl Keyword { + #[getter] + fn arg(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.arg { + return Ok(value.clone_ref(this.py())); + } + Self::load_arg(this) + } + #[getter] + fn value(this: PyRefMut<'_, Self>) -> PyResult { + if let Some(value) = &this.value { + return Ok(value.clone_ref(this.py())); + } + Self::load_value(this) + } + fn load_arg(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let ast = get_ast::(&store, node_id)?; + let value = optional_str( + py, + ast.arg.as_ref().map(ruff_python_ast::Identifier::as_str), + ); + cache_value(&mut this.arg, py, value) + } + fn load_value(mut this: PyRefMut<'_, Self>) -> PyResult { + let py = this.py(); + let (node_id, store) = { + let super_ = this.as_super(); + (super_.node_id(), super_.store().clone()) + }; + let locator = this.locator.locator(); + let ast = get_ast::(&store, node_id)?; + let value = expr_to_python(py, &locator, &ast.value, this.projection)?; + cache_value(&mut this.value, py, value) + } +} +impl Keyword { + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> (Self, bindings::Node) { + ( + Self { + arg: None, + value: None, + locator, + projection, + }, + bindings::Node::new_inner(py, kind, span, text, repr_value, node_id, store), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, + kind: String, + span: PyObject, + text: String, + repr_value: String, + node_id: u32, + store: AstStoreHandle, + locator: SourceFileHandle, + projection: ProjectionTypesRef, + ) -> PyResult { + let (this, node) = Self::init_parts( + py, kind, span, text, repr_value, node_id, store, locator, projection, + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + } +} diff --git a/crates/ruff_linter/src/external/ast/python/projection.rs b/crates/ruff_linter/src/external/ast/python/projection.rs new file mode 100644 index 0000000000000..d211733cdfa5c --- /dev/null +++ b/crates/ruff_linter/src/external/ast/python/projection.rs @@ -0,0 +1,2593 @@ +// This file is auto-generated by crates/ruff_python_ast/generate.py + +use pyo3::PyObject; +use pyo3::prelude::*; +use pyo3::types::PyTuple; + +use ruff_python_ast::AnyNodeRef; +use ruff_text_size::Ranged; + +use super::generated::{ + Alias, AnnAssignStmt, Arguments, AssertStmt, AssignStmt, AttributeExpr, AugAssignStmt, + AwaitExpr, BinOpExpr, BoolOpExpr, BooleanLiteralExpr, BreakStmt, BytesLiteral, + BytesLiteralExpr, CallExpr, ClassDefStmt, CompareExpr, Comprehension, ContinueStmt, Decorator, + DeleteStmt, DictCompExpr, DictExpr, ElifElseClause, EllipsisLiteralExpr, ExceptHandler, + ExprStmt, Expression, FString, FStringExpr, ForStmt, FunctionDefStmt, GeneratorExpr, + GlobalStmt, Identifier, IfExpr, IfStmt, ImportFromStmt, ImportStmt, InterpolatedElement, + InterpolatedStringFormatSpec, InterpolatedStringLiteralElement, IpyEscapeCommandExpr, + IpyEscapeCommandStmt, Keyword, LambdaExpr, ListCompExpr, ListExpr, MatchAsPattern, MatchCase, + MatchClassPattern, MatchMappingPattern, MatchOrPattern, MatchSequencePattern, + MatchSingletonPattern, MatchStarPattern, MatchStmt, MatchValuePattern, Module, NameExpr, + NamedExpr, NoneLiteralExpr, NonlocalStmt, NumberLiteralExpr, ParamSpec, Parameter, + ParameterWithDefault, Parameters, PassStmt, PatternArguments, PatternKeyword, RaiseStmt, + ReturnStmt, SetCompExpr, SetExpr, SliceExpr, StarredExpr, StringLiteral, StringLiteralExpr, + SubscriptExpr, TString, TStringExpr, TryStmt, TupleExpr, TypeAliasStmt, TypeParams, TypeVar, + TypeVarTuple, UnaryOpExpr, WhileStmt, WithItem, WithStmt, YieldExpr, YieldFromExpr, +}; +use super::{ + ProjectionTypesRef, expr_to_python, extract_callee, node_to_python, optional_str, py_bool, + py_string, source::SourceFileHandle, span_tuple, +}; +use crate::Locator; +use crate::external::ast::target::{ExprKind, StmtKind}; +#[derive(Clone, Copy, Debug)] +#[allow(dead_code)] +pub(crate) enum ProjectionMode<'a> { + Generic, + Typed, + Fields(&'a [&'a str]), +} + +impl ProjectionMode<'_> { + pub(crate) const fn wants_typed(&self) -> bool { + !matches!(self, ProjectionMode::Generic) + } +} +#[allow(unreachable_patterns)] +pub(crate) fn project_typed_node( + py: Python<'_>, + locator: &Locator<'_>, + node: AnyNodeRef<'_>, + mode: ProjectionMode<'_>, + types: ProjectionTypesRef, +) -> PyResult> { + if !mode.wants_typed() { + return Ok(None); + } + + match node { + AnyNodeRef::ModModule(node) => project_mod_module(py, locator, node, types).map(Some), + AnyNodeRef::ModExpression(node) => { + project_mod_expression(py, locator, node, types).map(Some) + } + AnyNodeRef::StmtFunctionDef(stmt) => { + project_stmt_function_def(py, locator, stmt, types).map(Some) + } + AnyNodeRef::StmtClassDef(stmt) => { + project_stmt_class_def(py, locator, stmt, types).map(Some) + } + AnyNodeRef::StmtReturn(stmt) => project_stmt_return(py, locator, stmt, types).map(Some), + AnyNodeRef::StmtDelete(stmt) => project_stmt_delete(py, locator, stmt, types).map(Some), + AnyNodeRef::StmtTypeAlias(stmt) => { + project_stmt_type_alias(py, locator, stmt, types).map(Some) + } + AnyNodeRef::StmtAssign(stmt) => project_stmt_assign(py, locator, stmt, types).map(Some), + AnyNodeRef::StmtAugAssign(stmt) => { + project_stmt_aug_assign(py, locator, stmt, types).map(Some) + } + AnyNodeRef::StmtAnnAssign(stmt) => { + project_stmt_ann_assign(py, locator, stmt, types).map(Some) + } + AnyNodeRef::StmtFor(stmt) => project_stmt_for(py, locator, stmt, types).map(Some), + AnyNodeRef::StmtWhile(stmt) => project_stmt_while(py, locator, stmt, types).map(Some), + AnyNodeRef::StmtIf(stmt) => project_stmt_if(py, locator, stmt, types).map(Some), + AnyNodeRef::StmtWith(stmt) => project_stmt_with(py, locator, stmt, types).map(Some), + AnyNodeRef::StmtMatch(stmt) => project_stmt_match(py, locator, stmt, types).map(Some), + AnyNodeRef::StmtRaise(stmt) => project_stmt_raise(py, locator, stmt, types).map(Some), + AnyNodeRef::StmtTry(stmt) => project_stmt_try(py, locator, stmt, types).map(Some), + AnyNodeRef::StmtAssert(stmt) => project_stmt_assert(py, locator, stmt, types).map(Some), + AnyNodeRef::StmtImport(stmt) => project_stmt_import(py, locator, stmt, types).map(Some), + AnyNodeRef::StmtImportFrom(stmt) => { + project_stmt_import_from(py, locator, stmt, types).map(Some) + } + AnyNodeRef::StmtGlobal(stmt) => project_stmt_global(py, locator, stmt, types).map(Some), + AnyNodeRef::StmtNonlocal(stmt) => project_stmt_nonlocal(py, locator, stmt, types).map(Some), + AnyNodeRef::StmtExpr(stmt) => project_stmt_expr(py, locator, stmt, types).map(Some), + AnyNodeRef::StmtPass(stmt) => project_stmt_pass(py, locator, stmt, types).map(Some), + AnyNodeRef::StmtBreak(stmt) => project_stmt_break(py, locator, stmt, types).map(Some), + AnyNodeRef::StmtContinue(stmt) => project_stmt_continue(py, locator, stmt, types).map(Some), + AnyNodeRef::StmtIpyEscapeCommand(stmt) => { + project_stmt_ipy_escape_command(py, locator, stmt, types).map(Some) + } + AnyNodeRef::ExprBoolOp(expr) => project_expr_bool_op(py, locator, expr, types).map(Some), + AnyNodeRef::ExprNamed(expr) => project_expr_named(py, locator, expr, types).map(Some), + AnyNodeRef::ExprBinOp(expr) => project_expr_bin_op(py, locator, expr, types).map(Some), + AnyNodeRef::ExprUnaryOp(expr) => project_expr_unary_op(py, locator, expr, types).map(Some), + AnyNodeRef::ExprLambda(expr) => project_expr_lambda(py, locator, expr, types).map(Some), + AnyNodeRef::ExprIf(expr) => project_expr_if(py, locator, expr, types).map(Some), + AnyNodeRef::ExprDict(expr) => project_expr_dict(py, locator, expr, types).map(Some), + AnyNodeRef::ExprSet(expr) => project_expr_set(py, locator, expr, types).map(Some), + AnyNodeRef::ExprListComp(expr) => { + project_expr_list_comp(py, locator, expr, types).map(Some) + } + AnyNodeRef::ExprSetComp(expr) => project_expr_set_comp(py, locator, expr, types).map(Some), + AnyNodeRef::ExprDictComp(expr) => { + project_expr_dict_comp(py, locator, expr, types).map(Some) + } + AnyNodeRef::ExprGenerator(expr) => { + project_expr_generator(py, locator, expr, types).map(Some) + } + AnyNodeRef::ExprAwait(expr) => project_expr_await(py, locator, expr, types).map(Some), + AnyNodeRef::ExprYield(expr) => project_expr_yield(py, locator, expr, types).map(Some), + AnyNodeRef::ExprYieldFrom(expr) => { + project_expr_yield_from(py, locator, expr, types).map(Some) + } + AnyNodeRef::ExprCompare(expr) => project_expr_compare(py, locator, expr, types).map(Some), + AnyNodeRef::ExprCall(expr) => project_expr_call(py, locator, expr, types).map(Some), + AnyNodeRef::ExprFString(expr) => project_expr_f_string(py, locator, expr, types).map(Some), + AnyNodeRef::ExprTString(expr) => project_expr_t_string(py, locator, expr, types).map(Some), + AnyNodeRef::ExprStringLiteral(expr) => { + project_expr_string_literal(py, locator, expr, types).map(Some) + } + AnyNodeRef::ExprBytesLiteral(expr) => { + project_expr_bytes_literal(py, locator, expr, types).map(Some) + } + AnyNodeRef::ExprNumberLiteral(expr) => { + project_expr_number_literal(py, locator, expr, types).map(Some) + } + AnyNodeRef::ExprBooleanLiteral(expr) => { + project_expr_boolean_literal(py, locator, expr, types).map(Some) + } + AnyNodeRef::ExprNoneLiteral(expr) => { + project_expr_none_literal(py, locator, expr, types).map(Some) + } + AnyNodeRef::ExprEllipsisLiteral(expr) => { + project_expr_ellipsis_literal(py, locator, expr, types).map(Some) + } + AnyNodeRef::ExprAttribute(expr) => { + project_expr_attribute(py, locator, expr, types).map(Some) + } + AnyNodeRef::ExprSubscript(expr) => { + project_expr_subscript(py, locator, expr, types).map(Some) + } + AnyNodeRef::ExprStarred(expr) => project_expr_starred(py, locator, expr, types).map(Some), + AnyNodeRef::ExprName(expr) => project_expr_name(py, locator, expr, types).map(Some), + AnyNodeRef::ExprList(expr) => project_expr_list(py, locator, expr, types).map(Some), + AnyNodeRef::ExprTuple(expr) => project_expr_tuple(py, locator, expr, types).map(Some), + AnyNodeRef::ExprSlice(expr) => project_expr_slice(py, locator, expr, types).map(Some), + AnyNodeRef::ExprIpyEscapeCommand(expr) => { + project_expr_ipy_escape_command(py, locator, expr, types).map(Some) + } + AnyNodeRef::ExceptHandlerExceptHandler(node) => { + project_except_handler_except_handler(py, locator, node, types).map(Some) + } + AnyNodeRef::InterpolatedElement(node) => { + project_interpolated_element(py, locator, node, types).map(Some) + } + AnyNodeRef::InterpolatedStringLiteralElement(node) => { + project_interpolated_string_literal_element(py, locator, node, types).map(Some) + } + AnyNodeRef::PatternMatchValue(node) => { + project_pattern_match_value(py, locator, node, types).map(Some) + } + AnyNodeRef::PatternMatchSingleton(node) => { + project_pattern_match_singleton(py, locator, node, types).map(Some) + } + AnyNodeRef::PatternMatchSequence(node) => { + project_pattern_match_sequence(py, locator, node, types).map(Some) + } + AnyNodeRef::PatternMatchMapping(node) => { + project_pattern_match_mapping(py, locator, node, types).map(Some) + } + AnyNodeRef::PatternMatchClass(node) => { + project_pattern_match_class(py, locator, node, types).map(Some) + } + AnyNodeRef::PatternMatchStar(node) => { + project_pattern_match_star(py, locator, node, types).map(Some) + } + AnyNodeRef::PatternMatchAs(node) => { + project_pattern_match_as(py, locator, node, types).map(Some) + } + AnyNodeRef::PatternMatchOr(node) => { + project_pattern_match_or(py, locator, node, types).map(Some) + } + AnyNodeRef::TypeParamTypeVar(node) => { + project_type_param_type_var(py, locator, node, types).map(Some) + } + AnyNodeRef::TypeParamTypeVarTuple(node) => { + project_type_param_type_var_tuple(py, locator, node, types).map(Some) + } + AnyNodeRef::TypeParamParamSpec(node) => { + project_type_param_param_spec(py, locator, node, types).map(Some) + } + AnyNodeRef::InterpolatedStringFormatSpec(node) => { + project_interpolated_string_format_spec(py, locator, node, types).map(Some) + } + AnyNodeRef::PatternArguments(node) => { + project_pattern_arguments(py, locator, node, types).map(Some) + } + AnyNodeRef::PatternKeyword(node) => { + project_pattern_keyword(py, locator, node, types).map(Some) + } + AnyNodeRef::Comprehension(node) => { + project_comprehension(py, locator, node, types).map(Some) + } + AnyNodeRef::Parameters(node) => project_parameters(py, locator, node, types).map(Some), + AnyNodeRef::Parameter(node) => project_parameter(py, locator, node, types).map(Some), + AnyNodeRef::ParameterWithDefault(node) => { + project_parameter_with_default(py, locator, node, types).map(Some) + } + AnyNodeRef::Alias(node) => project_alias(py, locator, node, types).map(Some), + AnyNodeRef::WithItem(node) => project_with_item(py, locator, node, types).map(Some), + AnyNodeRef::MatchCase(node) => project_match_case(py, locator, node, types).map(Some), + AnyNodeRef::Decorator(node) => project_decorator(py, locator, node, types).map(Some), + AnyNodeRef::ElifElseClause(node) => { + project_elif_else_clause(py, locator, node, types).map(Some) + } + AnyNodeRef::TypeParams(node) => project_type_params(py, locator, node, types).map(Some), + AnyNodeRef::FString(node) => project_f_string(py, locator, node, types).map(Some), + AnyNodeRef::TString(node) => project_t_string(py, locator, node, types).map(Some), + AnyNodeRef::StringLiteral(node) => { + project_string_literal(py, locator, node, types).map(Some) + } + AnyNodeRef::BytesLiteral(node) => project_bytes_literal(py, locator, node, types).map(Some), + AnyNodeRef::Identifier(node) => project_identifier(py, locator, node, types).map(Some), + AnyNodeRef::Arguments(node) => project_arguments(py, locator, node, types).map(Some), + AnyNodeRef::Keyword(node) => project_keyword(py, locator, node, types).map(Some), + _ => Ok(None), + } +} +fn project_mod_module( + py: Python<'_>, + locator: &Locator<'_>, + node: &ruff_python_ast::ModModule, + types: ProjectionTypesRef, +) -> PyResult { + let kind = "Module".to_string(); + let range = node.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{node:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(node); + Module::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_mod_expression( + py: Python<'_>, + locator: &Locator<'_>, + node: &ruff_python_ast::ModExpression, + types: ProjectionTypesRef, +) -> PyResult { + let kind = "Expression".to_string(); + let range = node.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{node:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(node); + Expression::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_stmt_function_def( + py: Python<'_>, + locator: &Locator<'_>, + stmt: &ruff_python_ast::StmtFunctionDef, + types: ProjectionTypesRef, +) -> PyResult { + let kind = StmtKind::FunctionDef.as_str().to_string(); + let range = stmt.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{stmt:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(stmt); + let is_async = py_bool(py, stmt.is_async); + let name = py_string(py, stmt.name.as_str()); + FunctionDefStmt::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + is_async, + name, + SourceFileHandle::new(), + types, + ) +} +fn project_stmt_class_def( + py: Python<'_>, + locator: &Locator<'_>, + stmt: &ruff_python_ast::StmtClassDef, + types: ProjectionTypesRef, +) -> PyResult { + let kind = StmtKind::ClassDef.as_str().to_string(); + let range = stmt.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{stmt:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(stmt); + let name = py_string(py, stmt.name.as_str()); + ClassDefStmt::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + name, + SourceFileHandle::new(), + types, + ) +} +fn project_stmt_return( + py: Python<'_>, + locator: &Locator<'_>, + stmt: &ruff_python_ast::StmtReturn, + types: ProjectionTypesRef, +) -> PyResult { + let kind = StmtKind::Return.as_str().to_string(); + let range = stmt.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{stmt:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(stmt); + ReturnStmt::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_stmt_delete( + py: Python<'_>, + locator: &Locator<'_>, + stmt: &ruff_python_ast::StmtDelete, + types: ProjectionTypesRef, +) -> PyResult { + let kind = StmtKind::Delete.as_str().to_string(); + let range = stmt.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{stmt:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(stmt); + DeleteStmt::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_stmt_type_alias( + py: Python<'_>, + locator: &Locator<'_>, + stmt: &ruff_python_ast::StmtTypeAlias, + types: ProjectionTypesRef, +) -> PyResult { + let kind = StmtKind::TypeAlias.as_str().to_string(); + let range = stmt.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{stmt:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(stmt); + TypeAliasStmt::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_stmt_assign( + py: Python<'_>, + locator: &Locator<'_>, + stmt: &ruff_python_ast::StmtAssign, + types: ProjectionTypesRef, +) -> PyResult { + let kind = StmtKind::Assign.as_str().to_string(); + let range = stmt.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{stmt:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(stmt); + AssignStmt::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_stmt_aug_assign( + py: Python<'_>, + locator: &Locator<'_>, + stmt: &ruff_python_ast::StmtAugAssign, + types: ProjectionTypesRef, +) -> PyResult { + let kind = StmtKind::AugAssign.as_str().to_string(); + let range = stmt.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{stmt:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(stmt); + let op = py_string(py, stmt.op.as_str()); + AugAssignStmt::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + op, + SourceFileHandle::new(), + types, + ) +} +fn project_stmt_ann_assign( + py: Python<'_>, + locator: &Locator<'_>, + stmt: &ruff_python_ast::StmtAnnAssign, + types: ProjectionTypesRef, +) -> PyResult { + let kind = StmtKind::AnnAssign.as_str().to_string(); + let range = stmt.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{stmt:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(stmt); + let simple = py_bool(py, stmt.simple); + AnnAssignStmt::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + simple, + SourceFileHandle::new(), + types, + ) +} +fn project_stmt_for( + py: Python<'_>, + locator: &Locator<'_>, + stmt: &ruff_python_ast::StmtFor, + types: ProjectionTypesRef, +) -> PyResult { + let kind = StmtKind::For.as_str().to_string(); + let range = stmt.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{stmt:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(stmt); + let is_async = py_bool(py, stmt.is_async); + ForStmt::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + is_async, + SourceFileHandle::new(), + types, + ) +} +fn project_stmt_while( + py: Python<'_>, + locator: &Locator<'_>, + stmt: &ruff_python_ast::StmtWhile, + types: ProjectionTypesRef, +) -> PyResult { + let kind = StmtKind::While.as_str().to_string(); + let range = stmt.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{stmt:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(stmt); + WhileStmt::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_stmt_if( + py: Python<'_>, + locator: &Locator<'_>, + stmt: &ruff_python_ast::StmtIf, + types: ProjectionTypesRef, +) -> PyResult { + let kind = StmtKind::If.as_str().to_string(); + let range = stmt.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{stmt:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(stmt); + let test = expr_to_python(py, locator, &stmt.test, types)?; + IfStmt::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + test, + SourceFileHandle::new(), + types, + ) +} +fn project_stmt_with( + py: Python<'_>, + locator: &Locator<'_>, + stmt: &ruff_python_ast::StmtWith, + types: ProjectionTypesRef, +) -> PyResult { + let kind = StmtKind::With.as_str().to_string(); + let range = stmt.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{stmt:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(stmt); + let is_async = py_bool(py, stmt.is_async); + WithStmt::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + is_async, + SourceFileHandle::new(), + types, + ) +} +fn project_stmt_match( + py: Python<'_>, + locator: &Locator<'_>, + stmt: &ruff_python_ast::StmtMatch, + types: ProjectionTypesRef, +) -> PyResult { + let kind = StmtKind::Match.as_str().to_string(); + let range = stmt.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{stmt:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(stmt); + MatchStmt::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_stmt_raise( + py: Python<'_>, + locator: &Locator<'_>, + stmt: &ruff_python_ast::StmtRaise, + types: ProjectionTypesRef, +) -> PyResult { + let kind = StmtKind::Raise.as_str().to_string(); + let range = stmt.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{stmt:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(stmt); + RaiseStmt::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_stmt_try( + py: Python<'_>, + locator: &Locator<'_>, + stmt: &ruff_python_ast::StmtTry, + types: ProjectionTypesRef, +) -> PyResult { + let kind = StmtKind::Try.as_str().to_string(); + let range = stmt.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{stmt:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(stmt); + let is_star = py_bool(py, stmt.is_star); + TryStmt::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + is_star, + SourceFileHandle::new(), + types, + ) +} +fn project_stmt_assert( + py: Python<'_>, + locator: &Locator<'_>, + stmt: &ruff_python_ast::StmtAssert, + types: ProjectionTypesRef, +) -> PyResult { + let kind = StmtKind::Assert.as_str().to_string(); + let range = stmt.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{stmt:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(stmt); + AssertStmt::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_stmt_import( + py: Python<'_>, + locator: &Locator<'_>, + stmt: &ruff_python_ast::StmtImport, + types: ProjectionTypesRef, +) -> PyResult { + let kind = StmtKind::Import.as_str().to_string(); + let range = stmt.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{stmt:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(stmt); + ImportStmt::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_stmt_import_from( + py: Python<'_>, + locator: &Locator<'_>, + stmt: &ruff_python_ast::StmtImportFrom, + types: ProjectionTypesRef, +) -> PyResult { + let kind = StmtKind::ImportFrom.as_str().to_string(); + let range = stmt.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{stmt:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(stmt); + let module = optional_str( + py, + stmt.module + .as_ref() + .map(ruff_python_ast::Identifier::as_str), + ); + ImportFromStmt::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + module, + SourceFileHandle::new(), + types, + ) +} +fn project_stmt_global( + py: Python<'_>, + locator: &Locator<'_>, + stmt: &ruff_python_ast::StmtGlobal, + types: ProjectionTypesRef, +) -> PyResult { + let kind = StmtKind::Global.as_str().to_string(); + let range = stmt.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{stmt:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(stmt); + GlobalStmt::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_stmt_nonlocal( + py: Python<'_>, + locator: &Locator<'_>, + stmt: &ruff_python_ast::StmtNonlocal, + types: ProjectionTypesRef, +) -> PyResult { + let kind = StmtKind::Nonlocal.as_str().to_string(); + let range = stmt.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{stmt:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(stmt); + NonlocalStmt::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_stmt_expr( + py: Python<'_>, + locator: &Locator<'_>, + stmt: &ruff_python_ast::StmtExpr, + types: ProjectionTypesRef, +) -> PyResult { + let kind = StmtKind::Expr.as_str().to_string(); + let range = stmt.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{stmt:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(stmt); + ExprStmt::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_stmt_pass( + py: Python<'_>, + locator: &Locator<'_>, + stmt: &ruff_python_ast::StmtPass, + types: ProjectionTypesRef, +) -> PyResult { + let kind = StmtKind::Pass.as_str().to_string(); + let range = stmt.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{stmt:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(stmt); + PassStmt::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_stmt_break( + py: Python<'_>, + locator: &Locator<'_>, + stmt: &ruff_python_ast::StmtBreak, + types: ProjectionTypesRef, +) -> PyResult { + let kind = StmtKind::Break.as_str().to_string(); + let range = stmt.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{stmt:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(stmt); + BreakStmt::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_stmt_continue( + py: Python<'_>, + locator: &Locator<'_>, + stmt: &ruff_python_ast::StmtContinue, + types: ProjectionTypesRef, +) -> PyResult { + let kind = StmtKind::Continue.as_str().to_string(); + let range = stmt.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{stmt:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(stmt); + ContinueStmt::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_stmt_ipy_escape_command( + py: Python<'_>, + locator: &Locator<'_>, + stmt: &ruff_python_ast::StmtIpyEscapeCommand, + types: ProjectionTypesRef, +) -> PyResult { + let kind = StmtKind::IpyEscapeCommand.as_str().to_string(); + let range = stmt.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{stmt:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(stmt); + let kind_field = py_string(py, stmt.kind.as_str()); + IpyEscapeCommandStmt::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + kind_field, + SourceFileHandle::new(), + types, + ) +} +fn project_expr_bool_op( + py: Python<'_>, + locator: &Locator<'_>, + expr: &ruff_python_ast::ExprBoolOp, + types: ProjectionTypesRef, +) -> PyResult { + let kind = ExprKind::BoolOp.as_str().to_string(); + let range = expr.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{expr:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(expr); + let op = py_string(py, expr.op.as_str()); + BoolOpExpr::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + op, + SourceFileHandle::new(), + types, + ) +} +fn project_expr_named( + py: Python<'_>, + locator: &Locator<'_>, + expr: &ruff_python_ast::ExprNamed, + types: ProjectionTypesRef, +) -> PyResult { + let kind = ExprKind::Named.as_str().to_string(); + let range = expr.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{expr:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(expr); + NamedExpr::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_expr_bin_op( + py: Python<'_>, + locator: &Locator<'_>, + expr: &ruff_python_ast::ExprBinOp, + types: ProjectionTypesRef, +) -> PyResult { + let kind = ExprKind::BinOp.as_str().to_string(); + let range = expr.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{expr:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(expr); + let op = py_string(py, expr.op.as_str()); + BinOpExpr::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + op, + SourceFileHandle::new(), + types, + ) +} +fn project_expr_unary_op( + py: Python<'_>, + locator: &Locator<'_>, + expr: &ruff_python_ast::ExprUnaryOp, + types: ProjectionTypesRef, +) -> PyResult { + let kind = ExprKind::UnaryOp.as_str().to_string(); + let range = expr.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{expr:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(expr); + let op = py_string(py, expr.op.as_str()); + UnaryOpExpr::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + op, + SourceFileHandle::new(), + types, + ) +} +fn project_expr_lambda( + py: Python<'_>, + locator: &Locator<'_>, + expr: &ruff_python_ast::ExprLambda, + types: ProjectionTypesRef, +) -> PyResult { + let kind = ExprKind::Lambda.as_str().to_string(); + let range = expr.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{expr:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(expr); + LambdaExpr::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_expr_if( + py: Python<'_>, + locator: &Locator<'_>, + expr: &ruff_python_ast::ExprIf, + types: ProjectionTypesRef, +) -> PyResult { + let kind = ExprKind::If.as_str().to_string(); + let range = expr.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{expr:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(expr); + let test = expr_to_python(py, locator, &expr.test, types)?; + IfExpr::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + test, + SourceFileHandle::new(), + types, + ) +} +fn project_expr_dict( + py: Python<'_>, + locator: &Locator<'_>, + expr: &ruff_python_ast::ExprDict, + types: ProjectionTypesRef, +) -> PyResult { + let kind = ExprKind::Dict.as_str().to_string(); + let range = expr.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{expr:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(expr); + DictExpr::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_expr_set( + py: Python<'_>, + locator: &Locator<'_>, + expr: &ruff_python_ast::ExprSet, + types: ProjectionTypesRef, +) -> PyResult { + let kind = ExprKind::Set.as_str().to_string(); + let range = expr.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{expr:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(expr); + SetExpr::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_expr_list_comp( + py: Python<'_>, + locator: &Locator<'_>, + expr: &ruff_python_ast::ExprListComp, + types: ProjectionTypesRef, +) -> PyResult { + let kind = ExprKind::ListComp.as_str().to_string(); + let range = expr.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{expr:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(expr); + ListCompExpr::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_expr_set_comp( + py: Python<'_>, + locator: &Locator<'_>, + expr: &ruff_python_ast::ExprSetComp, + types: ProjectionTypesRef, +) -> PyResult { + let kind = ExprKind::SetComp.as_str().to_string(); + let range = expr.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{expr:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(expr); + SetCompExpr::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_expr_dict_comp( + py: Python<'_>, + locator: &Locator<'_>, + expr: &ruff_python_ast::ExprDictComp, + types: ProjectionTypesRef, +) -> PyResult { + let kind = ExprKind::DictComp.as_str().to_string(); + let range = expr.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{expr:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(expr); + DictCompExpr::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_expr_generator( + py: Python<'_>, + locator: &Locator<'_>, + expr: &ruff_python_ast::ExprGenerator, + types: ProjectionTypesRef, +) -> PyResult { + let kind = ExprKind::Generator.as_str().to_string(); + let range = expr.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{expr:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(expr); + let parenthesized = py_bool(py, expr.parenthesized); + GeneratorExpr::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + parenthesized, + SourceFileHandle::new(), + types, + ) +} +fn project_expr_await( + py: Python<'_>, + locator: &Locator<'_>, + expr: &ruff_python_ast::ExprAwait, + types: ProjectionTypesRef, +) -> PyResult { + let kind = ExprKind::Await.as_str().to_string(); + let range = expr.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{expr:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(expr); + AwaitExpr::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_expr_yield( + py: Python<'_>, + locator: &Locator<'_>, + expr: &ruff_python_ast::ExprYield, + types: ProjectionTypesRef, +) -> PyResult { + let kind = ExprKind::Yield.as_str().to_string(); + let range = expr.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{expr:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(expr); + YieldExpr::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_expr_yield_from( + py: Python<'_>, + locator: &Locator<'_>, + expr: &ruff_python_ast::ExprYieldFrom, + types: ProjectionTypesRef, +) -> PyResult { + let kind = ExprKind::YieldFrom.as_str().to_string(); + let range = expr.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{expr:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(expr); + YieldFromExpr::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_expr_compare( + py: Python<'_>, + locator: &Locator<'_>, + expr: &ruff_python_ast::ExprCompare, + types: ProjectionTypesRef, +) -> PyResult { + let kind = ExprKind::Compare.as_str().to_string(); + let range = expr.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{expr:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(expr); + let mut ops_values = Vec::with_capacity(expr.ops.len()); + for value in &expr.ops { + ops_values.push(py_string(py, value.as_str())); + } + let ops = PyTuple::new(py, ops_values)?.into_any().unbind(); + CompareExpr::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + ops, + SourceFileHandle::new(), + types, + ) +} +fn project_expr_call( + py: Python<'_>, + locator: &Locator<'_>, + expr: &ruff_python_ast::ExprCall, + types: ProjectionTypesRef, +) -> PyResult { + let kind = ExprKind::Call.as_str().to_string(); + let range = expr.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{expr:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(expr); + let callee = extract_callee(locator, range, expr); + let function_text = Some(locator.slice(expr.func.range()).trim().to_string()); + let function_kind = Some(ExprKind::from(expr.func.as_ref()).as_str().to_owned()); + let arguments = node_to_python(py, locator, AnyNodeRef::from(&expr.arguments), types)?; + CallExpr::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + arguments, + callee, + function_text, + function_kind, + SourceFileHandle::new(), + types, + ) +} +fn project_expr_f_string( + py: Python<'_>, + locator: &Locator<'_>, + expr: &ruff_python_ast::ExprFString, + types: ProjectionTypesRef, +) -> PyResult { + let kind = ExprKind::FString.as_str().to_string(); + let range = expr.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{expr:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(expr); + FStringExpr::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_expr_t_string( + py: Python<'_>, + locator: &Locator<'_>, + expr: &ruff_python_ast::ExprTString, + types: ProjectionTypesRef, +) -> PyResult { + let kind = ExprKind::TString.as_str().to_string(); + let range = expr.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{expr:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(expr); + TStringExpr::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_expr_string_literal( + py: Python<'_>, + locator: &Locator<'_>, + expr: &ruff_python_ast::ExprStringLiteral, + types: ProjectionTypesRef, +) -> PyResult { + let kind = ExprKind::StringLiteral.as_str().to_string(); + let range = expr.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{expr:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(expr); + StringLiteralExpr::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_expr_bytes_literal( + py: Python<'_>, + locator: &Locator<'_>, + expr: &ruff_python_ast::ExprBytesLiteral, + types: ProjectionTypesRef, +) -> PyResult { + let kind = ExprKind::BytesLiteral.as_str().to_string(); + let range = expr.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{expr:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(expr); + BytesLiteralExpr::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_expr_number_literal( + py: Python<'_>, + locator: &Locator<'_>, + expr: &ruff_python_ast::ExprNumberLiteral, + types: ProjectionTypesRef, +) -> PyResult { + let kind = ExprKind::NumberLiteral.as_str().to_string(); + let range = expr.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{expr:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(expr); + NumberLiteralExpr::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_expr_boolean_literal( + py: Python<'_>, + locator: &Locator<'_>, + expr: &ruff_python_ast::ExprBooleanLiteral, + types: ProjectionTypesRef, +) -> PyResult { + let kind = ExprKind::BooleanLiteral.as_str().to_string(); + let range = expr.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{expr:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(expr); + let value = py_bool(py, expr.value); + BooleanLiteralExpr::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + value, + SourceFileHandle::new(), + types, + ) +} +fn project_expr_none_literal( + py: Python<'_>, + locator: &Locator<'_>, + expr: &ruff_python_ast::ExprNoneLiteral, + types: ProjectionTypesRef, +) -> PyResult { + let kind = ExprKind::NoneLiteral.as_str().to_string(); + let range = expr.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{expr:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(expr); + NoneLiteralExpr::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_expr_ellipsis_literal( + py: Python<'_>, + locator: &Locator<'_>, + expr: &ruff_python_ast::ExprEllipsisLiteral, + types: ProjectionTypesRef, +) -> PyResult { + let kind = ExprKind::EllipsisLiteral.as_str().to_string(); + let range = expr.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{expr:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(expr); + EllipsisLiteralExpr::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_expr_attribute( + py: Python<'_>, + locator: &Locator<'_>, + expr: &ruff_python_ast::ExprAttribute, + types: ProjectionTypesRef, +) -> PyResult { + let kind = ExprKind::Attribute.as_str().to_string(); + let range = expr.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{expr:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(expr); + let attr = py_string(py, expr.attr.as_str()); + let ctx_value = format!("{access:?}", access = expr.ctx); + let ctx = py_string(py, &ctx_value); + AttributeExpr::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + attr, + ctx, + SourceFileHandle::new(), + types, + ) +} +fn project_expr_subscript( + py: Python<'_>, + locator: &Locator<'_>, + expr: &ruff_python_ast::ExprSubscript, + types: ProjectionTypesRef, +) -> PyResult { + let kind = ExprKind::Subscript.as_str().to_string(); + let range = expr.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{expr:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(expr); + let ctx_value = format!("{access:?}", access = expr.ctx); + let ctx = py_string(py, &ctx_value); + SubscriptExpr::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + ctx, + SourceFileHandle::new(), + types, + ) +} +fn project_expr_starred( + py: Python<'_>, + locator: &Locator<'_>, + expr: &ruff_python_ast::ExprStarred, + types: ProjectionTypesRef, +) -> PyResult { + let kind = ExprKind::Starred.as_str().to_string(); + let range = expr.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{expr:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(expr); + let ctx_value = format!("{access:?}", access = expr.ctx); + let ctx = py_string(py, &ctx_value); + StarredExpr::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + ctx, + SourceFileHandle::new(), + types, + ) +} +fn project_expr_name( + py: Python<'_>, + locator: &Locator<'_>, + expr: &ruff_python_ast::ExprName, + types: ProjectionTypesRef, +) -> PyResult { + let kind = ExprKind::Name.as_str().to_string(); + let range = expr.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{expr:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(expr); + let ctx_value = format!("{access:?}", access = expr.ctx); + let ctx = py_string(py, &ctx_value); + NameExpr::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + ctx, + SourceFileHandle::new(), + types, + ) +} +fn project_expr_list( + py: Python<'_>, + locator: &Locator<'_>, + expr: &ruff_python_ast::ExprList, + types: ProjectionTypesRef, +) -> PyResult { + let kind = ExprKind::List.as_str().to_string(); + let range = expr.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{expr:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(expr); + let ctx_value = format!("{access:?}", access = expr.ctx); + let ctx = py_string(py, &ctx_value); + ListExpr::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + ctx, + SourceFileHandle::new(), + types, + ) +} +fn project_expr_tuple( + py: Python<'_>, + locator: &Locator<'_>, + expr: &ruff_python_ast::ExprTuple, + types: ProjectionTypesRef, +) -> PyResult { + let kind = ExprKind::Tuple.as_str().to_string(); + let range = expr.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{expr:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(expr); + let ctx_value = format!("{access:?}", access = expr.ctx); + let ctx = py_string(py, &ctx_value); + let parenthesized = py_bool(py, expr.parenthesized); + TupleExpr::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + ctx, + parenthesized, + SourceFileHandle::new(), + types, + ) +} +fn project_expr_slice( + py: Python<'_>, + locator: &Locator<'_>, + expr: &ruff_python_ast::ExprSlice, + types: ProjectionTypesRef, +) -> PyResult { + let kind = ExprKind::Slice.as_str().to_string(); + let range = expr.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{expr:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(expr); + SliceExpr::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_expr_ipy_escape_command( + py: Python<'_>, + locator: &Locator<'_>, + expr: &ruff_python_ast::ExprIpyEscapeCommand, + types: ProjectionTypesRef, +) -> PyResult { + let kind = ExprKind::IpyEscapeCommand.as_str().to_string(); + let range = expr.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{expr:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(expr); + let kind_field = py_string(py, expr.kind.as_str()); + IpyEscapeCommandExpr::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + kind_field, + SourceFileHandle::new(), + types, + ) +} +fn project_except_handler_except_handler( + py: Python<'_>, + locator: &Locator<'_>, + node: &ruff_python_ast::ExceptHandlerExceptHandler, + types: ProjectionTypesRef, +) -> PyResult { + let kind = "ExceptHandler".to_string(); + let range = node.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{node:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(node); + ExceptHandler::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_interpolated_element( + py: Python<'_>, + locator: &Locator<'_>, + node: &ruff_python_ast::InterpolatedElement, + types: ProjectionTypesRef, +) -> PyResult { + let kind = "Interpolation".to_string(); + let range = node.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{node:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(node); + InterpolatedElement::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_interpolated_string_literal_element( + py: Python<'_>, + locator: &Locator<'_>, + node: &ruff_python_ast::InterpolatedStringLiteralElement, + types: ProjectionTypesRef, +) -> PyResult { + let kind = "Literal".to_string(); + let range = node.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{node:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(node); + InterpolatedStringLiteralElement::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_pattern_match_value( + py: Python<'_>, + locator: &Locator<'_>, + node: &ruff_python_ast::PatternMatchValue, + types: ProjectionTypesRef, +) -> PyResult { + let kind = "MatchValue".to_string(); + let range = node.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{node:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(node); + MatchValuePattern::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_pattern_match_singleton( + py: Python<'_>, + locator: &Locator<'_>, + node: &ruff_python_ast::PatternMatchSingleton, + types: ProjectionTypesRef, +) -> PyResult { + let kind = "MatchSingleton".to_string(); + let range = node.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{node:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(node); + MatchSingletonPattern::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_pattern_match_sequence( + py: Python<'_>, + locator: &Locator<'_>, + node: &ruff_python_ast::PatternMatchSequence, + types: ProjectionTypesRef, +) -> PyResult { + let kind = "MatchSequence".to_string(); + let range = node.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{node:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(node); + MatchSequencePattern::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_pattern_match_mapping( + py: Python<'_>, + locator: &Locator<'_>, + node: &ruff_python_ast::PatternMatchMapping, + types: ProjectionTypesRef, +) -> PyResult { + let kind = "MatchMapping".to_string(); + let range = node.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{node:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(node); + let rest = optional_str( + py, + node.rest.as_ref().map(ruff_python_ast::Identifier::as_str), + ); + MatchMappingPattern::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + rest, + SourceFileHandle::new(), + types, + ) +} +fn project_pattern_match_class( + py: Python<'_>, + locator: &Locator<'_>, + node: &ruff_python_ast::PatternMatchClass, + types: ProjectionTypesRef, +) -> PyResult { + let kind = "MatchClass".to_string(); + let range = node.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{node:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(node); + MatchClassPattern::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_pattern_match_star( + py: Python<'_>, + locator: &Locator<'_>, + node: &ruff_python_ast::PatternMatchStar, + types: ProjectionTypesRef, +) -> PyResult { + let kind = "MatchStar".to_string(); + let range = node.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{node:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(node); + let name = optional_str( + py, + node.name.as_ref().map(ruff_python_ast::Identifier::as_str), + ); + MatchStarPattern::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + name, + SourceFileHandle::new(), + types, + ) +} +fn project_pattern_match_as( + py: Python<'_>, + locator: &Locator<'_>, + node: &ruff_python_ast::PatternMatchAs, + types: ProjectionTypesRef, +) -> PyResult { + let kind = "MatchAs".to_string(); + let range = node.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{node:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(node); + let name = optional_str( + py, + node.name.as_ref().map(ruff_python_ast::Identifier::as_str), + ); + MatchAsPattern::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + name, + SourceFileHandle::new(), + types, + ) +} +fn project_pattern_match_or( + py: Python<'_>, + locator: &Locator<'_>, + node: &ruff_python_ast::PatternMatchOr, + types: ProjectionTypesRef, +) -> PyResult { + let kind = "MatchOr".to_string(); + let range = node.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{node:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(node); + MatchOrPattern::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_type_param_type_var( + py: Python<'_>, + locator: &Locator<'_>, + node: &ruff_python_ast::TypeParamTypeVar, + types: ProjectionTypesRef, +) -> PyResult { + let kind = "TypeVar".to_string(); + let range = node.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{node:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(node); + let name = py_string(py, node.name.as_str()); + TypeVar::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + name, + SourceFileHandle::new(), + types, + ) +} +fn project_type_param_type_var_tuple( + py: Python<'_>, + locator: &Locator<'_>, + node: &ruff_python_ast::TypeParamTypeVarTuple, + types: ProjectionTypesRef, +) -> PyResult { + let kind = "TypeVarTuple".to_string(); + let range = node.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{node:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(node); + let name = py_string(py, node.name.as_str()); + TypeVarTuple::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + name, + SourceFileHandle::new(), + types, + ) +} +fn project_type_param_param_spec( + py: Python<'_>, + locator: &Locator<'_>, + node: &ruff_python_ast::TypeParamParamSpec, + types: ProjectionTypesRef, +) -> PyResult { + let kind = "ParamSpec".to_string(); + let range = node.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{node:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(node); + let name = py_string(py, node.name.as_str()); + ParamSpec::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + name, + SourceFileHandle::new(), + types, + ) +} +fn project_interpolated_string_format_spec( + py: Python<'_>, + locator: &Locator<'_>, + node: &ruff_python_ast::InterpolatedStringFormatSpec, + types: ProjectionTypesRef, +) -> PyResult { + let kind = "InterpolatedStringFormatSpec".to_string(); + let range = node.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{node:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(node); + InterpolatedStringFormatSpec::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_pattern_arguments( + py: Python<'_>, + locator: &Locator<'_>, + node: &ruff_python_ast::PatternArguments, + types: ProjectionTypesRef, +) -> PyResult { + let kind = "PatternArguments".to_string(); + let range = node.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{node:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(node); + PatternArguments::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_pattern_keyword( + py: Python<'_>, + locator: &Locator<'_>, + node: &ruff_python_ast::PatternKeyword, + types: ProjectionTypesRef, +) -> PyResult { + let kind = "PatternKeyword".to_string(); + let range = node.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{node:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(node); + PatternKeyword::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_comprehension( + py: Python<'_>, + locator: &Locator<'_>, + node: &ruff_python_ast::Comprehension, + types: ProjectionTypesRef, +) -> PyResult { + let kind = "Comprehension".to_string(); + let range = node.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{node:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(node); + Comprehension::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_parameters( + py: Python<'_>, + locator: &Locator<'_>, + node: &ruff_python_ast::Parameters, + types: ProjectionTypesRef, +) -> PyResult { + let kind = "Parameters".to_string(); + let range = node.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{node:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(node); + Parameters::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_parameter( + py: Python<'_>, + locator: &Locator<'_>, + node: &ruff_python_ast::Parameter, + types: ProjectionTypesRef, +) -> PyResult { + let kind = "Parameter".to_string(); + let range = node.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{node:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(node); + Parameter::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_parameter_with_default( + py: Python<'_>, + locator: &Locator<'_>, + node: &ruff_python_ast::ParameterWithDefault, + types: ProjectionTypesRef, +) -> PyResult { + let kind = "ParameterWithDefault".to_string(); + let range = node.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{node:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(node); + ParameterWithDefault::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_alias( + py: Python<'_>, + locator: &Locator<'_>, + node: &ruff_python_ast::Alias, + types: ProjectionTypesRef, +) -> PyResult { + let kind = "Alias".to_string(); + let range = node.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{node:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(node); + Alias::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_with_item( + py: Python<'_>, + locator: &Locator<'_>, + node: &ruff_python_ast::WithItem, + types: ProjectionTypesRef, +) -> PyResult { + let kind = "WithItem".to_string(); + let range = node.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{node:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(node); + WithItem::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_match_case( + py: Python<'_>, + locator: &Locator<'_>, + node: &ruff_python_ast::MatchCase, + types: ProjectionTypesRef, +) -> PyResult { + let kind = "MatchCase".to_string(); + let range = node.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{node:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(node); + MatchCase::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_decorator( + py: Python<'_>, + locator: &Locator<'_>, + node: &ruff_python_ast::Decorator, + types: ProjectionTypesRef, +) -> PyResult { + let kind = "Decorator".to_string(); + let range = node.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{node:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(node); + Decorator::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_elif_else_clause( + py: Python<'_>, + locator: &Locator<'_>, + node: &ruff_python_ast::ElifElseClause, + types: ProjectionTypesRef, +) -> PyResult { + let kind = "ElifElseClause".to_string(); + let range = node.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{node:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(node); + ElifElseClause::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_type_params( + py: Python<'_>, + locator: &Locator<'_>, + node: &ruff_python_ast::TypeParams, + types: ProjectionTypesRef, +) -> PyResult { + let kind = "TypeParams".to_string(); + let range = node.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{node:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(node); + TypeParams::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_f_string( + py: Python<'_>, + locator: &Locator<'_>, + node: &ruff_python_ast::FString, + types: ProjectionTypesRef, +) -> PyResult { + let kind = "FString".to_string(); + let range = node.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{node:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(node); + FString::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_t_string( + py: Python<'_>, + locator: &Locator<'_>, + node: &ruff_python_ast::TString, + types: ProjectionTypesRef, +) -> PyResult { + let kind = "TString".to_string(); + let range = node.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{node:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(node); + TString::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_string_literal( + py: Python<'_>, + locator: &Locator<'_>, + node: &ruff_python_ast::StringLiteral, + types: ProjectionTypesRef, +) -> PyResult { + let kind = "StringLiteral".to_string(); + let range = node.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{node:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(node); + StringLiteral::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_bytes_literal( + py: Python<'_>, + locator: &Locator<'_>, + node: &ruff_python_ast::BytesLiteral, + types: ProjectionTypesRef, +) -> PyResult { + let kind = "BytesLiteral".to_string(); + let range = node.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{node:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(node); + BytesLiteral::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_identifier( + py: Python<'_>, + locator: &Locator<'_>, + node: &ruff_python_ast::Identifier, + types: ProjectionTypesRef, +) -> PyResult { + let kind = "Identifier".to_string(); + let range = node.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{node:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(node); + Identifier::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_arguments( + py: Python<'_>, + locator: &Locator<'_>, + node: &ruff_python_ast::Arguments, + types: ProjectionTypesRef, +) -> PyResult { + let kind = "Arguments".to_string(); + let range = node.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{node:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(node); + Arguments::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} +fn project_keyword( + py: Python<'_>, + locator: &Locator<'_>, + node: &ruff_python_ast::Keyword, + types: ProjectionTypesRef, +) -> PyResult { + let kind = "Keyword".to_string(); + let range = node.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{node:?}"); + let store = super::store::current_store(); + let node_id = store.ensure(node); + Keyword::new_instance( + py, + kind, + span_tuple(py, range)?, + text, + repr_value, + node_id, + store, + SourceFileHandle::new(), + types, + ) +} diff --git a/crates/ruff_linter/src/external/ast/python/source.rs b/crates/ruff_linter/src/external/ast/python/source.rs new file mode 100644 index 0000000000000..7989ab90fe3e3 --- /dev/null +++ b/crates/ruff_linter/src/external/ast/python/source.rs @@ -0,0 +1,138 @@ +use std::cell::{Cell, RefCell}; +use std::rc::Rc; + +use ruff_source_file::SourceFile; +use scopeguard::guard; + +use crate::Locator; + +thread_local! { + static CURRENT_SOURCE: RefCell> = const { RefCell::new(None) }; +} + +/// Execute `f` with the given `SourceFile` installed as the current source for [`SourceFileHandle`]. +/// Restores the previous value when `f` returns. +pub(crate) fn with_source_file(source_file: &SourceFile, f: impl FnOnce() -> R) -> R { + CURRENT_SOURCE.with(|cell| { + debug_assert!( + cell.borrow().as_ref().is_none(), + "with_source_file is unexpectedly nested" + ); + let handle = SourceFileHandle::from_source(source_file.clone()); + let previous = cell.replace(Some(handle.clone())); + let _restore = guard((cell, previous), |(cell, previous)| { + cell.replace(previous); + }); + let result = f(); + handle.invalidate(); + result + }) +} + +#[derive(Clone, Debug)] +struct SourceState { + source_file: SourceFile, + active: Cell, +} + +impl SourceState { + fn new(source_file: SourceFile) -> Self { + Self { + source_file, + active: Cell::new(true), + } + } + + fn source_file(&self) -> &SourceFile { + &self.source_file + } + + fn invalidate(&self) { + self.active.set(false); + } + + fn is_active(&self) -> bool { + self.active.get() + } +} + +#[derive(Clone, Debug)] +pub(crate) struct SourceFileHandle { + inner: Rc, +} + +impl SourceFileHandle { + fn from_source(source_file: SourceFile) -> Self { + Self { + inner: Rc::new(SourceState::new(source_file)), + } + } + + pub(crate) fn new() -> Self { + CURRENT_SOURCE.with(|cell| { + cell.borrow() + .as_ref() + .cloned() + .expect("SourceFileHandle::new called without a current SourceFile") + }) + } + + pub(crate) fn locator(&self) -> Locator<'_> { + self.assert_active(); + + Locator::with_index( + self.inner.source_file().source_text(), + self.inner.source_file().index().clone(), + ) + } + + pub(crate) fn invalidate(&self) { + self.inner.invalidate(); + } + + fn assert_active(&self) { + assert!( + self.inner.is_active(), + "source file is no longer valid (store was invalidated)" + ); + } + + #[cfg(test)] + fn is_active(&self) -> bool { + self.inner.is_active() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ruff_source_file::SourceFileBuilder; + + #[test] + fn owned_source_uses_current_source_file() { + let contents = "print('hi')"; + let source_file = SourceFileBuilder::new("test.py", contents).finish(); + + with_source_file(&source_file, || { + let owned = SourceFileHandle::new(); + let recovered = owned.locator(); + assert_eq!(recovered.as_str(), contents); + }); + } + + #[test] + fn invalidates_after_scope() { + let contents = "print('bye')"; + let source_file = SourceFileBuilder::new("test.py", contents).finish(); + + let handle = with_source_file(&source_file, || { + let handle = SourceFileHandle::new(); + // Handle remains active inside the scope. + assert!(handle.is_active()); + handle + }); + + // Once the scope ends, the handle should report inactive. + assert!(!handle.is_active()); + } +} diff --git a/crates/ruff_linter/src/external/ast/python/store.rs b/crates/ruff_linter/src/external/ast/python/store.rs new file mode 100644 index 0000000000000..b090d269a4075 --- /dev/null +++ b/crates/ruff_linter/src/external/ast/python/store.rs @@ -0,0 +1,134 @@ +use std::any::TypeId; +use std::cell::{Cell, RefCell}; +use std::ptr::NonNull; +use std::rc::Rc; + +use pyo3::exceptions::PyRuntimeError; +use pyo3::prelude::*; +use ruff_python_ast::{AtomicNodeIndex, HasNodeIndex, NodeIndex}; +use rustc_hash::FxHashMap; + +thread_local! { + static CURRENT_STORE: RefCell> = const { RefCell::new(None) }; +} + +/// In-memory cache of AST nodes keyed by their node IDs. +#[derive(Debug, Default)] +struct AstStore { + nodes: RefCell>, + next_id: Cell, + active: Cell, +} + +#[derive(Clone, Copy, Debug)] +struct StoredNode { + ptr: NonNull<()>, + type_id: TypeId, +} + +#[derive(Clone, Debug)] +pub(crate) struct AstStoreHandle { + inner: Rc, +} + +impl AstStoreHandle { + pub(crate) fn new() -> Self { + let handle = Self { + inner: Rc::new(AstStore::default()), + }; + handle.inner.active.set(true); + handle + } + + pub(crate) fn ensure(&self, node: &T) -> u32 + where + T: HasNodeIndex + 'static, + { + let id = self.inner.ensure_id(node.node_index()); + let mut nodes = self.inner.nodes.borrow_mut(); + let ptr = NonNull::from(node).cast::<()>(); + let type_id = TypeId::of::(); + nodes.entry(id).or_insert(StoredNode { ptr, type_id }); + id + } + + pub(crate) fn assign_id(&self, node_index: &AtomicNodeIndex) -> u32 { + self.inner.ensure_id(node_index) + } + + #[allow( + unsafe_code, + reason = "This store is invalidated before the underlying AST nodes are dropped." + )] + pub(crate) fn get(&self, id: u32) -> PyResult<&T> + where + T: 'static, + { + if !self.inner.active.get() { + return Err(PyRuntimeError::new_err("AST store is no longer valid")); + } + let nodes = self.inner.nodes.borrow(); + let Some(&StoredNode { ptr, type_id }) = nodes.get(&id) else { + return Err(PyRuntimeError::new_err(format!( + "missing AST node for id {id}" + ))); + }; + if type_id != TypeId::of::() { + return Err(PyRuntimeError::new_err(format!( + "type mismatch for AST node {id}" + ))); + } + // SAFETY: Entries are only inserted via `ensure`, which stores a pointer to a `T`. + // We rely on `invalidate` being called before the underlying AST data can be dropped. + let reference = unsafe { ptr.cast::().as_ref() }; + Ok(reference) + } + + pub(crate) fn invalidate(&self) { + self.inner.invalidate(); + } +} + +impl AstStore { + fn ensure_id(&self, index: &AtomicNodeIndex) -> u32 { + debug_assert!(self.active.get(), "ensuring id on inactive store"); + if let Some(id) = index.load().as_u32() { + return id; + } + + let id = self.next_id.get(); + self.next_id + .set(id.checked_add(1).expect("exceeded maximum node id")); + index.set(NodeIndex::from(id)); + id + } + + fn invalidate(&self) { + self.nodes.borrow_mut().clear(); + self.active.set(false); + } +} + +pub(crate) fn with_store(store: AstStoreHandle, f: impl FnOnce() -> R) -> R { + struct Restore<'a>(&'a RefCell>, Option); + + impl Drop for Restore<'_> { + fn drop(&mut self) { + self.0.replace(self.1.take()); + } + } + + CURRENT_STORE.with(|cell| { + let previous = cell.replace(Some(store)); + let _restore = Restore(cell, previous); + f() + }) +} + +pub(crate) fn current_store() -> AstStoreHandle { + CURRENT_STORE.with(|cell| { + cell.borrow() + .clone() + .expect("AstStoreHandle requested without a current store") + }) +} diff --git a/crates/ruff_linter/src/external/ast/registry.rs b/crates/ruff_linter/src/external/ast/registry.rs new file mode 100644 index 0000000000000..791e4f672d217 --- /dev/null +++ b/crates/ruff_linter/src/external/ast/registry.rs @@ -0,0 +1,200 @@ +use std::hash::Hasher; + +use crate::external::ast::rule::{CallCalleeMatcher, ExternalAstLinter, ExternalAstRule}; +use crate::external::ast::target::{AstTarget, ExprKind, StmtKind}; +use crate::external::error::ExternalLinterError; +use rustc_hash::FxHashMap; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct RuleLocator { + pub linter_index: usize, + pub rule_index: usize, +} + +impl RuleLocator { + pub const fn new(linter_index: usize, rule_index: usize) -> Self { + Self { + linter_index, + rule_index, + } + } +} + +#[derive(Debug, Default, Clone)] +pub struct ExternalLintRegistry { + linters: Vec, + index_by_code: FxHashMap, + stmt_index: FxHashMap>, + expr_index: FxHashMap>, +} + +impl ExternalLintRegistry { + pub fn new() -> Self { + Self::default() + } + + pub fn is_empty(&self) -> bool { + self.linters.is_empty() + } + + pub fn linters(&self) -> &[ExternalAstLinter] { + &self.linters + } + + pub fn insert_linter(&mut self, linter: ExternalAstLinter) -> Result<(), ExternalLinterError> { + if self.linters.iter().any(|existing| existing.id == linter.id) { + return Err(ExternalLinterError::DuplicateLinter { id: linter.id }); + } + + let linter_index = self.linters.len(); + for (rule_index, rule) in linter.rules.iter().enumerate() { + let code = rule.code.as_str().to_string(); + if self.index_by_code.contains_key(&code) { + return Err(ExternalLinterError::DuplicateRule { + linter: linter.id.clone(), + code, + }); + } + self.index_by_code + .insert(code, RuleLocator::new(linter_index, rule_index)); + + if !linter.enabled { + continue; + } + + for target in &rule.targets { + match target { + AstTarget::Stmt(kind) => self + .stmt_index + .entry(*kind) + .or_default() + .push(RuleLocator::new(linter_index, rule_index)), + AstTarget::Expr(kind) => self + .expr_index + .entry(*kind) + .or_default() + .push(RuleLocator::new(linter_index, rule_index)), + } + } + } + self.linters.push(linter); + Ok(()) + } + + pub fn get_rule(&self, locator: RuleLocator) -> Option<&ExternalAstRule> { + self.linters + .get(locator.linter_index) + .and_then(|linter| linter.rules.get(locator.rule_index)) + } + + pub fn get_linter(&self, locator: RuleLocator) -> Option<&ExternalAstLinter> { + self.linters.get(locator.linter_index) + } + + pub fn find_rule_by_code( + &self, + code: &str, + ) -> Option<(RuleLocator, &ExternalAstRule, &ExternalAstLinter)> { + let locator = *self.index_by_code.get(code)?; + let linter = self.linters.get(locator.linter_index)?; + if !linter.enabled { + return None; + } + let rule = linter.rules.get(locator.rule_index)?; + Some((locator, rule, linter)) + } + + pub fn rules_for_stmt(&self, kind: StmtKind) -> impl Iterator + '_ { + self.stmt_index.get(&kind).into_iter().flatten().copied() + } + + pub fn rules_for_expr(&self, kind: ExprKind) -> impl Iterator + '_ { + self.expr_index.get(&kind).into_iter().flatten().copied() + } + + pub fn rule_entry( + &self, + locator: RuleLocator, + ) -> Option<(&ExternalAstRule, &ExternalAstLinter)> { + let linter = self.linters.get(locator.linter_index)?; + let rule = linter.rules.get(locator.rule_index)?; + Some((rule, linter)) + } + + pub fn iter_enabled_rules(&self) -> impl Iterator { + self.linters + .iter() + .filter(|linter| linter.enabled) + .flat_map(|linter| linter.rules.iter()) + } + + pub fn iter_enabled_linter_rules( + &self, + ) -> impl Iterator { + self.linters + .iter() + .filter(|linter| linter.enabled) + .flat_map(|linter| linter.rules.iter().map(move |rule| (linter, rule))) + } + + pub fn iter_enabled_rule_locators(&self) -> impl Iterator + '_ { + self.linters + .iter() + .enumerate() + .filter(|(_, linter)| linter.enabled) + .flat_map(|(linter_index, linter)| { + linter + .rules + .iter() + .enumerate() + .map(move |(rule_index, _)| RuleLocator::new(linter_index, rule_index)) + }) + } + + pub fn expect_entry(&self, locator: RuleLocator) -> (&ExternalAstLinter, &ExternalAstRule) { + let (rule, linter) = self + .rule_entry(locator) + .expect("rule locator does not reference a valid entry"); + (linter, rule) + } +} + +impl ruff_cache::CacheKey for ExternalLintRegistry { + fn cache_key(&self, key: &mut ruff_cache::CacheKeyHasher) { + key.write_usize(self.linters.len()); + for linter in &self.linters { + linter.id.as_str().cache_key(key); + linter.enabled.cache_key(key); + linter.name.as_str().cache_key(key); + linter.description.as_deref().cache_key(key); + key.write_usize(linter.rules.len()); + for rule in &linter.rules { + rule.code.as_str().cache_key(key); + rule.name.as_str().cache_key(key); + rule.summary.as_deref().cache_key(key); + rule.call_callee() + .map(CallCalleeMatcher::pattern) + .cache_key(key); + key.write_usize(rule.targets.len()); + for target in &rule.targets { + match target { + AstTarget::Stmt(kind) => { + key.write_u8(0); + key.write_u16(*kind as u16); + } + AstTarget::Expr(kind) => { + key.write_u8(1); + key.write_u16(*kind as u16); + } + } + } + let path_str = rule.script.path().to_string_lossy(); + key.write_usize(path_str.len()); + key.write(path_str.as_bytes()); + let contents_str = rule.script.body(); + key.write_usize(contents_str.len()); + key.write(contents_str.as_bytes()); + } + } + } +} diff --git a/crates/ruff_linter/src/external/ast/rule.rs b/crates/ruff_linter/src/external/ast/rule.rs new file mode 100644 index 0000000000000..45c7f9f711015 --- /dev/null +++ b/crates/ruff_linter/src/external/ast/rule.rs @@ -0,0 +1,203 @@ +use std::path::{Path, PathBuf}; +use std::sync::OnceLock; + +use regex::Regex; +use ruff_db::diagnostic::SecondaryCode; +use serde::Deserialize; +use thiserror::Error; + +use crate::external::ast::target::{AstTarget, AstTargetSpec}; + +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct ExternalRuleCode(Box); + +impl ExternalRuleCode { + pub fn new>(code: S) -> Result { + let code_ref = code.as_ref(); + if code_ref.is_empty() { + return Err(ExternalRuleCodeError::Empty); + } + if !Self::matches_format(code_ref) { + return Err(ExternalRuleCodeError::InvalidCharacters( + code_ref.to_string(), + )); + } + Ok(Self(code_ref.into())) + } + + pub fn as_str(&self) -> &str { + &self.0 + } + + pub fn to_secondary_code(&self) -> SecondaryCode { + SecondaryCode::new(self.as_str().to_string()) + } + + fn pattern() -> &'static Regex { + static PATTERN: OnceLock = OnceLock::new(); + PATTERN.get_or_init(|| Regex::new(r"^[A-Z]+[0-9]+$").expect("valid external rule regex")) + } + + pub(crate) fn matches_format(code: &str) -> bool { + Self::pattern().is_match(code) + } +} + +impl std::fmt::Display for ExternalRuleCode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.as_str()) + } +} + +#[derive(Debug, Error)] +pub enum ExternalRuleCodeError { + #[error("external rule codes must not be empty")] + Empty, + #[error("external rule codes must contain only uppercase ASCII letters and digits: `{0}`")] + InvalidCharacters(String), +} + +/// Fully resolved script content that can be handed to the runtime for compilation. +#[derive(Debug, Clone)] +pub struct ExternalRuleScript { + path: PathBuf, + contents: String, +} + +impl ExternalRuleScript { + pub fn file(path: PathBuf, contents: impl Into) -> Self { + Self { + path, + contents: contents.into(), + } + } + + pub fn path(&self) -> &Path { + &self.path + } + + pub fn body(&self) -> &str { + &self.contents + } +} + +/// User-facing metadata describing an external AST rule before targets are resolved. +#[derive(Debug, Clone, Deserialize)] +pub struct ExternalAstRuleSpec { + pub code: String, + pub name: String, + #[serde(default)] + pub summary: Option, + #[serde(default)] + pub targets: Vec, + #[serde(default, rename = "call-callee-regex")] + pub call_callee_regex: Option, + pub script: PathBuf, +} + +/// A validated, ready-to-run external AST rule definition. +#[derive(Debug, Clone)] +pub struct ExternalAstRule { + pub code: ExternalRuleCode, + pub name: String, + pub summary: Option, + pub targets: Box<[AstTarget]>, + pub script: ExternalRuleScript, + pub call_callee: Option, +} + +impl ExternalAstRule { + #[allow(clippy::too_many_arguments)] + pub fn new( + code: ExternalRuleCode, + name: impl Into, + summary: Option>, + targets: Vec, + script: ExternalRuleScript, + call_callee: Option, + ) -> Self { + let targets = targets.into_boxed_slice(); + Self { + code, + name: name.into(), + summary: summary.map(Into::into), + targets, + script, + call_callee, + } + } + + pub fn call_callee(&self) -> Option<&CallCalleeMatcher> { + self.call_callee.as_ref() + } +} + +/// Metadata about a collection of external AST rules loaded from a user-defined linter file. +#[derive(Debug, Clone)] +pub struct ExternalAstLinter { + pub id: String, + pub name: String, + pub description: Option, + pub enabled: bool, + pub rules: Vec, +} + +impl ExternalAstLinter { + pub fn new( + id: impl Into, + name: impl Into, + description: Option>, + enabled: bool, + rules: Vec, + ) -> Self { + Self { + id: id.into(), + name: name.into(), + description: description.map(Into::into), + enabled, + rules, + } + } +} + +impl std::fmt::Display for ExternalAstLinter { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!( + f, + "{}{}", + self.id, + if self.enabled { "" } else { " (disabled)" } + )?; + writeln!(f, " name: {}", self.name)?; + if let Some(description) = &self.description { + writeln!(f, " description: {description}")?; + } + writeln!(f, " rules:")?; + for rule in &self.rules { + writeln!(f, " - {} ({})", rule.code.as_str(), rule.name)?; + } + writeln!(f) + } +} + +#[derive(Debug, Clone)] +pub struct CallCalleeMatcher { + pattern: String, + regex: Regex, +} + +impl CallCalleeMatcher { + pub fn new(pattern: impl Into) -> Result { + let pattern = pattern.into(); + let regex = Regex::new(pattern.as_ref())?; + Ok(Self { pattern, regex }) + } + + pub fn pattern(&self) -> &str { + &self.pattern + } + + pub fn regex(&self) -> &Regex { + &self.regex + } +} diff --git a/crates/ruff_linter/src/external/ast/target.rs b/crates/ruff_linter/src/external/ast/target.rs new file mode 100644 index 0000000000000..5c2c8bf916e85 --- /dev/null +++ b/crates/ruff_linter/src/external/ast/target.rs @@ -0,0 +1,452 @@ +use std::fmt; +use std::str::FromStr; + +use ruff_python_ast::{Expr, Stmt}; +use serde::Deserialize; +use thiserror::Error; + +/// An AST node selector identifying which nodes a scripted rule should run against. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum AstTarget { + Stmt(StmtKind), + Expr(ExprKind), +} + +impl AstTarget { + pub const fn kind(&self) -> AstNodeClass { + match self { + AstTarget::Stmt(..) => AstNodeClass::Stmt, + AstTarget::Expr(..) => AstNodeClass::Expr, + } + } + + pub const fn name(&self) -> &'static str { + match self { + AstTarget::Stmt(kind) => kind.as_str(), + AstTarget::Expr(kind) => kind.as_str(), + } + } +} + +impl fmt::Display for AstTarget { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + AstTarget::Stmt(kind) => write!(f, "stmt:{}", kind.as_str()), + AstTarget::Expr(kind) => write!(f, "expr:{}", kind.as_str()), + } + } +} + +impl FromStr for AstTarget { + type Err = AstTargetParseError; + + fn from_str(s: &str) -> Result { + parse_target(s) + } +} + +/// Convenience wrapper that enables parsing `AstTarget` values directly from configuration. +#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)] +#[serde(transparent)] +pub struct AstTargetSpec(String); + +impl AstTargetSpec { + pub fn parse(&self) -> Result { + self.0.as_str().parse() + } + + pub fn raw(&self) -> &str { + &self.0 + } +} + +/// Broad AST node classes supported by scripted rules. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum AstNodeClass { + Stmt, + Expr, +} + +/// Statement kinds supported by scripted rules. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum StmtKind { + FunctionDef, + ClassDef, + Return, + Delete, + TypeAlias, + Assign, + AugAssign, + AnnAssign, + For, + While, + If, + With, + Match, + Raise, + Try, + Assert, + Import, + ImportFrom, + Global, + Nonlocal, + Expr, + Pass, + Break, + Continue, + IpyEscapeCommand, +} + +impl StmtKind { + pub const fn as_str(self) -> &'static str { + match self { + StmtKind::FunctionDef => "FunctionDef", + StmtKind::ClassDef => "ClassDef", + StmtKind::Return => "Return", + StmtKind::Delete => "Delete", + StmtKind::TypeAlias => "TypeAlias", + StmtKind::Assign => "Assign", + StmtKind::AugAssign => "AugAssign", + StmtKind::AnnAssign => "AnnAssign", + StmtKind::For => "For", + StmtKind::While => "While", + StmtKind::If => "If", + StmtKind::With => "With", + StmtKind::Match => "Match", + StmtKind::Raise => "Raise", + StmtKind::Try => "Try", + StmtKind::Assert => "Assert", + StmtKind::Import => "Import", + StmtKind::ImportFrom => "ImportFrom", + StmtKind::Global => "Global", + StmtKind::Nonlocal => "Nonlocal", + StmtKind::Expr => "Expr", + StmtKind::Pass => "Pass", + StmtKind::Break => "Break", + StmtKind::Continue => "Continue", + StmtKind::IpyEscapeCommand => "IpyEscapeCommand", + } + } + + pub fn matches(self, stmt: &Stmt) -> bool { + matches!( + (self, stmt), + (StmtKind::FunctionDef, Stmt::FunctionDef(_)) + | (StmtKind::ClassDef, Stmt::ClassDef(_)) + | (StmtKind::Return, Stmt::Return(_)) + | (StmtKind::Delete, Stmt::Delete(_)) + | (StmtKind::TypeAlias, Stmt::TypeAlias(_)) + | (StmtKind::Assign, Stmt::Assign(_)) + | (StmtKind::AugAssign, Stmt::AugAssign(_)) + | (StmtKind::AnnAssign, Stmt::AnnAssign(_)) + | (StmtKind::For, Stmt::For(_)) + | (StmtKind::While, Stmt::While(_)) + | (StmtKind::If, Stmt::If(_)) + | (StmtKind::With, Stmt::With(_)) + | (StmtKind::Match, Stmt::Match(_)) + | (StmtKind::Raise, Stmt::Raise(_)) + | (StmtKind::Try, Stmt::Try(_)) + | (StmtKind::Assert, Stmt::Assert(_)) + | (StmtKind::Import, Stmt::Import(_)) + | (StmtKind::ImportFrom, Stmt::ImportFrom(_)) + | (StmtKind::Global, Stmt::Global(_)) + | (StmtKind::Nonlocal, Stmt::Nonlocal(_)) + | (StmtKind::Expr, Stmt::Expr(_)) + | (StmtKind::Pass, Stmt::Pass(_)) + | (StmtKind::Break, Stmt::Break(_)) + | (StmtKind::Continue, Stmt::Continue(_)) + | (StmtKind::IpyEscapeCommand, Stmt::IpyEscapeCommand(_)) + ) + } +} + +impl fmt::Display for StmtKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +impl From<&Stmt> for StmtKind { + fn from(value: &Stmt) -> Self { + match value { + Stmt::FunctionDef(_) => StmtKind::FunctionDef, + Stmt::ClassDef(_) => StmtKind::ClassDef, + Stmt::Return(_) => StmtKind::Return, + Stmt::Delete(_) => StmtKind::Delete, + Stmt::TypeAlias(_) => StmtKind::TypeAlias, + Stmt::Assign(_) => StmtKind::Assign, + Stmt::AugAssign(_) => StmtKind::AugAssign, + Stmt::AnnAssign(_) => StmtKind::AnnAssign, + Stmt::For(_) => StmtKind::For, + Stmt::While(_) => StmtKind::While, + Stmt::If(_) => StmtKind::If, + Stmt::With(_) => StmtKind::With, + Stmt::Match(_) => StmtKind::Match, + Stmt::Raise(_) => StmtKind::Raise, + Stmt::Try(_) => StmtKind::Try, + Stmt::Assert(_) => StmtKind::Assert, + Stmt::Import(_) => StmtKind::Import, + Stmt::ImportFrom(_) => StmtKind::ImportFrom, + Stmt::Global(_) => StmtKind::Global, + Stmt::Nonlocal(_) => StmtKind::Nonlocal, + Stmt::Expr(_) => StmtKind::Expr, + Stmt::Pass(_) => StmtKind::Pass, + Stmt::Break(_) => StmtKind::Break, + Stmt::Continue(_) => StmtKind::Continue, + Stmt::IpyEscapeCommand(_) => StmtKind::IpyEscapeCommand, + } + } +} + +/// Expression kinds supported by scripted rules. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum ExprKind { + Attribute, + Await, + BinOp, + BoolOp, + BooleanLiteral, + BytesLiteral, + Call, + Compare, + Dict, + DictComp, + EllipsisLiteral, + FString, + TString, + Generator, + If, + IpyEscapeCommand, + Lambda, + List, + ListComp, + Name, + Named, + NoneLiteral, + NumberLiteral, + Set, + SetComp, + Slice, + Starred, + StringLiteral, + Subscript, + Tuple, + UnaryOp, + Yield, + YieldFrom, +} + +impl ExprKind { + pub const fn as_str(self) -> &'static str { + match self { + ExprKind::Attribute => "Attribute", + ExprKind::Await => "Await", + ExprKind::BinOp => "BinOp", + ExprKind::BoolOp => "BoolOp", + ExprKind::BooleanLiteral => "BooleanLiteral", + ExprKind::BytesLiteral => "BytesLiteral", + ExprKind::Call => "Call", + ExprKind::Compare => "Compare", + ExprKind::Dict => "Dict", + ExprKind::DictComp => "DictComp", + ExprKind::EllipsisLiteral => "EllipsisLiteral", + ExprKind::FString => "FString", + ExprKind::TString => "TString", + ExprKind::Generator => "Generator", + ExprKind::If => "If", + ExprKind::IpyEscapeCommand => "IpyEscapeCommand", + ExprKind::Lambda => "Lambda", + ExprKind::List => "List", + ExprKind::ListComp => "ListComp", + ExprKind::Name => "Name", + ExprKind::Named => "Named", + ExprKind::NoneLiteral => "NoneLiteral", + ExprKind::NumberLiteral => "NumberLiteral", + ExprKind::Set => "Set", + ExprKind::SetComp => "SetComp", + ExprKind::Slice => "Slice", + ExprKind::Starred => "Starred", + ExprKind::StringLiteral => "StringLiteral", + ExprKind::Subscript => "Subscript", + ExprKind::Tuple => "Tuple", + ExprKind::UnaryOp => "UnaryOp", + ExprKind::Yield => "Yield", + ExprKind::YieldFrom => "YieldFrom", + } + } + + pub fn matches(self, expr: &Expr) -> bool { + match self { + ExprKind::Attribute => matches!(expr, Expr::Attribute(_)), + ExprKind::Await => matches!(expr, Expr::Await(_)), + ExprKind::BinOp => matches!(expr, Expr::BinOp(_)), + ExprKind::BoolOp => matches!(expr, Expr::BoolOp(_)), + ExprKind::BooleanLiteral => matches!(expr, Expr::BooleanLiteral(_)), + ExprKind::BytesLiteral => matches!(expr, Expr::BytesLiteral(_)), + ExprKind::Call => matches!(expr, Expr::Call(_)), + ExprKind::Compare => matches!(expr, Expr::Compare(_)), + ExprKind::Dict => matches!(expr, Expr::Dict(_)), + ExprKind::DictComp => matches!(expr, Expr::DictComp(_)), + ExprKind::EllipsisLiteral => matches!(expr, Expr::EllipsisLiteral(_)), + ExprKind::FString => matches!(expr, Expr::FString(_)), + ExprKind::TString => matches!(expr, Expr::TString(_)), + ExprKind::Generator => matches!(expr, Expr::Generator(_)), + ExprKind::If => matches!(expr, Expr::If(_)), + ExprKind::IpyEscapeCommand => matches!(expr, Expr::IpyEscapeCommand(_)), + ExprKind::Lambda => matches!(expr, Expr::Lambda(_)), + ExprKind::List => matches!(expr, Expr::List(_)), + ExprKind::ListComp => matches!(expr, Expr::ListComp(_)), + ExprKind::Name => matches!(expr, Expr::Name(_)), + ExprKind::Named => matches!(expr, Expr::Named(_)), + ExprKind::NoneLiteral => matches!(expr, Expr::NoneLiteral(_)), + ExprKind::NumberLiteral => matches!(expr, Expr::NumberLiteral(_)), + ExprKind::Set => matches!(expr, Expr::Set(_)), + ExprKind::SetComp => matches!(expr, Expr::SetComp(_)), + ExprKind::Slice => matches!(expr, Expr::Slice(_)), + ExprKind::Starred => matches!(expr, Expr::Starred(_)), + ExprKind::StringLiteral => matches!(expr, Expr::StringLiteral(_)), + ExprKind::Subscript => matches!(expr, Expr::Subscript(_)), + ExprKind::Tuple => matches!(expr, Expr::Tuple(_)), + ExprKind::UnaryOp => matches!(expr, Expr::UnaryOp(_)), + ExprKind::Yield => matches!(expr, Expr::Yield(_)), + ExprKind::YieldFrom => matches!(expr, Expr::YieldFrom(_)), + } + } +} + +impl fmt::Display for ExprKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +impl From<&Expr> for ExprKind { + fn from(value: &Expr) -> Self { + match value { + Expr::Attribute(_) => ExprKind::Attribute, + Expr::Await(_) => ExprKind::Await, + Expr::BinOp(_) => ExprKind::BinOp, + Expr::BoolOp(_) => ExprKind::BoolOp, + Expr::BooleanLiteral(_) => ExprKind::BooleanLiteral, + Expr::BytesLiteral(_) => ExprKind::BytesLiteral, + Expr::Call(_) => ExprKind::Call, + Expr::Compare(_) => ExprKind::Compare, + Expr::Dict(_) => ExprKind::Dict, + Expr::DictComp(_) => ExprKind::DictComp, + Expr::EllipsisLiteral(_) => ExprKind::EllipsisLiteral, + Expr::FString(_) => ExprKind::FString, + Expr::TString(_) => ExprKind::TString, + Expr::Generator(_) => ExprKind::Generator, + Expr::If(_) => ExprKind::If, + Expr::IpyEscapeCommand(_) => ExprKind::IpyEscapeCommand, + Expr::Lambda(_) => ExprKind::Lambda, + Expr::List(_) => ExprKind::List, + Expr::ListComp(_) => ExprKind::ListComp, + Expr::Name(_) => ExprKind::Name, + Expr::Named(_) => ExprKind::Named, + Expr::NoneLiteral(_) => ExprKind::NoneLiteral, + Expr::NumberLiteral(_) => ExprKind::NumberLiteral, + Expr::Set(_) => ExprKind::Set, + Expr::SetComp(_) => ExprKind::SetComp, + Expr::Slice(_) => ExprKind::Slice, + Expr::Starred(_) => ExprKind::Starred, + Expr::StringLiteral(_) => ExprKind::StringLiteral, + Expr::Subscript(_) => ExprKind::Subscript, + Expr::Tuple(_) => ExprKind::Tuple, + Expr::UnaryOp(_) => ExprKind::UnaryOp, + Expr::Yield(_) => ExprKind::Yield, + Expr::YieldFrom(_) => ExprKind::YieldFrom, + } + } +} + +#[derive(Debug, Error)] +pub enum AstTargetParseError { + #[error("expected `stmt:` or `expr:` target selector")] + MissingPrefix, + #[error("unknown statement selector `{0}`")] + UnknownStmtKind(String), + #[error("unknown expression selector `{0}`")] + UnknownExprKind(String), +} + +fn parse_target(raw: &str) -> Result { + let (prefix, name) = raw + .split_once(':') + .ok_or(AstTargetParseError::MissingPrefix)?; + match prefix { + "stmt" => Ok(AstTarget::Stmt(parse_stmt_kind(name)?)), + "expr" => Ok(AstTarget::Expr(parse_expr_kind(name)?)), + _ => Err(AstTargetParseError::MissingPrefix), + } +} + +fn parse_stmt_kind(name: &str) -> Result { + match name { + "FunctionDef" => Ok(StmtKind::FunctionDef), + "ClassDef" => Ok(StmtKind::ClassDef), + "Return" => Ok(StmtKind::Return), + "Delete" => Ok(StmtKind::Delete), + "TypeAlias" => Ok(StmtKind::TypeAlias), + "Assign" => Ok(StmtKind::Assign), + "AugAssign" => Ok(StmtKind::AugAssign), + "AnnAssign" => Ok(StmtKind::AnnAssign), + "For" => Ok(StmtKind::For), + "While" => Ok(StmtKind::While), + "If" => Ok(StmtKind::If), + "With" => Ok(StmtKind::With), + "Match" => Ok(StmtKind::Match), + "Raise" => Ok(StmtKind::Raise), + "Try" => Ok(StmtKind::Try), + "Assert" => Ok(StmtKind::Assert), + "Import" => Ok(StmtKind::Import), + "ImportFrom" => Ok(StmtKind::ImportFrom), + "Global" => Ok(StmtKind::Global), + "Nonlocal" => Ok(StmtKind::Nonlocal), + "Expr" => Ok(StmtKind::Expr), + "Pass" => Ok(StmtKind::Pass), + "Break" => Ok(StmtKind::Break), + "Continue" => Ok(StmtKind::Continue), + "IpyEscapeCommand" => Ok(StmtKind::IpyEscapeCommand), + other => Err(AstTargetParseError::UnknownStmtKind(other.to_string())), + } +} + +fn parse_expr_kind(name: &str) -> Result { + match name { + "Attribute" => Ok(ExprKind::Attribute), + "Await" => Ok(ExprKind::Await), + "BinOp" => Ok(ExprKind::BinOp), + "BoolOp" => Ok(ExprKind::BoolOp), + "BooleanLiteral" => Ok(ExprKind::BooleanLiteral), + "BytesLiteral" => Ok(ExprKind::BytesLiteral), + "Call" => Ok(ExprKind::Call), + "Compare" => Ok(ExprKind::Compare), + "Dict" => Ok(ExprKind::Dict), + "DictComp" => Ok(ExprKind::DictComp), + "EllipsisLiteral" => Ok(ExprKind::EllipsisLiteral), + "FString" => Ok(ExprKind::FString), + "Generator" => Ok(ExprKind::Generator), + "If" => Ok(ExprKind::If), + "IpyEscapeCommand" => Ok(ExprKind::IpyEscapeCommand), + "Lambda" => Ok(ExprKind::Lambda), + "List" => Ok(ExprKind::List), + "ListComp" => Ok(ExprKind::ListComp), + "Name" => Ok(ExprKind::Name), + "Named" => Ok(ExprKind::Named), + "NoneLiteral" => Ok(ExprKind::NoneLiteral), + "NumberLiteral" => Ok(ExprKind::NumberLiteral), + "Set" => Ok(ExprKind::Set), + "SetComp" => Ok(ExprKind::SetComp), + "Slice" => Ok(ExprKind::Slice), + "Starred" => Ok(ExprKind::Starred), + "StringLiteral" => Ok(ExprKind::StringLiteral), + "Subscript" => Ok(ExprKind::Subscript), + "Tuple" => Ok(ExprKind::Tuple), + "TString" => Ok(ExprKind::FString), + "UnaryOp" => Ok(ExprKind::UnaryOp), + "Yield" => Ok(ExprKind::Yield), + "YieldFrom" => Ok(ExprKind::YieldFrom), + other => Err(AstTargetParseError::UnknownExprKind(other.to_string())), + } +} diff --git a/crates/ruff_linter/src/external/error.rs b/crates/ruff_linter/src/external/error.rs new file mode 100644 index 0000000000000..8946823e1313e --- /dev/null +++ b/crates/ruff_linter/src/external/error.rs @@ -0,0 +1,135 @@ +use std::path::PathBuf; + +use thiserror::Error; + +use crate::external::ast::target::AstTargetParseError; + +#[derive(Debug, Error)] +pub enum ExternalLinterError { + #[error("failed to read external linter definition `{path}`: {source}")] + Io { + path: PathBuf, + #[source] + source: std::io::Error, + }, + + #[error("failed to parse external linter definition `{path}`: {source}")] + Parse { + path: PathBuf, + #[source] + source: toml::de::Error, + }, + + #[error("invalid rule code `{code}` for external linter `{linter}`")] + InvalidRuleCode { linter: String, code: String }, + + #[error("unknown AST target `{target}` for external rule `{rule}` in linter `{linter}`")] + // Targets must expand to one of the supported StmtKind or ExprKind enums; anything else is rejected. + UnknownTarget { + linter: String, + rule: String, + target: String, + #[source] + source: AstTargetParseError, + }, + + #[error("duplicate rule code `{code}` in external linter `{linter}`")] + DuplicateRule { linter: String, code: String }, + + #[error("duplicate external linter identifier `{id}`")] + DuplicateLinter { id: String }, + + #[error("external linter `{id}` defines no rules")] + EmptyLinter { id: String }, + + #[error("external rule `{rule}` in linter `{linter}` must declare at least one AST target")] + MissingTargets { linter: String, rule: String }, + + #[error( + "failed to read script `{path}` for external rule `{rule}` in linter `{linter}`: {source}" + )] + ScriptIo { + linter: String, + rule: String, + path: PathBuf, + #[source] + source: std::io::Error, + }, + + #[error("no script body provided for external rule `{rule}` in linter `{linter}`")] + // Raised when we read a script file but it is empty or whitespace-only. + MissingScriptBody { linter: String, rule: String }, + + #[error( + "invalid `call-callee-regex` `{pattern}` for external rule `{rule}` in linter `{linter}`: {source}" + )] + InvalidCallCalleeRegex { + linter: String, + rule: String, + pattern: String, + #[source] + source: regex::Error, + }, + + #[error( + "external rule `{rule}` in linter `{linter}` declares `call-callee-regex` but does not target `expr:Call` nodes" + )] + CallCalleeRegexWithoutCallTarget { linter: String, rule: String }, + + #[error("{message}")] + ScriptCompile { message: String }, + + #[error( + "missing `{handler}` handler for external rule `{rule}` in linter `{linter}` (required to process {target})" + )] + MissingHandler { + linter: String, + rule: String, + handler: String, + target: String, + }, + + #[error( + "handler `{handler}` for external rule `{rule}` in linter `{linter}` has an incompatible signature: {details}" + )] + InvalidHandler { + linter: String, + rule: String, + handler: String, + details: String, + }, + + #[error("runtime error while executing external rule `{rule}` in linter `{linter}`: {message}")] + Runtime { + linter: String, + rule: String, + message: String, + }, + + #[error( + "invalid diagnostic reported by external rule `{rule}` in linter `{linter}`: {message}" + )] + InvalidReport { + linter: String, + rule: String, + message: String, + }, +} + +impl ExternalLinterError { + #[cfg_attr(not(feature = "ext-lint"), allow(dead_code))] + pub(crate) fn format_script_compile_message( + linter: &str, + rule: &str, + path: Option, + message: impl Into, + ) -> String { + let message = message.into(); + let location = path + .map(|p| format!(" at {}", p.display())) + .unwrap_or_default(); + format!( + "failed to compile script for external rule `{rule}` in linter `{linter}`{location}: {message}" + ) + } +} diff --git a/crates/ruff_linter/src/external/mod.rs b/crates/ruff_linter/src/external/mod.rs new file mode 100644 index 0000000000000..0a885e1ec9193 --- /dev/null +++ b/crates/ruff_linter/src/external/mod.rs @@ -0,0 +1,28 @@ +pub mod ast; +pub mod error; +pub mod runtime; + +use serde::Deserialize; +use std::path::PathBuf; + +fn default_true() -> bool { + true +} + +#[derive(Debug, Clone, Deserialize)] +pub struct PyprojectExternalLinterEntry { + pub toml_path: PathBuf, + #[serde(default = "default_true")] + pub enabled: bool, +} + +pub use ast::definition::ExternalAstLinterFile; +pub use ast::loader::{load_linter_from_entry, load_linter_into_registry}; +pub use ast::registry::{ExternalLintRegistry, RuleLocator}; +pub use ast::rule::{ + ExternalAstLinter, ExternalAstRule, ExternalAstRuleSpec, ExternalRuleCode, ExternalRuleScript, +}; +pub use ast::target::{AstNodeClass, AstTarget, AstTargetSpec, ExprKind, StmtKind}; +pub use error::ExternalLinterError; +pub use runtime::ExternalLintRuntimeHandle; +pub use runtime::verify_registry_scripts; diff --git a/crates/ruff_linter/src/external/runtime/mod.rs b/crates/ruff_linter/src/external/runtime/mod.rs new file mode 100644 index 0000000000000..c9b189fcd25fb --- /dev/null +++ b/crates/ruff_linter/src/external/runtime/mod.rs @@ -0,0 +1,107 @@ +use std::fmt; + +use crate::checkers::ast::Checker; +use crate::external::ast::registry::ExternalLintRegistry; +use ruff_python_ast::{Expr, Stmt}; + +#[cfg(not(feature = "ext-lint"))] +mod fallback { + use std::sync::Arc; + + use super::Checker; + use crate::external::ExternalLinterError; + use crate::external::ast::registry::ExternalLintRegistry; + use ruff_python_ast::{Expr, Stmt}; + + #[derive(Clone, Debug)] + pub(crate) struct ExternalLintRuntime { + registry: Arc, + } + + impl ExternalLintRuntime { + pub(crate) fn new(registry: ExternalLintRegistry) -> Self { + Self { + registry: Arc::new(registry), + } + } + + pub(crate) fn registry(&self) -> &ExternalLintRegistry { + &self.registry + } + + #[allow(clippy::unused_self)] + pub(crate) fn run_on_stmt(&self, _checker: &Checker<'_>, _stmt: &Stmt) {} + + #[allow(clippy::unused_self)] + pub(crate) fn run_on_expr(&self, _checker: &Checker<'_>, _expr: &Expr) {} + + #[allow(clippy::unused_self)] + pub(crate) fn run_in_session(&self, f: F) -> R + where + F: FnOnce() -> R, + { + f() + } + } + + pub fn verify_registry_scripts( + _registry: &ExternalLintRegistry, + ) -> Result<(), ExternalLinterError> { + Ok(()) + } +} + +#[cfg(feature = "ext-lint")] +mod python; + +#[cfg(not(feature = "ext-lint"))] +use fallback as imp; +#[cfg(feature = "ext-lint")] +use python as imp; + +pub(crate) use imp::ExternalLintRuntime; +pub use imp::verify_registry_scripts; + +#[derive(Clone)] +pub struct ExternalLintRuntimeHandle { + runtime: ExternalLintRuntime, +} + +#[cfg_attr(not(feature = "ext-lint"), allow(dead_code))] +impl ExternalLintRuntimeHandle { + pub fn new(registry: ExternalLintRegistry) -> Self { + Self { + runtime: ExternalLintRuntime::new(registry), + } + } + + pub fn registry(&self) -> &ExternalLintRegistry { + self.runtime.registry() + } + + #[cfg_attr(not(feature = "ext-lint"), allow(dead_code))] + pub(crate) fn run_on_stmt(&self, checker: &Checker<'_>, stmt: &Stmt) { + self.runtime.run_on_stmt(checker, stmt); + } + + #[cfg_attr(not(feature = "ext-lint"), allow(dead_code))] + pub(crate) fn run_on_expr(&self, checker: &Checker<'_>, expr: &Expr) { + self.runtime.run_on_expr(checker, expr); + } + + pub(crate) fn run_in_session(&self, f: F) -> R + where + F: FnOnce() -> R, + { + self.runtime.run_in_session(f) + } +} + +impl fmt::Debug for ExternalLintRuntimeHandle { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut debug = f.debug_struct("ExternalLintRuntimeHandle"); + debug.field("registry", self.runtime.registry()); + debug.field("runtime", &self.runtime); + debug.finish() + } +} diff --git a/crates/ruff_linter/src/external/runtime/python.rs b/crates/ruff_linter/src/external/runtime/python.rs new file mode 100644 index 0000000000000..51e5ddb7affce --- /dev/null +++ b/crates/ruff_linter/src/external/runtime/python.rs @@ -0,0 +1,721 @@ +#[cfg(not(Py_GIL_DISABLED))] +compile_error!( + "The external runtime now assumes a free-threaded CPython build. \ + Rebuild PyO3 with `UNSAFE_PYO3_BUILD_FREE_THREADED=1` so that `Py_GIL_DISABLED` is set." +); +thread_local! { + static PY_SESSION_DEPTH: Cell = const { Cell::new(0) }; +} + +use std::cell::{Cell, RefCell}; +use std::ffi::CString; +use std::fmt; +use std::hash::Hasher; +use std::sync::{Arc, Mutex, OnceLock}; + +use crate::checkers::ast::Checker; +use crate::external::RuleLocator; +use crate::external::ast::python::store::{AstStoreHandle, with_store}; +use crate::external::ast::python::{ + ModuleTypes, ProjectionTypesRef, expr_to_python, load_module_types, source, stmt_to_python, +}; +use crate::external::ast::registry::ExternalLintRegistry; +use crate::external::ast::target::{AstTarget, ExprKind, StmtKind}; +use crate::external::error::ExternalLinterError; +use crate::warn_user; +use pyo3::prelude::*; +use pyo3::types::{PyAny, PyModule}; +use ruff_cache::{CacheKey, CacheKeyHasher}; +use ruff_python_ast::name::UnqualifiedName; +use ruff_python_ast::{Expr, Stmt}; +use ruff_source_file::SourceFile; +use ruff_text_size::{Ranged, TextRange}; +use rustc_hash::{FxHashMap, FxHashSet}; + +thread_local! { + static RUNTIME_CACHE: RefCell> = + RefCell::new(FxHashMap::default()); +} + +static VERIFIED_REGISTRIES: OnceLock>> = OnceLock::new(); + +#[derive(Debug)] +pub(crate) struct ExternalRuleHandle { + check_stmt: Option>, + check_expr: Option>, +} + +#[derive(Clone, Debug)] +pub(crate) struct RuntimeEnvironment { + module_types: Arc, +} + +impl RuntimeEnvironment { + fn new(module_types: ModuleTypes) -> Self { + Self { + module_types: Arc::new(module_types), + } + } + + fn module_types(&self) -> Arc { + Arc::clone(&self.module_types) + } +} + +pub(crate) type CompiledCodeMap = FxHashMap; + +#[derive(Clone)] +pub(crate) struct ExternalLintRuntime { + registry: Arc, + runtime_cache: RuntimeCache, +} + +impl ExternalLintRuntime { + pub(crate) fn new(registry: ExternalLintRegistry) -> Self { + let mut hasher = CacheKeyHasher::new(); + registry.cache_key(&mut hasher); + let pool_id = hasher.finish(); + ensure_registry_verified(pool_id, ®istry); + + let registry = Arc::new(registry); + Self { + runtime_cache: RuntimeCache::new(pool_id), + registry, + } + } + + pub(crate) fn registry(&self) -> &ExternalLintRegistry { + self.registry.as_ref() + } + + pub(crate) fn run_on_stmt(&self, checker: &Checker<'_>, stmt: &Stmt) { + self.run_on_stmt_with_kind(checker, StmtKind::from(stmt), stmt); + } + + pub(crate) fn run_on_expr(&self, checker: &Checker<'_>, expr: &Expr) { + self.run_on_expr_with_kind(checker, ExprKind::from(expr), expr); + } + + #[allow(clippy::unused_self)] + pub(crate) fn run_in_session(&self, f: F) -> R + where + F: FnOnce() -> R, + { + with_attached_python(|_| f()) + } + + fn run_on_stmt_with_kind(&self, checker: &Checker<'_>, kind: StmtKind, stmt: &Stmt) { + let locators: Vec<_> = self.registry.rules_for_stmt(kind).collect(); + self.dispatch_rules( + checker, + stmt, + &locators, + |_| true, + |handle| handle.check_stmt.as_ref(), + stmt_to_python, + ); + } + + fn run_on_expr_with_kind(&self, checker: &Checker<'_>, kind: ExprKind, expr: &Expr) { + let locators: Vec<_> = self.registry.rules_for_expr(kind).collect(); + let mut call_callee_cache = CachedCallee::default(); + self.dispatch_rules( + checker, + expr, + &locators, + |rule| rule_applicable_to_expr(rule, kind, expr, &mut call_callee_cache), + |handle| handle.check_expr.as_ref(), + expr_to_python, + ); + } + + fn dispatch_rules<'node, Node>( + &self, + checker: &Checker<'_>, + node: &'node Node, + locators: &[RuleLocator], + mut should_run: impl FnMut(&crate::external::ast::rule::ExternalAstRule) -> bool, + get_callback: impl Fn(&ExternalRuleHandle) -> Option<&Py>, + convert: impl Fn( + Python<'_>, + &crate::Locator<'_>, + &'node Node, + ProjectionTypesRef, + ) -> PyResult, + ) where + Node: Ranged + 'node, + { + if locators.is_empty() { + return; + } + + with_attached_python(|py| { + self.runtime_cache + .with_runtime(self.registry.as_ref(), |environment, compiled| { + let module_types = environment.module_types(); + let context = ExternalCheckerContext::new(checker, module_types.projection); + context.with_checker_context(|| { + for &rule_locator in locators { + let (_linter, rule) = self.registry.expect_entry(rule_locator); + if !should_run(rule) { + continue; + } + if let Some(handle) = compiled.get(&rule_locator) { + if let Some(callback) = get_callback(handle) { + let result = (|| -> PyResult<()> { + let py_node = convert( + py, + checker.locator(), + node, + context.projection(), + )?; + let runtime_context = build_context( + py, + module_types.as_ref(), + rule, + node.range(), + )?; + let outcome = callback + .call1(py, (py_node, runtime_context.context(py))); + runtime_context.flush(py, checker); + outcome.map(|_| ()) + })(); + if let Err(err) = result { + self.report_python_error(py, rule_locator, &err); + } + } + } + } + }); + }); + }); + } + + fn report_python_error(&self, py: Python<'_>, locator: RuleLocator, err: &PyErr) { + let (linter, rule) = self.registry.expect_entry(locator); + warn_user!( + "Error while executing external rule `{}` in linter `{}`: {err}", + rule.code.as_str(), + linter.id + ); + err.print(py); + } +} + +impl fmt::Debug for ExternalLintRuntime { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut debug = f.debug_struct("ExternalLintRuntime"); + debug.field("registry", &self.registry); + debug.field("runtime_cache", &self.runtime_cache); + debug.finish() + } +} + +struct RegistryRuntime { + environment: RuntimeEnvironment, + compiled: CompiledCodeMap, +} + +impl fmt::Debug for RegistryRuntime { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("RegistryRuntime") + .field("environment", &"RuntimeEnvironment") + .field("compiled_rules", &self.compiled.len()) + .finish() + } +} + +#[derive(Clone, Copy, Debug)] +struct RuntimeCache { + id: u64, +} + +impl RuntimeCache { + fn new(id: u64) -> Self { + Self { id } + } + + fn with_runtime(self, registry: &ExternalLintRegistry, f: F) -> R + where + F: FnOnce(&RuntimeEnvironment, &CompiledCodeMap) -> R, + { + RUNTIME_CACHE.with(|cache| { + let mut cache = cache.borrow_mut(); + let entry = cache.entry(self.id).or_insert_with(|| { + create_registry_runtime(registry).unwrap_or_else(|error| { + panic!("failed to initialize external linter runtime: {error}") + }) + }); + f(&entry.environment, &entry.compiled) + }) + } +} + +fn create_registry_runtime( + registry: &ExternalLintRegistry, +) -> Result { + let (module_types, compiled) = compile_scripts(registry)?; + let environment = RuntimeEnvironment::new(module_types); + Ok(RegistryRuntime { + environment, + compiled, + }) +} + +fn ensure_python_initialized() { + static PYTHON_INIT: OnceLock<()> = OnceLock::new(); + PYTHON_INIT.get_or_init(|| { + pyo3::prepare_freethreaded_python(); + }); +} + +#[allow(unsafe_code)] +fn with_attached_python(f: F) -> R +where + F: for<'py> FnOnce(Python<'py>) -> R, +{ + struct DepthGuard<'a>(&'a Cell); + impl Drop for DepthGuard<'_> { + fn drop(&mut self) { + let current = self.0.get(); + debug_assert!(current > 0); + self.0.set(current - 1); + } + } + + ensure_python_initialized(); + + PY_SESSION_DEPTH.with(|depth| { + if depth.get() > 0 { + unsafe { f(Python::assume_gil_acquired()) } + } else { + Python::with_gil(|py| { + depth.set(1); + let _guard = DepthGuard(depth); + f(py) + }) + } + }) +} + +fn compile_scripts( + registry: &ExternalLintRegistry, +) -> Result<(ModuleTypes, CompiledCodeMap), ExternalLinterError> { + with_attached_python(|py| -> Result<_, ExternalLinterError> { + let module_types = + load_module_types(py).map_err(|err| ExternalLinterError::ScriptCompile { + message: format!("failed to initialize external runtime module: {err}"), + })?; + + let mut compiled = FxHashMap::default(); + let mut errors = Vec::new(); + + for locator in registry.iter_enabled_rule_locators() { + let (linter, rule) = registry.expect_entry(locator); + let module_name = format!("ruff_external_{}_{}", linter.id, rule.code.as_str()); + let Ok(code_cstr) = CString::new(rule.script.body()) else { + errors.push(ExternalLinterError::format_script_compile_message( + linter.id.as_ref(), + rule.code.as_str(), + Some(rule.script.path().to_path_buf()), + "script body contains an interior NUL byte", + )); + continue; + }; + let file_name_owned = rule.script.path().to_string_lossy(); + let Ok(file_cstr) = CString::new(file_name_owned.as_ref()) else { + errors.push(ExternalLinterError::format_script_compile_message( + linter.id.as_ref(), + rule.code.as_str(), + Some(rule.script.path().to_path_buf()), + "script path contains an interior NUL byte", + )); + continue; + }; + let Ok(module_cstr) = CString::new(module_name) else { + errors.push(ExternalLinterError::format_script_compile_message( + linter.id.as_ref(), + rule.code.as_str(), + Some(rule.script.path().to_path_buf()), + "module name contains an interior NUL byte", + )); + continue; + }; + match PyModule::from_code( + py, + code_cstr.as_c_str(), + file_cstr.as_c_str(), + module_cstr.as_c_str(), + ) { + Ok(module) => match build_rule_handle(&module, linter.id.as_ref(), rule) { + Ok(handle) => { + compiled.insert(locator, handle); + } + Err(err) => errors.push(err), + }, + Err(err) => errors.push(ExternalLinterError::format_script_compile_message( + linter.id.as_ref(), + rule.name.as_ref(), + Some(rule.script.path().to_path_buf()), + err.to_string(), + )), + } + } + + if !errors.is_empty() { + return Err(ExternalLinterError::ScriptCompile { + message: errors.join("\n"), + }); + } + + Ok((module_types, compiled)) + }) +} + +fn build_rule_handle( + module: &Bound<'_, PyModule>, + linter: &str, + rule: &crate::external::ast::rule::ExternalAstRule, +) -> Result { + let needs_stmt = rule + .targets + .iter() + .any(|target| matches!(target, AstTarget::Stmt(_))); + let needs_expr = rule + .targets + .iter() + .any(|target| matches!(target, AstTarget::Expr(_))); + + let check_stmt = lookup_callable(module, "check_stmt"); + let check_expr = lookup_callable(module, "check_expr"); + + if needs_stmt && check_stmt.is_none() { + return Err(ExternalLinterError::MissingHandler { + linter: linter.to_string(), + rule: rule.name.clone(), + handler: "check_stmt".to_string(), + target: "stmt".to_string(), + } + .to_string()); + } + + if needs_expr && check_expr.is_none() { + return Err(ExternalLinterError::MissingHandler { + linter: linter.to_string(), + rule: rule.name.clone(), + handler: "check_expr".to_string(), + target: "expr".to_string(), + } + .to_string()); + } + + Ok(ExternalRuleHandle { + check_stmt, + check_expr, + }) +} + +fn lookup_callable(module: &Bound<'_, PyModule>, name: &str) -> Option> { + match module.getattr(name) { + Ok(value) if value.is_callable() => Some(value.into_any().unbind()), + _ => None, + } +} + +struct RuntimeContext { + context: PyObject, + reporter: Py, +} + +impl RuntimeContext { + fn context(&self, py: Python<'_>) -> PyObject { + self.context.clone_ref(py) + } + + fn flush(&self, py: Python<'_>, checker: &Checker<'_>) { + let reporter = self.reporter.bind(py); + reporter.borrow().drain_into(checker); + } +} + +/// Per-checker state used when converting nodes and building contexts. +struct ExternalCheckerContext { + store: AstStoreHandle, + source_file: SourceFile, + projection: ProjectionTypesRef, +} + +impl ExternalCheckerContext { + fn new(checker: &Checker<'_>, projection: ProjectionTypesRef) -> Self { + let source_file = checker.owned_source_file(); + Self { + store: AstStoreHandle::new(), + source_file, + projection, + } + } + + fn projection(&self) -> ProjectionTypesRef { + self.projection + } + + fn store(&self) -> AstStoreHandle { + self.store.clone() + } + + fn source_file(&self) -> &SourceFile { + &self.source_file + } + + fn with_checker_context(&self, f: impl FnOnce() -> R) -> R { + with_store(self.store(), || { + source::with_source_file(self.source_file(), f) + }) + } +} + +impl Drop for ExternalCheckerContext { + fn drop(&mut self) { + // Ensure any Python nodes retained past the dispatch observe an invalid store rather + // than dereferencing freed AST data. + self.store.invalidate(); + } +} + +fn build_context( + py: Python<'_>, + module_types: &ModuleTypes, + rule: &crate::external::ast::rule::ExternalAstRule, + range: TextRange, +) -> PyResult { + let reporter = PyReporter::new(py, rule, range)?; + let context = module_types + .context + .bind(py) + .call1(( + rule.code.as_str(), + rule.name.as_str(), + reporter.clone_ref(py), + ))? + .into(); + + Ok(RuntimeContext { context, reporter }) +} + +fn rule_applicable_to_expr( + rule: &crate::external::ast::rule::ExternalAstRule, + kind: ExprKind, + expr: &Expr, + call_callee_cache: &mut CachedCallee, +) -> bool { + match rule.call_callee() { + Some(matcher) => { + if kind != ExprKind::Call { + return false; + } + + let callee = call_callee_cache.resolve(expr); + match callee { + Some(callee) => matcher.regex().is_match(callee), + None => false, + } + } + None => true, + } +} + +fn extract_call_callee(expr: &Expr) -> Option { + let call = expr.as_call_expr()?; + UnqualifiedName::from_expr(call.func.as_ref()).map(|name| name.to_string()) +} + +#[derive(Default)] +struct CachedCallee { + cached: CachedValue, +} + +#[derive(Default)] +enum CachedValue { + #[default] + Unknown, + Known(Option), +} + +impl CachedCallee { + fn resolve<'expr>(&'expr mut self, expr: &Expr) -> Option<&'expr str> { + if matches!(self.cached, CachedValue::Unknown) { + self.cached = CachedValue::Known(extract_call_callee(expr)); + } + + match self.cached { + CachedValue::Known(Some(ref value)) => Some(value.as_str()), + _ => None, + } + } +} + +fn ensure_registry_verified(id: u64, registry: &ExternalLintRegistry) { + let cache = VERIFIED_REGISTRIES.get_or_init(|| Mutex::new(FxHashSet::default())); + let mut cache = cache.lock().expect("verification cache poisoned"); + if cache.contains(&id) { + return; + } + verify_registry_scripts(registry) + .unwrap_or_else(|error| panic!("failed to compile external scripts: {error}")); + cache.insert(id); +} + +pub fn verify_registry_scripts(registry: &ExternalLintRegistry) -> Result<(), ExternalLinterError> { + compile_scripts(registry).map(|_| ()) +} + +mod reporter { + #![allow(unsafe_op_in_unsafe_fn)] + + use crate::checkers::ast::Checker; + use crate::rules::ruff::rules::external_ast::ExternalLinter as ExternalLinterViolation; + use pyo3::prelude::*; + use ruff_db::diagnostic::SecondaryCode; + use ruff_text_size::{TextRange, TextSize}; + use std::cell::RefCell; + + #[pyclass(module = "ruff_external", unsendable)] + pub(crate) struct PyReporter { + diagnostics: RefCell>, + rule_code: String, + rule_name: String, + default_span: (u32, u32), + } + + #[derive(Debug)] + struct PendingDiagnostic { + message: String, + span: Option<(u32, u32)>, + } + + impl PyReporter { + pub(crate) fn new( + py: Python<'_>, + rule: &crate::external::ast::rule::ExternalAstRule, + range: TextRange, + ) -> PyResult> { + Py::new( + py, + PyReporter { + diagnostics: RefCell::new(Vec::new()), + rule_code: rule.code.as_str().to_string(), + rule_name: rule.name.clone(), + default_span: (range.start().to_u32(), range.end().to_u32()), + }, + ) + } + + pub(crate) fn drain_into(&self, checker: &Checker<'_>) { + let pending = std::mem::take(&mut *self.diagnostics.borrow_mut()); + for diagnostic in pending { + let range = self.resolve_span(diagnostic.span); + let mut emitted = checker.report_diagnostic( + ExternalLinterViolation { + rule_name: self.rule_name.clone(), + message: diagnostic.message, + }, + range, + ); + emitted.set_secondary_code(SecondaryCode::new(self.rule_code.clone())); + } + } + + fn resolve_span(&self, span: Option<(u32, u32)>) -> TextRange { + let (start, end) = match span { + Some((start, end)) if end >= start => (start, end), + _ => self.default_span, + }; + TextRange::new(TextSize::new(start), TextSize::new(end)) + } + } + + #[pymethods] + impl PyReporter { + #[pyo3(signature = (message, span=None))] + fn __call__(&self, message: &str, span: Option<(u32, u32)>) { + self.diagnostics.borrow_mut().push(PendingDiagnostic { + message: message.to_string(), + span, + }); + } + } +} + +use reporter::PyReporter; + +#[cfg(test)] +mod tests { + use super::*; + use std::path::PathBuf; + + use crate::external::ast::rule::{ + ExternalAstLinter, ExternalAstRule, ExternalRuleCode, ExternalRuleScript, + }; + use crate::external::ast::target::{AstTarget, StmtKind}; + + fn basic_rule(code: &str, script: ExternalRuleScript) -> ExternalAstRule { + ExternalAstRule::new( + ExternalRuleCode::new(code).unwrap(), + "ExampleRule", + None::<&str>, + vec![AstTarget::Stmt(StmtKind::FunctionDef)], + script, + None, + ) + } + + #[test] + fn interpreter_runs_basic_code() { + let runtime = ExternalLintRuntime::new(ExternalLintRegistry::new()); + runtime + .runtime_cache + .with_runtime(runtime.registry(), |_, _| { + with_attached_python(|py| { + let code = CString::new("40 + 2").unwrap(); + let result: i32 = py + .eval(code.as_c_str(), None, None) + .unwrap() + .extract() + .unwrap(); + assert_eq!(result, 42); + }); + }); + } + + #[test] + fn compile_errors_surface_during_validation() { + let mut registry = ExternalLintRegistry::new(); + let rule = basic_rule( + "EXT001", + ExternalRuleScript::file(PathBuf::from("broken.py"), "def broken(:\n"), + ); + let linter = ExternalAstLinter::new("broken", "Broken", None::<&str>, true, vec![rule]); + registry.insert_linter(linter).unwrap(); + + let other_rule = basic_rule( + "EXT002", + ExternalRuleScript::file(PathBuf::from("other.py"), "def also_broken(:\n"), + ); + let other_linter = + ExternalAstLinter::new("other", "Other", None::<&str>, true, vec![other_rule]); + registry.insert_linter(other_linter).unwrap(); + + let err = verify_registry_scripts(®istry).expect_err("expected compile failure"); + match err { + ExternalLinterError::ScriptCompile { message } => { + assert!(message.contains("broken"), "message: {message}"); + assert!(message.contains("other"), "message: {message}"); + assert!( + message.lines().count() >= 2, + "expected multiple lines: {message}" + ); + } + other => panic!("unexpected error: {other:?}"), + } + } +} diff --git a/crates/ruff_linter/src/lib.rs b/crates/ruff_linter/src/lib.rs index eaafd7a526713..58d9248441853 100644 --- a/crates/ruff_linter/src/lib.rs +++ b/crates/ruff_linter/src/lib.rs @@ -26,6 +26,7 @@ mod cst; pub mod directives; mod doc_lines; mod docstrings; +pub mod external; mod fix; pub mod fs; mod importer; diff --git a/crates/ruff_linter/src/locator.rs b/crates/ruff_linter/src/locator.rs index 40653e672ebb6..b1e75dc38c3bd 100644 --- a/crates/ruff_linter/src/locator.rs +++ b/crates/ruff_linter/src/locator.rs @@ -26,6 +26,10 @@ impl<'a> Locator<'a> { } } + pub fn as_str(&self) -> &'a str { + self.contents + } + #[deprecated( note = "This is expensive, avoid using outside of the diagnostic phase. Prefer the other `Locator` methods instead." )] diff --git a/crates/ruff_linter/src/rule_selector.rs b/crates/ruff_linter/src/rule_selector.rs index b3eee0d8370b3..ecb7d6819774c 100644 --- a/crates/ruff_linter/src/rule_selector.rs +++ b/crates/ruff_linter/src/rule_selector.rs @@ -7,10 +7,15 @@ use strum_macros::EnumIter; use crate::codes::RuleIter; use crate::codes::{RuleCodePrefix, RuleGroup}; +use crate::external::ast::rule::ExternalRuleCode; use crate::registry::{Linter, Rule, RuleNamespace}; use crate::rule_redirects::get_redirect; use crate::settings::types::PreviewMode; +fn looks_like_external_rule_code(s: &str) -> bool { + ExternalRuleCode::matches_format(s) +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum RuleSelector { /// Select all rules (includes rules in preview if enabled) @@ -33,6 +38,8 @@ pub enum RuleSelector { prefix: RuleCodePrefix, redirected_from: Option<&'static str>, }, + /// Select an external rule code. + External { code: Box }, } impl From for RuleSelector { @@ -60,6 +67,11 @@ impl FromStr for RuleSelector { fn from_str(s: &str) -> Result { // **Changes should be reflected in `parse_no_redirect` as well** + // External AST rules reserve the `EXT` prefix; short-circuit before we + // attempt to interpret the selector as a built-in linter code. + if s.starts_with("EXT") && ExternalRuleCode::new(s).is_ok() { + return Ok(Self::External { code: s.into() }); + } match s { "ALL" => Ok(Self::All), "C" => Ok(Self::C), @@ -70,8 +82,16 @@ impl FromStr for RuleSelector { None => (s, None), }; - let (linter, code) = - Linter::parse_code(s).ok_or_else(|| ParseError::Unknown(s.to_string()))?; + let Some((linter, code)) = Linter::parse_code(s) else { + if looks_like_external_rule_code(s) { + if ExternalRuleCode::new(s).is_ok() { + return Ok(Self::External { code: s.into() }); + } + + return Err(ParseError::External(s.to_string())); + } + return Err(ParseError::Unknown(s.to_string())); + }; if code.is_empty() { return Ok(Self::Linter(linter)); @@ -119,10 +139,14 @@ pub enum ParseError { // TODO(martin): tell the user how to discover rule codes via the CLI once such a command is // implemented (but that should of course be done only in ruff and not here) Unknown(String), + #[error( + "External rule selector `{0}` must be provided via `--select-external` or `lint.select-external`." + )] + External(String), } impl RuleSelector { - pub fn prefix_and_code(&self) -> (&'static str, &'static str) { + pub fn prefix_and_code(&self) -> (&str, &str) { match self { RuleSelector::All => ("", "ALL"), RuleSelector::C => ("", "C"), @@ -131,6 +155,7 @@ impl RuleSelector { (prefix.linter().common_prefix(), prefix.short_code()) } RuleSelector::Linter(l) => (l.common_prefix(), ""), + RuleSelector::External { code } => ("", code.as_ref()), } } } @@ -140,6 +165,9 @@ impl Serialize for RuleSelector { where S: serde::Serializer, { + if let RuleSelector::External { code } = self { + return serializer.serialize_str(code); + } let (prefix, code) = self.prefix_and_code(); serializer.serialize_str(&format!("{prefix}{code}")) } @@ -174,7 +202,14 @@ impl Visitor<'_> for SelectorVisitor { where E: de::Error, { - FromStr::from_str(v).map_err(de::Error::custom) + match FromStr::from_str(v) { + Ok(value) => Ok(value), + Err(err @ ParseError::External(_)) => Err(de::Error::custom(err.to_string())), + Err(err) if looks_like_external_rule_code(v) => Err(de::Error::custom(format!( + "{err}. External rule selectors must be provided via `lint.select-external`." + ))), + Err(err) => Err(de::Error::custom(err)), + } } } @@ -198,6 +233,9 @@ impl RuleSelector { RuleSelector::Prefix { prefix, .. } | RuleSelector::Rule { prefix, .. } => { RuleSelectorIter::Vec(prefix.clone().rules()) } + RuleSelector::External { .. } => { + RuleSelectorIter::Vec(vec![Rule::ExternalLinter].into_iter()) + } } } @@ -224,7 +262,7 @@ impl RuleSelector { /// Returns true if this selector is exact i.e. selects a single rule by code pub fn is_exact(&self) -> bool { - matches!(self, Self::Rule { .. }) + matches!(self, Self::Rule { .. } | Self::External { .. }) } } @@ -337,6 +375,7 @@ impl RuleSelector { RuleSelector::C => Specificity::LinterGroup, RuleSelector::Linter(..) => Specificity::Linter, RuleSelector::Rule { .. } => Specificity::Rule, + RuleSelector::External { .. } => Specificity::Rule, RuleSelector::Prefix { prefix, .. } => { let prefix: &'static str = prefix.short_code(); match prefix.len() { @@ -355,13 +394,26 @@ impl RuleSelector { /// Parse [`RuleSelector`] from a string; but do not follow redirects. pub fn parse_no_redirect(s: &str) -> Result { // **Changes should be reflected in `from_str` as well** + // External AST rules reserve the `EXT` prefix; short-circuit before we + // attempt to interpret the selector as a built-in linter code. + if s.starts_with("EXT") && ExternalRuleCode::new(s).is_ok() { + return Ok(Self::External { code: s.into() }); + } match s { "ALL" => Ok(Self::All), "C" => Ok(Self::C), "T" => Ok(Self::T), _ => { - let (linter, code) = - Linter::parse_code(s).ok_or_else(|| ParseError::Unknown(s.to_string()))?; + let Some((linter, code)) = Linter::parse_code(s) else { + if looks_like_external_rule_code(s) { + if ExternalRuleCode::new(s).is_ok() { + return Ok(Self::External { code: s.into() }); + } + + return Err(ParseError::External(s.to_string())); + } + return Err(ParseError::Unknown(s.to_string())); + }; if code.is_empty() { return Ok(Self::Linter(linter)); @@ -415,7 +467,7 @@ pub mod clap_completion { RuleSelector, codes::RuleCodePrefix, registry::{Linter, RuleNamespace}, - rule_selector::is_single_rule_selector, + rule_selector::{ParseError, is_single_rule_selector}, }; #[derive(Clone)] @@ -442,20 +494,28 @@ pub mod clap_completion { .to_str() .ok_or_else(|| clap::Error::new(clap::error::ErrorKind::InvalidUtf8))?; - value.parse().map_err(|_| { - let mut error = - clap::Error::new(clap::error::ErrorKind::ValueValidation).with_cmd(cmd); - if let Some(arg) = arg { + value.parse().map_err(|err| match err { + ParseError::External(code) => clap::Error::raw( + clap::error::ErrorKind::ValueValidation, + format!( + "External rule selector `{code}` must be provided via `--select-external`." + ), + ), + ParseError::Unknown(_) => { + let mut error = + clap::Error::new(clap::error::ErrorKind::ValueValidation).with_cmd(cmd); + if let Some(arg) = arg { + error.insert( + clap::error::ContextKind::InvalidArg, + clap::error::ContextValue::String(arg.to_string()), + ); + } error.insert( - clap::error::ContextKind::InvalidArg, - clap::error::ContextValue::String(arg.to_string()), + clap::error::ContextKind::InvalidValue, + clap::error::ContextValue::String(value.to_string()), ); + error } - error.insert( - clap::error::ContextKind::InvalidValue, - clap::error::ContextValue::String(value.to_string()), - ); - error }) } diff --git a/crates/ruff_linter/src/rules/ruff/rules/external_ast.rs b/crates/ruff_linter/src/rules/ruff/rules/external_ast.rs new file mode 100644 index 0000000000000..25ac617815794 --- /dev/null +++ b/crates/ruff_linter/src/rules/ruff/rules/external_ast.rs @@ -0,0 +1,37 @@ +use ruff_macros::{CacheKey, ViolationMetadata, derive_message_formats}; + +/// Diagnostics surfaced by external AST linters +/// +/// ## What it does +/// +/// This is a meta rule that represents any/all external rules implemented +/// in Python. See more at TODO documentation link +/// +/// ## Why is this bad? +/// +/// Depends on the rule. +/// +#[derive(Debug, Clone, PartialEq, Eq, CacheKey, ViolationMetadata)] +#[violation_metadata(stable_since = "v0.0.0")] +pub(crate) struct ExternalLinter { + pub rule_name: String, + pub message: String, +} + +impl ExternalLinter { + #[allow(dead_code)] + pub(crate) fn new(rule_name: impl Into, message: String) -> Self { + Self { + rule_name: rule_name.into(), + message, + } + } +} + +impl crate::Violation for ExternalLinter { + #[derive_message_formats] + fn message(&self) -> String { + let ExternalLinter { rule_name, message } = self; + format!("{rule_name}: {message}") + } +} diff --git a/crates/ruff_linter/src/rules/ruff/rules/mod.rs b/crates/ruff_linter/src/rules/ruff/rules/mod.rs index 206a504e740b4..c4062fe9eda36 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/mod.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/mod.rs @@ -9,6 +9,7 @@ pub(crate) use dataclass_enum::*; pub(crate) use decimal_from_float_literal::*; pub(crate) use default_factory_kwarg::*; pub(crate) use explicit_f_string_type_conversion::*; +pub(crate) use external_ast::*; pub(crate) use falsy_dict_get_fallback::*; pub(crate) use function_call_in_dataclass_default::*; pub(crate) use if_key_in_dict_del::*; @@ -74,6 +75,7 @@ mod dataclass_enum; mod decimal_from_float_literal; mod default_factory_kwarg; mod explicit_f_string_type_conversion; +pub(crate) mod external_ast; mod falsy_dict_get_fallback; mod function_call_in_dataclass_default; mod if_key_in_dict_del; diff --git a/crates/ruff_linter/src/settings/mod.rs b/crates/ruff_linter/src/settings/mod.rs index b94e4edafbc29..d7180f3ec8656 100644 --- a/crates/ruff_linter/src/settings/mod.rs +++ b/crates/ruff_linter/src/settings/mod.rs @@ -10,6 +10,7 @@ use std::sync::LazyLock; use types::CompiledPerFileTargetVersionList; use crate::codes::RuleCodePrefix; +use crate::external::ExternalLintRegistry; use ruff_macros::CacheKey; use ruff_python_ast::PythonVersion; @@ -243,6 +244,9 @@ pub struct LinterSettings { pub builtins: Vec, pub dummy_variable_rgx: Regex, pub external: Vec, + pub external_ast: Option, + pub selected_external: Vec, + pub ignored_external: Vec, pub ignore_init_module_imports: bool, pub logger_objects: Vec, pub namespace_packages: Vec, @@ -319,6 +323,9 @@ impl Display for LinterSettings { self.typing_extensions, ] } + if let Some(registry) = &self.external_ast { + writeln!(f, "linter.external-ast = {registry:#?}")?; + } writeln!(f, "\n# Linter Plugins")?; display_settings! { formatter = f, @@ -410,6 +417,9 @@ impl LinterSettings { dummy_variable_rgx: DUMMY_VARIABLE_RGX.clone(), external: vec![], + external_ast: None, + selected_external: Vec::new(), + ignored_external: Vec::new(), ignore_init_module_imports: true, logger_objects: vec![], namespace_packages: vec![], diff --git a/crates/ruff_python_ast/ast.toml b/crates/ruff_python_ast/ast.toml index cbcde4e21332c..5a989e813a882 100644 --- a/crates/ruff_python_ast/ast.toml +++ b/crates/ruff_python_ast/ast.toml @@ -60,6 +60,18 @@ # variant: # The name of the enum variant for this syntax node. Defaults to the node # name with the group prefix removed. (That is, `StmtIf` becomes `If`.) +# +# python_projection: +# An optional table that describes how this node should be exposed to Python via +# the external-linter bridge. +# * class: The Python class name to use in the generated `.pyi`. +# * eager_fields: List of field names that should be populated eagerly; every +# other field is assumed to be lazily populated through the context object. +# * metadata_fields: Optional list of extra fields to add that aren't present +# on the AST node itself. Each entry must specify: +# - name: Field name (Python and Rust) +# - rust_type: Rust type stored on the generated struct +# - stub_type: Python stub type for `.pyi` [Mod] anynode_is_label = "module" @@ -69,10 +81,16 @@ doc = "See also [mod](https://docs.python.org/3/library/ast.html#ast.mod)" doc = "See also [Module](https://docs.python.org/3/library/ast.html#ast.Module)" fields = [{ name = "body", type = "Stmt*" }] +[Mod.nodes.ModModule.python_projection] +class = "Module" + [Mod.nodes.ModExpression] doc = "See also [Module](https://docs.python.org/3/library/ast.html#ast.Module)" fields = [{ name = "body", type = "Box" }] +[Mod.nodes.ModExpression.python_projection] +class = "Expression" + [Stmt] add_suffix_to_is_methods = true anynode_is_label = "statement" @@ -93,6 +111,10 @@ fields = [ { name = "body", type = "Stmt*" }, ] +[Stmt.nodes.StmtFunctionDef.python_projection] +class = "FunctionDefStmt" +eager_fields = ["is_async", "name"] + [Stmt.nodes.StmtClassDef] doc = "See also [ClassDef](https://docs.python.org/3/library/ast.html#ast.ClassDef)" fields = [ @@ -103,14 +125,24 @@ fields = [ { name = "body", type = "Stmt*" }, ] +[Stmt.nodes.StmtClassDef.python_projection] +class = "ClassDefStmt" +eager_fields = ["name"] + [Stmt.nodes.StmtReturn] doc = "See also [Return](https://docs.python.org/3/library/ast.html#ast.Return)" fields = [{ name = "value", type = "Expr?" }] +[Stmt.nodes.StmtReturn.python_projection] +class = "ReturnStmt" + [Stmt.nodes.StmtDelete] doc = "See also [Delete](https://docs.python.org/3/library/ast.html#ast.Delete)" fields = [{ name = "targets", type = "Expr*" }] +[Stmt.nodes.StmtDelete.python_projection] +class = "DeleteStmt" + [Stmt.nodes.StmtTypeAlias] doc = "See also [TypeAlias](https://docs.python.org/3/library/ast.html#ast.TypeAlias)" fields = [ @@ -119,6 +151,9 @@ fields = [ { name = "value", type = "Expr" }, ] +[Stmt.nodes.StmtTypeAlias.python_projection] +class = "TypeAliasStmt" + [Stmt.nodes.StmtAssign] doc = "See also [Assign](https://docs.python.org/3/library/ast.html#ast.Assign)" fields = [ @@ -126,6 +161,9 @@ fields = [ { name = "value", type = "Expr" }, ] +[Stmt.nodes.StmtAssign.python_projection] +class = "AssignStmt" + [Stmt.nodes.StmtAugAssign] doc = "See also [AugAssign](https://docs.python.org/3/library/ast.html#ast.AugAssign)" fields = [ @@ -134,6 +172,10 @@ fields = [ { name = "value", type = "Expr" }, ] +[Stmt.nodes.StmtAugAssign.python_projection] +class = "AugAssignStmt" +eager_fields = ["op"] + [Stmt.nodes.StmtAnnAssign] doc = "See also [AnnAssign](https://docs.python.org/3/library/ast.html#ast.AnnAssign)" fields = [ @@ -143,6 +185,10 @@ fields = [ { name = "simple", type = "bool" }, ] +[Stmt.nodes.StmtAnnAssign.python_projection] +class = "AnnAssignStmt" +eager_fields = ["simple"] + [Stmt.nodes.StmtFor] doc = """See also [For](https://docs.python.org/3/library/ast.html#ast.For) and [AsyncFor](https://docs.python.org/3/library/ast.html#ast.AsyncFor). @@ -156,6 +202,10 @@ fields = [ { name = "orelse", type = "Stmt*" }, ] +[Stmt.nodes.StmtFor.python_projection] +class = "ForStmt" +eager_fields = ["is_async"] + [Stmt.nodes.StmtWhile] doc = """See also [While](https://docs.python.org/3/library/ast.html#ast.While) and [AsyncWhile](https://docs.python.org/3/library/ast.html#ast.AsyncWhile).""" @@ -165,6 +215,9 @@ fields = [ { name = "orelse", type = "Stmt*" }, ] +[Stmt.nodes.StmtWhile.python_projection] +class = "WhileStmt" + [Stmt.nodes.StmtIf] doc = "See also [If](https://docs.python.org/3/library/ast.html#ast.If)" fields = [ @@ -174,6 +227,10 @@ fields = [ { name = "elif_else_clauses", type = "ElifElseClause*" }, ] +[Stmt.nodes.StmtIf.python_projection] +class = "IfStmt" +eager_fields = ["test"] + [Stmt.nodes.StmtWith] doc = """See also [With](https://docs.python.org/3/library/ast.html#ast.With) and [AsyncWith](https://docs.python.org/3/library/ast.html#ast.AsyncWith). @@ -185,6 +242,10 @@ fields = [ { name = "body", type = "Stmt*" }, ] +[Stmt.nodes.StmtWith.python_projection] +class = "WithStmt" +eager_fields = ["is_async"] + [Stmt.nodes.StmtMatch] doc = "See also [Match](https://docs.python.org/3/library/ast.html#ast.Match)" fields = [ @@ -192,10 +253,16 @@ fields = [ { name = "cases", type = "MatchCase*" }, ] +[Stmt.nodes.StmtMatch.python_projection] +class = "MatchStmt" + [Stmt.nodes.StmtRaise] doc = "See also [Raise](https://docs.python.org/3/library/ast.html#ast.Raise)" fields = [{ name = "exc", type = "Expr?" }, { name = "cause", type = "Expr?" }] +[Stmt.nodes.StmtRaise.python_projection] +class = "RaiseStmt" + [Stmt.nodes.StmtTry] doc = """See also [Try](https://docs.python.org/3/library/ast.html#ast.Try) and [TryStar](https://docs.python.org/3/library/ast.html#ast.TryStar)""" @@ -207,14 +274,24 @@ fields = [ { name = "is_star", type = "bool" }, ] +[Stmt.nodes.StmtTry.python_projection] +class = "TryStmt" +eager_fields = ["is_star"] + [Stmt.nodes.StmtAssert] doc = "See also [Assert](https://docs.python.org/3/library/ast.html#ast.Assert)" fields = [{ name = "test", type = "Expr" }, { name = "msg", type = "Expr?" }] +[Stmt.nodes.StmtAssert.python_projection] +class = "AssertStmt" + [Stmt.nodes.StmtImport] doc = "See also [Import](https://docs.python.org/3/library/ast.html#ast.Import)" fields = [{ name = "names", type = "Alias*" }] +[Stmt.nodes.StmtImport.python_projection] +class = "ImportStmt" + [Stmt.nodes.StmtImportFrom] doc = "See also [ImportFrom](https://docs.python.org/3/library/ast.html#ast.ImportFrom)" fields = [ @@ -223,30 +300,52 @@ fields = [ { name = "level", type = "u32" }, ] +[Stmt.nodes.StmtImportFrom.python_projection] +class = "ImportFromStmt" +eager_fields = ["module"] + [Stmt.nodes.StmtGlobal] doc = "See also [Global](https://docs.python.org/3/library/ast.html#ast.Global)" fields = [{ name = "names", type = "Identifier*" }] +[Stmt.nodes.StmtGlobal.python_projection] +class = "GlobalStmt" + [Stmt.nodes.StmtNonlocal] doc = "See also [Nonlocal](https://docs.python.org/3/library/ast.html#ast.Nonlocal)" fields = [{ name = "names", type = "Identifier*" }] +[Stmt.nodes.StmtNonlocal.python_projection] +class = "NonlocalStmt" + [Stmt.nodes.StmtExpr] doc = "See also [Expr](https://docs.python.org/3/library/ast.html#ast.Expr)" fields = [{ name = "value", type = "Expr" }] +[Stmt.nodes.StmtExpr.python_projection] +class = "ExprStmt" + [Stmt.nodes.StmtPass] doc = "See also [Pass](https://docs.python.org/3/library/ast.html#ast.Pass)" fields = [] +[Stmt.nodes.StmtPass.python_projection] +class = "PassStmt" + [Stmt.nodes.StmtBreak] doc = "See also [Break](https://docs.python.org/3/library/ast.html#ast.Break)" fields = [] +[Stmt.nodes.StmtBreak.python_projection] +class = "BreakStmt" + [Stmt.nodes.StmtContinue] doc = "See also [Continue](https://docs.python.org/3/library/ast.html#ast.Continue)" fields = [] +[Stmt.nodes.StmtContinue.python_projection] +class = "ContinueStmt" + [Stmt.nodes.StmtIpyEscapeCommand] # TODO: remove crate:: prefix from IpyEscapeKind after it's auto generated doc = """An AST node used to represent a IPython escape command at the statement level. @@ -308,6 +407,10 @@ fields = [ { name = "value", type = "Box" }, ] +[Stmt.nodes.StmtIpyEscapeCommand.python_projection] +class = "IpyEscapeCommandStmt" +eager_fields = ["kind"] + [Expr] add_suffix_to_is_methods = true anynode_is_label = "expression" @@ -318,10 +421,17 @@ doc = "See also [BoolOp](https://docs.python.org/3/library/ast.html#ast.BoolOp)" fields = [{ name = "op", type = "BoolOp" }, { name = "values", type = "Expr*" }] custom_source_order = true +[Expr.nodes.ExprBoolOp.python_projection] +class = "BoolOpExpr" +eager_fields = ["op"] + [Expr.nodes.ExprNamed] doc = "See also [NamedExpr](https://docs.python.org/3/library/ast.html#ast.NamedExpr)" fields = [{ name = "target", type = "Expr" }, { name = "value", type = "Expr" }] +[Expr.nodes.ExprNamed.python_projection] +class = "NamedExpr" + [Expr.nodes.ExprBinOp] doc = "See also [BinOp](https://docs.python.org/3/library/ast.html#ast.BinOp)" fields = [ @@ -330,6 +440,10 @@ fields = [ { name = "right", type = "Expr" }, ] +[Expr.nodes.ExprBinOp.python_projection] +class = "BinOpExpr" +eager_fields = ["op"] + [Expr.nodes.ExprUnaryOp] doc = "See also [UnaryOp](https://docs.python.org/3/library/ast.html#ast.UnaryOp)" fields = [ @@ -337,6 +451,10 @@ fields = [ { name = "operand", type = "Expr" }, ] +[Expr.nodes.ExprUnaryOp.python_projection] +class = "UnaryOpExpr" +eager_fields = ["op"] + [Expr.nodes.ExprLambda] doc = "See also [Lambda](https://docs.python.org/3/library/ast.html#ast.Lambda)" fields = [ @@ -344,6 +462,9 @@ fields = [ { name = "body", type = "Expr" }, ] +[Expr.nodes.ExprLambda.python_projection] +class = "LambdaExpr" + [Expr.nodes.ExprIf] doc = "See also [IfExp](https://docs.python.org/3/library/ast.html#ast.IfExp)" fields = [ @@ -353,15 +474,25 @@ fields = [ ] source_order = ["body", "test", "orelse"] +[Expr.nodes.ExprIf.python_projection] +class = "IfExpr" +eager_fields = ["test"] + [Expr.nodes.ExprDict] doc = "See also [Dict](https://docs.python.org/3/library/ast.html#ast.Dict)" fields = [{ name = "items", type = "DictItem*" }] custom_source_order = true +[Expr.nodes.ExprDict.python_projection] +class = "DictExpr" + [Expr.nodes.ExprSet] doc = "See also [Set](https://docs.python.org/3/library/ast.html#ast.Set)" fields = [{ name = "elts", type = "Expr*" }] +[Expr.nodes.ExprSet.python_projection] +class = "SetExpr" + [Expr.nodes.ExprListComp] doc = "See also [ListComp](https://docs.python.org/3/library/ast.html#ast.ListComp)" fields = [ @@ -369,6 +500,9 @@ fields = [ { name = "generators", type = "Comprehension*" }, ] +[Expr.nodes.ExprListComp.python_projection] +class = "ListCompExpr" + [Expr.nodes.ExprSetComp] doc = "See also [SetComp](https://docs.python.org/3/library/ast.html#ast.SetComp)" fields = [ @@ -376,6 +510,9 @@ fields = [ { name = "generators", type = "Comprehension*" }, ] +[Expr.nodes.ExprSetComp.python_projection] +class = "SetCompExpr" + [Expr.nodes.ExprDictComp] doc = "See also [DictComp](https://docs.python.org/3/library/ast.html#ast.DictComp)" fields = [ @@ -384,6 +521,9 @@ fields = [ { name = "generators", type = "Comprehension*" }, ] +[Expr.nodes.ExprDictComp.python_projection] +class = "DictCompExpr" + [Expr.nodes.ExprGenerator] doc = "See also [GeneratorExp](https://docs.python.org/3/library/ast.html#ast.GeneratorExp)" fields = [ @@ -392,18 +532,31 @@ fields = [ { name = "parenthesized", type = "bool" }, ] +[Expr.nodes.ExprGenerator.python_projection] +class = "GeneratorExpr" +eager_fields = ["parenthesized"] + [Expr.nodes.ExprAwait] doc = "See also [Await](https://docs.python.org/3/library/ast.html#ast.Await)" fields = [{ name = "value", type = "Expr" }] +[Expr.nodes.ExprAwait.python_projection] +class = "AwaitExpr" + [Expr.nodes.ExprYield] doc = "See also [Yield](https://docs.python.org/3/library/ast.html#ast.Yield)" fields = [{ name = "value", type = "Expr?" }] +[Expr.nodes.ExprYield.python_projection] +class = "YieldExpr" + [Expr.nodes.ExprYieldFrom] doc = "See also [YieldFrom](https://docs.python.org/3/library/ast.html#ast.YieldFrom)" fields = [{ name = "value", type = "Expr" }] +[Expr.nodes.ExprYieldFrom.python_projection] +class = "YieldFromExpr" + [Expr.nodes.ExprCompare] doc = "See also [Compare](https://docs.python.org/3/library/ast.html#ast.Compare)" fields = [ @@ -414,6 +567,10 @@ fields = [ # The fields must be visited simultaneously custom_source_order = true +[Expr.nodes.ExprCompare.python_projection] +class = "CompareExpr" +eager_fields = ["ops"] + [Expr.nodes.ExprCall] doc = "See also [Call](https://docs.python.org/3/library/ast.html#ast.Call)" fields = [ @@ -421,6 +578,15 @@ fields = [ { name = "arguments", type = "Arguments" }, ] +[Expr.nodes.ExprCall.python_projection] +class = "CallExpr" +eager_fields = ["arguments"] +metadata_fields = [ + { name = "callee", rust_type = "Option", stub_type = "Optional[str]" }, + { name = "function_text", rust_type = "Option", stub_type = "Optional[str]" }, + { name = "function_kind", rust_type = "Option", stub_type = "Optional[str]" }, +] + [Expr.nodes.ExprFString] doc = """An AST node that represents either a single-part f-string literal or an implicitly concatenated f-string literal. @@ -433,6 +599,9 @@ See also [JoinedStr](https://docs.python.org/3/library/ast.html#ast.JoinedStr)"" fields = [{ name = "value", type = "FStringValue" }] custom_source_order = true +[Expr.nodes.ExprFString.python_projection] +class = "FStringExpr" + [Expr.nodes.ExprTString] doc = """An AST node that represents either a single-part t-string literal or an implicitly concatenated t-string literal. @@ -445,6 +614,9 @@ See also [TemplateStr](https://docs.python.org/3/library/ast.html#ast.TemplateSt fields = [{ name = "value", type = "TStringValue" }] custom_source_order = true +[Expr.nodes.ExprTString.python_projection] +class = "TStringExpr" + [Expr.nodes.ExprStringLiteral] doc = """An AST node that represents either a single-part string literal or an implicitly concatenated string literal.""" @@ -452,6 +624,9 @@ fields = [{ name = "value", type = "StringLiteralValue" }] # Because StringLiteralValue type is an iterator and it's not clear from the type custom_source_order = true +[Expr.nodes.ExprStringLiteral.python_projection] +class = "StringLiteralExpr" + [Expr.nodes.ExprBytesLiteral] doc = """An AST node that represents either a single-part bytestring literal or an implicitly concatenated bytestring literal.""" @@ -459,21 +634,37 @@ fields = [{ name = "value", type = "BytesLiteralValue" }] # Because BytesLiteralValue type is an iterator and it's not clear from the type custom_source_order = true +[Expr.nodes.ExprBytesLiteral.python_projection] +class = "BytesLiteralExpr" + [Expr.nodes.ExprNumberLiteral] fields = [{ name = "value", type = "Number" }] +[Expr.nodes.ExprNumberLiteral.python_projection] +class = "NumberLiteralExpr" + [Expr.nodes.ExprBooleanLiteral] fields = [{ name = "value", type = "bool" }] derives = ["Default"] +[Expr.nodes.ExprBooleanLiteral.python_projection] +class = "BooleanLiteralExpr" +eager_fields = ["value"] + [Expr.nodes.ExprNoneLiteral] fields = [] derives = ["Default"] +[Expr.nodes.ExprNoneLiteral.python_projection] +class = "NoneLiteralExpr" + [Expr.nodes.ExprEllipsisLiteral] fields = [] derives = ["Default"] +[Expr.nodes.ExprEllipsisLiteral.python_projection] +class = "EllipsisLiteralExpr" + [Expr.nodes.ExprAttribute] doc = "See also [Attribute](https://docs.python.org/3/library/ast.html#ast.Attribute)" fields = [ @@ -482,6 +673,10 @@ fields = [ { name = "ctx", type = "ExprContext" }, ] +[Expr.nodes.ExprAttribute.python_projection] +class = "AttributeExpr" +eager_fields = ["attr", "ctx"] + [Expr.nodes.ExprSubscript] doc = "See also [Subscript](https://docs.python.org/3/library/ast.html#ast.Subscript)" fields = [ @@ -490,6 +685,10 @@ fields = [ { name = "ctx", type = "ExprContext" }, ] +[Expr.nodes.ExprSubscript.python_projection] +class = "SubscriptExpr" +eager_fields = ["ctx"] + [Expr.nodes.ExprStarred] doc = "See also [Starred](https://docs.python.org/3/library/ast.html#ast.Starred)" fields = [ @@ -497,6 +696,10 @@ fields = [ { name = "ctx", type = "ExprContext" }, ] +[Expr.nodes.ExprStarred.python_projection] +class = "StarredExpr" +eager_fields = ["ctx"] + [Expr.nodes.ExprName] doc = "See also [Name](https://docs.python.org/3/library/ast.html#ast.Name)" fields = [ @@ -504,6 +707,10 @@ fields = [ { name = "ctx", type = "ExprContext" }, ] +[Expr.nodes.ExprName.python_projection] +class = "NameExpr" +eager_fields = ["ctx"] + [Expr.nodes.ExprList] doc = "See also [List](https://docs.python.org/3/library/ast.html#ast.List)" fields = [ @@ -511,6 +718,10 @@ fields = [ { name = "ctx", type = "ExprContext" }, ] +[Expr.nodes.ExprList.python_projection] +class = "ListExpr" +eager_fields = ["ctx"] + [Expr.nodes.ExprTuple] doc = "See also [Tuple](https://docs.python.org/3/library/ast.html#ast.Tuple)" fields = [ @@ -519,6 +730,10 @@ fields = [ { name = "parenthesized", type = "bool" }, ] +[Expr.nodes.ExprTuple.python_projection] +class = "TupleExpr" +eager_fields = ["ctx", "parenthesized"] + [Expr.nodes.ExprSlice] doc = "See also [Slice](https://docs.python.org/3/library/ast.html#ast.Slice)" fields = [ @@ -527,6 +742,9 @@ fields = [ { name = "step", type = "Expr?" }, ] +[Expr.nodes.ExprSlice.python_projection] +class = "SliceExpr" + [Expr.nodes.ExprIpyEscapeCommand] doc = """An AST node used to represent a IPython escape command at the expression level. @@ -545,16 +763,30 @@ fields = [ { name = "value", type = "Box" }, ] +[Expr.nodes.ExprIpyEscapeCommand.python_projection] +class = "IpyEscapeCommandExpr" +eager_fields = ["kind"] + [ExceptHandler] doc = "See also [excepthandler](https://docs.python.org/3/library/ast.html#ast.excepthandler)" -[ExceptHandler.nodes] -ExceptHandlerExceptHandler = {} +[ExceptHandler.nodes.ExceptHandlerExceptHandler] + +[ExceptHandler.nodes.ExceptHandlerExceptHandler.python_projection] +class = "ExceptHandler" -[InterpolatedStringElement.nodes] -InterpolatedElement = { variant = "Interpolation" } -InterpolatedStringLiteralElement = { variant = "Literal" } +[InterpolatedStringElement.nodes.InterpolatedElement] +variant = "Interpolation" +[InterpolatedStringElement.nodes.InterpolatedStringLiteralElement] +variant = "Literal" + + +[InterpolatedStringElement.nodes.InterpolatedElement.python_projection] +class = "InterpolatedElement" + +[InterpolatedStringElement.nodes.InterpolatedStringLiteralElement.python_projection] +class = "InterpolatedStringLiteralElement" [Pattern] doc = "See also [pattern](https://docs.python.org/3/library/ast.html#ast.pattern)" @@ -563,14 +795,23 @@ doc = "See also [pattern](https://docs.python.org/3/library/ast.html#ast.pattern doc = "See also [MatchValue](https://docs.python.org/3/library/ast.html#ast.MatchValue)" fields = [{ name = "value", type = "Box" }] +[Pattern.nodes.PatternMatchValue.python_projection] +class = "MatchValuePattern" + [Pattern.nodes.PatternMatchSingleton] doc = "See also [MatchSingleton](https://docs.python.org/3/library/ast.html#ast.MatchSingleton)" fields = [{ name = "value", type = "Singleton" }] +[Pattern.nodes.PatternMatchSingleton.python_projection] +class = "MatchSingletonPattern" + [Pattern.nodes.PatternMatchSequence] doc = "See also [MatchSequence](https://docs.python.org/3/library/ast.html#ast.MatchSequence)" fields = [{ name = "patterns", type = "Pattern*" }] +[Pattern.nodes.PatternMatchSequence.python_projection] +class = "MatchSequencePattern" + [Pattern.nodes.PatternMatchMapping] doc = "See also [MatchMapping](https://docs.python.org/3/library/ast.html#ast.MatchMapping)" fields = [ @@ -580,6 +821,10 @@ fields = [ ] custom_source_order = true +[Pattern.nodes.PatternMatchMapping.python_projection] +class = "MatchMappingPattern" +eager_fields = ["rest"] + [Pattern.nodes.PatternMatchClass] doc = "See also [MatchClass](https://docs.python.org/3/library/ast.html#ast.MatchClass)" fields = [ @@ -587,10 +832,17 @@ fields = [ { name = "arguments", type = "PatternArguments" }, ] +[Pattern.nodes.PatternMatchClass.python_projection] +class = "MatchClassPattern" + [Pattern.nodes.PatternMatchStar] doc = "See also [MatchStar](https://docs.python.org/3/library/ast.html#ast.MatchStar)" fields = [{ name = "name", type = "Identifier?" }] +[Pattern.nodes.PatternMatchStar.python_projection] +class = "MatchStarPattern" +eager_fields = ["name"] + [Pattern.nodes.PatternMatchAs] doc = "See also [MatchAs](https://docs.python.org/3/library/ast.html#ast.MatchAs)" fields = [ @@ -598,10 +850,17 @@ fields = [ { name = "name", type = "Identifier?" }, ] +[Pattern.nodes.PatternMatchAs.python_projection] +class = "MatchAsPattern" +eager_fields = ["name"] + [Pattern.nodes.PatternMatchOr] doc = "See also [MatchOr](https://docs.python.org/3/library/ast.html#ast.MatchOr)" fields = [{ name = "patterns", type = "Pattern*" }] +[Pattern.nodes.PatternMatchOr.python_projection] +class = "MatchOrPattern" + [TypeParam] doc = "See also [type_param](https://docs.python.org/3/library/ast.html#ast.type_param)" @@ -613,6 +872,10 @@ fields = [ { name = "default", type = "Box?" }, ] +[TypeParam.nodes.TypeParamTypeVar.python_projection] +class = "TypeVar" +eager_fields = ["name"] + [TypeParam.nodes.TypeParamTypeVarTuple] doc = "See also [TypeVarTuple](https://docs.python.org/3/library/ast.html#ast.TypeVarTuple)" fields = [ @@ -620,6 +883,10 @@ fields = [ { name = "default", type = "Box?" }, ] +[TypeParam.nodes.TypeParamTypeVarTuple.python_projection] +class = "TypeVarTuple" +eager_fields = ["name"] + [TypeParam.nodes.TypeParamParamSpec] doc = "See also [ParamSpec](https://docs.python.org/3/library/ast.html#ast.ParamSpec)" fields = [ @@ -627,24 +894,127 @@ fields = [ { name = "default", type = "Box?" }, ] -[ungrouped.nodes] -InterpolatedStringFormatSpec = {} -PatternArguments = {} -PatternKeyword = {} -Comprehension = {} -Arguments = {} -Parameters = {} -Parameter = {} -ParameterWithDefault = {} -Keyword = {} -Alias = {} -WithItem = {} -MatchCase = {} -Decorator = {} -ElifElseClause = {} -TypeParams = {} -FString = {} -TString = {} -StringLiteral = {} -BytesLiteral = {} -Identifier = {} +[TypeParam.nodes.TypeParamParamSpec.python_projection] +class = "ParamSpec" +eager_fields = ["name"] + +[ungrouped.nodes.InterpolatedStringFormatSpec] + +[ungrouped.nodes.PatternArguments] + +[ungrouped.nodes.PatternKeyword] + +[ungrouped.nodes.Comprehension] + +[ungrouped.nodes.Parameters] +fields = [ + { name = "posonlyargs", type = "ParameterWithDefault*" }, + { name = "args", type = "ParameterWithDefault*" }, + { name = "vararg", type = "Box?" }, + { name = "kwonlyargs", type = "ParameterWithDefault*" }, + { name = "kwarg", type = "Box?" }, +] + +[ungrouped.nodes.Parameter] +fields = [ + { name = "name", type = "Identifier" }, + { name = "annotation", type = "Expr?", is_annotation = true }, +] + +[ungrouped.nodes.ParameterWithDefault] +fields = [ + { name = "parameter", type = "Parameter" }, + { name = "default", type = "Expr?" }, +] + +[ungrouped.nodes.Alias] + +[ungrouped.nodes.WithItem] + +[ungrouped.nodes.MatchCase] + +[ungrouped.nodes.Decorator] + +[ungrouped.nodes.ElifElseClause] + +[ungrouped.nodes.TypeParams] + +[ungrouped.nodes.FString] + +[ungrouped.nodes.TString] + +[ungrouped.nodes.StringLiteral] + +[ungrouped.nodes.BytesLiteral] + +[ungrouped.nodes.Identifier] + +[ungrouped.nodes.InterpolatedStringFormatSpec.python_projection] +class = "InterpolatedStringFormatSpec" + +[ungrouped.nodes.PatternArguments.python_projection] +class = "PatternArguments" + +[ungrouped.nodes.PatternKeyword.python_projection] +class = "PatternKeyword" + +[ungrouped.nodes.Comprehension.python_projection] +class = "Comprehension" + +[ungrouped.nodes.Parameters.python_projection] +class = "Parameters" + +[ungrouped.nodes.Parameter.python_projection] +class = "Parameter" + +[ungrouped.nodes.ParameterWithDefault.python_projection] +class = "ParameterWithDefault" + +[ungrouped.nodes.Alias.python_projection] +class = "Alias" + +[ungrouped.nodes.WithItem.python_projection] +class = "WithItem" + +[ungrouped.nodes.MatchCase.python_projection] +class = "MatchCase" + +[ungrouped.nodes.Decorator.python_projection] +class = "Decorator" + +[ungrouped.nodes.ElifElseClause.python_projection] +class = "ElifElseClause" + +[ungrouped.nodes.TypeParams.python_projection] +class = "TypeParams" + +[ungrouped.nodes.FString.python_projection] +class = "FString" + +[ungrouped.nodes.TString.python_projection] +class = "TString" + +[ungrouped.nodes.StringLiteral.python_projection] +class = "StringLiteral" + +[ungrouped.nodes.BytesLiteral.python_projection] +class = "BytesLiteral" + +[ungrouped.nodes.Identifier.python_projection] +class = "Identifier" + +[ungrouped.nodes.Arguments] +fields = [ + { name = "args", type = "Expr*" }, + { name = "keywords", type = "Keyword*" }, +] +[ungrouped.nodes.Arguments.python_projection] +class = "Arguments" + +[ungrouped.nodes.Keyword] +fields = [ + { name = "arg", type = "Identifier?" }, + { name = "value", type = "Expr" }, +] +[ungrouped.nodes.Keyword.python_projection] +class = "Keyword" diff --git a/crates/ruff_python_ast/generate.py b/crates/ruff_python_ast/generate.py index 7ed8646f5be08..08c6f1ca02b19 100644 --- a/crates/ruff_python_ast/generate.py +++ b/crates/ruff_python_ast/generate.py @@ -10,6 +10,7 @@ from dataclasses import dataclass from pathlib import Path from subprocess import check_output +from textwrap import dedent, indent from typing import Any import tomllib @@ -140,16 +141,19 @@ class Node: name: str variant: str ty: str + group: Group doc: str | None fields: list[Field] | None derives: list[str] custom_source_order: bool source_order: list[str] | None + python_projection: PythonProjection | None def __init__(self, group: Group, node_name: str, node: dict[str, Any]) -> None: self.name = node_name self.variant = node.get("variant", node_name.removeprefix(group.name)) self.ty = f"crate::{node_name}" + self.group = group self.fields = None fields = node.get("fields") if fields is not None: @@ -158,6 +162,15 @@ def __init__(self, group: Group, node_name: str, node: dict[str, Any]) -> None: self.derives = node.get("derives", []) self.doc = node.get("doc") self.source_order = node.get("source_order") + projection = node.get("python_projection") + self.python_projection = None + if projection is not None: + field_names = [] + if self.fields is not None: + field_names = [field.name for field in self.fields] + self.python_projection = PythonProjection( + self.name, field_names, projection + ) def fields_in_source_order(self) -> list[Field]: if self.fields is None: @@ -184,6 +197,7 @@ class Field: ty: str _skip_visit: bool is_annotation: bool + behavior: FieldBehavior parsed_ty: FieldType def __init__(self, field: dict[str, Any]) -> None: @@ -192,6 +206,7 @@ def __init__(self, field: dict[str, Any]) -> None: self.parsed_ty = FieldType(self.ty) self._skip_visit = field.get("skip_visit", False) self.is_annotation = field.get("is_annotation", False) + self.behavior = describe_field(self) def skip_source_order(self) -> bool: return self._skip_visit or self.parsed_ty.inner in [ @@ -240,8 +255,7 @@ def __init__(self, rule: str) -> None: self.name = "" self.inner = extract_type_argument(rule) - # The following cases are the limitations of this parser(and not used in the ast.toml): - # * Rules that involve declaring a sequence with optional items e.g. Vec> + # Some special casing that isn't currently defined in ast.toml last_pos = len(rule) - 1 for i, ch in enumerate(rule): if ch == "?": @@ -270,6 +284,56 @@ def __init__(self, rule: str) -> None: raise ValueError(f"optional field cannot be sequence or slice: {rule}") +@dataclass(frozen=True) +class FieldBehavior: + stub_base: str + is_optional: bool + is_sequence: bool + is_slice: bool + is_expr: bool = False + is_stmt: bool = False + is_arguments: bool = False + is_keyword: bool = False + is_identifier: bool = False + is_parameter_like: bool = False + is_dict_item: bool = False + is_expr_context: bool = False + is_string_enum: bool = False + is_bool: bool = False + is_str: bool = False + is_int: bool = False + is_generic_node: bool = False + + def uses_expr_to_python(self) -> bool: + return self.is_expr + + def uses_stmt_to_python(self) -> bool: + return self.is_stmt + + def uses_node_to_python(self) -> bool: + return self.is_arguments or self.is_keyword or self.is_parameter_like + + def uses_generic_node_to_python(self) -> bool: + return self.is_generic_node + + def uses_py_string(self) -> bool: + return ( + (self.is_identifier and not self.is_optional) + or self.is_string_enum + or self.is_expr_context + or self.is_str + ) + + def needs_locator(self) -> bool: + return ( + self.is_expr + or self.is_stmt + or self.uses_node_to_python() + or self.is_dict_item + or self.is_generic_node + ) + + # ------------------------------------------------------------------------------ # Preamble @@ -1066,6 +1130,1260 @@ def write_source_order(out: list[str], ast: Ast) -> None: """) +# ------------------------------------------------------------------------------ +# Python projection outputs for external linters. +# +# Generates three outputs: Rust-side PyO3 class definitions, projection and +# lazy-loading implementations, and the Python-side interface classes. + + +def append_block(out: list[str], block: str) -> None: + out.append(dedent(block).strip("\n")) + + +def node_var_name(node: Node) -> str: + if node.group.name == "Stmt": + return "stmt" + if node.group.name == "Expr": + return "expr" + return "node" + + +def kind_enum_name(node: Node) -> str: + if node.group.name == "Stmt": + return "StmtKind" + if node.group.name == "Expr": + return "ExprKind" + return "" + + +def any_node_variant(node: Node) -> str: + return node.name + + +def type_path(node: Node) -> str: + return f"ruff_python_ast::{node.name}" + + +def projection_metadata(node: Node) -> list[ProjectionMetadata]: + return node.python_projection.metadata_fields # type: ignore[union-attr] + + +def projection_special_cases( + node: Node, node_var: str, kind_enum: str +) -> tuple[list[str], dict[str, str], dict[str, str]]: + """ + Returns (setup_lines, metadata_values, precomputed_eager_fields) + - metadata_values maps metadata field names to the local variable that holds the value + - precomputed_eager_fields maps eager field names to local variable names + """ + setup_lines: list[str] = [] + metadata_values: dict[str, str] = {} + precomputed_eager_fields: dict[str, str] = {} + + if node.name == "ExprCall": + setup_lines.extend( + [ + f" let callee = extract_callee(locator, range, {node_var});", + f" let function_text = Some(locator.slice({node_var}.func.range()).trim().to_string());", + f" let function_kind = Some({kind_enum}::from({node_var}.func.as_ref()).as_str().to_owned());", + f" let arguments = node_to_python(py, locator, AnyNodeRef::from(&{node_var}.arguments), types)?;", + ] + ) + metadata_values = { + "callee": "callee", + "function_text": "function_text", + "function_kind": "function_kind", + } + precomputed_eager_fields["arguments"] = "arguments" + elif projection_metadata(node): + raise ValueError( + f"metadata_fields not supported for node {node.name}; add handling in projection_special_cases" + ) + + return setup_lines, metadata_values, precomputed_eager_fields + + +STRINGY_ENUM_TYPES: set[str] = { + "ExprContext", + "BoolOp", + "Operator", + "UnaryOp", + "CmpOp", + "IpyEscapeKind", + "Singleton", +} + + +@dataclass +class PythonProjection: + class_name: str + eager_fields: list[str] + _eager_fields: set[str] + metadata_fields: list[ProjectionMetadata] + + def __init__( + self, node_name: str, field_names: list[str], projection: dict[str, Any] + ) -> None: + self.class_name = projection["class"] + eager_fields = projection.get("eager_fields", []) + unknown_fields = set(eager_fields) - set(field_names) + if unknown_fields: + raise ValueError( + f"Unknown eager fields for {node_name}: {', '.join(sorted(unknown_fields))}" + ) + self.eager_fields = eager_fields + self._eager_fields = set(eager_fields) + self.metadata_fields = [ + ProjectionMetadata(field) for field in projection.get("metadata_fields", []) + ] + + def is_eager(self, field_name: str) -> bool: + return field_name in self._eager_fields + + +@dataclass +class ProjectionMetadata: + name: str + rust_type: str + stub_type: str + + def __init__(self, field: dict[str, Any]) -> None: + self.name = field["name"] + self.rust_type = field["rust_type"] + self.stub_type = field["stub_type"] + + +@dataclass +class ProjectionModel: + nodes: list[Node] + + @classmethod + def from_ast(cls, ast: Ast) -> ProjectionModel: + return cls([node for node in ast.all_nodes if node.python_projection]) + + def fields(self) -> list[tuple[Node, Field]]: + return [(node, field) for node in self.nodes for field in node.fields or []] + + def eager_fields(self) -> list[tuple[Node, Field]]: + return [ + (node, field) + for node, field in self.fields() + if node.python_projection.is_eager(field.name) + ] + + def lazy_fields(self) -> list[tuple[Node, Field]]: + return [ + (node, field) + for node, field in self.fields() + if not node.python_projection.is_eager(field.name) + ] + + +def describe_field(field: Field) -> FieldBehavior: + parsed = field.parsed_ty + inner = parsed.inner + stub_base = { + "Identifier": "str", + "Name": "str", + "ExprContext": "str", + "bool": "bool", + "str": "str", + "u32": "int", + "Number": "float", + "FStringValue": "str", + "TStringValue": "str", + "StringLiteralValue": "str", + "BytesLiteralValue": "str", + "Arguments": "Arguments", + "Keyword": "Keyword", + "Alias": "Alias", + "WithItem": "WithItem", + "MatchCase": "MatchCase", + "Decorator": "Decorator", + "ElifElseClause": "ElifElseClause", + "TypeParams": "TypeParams", + "Comprehension": "Comprehension", + "PatternArguments": "PatternArguments", + "PatternKeyword": "PatternKeyword", + "Parameter": "Parameter", + "ParameterWithDefault": "ParameterWithDefault", + "Parameters": "Parameters", + }.get(inner, "Node") + if stub_base == "Node" and inner in STRINGY_ENUM_TYPES: + stub_base = "str" + + string_enum = inner in STRINGY_ENUM_TYPES + expr_context = inner == "ExprContext" + is_str = inner in { + "str", + "Name", + "Identifier", + "FStringValue", + "TStringValue", + "StringLiteralValue", + "BytesLiteralValue", + } + is_int = inner == "u32" + known_node_kinds = { + "Expr", + "Stmt", + "Arguments", + "Keyword", + "Identifier", + "Parameters", + "Parameter", + "ParameterWithDefault", + "DictItem", + } + known_primitives = { + "bool", + "str", + "Name", + "u32", + "Number", + "ExprContext", + "FStringValue", + "TStringValue", + "StringLiteralValue", + "BytesLiteralValue", + } + return FieldBehavior( + stub_base=stub_base, + is_optional=parsed.optional, + is_sequence=parsed.seq, + is_slice=parsed.slice_, + is_expr=inner == "Expr", + is_stmt=inner == "Stmt", + is_arguments=inner == "Arguments", + is_keyword=inner == "Keyword", + is_identifier=inner == "Identifier", + is_parameter_like=inner + in { + "Parameters", + "Parameter", + "ParameterWithDefault", + }, + is_dict_item=inner == "DictItem", + is_expr_context=expr_context, + is_string_enum=string_enum, + is_bool=inner == "bool", + is_str=is_str, + is_int=is_int, + is_generic_node=inner not in known_node_kinds + and inner not in known_primitives + and not string_enum + and not expr_context, + ) + + +# Python stub generation + + +def python_field_type(field: Field) -> tuple[str, set[str]]: + behavior = field.behavior + + ty = behavior.stub_base + typing_imports: set[str] = set() + if behavior.is_slice or behavior.is_sequence: + ty = f"Sequence[{ty}]" + typing_imports.add("Sequence") + if behavior.is_optional: + ty = f"Optional[{ty}]" + typing_imports.add("Optional") + return ty, typing_imports + + +def write_python_stub(out: list[str], ast: Ast) -> None: + projection = ProjectionModel.from_ast(ast) + projected_nodes = projection.nodes + typing_imports: set[str] = set() + for node in projected_nodes: + for field in node.fields or []: + _, imports = python_field_type(field) + typing_imports.update(imports) + for metadata in node.python_projection.metadata_fields: # type: ignore[union-attr] + if metadata.stub_type.startswith("Optional["): + typing_imports.add("Optional") + typing_line = ( + f"from typing import {', '.join(sorted(typing_imports))}\n\n" + if typing_imports + else "" + ) + + class_names = [node.python_projection.class_name for node in projected_nodes] + + header = dedent( + """\ + # This file is auto-generated by crates/ruff_python_ast/generate.py + from __future__ import annotations + + {typing_line}from . import Node + + __all__ = {class_names} + + """ + ).format(typing_line=typing_line, class_names=class_names) + out.append(header) + + for node in projected_nodes: + assert node.python_projection is not None + class_name = node.python_projection.class_name + class_lines = [f"class {class_name}(Node):"] + if not node.fields: + class_lines.append(" pass") + else: + for field in node.fields: + ty, _ = python_field_type(field) + class_lines.append(f" {field.name}: {ty}") + for metadata in node.python_projection.metadata_fields: + class_lines.append(f" {metadata.name}: {metadata.stub_type}") + out.append("\n".join(class_lines) + "\n\n") + + # Ensure a trailing blank line so tools like isort keep the file unchanged. + out[-1] = out[-1].rstrip("\n") + "\n\n" + + +def generate_python_stub(ast: Ast) -> list[str]: + out = [] + write_python_stub(out, ast) + return out + + +def write_python_stub_output(root: Path, out: list[str]) -> None: + out_path = root.joinpath( + "crates", "ruff_linter", "resources", "ruff_external", "nodes.pyi" + ) + out_path.write_text("".join(out)) + + +@dataclass(frozen=True) +class BaseArg: + name: str + ty: str + expr: str + optional: bool = False + + +BASE_ARGS: list[BaseArg] = [ + BaseArg("kind", "String", "kind"), + BaseArg("span", "PyObject", "span_tuple(py, range)?"), + BaseArg("text", "String", "text"), + BaseArg("repr_value", "String", "repr_value"), + BaseArg("node_id", "u32", "node_id"), + BaseArg("store", "AstStoreHandle", "store"), +] +BASE_ARG_NAMES: set[str] = {arg.name for arg in BASE_ARGS} + + +def write_projection_bindings(out: list[str], projection: ProjectionModel) -> None: + projected_nodes = projection.nodes + lazy_fields = projection.lazy_fields() + uses_expr_fields = any(field.behavior.is_expr for _, field in lazy_fields) + uses_stmt_fields = any(field.behavior.is_stmt for _, field in lazy_fields) + uses_arguments_fields = any(field.behavior.is_arguments for _, field in lazy_fields) + uses_keyword_fields = any(field.behavior.is_keyword for _, field in lazy_fields) + uses_generic_node_fields = any( + field.behavior.is_generic_node for _, field in lazy_fields + ) + uses_string_fields = any(field.behavior.is_str for _, field in lazy_fields) + uses_int_fields = any(field.behavior.is_int for _, field in lazy_fields) + uses_identifier_fields = any( + field.behavior.is_identifier and not field.behavior.is_optional + for _, field in lazy_fields + ) + uses_optional_identifier_fields = any( + field.behavior.is_identifier and field.behavior.is_optional + for _, field in lazy_fields + ) + needs_call_metadata = any(node.name == "ExprCall" for node in projected_nodes) + + super_imports: list[str] = ["py_none"] + if uses_expr_fields: + super_imports.append("expr_to_python") + if uses_stmt_fields: + super_imports.append("stmt_to_python") + if uses_arguments_fields or uses_keyword_fields or uses_generic_node_fields: + super_imports.append("node_to_python") + if uses_identifier_fields or uses_string_fields: + super_imports.append("py_string") + if uses_int_fields: + super_imports.append("py_int") + if uses_optional_identifier_fields or needs_call_metadata: + super_imports.append("optional_str") + append_block( + out, + f""" + // This file is auto-generated by crates/ruff_python_ast/generate.py + + #![allow(clippy::unnecessary_wraps)] + + use pyo3::exceptions::PyRuntimeError; + + use pyo3::prelude::*; + use pyo3::PyClassInitializer; + use pyo3::types::{{PyModule, PyTuple}}; + + use ruff_python_ast::AnyNodeRef; + + use super::bindings; + use super::source::SourceFileHandle; + use super::store::AstStoreHandle; + use super::ProjectionTypesRef; + use super::{{{", ".join(super_imports)}}}; + """, + ) + append_block( + out, + """ + fn cache_value( + slot: &mut Option, + py: Python<'_>, + value: PyObject, + ) -> PyResult { + *slot = Some(value.clone_ref(py)); + Ok(value) + } + + fn map_option( + py: Python<'_>, + value: Option, + mapper: impl FnOnce(T) -> PyResult, + ) -> PyResult { + match value { + Some(value) => mapper(value), + None => Ok(py_none(py)), + } + } + + fn map_tuple(py: Python<'_>, iter: I, mut mapper: F) -> PyResult + where + I: IntoIterator, + F: FnMut(I::Item) -> PyResult, + { + let mut values = Vec::new(); + for value in iter { + values.push(mapper(value)?); + } + Ok(PyTuple::new(py, values)?.into_any().unbind()) + } + + fn get_ast(store: &AstStoreHandle, id: u32) -> PyResult<&T> + where + T: 'static, + { + store.get::(id).map_err(|err| PyRuntimeError::new_err(err.to_string())) + } + """, + ) + if projected_nodes: + exports = ", ".join( + f'"{node.python_projection.class_name}"' # type: ignore[union-attr] + for node in projected_nodes + ) + out.append(f"pub(crate) const GENERATED_EXPORTS: &[&str] = &[{exports}];") + else: + out.append("pub(crate) const GENERATED_EXPORTS: &[&str] = &[];") + + registrations = "\n".join( + f" module.add_class::<{node.python_projection.class_name}>()?;" + for node in projected_nodes + ) + registration_block = indent(registrations, " ") if registrations else "" + append_block( + out, + f""" + pub(crate) fn add_generated_classes(module: &Bound<'_, PyModule>) -> PyResult<()> {{ +{registration_block} + Ok(()) + }} + """, + ) + + for node in projected_nodes: + assert node.python_projection is not None + class_name = node.python_projection.class_name + fields = node.fields or [] + metadata_fields = [ + (metadata.name, metadata.rust_type) + for metadata in node.python_projection.metadata_fields + ] + field_lines = [f"{field.name}: Option," for field in fields] + field_lines.extend( + [ + "#[allow(dead_code)]", + "locator: SourceFileHandle,", + "#[allow(dead_code)]", + "projection: ProjectionTypesRef,", + ] + ) + for name, ty in metadata_fields: + field_lines.append(f"{name}: {ty},") + struct_fields = indent("\n".join(field_lines), " ") + append_block( + out, + f""" + #[pyclass(module = "ruff_external", extends = bindings::Node, unsendable)] + pub(crate) struct {class_name} {{ +{struct_fields} + }} + """, + ) + + eager_fields = { + field.name + for field in fields + if node.python_projection.is_eager(field.name) + } + required_base_args = [arg for arg in BASE_ARGS if not arg.optional] + optional_base_args = [arg for arg in BASE_ARGS if arg.optional] + arg_entries = [f"{arg.name}: {arg.ty}" for arg in required_base_args] + arg_entries.extend( + f"{eager_field_var_name(field)}: PyObject" + for field in fields + if field.name in eager_fields + ) + arg_entries.extend(f"{name}: {ty}" for name, ty in metadata_fields) + arg_entries.extend( + ["locator: SourceFileHandle", "projection: ProjectionTypesRef"] + ) + helper_arg_entries = list(arg_entries) + helper_arg_entries.extend(f"{arg.name}: {arg.ty}" for arg in optional_base_args) + arg_entries = ["py: Python<'_>"] + arg_entries.extend(helper_arg_entries) + helper_arg_list = ",\n ".join(helper_arg_entries) + + constructor_arg_names = ( + [arg.name for arg in required_base_args] + + [ + eager_field_var_name(field) + for field in fields + if field.name in eager_fields + ] + + [name for name, _ in metadata_fields] + + ["locator", "projection"] + + [arg.name for arg in optional_base_args] + ) + append_block( + out, + f""" + #[pymethods] + impl {class_name} {{ + """, + ) + field_inits = [] + for field in fields: + if field.name in eager_fields: + field_inits.append( + f"{field.name}: Some({eager_field_var_name(field)})," + ) + else: + field_inits.append(f"{field.name}: None,") + field_inits.extend(["locator,", "projection,"]) + for name, _ in metadata_fields: + field_inits.append(f"{name},") + init_block = indent("\n".join(field_inits), " ") + node_inner_args = [arg.name for arg in BASE_ARGS] + node_inner_list = ",\n ".join(node_inner_args) + helper_call_args = ",\n ".join(constructor_arg_names) + + if fields: + for field in fields: + iter_attr = ( + "#[allow(clippy::iter_not_returning_iterator)]\n" + if field.name == "iter" + else "" + ) + if field.name in eager_fields: + append_block( + out, + f""" + {iter_attr}#[getter] + fn {field.name}(&self, py: Python<'_>) -> PyResult {{ + Ok(self.{field.name}.as_ref().unwrap().clone_ref(py)) + }} + """, + ) + else: + append_block( + out, + f""" + {iter_attr}#[getter] + fn {field.name}(this: PyRefMut<'_, Self>) -> PyResult {{ + if let Some(value) = &this.{field.name} {{ + return Ok(value.clone_ref(this.py())); + }} + Self::load_{field.name}(this) + }} + """, + ) + for field in fields: + if field.name not in eager_fields: + append_block(out, lazy_field_loader(node, field)) + if metadata_fields: + for name, _ in metadata_fields: + append_block( + out, + f""" + #[getter] + fn {name}(&self, py: Python<'_>) -> PyResult {{ + Ok(optional_str(py, self.{name}.as_deref())) + }} + """, + ) + + append_block( + out, + """ + } + """, + ) + append_block( + out, + f""" + impl {class_name} {{ + #[allow(clippy::too_many_arguments)] + fn init_parts( + py: Python<'_>, +{helper_arg_list}, + ) -> (Self, bindings::Node) {{ + ( + Self {{ +{init_block} + }}, + bindings::Node::new_inner( + py, + {node_inner_list}, + ), + ) + }} + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_instance( + py: Python<'_>, +{helper_arg_list}, + ) -> PyResult {{ + let (this, node) = Self::init_parts( + py, + {helper_call_args} + ); + let initializer = PyClassInitializer::from(node).add_subclass(this); + Ok(Py::new(py, initializer)?.into_any()) + }} + }} + """, + ) + + +def eager_field_var_name(field: Field) -> str: + if field.name in BASE_ARG_NAMES: + return f"{field.name}_field" + return field.name + + +def eager_field_assignment(node: Node, field: Field) -> tuple[list[str], str]: + node_var = node_var_name(node) + access = f"{node_var}.{field.name}" + parsed = field.parsed_ty + behavior = field.behavior + name = eager_field_var_name(field) + + if behavior.is_sequence or behavior.is_slice: + if behavior.is_string_enum and parsed.inner == "CmpOp": + lines = [ + f" let mut {name}_values = Vec::with_capacity({access}.len());", + f" for value in &{access} {{", + f" {name}_values.push(py_string(py, value.as_str()));", + " }", + f" let {name} = PyTuple::new(py, {name}_values)?.into_any().unbind();", + ] + return lines, name + raise ValueError( + f"Sequenced eager fields are not supported yet ({node.name}.{field.name})" + ) + + if behavior.is_expr: + if behavior.is_optional: + lines = [ + f" let {name} = if let Some(value) = {access}.as_ref() {{", + " expr_to_python(py, locator, value, types)?", + " } else {", + " py_none(py)", + " };", + ] + else: + lines = [ + f" let {name} = expr_to_python(py, locator, &{access}, types)?;" + ] + return lines, name + + if behavior.is_stmt: + if behavior.is_optional: + lines = [ + f" let {name} = if let Some(value) = {access}.as_ref() {{", + " stmt_to_python(py, locator, value, types)?", + " } else {", + " py_none(py)", + " };", + ] + else: + lines = [ + f" let {name} = stmt_to_python(py, locator, &{access}, types)?;" + ] + return lines, name + + if behavior.is_identifier: + if behavior.is_optional: + lines = [ + f" let {name} = optional_str(py, {access}.as_ref().map(ruff_python_ast::Identifier::as_str));" + ] + else: + lines = [f" let {name} = py_string(py, {access}.as_str());"] + return lines, name + + if behavior.is_arguments: + if behavior.is_optional: + lines = [ + f" let {name} = if let Some(value) = {access}.as_ref() {{", + " node_to_python(py, locator, AnyNodeRef::from(value.as_ref()), types)?", + " } else {", + " py_none(py)", + " };", + ] + else: + lines = [ + f" let {name} = node_to_python(py, locator, AnyNodeRef::from(&{access}), types)?;" + ] + return lines, name + + if behavior.is_keyword: + if behavior.is_optional: + lines = [ + f" let {name} = if let Some(value) = {access}.as_ref() {{", + " node_to_python(py, locator, AnyNodeRef::from(value), types)?", + " } else {", + " py_none(py)", + " };", + ] + else: + lines = [ + f" let {name} = node_to_python(py, locator, AnyNodeRef::from(&{access}), types)?;" + ] + return lines, name + + if behavior.is_expr_context: + lines = [ + f' let {name}_value = format!("{{access:?}}", access = {access});', + f" let {name} = py_string(py, &{name}_value);", + ] + return lines, name + + if behavior.is_string_enum: + lines = [f" let {name} = py_string(py, {access}.as_str());"] + return lines, name + + if behavior.is_bool: + lines = [f" let {name} = py_bool(py, {access});"] + return lines, name + + if behavior.is_str: + if behavior.is_optional: + lines = [ + f" let {name} = if let Some(value) = {access}.as_ref() {{", + ' py_string(py, &format!("{value:?}"))', + " } else {", + " py_none(py)", + " };", + ] + else: + lines = [ + f' let {name} = py_string(py, &format!("{{value:?}}", value = {access}));' + ] + return lines, name + + if behavior.is_int: + if behavior.is_optional: + lines = [ + f" let {name} = if let Some(value) = {access} {{", + " py_int(py, value)", + " } else {", + " py_none(py)", + " };", + ] + else: + lines = [f" let {name} = py_int(py, {access});"] + return lines, name + + if behavior.is_generic_node: + if behavior.is_optional: + if parsed.rule.startswith("Box<"): + lines = [ + f" let {name} = if let Some(value) = {access}.as_ref() {{", + " node_to_python(py, locator, AnyNodeRef::from(value.as_ref()), types)?", + " } else {", + " py_none(py)", + " };", + ] + else: + lines = [ + f" let {name} = if let Some(value) = {access}.as_ref() {{", + " node_to_python(py, locator, AnyNodeRef::from(value), types)?", + " } else {", + " py_none(py)", + " };", + ] + elif behavior.is_sequence or behavior.is_slice: + raise ValueError( + f"Sequenced eager fields are not supported yet ({node.name}.{field.name})" + ) + else: + lines = [ + f" let {name} = node_to_python(py, locator, AnyNodeRef::from(&{access}), types)?;" + ] + return lines, name + + raise ValueError( + f"Unsupported eager field type for {node.name}.{field.name}: {parsed.inner}" + ) + + +def lazy_field_loader(node: Node, field: Field) -> str: + field_name = field.name + accessor = f"ast.{field.name}" + parsed = field.parsed_ty + behavior = field.behavior + needs_locator = False + needs_ast = False + conversion = "py_none(py)" + + def set_locator() -> None: + nonlocal needs_locator + needs_locator = True + + def ensure_ast() -> None: + nonlocal needs_ast + needs_ast = True + + if behavior.is_expr: + ensure_ast() + set_locator() + if behavior.is_optional: + conversion = ( + f"map_option(py, {accessor}.as_ref(), |value| " + "expr_to_python(py, &locator, value, this.projection))?" + ) + elif behavior.is_sequence or behavior.is_slice: + conversion = ( + f"map_tuple(py, {accessor}.iter(), |value| " + "expr_to_python(py, &locator, value, this.projection))?" + ) + else: + conversion = f"expr_to_python(py, &locator, &{accessor}, this.projection)?" + elif behavior.is_stmt: + ensure_ast() + set_locator() + if behavior.is_optional: + conversion = ( + f"map_option(py, {accessor}.as_ref(), |value| " + "stmt_to_python(py, &locator, value, this.projection))?" + ) + elif behavior.is_sequence or behavior.is_slice: + conversion = ( + f"map_tuple(py, {accessor}.iter(), |value| " + "stmt_to_python(py, &locator, value, this.projection))?" + ) + else: + conversion = f"stmt_to_python(py, &locator, &{accessor}, this.projection)?" + elif behavior.is_arguments: + ensure_ast() + set_locator() + if behavior.is_optional: + conversion = ( + f"map_option(py, {accessor}.as_ref(), |value| " + "node_to_python(py, &locator, AnyNodeRef::from(value.as_ref()), this.projection))?" + ) + elif behavior.is_sequence or behavior.is_slice: + conversion = ( + f"map_tuple(py, {accessor}.iter(), |value| " + "node_to_python(py, &locator, AnyNodeRef::from(value), this.projection))?" + ) + else: + conversion = f"node_to_python(py, &locator, AnyNodeRef::from(&{accessor}), this.projection)?" + elif behavior.is_keyword: + ensure_ast() + set_locator() + if behavior.is_optional: + conversion = ( + f"map_option(py, {accessor}.as_ref(), |value| " + "node_to_python(py, &locator, AnyNodeRef::from(value), this.projection))?" + ) + elif behavior.is_sequence or behavior.is_slice: + conversion = ( + f"map_tuple(py, {accessor}.iter(), |value| " + "node_to_python(py, &locator, AnyNodeRef::from(value), this.projection))?" + ) + else: + conversion = f"node_to_python(py, &locator, AnyNodeRef::from(&{accessor}), this.projection)?" + elif behavior.is_identifier: + ensure_ast() + if behavior.is_sequence or behavior.is_slice: + conversion = ( + f"map_tuple(py, {accessor}.iter(), |value| " + "Ok(py_string(py, value.as_str())))?" + ) + elif behavior.is_optional: + conversion = f"optional_str(py, {accessor}.as_ref().map(ruff_python_ast::Identifier::as_str))" + else: + conversion = f"py_string(py, {accessor}.as_str())" + elif behavior.is_str: + ensure_ast() + if behavior.is_sequence or behavior.is_slice: + conversion = f'map_tuple(py, {accessor}.iter(), |value| Ok(py_string(py, &format!("{{value:?}}"))))?' + elif behavior.is_optional: + conversion = f'map_option(py, {accessor}.as_ref(), |value| Ok(py_string(py, &format!("{{value:?}}"))))?' + else: + conversion = f'py_string(py, &format!("{{value:?}}", value = {accessor}))' + elif behavior.is_int: + ensure_ast() + if behavior.is_sequence or behavior.is_slice: + conversion = ( + f"map_tuple(py, {accessor}.iter(), |value| Ok(py_int(py, *value)))?" + ) + elif behavior.is_optional: + conversion = ( + f"map_option(py, {accessor}.as_ref(), |value| Ok(py_int(py, *value)))?" + ) + else: + conversion = f"py_int(py, {accessor})" + elif behavior.is_parameter_like: + ensure_ast() + set_locator() + if behavior.is_optional: + if parsed.rule.startswith("Box<"): + conversion = ( + f"map_option(py, {accessor}.as_ref(), |value| " + "node_to_python(py, &locator, AnyNodeRef::from(value.as_ref()), this.projection))?" + ) + else: + conversion = ( + f"map_option(py, {accessor}.as_ref(), |value| " + "node_to_python(py, &locator, AnyNodeRef::from(value), this.projection))?" + ) + elif behavior.is_sequence or behavior.is_slice: + conversion = ( + f"map_tuple(py, {accessor}.iter(), |value| " + "node_to_python(py, &locator, AnyNodeRef::from(value), this.projection))?" + ) + else: + reference = ( + f"{accessor}.as_ref()" + if parsed.rule.startswith("Box<") + else f"&{accessor}" + ) + conversion = f"node_to_python(py, &locator, AnyNodeRef::from({reference}), this.projection)?" + elif behavior.is_dict_item: + ensure_ast() + set_locator() + conversion = ( + f"map_tuple(py, {accessor}.iter(), |item| {{\n" + " let key = map_option(py, item.key.as_ref(), |key| expr_to_python(py, &locator, key, this.projection))?;\n" + " let value = expr_to_python(py, &locator, &item.value, this.projection)?;\n" + " PyTuple::new(py, [key, value]).map(|tuple| tuple.into_any().unbind())\n" + " })?" + ) + elif behavior.is_generic_node: + ensure_ast() + set_locator() + if behavior.is_optional: + if parsed.rule.startswith("Box<"): + conversion = ( + f"map_option(py, {accessor}.as_ref(), |value| " + "node_to_python(py, &locator, AnyNodeRef::from(value.as_ref()), this.projection))?" + ) + else: + conversion = ( + f"map_option(py, {accessor}.as_ref(), |value| " + "node_to_python(py, &locator, AnyNodeRef::from(value), this.projection))?" + ) + elif behavior.is_sequence or behavior.is_slice: + conversion = ( + f"map_tuple(py, {accessor}.iter(), |value| " + "node_to_python(py, &locator, AnyNodeRef::from(value), this.projection))?" + ) + else: + conversion = f"node_to_python(py, &locator, AnyNodeRef::from(&{accessor}), this.projection)?" + + setup_lines = [" let py = this.py();"] + + if needs_ast: + setup_lines.extend( + [ + " let (node_id, store) = {", + " let super_ = this.as_super();", + " (super_.node_id(), super_.store().clone())", + " };", + ] + ) + + if needs_locator: + setup_lines.append(" let locator = this.locator.locator();") + + if needs_ast: + setup_lines.append( + f" let ast = get_ast::<{type_path(node)}>(&store, node_id)?;" + ) + + setup = "\n".join(setup_lines) + return dedent( + f""" + fn load_{field_name}(mut this: PyRefMut<'_, Self>) -> PyResult {{ +{setup} + let value = {conversion}; + cache_value(&mut this.{field_name}, py, value) + }} + """ + ).strip("\n") + + +def generate_projection_bindings(ast: Ast) -> list[str]: + projection = ProjectionModel.from_ast(ast) + out: list[str] = [] + write_projection_bindings(out, projection) + return out + + +def write_projection_bindings_output(root: Path, out: list[str]) -> None: + out_path = root.joinpath( + "crates", "ruff_linter", "src", "external", "ast", "python", "generated.rs" + ) + out_path.parent.mkdir(parents=True, exist_ok=True) + out_path.write_text(rustfmt("\n".join(out))) + + +def write_projection_helpers(out: list[str], projection: ProjectionModel) -> None: + projected_nodes = projection.nodes + + eager_fields = projection.eager_fields() + uses_expr_fields = any(field.behavior.is_expr for _, field in eager_fields) + uses_stmt_fields = any(field.behavior.is_stmt for _, field in eager_fields) + uses_py_string = any(field.behavior.uses_py_string() for _, field in eager_fields) + uses_py_bool = any( + field.behavior.is_bool + for node in projected_nodes + for field in node.fields or [] + ) + uses_optional_identifier_fields = any( + field.behavior.is_identifier and field.behavior.is_optional + for node in projected_nodes + for field in node.fields or [] + ) + has_stmt_nodes = any(node.group.name == "Stmt" for node in projected_nodes) + has_expr_nodes = any(node.group.name == "Expr" for node in projected_nodes) + needs_call_helpers = any(node.name == "ExprCall" for node in projected_nodes) + + append_block( + out, + """ + // This file is auto-generated by crates/ruff_python_ast/generate.py + + use pyo3::prelude::*; + use pyo3::types::PyTuple; + use pyo3::PyObject; + + use ruff_python_ast::AnyNodeRef; + use ruff_text_size::Ranged; + + use crate::Locator; + """, + ) + + target_imports = [] + if has_expr_nodes: + target_imports.append("ExprKind") + if has_stmt_nodes: + target_imports.append("StmtKind") + if target_imports: + joined = ", ".join(target_imports) + out.append(f"use crate::external::ast::target::{{{joined}}};") + + super_imports = [ + "source::SourceFileHandle", + "span_tuple", + "ProjectionTypesRef", + ] + + if uses_expr_fields: + super_imports.append("expr_to_python") + if uses_stmt_fields: + super_imports.append("stmt_to_python") + if uses_py_string: + super_imports.append("py_string") + if uses_py_bool: + super_imports.append("py_bool") + if uses_optional_identifier_fields: + super_imports.append("optional_str") + if needs_call_helpers: + super_imports.extend(["node_to_python", "extract_callee"]) + + out.append(f"use super::{{{', '.join(super_imports)}}};") + if projected_nodes: + class_imports = ", ".join( + node.python_projection.class_name for node in projected_nodes + ) + out.append(f"use super::generated::{{{class_imports}}};") + + append_block( + out, + """ + #[derive(Clone, Copy, Debug)] + #[allow(dead_code)] + pub(crate) enum ProjectionMode<'a> { + Generic, + Typed, + Fields(&'a [&'a str]), + } + + impl ProjectionMode<'_> { + pub(crate) const fn wants_typed(&self) -> bool { + !matches!(self, ProjectionMode::Generic) + } + } + """, + ) + + append_block( + out, + """ + #[allow(unreachable_patterns)] + pub(crate) fn project_typed_node( + py: Python<'_>, + locator: &Locator<'_>, + node: AnyNodeRef<'_>, + mode: ProjectionMode<'_>, + types: ProjectionTypesRef, + ) -> PyResult> { + if !mode.wants_typed() { + return Ok(None); + } + + match node { + """, + ) + + if projected_nodes: + for node in projected_nodes: + helper_name = f"project_{to_snake_case(node.name)}" + node_var = node_var_name(node) + out.append( + f"AnyNodeRef::{node.name}({node_var}) => {helper_name}(py, locator, {node_var}, types).map(Some)," + ) + out.append(" _ => Ok(None),") + else: + out.append(" _ => Ok(None),") + + append_block( + out, + """ + } + } + """, + ) + + for node in projected_nodes: + helper_name = f"project_{to_snake_case(node.name)}" + node_var = node_var_name(node) + helper_type = type_path(node) + kind_enum = kind_enum_name(node) + kind_variant = node.variant + repr_format = "{" + f"{node_var}" + ":?}" + if kind_enum: + kind_line = f"let kind = {kind_enum}::{kind_variant}.as_str().to_string();" + else: + kind_line = f'let kind = "{kind_variant}".to_string();' + + append_block( + out, + f""" + fn {helper_name}( + py: Python<'_>, + locator: &Locator<'_>, + {node_var}: &{helper_type}, + types: ProjectionTypesRef, + ) -> PyResult {{ + {kind_line} + let range = {node_var}.range(); + let text = locator.slice(range).to_string(); + let repr_value = format!("{repr_format}"); + let store = super::store::current_store(); + let node_id = store.ensure({node_var}); + """, + ) + + setup_lines, metadata_values, precomputed_eager_fields = ( + projection_special_cases(node, node_var, kind_enum) + ) + if setup_lines: + out.append("\n".join(setup_lines)) + + field_exprs: list[str] = [] + for field in node.fields or []: + if node.python_projection.is_eager(field.name): # type: ignore[union-attr] + if field.name in precomputed_eager_fields: + field_exprs.append(precomputed_eager_fields[field.name]) + continue + lines, value_name = eager_field_assignment(node, field) + out.extend(lines) + field_exprs.append(value_name) + for metadata in projection_metadata(node): + field_exprs.append(metadata_values.get(metadata.name, "py_none(py)")) + field_exprs.extend( + [ + "SourceFileHandle::new()", + "types", + ] + ) + + base_arg_map = {arg.name: arg.expr for arg in BASE_ARGS} + required_base_args = [arg for arg in BASE_ARGS if not arg.optional] + optional_base_args = [arg for arg in BASE_ARGS if arg.optional] + ordered_required = [base_arg_map[arg.name] for arg in required_base_args] + ordered_optional = [base_arg_map[arg.name] for arg in optional_base_args] + all_args = ordered_required + field_exprs + ordered_optional + args_str = ",\n ".join(all_args) + + append_block( + out, + f""" + {node.python_projection.class_name}::new_instance( + py, + {args_str}, + ) + }} + """, + ) + + +def generate_projection_helpers(ast: Ast) -> list[str]: + projection = ProjectionModel.from_ast(ast) + out: list[str] = [] + write_projection_helpers(out, projection) + return out + + +def write_projection_helpers_output(root: Path, out: list[str]) -> None: + out_path = root.joinpath( + "crates", + "ruff_linter", + "src", + "external", + "ast", + "python", + "projection.rs", + ) + out_path.parent.mkdir(parents=True, exist_ok=True) + out_path.write_text(rustfmt("\n".join(out))) + + # ------------------------------------------------------------------------------ # Format and write output @@ -1099,6 +2417,12 @@ def main() -> None: ast = load_ast(root) out = generate(ast) write_output(root, out) + python_stub = generate_python_stub(ast) + write_python_stub_output(root, python_stub) + projection_bindings = generate_projection_bindings(ast) + write_projection_bindings_output(root, projection_bindings) + projection_helpers = generate_projection_helpers(ast) + write_projection_helpers_output(root, projection_helpers) if __name__ == "__main__": diff --git a/crates/ruff_python_ast/src/generated.rs b/crates/ruff_python_ast/src/generated.rs index 547c50d631962..557740b4430e4 100644 --- a/crates/ruff_python_ast/src/generated.rs +++ b/crates/ruff_python_ast/src/generated.rs @@ -4032,12 +4032,6 @@ impl ruff_text_size::Ranged for crate::Comprehension { } } -impl ruff_text_size::Ranged for crate::Arguments { - fn range(&self) -> ruff_text_size::TextRange { - self.range - } -} - impl ruff_text_size::Ranged for crate::Parameters { fn range(&self) -> ruff_text_size::TextRange { self.range @@ -4056,12 +4050,6 @@ impl ruff_text_size::Ranged for crate::ParameterWithDefault { } } -impl ruff_text_size::Ranged for crate::Keyword { - fn range(&self) -> ruff_text_size::TextRange { - self.range - } -} - impl ruff_text_size::Ranged for crate::Alias { fn range(&self) -> ruff_text_size::TextRange { self.range @@ -4128,6 +4116,18 @@ impl ruff_text_size::Ranged for crate::Identifier { } } +impl ruff_text_size::Ranged for crate::Arguments { + fn range(&self) -> ruff_text_size::TextRange { + self.range + } +} + +impl ruff_text_size::Ranged for crate::Keyword { + fn range(&self) -> ruff_text_size::TextRange { + self.range + } +} + impl crate::HasNodeIndex for crate::ModModule { fn node_index(&self) -> &crate::AtomicNodeIndex { &self.node_index @@ -4596,12 +4596,6 @@ impl crate::HasNodeIndex for crate::Comprehension { } } -impl crate::HasNodeIndex for crate::Arguments { - fn node_index(&self) -> &crate::AtomicNodeIndex { - &self.node_index - } -} - impl crate::HasNodeIndex for crate::Parameters { fn node_index(&self) -> &crate::AtomicNodeIndex { &self.node_index @@ -4620,12 +4614,6 @@ impl crate::HasNodeIndex for crate::ParameterWithDefault { } } -impl crate::HasNodeIndex for crate::Keyword { - fn node_index(&self) -> &crate::AtomicNodeIndex { - &self.node_index - } -} - impl crate::HasNodeIndex for crate::Alias { fn node_index(&self) -> &crate::AtomicNodeIndex { &self.node_index @@ -4692,6 +4680,18 @@ impl crate::HasNodeIndex for crate::Identifier { } } +impl crate::HasNodeIndex for crate::Arguments { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index + } +} + +impl crate::HasNodeIndex for crate::Keyword { + fn node_index(&self) -> &crate::AtomicNodeIndex { + &self.node_index + } +} + impl Mod { #[allow(unused)] pub(crate) fn visit_source_order<'a, V>(&'a self, visitor: &mut V) @@ -5913,11 +5913,9 @@ pub enum AnyNodeRef<'a> { PatternArguments(&'a crate::PatternArguments), PatternKeyword(&'a crate::PatternKeyword), Comprehension(&'a crate::Comprehension), - Arguments(&'a crate::Arguments), Parameters(&'a crate::Parameters), Parameter(&'a crate::Parameter), ParameterWithDefault(&'a crate::ParameterWithDefault), - Keyword(&'a crate::Keyword), Alias(&'a crate::Alias), WithItem(&'a crate::WithItem), MatchCase(&'a crate::MatchCase), @@ -5929,6 +5927,8 @@ pub enum AnyNodeRef<'a> { StringLiteral(&'a crate::StringLiteral), BytesLiteral(&'a crate::BytesLiteral), Identifier(&'a crate::Identifier), + Arguments(&'a crate::Arguments), + Keyword(&'a crate::Keyword), } impl<'a> From<&'a Mod> for AnyNodeRef<'a> { @@ -6792,12 +6792,6 @@ impl<'a> From<&'a crate::Comprehension> for AnyNodeRef<'a> { } } -impl<'a> From<&'a crate::Arguments> for AnyNodeRef<'a> { - fn from(node: &'a crate::Arguments) -> AnyNodeRef<'a> { - AnyNodeRef::Arguments(node) - } -} - impl<'a> From<&'a crate::Parameters> for AnyNodeRef<'a> { fn from(node: &'a crate::Parameters) -> AnyNodeRef<'a> { AnyNodeRef::Parameters(node) @@ -6816,12 +6810,6 @@ impl<'a> From<&'a crate::ParameterWithDefault> for AnyNodeRef<'a> { } } -impl<'a> From<&'a crate::Keyword> for AnyNodeRef<'a> { - fn from(node: &'a crate::Keyword) -> AnyNodeRef<'a> { - AnyNodeRef::Keyword(node) - } -} - impl<'a> From<&'a crate::Alias> for AnyNodeRef<'a> { fn from(node: &'a crate::Alias) -> AnyNodeRef<'a> { AnyNodeRef::Alias(node) @@ -6888,6 +6876,18 @@ impl<'a> From<&'a crate::Identifier> for AnyNodeRef<'a> { } } +impl<'a> From<&'a crate::Arguments> for AnyNodeRef<'a> { + fn from(node: &'a crate::Arguments) -> AnyNodeRef<'a> { + AnyNodeRef::Arguments(node) + } +} + +impl<'a> From<&'a crate::Keyword> for AnyNodeRef<'a> { + fn from(node: &'a crate::Keyword) -> AnyNodeRef<'a> { + AnyNodeRef::Keyword(node) + } +} + impl ruff_text_size::Ranged for AnyNodeRef<'_> { fn range(&self) -> ruff_text_size::TextRange { match self { @@ -6969,11 +6969,9 @@ impl ruff_text_size::Ranged for AnyNodeRef<'_> { AnyNodeRef::PatternArguments(node) => node.range(), AnyNodeRef::PatternKeyword(node) => node.range(), AnyNodeRef::Comprehension(node) => node.range(), - AnyNodeRef::Arguments(node) => node.range(), AnyNodeRef::Parameters(node) => node.range(), AnyNodeRef::Parameter(node) => node.range(), AnyNodeRef::ParameterWithDefault(node) => node.range(), - AnyNodeRef::Keyword(node) => node.range(), AnyNodeRef::Alias(node) => node.range(), AnyNodeRef::WithItem(node) => node.range(), AnyNodeRef::MatchCase(node) => node.range(), @@ -6985,6 +6983,8 @@ impl ruff_text_size::Ranged for AnyNodeRef<'_> { AnyNodeRef::StringLiteral(node) => node.range(), AnyNodeRef::BytesLiteral(node) => node.range(), AnyNodeRef::Identifier(node) => node.range(), + AnyNodeRef::Arguments(node) => node.range(), + AnyNodeRef::Keyword(node) => node.range(), } } } @@ -7070,11 +7070,9 @@ impl crate::HasNodeIndex for AnyNodeRef<'_> { AnyNodeRef::PatternArguments(node) => node.node_index(), AnyNodeRef::PatternKeyword(node) => node.node_index(), AnyNodeRef::Comprehension(node) => node.node_index(), - AnyNodeRef::Arguments(node) => node.node_index(), AnyNodeRef::Parameters(node) => node.node_index(), AnyNodeRef::Parameter(node) => node.node_index(), AnyNodeRef::ParameterWithDefault(node) => node.node_index(), - AnyNodeRef::Keyword(node) => node.node_index(), AnyNodeRef::Alias(node) => node.node_index(), AnyNodeRef::WithItem(node) => node.node_index(), AnyNodeRef::MatchCase(node) => node.node_index(), @@ -7086,6 +7084,8 @@ impl crate::HasNodeIndex for AnyNodeRef<'_> { AnyNodeRef::StringLiteral(node) => node.node_index(), AnyNodeRef::BytesLiteral(node) => node.node_index(), AnyNodeRef::Identifier(node) => node.node_index(), + AnyNodeRef::Arguments(node) => node.node_index(), + AnyNodeRef::Keyword(node) => node.node_index(), } } } @@ -7173,11 +7173,9 @@ impl AnyNodeRef<'_> { AnyNodeRef::PatternArguments(node) => std::ptr::NonNull::from(*node).cast(), AnyNodeRef::PatternKeyword(node) => std::ptr::NonNull::from(*node).cast(), AnyNodeRef::Comprehension(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::Arguments(node) => std::ptr::NonNull::from(*node).cast(), AnyNodeRef::Parameters(node) => std::ptr::NonNull::from(*node).cast(), AnyNodeRef::Parameter(node) => std::ptr::NonNull::from(*node).cast(), AnyNodeRef::ParameterWithDefault(node) => std::ptr::NonNull::from(*node).cast(), - AnyNodeRef::Keyword(node) => std::ptr::NonNull::from(*node).cast(), AnyNodeRef::Alias(node) => std::ptr::NonNull::from(*node).cast(), AnyNodeRef::WithItem(node) => std::ptr::NonNull::from(*node).cast(), AnyNodeRef::MatchCase(node) => std::ptr::NonNull::from(*node).cast(), @@ -7189,6 +7187,8 @@ impl AnyNodeRef<'_> { AnyNodeRef::StringLiteral(node) => std::ptr::NonNull::from(*node).cast(), AnyNodeRef::BytesLiteral(node) => std::ptr::NonNull::from(*node).cast(), AnyNodeRef::Identifier(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::Arguments(node) => std::ptr::NonNull::from(*node).cast(), + AnyNodeRef::Keyword(node) => std::ptr::NonNull::from(*node).cast(), } } } @@ -7278,11 +7278,9 @@ impl<'a> AnyNodeRef<'a> { AnyNodeRef::PatternArguments(node) => node.visit_source_order(visitor), AnyNodeRef::PatternKeyword(node) => node.visit_source_order(visitor), AnyNodeRef::Comprehension(node) => node.visit_source_order(visitor), - AnyNodeRef::Arguments(node) => node.visit_source_order(visitor), AnyNodeRef::Parameters(node) => node.visit_source_order(visitor), AnyNodeRef::Parameter(node) => node.visit_source_order(visitor), AnyNodeRef::ParameterWithDefault(node) => node.visit_source_order(visitor), - AnyNodeRef::Keyword(node) => node.visit_source_order(visitor), AnyNodeRef::Alias(node) => node.visit_source_order(visitor), AnyNodeRef::WithItem(node) => node.visit_source_order(visitor), AnyNodeRef::MatchCase(node) => node.visit_source_order(visitor), @@ -7294,6 +7292,8 @@ impl<'a> AnyNodeRef<'a> { AnyNodeRef::StringLiteral(node) => node.visit_source_order(visitor), AnyNodeRef::BytesLiteral(node) => node.visit_source_order(visitor), AnyNodeRef::Identifier(node) => node.visit_source_order(visitor), + AnyNodeRef::Arguments(node) => node.visit_source_order(visitor), + AnyNodeRef::Keyword(node) => node.visit_source_order(visitor), } } } @@ -7446,11 +7446,9 @@ pub enum AnyRootNodeRef<'a> { PatternArguments(&'a crate::PatternArguments), PatternKeyword(&'a crate::PatternKeyword), Comprehension(&'a crate::Comprehension), - Arguments(&'a crate::Arguments), Parameters(&'a crate::Parameters), Parameter(&'a crate::Parameter), ParameterWithDefault(&'a crate::ParameterWithDefault), - Keyword(&'a crate::Keyword), Alias(&'a crate::Alias), WithItem(&'a crate::WithItem), MatchCase(&'a crate::MatchCase), @@ -7462,6 +7460,8 @@ pub enum AnyRootNodeRef<'a> { StringLiteral(&'a crate::StringLiteral), BytesLiteral(&'a crate::BytesLiteral), Identifier(&'a crate::Identifier), + Arguments(&'a crate::Arguments), + Keyword(&'a crate::Keyword), } impl<'a> From<&'a Mod> for AnyRootNodeRef<'a> { @@ -8386,22 +8386,6 @@ impl<'a> TryFrom> for &'a crate::Comprehension { } } -impl<'a> From<&'a crate::Arguments> for AnyRootNodeRef<'a> { - fn from(node: &'a crate::Arguments) -> AnyRootNodeRef<'a> { - AnyRootNodeRef::Arguments(node) - } -} - -impl<'a> TryFrom> for &'a crate::Arguments { - type Error = (); - fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::Arguments, ()> { - match node { - AnyRootNodeRef::Arguments(node) => Ok(node), - _ => Err(()), - } - } -} - impl<'a> From<&'a crate::Parameters> for AnyRootNodeRef<'a> { fn from(node: &'a crate::Parameters) -> AnyRootNodeRef<'a> { AnyRootNodeRef::Parameters(node) @@ -8450,22 +8434,6 @@ impl<'a> TryFrom> for &'a crate::ParameterWithDefault { } } -impl<'a> From<&'a crate::Keyword> for AnyRootNodeRef<'a> { - fn from(node: &'a crate::Keyword) -> AnyRootNodeRef<'a> { - AnyRootNodeRef::Keyword(node) - } -} - -impl<'a> TryFrom> for &'a crate::Keyword { - type Error = (); - fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::Keyword, ()> { - match node { - AnyRootNodeRef::Keyword(node) => Ok(node), - _ => Err(()), - } - } -} - impl<'a> From<&'a crate::Alias> for AnyRootNodeRef<'a> { fn from(node: &'a crate::Alias) -> AnyRootNodeRef<'a> { AnyRootNodeRef::Alias(node) @@ -8642,6 +8610,38 @@ impl<'a> TryFrom> for &'a crate::Identifier { } } +impl<'a> From<&'a crate::Arguments> for AnyRootNodeRef<'a> { + fn from(node: &'a crate::Arguments) -> AnyRootNodeRef<'a> { + AnyRootNodeRef::Arguments(node) + } +} + +impl<'a> TryFrom> for &'a crate::Arguments { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::Arguments, ()> { + match node { + AnyRootNodeRef::Arguments(node) => Ok(node), + _ => Err(()), + } + } +} + +impl<'a> From<&'a crate::Keyword> for AnyRootNodeRef<'a> { + fn from(node: &'a crate::Keyword) -> AnyRootNodeRef<'a> { + AnyRootNodeRef::Keyword(node) + } +} + +impl<'a> TryFrom> for &'a crate::Keyword { + type Error = (); + fn try_from(node: AnyRootNodeRef<'a>) -> Result<&'a crate::Keyword, ()> { + match node { + AnyRootNodeRef::Keyword(node) => Ok(node), + _ => Err(()), + } + } +} + impl ruff_text_size::Ranged for AnyRootNodeRef<'_> { fn range(&self) -> ruff_text_size::TextRange { match self { @@ -8656,11 +8656,9 @@ impl ruff_text_size::Ranged for AnyRootNodeRef<'_> { AnyRootNodeRef::PatternArguments(node) => node.range(), AnyRootNodeRef::PatternKeyword(node) => node.range(), AnyRootNodeRef::Comprehension(node) => node.range(), - AnyRootNodeRef::Arguments(node) => node.range(), AnyRootNodeRef::Parameters(node) => node.range(), AnyRootNodeRef::Parameter(node) => node.range(), AnyRootNodeRef::ParameterWithDefault(node) => node.range(), - AnyRootNodeRef::Keyword(node) => node.range(), AnyRootNodeRef::Alias(node) => node.range(), AnyRootNodeRef::WithItem(node) => node.range(), AnyRootNodeRef::MatchCase(node) => node.range(), @@ -8672,6 +8670,8 @@ impl ruff_text_size::Ranged for AnyRootNodeRef<'_> { AnyRootNodeRef::StringLiteral(node) => node.range(), AnyRootNodeRef::BytesLiteral(node) => node.range(), AnyRootNodeRef::Identifier(node) => node.range(), + AnyRootNodeRef::Arguments(node) => node.range(), + AnyRootNodeRef::Keyword(node) => node.range(), } } } @@ -8690,11 +8690,9 @@ impl crate::HasNodeIndex for AnyRootNodeRef<'_> { AnyRootNodeRef::PatternArguments(node) => node.node_index(), AnyRootNodeRef::PatternKeyword(node) => node.node_index(), AnyRootNodeRef::Comprehension(node) => node.node_index(), - AnyRootNodeRef::Arguments(node) => node.node_index(), AnyRootNodeRef::Parameters(node) => node.node_index(), AnyRootNodeRef::Parameter(node) => node.node_index(), AnyRootNodeRef::ParameterWithDefault(node) => node.node_index(), - AnyRootNodeRef::Keyword(node) => node.node_index(), AnyRootNodeRef::Alias(node) => node.node_index(), AnyRootNodeRef::WithItem(node) => node.node_index(), AnyRootNodeRef::MatchCase(node) => node.node_index(), @@ -8706,6 +8704,8 @@ impl crate::HasNodeIndex for AnyRootNodeRef<'_> { AnyRootNodeRef::StringLiteral(node) => node.node_index(), AnyRootNodeRef::BytesLiteral(node) => node.node_index(), AnyRootNodeRef::Identifier(node) => node.node_index(), + AnyRootNodeRef::Arguments(node) => node.node_index(), + AnyRootNodeRef::Keyword(node) => node.node_index(), } } } @@ -8728,11 +8728,9 @@ impl<'a> AnyRootNodeRef<'a> { AnyRootNodeRef::PatternArguments(node) => node.visit_source_order(visitor), AnyRootNodeRef::PatternKeyword(node) => node.visit_source_order(visitor), AnyRootNodeRef::Comprehension(node) => node.visit_source_order(visitor), - AnyRootNodeRef::Arguments(node) => node.visit_source_order(visitor), AnyRootNodeRef::Parameters(node) => node.visit_source_order(visitor), AnyRootNodeRef::Parameter(node) => node.visit_source_order(visitor), AnyRootNodeRef::ParameterWithDefault(node) => node.visit_source_order(visitor), - AnyRootNodeRef::Keyword(node) => node.visit_source_order(visitor), AnyRootNodeRef::Alias(node) => node.visit_source_order(visitor), AnyRootNodeRef::WithItem(node) => node.visit_source_order(visitor), AnyRootNodeRef::MatchCase(node) => node.visit_source_order(visitor), @@ -8744,6 +8742,8 @@ impl<'a> AnyRootNodeRef<'a> { AnyRootNodeRef::StringLiteral(node) => node.visit_source_order(visitor), AnyRootNodeRef::BytesLiteral(node) => node.visit_source_order(visitor), AnyRootNodeRef::Identifier(node) => node.visit_source_order(visitor), + AnyRootNodeRef::Arguments(node) => node.visit_source_order(visitor), + AnyRootNodeRef::Keyword(node) => node.visit_source_order(visitor), } } } @@ -8828,11 +8828,9 @@ pub enum NodeKind { PatternArguments, PatternKeyword, Comprehension, - Arguments, Parameters, Parameter, ParameterWithDefault, - Keyword, Alias, WithItem, MatchCase, @@ -8844,6 +8842,8 @@ pub enum NodeKind { StringLiteral, BytesLiteral, Identifier, + Arguments, + Keyword, } impl AnyNodeRef<'_> { @@ -8929,11 +8929,9 @@ impl AnyNodeRef<'_> { AnyNodeRef::PatternArguments(_) => NodeKind::PatternArguments, AnyNodeRef::PatternKeyword(_) => NodeKind::PatternKeyword, AnyNodeRef::Comprehension(_) => NodeKind::Comprehension, - AnyNodeRef::Arguments(_) => NodeKind::Arguments, AnyNodeRef::Parameters(_) => NodeKind::Parameters, AnyNodeRef::Parameter(_) => NodeKind::Parameter, AnyNodeRef::ParameterWithDefault(_) => NodeKind::ParameterWithDefault, - AnyNodeRef::Keyword(_) => NodeKind::Keyword, AnyNodeRef::Alias(_) => NodeKind::Alias, AnyNodeRef::WithItem(_) => NodeKind::WithItem, AnyNodeRef::MatchCase(_) => NodeKind::MatchCase, @@ -8945,6 +8943,8 @@ impl AnyNodeRef<'_> { AnyNodeRef::StringLiteral(_) => NodeKind::StringLiteral, AnyNodeRef::BytesLiteral(_) => NodeKind::BytesLiteral, AnyNodeRef::Identifier(_) => NodeKind::Identifier, + AnyNodeRef::Arguments(_) => NodeKind::Arguments, + AnyNodeRef::Keyword(_) => NodeKind::Keyword, } } } diff --git a/crates/ruff_server/src/session/options.rs b/crates/ruff_server/src/session/options.rs index dba88c99ae3fd..19c221aa1ee59 100644 --- a/crates/ruff_server/src/session/options.rs +++ b/crates/ruff_server/src/session/options.rs @@ -187,7 +187,7 @@ impl ClientOptions { for rule in rules { match RuleSelector::from_str(rule) { Ok(selector) => known.push(selector), - Err(ParseError::Unknown(_)) => unknown.push(rule), + Err(ParseError::Unknown(_) | ParseError::External(_)) => unknown.push(rule), } } if !unknown.is_empty() { diff --git a/crates/ruff_workspace/Cargo.toml b/crates/ruff_workspace/Cargo.toml index 9ef114df94881..9145fabcb7546 100644 --- a/crates/ruff_workspace/Cargo.toml +++ b/crates/ruff_workspace/Cargo.toml @@ -70,6 +70,7 @@ schemars = [ "ruff_python_formatter/schemars", "ruff_python_semantic/schemars", ] +ext-lint = ["ruff_linter/ext-lint"] [lints] workspace = true diff --git a/crates/ruff_workspace/src/configuration.rs b/crates/ruff_workspace/src/configuration.rs index bf50749a45dc6..7a30e8fb9bd5d 100644 --- a/crates/ruff_workspace/src/configuration.rs +++ b/crates/ruff_workspace/src/configuration.rs @@ -21,6 +21,9 @@ use strum::IntoEnumIterator; use ruff_cache::cache_dir; use ruff_formatter::IndentStyle; use ruff_graph::{AnalyzeSettings, Direction, StringImports}; +use ruff_linter::external::{ + ExternalLintRegistry, PyprojectExternalLinterEntry, load_linter_into_registry, +}; use ruff_linter::line_width::{IndentWidth, LineLength}; use ruff_linter::registry::{INCOMPATIBLE_CODES, Rule, RuleNamespace, RuleSet}; use ruff_linter::rule_selector::{PreviewOptions, Specificity}; @@ -69,6 +72,14 @@ pub struct RuleSelection { pub extend_fixable: Vec, } +#[derive(Clone, Debug, Default)] +pub struct ExternalRuleSelection { + pub select: Option>, + pub extend_select: Vec, + pub ignore: Vec, + pub extend_ignore: Vec, +} + #[derive(Debug, Eq, PartialEq, is_macro::Is)] pub enum RuleSelectorKind { /// Enables the selected rules @@ -112,6 +123,65 @@ impl RuleSelection { .map(|selector| (RuleSelectorKind::Modify, selector)), ) } + + pub fn selectors(&self) -> impl Iterator { + self.selectors_by_kind().map(|(_, selector)| selector) + } +} + +fn build_external_registry( + entries: &BTreeMap, +) -> Result> { + let mut registry = ExternalLintRegistry::new(); + for (id, entry) in entries { + if !entry.enabled { + continue; + } + let py_entry = PyprojectExternalLinterEntry { + toml_path: entry.path.clone(), + enabled: entry.enabled, + }; + load_linter_into_registry(&mut registry, id, &py_entry)?; + } + if registry.is_empty() { + Ok(None) + } else { + Ok(Some(registry)) + } +} + +fn resolve_external_rule_selections( + selections: &[ExternalRuleSelection], +) -> (Vec, Vec) { + let mut selected: FxHashSet = FxHashSet::default(); + let mut ignored: FxHashSet = FxHashSet::default(); + + for selection in selections { + if let Some(select) = &selection.select { + selected = select.iter().cloned().collect(); + } + for code in &selection.extend_select { + selected.insert(code.clone()); + } + for code in &selection.ignore { + ignored.insert(code.clone()); + } + for code in &selection.extend_ignore { + ignored.insert(code.clone()); + } + } + + let mut selected_vec: Vec = selected.into_iter().collect(); + let mut ignored_vec: Vec = ignored.into_iter().collect(); + selected_vec.sort_unstable(); + ignored_vec.sort_unstable(); + (selected_vec, ignored_vec) +} + +#[derive(Debug, Clone)] +pub struct ExternalLinterEntry { + pub path: PathBuf, + pub enabled: bool, } #[derive(Debug, Default, Clone)] @@ -242,7 +312,8 @@ impl Configuration { let line_length = self.line_length.unwrap_or_default(); - let rules = lint.as_rule_table(lint_preview)?; + #[allow(unused_mut)] + let mut rules = lint.as_rule_table(lint_preview)?; // LinterSettings validation let isort = lint @@ -260,6 +331,76 @@ impl Configuration { let future_annotations = lint.future_annotations.unwrap_or_default(); + let (configured_selected_external_vec, configured_ignored_external_vec) = + resolve_external_rule_selections(&lint.external_rule_selections); + + let mut external_codes = lint.external.unwrap_or_default(); + let mut seen_external_codes = external_codes.iter().cloned().collect::>(); + + let external_ast_registry = match lint.external_ast.as_ref() { + Some(entries) => build_external_registry(entries)?, + None => None, + }; + + if let Some(registry) = external_ast_registry.as_ref() { + ruff_linter::external::verify_registry_scripts(registry)?; + for rule in registry.iter_enabled_rules() { + let code = rule.code.as_str().to_owned(); + if seen_external_codes.insert(code.clone()) { + external_codes.push(code); + } + } + } + for code in &configured_selected_external_vec { + if seen_external_codes.insert(code.clone()) { + external_codes.push(code.clone()); + } + } + + let available_external_codes = external_ast_registry.as_ref().map(|registry| { + registry + .iter_enabled_rules() + .map(|rule| rule.code.as_str().to_string()) + .collect::>() + }); + + let selectors = lint + .rule_selections + .iter() + .flat_map(RuleSelection::selectors) + .chain(lint.extend_safe_fixes.iter()) + .chain(lint.extend_unsafe_fixes.iter()); + + let mut missing_external = FxHashSet::default(); + for selector in selectors { + if let RuleSelector::External { code } = selector { + let is_known = available_external_codes + .as_ref() + .is_some_and(|available| available.contains(code.as_ref())); + if !is_known { + missing_external.insert(code.as_ref().to_string()); + } + } + } + + if !missing_external.is_empty() { + let mut missing: Vec<_> = missing_external.into_iter().collect(); + missing.sort_unstable(); + let formatted = missing + .into_iter() + .map(|code| format!("`{code}`")) + .collect::>(); + let message = if formatted.len() == 1 { + format!("Unknown rule selector: {}", formatted[0]) + } else { + format!("Unknown rule selectors: {}", formatted.join(", ")) + }; + return Err(anyhow!(message)); + } + if external_ast_registry.is_none() { + rules.disable(Rule::ExternalLinter); + } + Ok(Settings { cache_dir: self .cache_dir @@ -306,7 +447,10 @@ impl Configuration { dummy_variable_rgx: lint .dummy_variable_rgx .unwrap_or_else(|| DUMMY_VARIABLE_RGX.clone()), - external: lint.external.unwrap_or_default(), + external: external_codes, + external_ast: external_ast_registry, + selected_external: configured_selected_external_vec, + ignored_external: configured_ignored_external_vec, ignore_init_module_imports: lint.ignore_init_module_imports.unwrap_or(true), line_length, tab_size: self.indent_width.unwrap_or_default(), @@ -638,6 +782,7 @@ pub struct LintConfiguration { pub per_file_ignores: Option>, pub rule_selections: Vec, pub explicit_preview_rules: Option, + pub external_rule_selections: Vec, // Fix configuration pub extend_unsafe_fixes: Vec, @@ -647,6 +792,7 @@ pub struct LintConfiguration { pub allowed_confusables: Option>, pub dummy_variable_rgx: Option, pub external: Option>, + pub external_ast: Option>, pub ignore_init_module_imports: Option, pub logger_objects: Option>, pub task_tags: Option>, @@ -713,6 +859,118 @@ impl LintConfiguration { options.common.ignore_init_module_imports }; + for (label, selectors) in [ + ("lint.select", options.common.select.as_ref()), + ("lint.extend-select", options.common.extend_select.as_ref()), + ] { + if let Some(selectors) = selectors { + if let Some(code) = selectors.iter().find_map(|selector| { + if let RuleSelector::External { code } = selector { + Some(code.as_ref().to_string()) + } else { + None + } + }) { + return Err(anyhow::anyhow!( + "External rule `{code}` cannot be enabled via `{label}`; use `lint.select-external` instead." + )); + } + } + } + + if let Some(internal) = options.common.select_external.as_ref().and_then(|values| { + values.iter().find(|value| { + matches!( + RuleSelector::from_str(value), + Ok(RuleSelector::Linter(_) + | RuleSelector::Prefix { .. } + | RuleSelector::Rule { .. }) + ) + }) + }) { + return Err(anyhow::anyhow!( + "Internal rule `{internal}` cannot be enabled via `lint.select-external`; use `lint.select` instead." + )); + } + if let Some(internal) = options + .common + .extend_select_external + .as_ref() + .and_then(|values| { + values.iter().find(|value| { + matches!( + RuleSelector::from_str(value), + Ok(RuleSelector::Linter(_) + | RuleSelector::Prefix { .. } + | RuleSelector::Rule { .. }) + ) + }) + }) + { + return Err(anyhow::anyhow!( + "Internal rule `{internal}` cannot be enabled via `lint.extend-select-external`; use `lint.extend-select` instead." + )); + } + if let Some(internal) = options.common.ignore_external.as_ref().and_then(|values| { + values.iter().find(|value| { + matches!( + RuleSelector::from_str(value), + Ok(RuleSelector::Linter(_) + | RuleSelector::Prefix { .. } + | RuleSelector::Rule { .. }) + ) + }) + }) { + return Err(anyhow::anyhow!( + "Internal rule `{internal}` cannot be disabled via `lint.ignore-external`; use `lint.ignore` instead." + )); + } + if let Some(internal) = options + .common + .extend_ignore_external + .as_ref() + .and_then(|values| { + values.iter().find(|value| { + matches!( + RuleSelector::from_str(value), + Ok(RuleSelector::Linter(_) + | RuleSelector::Prefix { .. } + | RuleSelector::Rule { .. }) + ) + }) + }) + { + return Err(anyhow::anyhow!( + "Internal rule `{internal}` cannot be disabled via `lint.extend-ignore-external`; use `lint.extend-ignore` instead." + )); + } + + let external_ast_entries = options + .external_ast + .map(|entries| { + entries + .into_iter() + .map(|(id, entry)| { + let raw_path = entry + .path + .ok_or_else(|| anyhow!("external linter `{id}` must define `path`"))?; + let path = if raw_path.is_absolute() { + raw_path + } else { + project_root.join(raw_path) + }; + Ok(( + id, + ExternalLinterEntry { + path, + enabled: entry.enabled.unwrap_or(true), + }, + )) + }) + .collect::>>() + }) + .transpose()?; + Ok(LintConfiguration { exclude: options.exclude.map(|paths| { paths @@ -733,6 +991,12 @@ impl LintConfiguration { unfixable, extend_fixable: options.common.extend_fixable.unwrap_or_default(), }], + external_rule_selections: vec![ExternalRuleSelection { + select: options.common.select_external, + extend_select: options.common.extend_select_external.unwrap_or_default(), + ignore: options.common.ignore_external.unwrap_or_default(), + extend_ignore: options.common.extend_ignore_external.unwrap_or_default(), + }], extend_safe_fixes: options.common.extend_safe_fixes.unwrap_or_default(), extend_unsafe_fixes: options.common.extend_unsafe_fixes.unwrap_or_default(), allowed_confusables: options.common.allowed_confusables, @@ -755,6 +1019,7 @@ impl LintConfiguration { }) .unwrap_or_default(), external: options.common.external, + external_ast: external_ast_entries, ignore_init_module_imports, explicit_preview_rules: options.common.explicit_preview_rules, per_file_ignores: options.common.per_file_ignores.map(|per_file_ignores| { @@ -1136,6 +1401,8 @@ impl LintConfiguration { let mut extend_per_file_ignores = config.extend_per_file_ignores; extend_per_file_ignores.extend(self.extend_per_file_ignores); + let mut external_rule_selections = config.external_rule_selections; + external_rule_selections.extend(self.external_rule_selections); Self { exclude: self.exclude.or(config.exclude), @@ -1143,10 +1410,12 @@ impl LintConfiguration { rule_selections, extend_safe_fixes, extend_unsafe_fixes, + external_rule_selections, allowed_confusables: self.allowed_confusables.or(config.allowed_confusables), dummy_variable_rgx: self.dummy_variable_rgx.or(config.dummy_variable_rgx), extend_per_file_ignores, external: self.external.or(config.external), + external_ast: self.external_ast.or(config.external_ast), ignore_init_module_imports: self .ignore_init_module_imports .or(config.ignore_init_module_imports), @@ -1414,6 +1683,8 @@ fn warn_about_deprecated_top_level_lint_options( pyupgrade, per_file_ignores, extend_per_file_ignores, + select_external, + .. } = top_level_options; let mut used_options = Vec::new(); @@ -1472,6 +1743,9 @@ fn warn_about_deprecated_top_level_lint_options( if select.is_some() { used_options.push("select"); } + if select_external.is_some() { + used_options.push("select-external"); + } if explicit_preview_rules.is_some() { used_options.push("explicit-preview-rules"); diff --git a/crates/ruff_workspace/src/options.rs b/crates/ruff_workspace/src/options.rs index 472b0e66f4cfa..11e38ef0ba70f 100644 --- a/crates/ruff_workspace/src/options.rs +++ b/crates/ruff_workspace/src/options.rs @@ -554,6 +554,8 @@ pub struct LintOptions { "# )] pub future_annotations: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub external_ast: Option>, } /// Newtype wrapper for [`LintCommonOptions`] that allows customizing the JSON schema and omitting the fields from the [`OptionsMetadata`]. @@ -569,6 +571,23 @@ impl OptionsMetadata for DeprecatedTopLevelLintOptions { } } +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[derive(Clone, Debug, PartialEq, Eq, OptionsMetadata, CombineOptions, Serialize, Deserialize)] +#[serde(deny_unknown_fields, rename_all = "kebab-case")] +pub struct ExternalAstLinterOptions { + /// Path to the TOML file containing external lint rule definitions. + #[option( + default = "null", + value_type = "path", + example = r#"path = "lint/custom_rules.toml""# + )] + pub path: Option, + /// Whether this external linter should be considered during lint runs. + #[option(default = "true", value_type = "bool", example = "enabled = false")] + #[serde(default)] + pub enabled: Option, +} + #[cfg(feature = "schemars")] impl schemars::JsonSchema for DeprecatedTopLevelLintOptions { fn schema_name() -> std::borrow::Cow<'static, str> { @@ -666,6 +685,45 @@ pub struct LintCommonOptions { "# )] pub extend_select: Option>, + /// A list of external linter IDs or rule codes to enable. When omitted, external linters remain disabled. + #[option( + default = "[]", + value_type = "list[str]", + example = r#" + # Enable the `logging_interpolation` external linter by default. + select-external = ["logging_interpolation"] + "# + )] + pub select_external: Option>, + /// A list of external linter IDs or rule codes to enable, in addition to those specified by [`select-external`](#lint_select-external). + #[option( + default = "[]", + value_type = "list[str]", + example = r#" + # Enable the `logging_interpolation` external linter alongside those specified by `select-external`. + extend-select-external = ["logging_interpolation"] + "# + )] + pub extend_select_external: Option>, + /// A list of external linter IDs or rule codes to ignore. Prefixes are not supported. + #[option( + default = "[]", + value_type = "list[str]", + example = r#" + # Suppress the `logging_interpolation.dangerous` rule, even if the linter is otherwise enabled. + ignore-external = ["logging_interpolation.dangerous"] + "# + )] + pub ignore_external: Option>, + /// A list of external linter IDs or rule codes to ignore, in addition to those specified by [`ignore-external`](#lint_ignore-external). + #[option( + default = "[]", + value_type = "list[str]", + example = r#" + extend-ignore-external = ["logging_interpolation.dangerous"] + "# + )] + pub extend_ignore_external: Option>, /// A list of rule codes or prefixes to consider fixable, in addition to those /// specified by [`fixable`](#lint_fixable). @@ -3916,9 +3974,14 @@ pub struct LintOptionsWire { dummy_variable_rgx: Option, extend_ignore: Option>, extend_select: Option>, + select_external: Option>, + extend_select_external: Option>, + ignore_external: Option>, + extend_ignore_external: Option>, extend_fixable: Option>, extend_unfixable: Option>, external: Option>, + external_ast: Option>, fixable: Option>, ignore: Option>, extend_safe_fixes: Option>, @@ -3973,9 +4036,14 @@ impl From for LintOptions { dummy_variable_rgx, extend_ignore, extend_select, + select_external, + extend_select_external, + ignore_external, + extend_ignore_external, extend_fixable, extend_unfixable, external, + external_ast, fixable, ignore, extend_safe_fixes, @@ -4029,6 +4097,10 @@ impl From for LintOptions { dummy_variable_rgx, extend_ignore, extend_select, + select_external, + extend_select_external, + ignore_external, + extend_ignore_external, extend_fixable, extend_unfixable, external, @@ -4076,6 +4148,7 @@ impl From for LintOptions { ruff, preview, typing_extensions, + external_ast, future_annotations, } } diff --git a/crates/ruff_workspace/src/resolver.rs b/crates/ruff_workspace/src/resolver.rs index 1740cc184a250..b184894548be3 100644 --- a/crates/ruff_workspace/src/resolver.rs +++ b/crates/ruff_workspace/src/resolver.rs @@ -257,6 +257,23 @@ impl<'a> Resolver<'a> { pub fn settings(&self) -> impl Iterator { std::iter::once(&self.pyproject_config.settings).chain(&self.settings) } + + /// Return a mutable iterator over resolved [`Settings`] excluding the base configuration. + pub fn settings_mut(&mut self) -> impl Iterator { + self.settings.iter_mut() + } + + /// Apply a transformation to each resolved [`Settings`] (excluding the base configuration) + /// and return the [`Resolver`] for further use. + pub fn transform_settings(mut self, mut f: F) -> Result + where + F: FnMut(&mut Settings) -> Result<()>, + { + for settings in &mut self.settings { + f(settings)?; + } + Ok(self) + } } /// A wrapper around `detect_package_root` to cache filesystem lookups. diff --git a/docs/configuration.md b/docs/configuration.md index 3420611e1804a..7796bcf5055c8 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -679,6 +679,22 @@ Miscellaneous: Exit with a non-zero status code if any files were modified via fix, even if no lint violations remain +External linter options: + --list-external-linters + List configured external AST linters and exit + --select-external + Restrict linting to the given external linter IDs + --extend-select-external + Enable additional external linter IDs or rule codes without replacing + existing selections + --ignore-external + Disable the given external linter IDs or rule codes + --extend-ignore-external + Disable additional external linter IDs or rule codes without + replacing existing ignores + --verify-external-linters + Validate external linter definitions without running lint checks + Log levels: -v, --verbose Enable verbose logging -q, --quiet Print diagnostics, but nothing else diff --git a/ruff.schema.json b/ruff.schema.json index 1c8a092042a57..32d756a67e8bd 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -109,6 +109,17 @@ "$ref": "#/definitions/RuleSelector" } }, + "extend-ignore-external": { + "description": "A list of external linter IDs or rule codes to ignore, in addition to those specified by [`ignore-external`](#lint_ignore-external).", + "type": [ + "array", + "null" + ], + "deprecated": true, + "items": { + "type": "string" + } + }, "extend-include": { "description": "A list of file patterns to include when linting, in addition to those\nspecified by [`include`](#include).\n\nInclusion are based on globs, and should be single-path patterns, like\n`*.pyw`, to include any file with the `.pyw` extension.\n\nFor more information on the glob syntax, refer to the [`globset` documentation](https://docs.rs/globset/latest/globset/#syntax).", "type": [ @@ -155,6 +166,17 @@ "$ref": "#/definitions/RuleSelector" } }, + "extend-select-external": { + "description": "A list of external linter IDs or rule codes to enable, in addition to those specified by [`select-external`](#lint_select-external).", + "type": [ + "array", + "null" + ], + "deprecated": true, + "items": { + "type": "string" + } + }, "extend-unfixable": { "description": "A list of rule codes or prefixes to consider non-auto-fixable, in addition to those\nspecified by [`unfixable`](#lint_unfixable).", "type": [ @@ -446,6 +468,17 @@ "$ref": "#/definitions/RuleSelector" } }, + "ignore-external": { + "description": "A list of external linter IDs or rule codes to ignore. Prefixes are not supported.", + "type": [ + "array", + "null" + ], + "deprecated": true, + "items": { + "type": "string" + } + }, "ignore-init-module-imports": { "description": "Avoid automatically removing unused imports in `__init__.py` files. Such\nimports will still be flagged, but with a dedicated message suggesting\nthat the import is either added to the module's `__all__` symbol, or\nre-exported with a redundant alias (e.g., `import os as os`).\n\nThis option is enabled by default, but you can opt-in to removal of imports\nvia an unsafe fix.", "type": [ @@ -684,6 +717,17 @@ "$ref": "#/definitions/RuleSelector" } }, + "select-external": { + "description": "A list of external linter IDs or rule codes to enable. When omitted, external linters remain disabled.", + "type": [ + "array", + "null" + ], + "deprecated": true, + "items": { + "type": "string" + } + }, "show-fixes": { "description": "Whether to show an enumeration of all fixed lint violations\n(overridden by the `--show-fixes` command-line flag).", "type": [ @@ -907,6 +951,27 @@ } ] }, + "ExternalAstLinterOptions": { + "type": "object", + "properties": { + "enabled": { + "description": "Whether this external linter should be considered during lint runs.", + "type": [ + "boolean", + "null" + ], + "default": null + }, + "path": { + "description": "Path to the TOML file containing external lint rule definitions.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, "Flake8AnnotationsOptions": { "description": "Options for the `flake8-annotations` plugin.", "type": "object", @@ -2016,6 +2081,16 @@ "$ref": "#/definitions/RuleSelector" } }, + "extend-ignore-external": { + "description": "A list of external linter IDs or rule codes to ignore, in addition to those specified by [`ignore-external`](#lint_ignore-external).", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, "extend-per-file-ignores": { "description": "A list of mappings from file pattern to rule codes or prefixes to\nexclude, in addition to any rules excluded by [`per-file-ignores`](#lint_per-file-ignores).", "type": [ @@ -2049,6 +2124,16 @@ "$ref": "#/definitions/RuleSelector" } }, + "extend-select-external": { + "description": "A list of external linter IDs or rule codes to enable, in addition to those specified by [`select-external`](#lint_select-external).", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, "extend-unfixable": { "description": "A list of rule codes or prefixes to consider non-auto-fixable, in addition to those\nspecified by [`unfixable`](#lint_unfixable).", "type": [ @@ -2080,6 +2165,15 @@ "type": "string" } }, + "external-ast": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "$ref": "#/definitions/ExternalAstLinterOptions" + } + }, "fixable": { "description": "A list of rule codes or prefixes to consider fixable. By default,\nall rules are considered fixable.", "type": [ @@ -2294,6 +2388,16 @@ "$ref": "#/definitions/RuleSelector" } }, + "ignore-external": { + "description": "A list of external linter IDs or rule codes to ignore. Prefixes are not supported.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, "ignore-init-module-imports": { "description": "Avoid automatically removing unused imports in `__init__.py` files. Such\nimports will still be flagged, but with a dedicated message suggesting\nthat the import is either added to the module's `__all__` symbol, or\nre-exported with a redundant alias (e.g., `import os as os`).\n\nThis option is enabled by default, but you can opt-in to removal of imports\nvia an unsafe fix.", "type": [ @@ -2452,6 +2556,16 @@ "$ref": "#/definitions/RuleSelector" } }, + "select-external": { + "description": "A list of external linter IDs or rule codes to enable. When omitted, external linters remain disabled.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, "task-tags": { "description": "A list of task tags to recognize (e.g., \"TODO\", \"FIXME\", \"XXX\").\n\nComments starting with these tags will be ignored by commented-out code\ndetection (`ERA`), and skipped by line-length rules (`E501`) if\n[`ignore-overlong-task-comments`](#lint_pycodestyle_ignore-overlong-task-comments) is set to `true`.", "type": [ @@ -4053,6 +4167,9 @@ "RUF2", "RUF20", "RUF200", + "RUF3", + "RUF30", + "RUF300", "S", "S1", "S10", diff --git a/scripts/nogil/build_python_3_13_nogil.sh b/scripts/nogil/build_python_3_13_nogil.sh new file mode 100755 index 0000000000000..59295b3267c15 --- /dev/null +++ b/scripts/nogil/build_python_3_13_nogil.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Build a free-threaded Python 3.13 with the GIL disabled and libpython shared. +# Usage: +# ./scripts/build_python_3_13_nogil.sh [/path/to/prefix] +# Environment variables: +# PYTHON_VERSION Override the version (default: 3.13.0) +# BUILD_JOBS Override the job count for make (default: nproc) + +PYTHON_VERSION="${PYTHON_VERSION:-3.13.0}" +PREFIX="${1:-${PYTHON_PREFIX:-"$HOME/python-${PYTHON_VERSION}-nogil"}}" + +if command -v nproc >/dev/null 2>&1; then + DEFAULT_JOBS="$(nproc)" +elif command -v sysctl >/dev/null 2>&1; then + DEFAULT_JOBS="$(sysctl -n hw.ncpu)" +else + DEFAULT_JOBS=1 +fi + +JOBS="${BUILD_JOBS:-${DEFAULT_JOBS}}" + +TARBALL="Python-${PYTHON_VERSION}.tgz" +SOURCE_DIR="Python-${PYTHON_VERSION}" +DOWNLOAD_URL="https://www.python.org/ftp/python/${PYTHON_VERSION}/${TARBALL}" + +echo "Downloading ${DOWNLOAD_URL}..." +curl -L "${DOWNLOAD_URL}" -o "${TARBALL}" + +echo "Extracting ${TARBALL}..." +rm -rf "${SOURCE_DIR}" +tar -xzf "${TARBALL}" + +pushd "${SOURCE_DIR}" >/dev/null + +echo "Configuring Python ${PYTHON_VERSION} (prefix=${PREFIX})..." +./configure \ + --prefix="${PREFIX}" \ + --disable-gil \ + --enable-shared \ + --with-ensurepip=install + +echo "Building with ${JOBS} job(s)..." +make -j"${JOBS}" + +echo "Installing to ${PREFIX}..." +make install + +popd >/dev/null + +cat <&2 + return 1 + fi + + PYTHON_VERSION="${PYTHON_VERSION:-3.13}" + RAW_PREFIX="${1:-}" + + if [[ -z "${RAW_PREFIX}" ]]; then + echo "Usage: source scripts/nogil/nogil_env.sh /path/to/python-3.13-nogil" >&2 + return 1 + fi + + if ! PREFIX="$(cd "${RAW_PREFIX}" 2>/dev/null && pwd)"; then + echo "nogil_env.sh: cannot resolve '${RAW_PREFIX}'" >&2 + return 1 + fi + + PYTHON_BIN="${PREFIX}/bin/python${PYTHON_VERSION}" + STDLIB_DIR="${PREFIX}/lib/python${PYTHON_VERSION}" + DYNLOAD_DIR="${STDLIB_DIR}/lib-dynload" + LIB_DIR="${PREFIX}/lib" + + export PATH="${PREFIX}/bin:${PATH}" + export PYTHONPATH="${STDLIB_DIR}:${DYNLOAD_DIR}${PYTHONPATH:+:${PYTHONPATH}}" + export DYLD_LIBRARY_PATH="${LIB_DIR}${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}}" + export LD_LIBRARY_PATH="${LIB_DIR}${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}" + + export PYO3_PYTHON="${PYTHON_BIN}" + export PYTHONEXECUTABLE="${PYTHON_BIN}" + export PYTHONHOME="${PREFIX}" +} + +nogil_env "$@"