diff --git a/Cargo.lock b/Cargo.lock index c902caeb4d7f..fdacd82a70e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -108,8 +108,8 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -123,12 +123,22 @@ name = "cfg-if" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "chalk-derive" +version = "0.1.0" +source = "git+https://github.com/rust-lang/chalk.git?rev=8314f2fcec8582a58c24b638f1a259d4145a0809#8314f2fcec8582a58c24b638f1a259d4145a0809" +dependencies = [ + "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "chalk-engine" version = "0.9.0" -source = "git+https://github.com/rust-lang/chalk.git#13303bb0067c6ed0572322080ae367ee38f9e7c9" +source = "git+https://github.com/rust-lang/chalk.git?rev=8314f2fcec8582a58c24b638f1a259d4145a0809#8314f2fcec8582a58c24b638f1a259d4145a0809" dependencies = [ - "chalk-macros 0.1.1 (git+https://github.com/rust-lang/chalk.git)", + "chalk-macros 0.1.1 (git+https://github.com/rust-lang/chalk.git?rev=8314f2fcec8582a58c24b638f1a259d4145a0809)", "rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "stacker 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -136,17 +146,18 @@ dependencies = [ [[package]] name = "chalk-ir" version = "0.1.0" -source = "git+https://github.com/rust-lang/chalk.git#13303bb0067c6ed0572322080ae367ee38f9e7c9" +source = "git+https://github.com/rust-lang/chalk.git?rev=8314f2fcec8582a58c24b638f1a259d4145a0809#8314f2fcec8582a58c24b638f1a259d4145a0809" dependencies = [ - "chalk-engine 0.9.0 (git+https://github.com/rust-lang/chalk.git)", - "chalk-macros 0.1.1 (git+https://github.com/rust-lang/chalk.git)", + "chalk-derive 0.1.0 (git+https://github.com/rust-lang/chalk.git?rev=8314f2fcec8582a58c24b638f1a259d4145a0809)", + "chalk-engine 0.9.0 (git+https://github.com/rust-lang/chalk.git?rev=8314f2fcec8582a58c24b638f1a259d4145a0809)", + "chalk-macros 0.1.1 (git+https://github.com/rust-lang/chalk.git?rev=8314f2fcec8582a58c24b638f1a259d4145a0809)", "lalrpop-intern 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "chalk-macros" version = "0.1.1" -source = "git+https://github.com/rust-lang/chalk.git#13303bb0067c6ed0572322080ae367ee38f9e7c9" +source = "git+https://github.com/rust-lang/chalk.git?rev=8314f2fcec8582a58c24b638f1a259d4145a0809#8314f2fcec8582a58c24b638f1a259d4145a0809" dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -154,24 +165,25 @@ dependencies = [ [[package]] name = "chalk-rust-ir" version = "0.1.0" -source = "git+https://github.com/rust-lang/chalk.git#13303bb0067c6ed0572322080ae367ee38f9e7c9" +source = "git+https://github.com/rust-lang/chalk.git?rev=8314f2fcec8582a58c24b638f1a259d4145a0809#8314f2fcec8582a58c24b638f1a259d4145a0809" dependencies = [ - "chalk-engine 0.9.0 (git+https://github.com/rust-lang/chalk.git)", - "chalk-ir 0.1.0 (git+https://github.com/rust-lang/chalk.git)", - "chalk-macros 0.1.1 (git+https://github.com/rust-lang/chalk.git)", + "chalk-derive 0.1.0 (git+https://github.com/rust-lang/chalk.git?rev=8314f2fcec8582a58c24b638f1a259d4145a0809)", + "chalk-engine 0.9.0 (git+https://github.com/rust-lang/chalk.git?rev=8314f2fcec8582a58c24b638f1a259d4145a0809)", + "chalk-ir 0.1.0 (git+https://github.com/rust-lang/chalk.git?rev=8314f2fcec8582a58c24b638f1a259d4145a0809)", + "chalk-macros 0.1.1 (git+https://github.com/rust-lang/chalk.git?rev=8314f2fcec8582a58c24b638f1a259d4145a0809)", ] [[package]] name = "chalk-solve" version = "0.1.0" -source = "git+https://github.com/rust-lang/chalk.git#13303bb0067c6ed0572322080ae367ee38f9e7c9" +source = "git+https://github.com/rust-lang/chalk.git?rev=8314f2fcec8582a58c24b638f1a259d4145a0809#8314f2fcec8582a58c24b638f1a259d4145a0809" dependencies = [ - "chalk-engine 0.9.0 (git+https://github.com/rust-lang/chalk.git)", - "chalk-ir 0.1.0 (git+https://github.com/rust-lang/chalk.git)", - "chalk-macros 0.1.1 (git+https://github.com/rust-lang/chalk.git)", - "chalk-rust-ir 0.1.0 (git+https://github.com/rust-lang/chalk.git)", + "chalk-engine 0.9.0 (git+https://github.com/rust-lang/chalk.git?rev=8314f2fcec8582a58c24b638f1a259d4145a0809)", + "chalk-ir 0.1.0 (git+https://github.com/rust-lang/chalk.git?rev=8314f2fcec8582a58c24b638f1a259d4145a0809)", + "chalk-macros 0.1.1 (git+https://github.com/rust-lang/chalk.git?rev=8314f2fcec8582a58c24b638f1a259d4145a0809)", + "chalk-rust-ir 0.1.0 (git+https://github.com/rust-lang/chalk.git?rev=8314f2fcec8582a58c24b638f1a259d4145a0809)", "ena 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", - "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "petgraph 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -288,7 +300,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -413,7 +425,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "getrandom" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", @@ -443,7 +455,7 @@ name = "heck" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-segmentation 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -501,7 +513,7 @@ dependencies = [ "console 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", "serde_yaml 0.8.11 (registry+https://github.com/rust-lang/crates.io-index)", "uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -517,7 +529,7 @@ dependencies = [ [[package]] name = "itertools" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -624,7 +636,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "crossbeam-channel 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -634,7 +646,7 @@ version = "0.61.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", "serde_repr 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -790,7 +802,7 @@ dependencies = [ "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -811,7 +823,7 @@ dependencies = [ "proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -845,7 +857,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -903,7 +915,7 @@ name = "ra_assists" version = "0.1.0" dependencies = [ "format-buf 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "join_to_string 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "ra_db 0.1.0", "ra_fmt 0.1.0", @@ -970,7 +982,7 @@ dependencies = [ name = "ra_fmt" version = "0.1.0" dependencies = [ - "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "ra_syntax 0.1.0", ] @@ -979,9 +991,9 @@ name = "ra_hir" version = "0.1.0" dependencies = [ "arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "chalk-ir 0.1.0 (git+https://github.com/rust-lang/chalk.git)", - "chalk-rust-ir 0.1.0 (git+https://github.com/rust-lang/chalk.git)", - "chalk-solve 0.1.0 (git+https://github.com/rust-lang/chalk.git)", + "chalk-ir 0.1.0 (git+https://github.com/rust-lang/chalk.git?rev=8314f2fcec8582a58c24b638f1a259d4145a0809)", + "chalk-rust-ir 0.1.0 (git+https://github.com/rust-lang/chalk.git?rev=8314f2fcec8582a58c24b638f1a259d4145a0809)", + "chalk-solve 0.1.0 (git+https://github.com/rust-lang/chalk.git?rev=8314f2fcec8582a58c24b638f1a259d4145a0809)", "ena 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", "insta 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", "lalrpop-intern 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -991,6 +1003,8 @@ dependencies = [ "ra_arena 0.1.0", "ra_cfg 0.1.0", "ra_db 0.1.0", + "ra_hir_def 0.1.0", + "ra_hir_expand 0.1.0", "ra_mbe 0.1.0", "ra_prof 0.1.0", "ra_syntax 0.1.0", @@ -1000,6 +1014,38 @@ dependencies = [ "test_utils 0.1.0", ] +[[package]] +name = "ra_hir_def" +version = "0.1.0" +dependencies = [ + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "once_cell 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ra_arena 0.1.0", + "ra_cfg 0.1.0", + "ra_db 0.1.0", + "ra_hir_expand 0.1.0", + "ra_mbe 0.1.0", + "ra_prof 0.1.0", + "ra_syntax 0.1.0", + "ra_tt 0.1.0", + "relative-path 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "test_utils 0.1.0", +] + +[[package]] +name = "ra_hir_expand" +version = "0.1.0" +dependencies = [ + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "ra_arena 0.1.0", + "ra_db 0.1.0", + "ra_mbe 0.1.0", + "ra_prof 0.1.0", + "ra_syntax 0.1.0", + "ra_tt 0.1.0", +] + [[package]] name = "ra_ide_api" version = "0.1.0" @@ -1007,7 +1053,7 @@ dependencies = [ "format-buf 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "fst 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "insta 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", - "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "join_to_string 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "once_cell 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1049,7 +1095,7 @@ dependencies = [ "ra_vfs_glob 0.1.0", "relative-path 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "test_utils 0.1.0", @@ -1065,7 +1111,7 @@ dependencies = [ "ra_syntax 0.1.0", "ra_tt 0.1.0", "rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", "test_utils 0.1.0", ] @@ -1081,7 +1127,7 @@ name = "ra_prof" version = "0.1.0" dependencies = [ "backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)", - "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "jemalloc-ctl 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "jemallocator 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "once_cell 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1097,7 +1143,7 @@ dependencies = [ "ra_cfg 0.1.0", "ra_db 0.1.0", "rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1106,7 +1152,7 @@ name = "ra_syntax" version = "0.1.0" dependencies = [ "arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "once_cell 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "ra_parser 0.1.0", "ra_text_edit 0.1.0", @@ -1180,7 +1226,7 @@ name = "rand" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", "rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1224,7 +1270,7 @@ name = "rand_core" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1370,7 +1416,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1430,7 +1476,7 @@ dependencies = [ "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "salsa-macros 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)", - "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1441,7 +1487,7 @@ dependencies = [ "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1463,7 +1509,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1473,20 +1519,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "serde" -version = "1.0.101" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "serde_derive" -version = "1.0.101" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1496,7 +1542,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1506,7 +1552,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1516,7 +1562,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", "yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1527,7 +1573,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "smallvec" -version = "0.6.10" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -1535,7 +1581,7 @@ name = "smol_str" version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1557,7 +1603,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "syn" -version = "1.0.5" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1647,12 +1693,12 @@ name = "unicode-normalization" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "unicode-segmentation" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -1673,7 +1719,7 @@ dependencies = [ "idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1682,7 +1728,7 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1759,7 +1805,7 @@ dependencies = [ "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "ron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", "walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1794,11 +1840,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum cargo_metadata 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8d2d1617e838936c0d2323a65cc151e03ae19a7678dd24f72bccf27119b90a5d" "checksum cc 1.0.46 (registry+https://github.com/rust-lang/crates.io-index)" = "0213d356d3c4ea2c18c40b037c3be23cd639825c18f25ee670ac7813beeef99c" "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" -"checksum chalk-engine 0.9.0 (git+https://github.com/rust-lang/chalk.git)" = "" -"checksum chalk-ir 0.1.0 (git+https://github.com/rust-lang/chalk.git)" = "" -"checksum chalk-macros 0.1.1 (git+https://github.com/rust-lang/chalk.git)" = "" -"checksum chalk-rust-ir 0.1.0 (git+https://github.com/rust-lang/chalk.git)" = "" -"checksum chalk-solve 0.1.0 (git+https://github.com/rust-lang/chalk.git)" = "" +"checksum chalk-derive 0.1.0 (git+https://github.com/rust-lang/chalk.git?rev=8314f2fcec8582a58c24b638f1a259d4145a0809)" = "" +"checksum chalk-engine 0.9.0 (git+https://github.com/rust-lang/chalk.git?rev=8314f2fcec8582a58c24b638f1a259d4145a0809)" = "" +"checksum chalk-ir 0.1.0 (git+https://github.com/rust-lang/chalk.git?rev=8314f2fcec8582a58c24b638f1a259d4145a0809)" = "" +"checksum chalk-macros 0.1.1 (git+https://github.com/rust-lang/chalk.git?rev=8314f2fcec8582a58c24b638f1a259d4145a0809)" = "" +"checksum chalk-rust-ir 0.1.0 (git+https://github.com/rust-lang/chalk.git?rev=8314f2fcec8582a58c24b638f1a259d4145a0809)" = "" +"checksum chalk-solve 0.1.0 (git+https://github.com/rust-lang/chalk.git?rev=8314f2fcec8582a58c24b638f1a259d4145a0809)" = "" "checksum chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e8493056968583b0193c1bb04d6f7684586f3726992d6c573261941a895dbd68" "checksum clicolors-control 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90082ee5dcdd64dc4e9e0d37fbf3ee325419e39c0092191e0393df65518f741e" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" @@ -1828,7 +1875,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" -"checksum getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "473a1265acc8ff1e808cd0a1af8cee3c2ee5200916058a2ca113c29f2d903571" +"checksum getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "e7db7ca94ed4cd01190ceee0d8a8052f08a247aa1b469a7f68c6a3b71afcf407" "checksum glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" "checksum globset 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "925aa2cac82d8834e2b2a4415b6f6879757fb5c0928fc445ae76461a12eed8f2" "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" @@ -1839,7 +1886,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum inotify-sys 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e74a1aa87c59aeff6ef2cc2fa62d41bc43f54952f55652656b18a02fd5e356c0" "checksum insta 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0d499dc062e841590a67230d853bce62d0abeb91304927871670b7c55c461349" "checksum iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" -"checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358" +"checksum itertools 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "87fa75c9dea7b07be3138c49abbb83fd4bea199b5cdc76f9804458edc5da0d6e" "checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" "checksum jemalloc-ctl 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c502a5ff9dd2924f1ed32ba96e3b65735d837b4bfd978d3161b1702e66aca4b7" "checksum jemalloc-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0d3b9f3f5c9b31aa0f5ed3260385ac205db665baa41d49bb8338008ae94ede45" @@ -1922,17 +1969,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" -"checksum serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "9796c9b7ba2ffe7a9ce53c2287dfc48080f4b2b362fcc245a259b3a7201119dd" -"checksum serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "4b133a43a1ecd55d4086bd5b4dc6c1751c68b1bfbeba7a5040442022c7e7c02e" +"checksum serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)" = "0c4b39bd9b0b087684013a792c59e3e07a46a01d2322518d8a1104641a0b1be0" +"checksum serde_derive 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)" = "ca13fc1a832f793322228923fbb3aba9f3f44444898f835d31ad1b74fa0a2bf8" "checksum serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)" = "2f72eb2a68a7dc3f9a691bfda9305a1c017a6215e5a4545c258500d2099a37c2" "checksum serde_repr 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "cd02c7587ec314570041b2754829f84d873ced14a96d1fd1823531e11db40573" "checksum serde_yaml 0.8.11 (registry+https://github.com/rust-lang/crates.io-index)" = "691b17f19fc1ec9d94ec0b5864859290dff279dbd7b03f017afda54eb36c3c35" "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" -"checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7" +"checksum smallvec 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "cefaa50e76a6f10b86f36e640eb1739eafbd4084865067778463913e43a77ff3" "checksum smol_str 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "34836c9a295c62c2ce3514471117c5cb269891e8421b2aafdd910050576c4d8b" "checksum stacker 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "d96fc4f13a0ac088e9a3cd9af1cc8c5cc1ab5deb2145cef661267dfc9c542f8a" "checksum superslice 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ab16ced94dbd8a46c82fd81e3ed9a8727dac2977ea869d217bcc4ea1f122e81f" -"checksum syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf" +"checksum syn 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "0e7bedb3320d0f3035594b0b723c8a28d7d336a3eda3881db79e61d676fb644c" "checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" "checksum termios 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "72b620c5ea021d75a735c943269bb07d30c9b77d6ac6b236bc8b5c496ef05625" "checksum text_unit 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e08bbcb7a3adbda0eb23431206b653bdad3d8dea311e72d36bf2215e27a42579" @@ -1942,7 +1989,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum unicase 2.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2e2e6bd1e59e56598518beb94fd6db628ded570326f0a98c679a304bd9f00150" "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" "checksum unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "141339a08b982d942be2ca06ff8b076563cbe223d1befd5450716790d44e2426" -"checksum unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1967f4cdfc355b37fd76d2a954fb2ed3871034eb4f26d60537d88795cfc332a9" +"checksum unicode-segmentation 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dc5415c074426c7c65db13bd647c23d78c0fb2e10dca0b8fb0f40058a59bccdf" "checksum unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7007dbd421b92cc6e28410fe7362e2e0a2503394908f417b68ec8d1c364c4e20" "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" "checksum url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "75b414f6c464c879d7f9babf951f23bc3743fb7313c081b2e6ca719067ea9d61" diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs index e270c5d60da3..1908bdec90df 100644 --- a/crates/ra_assists/src/assist_ctx.rs +++ b/crates/ra_assists/src/assist_ctx.rs @@ -1,11 +1,11 @@ -//! FIXME: write short doc here +//! This module defines `AssistCtx` -- the API surface that is exposed to assists. use hir::db::HirDatabase; use ra_db::FileRange; use ra_fmt::{leading_indent, reindent}; use ra_syntax::{ algo::{self, find_covering_element, find_node_at_offset}, - AstNode, SourceFile, SyntaxElement, SyntaxNode, SyntaxToken, TextRange, TextUnit, + AstNode, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextUnit, TokenAtOffset, }; use ra_text_edit::TextEditBuilder; @@ -14,8 +14,8 @@ use crate::{AssistAction, AssistId, AssistLabel}; #[derive(Clone, Debug)] pub(crate) enum Assist { - Unresolved(Vec), - Resolved(Vec<(AssistLabel, AssistAction)>), + Unresolved { label: AssistLabel }, + Resolved { label: AssistLabel, action: AssistAction }, } /// `AssistCtx` allows to apply an assist or check if it could be applied. @@ -54,7 +54,6 @@ pub(crate) struct AssistCtx<'a, DB> { pub(crate) frange: FileRange, source_file: SourceFile, should_compute_edit: bool, - assist: Assist, } impl<'a, DB> Clone for AssistCtx<'a, DB> { @@ -64,7 +63,6 @@ impl<'a, DB> Clone for AssistCtx<'a, DB> { frange: self.frange, source_file: self.source_file.clone(), should_compute_edit: self.should_compute_edit, - assist: self.assist.clone(), } } } @@ -75,43 +73,41 @@ impl<'a, DB: HirDatabase> AssistCtx<'a, DB> { F: FnOnce(AssistCtx) -> T, { let parse = db.parse(frange.file_id); - let assist = - if should_compute_edit { Assist::Resolved(vec![]) } else { Assist::Unresolved(vec![]) }; - let ctx = AssistCtx { db, frange, source_file: parse.tree(), should_compute_edit, assist }; + let ctx = AssistCtx { db, frange, source_file: parse.tree(), should_compute_edit }; f(ctx) } - pub(crate) fn add_action( - &mut self, + pub(crate) fn add_assist( + self, id: AssistId, label: impl Into, f: impl FnOnce(&mut AssistBuilder), - ) -> &mut Self { + ) -> Option { let label = AssistLabel { label: label.into(), id }; - match &mut self.assist { - Assist::Unresolved(labels) => labels.push(label), - Assist::Resolved(labels_actions) => { - let action = { - let mut edit = AssistBuilder::default(); - f(&mut edit); - edit.build() - }; - labels_actions.push((label, action)); - } - } - self - } + let assist = if self.should_compute_edit { + let action = { + let mut edit = AssistBuilder::default(); + f(&mut edit); + edit.build() + }; + Assist::Resolved { label, action } + } else { + Assist::Unresolved { label } + }; - pub(crate) fn build(self) -> Option { - Some(self.assist) + Some(assist) } pub(crate) fn token_at_offset(&self) -> TokenAtOffset { self.source_file.syntax().token_at_offset(self.frange.range.start()) } - pub(crate) fn node_at_offset(&self) -> Option { + pub(crate) fn find_token_at_offset(&self, kind: SyntaxKind) -> Option { + self.token_at_offset().find(|it| it.kind() == kind) + } + + pub(crate) fn find_node_at_offset(&self) -> Option { find_node_at_offset(self.source_file.syntax(), self.frange.range.start()) } pub(crate) fn covering_element(&self) -> SyntaxElement { diff --git a/crates/ra_assists/src/assists/add_derive.rs b/crates/ra_assists/src/assists/add_derive.rs index 77ecc33c9d9f..764b17bd8dc4 100644 --- a/crates/ra_assists/src/assists/add_derive.rs +++ b/crates/ra_assists/src/assists/add_derive.rs @@ -1,5 +1,3 @@ -//! FIXME: write short doc here - use hir::db::HirDatabase; use ra_syntax::{ ast::{self, AstNode, AttrsOwner}, @@ -9,10 +7,28 @@ use ra_syntax::{ use crate::{Assist, AssistCtx, AssistId}; -pub(crate) fn add_derive(mut ctx: AssistCtx) -> Option { - let nominal = ctx.node_at_offset::()?; +// Assist: add_derive +// +// Adds a new `#[derive()]` clause to a struct or enum. +// +// ``` +// struct Point { +// x: u32, +// y: u32,<|> +// } +// ``` +// -> +// ``` +// #[derive()] +// struct Point { +// x: u32, +// y: u32, +// } +// ``` +pub(crate) fn add_derive(ctx: AssistCtx) -> Option { + let nominal = ctx.find_node_at_offset::()?; let node_start = derive_insertion_offset(&nominal)?; - ctx.add_action(AssistId("add_derive"), "add `#[derive]`", |edit| { + ctx.add_assist(AssistId("add_derive"), "add `#[derive]`", |edit| { let derive_attr = nominal .attrs() .filter_map(|x| x.as_simple_call()) @@ -28,9 +44,7 @@ pub(crate) fn add_derive(mut ctx: AssistCtx) -> Option }; edit.target(nominal.syntax().text_range()); edit.set_cursor(offset) - }); - - ctx.build() + }) } // Insert `derive` after doc comments. diff --git a/crates/ra_assists/src/assists/add_explicit_type.rs b/crates/ra_assists/src/assists/add_explicit_type.rs index 8c83dc9878c3..ddda1a0f2c66 100644 --- a/crates/ra_assists/src/assists/add_explicit_type.rs +++ b/crates/ra_assists/src/assists/add_explicit_type.rs @@ -1,5 +1,3 @@ -//! FIXME: write short doc here - use hir::{db::HirDatabase, HirDisplay, Ty}; use ra_syntax::{ ast::{self, AstNode, LetStmt, NameOwner}, @@ -8,9 +6,23 @@ use ra_syntax::{ use crate::{Assist, AssistCtx, AssistId}; -/// Add explicit type assist. -pub(crate) fn add_explicit_type(mut ctx: AssistCtx) -> Option { - let stmt = ctx.node_at_offset::()?; +// Assist: add_explicit_type +// +// Specify type for a let binding. +// +// ``` +// fn main() { +// let x<|> = 92; +// } +// ``` +// -> +// ``` +// fn main() { +// let x: i32 = 92; +// } +// ``` +pub(crate) fn add_explicit_type(ctx: AssistCtx) -> Option { + let stmt = ctx.find_node_at_offset::()?; let expr = stmt.initializer()?; let pat = stmt.pat()?; // Must be a binding @@ -35,11 +47,10 @@ pub(crate) fn add_explicit_type(mut ctx: AssistCtx) -> Option< return None; } - ctx.add_action(AssistId("add_explicit_type"), "add explicit type", |edit| { + ctx.add_assist(AssistId("add_explicit_type"), "add explicit type", |edit| { edit.target(pat_range); edit.insert(name_range.end(), format!(": {}", ty.display(db))); - }); - ctx.build() + }) } /// Returns true if any type parameter is unknown diff --git a/crates/ra_assists/src/assists/add_impl.rs b/crates/ra_assists/src/assists/add_impl.rs index 94801fbc9a0e..7da0cfd0d326 100644 --- a/crates/ra_assists/src/assists/add_impl.rs +++ b/crates/ra_assists/src/assists/add_impl.rs @@ -1,5 +1,3 @@ -//! FIXME: write short doc here - use format_buf::format; use hir::db::HirDatabase; use join_to_string::join; @@ -10,10 +8,29 @@ use ra_syntax::{ use crate::{Assist, AssistCtx, AssistId}; -pub(crate) fn add_impl(mut ctx: AssistCtx) -> Option { - let nominal = ctx.node_at_offset::()?; +// Assist: add_impl +// +// Adds a new inherent impl for a type. +// +// ``` +// struct Ctx { +// data: T,<|> +// } +// ``` +// -> +// ``` +// struct Ctx { +// data: T, +// } +// +// impl Ctx { +// +// } +// ``` +pub(crate) fn add_impl(ctx: AssistCtx) -> Option { + let nominal = ctx.find_node_at_offset::()?; let name = nominal.name()?; - ctx.add_action(AssistId("add_impl"), "add impl", |edit| { + ctx.add_assist(AssistId("add_impl"), "add impl", |edit| { edit.target(nominal.syntax().text_range()); let type_params = nominal.type_param_list(); let start_offset = nominal.syntax().text_range().end(); @@ -37,9 +54,7 @@ pub(crate) fn add_impl(mut ctx: AssistCtx) -> Option { edit.set_cursor(start_offset + TextUnit::of_str(&buf)); buf.push_str("\n}"); edit.insert(start_offset, buf); - }); - - ctx.build() + }) } #[cfg(test)] diff --git a/crates/ra_assists/src/assists/auto_import.rs b/crates/ra_assists/src/assists/add_import.rs similarity index 89% rename from crates/ra_assists/src/assists/auto_import.rs rename to crates/ra_assists/src/assists/add_import.rs index 02c58e7c6de6..363ade016b04 100644 --- a/crates/ra_assists/src/assists/auto_import.rs +++ b/crates/ra_assists/src/assists/add_import.rs @@ -1,18 +1,81 @@ -//! FIXME: write short doc here - use hir::{self, db::HirDatabase}; -use ra_text_edit::TextEditBuilder; - -use crate::{ - assist_ctx::{Assist, AssistCtx}, - AssistId, -}; use ra_syntax::{ ast::{self, NameOwner}, AstNode, Direction, SmolStr, SyntaxKind::{PATH, PATH_SEGMENT}, SyntaxNode, TextRange, T, }; +use ra_text_edit::TextEditBuilder; + +use crate::{ + assist_ctx::{Assist, AssistCtx}, + AssistId, +}; + +/// This function produces sequence of text edits into edit +/// to import the target path in the most appropriate scope given +/// the cursor position +pub fn auto_import_text_edit( + // Ideally the position of the cursor, used to + position: &SyntaxNode, + // The statement to use as anchor (last resort) + anchor: &SyntaxNode, + // The path to import as a sequence of strings + target: &[SmolStr], + edit: &mut TextEditBuilder, +) { + let container = position.ancestors().find_map(|n| { + if let Some(module) = ast::Module::cast(n.clone()) { + return module.item_list().map(|it| it.syntax().clone()); + } + ast::SourceFile::cast(n).map(|it| it.syntax().clone()) + }); + + if let Some(container) = container { + let action = best_action_for_target(container, anchor.clone(), target); + make_assist(&action, target, edit); + } +} + +// Assist: add_import +// +// Adds a use statement for a given fully-qualified path. +// +// ``` +// fn process(map: std::collections::<|>HashMap) {} +// ``` +// -> +// ``` +// use std::collections::HashMap; +// +// fn process(map: HashMap) {} +// ``` +pub(crate) fn add_import(ctx: AssistCtx) -> Option { + let path: ast::Path = ctx.find_node_at_offset()?; + // We don't want to mess with use statements + if path.syntax().ancestors().find_map(ast::UseItem::cast).is_some() { + return None; + } + + let hir_path = hir::Path::from_ast(path.clone())?; + let segments = collect_hir_path_segments(&hir_path)?; + if segments.len() < 2 { + return None; + } + + let module = path.syntax().ancestors().find_map(ast::Module::cast); + let position = match module.and_then(|it| it.item_list()) { + Some(item_list) => item_list.syntax().clone(), + None => { + let current_file = path.syntax().ancestors().find_map(ast::SourceFile::cast)?; + current_file.syntax().clone() + } + }; + + ctx.add_assist(AssistId("add_import"), format!("import {}", fmt_segments(&segments)), |edit| { + apply_auto_import(&position, &path, &segments, edit.text_edit_builder()); + }) +} fn collect_path_segments_raw( segments: &mut Vec, @@ -61,9 +124,9 @@ fn fmt_segments_raw(segments: &[SmolStr], buf: &mut String) { } } -// Returns the numeber of common segments. +/// Returns the number of common segments. fn compare_path_segments(left: &[SmolStr], right: &[ast::PathSegment]) -> usize { - left.iter().zip(right).filter(|(l, r)| compare_path_segment(l, r)).count() + left.iter().zip(right).take_while(|(l, r)| compare_path_segment(l, r)).count() } fn compare_path_segment(a: &SmolStr, b: &ast::PathSegment) -> bool { @@ -84,7 +147,7 @@ fn compare_path_segment_with_name(a: &SmolStr, b: &ast::Name) -> bool { a == b.text() } -#[derive(Clone)] +#[derive(Clone, Debug)] enum ImportAction { Nothing, // Add a brand new use statement. @@ -154,10 +217,18 @@ impl ImportAction { ( ImportAction::AddNestedImport { common_segments: n, .. }, ImportAction::AddInTreeList { common_segments: m, .. }, - ) => n > m, - ( + ) + | ( ImportAction::AddInTreeList { common_segments: n, .. }, ImportAction::AddNestedImport { common_segments: m, .. }, + ) + | ( + ImportAction::AddInTreeList { common_segments: n, .. }, + ImportAction::AddInTreeList { common_segments: m, .. }, + ) + | ( + ImportAction::AddNestedImport { common_segments: n, .. }, + ImportAction::AddNestedImport { common_segments: m, .. }, ) => n > m, (ImportAction::AddInTreeList { .. }, _) => true, (ImportAction::AddNestedImport { .. }, ImportAction::Nothing) => false, @@ -226,7 +297,7 @@ fn walk_use_tree_for_best_action( common if common == left.len() && left.len() == right.len() => { // e.g: target is std::fmt and we can have // 1- use std::fmt; - // 2- use std::fmt:{ ... } + // 2- use std::fmt::{ ... } if let Some(list) = tree_list { // In case 2 we need to add self to the nested list // unless it's already there @@ -474,7 +545,7 @@ fn make_assist_add_nested_import( if add_colon_colon { buf.push_str("::"); } - buf.push_str("{ "); + buf.push_str("{"); if add_self { buf.push_str("self, "); } @@ -505,7 +576,7 @@ fn apply_auto_import( } } -pub fn collect_hir_path_segments(path: &hir::Path) -> Option> { +fn collect_hir_path_segments(path: &hir::Path) -> Option> { let mut ps = Vec::::with_capacity(10); match path.kind { hir::PathKind::Abs => ps.push("".into()), @@ -521,87 +592,16 @@ pub fn collect_hir_path_segments(path: &hir::Path) -> Option> { Some(ps) } -// This function produces sequence of text edits into edit -// to import the target path in the most appropriate scope given -// the cursor position -pub fn auto_import_text_edit( - // Ideally the position of the cursor, used to - position: &SyntaxNode, - // The statement to use as anchor (last resort) - anchor: &SyntaxNode, - // The path to import as a sequence of strings - target: &[SmolStr], - edit: &mut TextEditBuilder, -) { - let container = position.ancestors().find_map(|n| { - if let Some(module) = ast::Module::cast(n.clone()) { - return module.item_list().map(|it| it.syntax().clone()); - } - ast::SourceFile::cast(n).map(|it| it.syntax().clone()) - }); - - if let Some(container) = container { - let action = best_action_for_target(container, anchor.clone(), target); - make_assist(&action, target, edit); - } -} - -pub(crate) fn auto_import(mut ctx: AssistCtx) -> Option { - let path: ast::Path = ctx.node_at_offset()?; - // We don't want to mess with use statements - if path.syntax().ancestors().find_map(ast::UseItem::cast).is_some() { - return None; - } - - let hir_path = hir::Path::from_ast(path.clone())?; - let segments = collect_hir_path_segments(&hir_path)?; - if segments.len() < 2 { - return None; - } - - if let Some(module) = path.syntax().ancestors().find_map(ast::Module::cast) { - if let (Some(item_list), Some(name)) = (module.item_list(), module.name()) { - ctx.add_action( - AssistId("auto_import"), - format!("import {} in mod {}", fmt_segments(&segments), name.text()), - |edit| { - apply_auto_import( - item_list.syntax(), - &path, - &segments, - edit.text_edit_builder(), - ); - }, - ); - } - } else { - let current_file = path.syntax().ancestors().find_map(ast::SourceFile::cast)?; - ctx.add_action( - AssistId("auto_import"), - format!("import {} in the current file", fmt_segments(&segments)), - |edit| { - apply_auto_import( - current_file.syntax(), - &path, - &segments, - edit.text_edit_builder(), - ); - }, - ); - } - - ctx.build() -} - #[cfg(test)] mod tests { - use super::*; use crate::helpers::{check_assist, check_assist_not_applicable}; + use super::*; + #[test] fn test_auto_import_add_use_no_anchor() { check_assist( - auto_import, + add_import, " std::fmt::Debug<|> ", @@ -615,7 +615,7 @@ Debug<|> #[test] fn test_auto_import_add_use_no_anchor_with_item_below() { check_assist( - auto_import, + add_import, " std::fmt::Debug<|> @@ -636,7 +636,7 @@ fn main() { #[test] fn test_auto_import_add_use_no_anchor_with_item_above() { check_assist( - auto_import, + add_import, " fn main() { } @@ -657,7 +657,7 @@ Debug<|> #[test] fn test_auto_import_add_use_no_anchor_2seg() { check_assist( - auto_import, + add_import, " std::fmt<|>::Debug ", @@ -672,7 +672,7 @@ fmt<|>::Debug #[test] fn test_auto_import_add_use() { check_assist( - auto_import, + add_import, " use stdx; @@ -692,7 +692,7 @@ impl Debug<|> for Foo { #[test] fn test_auto_import_file_use_other_anchor() { check_assist( - auto_import, + add_import, " impl std::fmt::Debug<|> for Foo { } @@ -709,7 +709,7 @@ impl Debug<|> for Foo { #[test] fn test_auto_import_add_use_other_anchor_indent() { check_assist( - auto_import, + add_import, " impl std::fmt::Debug<|> for Foo { } @@ -726,7 +726,7 @@ impl Debug<|> for Foo { #[test] fn test_auto_import_split_different() { check_assist( - auto_import, + add_import, " use std::fmt; @@ -734,7 +734,7 @@ impl std::io<|> for Foo { } ", " -use std::{ io, fmt}; +use std::{io, fmt}; impl io<|> for Foo { } @@ -745,7 +745,7 @@ impl io<|> for Foo { #[test] fn test_auto_import_split_self_for_use() { check_assist( - auto_import, + add_import, " use std::fmt; @@ -753,7 +753,7 @@ impl std::fmt::Debug<|> for Foo { } ", " -use std::fmt::{ self, Debug, }; +use std::fmt::{self, Debug, }; impl Debug<|> for Foo { } @@ -764,7 +764,7 @@ impl Debug<|> for Foo { #[test] fn test_auto_import_split_self_for_target() { check_assist( - auto_import, + add_import, " use std::fmt::Debug; @@ -772,7 +772,7 @@ impl std::fmt<|> for Foo { } ", " -use std::fmt::{ self, Debug}; +use std::fmt::{self, Debug}; impl fmt<|> for Foo { } @@ -783,7 +783,7 @@ impl fmt<|> for Foo { #[test] fn test_auto_import_add_to_nested_self_nested() { check_assist( - auto_import, + add_import, " use std::fmt::{Debug, nested::{Display}}; @@ -802,7 +802,7 @@ impl nested<|> for Foo { #[test] fn test_auto_import_add_to_nested_self_already_included() { check_assist( - auto_import, + add_import, " use std::fmt::{Debug, nested::{self, Display}}; @@ -821,7 +821,7 @@ impl nested<|> for Foo { #[test] fn test_auto_import_add_to_nested_nested() { check_assist( - auto_import, + add_import, " use std::fmt::{Debug, nested::{Display}}; @@ -840,7 +840,7 @@ impl Debug<|> for Foo { #[test] fn test_auto_import_split_common_target_longer() { check_assist( - auto_import, + add_import, " use std::fmt::Debug; @@ -848,7 +848,7 @@ impl std::fmt::nested::Display<|> for Foo { } ", " -use std::fmt::{ nested::Display, Debug}; +use std::fmt::{nested::Display, Debug}; impl Display<|> for Foo { } @@ -859,7 +859,7 @@ impl Display<|> for Foo { #[test] fn test_auto_import_split_common_use_longer() { check_assist( - auto_import, + add_import, " use std::fmt::nested::Debug; @@ -867,7 +867,7 @@ impl std::fmt::Display<|> for Foo { } ", " -use std::fmt::{ Display, nested::Debug}; +use std::fmt::{Display, nested::Debug}; impl Display<|> for Foo { } @@ -875,10 +875,33 @@ impl Display<|> for Foo { ); } + #[test] + fn test_auto_import_use_nested_import() { + check_assist( + add_import, + " +use crate::{ + ty::{Substs, Ty}, + AssocItem, +}; + +fn foo() { crate::ty::lower<|>::trait_env() } +", + " +use crate::{ + ty::{Substs, Ty, lower}, + AssocItem, +}; + +fn foo() { lower<|>::trait_env() } +", + ); + } + #[test] fn test_auto_import_alias() { check_assist( - auto_import, + add_import, " use std::fmt as foo; @@ -897,7 +920,7 @@ impl Debug<|> for Foo { #[test] fn test_auto_import_not_applicable_one_segment() { check_assist_not_applicable( - auto_import, + add_import, " impl foo<|> for Foo { } @@ -908,7 +931,7 @@ impl foo<|> for Foo { #[test] fn test_auto_import_not_applicable_in_use() { check_assist_not_applicable( - auto_import, + add_import, " use std::fmt<|>; ", @@ -918,7 +941,7 @@ use std::fmt<|>; #[test] fn test_auto_import_add_use_no_anchor_in_mod_mod() { check_assist( - auto_import, + add_import, " mod foo { mod bar { diff --git a/crates/ra_assists/src/assists/add_missing_impl_members.rs b/crates/ra_assists/src/assists/add_missing_impl_members.rs index 565b96fb509f..41de239219fb 100644 --- a/crates/ra_assists/src/assists/add_missing_impl_members.rs +++ b/crates/ra_assists/src/assists/add_missing_impl_members.rs @@ -1,5 +1,3 @@ -//! FIXME: write short doc here - use hir::{db::HirDatabase, HasSource}; use ra_syntax::{ ast::{self, edit, make, AstNode, NameOwner}, @@ -14,6 +12,34 @@ enum AddMissingImplMembersMode { NoDefaultMethods, } +// Assist: add_impl_missing_members +// +// Adds scaffold for required impl members. +// +// ``` +// trait T { +// Type X; +// fn foo(&self); +// fn bar(&self) {} +// } +// +// impl T for () {<|> +// +// } +// ``` +// -> +// ``` +// trait T { +// Type X; +// fn foo(&self); +// fn bar(&self) {} +// } +// +// impl T for () { +// fn foo(&self) { unimplemented!() } +// +// } +// ``` pub(crate) fn add_missing_impl_members(ctx: AssistCtx) -> Option { add_missing_impl_members_inner( ctx, @@ -23,6 +49,38 @@ pub(crate) fn add_missing_impl_members(ctx: AssistCtx) -> Opti ) } +// Assist: add_impl_default_members +// +// Adds scaffold for overriding default impl members. +// +// ``` +// trait T { +// Type X; +// fn foo(&self); +// fn bar(&self) {} +// } +// +// impl T for () { +// Type X = (); +// fn foo(&self) {}<|> +// +// } +// ``` +// -> +// ``` +// trait T { +// Type X; +// fn foo(&self); +// fn bar(&self) {} +// } +// +// impl T for () { +// Type X = (); +// fn foo(&self) {} +// fn bar(&self) {} +// +// } +// ``` pub(crate) fn add_missing_default_members(ctx: AssistCtx) -> Option { add_missing_impl_members_inner( ctx, @@ -33,12 +91,12 @@ pub(crate) fn add_missing_default_members(ctx: AssistCtx) -> O } fn add_missing_impl_members_inner( - mut ctx: AssistCtx, + ctx: AssistCtx, mode: AddMissingImplMembersMode, assist_id: &'static str, label: &'static str, ) -> Option { - let impl_node = ctx.node_at_offset::()?; + let impl_node = ctx.find_node_at_offset::()?; let impl_item_list = impl_node.item_list()?; let trait_def = { @@ -75,7 +133,7 @@ fn add_missing_impl_members_inner( return None; } - ctx.add_action(AssistId(assist_id), label, |edit| { + ctx.add_assist(AssistId(assist_id), label, |edit| { let n_existing_items = impl_item_list.impl_items().count(); let items = missing_items .into_iter() @@ -92,9 +150,7 @@ fn add_missing_impl_members_inner( edit.replace_ast(impl_item_list, new_impl_item_list); edit.set_cursor(cursor_position); - }); - - ctx.build() + }) } fn add_body(fn_def: ast::FnDef) -> ast::FnDef { diff --git a/crates/ra_assists/src/assists/apply_demorgan.rs b/crates/ra_assists/src/assists/apply_demorgan.rs index 5f2b0dd189b3..068da1774d98 100644 --- a/crates/ra_assists/src/assists/apply_demorgan.rs +++ b/crates/ra_assists/src/assists/apply_demorgan.rs @@ -1,20 +1,30 @@ -//! This contains the functions associated with the demorgan assist. -//! This assist transforms boolean expressions of the form `!a || !b` into -//! `!(a && b)`. use hir::db::HirDatabase; use ra_syntax::ast::{self, AstNode}; use ra_syntax::SyntaxNode; use crate::{Assist, AssistCtx, AssistId}; -/// Assist for applying demorgan's law -/// -/// This transforms expressions of the form `!l || !r` into `!(l && r)`. -/// This also works with `&&`. This assist can only be applied with the cursor -/// on either `||` or `&&`, with both operands being a negation of some kind. -/// This means something of the form `!x` or `x != y`. -pub(crate) fn apply_demorgan(mut ctx: AssistCtx) -> Option { - let expr = ctx.node_at_offset::()?; +// Assist: apply_demorgan +// +// Apply [De Morgan's law](https://en.wikipedia.org/wiki/De_Morgan%27s_laws). +// This transforms expressions of the form `!l || !r` into `!(l && r)`. +// This also works with `&&`. This assist can only be applied with the cursor +// on either `||` or `&&`, with both operands being a negation of some kind. +// This means something of the form `!x` or `x != y`. +// +// ``` +// fn main() { +// if x != 4 ||<|> !y {} +// } +// ``` +// -> +// ``` +// fn main() { +// if !(x == 4 && y) {} +// } +// ``` +pub(crate) fn apply_demorgan(ctx: AssistCtx) -> Option { + let expr = ctx.find_node_at_offset::()?; let op = expr.op_kind()?; let op_range = expr.op_token()?.text_range(); let opposite_op = opposite_logic_op(op)?; @@ -29,13 +39,12 @@ pub(crate) fn apply_demorgan(mut ctx: AssistCtx) -> Optionfn frobnicate() {} +// ``` +// -> +// ``` +// pub(crate) fn frobnicate() {} +// ``` pub(crate) fn change_visibility(ctx: AssistCtx) -> Option { - if let Some(vis) = ctx.node_at_offset::() { + if let Some(vis) = ctx.find_node_at_offset::() { return change_vis(ctx, vis); } add_vis(ctx) } -fn add_vis(mut ctx: AssistCtx) -> Option { +fn add_vis(ctx: AssistCtx) -> Option { let item_keyword = ctx.token_at_offset().find(|leaf| match leaf.kind() { T![fn] | T![mod] | T![struct] | T![enum] | T![trait] => true, _ => false, @@ -48,13 +57,11 @@ fn add_vis(mut ctx: AssistCtx) -> Option { (vis_offset(field.syntax()), ident.text_range()) }; - ctx.add_action(AssistId("change_visibility"), "make pub(crate)", |edit| { + ctx.add_assist(AssistId("change_visibility"), "make pub(crate)", |edit| { edit.target(target); edit.insert(offset, "pub(crate) "); edit.set_cursor(offset); - }); - - ctx.build() + }) } fn vis_offset(node: &SyntaxNode) -> TextUnit { @@ -68,24 +75,20 @@ fn vis_offset(node: &SyntaxNode) -> TextUnit { .unwrap_or_else(|| node.text_range().start()) } -fn change_vis(mut ctx: AssistCtx, vis: ast::Visibility) -> Option { +fn change_vis(ctx: AssistCtx, vis: ast::Visibility) -> Option { if vis.syntax().text() == "pub" { - ctx.add_action(AssistId("change_visibility"), "change to pub(crate)", |edit| { + return ctx.add_assist(AssistId("change_visibility"), "change to pub(crate)", |edit| { edit.target(vis.syntax().text_range()); edit.replace(vis.syntax().text_range(), "pub(crate)"); edit.set_cursor(vis.syntax().text_range().start()) }); - - return ctx.build(); } if vis.syntax().text() == "pub(crate)" { - ctx.add_action(AssistId("change_visibility"), "change to pub", |edit| { + return ctx.add_assist(AssistId("change_visibility"), "change to pub", |edit| { edit.target(vis.syntax().text_range()); edit.replace(vis.syntax().text_range(), "pub"); edit.set_cursor(vis.syntax().text_range().start()); }); - - return ctx.build(); } None } diff --git a/crates/ra_assists/src/assists/early_return.rs b/crates/ra_assists/src/assists/early_return.rs index f7d7e12e73c9..ad6c5695a3a4 100644 --- a/crates/ra_assists/src/assists/early_return.rs +++ b/crates/ra_assists/src/assists/early_return.rs @@ -1,26 +1,3 @@ -//! Assist: `convert_to_guarded_return` -//! -//! Replace a large conditional with a guarded return. -//! -//! ```text -//! fn <|>main() { -//! if cond { -//! foo(); -//! bar(); -//! } -//! } -//! ``` -//! -> -//! ```text -//! fn main() { -//! if !cond { -//! return; -//! } -//! foo(); -//! bar(); -//! } -//! ``` - use std::ops::RangeInclusive; use hir::db::HirDatabase; @@ -36,8 +13,30 @@ use crate::{ AssistId, }; -pub(crate) fn convert_to_guarded_return(mut ctx: AssistCtx) -> Option { - let if_expr: ast::IfExpr = ctx.node_at_offset()?; +// Assist: convert_to_guarded_return +// +// Replace a large conditional with a guarded return. +// +// ``` +// fn main() { +// <|>if cond { +// foo(); +// bar(); +// } +// } +// ``` +// -> +// ``` +// fn main() { +// if !cond { +// return; +// } +// foo(); +// bar(); +// } +// ``` +pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option { + let if_expr: ast::IfExpr = ctx.find_node_at_offset()?; let expr = if_expr.condition()?.expr()?; let then_block = if_expr.then_branch()?.block()?; if if_expr.else_branch().is_some() { @@ -51,7 +50,7 @@ pub(crate) fn convert_to_guarded_return(mut ctx: AssistCtx) -> } // check for early return and continue - let first_in_then_block = then_block.syntax().first_child()?.clone(); + let first_in_then_block = then_block.syntax().first_child()?; if ast::ReturnExpr::can_cast(first_in_then_block.kind()) || ast::ContinueExpr::can_cast(first_in_then_block.kind()) || first_in_then_block @@ -76,7 +75,7 @@ pub(crate) fn convert_to_guarded_return(mut ctx: AssistCtx) -> then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?; let cursor_position = ctx.frange.range.start(); - ctx.add_action(AssistId("convert_to_guarded_return"), "convert to guarded return", |edit| { + ctx.add_assist(AssistId("convert_to_guarded_return"), "convert to guarded return", |edit| { let if_indent_level = IndentLevel::from_node(&if_expr.syntax()); let new_if_expr = if_indent_level.increase_indent(make::if_expression(&expr, early_expression)); @@ -106,8 +105,7 @@ pub(crate) fn convert_to_guarded_return(mut ctx: AssistCtx) -> edit.target(if_expr.syntax().text_range()); edit.replace_ast(parent_block, ast::Block::cast(new_block).unwrap()); edit.set_cursor(cursor_position); - }); - ctx.build() + }) } #[cfg(test)] diff --git a/crates/ra_assists/src/assists/fill_match_arms.rs b/crates/ra_assists/src/assists/fill_match_arms.rs index e3f30b5defce..2b74f355c6ab 100644 --- a/crates/ra_assists/src/assists/fill_match_arms.rs +++ b/crates/ra_assists/src/assists/fill_match_arms.rs @@ -7,8 +7,32 @@ use ra_syntax::ast::{self, edit::IndentLevel, make, AstNode, NameOwner}; use crate::{Assist, AssistCtx, AssistId}; -pub(crate) fn fill_match_arms(mut ctx: AssistCtx) -> Option { - let match_expr = ctx.node_at_offset::()?; +// Assist: fill_match_arms +// +// Adds missing clauses to a `match` expression. +// +// ``` +// enum Action { Move { distance: u32 }, Stop } +// +// fn handle(action: Action) { +// match action { +// <|> +// } +// } +// ``` +// -> +// ``` +// enum Action { Move { distance: u32 }, Stop } +// +// fn handle(action: Action) { +// match action { +// Action::Move { distance } => (), +// Action::Stop => (), +// } +// } +// ``` +pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option { + let match_expr = ctx.find_node_at_offset::()?; let match_arm_list = match_expr.match_arm_list()?; // We already have some match arms, so we don't provide any assists. @@ -29,7 +53,7 @@ pub(crate) fn fill_match_arms(mut ctx: AssistCtx) -> Option) -> Option bool { @@ -130,7 +152,7 @@ mod tests { A::Bs => (), A::Cs(_) => (), A::Ds(_, _) => (), - A::Es{ x, y } => (), + A::Es { x, y } => (), } } "#, @@ -183,7 +205,7 @@ mod tests { fn foo(a: &mut A) { match <|>a { - A::Es{ x, y } => (), + A::Es { x, y } => (), } } "#, diff --git a/crates/ra_assists/src/assists/flip_binexpr.rs b/crates/ra_assists/src/assists/flip_binexpr.rs index c51035282309..386045eb0a05 100644 --- a/crates/ra_assists/src/assists/flip_binexpr.rs +++ b/crates/ra_assists/src/assists/flip_binexpr.rs @@ -1,13 +1,25 @@ -//! FIXME: write short doc here - use hir::db::HirDatabase; use ra_syntax::ast::{AstNode, BinExpr, BinOp}; use crate::{Assist, AssistCtx, AssistId}; -/// Flip binary expression assist. -pub(crate) fn flip_binexpr(mut ctx: AssistCtx) -> Option { - let expr = ctx.node_at_offset::()?; +// Assist: flip_binexpr +// +// Flips operands of a binary expression. +// +// ``` +// fn main() { +// let _ = 90 +<|> 2; +// } +// ``` +// -> +// ``` +// fn main() { +// let _ = 2 + 90; +// } +// ``` +pub(crate) fn flip_binexpr(ctx: AssistCtx) -> Option { + let expr = ctx.find_node_at_offset::()?; let lhs = expr.lhs()?.syntax().clone(); let rhs = expr.rhs()?.syntax().clone(); let op_range = expr.op_token()?.text_range(); @@ -22,16 +34,14 @@ pub(crate) fn flip_binexpr(mut ctx: AssistCtx) -> Option) -> Option { - let comma = ctx.token_at_offset().find(|leaf| leaf.kind() == T![,])?; +// Assist: flip_comma +// +// Flips two comma-separated items. +// +// ``` +// fn main() { +// ((1, 2),<|> (3, 4)); +// } +// ``` +// -> +// ``` +// fn main() { +// ((3, 4), (1, 2)); +// } +// ``` +pub(crate) fn flip_comma(ctx: AssistCtx) -> Option { + let comma = ctx.find_token_at_offset(T![,])?; let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?; let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?; @@ -16,13 +29,11 @@ pub(crate) fn flip_comma(mut ctx: AssistCtx) -> Option return None; } - ctx.add_action(AssistId("flip_comma"), "flip comma", |edit| { + ctx.add_assist(AssistId("flip_comma"), "flip comma", |edit| { edit.target(comma.text_range()); edit.replace(prev.text_range(), next.to_string()); edit.replace(next.text_range(), prev.to_string()); - }); - - ctx.build() + }) } #[cfg(test)] diff --git a/crates/ra_assists/src/assists/flip_trait_bound.rs b/crates/ra_assists/src/assists/flip_trait_bound.rs new file mode 100644 index 000000000000..6017b39dd84b --- /dev/null +++ b/crates/ra_assists/src/assists/flip_trait_bound.rs @@ -0,0 +1,117 @@ +use hir::db::HirDatabase; +use ra_syntax::{ + algo::non_trivia_sibling, + ast::{self, AstNode}, + Direction, T, +}; + +use crate::{Assist, AssistCtx, AssistId}; + +// Assist: flip_trait_bound +// +// Flips two trait bounds. +// +// ``` +// fn foo Copy>() { } +// ``` +// -> +// ``` +// fn foo() { } +// ``` +pub(crate) fn flip_trait_bound(ctx: AssistCtx) -> Option { + // We want to replicate the behavior of `flip_binexpr` by only suggesting + // the assist when the cursor is on a `+` + let plus = ctx.find_token_at_offset(T![+])?; + + // Make sure we're in a `TypeBoundList` + if ast::TypeBoundList::cast(plus.parent()).is_none() { + return None; + } + + let (before, after) = ( + non_trivia_sibling(plus.clone().into(), Direction::Prev)?, + non_trivia_sibling(plus.clone().into(), Direction::Next)?, + ); + + ctx.add_assist(AssistId("flip_trait_bound"), "flip trait bound", |edit| { + edit.target(plus.text_range()); + edit.replace(before.text_range(), after.to_string()); + edit.replace(after.text_range(), before.to_string()); + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; + + #[test] + fn flip_trait_bound_assist_available() { + check_assist_target(flip_trait_bound, "struct S where T: A <|>+ B + C { }", "+") + } + + #[test] + fn flip_trait_bound_not_applicable_for_single_trait_bound() { + check_assist_not_applicable(flip_trait_bound, "struct S where T: <|>A { }") + } + + #[test] + fn flip_trait_bound_works_for_struct() { + check_assist( + flip_trait_bound, + "struct S where T: A <|>+ B { }", + "struct S where T: B <|>+ A { }", + ) + } + + #[test] + fn flip_trait_bound_works_for_trait_impl() { + check_assist( + flip_trait_bound, + "impl X for S where T: A +<|> B { }", + "impl X for S where T: B +<|> A { }", + ) + } + + #[test] + fn flip_trait_bound_works_for_fn() { + check_assist(flip_trait_bound, "fn f+ B>(t: T) { }", "fn f+ A>(t: T) { }") + } + + #[test] + fn flip_trait_bound_works_for_fn_where_clause() { + check_assist( + flip_trait_bound, + "fn f(t: T) where T: A +<|> B { }", + "fn f(t: T) where T: B +<|> A { }", + ) + } + + #[test] + fn flip_trait_bound_works_for_lifetime() { + check_assist( + flip_trait_bound, + "fn f(t: T) where T: A <|>+ 'static { }", + "fn f(t: T) where T: 'static <|>+ A { }", + ) + } + + #[test] + fn flip_trait_bound_works_for_complex_bounds() { + check_assist( + flip_trait_bound, + "struct S where T: A <|>+ b_mod::B + C { }", + "struct S where T: b_mod::B <|>+ A + C { }", + ) + } + + #[test] + fn flip_trait_bound_works_for_long_bounds() { + check_assist( + flip_trait_bound, + "struct S where T: A + B + C + D + E + F +<|> G + H + I + J { }", + "struct S where T: A + B + C + D + E + G +<|> F + H + I + J { }", + ) + } +} diff --git a/crates/ra_assists/src/assists/inline_local_variable.rs b/crates/ra_assists/src/assists/inline_local_variable.rs index 9bd64decc9db..a7fd9b6d2d6e 100644 --- a/crates/ra_assists/src/assists/inline_local_variable.rs +++ b/crates/ra_assists/src/assists/inline_local_variable.rs @@ -1,5 +1,3 @@ -//! FIXME: write short doc here - use hir::db::HirDatabase; use ra_syntax::{ ast::{self, AstNode, AstToken}, @@ -9,8 +7,24 @@ use ra_syntax::{ use crate::assist_ctx::AssistBuilder; use crate::{Assist, AssistCtx, AssistId}; -pub(crate) fn inline_local_varialbe(mut ctx: AssistCtx) -> Option { - let let_stmt = ctx.node_at_offset::()?; +// Assist: inline_local_variable +// +// Inlines local variable. +// +// ``` +// fn main() { +// let x<|> = 1 + 2; +// x * 4; +// } +// ``` +// -> +// ``` +// fn main() { +// (1 + 2) * 4; +// } +// ``` +pub(crate) fn inline_local_varialbe(ctx: AssistCtx) -> Option { + let let_stmt = ctx.find_node_at_offset::()?; let bind_pat = match let_stmt.pat()? { ast::Pat::BindPat(pat) => pat, _ => return None, @@ -37,10 +51,8 @@ pub(crate) fn inline_local_varialbe(mut ctx: AssistCtx) -> Opt let mut wrap_in_parens = vec![true; refs.len()]; for (i, desc) in refs.iter().enumerate() { - let usage_node = ctx - .covering_node_for_range(desc.range) - .ancestors() - .find_map(|node| ast::PathExpr::cast(node))?; + let usage_node = + ctx.covering_node_for_range(desc.range).ancestors().find_map(ast::PathExpr::cast)?; let usage_parent_option = usage_node.syntax().parent().and_then(ast::Expr::cast); let usage_parent = match usage_parent_option { Some(u) => u, @@ -79,7 +91,7 @@ pub(crate) fn inline_local_varialbe(mut ctx: AssistCtx) -> Opt let init_str = initializer_expr.syntax().text().to_string(); let init_in_paren = format!("({})", &init_str); - ctx.add_action( + ctx.add_assist( AssistId("inline_local_variable"), "inline local variable", move |edit: &mut AssistBuilder| { @@ -93,9 +105,7 @@ pub(crate) fn inline_local_varialbe(mut ctx: AssistCtx) -> Opt } edit.set_cursor(delete_range.start()) }, - ); - - ctx.build() + ) } #[cfg(test)] diff --git a/crates/ra_assists/src/assists/introduce_variable.rs b/crates/ra_assists/src/assists/introduce_variable.rs index 43378c4b05ea..0623d44759e5 100644 --- a/crates/ra_assists/src/assists/introduce_variable.rs +++ b/crates/ra_assists/src/assists/introduce_variable.rs @@ -1,5 +1,3 @@ -//! FIXME: write short doc here - use format_buf::format; use hir::db::HirDatabase; use ra_syntax::{ @@ -14,7 +12,23 @@ use test_utils::tested_by; use crate::{Assist, AssistCtx, AssistId}; -pub(crate) fn introduce_variable(mut ctx: AssistCtx) -> Option { +// Assist: introduce_variable +// +// Extracts subexpression into a variable. +// +// ``` +// fn main() { +// <|>(1 + 2)<|> * 4; +// } +// ``` +// -> +// ``` +// fn main() { +// let var_name = (1 + 2); +// var_name * 4; +// } +// ``` +pub(crate) fn introduce_variable(ctx: AssistCtx) -> Option { if ctx.frange.range.is_empty() { return None; } @@ -29,7 +43,7 @@ pub(crate) fn introduce_variable(mut ctx: AssistCtx) -> Option if indent.kind() != WHITESPACE { return None; } - ctx.add_action(AssistId("introduce_variable"), "introduce variable", move |edit| { + ctx.add_assist(AssistId("introduce_variable"), "introduce variable", move |edit| { let mut buf = String::new(); let cursor_offset = if wrap_in_block { @@ -74,9 +88,7 @@ pub(crate) fn introduce_variable(mut ctx: AssistCtx) -> Option } } edit.set_cursor(anchor_stmt.text_range().start() + cursor_offset); - }); - - ctx.build() + }) } /// Check whether the node is a valid expression which can be extracted to a variable. diff --git a/crates/ra_assists/src/assists/merge_match_arms.rs b/crates/ra_assists/src/assists/merge_match_arms.rs index 17baa98f9272..e9f2cae91d35 100644 --- a/crates/ra_assists/src/assists/merge_match_arms.rs +++ b/crates/ra_assists/src/assists/merge_match_arms.rs @@ -1,11 +1,33 @@ -//! FIXME: write short doc here - use crate::{Assist, AssistCtx, AssistId, TextRange, TextUnit}; use hir::db::HirDatabase; use ra_syntax::ast::{AstNode, MatchArm}; -pub(crate) fn merge_match_arms(mut ctx: AssistCtx) -> Option { - let current_arm = ctx.node_at_offset::()?; +// Assist: merge_match_arms +// +// Merges identical match arms. +// +// ``` +// enum Action { Move { distance: u32 }, Stop } +// +// fn handle(action: Action) { +// match action { +// <|>Action::Move(..) => foo(), +// Action::Stop => foo(), +// } +// } +// ``` +// -> +// ``` +// enum Action { Move { distance: u32 }, Stop } +// +// fn handle(action: Action) { +// match action { +// Action::Move(..) | Action::Stop => foo(), +// } +// } +// ``` +pub(crate) fn merge_match_arms(ctx: AssistCtx) -> Option { + let current_arm = ctx.find_node_at_offset::()?; // We check if the following match arm matches this one. We could, but don't, // compare to the previous match arm as well. @@ -30,7 +52,7 @@ pub(crate) fn merge_match_arms(mut ctx: AssistCtx) -> Option bool { a.pats().any(|x| match x { ra_syntax::ast::Pat::PlaceholderPat(..) => true, @@ -58,9 +80,7 @@ pub(crate) fn merge_match_arms(mut ctx: AssistCtx) -> Option) -> Option { - let type_param_list = ctx.node_at_offset::()?; +// Assist: move_bounds_to_where_clause +// +// Moves inline type bounds to a where clause. +// +// ``` +// fn applyF: FnOnce(T) -> U>(f: F, x: T) -> U { +// f(x) +// } +// ``` +// -> +// ``` +// fn apply(f: F, x: T) -> U where F: FnOnce(T) -> U { +// f(x) +// } +// ``` +pub(crate) fn move_bounds_to_where_clause(ctx: AssistCtx) -> Option { + let type_param_list = ctx.find_node_at_offset::()?; let mut type_params = type_param_list.type_params(); if type_params.all(|p| p.type_bound_list().is_none()) { @@ -33,38 +46,30 @@ pub(crate) fn move_bounds_to_where_clause(mut ctx: AssistCtx) _ => return None, }; - ctx.add_action( - AssistId("move_bounds_to_where_clause"), - "move_bounds_to_where_clause", - |edit| { - let new_params = type_param_list - .type_params() - .filter(|it| it.type_bound_list().is_some()) - .map(|type_param| { - let without_bounds = type_param.remove_bounds(); - (type_param, without_bounds) - }); + ctx.add_assist(AssistId("move_bounds_to_where_clause"), "move_bounds_to_where_clause", |edit| { + let new_params = type_param_list + .type_params() + .filter(|it| it.type_bound_list().is_some()) + .map(|type_param| { + let without_bounds = type_param.remove_bounds(); + (type_param, without_bounds) + }); - let new_type_param_list = edit::replace_descendants(&type_param_list, new_params); - edit.replace_ast(type_param_list.clone(), new_type_param_list); + let new_type_param_list = edit::replace_descendants(&type_param_list, new_params); + edit.replace_ast(type_param_list.clone(), new_type_param_list); - let where_clause = { - let predicates = type_param_list.type_params().filter_map(build_predicate); - make::where_clause(predicates) - }; + let where_clause = { + let predicates = type_param_list.type_params().filter_map(build_predicate); + make::where_clause(predicates) + }; - let to_insert = match anchor.prev_sibling_or_token() { - Some(ref elem) if elem.kind() == WHITESPACE => { - format!("{} ", where_clause.syntax()) - } - _ => format!(" {}", where_clause.syntax()), - }; - edit.insert(anchor.text_range().start(), to_insert); - edit.target(type_param_list.syntax().text_range()); - }, - ); - - ctx.build() + let to_insert = match anchor.prev_sibling_or_token() { + Some(ref elem) if elem.kind() == WHITESPACE => format!("{} ", where_clause.syntax()), + _ => format!(" {}", where_clause.syntax()), + }; + edit.insert(anchor.text_range().start(), to_insert); + edit.target(type_param_list.syntax().text_range()); + }) } fn build_predicate(param: ast::TypeParam) -> Option { diff --git a/crates/ra_assists/src/assists/move_guard.rs b/crates/ra_assists/src/assists/move_guard.rs index 51aea6334080..b49ec6172ac3 100644 --- a/crates/ra_assists/src/assists/move_guard.rs +++ b/crates/ra_assists/src/assists/move_guard.rs @@ -1,5 +1,3 @@ -//! FIXME: write short doc here - use hir::db::HirDatabase; use ra_syntax::{ ast, @@ -9,8 +7,33 @@ use ra_syntax::{ use crate::{Assist, AssistCtx, AssistId}; -pub(crate) fn move_guard_to_arm_body(mut ctx: AssistCtx) -> Option { - let match_arm = ctx.node_at_offset::()?; +// Assist: move_guard_to_arm_body +// +// Moves match guard into match arm body. +// +// ``` +// enum Action { Move { distance: u32 }, Stop } +// +// fn handle(action: Action) { +// match action { +// Action::Move { distance } <|>if distance > 10 => foo(), +// _ => (), +// } +// } +// ``` +// -> +// ``` +// enum Action { Move { distance: u32 }, Stop } +// +// fn handle(action: Action) { +// match action { +// Action::Move { distance } => if distance > 10 { foo() }, +// _ => (), +// } +// } +// ``` +pub(crate) fn move_guard_to_arm_body(ctx: AssistCtx) -> Option { + let match_arm = ctx.find_node_at_offset::()?; let guard = match_arm.guard()?; let space_before_guard = guard.syntax().prev_sibling_or_token(); @@ -18,7 +41,7 @@ pub(crate) fn move_guard_to_arm_body(mut ctx: AssistCtx) -> Op let arm_expr = match_arm.expr()?; let buf = format!("if {} {{ {} }}", guard_conditions.syntax().text(), arm_expr.syntax().text()); - ctx.add_action(AssistId("move_guard_to_arm_body"), "move guard to arm body", |edit| { + ctx.add_assist(AssistId("move_guard_to_arm_body"), "move guard to arm body", |edit| { edit.target(guard.syntax().text_range()); let offseting_amount = match space_before_guard.and_then(|it| it.into_token()) { Some(tok) => { @@ -38,12 +61,36 @@ pub(crate) fn move_guard_to_arm_body(mut ctx: AssistCtx) -> Op edit.set_cursor( arm_expr.syntax().text_range().start() + TextUnit::from(3) - offseting_amount, ); - }); - ctx.build() + }) } -pub(crate) fn move_arm_cond_to_match_guard(mut ctx: AssistCtx) -> Option { - let match_arm: MatchArm = ctx.node_at_offset::()?; +// Assist: move_arm_cond_to_match_guard +// +// Moves if expression from match arm body into a guard. +// +// ``` +// enum Action { Move { distance: u32 }, Stop } +// +// fn handle(action: Action) { +// match action { +// Action::Move { distance } => <|>if distance > 10 { foo() }, +// _ => (), +// } +// } +// ``` +// -> +// ``` +// enum Action { Move { distance: u32 }, Stop } +// +// fn handle(action: Action) { +// match action { +// Action::Move { distance } if distance > 10 => foo(), +// _ => (), +// } +// } +// ``` +pub(crate) fn move_arm_cond_to_match_guard(ctx: AssistCtx) -> Option { + let match_arm: MatchArm = ctx.find_node_at_offset::()?; let last_match_pat = match_arm.pats().last()?; let arm_body = match_arm.expr()?; @@ -62,7 +109,7 @@ pub(crate) fn move_arm_cond_to_match_guard(mut ctx: AssistCtx) let buf = format!(" if {}", cond.syntax().text()); - ctx.add_action( + ctx.add_assist( AssistId("move_arm_cond_to_match_guard"), "move condition to match guard", |edit| { @@ -79,8 +126,7 @@ pub(crate) fn move_arm_cond_to_match_guard(mut ctx: AssistCtx) edit.insert(last_match_pat.syntax().text_range().end(), buf); edit.set_cursor(last_match_pat.syntax().text_range().end() + TextUnit::from(1)); }, - ); - ctx.build() + ) } #[cfg(test)] diff --git a/crates/ra_assists/src/assists/raw_string.rs b/crates/ra_assists/src/assists/raw_string.rs index 2d2e31e5134d..58f7157ae21c 100644 --- a/crates/ra_assists/src/assists/raw_string.rs +++ b/crates/ra_assists/src/assists/raw_string.rs @@ -1,17 +1,29 @@ -//! FIXME: write short doc here - use hir::db::HirDatabase; -use ra_syntax::{ast::AstNode, ast::Literal, TextRange, TextUnit}; +use ra_syntax::{ + SyntaxKind::{RAW_STRING, STRING}, + TextRange, TextUnit, +}; use rustc_lexer; use crate::{Assist, AssistCtx, AssistId}; -pub(crate) fn make_raw_string(mut ctx: AssistCtx) -> Option { - let literal = ctx.node_at_offset::()?; - if literal.token().kind() != ra_syntax::SyntaxKind::STRING { - return None; - } - let token = literal.token(); +// Assist: make_raw_string +// +// Adds `r#` to a plain string literal. +// +// ``` +// fn main() { +// "Hello,<|> World!"; +// } +// ``` +// -> +// ``` +// fn main() { +// r#"Hello, World!"#; +// } +// ``` +pub(crate) fn make_raw_string(ctx: AssistCtx) -> Option { + let token = ctx.find_token_at_offset(STRING)?; let text = token.text().as_str(); let usual_string_range = find_usual_string_range(text)?; let start_of_inside = usual_string_range.start().to_usize() + 1; @@ -29,19 +41,105 @@ pub(crate) fn make_raw_string(mut ctx: AssistCtx) -> Option "World!""#; +// } +// ``` +// -> +// ``` +// fn main() { +// "Hello, \"World!\""; +// } +// ``` +pub(crate) fn make_usual_string(ctx: AssistCtx) -> Option { + let token = ctx.find_token_at_offset(RAW_STRING)?; + let text = token.text().as_str(); + let usual_string_range = find_usual_string_range(text)?; + ctx.add_assist(AssistId("make_usual_string"), "make usual string", |edit| { + edit.target(token.text_range()); + // parse inside string to escape `"` + let start_of_inside = usual_string_range.start().to_usize() + 1; + let end_of_inside = usual_string_range.end().to_usize(); + let inside_str = &text[start_of_inside..end_of_inside]; + let escaped = inside_str.escape_default().to_string(); + edit.replace(token.text_range(), format!("\"{}\"", escaped)); + }) +} + +// Assist: add_hash +// +// Adds a hash to a raw string literal. +// +// ``` +// fn main() { +// r#"Hello,<|> World!"#; +// } +// ``` +// -> +// ``` +// fn main() { +// r##"Hello, World!"##; +// } +// ``` +pub(crate) fn add_hash(ctx: AssistCtx) -> Option { + let token = ctx.find_token_at_offset(RAW_STRING)?; + ctx.add_assist(AssistId("add_hash"), "add hash to raw string", |edit| { + edit.target(token.text_range()); + edit.insert(token.text_range().start() + TextUnit::of_char('r'), "#"); + edit.insert(token.text_range().end(), "#"); + }) +} + +// Assist: remove_hash +// +// Removes a hash from a raw string literal. +// +// ``` +// fn main() { +// r#"Hello,<|> World!"#; +// } +// ``` +// -> +// ``` +// fn main() { +// r"Hello, World!"; +// } +// ``` +pub(crate) fn remove_hash(ctx: AssistCtx) -> Option { + let token = ctx.find_token_at_offset(RAW_STRING)?; + let text = token.text().as_str(); + if text.starts_with("r\"") { + // no hash to remove + return None; + } + ctx.add_assist(AssistId("remove_hash"), "remove hash from raw string", |edit| { + edit.target(token.text_range()); + let result = &text[2..text.len() - 1]; + let result = if result.starts_with('\"') { + // no more hash, escape + let internal_str = &result[1..result.len() - 1]; + format!("\"{}\"", internal_str.escape_default().to_string()) + } else { + result.to_owned() + }; + edit.replace(token.text_range(), format!("r{}", result)); + }) } fn count_hashes(s: &str) -> usize { @@ -57,69 +155,17 @@ fn count_hashes(s: &str) -> usize { } fn find_usual_string_range(s: &str) -> Option { - Some(TextRange::from_to( - TextUnit::from(s.find('"')? as u32), - TextUnit::from(s.rfind('"')? as u32), - )) -} - -pub(crate) fn make_usual_string(mut ctx: AssistCtx) -> Option { - let literal = ctx.node_at_offset::()?; - if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING { - return None; + let left_quote = s.find('"')?; + let right_quote = s.rfind('"')?; + if left_quote == right_quote { + // `s` only contains one quote + None + } else { + Some(TextRange::from_to( + TextUnit::from(left_quote as u32), + TextUnit::from(right_quote as u32), + )) } - let token = literal.token(); - let text = token.text().as_str(); - let usual_string_range = find_usual_string_range(text)?; - ctx.add_action(AssistId("make_usual_string"), "make usual string", |edit| { - edit.target(literal.syntax().text_range()); - // parse inside string to escape `"` - let start_of_inside = usual_string_range.start().to_usize() + 1; - let end_of_inside = usual_string_range.end().to_usize(); - let inside_str = &text[start_of_inside..end_of_inside]; - let escaped = inside_str.escape_default().to_string(); - edit.replace(literal.syntax().text_range(), format!("\"{}\"", escaped)); - }); - ctx.build() -} - -pub(crate) fn add_hash(mut ctx: AssistCtx) -> Option { - let literal = ctx.node_at_offset::()?; - if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING { - return None; - } - ctx.add_action(AssistId("add_hash"), "add hash to raw string", |edit| { - edit.target(literal.syntax().text_range()); - edit.insert(literal.syntax().text_range().start() + TextUnit::of_char('r'), "#"); - edit.insert(literal.syntax().text_range().end(), "#"); - }); - ctx.build() -} - -pub(crate) fn remove_hash(mut ctx: AssistCtx) -> Option { - let literal = ctx.node_at_offset::()?; - if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING { - return None; - } - let token = literal.token(); - let text = token.text().as_str(); - if text.starts_with("r\"") { - // no hash to remove - return None; - } - ctx.add_action(AssistId("remove_hash"), "remove hash from raw string", |edit| { - edit.target(literal.syntax().text_range()); - let result = &text[2..text.len() - 1]; - let result = if result.starts_with("\"") { - // no more hash, escape - let internal_str = &result[1..result.len() - 1]; - format!("\"{}\"", internal_str.escape_default().to_string()) - } else { - result.to_owned() - }; - edit.replace(literal.syntax().text_range(), format!("r{}", result)); - }); - ctx.build() } #[cfg(test)] @@ -158,6 +204,23 @@ string"#; ) } + #[test] + fn make_raw_string_works_inside_macros() { + check_assist( + make_raw_string, + r#" + fn f() { + format!(<|>"x = {}", 92) + } + "#, + r##" + fn f() { + format!(<|>r#"x = {}"#, 92) + } + "##, + ) + } + #[test] fn make_raw_string_hashes_inside_works() { check_assist( @@ -211,6 +274,30 @@ string"###; ) } + #[test] + fn make_raw_string_not_works_on_partial_string() { + check_assist_not_applicable( + make_raw_string, + r#" + fn f() { + let s = "foo<|> + } + "#, + ) + } + + #[test] + fn make_usual_string_not_works_on_partial_string() { + check_assist_not_applicable( + make_usual_string, + r#" + fn main() { + let s = r#"bar<|> + } + "#, + ) + } + #[test] fn add_hash_target() { check_assist_target( diff --git a/crates/ra_assists/src/assists/remove_dbg.rs b/crates/ra_assists/src/assists/remove_dbg.rs index 1a7e2b3050cb..aedf8747f22a 100644 --- a/crates/ra_assists/src/assists/remove_dbg.rs +++ b/crates/ra_assists/src/assists/remove_dbg.rs @@ -1,14 +1,28 @@ -//! FIXME: write short doc here - -use crate::{Assist, AssistCtx, AssistId}; use hir::db::HirDatabase; use ra_syntax::{ ast::{self, AstNode}, TextUnit, T, }; -pub(crate) fn remove_dbg(mut ctx: AssistCtx) -> Option { - let macro_call = ctx.node_at_offset::()?; +use crate::{Assist, AssistCtx, AssistId}; + +// Assist: remove_dbg +// +// Removes `dbg!()` macro call. +// +// ``` +// fn main() { +// <|>dbg!(92); +// } +// ``` +// -> +// ``` +// fn main() { +// 92; +// } +// ``` +pub(crate) fn remove_dbg(ctx: AssistCtx) -> Option { + let macro_call = ctx.find_node_at_offset::()?; if !is_valid_macrocall(¯o_call, "dbg")? { return None; @@ -44,13 +58,11 @@ pub(crate) fn remove_dbg(mut ctx: AssistCtx) -> Option text.slice(without_parens).to_string() }; - ctx.add_action(AssistId("remove_dbg"), "remove dbg!()", |edit| { + ctx.add_assist(AssistId("remove_dbg"), "remove dbg!()", |edit| { edit.target(macro_call.syntax().text_range()); edit.replace(macro_range, macro_content); edit.set_cursor(cursor_pos); - }); - - ctx.build() + }) } /// Verifies that the given macro_call actually matches the given name diff --git a/crates/ra_assists/src/assists/replace_if_let_with_match.rs b/crates/ra_assists/src/assists/replace_if_let_with_match.rs index 749ff338aa6d..dff84d86516c 100644 --- a/crates/ra_assists/src/assists/replace_if_let_with_match.rs +++ b/crates/ra_assists/src/assists/replace_if_let_with_match.rs @@ -1,5 +1,3 @@ -//! FIXME: write short doc here - use format_buf::format; use hir::db::HirDatabase; use ra_fmt::extract_trivial_expression; @@ -7,8 +5,34 @@ use ra_syntax::{ast, AstNode}; use crate::{Assist, AssistCtx, AssistId}; -pub(crate) fn replace_if_let_with_match(mut ctx: AssistCtx) -> Option { - let if_expr: ast::IfExpr = ctx.node_at_offset()?; +// Assist: replace_if_let_with_match +// +// Replaces `if let` with an else branch with a `match` expression. +// +// ``` +// enum Action { Move { distance: u32 }, Stop } +// +// fn handle(action: Action) { +// <|>if let Action::Move { distance } = action { +// foo(distance) +// } else { +// bar() +// } +// } +// ``` +// -> +// ``` +// enum Action { Move { distance: u32 }, Stop } +// +// fn handle(action: Action) { +// match action { +// Action::Move { distance } => foo(distance), +// _ => bar(), +// } +// } +// ``` +pub(crate) fn replace_if_let_with_match(ctx: AssistCtx) -> Option { + let if_expr: ast::IfExpr = ctx.find_node_at_offset()?; let cond = if_expr.condition()?; let pat = cond.pat()?; let expr = cond.expr()?; @@ -18,14 +42,12 @@ pub(crate) fn replace_if_let_with_match(mut ctx: AssistCtx) -> ast::ElseBranch::IfExpr(_) => return None, }; - ctx.add_action(AssistId("replace_if_let_with_match"), "replace with match", |edit| { + ctx.add_assist(AssistId("replace_if_let_with_match"), "replace with match", |edit| { let match_expr = build_match_expr(expr, pat, then_block, else_block); edit.target(if_expr.syntax().text_range()); edit.replace_node_and_indent(if_expr.syntax(), match_expr); edit.set_cursor(if_expr.syntax().text_range().start()) - }); - - ctx.build() + }) } fn build_match_expr( diff --git a/crates/ra_assists/src/assists/split_import.rs b/crates/ra_assists/src/assists/split_import.rs index fe3e64af50e0..5f8d6b0be1ff 100644 --- a/crates/ra_assists/src/assists/split_import.rs +++ b/crates/ra_assists/src/assists/split_import.rs @@ -1,5 +1,3 @@ -//! FIXME: write short doc here - use std::iter::successors; use hir::db::HirDatabase; @@ -7,8 +5,19 @@ use ra_syntax::{ast, AstNode, TextUnit, T}; use crate::{Assist, AssistCtx, AssistId}; -pub(crate) fn split_import(mut ctx: AssistCtx) -> Option { - let colon_colon = ctx.token_at_offset().find(|leaf| leaf.kind() == T![::])?; +// Assist: split_import +// +// Wraps the tail of import into braces. +// +// ``` +// use std::<|>collections::HashMap; +// ``` +// -> +// ``` +// use std::{collections::HashMap}; +// ``` +pub(crate) fn split_import(ctx: AssistCtx) -> Option { + let colon_colon = ctx.find_token_at_offset(T![::])?; let path = ast::Path::cast(colon_colon.parent())?; let top_path = successors(Some(path), |it| it.parent_path()).last()?; @@ -23,14 +32,12 @@ pub(crate) fn split_import(mut ctx: AssistCtx) -> Option top_path.syntax().text_range().end(), }; - ctx.add_action(AssistId("split_import"), "split import", |edit| { + ctx.add_assist(AssistId("split_import"), "split import", |edit| { edit.target(colon_colon.text_range()); edit.insert(l_curly, "{"); edit.insert(r_curly, "}"); edit.set_cursor(l_curly + TextUnit::of_str("{")); - }); - - ctx.build() + }) } #[cfg(test)] diff --git a/crates/ra_assists/src/doc_tests.rs b/crates/ra_assists/src/doc_tests.rs new file mode 100644 index 000000000000..6e1e3de843e6 --- /dev/null +++ b/crates/ra_assists/src/doc_tests.rs @@ -0,0 +1,34 @@ +//! Each assist definition has a special comment, which specifies docs and +//! example. +//! +//! We collect all the example and write the as tests in this module. + +mod generated; + +use hir::mock::MockDatabase; +use ra_db::FileRange; +use test_utils::{assert_eq_text, extract_range_or_offset}; + +fn check(assist_id: &str, before: &str, after: &str) { + let (selection, before) = extract_range_or_offset(before); + let (db, _source_root, file_id) = MockDatabase::with_single_file(&before); + let frange = FileRange { file_id, range: selection.into() }; + + let (_assist_id, action) = crate::assists(&db, frange) + .into_iter() + .find(|(id, _)| id.id.0 == assist_id) + .unwrap_or_else(|| { + panic!( + "\n\nAssist is not applicable: {}\nAvailable assists: {}", + assist_id, + crate::assists(&db, frange) + .into_iter() + .map(|(id, _)| id.id.0) + .collect::>() + .join(", ") + ) + }); + + let actual = action.edit.apply(&before); + assert_eq_text!(after, &actual); +} diff --git a/crates/ra_assists/src/doc_tests/generated.rs b/crates/ra_assists/src/doc_tests/generated.rs new file mode 100644 index 000000000000..1bee76f59a33 --- /dev/null +++ b/crates/ra_assists/src/doc_tests/generated.rs @@ -0,0 +1,526 @@ +//! Generated file, do not edit by hand, see `crate/ra_tools/src/codegen` + +use super::check; + +#[test] +fn doctest_add_derive() { + check( + "add_derive", + r#####" +struct Point { + x: u32, + y: u32,<|> +} +"#####, + r#####" +#[derive()] +struct Point { + x: u32, + y: u32, +} +"#####, + ) +} + +#[test] +fn doctest_add_explicit_type() { + check( + "add_explicit_type", + r#####" +fn main() { + let x<|> = 92; +} +"#####, + r#####" +fn main() { + let x: i32 = 92; +} +"#####, + ) +} + +#[test] +fn doctest_add_hash() { + check( + "add_hash", + r#####" +fn main() { + r#"Hello,<|> World!"#; +} +"#####, + r#####" +fn main() { + r##"Hello, World!"##; +} +"#####, + ) +} + +#[test] +fn doctest_add_impl() { + check( + "add_impl", + r#####" +struct Ctx { + data: T,<|> +} +"#####, + r#####" +struct Ctx { + data: T, +} + +impl Ctx { + +} +"#####, + ) +} + +#[test] +fn doctest_add_impl_default_members() { + check( + "add_impl_default_members", + r#####" +trait T { + Type X; + fn foo(&self); + fn bar(&self) {} +} + +impl T for () { + Type X = (); + fn foo(&self) {}<|> + +} +"#####, + r#####" +trait T { + Type X; + fn foo(&self); + fn bar(&self) {} +} + +impl T for () { + Type X = (); + fn foo(&self) {} + fn bar(&self) {} + +} +"#####, + ) +} + +#[test] +fn doctest_add_impl_missing_members() { + check( + "add_impl_missing_members", + r#####" +trait T { + Type X; + fn foo(&self); + fn bar(&self) {} +} + +impl T for () {<|> + +} +"#####, + r#####" +trait T { + Type X; + fn foo(&self); + fn bar(&self) {} +} + +impl T for () { + fn foo(&self) { unimplemented!() } + +} +"#####, + ) +} + +#[test] +fn doctest_add_import() { + check( + "add_import", + r#####" +fn process(map: std::collections::<|>HashMap) {} +"#####, + r#####" +use std::collections::HashMap; + +fn process(map: HashMap) {} +"#####, + ) +} + +#[test] +fn doctest_apply_demorgan() { + check( + "apply_demorgan", + r#####" +fn main() { + if x != 4 ||<|> !y {} +} +"#####, + r#####" +fn main() { + if !(x == 4 && y) {} +} +"#####, + ) +} + +#[test] +fn doctest_change_visibility() { + check( + "change_visibility", + r#####" +<|>fn frobnicate() {} +"#####, + r#####" +pub(crate) fn frobnicate() {} +"#####, + ) +} + +#[test] +fn doctest_convert_to_guarded_return() { + check( + "convert_to_guarded_return", + r#####" +fn main() { + <|>if cond { + foo(); + bar(); + } +} +"#####, + r#####" +fn main() { + if !cond { + return; + } + foo(); + bar(); +} +"#####, + ) +} + +#[test] +fn doctest_fill_match_arms() { + check( + "fill_match_arms", + r#####" +enum Action { Move { distance: u32 }, Stop } + +fn handle(action: Action) { + match action { + <|> + } +} +"#####, + r#####" +enum Action { Move { distance: u32 }, Stop } + +fn handle(action: Action) { + match action { + Action::Move { distance } => (), + Action::Stop => (), + } +} +"#####, + ) +} + +#[test] +fn doctest_flip_binexpr() { + check( + "flip_binexpr", + r#####" +fn main() { + let _ = 90 +<|> 2; +} +"#####, + r#####" +fn main() { + let _ = 2 + 90; +} +"#####, + ) +} + +#[test] +fn doctest_flip_comma() { + check( + "flip_comma", + r#####" +fn main() { + ((1, 2),<|> (3, 4)); +} +"#####, + r#####" +fn main() { + ((3, 4), (1, 2)); +} +"#####, + ) +} + +#[test] +fn doctest_flip_trait_bound() { + check( + "flip_trait_bound", + r#####" +fn foo Copy>() { } +"#####, + r#####" +fn foo() { } +"#####, + ) +} + +#[test] +fn doctest_inline_local_variable() { + check( + "inline_local_variable", + r#####" +fn main() { + let x<|> = 1 + 2; + x * 4; +} +"#####, + r#####" +fn main() { + (1 + 2) * 4; +} +"#####, + ) +} + +#[test] +fn doctest_introduce_variable() { + check( + "introduce_variable", + r#####" +fn main() { + <|>(1 + 2)<|> * 4; +} +"#####, + r#####" +fn main() { + let var_name = (1 + 2); + var_name * 4; +} +"#####, + ) +} + +#[test] +fn doctest_make_raw_string() { + check( + "make_raw_string", + r#####" +fn main() { + "Hello,<|> World!"; +} +"#####, + r#####" +fn main() { + r#"Hello, World!"#; +} +"#####, + ) +} + +#[test] +fn doctest_make_usual_string() { + check( + "make_usual_string", + r#####" +fn main() { + r#"Hello,<|> "World!""#; +} +"#####, + r#####" +fn main() { + "Hello, \"World!\""; +} +"#####, + ) +} + +#[test] +fn doctest_merge_match_arms() { + check( + "merge_match_arms", + r#####" +enum Action { Move { distance: u32 }, Stop } + +fn handle(action: Action) { + match action { + <|>Action::Move(..) => foo(), + Action::Stop => foo(), + } +} +"#####, + r#####" +enum Action { Move { distance: u32 }, Stop } + +fn handle(action: Action) { + match action { + Action::Move(..) | Action::Stop => foo(), + } +} +"#####, + ) +} + +#[test] +fn doctest_move_arm_cond_to_match_guard() { + check( + "move_arm_cond_to_match_guard", + r#####" +enum Action { Move { distance: u32 }, Stop } + +fn handle(action: Action) { + match action { + Action::Move { distance } => <|>if distance > 10 { foo() }, + _ => (), + } +} +"#####, + r#####" +enum Action { Move { distance: u32 }, Stop } + +fn handle(action: Action) { + match action { + Action::Move { distance } if distance > 10 => foo(), + _ => (), + } +} +"#####, + ) +} + +#[test] +fn doctest_move_bounds_to_where_clause() { + check( + "move_bounds_to_where_clause", + r#####" +fn applyF: FnOnce(T) -> U>(f: F, x: T) -> U { + f(x) +} +"#####, + r#####" +fn apply(f: F, x: T) -> U where F: FnOnce(T) -> U { + f(x) +} +"#####, + ) +} + +#[test] +fn doctest_move_guard_to_arm_body() { + check( + "move_guard_to_arm_body", + r#####" +enum Action { Move { distance: u32 }, Stop } + +fn handle(action: Action) { + match action { + Action::Move { distance } <|>if distance > 10 => foo(), + _ => (), + } +} +"#####, + r#####" +enum Action { Move { distance: u32 }, Stop } + +fn handle(action: Action) { + match action { + Action::Move { distance } => if distance > 10 { foo() }, + _ => (), + } +} +"#####, + ) +} + +#[test] +fn doctest_remove_dbg() { + check( + "remove_dbg", + r#####" +fn main() { + <|>dbg!(92); +} +"#####, + r#####" +fn main() { + 92; +} +"#####, + ) +} + +#[test] +fn doctest_remove_hash() { + check( + "remove_hash", + r#####" +fn main() { + r#"Hello,<|> World!"#; +} +"#####, + r#####" +fn main() { + r"Hello, World!"; +} +"#####, + ) +} + +#[test] +fn doctest_replace_if_let_with_match() { + check( + "replace_if_let_with_match", + r#####" +enum Action { Move { distance: u32 }, Stop } + +fn handle(action: Action) { + <|>if let Action::Move { distance } = action { + foo(distance) + } else { + bar() + } +} +"#####, + r#####" +enum Action { Move { distance: u32 }, Stop } + +fn handle(action: Action) { + match action { + Action::Move { distance } => foo(distance), + _ => bar(), + } +} +"#####, + ) +} + +#[test] +fn doctest_split_import() { + check( + "split_import", + r#####" +use std::<|>collections::HashMap; +"#####, + r#####" +use std::{collections::HashMap}; +"#####, + ) +} diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index ab77b46a9977..38599d4f173c 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs @@ -7,15 +7,16 @@ mod assist_ctx; mod marks; +#[cfg(test)] +mod doc_tests; use hir::db::HirDatabase; -use itertools::Itertools; use ra_db::FileRange; use ra_syntax::{TextRange, TextUnit}; use ra_text_edit::TextEdit; pub(crate) use crate::assist_ctx::{Assist, AssistCtx}; -pub use crate::assists::auto_import::auto_import_text_edit; +pub use crate::assists::add_import::auto_import_text_edit; /// Unique identifier of the assist, should not be shown to the user /// directly. @@ -36,7 +37,7 @@ pub struct AssistAction { pub target: Option, } -/// Return all the assists eapplicable at the given position. +/// Return all the assists applicable at the given position. /// /// Assists are returned in the "unresolved" state, that is only labels are /// returned, without actual edits. @@ -49,10 +50,10 @@ where .iter() .filter_map(|f| f(ctx.clone())) .map(|a| match a { - Assist::Unresolved(labels) => labels, - Assist::Resolved(..) => unreachable!(), + Assist::Unresolved { label } => label, + Assist::Resolved { .. } => unreachable!(), }) - .concat() + .collect() }) } @@ -71,10 +72,10 @@ where .iter() .filter_map(|f| f(ctx.clone())) .map(|a| match a { - Assist::Resolved(labels_actions) => labels_actions, - Assist::Unresolved(..) => unreachable!(), + Assist::Resolved { label, action } => (label, action), + Assist::Unresolved { .. } => unreachable!(), }) - .concat(); + .collect::>(); a.sort_by(|a, b| match (a.1.target, b.1.target) { (Some(a), Some(b)) => a.len().cmp(&b.len()), (Some(_), None) => Ordering::Less, @@ -95,6 +96,7 @@ mod assists { mod apply_demorgan; mod flip_comma; mod flip_binexpr; + mod flip_trait_bound; mod change_visibility; mod fill_match_arms; mod merge_match_arms; @@ -104,7 +106,7 @@ mod assists { mod replace_if_let_with_match; mod split_import; mod remove_dbg; - pub(crate) mod auto_import; + pub(crate) mod add_import; mod add_missing_impl_members; mod move_guard; mod move_bounds; @@ -121,11 +123,12 @@ mod assists { merge_match_arms::merge_match_arms, flip_comma::flip_comma, flip_binexpr::flip_binexpr, + flip_trait_bound::flip_trait_bound, introduce_variable::introduce_variable, replace_if_let_with_match::replace_if_let_with_match, split_import::split_import, remove_dbg::remove_dbg, - auto_import::auto_import, + add_import::add_import, add_missing_impl_members::add_missing_impl_members, add_missing_impl_members::add_missing_default_members, inline_local_variable::inline_local_varialbe, @@ -154,39 +157,6 @@ mod helpers { assist: fn(AssistCtx) -> Option, before: &str, after: &str, - ) { - check_assist_nth_action(assist, before, after, 0) - } - - pub(crate) fn check_assist_range( - assist: fn(AssistCtx) -> Option, - before: &str, - after: &str, - ) { - check_assist_range_nth_action(assist, before, after, 0) - } - - pub(crate) fn check_assist_target( - assist: fn(AssistCtx) -> Option, - before: &str, - target: &str, - ) { - check_assist_target_nth_action(assist, before, target, 0) - } - - pub(crate) fn check_assist_range_target( - assist: fn(AssistCtx) -> Option, - before: &str, - target: &str, - ) { - check_assist_range_target_nth_action(assist, before, target, 0) - } - - pub(crate) fn check_assist_nth_action( - assist: fn(AssistCtx) -> Option, - before: &str, - after: &str, - index: usize, ) { let (before_cursor_pos, before) = extract_offset(before); let (db, _source_root, file_id) = MockDatabase::with_single_file(&before); @@ -194,12 +164,11 @@ mod helpers { FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) }; let assist = AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); - let labels_actions = match assist { - Assist::Unresolved(_) => unreachable!(), - Assist::Resolved(labels_actions) => labels_actions, + let action = match assist { + Assist::Unresolved { .. } => unreachable!(), + Assist::Resolved { action, .. } => action, }; - let (_, action) = labels_actions.get(index).expect("expect assist action at index"); let actual = action.edit.apply(&before); let actual_cursor_pos = match action.cursor_position { None => action @@ -212,23 +181,21 @@ mod helpers { assert_eq_text!(after, &actual); } - pub(crate) fn check_assist_range_nth_action( + pub(crate) fn check_assist_range( assist: fn(AssistCtx) -> Option, before: &str, after: &str, - index: usize, ) { let (range, before) = extract_range(before); let (db, _source_root, file_id) = MockDatabase::with_single_file(&before); let frange = FileRange { file_id, range }; let assist = AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); - let labels_actions = match assist { - Assist::Unresolved(_) => unreachable!(), - Assist::Resolved(labels_actions) => labels_actions, + let action = match assist { + Assist::Unresolved { .. } => unreachable!(), + Assist::Resolved { action, .. } => action, }; - let (_, action) = labels_actions.get(index).expect("expect assist action at index"); let mut actual = action.edit.apply(&before); if let Some(pos) = action.cursor_position { actual = add_cursor(&actual, pos); @@ -236,11 +203,10 @@ mod helpers { assert_eq_text!(after, &actual); } - pub(crate) fn check_assist_target_nth_action( + pub(crate) fn check_assist_target( assist: fn(AssistCtx) -> Option, before: &str, target: &str, - index: usize, ) { let (before_cursor_pos, before) = extract_offset(before); let (db, _source_root, file_id) = MockDatabase::with_single_file(&before); @@ -248,33 +214,30 @@ mod helpers { FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) }; let assist = AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); - let labels_actions = match assist { - Assist::Unresolved(_) => unreachable!(), - Assist::Resolved(labels_actions) => labels_actions, + let action = match assist { + Assist::Unresolved { .. } => unreachable!(), + Assist::Resolved { action, .. } => action, }; - let (_, action) = labels_actions.get(index).expect("expect assist action at index"); let range = action.target.expect("expected target on action"); assert_eq_text!(&before[range.start().to_usize()..range.end().to_usize()], target); } - pub(crate) fn check_assist_range_target_nth_action( + pub(crate) fn check_assist_range_target( assist: fn(AssistCtx) -> Option, before: &str, target: &str, - index: usize, ) { let (range, before) = extract_range(before); let (db, _source_root, file_id) = MockDatabase::with_single_file(&before); let frange = FileRange { file_id, range }; let assist = AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); - let labels_actions = match assist { - Assist::Unresolved(_) => unreachable!(), - Assist::Resolved(labels_actions) => labels_actions, + let action = match assist { + Assist::Unresolved { .. } => unreachable!(), + Assist::Resolved { action, .. } => action, }; - let (_, action) = labels_actions.get(index).expect("expect assist action at index"); let range = action.target.expect("expected target on action"); assert_eq_text!(&before[range.start().to_usize()..range.end().to_usize()], target); } diff --git a/crates/ra_db/src/lib.rs b/crates/ra_db/src/lib.rs index fc5d6d39654c..0d1ab48438d0 100644 --- a/crates/ra_db/src/lib.rs +++ b/crates/ra_db/src/lib.rs @@ -134,10 +134,7 @@ impl FileLoader for FileLoaderDelegate<&'_ T> { ) -> Option { let path = { let mut path = self.0.file_relative_path(anchor); - // Workaround for relative path API: turn `lib.rs` into ``. - if !path.pop() { - path = RelativePathBuf::default(); - } + assert!(path.pop()); path.push(relative_path); path.normalize() }; diff --git a/crates/ra_hir/Cargo.toml b/crates/ra_hir/Cargo.toml index 54c09788aa0a..5df371bc0f97 100644 --- a/crates/ra_hir/Cargo.toml +++ b/crates/ra_hir/Cargo.toml @@ -19,12 +19,14 @@ ra_cfg = { path = "../ra_cfg" } ra_db = { path = "../ra_db" } mbe = { path = "../ra_mbe", package = "ra_mbe" } tt = { path = "../ra_tt", package = "ra_tt" } +hir_expand = { path = "../ra_hir_expand", package = "ra_hir_expand" } +hir_def = { path = "../ra_hir_def", package = "ra_hir_def" } test_utils = { path = "../test_utils" } ra_prof = { path = "../ra_prof" } -chalk-solve = { git = "https://github.com/rust-lang/chalk.git" } -chalk-rust-ir = { git = "https://github.com/rust-lang/chalk.git" } -chalk-ir = { git = "https://github.com/rust-lang/chalk.git" } +chalk-solve = { git = "https://github.com/rust-lang/chalk.git", rev = "8314f2fcec8582a58c24b638f1a259d4145a0809" } +chalk-rust-ir = { git = "https://github.com/rust-lang/chalk.git", rev = "8314f2fcec8582a58c24b638f1a259d4145a0809" } +chalk-ir = { git = "https://github.com/rust-lang/chalk.git", rev = "8314f2fcec8582a58c24b638f1a259d4145a0809" } lalrpop-intern = "0.15.1" [dev-dependencies] diff --git a/crates/ra_hir/src/adt.rs b/crates/ra_hir/src/adt.rs index 3e9cd3c63d46..4fa2062bdba1 100644 --- a/crates/ra_hir/src/adt.rs +++ b/crates/ra_hir/src/adt.rs @@ -3,13 +3,14 @@ use std::sync::Arc; +use hir_def::{type_ref::TypeRef, LocalEnumVariantId}; +use hir_expand::name::AsName; use ra_arena::{impl_arena_id, Arena, RawId}; use ra_syntax::ast::{self, NameOwner, StructKind, TypeAscriptionOwner}; use crate::{ db::{AstDatabase, DefDatabase, HirDatabase}, - type_ref::TypeRef, - AsName, Enum, EnumVariant, FieldSource, HasSource, Module, Name, Source, Struct, StructField, + Enum, EnumVariant, FieldSource, HasSource, Module, Name, Source, Struct, StructField, }; impl Struct { @@ -67,7 +68,7 @@ impl EnumVariant { #[derive(Debug, Clone, PartialEq, Eq)] pub struct EnumData { pub(crate) name: Option, - pub(crate) variants: Arena, + pub(crate) variants: Arena, } impl EnumData { @@ -84,10 +85,6 @@ impl EnumData { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub(crate) struct EnumVariantId(RawId); -impl_arena_id!(EnumVariantId); - #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) struct EnumVariantData { pub(crate) name: Option, diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index 8eb3c577d671..b32aa145ea8b 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs @@ -5,11 +5,17 @@ pub(crate) mod docs; use std::sync::Arc; -use ra_db::{CrateId, Edition, FileId}; +use hir_def::{ + builtin_type::BuiltinType, + type_ref::{Mutability, TypeRef}, + CrateModuleId, LocalEnumVariantId, ModuleId, +}; +use hir_expand::name::{self, AsName}; +use ra_db::{CrateId, Edition}; use ra_syntax::ast::{self, NameOwner, TypeAscriptionOwner}; use crate::{ - adt::{EnumVariantId, StructFieldId, VariantDef}, + adt::{StructFieldId, VariantDef}, db::{AstDatabase, DefDatabase, HirDatabase}, diagnostics::DiagnosticSink, expr::{validation::ExprValidator, Body, BodySourceMap}, @@ -19,20 +25,11 @@ use crate::{ TypeAliasId, }, impl_block::ImplBlock, - name::{ - BOOL, CHAR, F32, F64, I128, I16, I32, I64, I8, ISIZE, SELF_TYPE, STR, U128, U16, U32, U64, - U8, USIZE, - }, - nameres::{CrateModuleId, ImportId, ModuleScope, Namespace}, + nameres::{ImportId, ModuleScope, Namespace}, resolve::{Resolver, Scope, TypeNs}, traits::TraitData, - ty::{ - primitive::{FloatBitness, FloatTy, IntBitness, IntTy, Signedness}, - InferenceResult, TraitRef, - }, - type_ref::Mutability, - type_ref::TypeRef, - AsName, AstId, Either, HasSource, Name, Ty, + ty::{InferenceResult, TraitRef}, + Either, HasSource, Name, Ty, }; /// hir::Crate describes a single crate. It's the main interface with which @@ -67,8 +64,7 @@ impl Crate { pub fn root_module(self, db: &impl DefDatabase) -> Option { let module_id = db.crate_def_map(self).root(); - let module = Module { krate: self, module_id }; - Some(module) + Some(Module::new(self, module_id)) } pub fn edition(self, db: &impl DefDatabase) -> Edition { @@ -83,43 +79,7 @@ impl Crate { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Module { - pub(crate) krate: Crate, - pub(crate) module_id: CrateModuleId, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum BuiltinType { - Char, - Bool, - Str, - Int(IntTy), - Float(FloatTy), -} - -impl BuiltinType { - #[rustfmt::skip] - pub(crate) const ALL: &'static [(Name, BuiltinType)] = &[ - (CHAR, BuiltinType::Char), - (BOOL, BuiltinType::Bool), - (STR, BuiltinType::Str), - - (ISIZE, BuiltinType::Int(IntTy { signedness: Signedness::Signed, bitness: IntBitness::Xsize })), - (I8, BuiltinType::Int(IntTy { signedness: Signedness::Signed, bitness: IntBitness::X8 })), - (I16, BuiltinType::Int(IntTy { signedness: Signedness::Signed, bitness: IntBitness::X16 })), - (I32, BuiltinType::Int(IntTy { signedness: Signedness::Signed, bitness: IntBitness::X32 })), - (I64, BuiltinType::Int(IntTy { signedness: Signedness::Signed, bitness: IntBitness::X64 })), - (I128, BuiltinType::Int(IntTy { signedness: Signedness::Signed, bitness: IntBitness::X128 })), - - (USIZE, BuiltinType::Int(IntTy { signedness: Signedness::Unsigned, bitness: IntBitness::Xsize })), - (U8, BuiltinType::Int(IntTy { signedness: Signedness::Unsigned, bitness: IntBitness::X8 })), - (U16, BuiltinType::Int(IntTy { signedness: Signedness::Unsigned, bitness: IntBitness::X16 })), - (U32, BuiltinType::Int(IntTy { signedness: Signedness::Unsigned, bitness: IntBitness::X32 })), - (U64, BuiltinType::Int(IntTy { signedness: Signedness::Unsigned, bitness: IntBitness::X64 })), - (U128, BuiltinType::Int(IntTy { signedness: Signedness::Unsigned, bitness: IntBitness::X128 })), - - (F32, BuiltinType::Float(FloatTy { bitness: FloatBitness::X32 })), - (F64, BuiltinType::Float(FloatTy { bitness: FloatBitness::X64 })), - ]; + pub(crate) id: ModuleId, } /// The defs which can be visible in the module. @@ -148,39 +108,19 @@ impl_froms!( BuiltinType ); -pub enum ModuleSource { - SourceFile(ast::SourceFile), - Module(ast::Module), -} - -impl ModuleSource { - pub(crate) fn new( - db: &(impl DefDatabase + AstDatabase), - file_id: Option, - decl_id: Option>, - ) -> ModuleSource { - match (file_id, decl_id) { - (Some(file_id), _) => { - let source_file = db.parse(file_id).tree(); - ModuleSource::SourceFile(source_file) - } - (None, Some(item_id)) => { - let module = item_id.to_node(db); - assert!(module.item_list().is_some(), "expected inline module"); - ModuleSource::Module(module) - } - (None, None) => panic!(), - } - } -} +pub use hir_def::ModuleSource; impl Module { + pub(crate) fn new(krate: Crate, crate_module_id: CrateModuleId) -> Module { + Module { id: ModuleId { krate: krate.crate_id, module_id: crate_module_id } } + } + /// Name of this module. pub fn name(self, db: &impl DefDatabase) -> Option { - let def_map = db.crate_def_map(self.krate); - let parent = def_map[self.module_id].parent?; + let def_map = db.crate_def_map(self.krate()); + let parent = def_map[self.id.module_id].parent?; def_map[parent].children.iter().find_map(|(name, module_id)| { - if *module_id == self.module_id { + if *module_id == self.id.module_id { Some(name.clone()) } else { None @@ -200,29 +140,29 @@ impl Module { } /// Returns the crate this module is part of. - pub fn krate(self, _db: &impl DefDatabase) -> Option { - Some(self.krate) + pub fn krate(self) -> Crate { + Crate { crate_id: self.id.krate } } /// Topmost parent of this module. Every module has a `crate_root`, but some /// might be missing `krate`. This can happen if a module's file is not included /// in the module tree of any target in `Cargo.toml`. pub fn crate_root(self, db: &impl DefDatabase) -> Module { - let def_map = db.crate_def_map(self.krate); + let def_map = db.crate_def_map(self.krate()); self.with_module_id(def_map.root()) } /// Finds a child module with the specified name. pub fn child(self, db: &impl HirDatabase, name: &Name) -> Option { - let def_map = db.crate_def_map(self.krate); - let child_id = def_map[self.module_id].children.get(name)?; + let def_map = db.crate_def_map(self.krate()); + let child_id = def_map[self.id.module_id].children.get(name)?; Some(self.with_module_id(*child_id)) } /// Iterates over all child modules. pub fn children(self, db: &impl DefDatabase) -> impl Iterator { - let def_map = db.crate_def_map(self.krate); - let children = def_map[self.module_id] + let def_map = db.crate_def_map(self.krate()); + let children = def_map[self.id.module_id] .children .iter() .map(|(_, module_id)| self.with_module_id(*module_id)) @@ -232,8 +172,8 @@ impl Module { /// Finds a parent module. pub fn parent(self, db: &impl DefDatabase) -> Option { - let def_map = db.crate_def_map(self.krate); - let parent_id = def_map[self.module_id].parent?; + let def_map = db.crate_def_map(self.krate()); + let parent_id = def_map[self.id.module_id].parent?; Some(self.with_module_id(parent_id)) } @@ -249,11 +189,11 @@ impl Module { /// Returns a `ModuleScope`: a set of items, visible in this module. pub fn scope(self, db: &impl HirDatabase) -> ModuleScope { - db.crate_def_map(self.krate)[self.module_id].scope.clone() + db.crate_def_map(self.krate())[self.id.module_id].scope.clone() } pub fn diagnostics(self, db: &impl HirDatabase, sink: &mut DiagnosticSink) { - db.crate_def_map(self.krate).add_diagnostics(db, self.module_id, sink); + db.crate_def_map(self.krate()).add_diagnostics(db, self.id.module_id, sink); for decl in self.declarations(db) { match decl { crate::ModuleDef::Function(f) => f.diagnostics(db, sink), @@ -277,13 +217,13 @@ impl Module { } pub(crate) fn resolver(self, db: &impl DefDatabase) -> Resolver { - let def_map = db.crate_def_map(self.krate); - Resolver::default().push_module_scope(def_map, self.module_id) + let def_map = db.crate_def_map(self.krate()); + Resolver::default().push_module_scope(def_map, self.id.module_id) } pub fn declarations(self, db: &impl DefDatabase) -> Vec { - let def_map = db.crate_def_map(self.krate); - def_map[self.module_id] + let def_map = db.crate_def_map(self.krate()); + def_map[self.id.module_id] .scope .entries() .filter_map(|(_name, res)| if res.import.is_none() { Some(res.def) } else { None }) @@ -303,7 +243,7 @@ impl Module { } fn with_module_id(self, module_id: CrateModuleId) -> Module { - Module { module_id, krate: self.krate } + Module::new(self.krate(), module_id) } } @@ -340,11 +280,11 @@ pub struct Struct { impl Struct { pub fn module(self, db: &impl DefDatabase) -> Module { - self.id.module(db) + Module { id: self.id.module(db) } } pub fn krate(self, db: &impl DefDatabase) -> Option { - self.module(db).krate(db) + Some(self.module(db).krate()) } pub fn name(self, db: &impl DefDatabase) -> Option { @@ -402,7 +342,7 @@ impl Union { } pub fn module(self, db: &impl HirDatabase) -> Module { - self.id.module(db) + Module { id: self.id.module(db) } } pub fn ty(self, db: &impl HirDatabase) -> Ty { @@ -428,11 +368,11 @@ pub struct Enum { impl Enum { pub fn module(self, db: &impl DefDatabase) -> Module { - self.id.module(db) + Module { id: self.id.module(db) } } pub fn krate(self, db: &impl DefDatabase) -> Option { - self.module(db).krate(db) + Some(self.module(db).krate()) } pub fn name(self, db: &impl DefDatabase) -> Option { @@ -470,7 +410,7 @@ impl Enum { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct EnumVariant { pub(crate) parent: Enum, - pub(crate) id: EnumVariantId, + pub(crate) id: LocalEnumVariantId, } impl EnumVariant { @@ -523,12 +463,14 @@ impl Adt { } pub fn krate(self, db: &impl HirDatabase) -> Option { - match self { - Adt::Struct(s) => s.module(db), - Adt::Union(s) => s.module(db), - Adt::Enum(e) => e.module(db), - } - .krate(db) + Some( + match self { + Adt::Struct(s) => s.module(db), + Adt::Union(s) => s.module(db), + Adt::Enum(e) => e.module(db), + } + .krate(), + ) } pub(crate) fn resolver(self, db: &impl HirDatabase) -> Resolver { @@ -643,7 +585,7 @@ impl FnData { let self_type = if let Some(type_ref) = self_param.ascribed_type() { TypeRef::from_ast(type_ref) } else { - let self_type = TypeRef::Path(SELF_TYPE.into()); + let self_type = TypeRef::Path(name::SELF_TYPE.into()); match self_param.kind() { ast::SelfParamKind::Owned => self_type, ast::SelfParamKind::Ref => { @@ -692,11 +634,11 @@ impl FnData { impl Function { pub fn module(self, db: &impl DefDatabase) -> Module { - self.id.module(db) + Module { id: self.id.module(db) } } pub fn krate(self, db: &impl DefDatabase) -> Option { - self.module(db).krate(db) + Some(self.module(db).krate()) } pub fn name(self, db: &impl HirDatabase) -> Name { @@ -770,11 +712,11 @@ pub struct Const { impl Const { pub fn module(self, db: &impl DefDatabase) -> Module { - self.id.module(db) + Module { id: self.id.module(db) } } pub fn krate(self, db: &impl DefDatabase) -> Option { - self.module(db).krate(db) + Some(self.module(db).krate()) } pub fn data(self, db: &impl HirDatabase) -> Arc { @@ -867,11 +809,11 @@ pub struct Static { impl Static { pub fn module(self, db: &impl DefDatabase) -> Module { - self.id.module(db) + Module { id: self.id.module(db) } } pub fn krate(self, db: &impl DefDatabase) -> Option { - self.module(db).krate(db) + Some(self.module(db).krate()) } pub fn data(self, db: &impl HirDatabase) -> Arc { @@ -896,7 +838,7 @@ pub struct Trait { impl Trait { pub fn module(self, db: &impl DefDatabase) -> Module { - self.id.module(db) + Module { id: self.id.module(db) } } pub fn name(self, db: &impl DefDatabase) -> Option { @@ -917,9 +859,7 @@ impl Trait { .where_predicates .iter() .filter_map(|pred| match &pred.type_ref { - TypeRef::Path(p) if p.as_ident() == Some(&crate::name::SELF_TYPE) => { - pred.bound.as_path() - } + TypeRef::Path(p) if p.as_ident() == Some(&name::SELF_TYPE) => pred.bound.as_path(), _ => None, }) .filter_map(|path| match resolver.resolve_path_in_type_ns_fully(db, path) { @@ -998,11 +938,11 @@ pub struct TypeAlias { impl TypeAlias { pub fn module(self, db: &impl DefDatabase) -> Module { - self.id.module(db) + Module { id: self.id.module(db) } } pub fn krate(self, db: &impl DefDatabase) -> Option { - self.module(db).krate(db) + Some(self.module(db).krate()) } /// The containing impl block, if this is a method. diff --git a/crates/ra_hir/src/code_model/src.rs b/crates/ra_hir/src/code_model/src.rs index fdae269060e0..5c7f61eefb46 100644 --- a/crates/ra_hir/src/code_model/src.rs +++ b/crates/ra_hir/src/code_model/src.rs @@ -1,9 +1,6 @@ //! FIXME: write short doc here -use ra_syntax::{ - ast::{self, AstNode}, - SyntaxNode, -}; +use ra_syntax::ast::{self, AstNode}; use crate::{ db::{AstDatabase, DefDatabase, HirDatabase}, @@ -12,34 +9,21 @@ use crate::{ ModuleSource, Static, Struct, StructField, Trait, TypeAlias, Union, }; -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub struct Source { - pub file_id: HirFileId, - pub ast: T, -} +pub use hir_def::Source; pub trait HasSource { type Ast; fn source(self, db: &(impl DefDatabase + AstDatabase)) -> Source; } -impl Source { - pub(crate) fn map U, U>(self, f: F) -> Source { - Source { file_id: self.file_id, ast: f(self.ast) } - } - pub(crate) fn file_syntax(&self, db: &impl AstDatabase) -> SyntaxNode { - db.parse_or_expand(self.file_id).expect("source created from invalid file") - } -} - /// NB: Module is !HasSource, because it has two source nodes at the same time: /// definition and declaration. impl Module { /// Returns a node which defines this module. That is, a file or a `mod foo {}` with items. pub fn definition_source(self, db: &(impl DefDatabase + AstDatabase)) -> Source { - let def_map = db.crate_def_map(self.krate); - let decl_id = def_map[self.module_id].declaration; - let file_id = def_map[self.module_id].definition; + let def_map = db.crate_def_map(self.krate()); + let decl_id = def_map[self.id.module_id].declaration; + let file_id = def_map[self.id.module_id].definition; let ast = ModuleSource::new(db, file_id, decl_id); let file_id = file_id.map(HirFileId::from).unwrap_or_else(|| decl_id.unwrap().file_id()); Source { file_id, ast } @@ -51,8 +35,8 @@ impl Module { self, db: &(impl DefDatabase + AstDatabase), ) -> Option> { - let def_map = db.crate_def_map(self.krate); - let decl = def_map[self.module_id].declaration?; + let def_map = db.crate_def_map(self.krate()); + let decl = def_map[self.id.module_id].declaration?; let ast = decl.to_node(db); Some(Source { file_id: decl.file_id(), ast }) } diff --git a/crates/ra_hir/src/db.rs b/crates/ra_hir/src/db.rs index 489a3b19cabf..ebfd970ebc44 100644 --- a/crates/ra_hir/src/db.rs +++ b/crates/ra_hir/src/db.rs @@ -2,8 +2,8 @@ use std::sync::Arc; -use ra_db::{salsa, SourceDatabase}; -use ra_syntax::{ast, Parse, SmolStr, SyntaxNode}; +use ra_db::salsa; +use ra_syntax::SmolStr; use crate::{ adt::{EnumData, StructData}, @@ -12,81 +12,30 @@ use crate::{ ids, impl_block::{ImplBlock, ImplSourceMap, ModuleImplBlocks}, lang_item::{LangItemTarget, LangItems}, - nameres::{CrateDefMap, ImportSourceMap, Namespace, RawItems}, + nameres::{CrateDefMap, Namespace}, traits::TraitData, ty::{ method_resolution::CrateImplBlocks, traits::Impl, CallableDef, FnSig, GenericPredicate, InferenceResult, Substs, Ty, TypableDef, TypeCtor, }, type_alias::TypeAliasData, - AstIdMap, Const, ConstData, Crate, DefWithBody, Enum, ErasedFileAstId, ExprScopes, FnData, - Function, HirFileId, MacroCallLoc, MacroDefId, Module, Static, Struct, StructField, Trait, - TypeAlias, + Const, ConstData, Crate, DefWithBody, Enum, ExprScopes, FnData, Function, Module, Static, + Struct, StructField, Trait, TypeAlias, }; -/// We store all interned things in the single QueryGroup. -/// -/// This is done mainly to allow both "volatile" `AstDatabase` and "stable" -/// `DefDatabase` to access macros, without adding hard dependencies between the -/// two. -#[salsa::query_group(InternDatabaseStorage)] -pub trait InternDatabase: SourceDatabase { - #[salsa::interned] - fn intern_macro(&self, macro_call: MacroCallLoc) -> ids::MacroCallId; - #[salsa::interned] - fn intern_function(&self, loc: ids::ItemLoc) -> ids::FunctionId; - #[salsa::interned] - fn intern_struct(&self, loc: ids::ItemLoc) -> ids::StructId; - #[salsa::interned] - fn intern_enum(&self, loc: ids::ItemLoc) -> ids::EnumId; - #[salsa::interned] - fn intern_const(&self, loc: ids::ItemLoc) -> ids::ConstId; - #[salsa::interned] - fn intern_static(&self, loc: ids::ItemLoc) -> ids::StaticId; - #[salsa::interned] - fn intern_trait(&self, loc: ids::ItemLoc) -> ids::TraitId; - #[salsa::interned] - fn intern_type_alias(&self, loc: ids::ItemLoc) -> ids::TypeAliasId; - - // Interned IDs for Chalk integration - #[salsa::interned] - fn intern_type_ctor(&self, type_ctor: TypeCtor) -> ids::TypeCtorId; - #[salsa::interned] - fn intern_impl(&self, impl_: Impl) -> ids::GlobalImplId; -} - -/// This database has access to source code, so queries here are not really -/// incremental. -#[salsa::query_group(AstDatabaseStorage)] -pub trait AstDatabase: InternDatabase { - #[salsa::invoke(crate::source_id::AstIdMap::ast_id_map_query)] - fn ast_id_map(&self, file_id: HirFileId) -> Arc; - - #[salsa::transparent] - #[salsa::invoke(crate::source_id::AstIdMap::file_item_query)] - fn ast_id_to_node(&self, file_id: HirFileId, ast_id: ErasedFileAstId) -> SyntaxNode; - - #[salsa::transparent] - #[salsa::invoke(crate::ids::HirFileId::parse_or_expand_query)] - fn parse_or_expand(&self, file_id: HirFileId) -> Option; - - #[salsa::invoke(crate::ids::HirFileId::parse_macro_query)] - fn parse_macro(&self, macro_file: ids::MacroFile) -> Option>; - - #[salsa::invoke(crate::ids::macro_def_query)] - fn macro_def(&self, macro_id: MacroDefId) -> Option>; - - #[salsa::invoke(crate::ids::macro_arg_query)] - fn macro_arg(&self, macro_call: ids::MacroCallId) -> Option>; - - #[salsa::invoke(crate::ids::macro_expand_query)] - fn macro_expand(&self, macro_call: ids::MacroCallId) -> Result, String>; -} +pub use hir_def::db::{ + DefDatabase2, DefDatabase2Storage, InternDatabase, InternDatabaseStorage, RawItemsQuery, + RawItemsWithSourceMapQuery, +}; +pub use hir_expand::db::{ + AstDatabase, AstDatabaseStorage, AstIdMapQuery, MacroArgQuery, MacroDefQuery, MacroExpandQuery, + ParseMacroQuery, +}; // This database uses `AstDatabase` internally, #[salsa::query_group(DefDatabaseStorage)] #[salsa::requires(AstDatabase)] -pub trait DefDatabase: InternDatabase + HirDebugDatabase { +pub trait DefDatabase: HirDebugDatabase + DefDatabase2 { #[salsa::invoke(crate::adt::StructData::struct_data_query)] fn struct_data(&self, s: Struct) -> Arc; @@ -99,15 +48,6 @@ pub trait DefDatabase: InternDatabase + HirDebugDatabase { #[salsa::invoke(crate::traits::TraitItemsIndex::trait_items_index)] fn trait_items_index(&self, module: Module) -> crate::traits::TraitItemsIndex; - #[salsa::invoke(RawItems::raw_items_with_source_map_query)] - fn raw_items_with_source_map( - &self, - file_id: HirFileId, - ) -> (Arc, Arc); - - #[salsa::invoke(RawItems::raw_items_query)] - fn raw_items(&self, file_id: HirFileId) -> Arc; - #[salsa::invoke(CrateDefMap::crate_def_map_query)] fn crate_def_map(&self, krate: Crate) -> Arc; @@ -202,6 +142,12 @@ pub trait HirDatabase: DefDatabase + AstDatabase { #[salsa::invoke(crate::ty::traits::trait_solver_query)] fn trait_solver(&self, krate: Crate) -> crate::ty::traits::TraitSolver; + // Interned IDs for Chalk integration + #[salsa::interned] + fn intern_type_ctor(&self, type_ctor: TypeCtor) -> ids::TypeCtorId; + #[salsa::interned] + fn intern_impl(&self, impl_: Impl) -> ids::GlobalImplId; + #[salsa::invoke(crate::ty::traits::chalk::associated_ty_data_query)] fn associated_ty_data(&self, id: chalk_ir::TypeId) -> Arc; diff --git a/crates/ra_hir/src/debug.rs b/crates/ra_hir/src/debug.rs index 48b69000bcdc..4f3e922c3c5c 100644 --- a/crates/ra_hir/src/debug.rs +++ b/crates/ra_hir/src/debug.rs @@ -36,12 +36,6 @@ impl Module { } } -impl HirFileId { - pub fn debug(self, db: &impl HirDebugDatabase) -> impl fmt::Debug + '_ { - debug_fn(move |fmt| db.debug_hir_file_id(self, fmt)) - } -} - pub trait HirDebugHelper: HirDatabase { fn crate_name(&self, _krate: CrateId) -> Option { None diff --git a/crates/ra_hir/src/expr.rs b/crates/ra_hir/src/expr.rs index d238741babe8..6e23197a4d1c 100644 --- a/crates/ra_hir/src/expr.rs +++ b/crates/ra_hir/src/expr.rs @@ -6,15 +6,17 @@ pub(crate) mod validation; use std::{ops::Index, sync::Arc}; +use hir_def::{ + path::GenericArgs, + type_ref::{Mutability, TypeRef}, +}; use ra_arena::{impl_arena_id, map::ArenaMap, Arena, RawId}; use ra_syntax::{ast, AstPtr}; use rustc_hash::FxHashMap; use crate::{ db::HirDatabase, - path::GenericArgs, ty::primitive::{UncertainFloatTy, UncertainIntTy}, - type_ref::{Mutability, TypeRef}, DefWithBody, Either, HasSource, Name, Path, Resolver, Source, }; diff --git a/crates/ra_hir/src/expr/lower.rs b/crates/ra_hir/src/expr/lower.rs index 50ea429ea1e6..6463dd65e1ed 100644 --- a/crates/ra_hir/src/expr/lower.rs +++ b/crates/ra_hir/src/expr/lower.rs @@ -1,5 +1,10 @@ //! FIXME: write short doc here +use hir_def::{path::GenericArgs, type_ref::TypeRef}; +use hir_expand::{ + hygiene::Hygiene, + name::{self, AsName, Name}, +}; use ra_arena::Arena; use ra_syntax::{ ast::{ @@ -12,11 +17,8 @@ use test_utils::tested_by; use crate::{ db::HirDatabase, - name::{AsName, Name, SELF_PARAM}, - path::GenericArgs, ty::primitive::{FloatTy, IntTy, UncertainFloatTy, UncertainIntTy}, - type_ref::TypeRef, - DefWithBody, Either, HirFileId, MacroCallLoc, MacroFileKind, Mutability, Path, Resolver, + AstId, DefWithBody, Either, HirFileId, MacroCallLoc, MacroFileKind, Mutability, Path, Resolver, Source, }; @@ -78,7 +80,7 @@ where let ptr = AstPtr::new(&self_param); let param_pat = self.alloc_pat( Pat::Bind { - name: SELF_PARAM, + name: name::SELF_PARAM, mode: BindingAnnotation::Unannotated, subpat: None, }, @@ -458,15 +460,14 @@ where ast::Expr::Label(_e) => self.alloc_expr(Expr::Missing, syntax_ptr), ast::Expr::RangeExpr(_e) => self.alloc_expr(Expr::Missing, syntax_ptr), ast::Expr::MacroCall(e) => { - let ast_id = self - .db - .ast_id_map(self.current_file_id) - .ast_id(&e) - .with_file_id(self.current_file_id); + let ast_id = AstId::new( + self.current_file_id, + self.db.ast_id_map(self.current_file_id).ast_id(&e), + ); if let Some(path) = e.path().and_then(|path| self.parse_path(path)) { if let Some(def) = self.resolver.resolve_path_as_macro(self.db, &path) { - let call_id = MacroCallLoc { def: def.id, ast_id }.id(self.db); + let call_id = self.db.intern_macro(MacroCallLoc { def: def.id, ast_id }); let file_id = call_id.as_file(MacroFileKind::Expr); if let Some(node) = self.db.parse_or_expand(file_id) { if let Some(expr) = ast::Expr::cast(node) { @@ -596,7 +597,8 @@ where } fn parse_path(&mut self, path: ast::Path) -> Option { - Path::from_src(Source { ast: path, file_id: self.current_file_id }, self.db) + let hygiene = Hygiene::new(self.db, self.current_file_id); + Path::from_src(path, &hygiene) } } diff --git a/crates/ra_hir/src/expr/validation.rs b/crates/ra_hir/src/expr/validation.rs index 1aa853c3e96c..c685edda193f 100644 --- a/crates/ra_hir/src/expr/validation.rs +++ b/crates/ra_hir/src/expr/validation.rs @@ -2,6 +2,7 @@ use std::sync::Arc; +use hir_def::path::known; use ra_syntax::ast; use rustc_hash::FxHashSet; @@ -9,7 +10,6 @@ use crate::{ db::HirDatabase, diagnostics::{DiagnosticSink, MissingFields, MissingOkInTailExpr}, expr::AstPtr, - path::known, ty::{ApplicationTy, InferenceResult, Ty, TypeCtor}, Adt, Function, Name, Path, }; diff --git a/crates/ra_hir/src/from_source.rs b/crates/ra_hir/src/from_source.rs index f80d8eb5f1ed..a9de0145538b 100644 --- a/crates/ra_hir/src/from_source.rs +++ b/crates/ra_hir/src/from_source.rs @@ -1,17 +1,12 @@ //! FIXME: write short doc here -use ra_db::{FileId, FilePosition}; -use ra_syntax::{ - algo::find_node_at_offset, - ast::{self, AstNode, NameOwner}, - SyntaxNode, -}; +use hir_expand::name::AsName; +use ra_syntax::ast::{self, AstNode, NameOwner}; use crate::{ db::{AstDatabase, DefDatabase, HirDatabase}, ids::{AstItemDef, LocationCtx}, - name::AsName, - Const, Crate, Enum, EnumVariant, FieldSource, Function, HasSource, ImplBlock, Module, + AstId, Const, Crate, Enum, EnumVariant, FieldSource, Function, HasSource, ImplBlock, Module, ModuleSource, Source, Static, Struct, StructField, Trait, TypeAlias, Union, VariantDef, }; @@ -129,41 +124,6 @@ impl FromSource for StructField { } } -// FIXME: simplify it -impl ModuleSource { - pub fn from_position( - db: &(impl DefDatabase + AstDatabase), - position: FilePosition, - ) -> ModuleSource { - let parse = db.parse(position.file_id); - match &find_node_at_offset::(parse.tree().syntax(), position.offset) { - Some(m) if !m.has_semi() => ModuleSource::Module(m.clone()), - _ => { - let source_file = parse.tree(); - ModuleSource::SourceFile(source_file) - } - } - } - - pub fn from_child_node( - db: &(impl DefDatabase + AstDatabase), - file_id: FileId, - child: &SyntaxNode, - ) -> ModuleSource { - if let Some(m) = child.ancestors().filter_map(ast::Module::cast).find(|it| !it.has_semi()) { - ModuleSource::Module(m) - } else { - let source_file = db.parse(file_id).tree(); - ModuleSource::SourceFile(source_file) - } - } - - pub fn from_file_id(db: &(impl DefDatabase + AstDatabase), file_id: FileId) -> ModuleSource { - let source_file = db.parse(file_id).tree(); - ModuleSource::SourceFile(source_file) - } -} - impl Module { pub fn from_declaration(db: &impl HirDatabase, src: Source) -> Option { let src_parent = Source { @@ -183,7 +143,7 @@ impl Module { ModuleSource::Module(ref module) => { assert!(!module.has_semi()); let ast_id_map = db.ast_id_map(src.file_id); - let item_id = ast_id_map.ast_id(module).with_file_id(src.file_id); + let item_id = AstId::new(src.file_id, ast_id_map.ast_id(module)); Some(item_id) } ModuleSource::SourceFile(_) => None, @@ -195,7 +155,7 @@ impl Module { .find_map(|krate| { let def_map = db.crate_def_map(krate); let module_id = def_map.find_module_by_source(src.file_id, decl_id)?; - Some(Module { krate, module_id }) + Some(Module::new(krate, module_id)) }) } } @@ -208,6 +168,6 @@ where let module_src = crate::ModuleSource::from_child_node(db, src.file_id.original_file(db), &src.ast.syntax()); let module = Module::from_definition(db, Source { file_id: src.file_id, ast: module_src })?; - let ctx = LocationCtx::new(db, module, src.file_id); + let ctx = LocationCtx::new(db, module.id, src.file_id); Some(DEF::from_ast(ctx, &src.ast)) } diff --git a/crates/ra_hir/src/generics.rs b/crates/ra_hir/src/generics.rs index 4ce7551c3354..52e1fbf2946b 100644 --- a/crates/ra_hir/src/generics.rs +++ b/crates/ra_hir/src/generics.rs @@ -5,15 +5,17 @@ use std::sync::Arc; +use hir_def::{ + path::Path, + type_ref::{TypeBound, TypeRef}, +}; +use hir_expand::name::{self, AsName}; use ra_syntax::ast::{self, DefaultTypeParamOwner, NameOwner, TypeBoundsOwner, TypeParamsOwner}; use crate::{ db::{AstDatabase, DefDatabase, HirDatabase}, - name::SELF_TYPE, - path::Path, - type_ref::{TypeBound, TypeRef}, - Adt, AsName, Const, Container, Enum, EnumVariant, Function, HasSource, ImplBlock, Name, Struct, - Trait, TypeAlias, Union, + Adt, Const, Container, Enum, EnumVariant, Function, HasSource, ImplBlock, Name, Struct, Trait, + TypeAlias, Union, }; /// Data about a generic parameter (to a function, struct, impl, ...). @@ -94,11 +96,15 @@ impl GenericParams { GenericDef::Adt(Adt::Enum(it)) => generics.fill(&it.source(db).ast, start), GenericDef::Trait(it) => { // traits get the Self type as an implicit first type parameter - generics.params.push(GenericParam { idx: start, name: SELF_TYPE, default: None }); + generics.params.push(GenericParam { + idx: start, + name: name::SELF_TYPE, + default: None, + }); generics.fill(&it.source(db).ast, start + 1); // add super traits as bounds on Self // i.e., trait Foo: Bar is equivalent to trait Foo where Self: Bar - let self_param = TypeRef::Path(SELF_TYPE.into()); + let self_param = TypeRef::Path(name::SELF_TYPE.into()); generics.fill_bounds(&it.source(db).ast, self_param); } GenericDef::TypeAlias(it) => generics.fill(&it.source(db).ast, start), diff --git a/crates/ra_hir/src/ids.rs b/crates/ra_hir/src/ids.rs index 499dcafea2eb..fe083c0c65f6 100644 --- a/crates/ra_hir/src/ids.rs +++ b/crates/ra_hir/src/ids.rs @@ -1,168 +1,17 @@ -//! FIXME: write short doc here +//! hir makes heavy use of ids: integer (u32) handlers to various things. You +//! can think of id as a pointer (but without a lifetime) or a file descriptor +//! (but for hir objects). +//! +//! This module defines a bunch of ids we are using. The most important ones are +//! probably `HirFileId` and `DefId`. -use std::{ - hash::{Hash, Hasher}, - sync::Arc, +use ra_db::salsa; + +pub use hir_def::{ + AstItemDef, ConstId, EnumId, FunctionId, ItemLoc, LocationCtx, StaticId, StructId, TraitId, + TypeAliasId, }; - -use mbe::MacroRules; -use ra_db::{salsa, FileId}; -use ra_prof::profile; -use ra_syntax::{ast, AstNode, Parse, SyntaxNode}; - -use crate::{ - db::{AstDatabase, DefDatabase, InternDatabase}, - AstId, Crate, FileAstId, Module, Source, -}; - -/// hir makes heavy use of ids: integer (u32) handlers to various things. You -/// can think of id as a pointer (but without a lifetime) or a file descriptor -/// (but for hir objects). -/// -/// This module defines a bunch of ids we are using. The most important ones are -/// probably `HirFileId` and `DefId`. - -/// Input to the analyzer is a set of files, where each file is identified by -/// `FileId` and contains source code. However, another source of source code in -/// Rust are macros: each macro can be thought of as producing a "temporary -/// file". To assign an id to such a file, we use the id of the macro call that -/// produced the file. So, a `HirFileId` is either a `FileId` (source code -/// written by user), or a `MacroCallId` (source code produced by macro). -/// -/// What is a `MacroCallId`? Simplifying, it's a `HirFileId` of a file -/// containing the call plus the offset of the macro call in the file. Note that -/// this is a recursive definition! However, the size_of of `HirFileId` is -/// finite (because everything bottoms out at the real `FileId`) and small -/// (`MacroCallId` uses the location interner). -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct HirFileId(HirFileIdRepr); - -impl HirFileId { - /// For macro-expansion files, returns the file original source file the - /// expansion originated from. - pub fn original_file(self, db: &impl InternDatabase) -> FileId { - match self.0 { - HirFileIdRepr::File(file_id) => file_id, - HirFileIdRepr::Macro(macro_file) => { - let loc = macro_file.macro_call_id.loc(db); - loc.ast_id.file_id().original_file(db) - } - } - } - - /// Get the crate which the macro lives in, if it is a macro file. - pub(crate) fn macro_crate(self, db: &impl AstDatabase) -> Option { - match self.0 { - HirFileIdRepr::File(_) => None, - HirFileIdRepr::Macro(macro_file) => { - let loc = macro_file.macro_call_id.loc(db); - Some(loc.def.krate) - } - } - } - - pub(crate) fn parse_or_expand_query( - db: &impl AstDatabase, - file_id: HirFileId, - ) -> Option { - match file_id.0 { - HirFileIdRepr::File(file_id) => Some(db.parse(file_id).tree().syntax().clone()), - HirFileIdRepr::Macro(macro_file) => { - db.parse_macro(macro_file).map(|it| it.syntax_node()) - } - } - } - - pub(crate) fn parse_macro_query( - db: &impl AstDatabase, - macro_file: MacroFile, - ) -> Option> { - let _p = profile("parse_macro_query"); - let macro_call_id = macro_file.macro_call_id; - let tt = db - .macro_expand(macro_call_id) - .map_err(|err| { - // Note: - // The final goal we would like to make all parse_macro success, - // such that the following log will not call anyway. - log::warn!("fail on macro_parse: (reason: {})", err,); - }) - .ok()?; - match macro_file.macro_file_kind { - MacroFileKind::Items => mbe::token_tree_to_items(&tt).ok().map(Parse::to_syntax), - MacroFileKind::Expr => mbe::token_tree_to_expr(&tt).ok().map(Parse::to_syntax), - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -enum HirFileIdRepr { - File(FileId), - Macro(MacroFile), -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct MacroFile { - macro_call_id: MacroCallId, - macro_file_kind: MacroFileKind, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub(crate) enum MacroFileKind { - Items, - Expr, -} - -impl From for HirFileId { - fn from(file_id: FileId) -> HirFileId { - HirFileId(HirFileIdRepr::File(file_id)) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct MacroDefId { - pub(crate) ast_id: AstId, - pub(crate) krate: Crate, -} - -pub(crate) fn macro_def_query(db: &impl AstDatabase, id: MacroDefId) -> Option> { - let macro_call = id.ast_id.to_node(db); - let arg = macro_call.token_tree()?; - let (tt, _) = mbe::ast_to_token_tree(&arg).or_else(|| { - log::warn!("fail on macro_def to token tree: {:#?}", arg); - None - })?; - let rules = MacroRules::parse(&tt).ok().or_else(|| { - log::warn!("fail on macro_def parse: {:#?}", tt); - None - })?; - Some(Arc::new(rules)) -} - -pub(crate) fn macro_arg_query(db: &impl AstDatabase, id: MacroCallId) -> Option> { - let loc = id.loc(db); - let macro_call = loc.ast_id.to_node(db); - let arg = macro_call.token_tree()?; - let (tt, _) = mbe::ast_to_token_tree(&arg)?; - Some(Arc::new(tt)) -} - -pub(crate) fn macro_expand_query( - db: &impl AstDatabase, - id: MacroCallId, -) -> Result, String> { - let loc = id.loc(db); - let macro_arg = db.macro_arg(id).ok_or("Fail to args in to tt::TokenTree")?; - - let macro_rules = db.macro_def(loc.def).ok_or("Fail to find macro definition")?; - let tt = macro_rules.expand(¯o_arg).map_err(|err| format!("{:?}", err))?; - // Set a hard limit for the expanded tt - let count = tt.count(); - if count > 65536 { - return Err(format!("Total tokens count exceed limit : count = {}", count)); - } - Ok(Arc::new(tt)) -} +pub use hir_expand::{HirFileId, MacroCallId, MacroCallLoc, MacroDefId, MacroFile, MacroFileKind}; macro_rules! impl_intern_key { ($name:ident) => { @@ -177,192 +26,6 @@ macro_rules! impl_intern_key { }; } -/// `MacroCallId` identifies a particular macro invocation, like -/// `println!("Hello, {}", world)`. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct MacroCallId(salsa::InternId); -impl_intern_key!(MacroCallId); - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct MacroCallLoc { - pub(crate) def: MacroDefId, - pub(crate) ast_id: AstId, -} - -impl MacroCallId { - pub(crate) fn loc(self, db: &impl InternDatabase) -> MacroCallLoc { - db.lookup_intern_macro(self) - } - - pub(crate) fn as_file(self, kind: MacroFileKind) -> HirFileId { - let macro_file = MacroFile { macro_call_id: self, macro_file_kind: kind }; - HirFileId(HirFileIdRepr::Macro(macro_file)) - } -} - -impl MacroCallLoc { - pub(crate) fn id(self, db: &impl InternDatabase) -> MacroCallId { - db.intern_macro(self) - } -} - -#[derive(Debug)] -pub struct ItemLoc { - pub(crate) module: Module, - ast_id: AstId, -} - -impl PartialEq for ItemLoc { - fn eq(&self, other: &Self) -> bool { - self.module == other.module && self.ast_id == other.ast_id - } -} -impl Eq for ItemLoc {} -impl Hash for ItemLoc { - fn hash(&self, hasher: &mut H) { - self.module.hash(hasher); - self.ast_id.hash(hasher); - } -} - -impl Clone for ItemLoc { - fn clone(&self) -> ItemLoc { - ItemLoc { module: self.module, ast_id: self.ast_id } - } -} - -#[derive(Clone, Copy)] -pub(crate) struct LocationCtx { - db: DB, - module: Module, - file_id: HirFileId, -} - -impl<'a, DB: DefDatabase> LocationCtx<&'a DB> { - pub(crate) fn new(db: &'a DB, module: Module, file_id: HirFileId) -> LocationCtx<&'a DB> { - LocationCtx { db, module, file_id } - } -} - -impl<'a, DB: DefDatabase + AstDatabase> LocationCtx<&'a DB> { - pub(crate) fn to_def(self, ast: &N) -> DEF - where - N: AstNode, - DEF: AstItemDef, - { - DEF::from_ast(self, ast) - } -} - -pub(crate) trait AstItemDef: salsa::InternKey + Clone { - fn intern(db: &impl DefDatabase, loc: ItemLoc) -> Self; - fn lookup_intern(self, db: &impl DefDatabase) -> ItemLoc; - - fn from_ast(ctx: LocationCtx<&(impl AstDatabase + DefDatabase)>, ast: &N) -> Self { - let items = ctx.db.ast_id_map(ctx.file_id); - let item_id = items.ast_id(ast); - Self::from_ast_id(ctx, item_id) - } - fn from_ast_id(ctx: LocationCtx<&impl DefDatabase>, ast_id: FileAstId) -> Self { - let loc = ItemLoc { module: ctx.module, ast_id: ast_id.with_file_id(ctx.file_id) }; - Self::intern(ctx.db, loc) - } - fn source(self, db: &(impl AstDatabase + DefDatabase)) -> Source { - let loc = self.lookup_intern(db); - let ast = loc.ast_id.to_node(db); - Source { file_id: loc.ast_id.file_id(), ast } - } - fn module(self, db: &impl DefDatabase) -> Module { - let loc = self.lookup_intern(db); - loc.module - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct FunctionId(salsa::InternId); -impl_intern_key!(FunctionId); - -impl AstItemDef for FunctionId { - fn intern(db: &impl DefDatabase, loc: ItemLoc) -> Self { - db.intern_function(loc) - } - fn lookup_intern(self, db: &impl DefDatabase) -> ItemLoc { - db.lookup_intern_function(self) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct StructId(salsa::InternId); -impl_intern_key!(StructId); -impl AstItemDef for StructId { - fn intern(db: &impl DefDatabase, loc: ItemLoc) -> Self { - db.intern_struct(loc) - } - fn lookup_intern(self, db: &impl DefDatabase) -> ItemLoc { - db.lookup_intern_struct(self) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct EnumId(salsa::InternId); -impl_intern_key!(EnumId); -impl AstItemDef for EnumId { - fn intern(db: &impl DefDatabase, loc: ItemLoc) -> Self { - db.intern_enum(loc) - } - fn lookup_intern(self, db: &impl DefDatabase) -> ItemLoc { - db.lookup_intern_enum(self) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct ConstId(salsa::InternId); -impl_intern_key!(ConstId); -impl AstItemDef for ConstId { - fn intern(db: &impl DefDatabase, loc: ItemLoc) -> Self { - db.intern_const(loc) - } - fn lookup_intern(self, db: &impl DefDatabase) -> ItemLoc { - db.lookup_intern_const(self) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct StaticId(salsa::InternId); -impl_intern_key!(StaticId); -impl AstItemDef for StaticId { - fn intern(db: &impl DefDatabase, loc: ItemLoc) -> Self { - db.intern_static(loc) - } - fn lookup_intern(self, db: &impl DefDatabase) -> ItemLoc { - db.lookup_intern_static(self) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct TraitId(salsa::InternId); -impl_intern_key!(TraitId); -impl AstItemDef for TraitId { - fn intern(db: &impl DefDatabase, loc: ItemLoc) -> Self { - db.intern_trait(loc) - } - fn lookup_intern(self, db: &impl DefDatabase) -> ItemLoc { - db.lookup_intern_trait(self) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct TypeAliasId(salsa::InternId); -impl_intern_key!(TypeAliasId); -impl AstItemDef for TypeAliasId { - fn intern(db: &impl DefDatabase, loc: ItemLoc) -> Self { - db.intern_type_alias(loc) - } - fn lookup_intern(self, db: &impl DefDatabase) -> ItemLoc { - db.lookup_intern_type_alias(self) - } -} - /// This exists just for Chalk, because Chalk just has a single `StructId` where /// we have different kinds of ADTs, primitive types and special type /// constructors like tuples and function pointers. diff --git a/crates/ra_hir/src/impl_block.rs b/crates/ra_hir/src/impl_block.rs index 33ef875636d0..b1a0140745c4 100644 --- a/crates/ra_hir/src/impl_block.rs +++ b/crates/ra_hir/src/impl_block.rs @@ -3,6 +3,8 @@ use rustc_hash::FxHashMap; use std::sync::Arc; +use hir_def::{attr::Attr, type_ref::TypeRef}; +use hir_expand::hygiene::Hygiene; use ra_arena::{impl_arena_id, map::ArenaMap, Arena, RawId}; use ra_cfg::CfgOptions; use ra_syntax::{ @@ -11,7 +13,6 @@ use ra_syntax::{ }; use crate::{ - attr::Attr, code_model::{Module, ModuleSource}, db::{AstDatabase, DefDatabase, HirDatabase}, generics::HasGenericParams, @@ -19,8 +20,7 @@ use crate::{ ids::MacroCallLoc, resolve::Resolver, ty::Ty, - type_ref::TypeRef, - AssocItem, Const, Function, HasSource, HirFileId, MacroFileKind, Path, Source, TraitRef, + AssocItem, AstId, Const, Function, HasSource, HirFileId, MacroFileKind, Path, Source, TraitRef, TypeAlias, }; @@ -129,7 +129,7 @@ impl ImplData { ) -> Self { let target_trait = node.target_trait().map(TypeRef::from_ast); let target_type = TypeRef::from_ast_opt(node.target_type()); - let ctx = LocationCtx::new(db, module, file_id); + let ctx = LocationCtx::new(db, module.id, file_id); let negative = node.is_negative(); let items = if let Some(item_list) = node.item_list() { item_list @@ -182,7 +182,7 @@ impl ModuleImplBlocks { ) -> (Arc, Arc) { let mut source_map = ImplSourceMap::default(); let crate_graph = db.crate_graph(); - let cfg_options = crate_graph.cfg_options(module.krate.crate_id()); + let cfg_options = crate_graph.cfg_options(module.id.krate); let result = ModuleImplBlocks::collect(db, cfg_options, module, &mut source_map); (Arc::new(result), Arc::new(source_map)) @@ -228,10 +228,11 @@ impl ModuleImplBlocks { owner: &dyn ast::ModuleItemOwner, file_id: HirFileId, ) { + let hygiene = Hygiene::new(db, file_id); for item in owner.items_with_macros() { match item { ast::ItemOrMacro::Item(ast::ModuleItem::ImplBlock(impl_block_ast)) => { - let attrs = Attr::from_attrs_owner(file_id, &impl_block_ast, db); + let attrs = Attr::from_attrs_owner(&impl_block_ast, &hygiene); if attrs.map_or(false, |attrs| { attrs.iter().any(|attr| attr.is_cfg_enabled(cfg_options) == Some(false)) }) { @@ -248,7 +249,7 @@ impl ModuleImplBlocks { } ast::ItemOrMacro::Item(_) => (), ast::ItemOrMacro::Macro(macro_call) => { - let attrs = Attr::from_attrs_owner(file_id, ¯o_call, db); + let attrs = Attr::from_attrs_owner(¯o_call, &hygiene); if attrs.map_or(false, |attrs| { attrs.iter().any(|attr| attr.is_cfg_enabled(cfg_options) == Some(false)) }) { @@ -256,14 +257,13 @@ impl ModuleImplBlocks { } //FIXME: we should really cut down on the boilerplate required to process a macro - let ast_id = db.ast_id_map(file_id).ast_id(¯o_call).with_file_id(file_id); - if let Some(path) = macro_call - .path() - .and_then(|path| Path::from_src(Source { ast: path, file_id }, db)) + let ast_id = AstId::new(file_id, db.ast_id_map(file_id).ast_id(¯o_call)); + if let Some(path) = + macro_call.path().and_then(|path| Path::from_src(path, &hygiene)) { if let Some(def) = self.module.resolver(db).resolve_path_as_macro(db, &path) { - let call_id = MacroCallLoc { def: def.id, ast_id }.id(db); + let call_id = db.intern_macro(MacroCallLoc { def: def.id, ast_id }); let file_id = call_id.as_file(MacroFileKind::Items); if let Some(item_list) = db.parse_or_expand(file_id).and_then(ast::MacroItems::cast) diff --git a/crates/ra_hir/src/lang_item.rs b/crates/ra_hir/src/lang_item.rs index 6c4e8ffbd3bd..e1780ed388f0 100644 --- a/crates/ra_hir/src/lang_item.rs +++ b/crates/ra_hir/src/lang_item.rs @@ -22,14 +22,14 @@ pub enum LangItemTarget { impl LangItemTarget { pub(crate) fn krate(&self, db: &impl HirDatabase) -> Option { - match self { - LangItemTarget::Enum(e) => e.module(db).krate(db), - LangItemTarget::Function(f) => f.module(db).krate(db), - LangItemTarget::ImplBlock(i) => i.module().krate(db), - LangItemTarget::Static(s) => s.module(db).krate(db), - LangItemTarget::Struct(s) => s.module(db).krate(db), - LangItemTarget::Trait(t) => t.module(db).krate(db), - } + Some(match self { + LangItemTarget::Enum(e) => e.module(db).krate(), + LangItemTarget::Function(f) => f.module(db).krate(), + LangItemTarget::ImplBlock(i) => i.module().krate(), + LangItemTarget::Static(s) => s.module(db).krate(), + LangItemTarget::Struct(s) => s.module(db).krate(), + LangItemTarget::Trait(t) => t.module(db).krate(), + }) } } diff --git a/crates/ra_hir/src/lib.rs b/crates/ra_hir/src/lib.rs index ca261e8f541a..40f5562b406a 100644 --- a/crates/ra_hir/src/lib.rs +++ b/crates/ra_hir/src/lib.rs @@ -26,25 +26,19 @@ macro_rules! impl_froms { } } -mod either; pub mod debug; pub mod db; #[macro_use] pub mod mock; -mod path; pub mod source_binder; -mod source_id; mod ids; -mod name; mod nameres; mod adt; mod traits; mod type_alias; -mod type_ref; mod ty; -mod attr; mod impl_block; mod expr; mod lang_item; @@ -60,37 +54,35 @@ pub mod from_source; #[cfg(test)] mod marks; -use crate::{ - ids::MacroFileKind, - name::AsName, - resolve::Resolver, - source_id::{AstId, FileAstId}, -}; +use hir_expand::AstId; -pub use self::{ +use crate::{ids::MacroFileKind, resolve::Resolver}; + +pub use crate::{ adt::VariantDef, - either::Either, + code_model::{ + docs::{DocDef, Docs, Documentation}, + src::{HasBodySource, HasSource, Source}, + Adt, AssocItem, Const, ConstData, Container, Crate, CrateDependency, DefWithBody, Enum, + EnumVariant, FieldSource, FnData, Function, HasBody, MacroDef, Module, ModuleDef, + ModuleSource, Static, Struct, StructField, Trait, TypeAlias, Union, + }, expr::ExprScopes, from_source::FromSource, generics::{GenericDef, GenericParam, GenericParams, HasGenericParams}, ids::{HirFileId, MacroCallId, MacroCallLoc, MacroDefId, MacroFile}, impl_block::ImplBlock, - name::Name, nameres::{ImportId, Namespace, PerNs}, - path::{Path, PathKind}, resolve::ScopeDef, source_binder::{PathResolution, ScopeEntryWithSyntax, SourceAnalyzer}, - source_id::{AstIdMap, ErasedFileAstId}, ty::{ display::HirDisplay, ApplicationTy, CallableDef, Substs, TraitRef, Ty, TypeCtor, TypeWalk, }, - type_ref::Mutability, }; -pub use self::code_model::{ - docs::{DocDef, Docs, Documentation}, - src::{HasBodySource, HasSource, Source}, - Adt, AssocItem, BuiltinType, Const, ConstData, Container, Crate, CrateDependency, DefWithBody, - Enum, EnumVariant, FieldSource, FnData, Function, HasBody, MacroDef, Module, ModuleDef, - ModuleSource, Static, Struct, StructField, Trait, TypeAlias, Union, +pub use hir_def::{ + builtin_type::BuiltinType, + path::{Path, PathKind}, + type_ref::Mutability, }; +pub use hir_expand::{either::Either, name::Name}; diff --git a/crates/ra_hir/src/marks.rs b/crates/ra_hir/src/marks.rs index 79af24b201a1..b423489a1117 100644 --- a/crates/ra_hir/src/marks.rs +++ b/crates/ra_hir/src/marks.rs @@ -2,6 +2,7 @@ test_utils::marks!( bogus_paths + // FIXME: restore this mark once hir is split name_res_works_for_broken_modules can_import_enum_variant type_var_cycles_resolve_completely diff --git a/crates/ra_hir/src/mock.rs b/crates/ra_hir/src/mock.rs index 0b278deb3ae3..35dfaf3bad0e 100644 --- a/crates/ra_hir/src/mock.rs +++ b/crates/ra_hir/src/mock.rs @@ -22,6 +22,7 @@ pub const WORKSPACE: SourceRootId = SourceRootId(0); db::InternDatabaseStorage, db::AstDatabaseStorage, db::DefDatabaseStorage, + db::DefDatabase2Storage, db::HirDatabaseStorage )] #[derive(Debug)] diff --git a/crates/ra_hir/src/nameres.rs b/crates/ra_hir/src/nameres.rs index 67adcfa2882f..7ba03182718c 100644 --- a/crates/ra_hir/src/nameres.rs +++ b/crates/ra_hir/src/nameres.rs @@ -48,16 +48,15 @@ //! on the result mod per_ns; -mod raw; mod collector; -mod mod_resolution; #[cfg(test)] mod tests; use std::sync::Arc; +use hir_def::{builtin_type::BuiltinType, CrateModuleId}; use once_cell::sync::Lazy; -use ra_arena::{impl_arena_id, Arena, RawId}; +use ra_arena::Arena; use ra_db::{Edition, FileId}; use ra_prof::profile; use ra_syntax::ast; @@ -69,16 +68,12 @@ use crate::{ diagnostics::DiagnosticSink, ids::MacroDefId, nameres::diagnostics::DefDiagnostic, - Adt, AstId, BuiltinType, Crate, HirFileId, MacroDef, Module, ModuleDef, Name, Path, PathKind, - Trait, + Adt, AstId, Crate, HirFileId, MacroDef, Module, ModuleDef, Name, Path, PathKind, Trait, }; -pub(crate) use self::raw::{ImportSourceMap, RawItems}; +pub use self::per_ns::{Namespace, PerNs}; -pub use self::{ - per_ns::{Namespace, PerNs}, - raw::ImportId, -}; +pub use hir_def::nameres::raw::ImportId; /// Contains all top-level defs from a macro-expanded crate #[derive(Debug, PartialEq, Eq)] @@ -115,13 +110,8 @@ impl std::ops::Index for CrateDefMap { } } -/// An ID of a module, **local** to a specific crate -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub(crate) struct CrateModuleId(RawId); -impl_arena_id!(CrateModuleId); - #[derive(Default, Debug, PartialEq, Eq)] -pub(crate) struct ModuleData { +pub struct ModuleData { pub(crate) parent: Option, pub(crate) children: FxHashMap, pub(crate) scope: ModuleScope, @@ -332,10 +322,11 @@ impl CrateDefMap { ) -> ResolvePathResult { let mut segments = path.segments.iter().enumerate(); let mut curr_per_ns: PerNs = match path.kind { - PathKind::DollarCrate(krate) => { + PathKind::DollarCrate(crate_id) => { + let krate = Crate { crate_id }; if krate == self.krate { tested_by!(macro_dollar_crate_self); - PerNs::types(Module { krate: self.krate, module_id: self.root }.into()) + PerNs::types(Module::new(self.krate, self.root).into()) } else { match krate.root_module(db) { Some(module) => { @@ -346,12 +337,8 @@ impl CrateDefMap { } } } - PathKind::Crate => { - PerNs::types(Module { krate: self.krate, module_id: self.root }.into()) - } - PathKind::Self_ => { - PerNs::types(Module { krate: self.krate, module_id: original_module }.into()) - } + PathKind::Crate => PerNs::types(Module::new(self.krate, self.root).into()), + PathKind::Self_ => PerNs::types(Module::new(self.krate, original_module).into()), // plain import or absolute path in 2015: crate-relative with // fallback to extern prelude (with the simplification in // rust-lang/rust#57745) @@ -377,7 +364,7 @@ impl CrateDefMap { } PathKind::Super => { if let Some(p) = self.modules[original_module].parent { - PerNs::types(Module { krate: self.krate, module_id: p }.into()) + PerNs::types(Module::new(self.krate, p).into()) } else { log::debug!("super path in root module"); return ResolvePathResult::empty(ReachedFixedPoint::Yes); @@ -419,12 +406,12 @@ impl CrateDefMap { curr_per_ns = match curr { ModuleDef::Module(module) => { - if module.krate != self.krate { + if module.krate() != self.krate { let path = Path { segments: path.segments[i..].to_vec(), kind: PathKind::Self_ }; log::debug!("resolving {:?} in other crate", path); - let defp_map = db.crate_def_map(module.krate); - let (def, s) = defp_map.resolve_path(db, module.module_id, &path); + let defp_map = db.crate_def_map(module.krate()); + let (def, s) = defp_map.resolve_path(db, module.id.module_id, &path); return ResolvePathResult::with( def, ReachedFixedPoint::Yes, @@ -433,7 +420,7 @@ impl CrateDefMap { } // Since it is a qualified path here, it should not contains legacy macros - match self[module.module_id].scope.get(&segment.name) { + match self[module.id.module_id].scope.get(&segment.name) { Some(res) => res.def, _ => { log::debug!("path segment {:?} not found", segment.name); @@ -511,14 +498,14 @@ impl CrateDefMap { fn resolve_in_prelude(&self, db: &impl DefDatabase, name: &Name) -> PerNs { if let Some(prelude) = self.prelude { let keep; - let def_map = if prelude.krate == self.krate { + let def_map = if prelude.krate() == self.krate { self } else { // Extend lifetime - keep = db.crate_def_map(prelude.krate); + keep = db.crate_def_map(prelude.krate()); &keep }; - def_map[prelude.module_id].scope.get(name).map_or_else(PerNs::none, |res| res.def) + def_map[prelude.id.module_id].scope.get(name).map_or_else(PerNs::none, |res| res.def) } else { PerNs::none() } diff --git a/crates/ra_hir/src/nameres/collector.rs b/crates/ra_hir/src/nameres/collector.rs index b5fe16bfa4d4..ee0a4c99fe00 100644 --- a/crates/ra_hir/src/nameres/collector.rs +++ b/crates/ra_hir/src/nameres/collector.rs @@ -1,5 +1,10 @@ //! FIXME: write short doc here +use hir_def::{ + attr::Attr, + nameres::{mod_resolution::ModDir, raw}, +}; +use hir_expand::name; use ra_cfg::CfgOptions; use ra_db::FileId; use ra_syntax::{ast, SmolStr}; @@ -7,13 +12,11 @@ use rustc_hash::FxHashMap; use test_utils::tested_by; use crate::{ - attr::Attr, db::DefDatabase, ids::{AstItemDef, LocationCtx, MacroCallId, MacroCallLoc, MacroDefId, MacroFileKind}, - name::MACRO_RULES, nameres::{ - diagnostics::DefDiagnostic, mod_resolution::ModDir, raw, Crate, CrateDefMap, CrateModuleId, - ModuleData, ModuleDef, PerNs, ReachedFixedPoint, Resolution, ResolveMode, + diagnostics::DefDiagnostic, Crate, CrateDefMap, CrateModuleId, ModuleData, ModuleDef, + PerNs, ReachedFixedPoint, Resolution, ResolveMode, }, Adt, AstId, Const, Enum, Function, HirFileId, MacroDef, Module, Name, Path, PathKind, Static, Struct, Trait, TypeAlias, Union, @@ -212,7 +215,7 @@ where if let Some(ModuleDef::Module(m)) = res.take_types() { tested_by!(macro_rules_from_other_crates_are_visible_with_macro_use); - self.import_all_macros_exported(current_module_id, m.krate); + self.import_all_macros_exported(current_module_id, m.krate()); } } @@ -289,11 +292,11 @@ where if import.is_prelude { tested_by!(std_prelude); self.def_map.prelude = Some(m); - } else if m.krate != self.def_map.krate { + } else if m.krate() != self.def_map.krate { tested_by!(glob_across_crates); // glob import from other crate => we can just import everything once - let item_map = self.db.crate_def_map(m.krate); - let scope = &item_map[m.module_id].scope; + let item_map = self.db.crate_def_map(m.krate()); + let scope = &item_map[m.id.module_id].scope; // Module scoped macros is included let items = scope @@ -307,7 +310,7 @@ where // glob import from same crate => we do an initial // import, and then need to propagate any further // additions - let scope = &self.def_map[m.module_id].scope; + let scope = &self.def_map[m.id.module_id].scope; // Module scoped macros is included let items = scope @@ -319,7 +322,7 @@ where self.update(module_id, Some(import_id), &items); // record the glob import in case we add further items self.glob_imports - .entry(m.module_id) + .entry(m.id.module_id) .or_default() .push((module_id, import_id)); } @@ -448,7 +451,7 @@ where ); if let Some(def) = resolved_res.resolved_def.get_macros() { - let call_id = MacroCallLoc { def: def.id, ast_id: *ast_id }.id(self.db); + let call_id = self.db.intern_macro(MacroCallLoc { def: def.id, ast_id: *ast_id }); resolved.push((*module_id, call_id, def.id)); res = ReachedFixedPoint::No; return false; @@ -523,9 +526,10 @@ where // Prelude module is always considered to be `#[macro_use]`. if let Some(prelude_module) = self.def_collector.def_map.prelude { - if prelude_module.krate != self.def_collector.def_map.krate { + if prelude_module.krate() != self.def_collector.def_map.krate { tested_by!(prelude_is_macro_use); - self.def_collector.import_all_macros_exported(self.module_id, prelude_module.krate); + self.def_collector + .import_all_macros_exported(self.module_id, prelude_module.krate()); } } @@ -567,7 +571,7 @@ where // inline module, just recurse raw::ModuleData::Definition { name, items, ast_id } => { let module_id = - self.push_child_module(name.clone(), ast_id.with_file_id(self.file_id), None); + self.push_child_module(name.clone(), AstId::new(self.file_id, *ast_id), None); ModCollector { def_collector: &mut *self.def_collector, @@ -583,7 +587,7 @@ where } // out of line module, resolve, parse and recurse raw::ModuleData::Declaration { name, ast_id } => { - let ast_id = ast_id.with_file_id(self.file_id); + let ast_id = AstId::new(self.file_id, *ast_id); match self.mod_dir.resolve_declaration( self.def_collector.db, self.file_id, @@ -631,9 +635,7 @@ where modules[res].scope.legacy_macros = modules[self.module_id].scope.legacy_macros.clone(); modules[self.module_id].children.insert(name.clone(), res); let resolution = Resolution { - def: PerNs::types( - Module { krate: self.def_collector.def_map.krate, module_id: res }.into(), - ), + def: PerNs::types(Module::new(self.def_collector.def_map.krate, res).into()), import: None, }; self.def_collector.update(self.module_id, None, &[(name, resolution)]); @@ -641,8 +643,8 @@ where } fn define_def(&mut self, def: &raw::DefData) { - let module = Module { krate: self.def_collector.def_map.krate, module_id: self.module_id }; - let ctx = LocationCtx::new(self.def_collector.db, module, self.file_id); + let module = Module::new(self.def_collector.def_map.krate, self.module_id); + let ctx = LocationCtx::new(self.def_collector.db, module.id, self.file_id); macro_rules! def { ($kind:ident, $ast_id:ident) => { @@ -671,28 +673,26 @@ where } fn collect_macro(&mut self, mac: &raw::MacroData) { + let ast_id = AstId::new(self.file_id, mac.ast_id); + // Case 1: macro rules, define a macro in crate-global mutable scope if is_macro_rules(&mac.path) { if let Some(name) = &mac.name { - let macro_id = MacroDefId { - ast_id: mac.ast_id.with_file_id(self.file_id), - krate: self.def_collector.def_map.krate, - }; + let macro_id = + MacroDefId { ast_id, krate: self.def_collector.def_map.krate.crate_id }; let macro_ = MacroDef { id: macro_id }; self.def_collector.define_macro(self.module_id, name.clone(), macro_, mac.export); } return; } - let ast_id = mac.ast_id.with_file_id(self.file_id); - // Case 2: try to resolve in legacy scope and expand macro_rules, triggering // recursive item collection. if let Some(macro_def) = mac.path.as_ident().and_then(|name| { self.def_collector.def_map[self.module_id].scope.get_legacy_macro(&name) }) { let def = macro_def.id; - let macro_call_id = MacroCallLoc { def, ast_id }.id(self.def_collector.db); + let macro_call_id = self.def_collector.db.intern_macro(MacroCallLoc { def, ast_id }); self.def_collector.collect_macro_expansion(self.module_id, macro_call_id, def); return; @@ -728,7 +728,7 @@ where } fn is_macro_rules(path: &Path) -> bool { - path.as_ident() == Some(&MACRO_RULES) + path.as_ident() == Some(&name::MACRO_RULES) } #[cfg(test)] diff --git a/crates/ra_hir/src/nameres/tests/mod_resolution.rs b/crates/ra_hir/src/nameres/tests/mod_resolution.rs index f569aacdc6bd..abfe8b1c346e 100644 --- a/crates/ra_hir/src/nameres/tests/mod_resolution.rs +++ b/crates/ra_hir/src/nameres/tests/mod_resolution.rs @@ -2,7 +2,7 @@ use super::*; #[test] fn name_res_works_for_broken_modules() { - covers!(name_res_works_for_broken_modules); + // covers!(name_res_works_for_broken_modules); let map = def_map( " //- /lib.rs diff --git a/crates/ra_hir/src/resolve.rs b/crates/ra_hir/src/resolve.rs index 3c797c0c3a82..75b24d386f16 100644 --- a/crates/ra_hir/src/resolve.rs +++ b/crates/ra_hir/src/resolve.rs @@ -1,6 +1,12 @@ //! Name resolution. use std::sync::Arc; +use hir_def::{ + builtin_type::BuiltinType, + path::{Path, PathKind}, + CrateModuleId, +}; +use hir_expand::name::{self, Name}; use rustc_hash::FxHashSet; use crate::{ @@ -12,11 +18,8 @@ use crate::{ }, generics::GenericParams, impl_block::ImplBlock, - name::{Name, SELF_PARAM, SELF_TYPE}, - nameres::{CrateDefMap, CrateModuleId, PerNs}, - path::{Path, PathKind}, - Adt, BuiltinType, Const, Enum, EnumVariant, Function, MacroDef, ModuleDef, Static, Struct, - Trait, TypeAlias, + nameres::{CrateDefMap, PerNs}, + Adt, Const, Enum, EnumVariant, Function, MacroDef, ModuleDef, Static, Struct, Trait, TypeAlias, }; #[derive(Debug, Clone, Default)] @@ -149,13 +152,13 @@ impl Resolver { } } Scope::ImplBlockScope(impl_) => { - if first_name == &SELF_TYPE { + if first_name == &name::SELF_TYPE { let idx = if path.segments.len() == 1 { None } else { Some(1) }; return Some((TypeNs::SelfType(*impl_), idx)); } } Scope::AdtScope(adt) => { - if first_name == &SELF_TYPE { + if first_name == &name::SELF_TYPE { let idx = if path.segments.len() == 1 { None } else { Some(1) }; return Some((TypeNs::AdtSelfType(*adt), idx)); } @@ -204,7 +207,7 @@ impl Resolver { return None; } let n_segments = path.segments.len(); - let tmp = SELF_PARAM; + let tmp = name::SELF_PARAM; let first_name = if path.is_self() { &tmp } else { &path.segments.first()?.name }; let skip_to_mod = path.kind != PathKind::Plain && !path.is_self(); for scope in self.scopes.iter().rev() { @@ -240,13 +243,13 @@ impl Resolver { Scope::GenericParams(_) => continue, Scope::ImplBlockScope(impl_) if n_segments > 1 => { - if first_name == &SELF_TYPE { + if first_name == &name::SELF_TYPE { let ty = TypeNs::SelfType(*impl_); return Some(ResolveValueResult::Partial(ty, 1)); } } Scope::AdtScope(adt) if n_segments > 1 => { - if first_name == &SELF_TYPE { + if first_name == &name::SELF_TYPE { let ty = TypeNs::AdtSelfType(*adt); return Some(ResolveValueResult::Partial(ty, 1)); } @@ -330,8 +333,8 @@ impl Resolver { for scope in &self.scopes { if let Scope::ModuleScope(m) = scope { if let Some(prelude) = m.crate_def_map.prelude() { - let prelude_def_map = db.crate_def_map(prelude.krate); - traits.extend(prelude_def_map[prelude.module_id].scope.traits()); + let prelude_def_map = db.crate_def_map(prelude.krate()); + traits.extend(prelude_def_map[prelude.id.module_id].scope.traits()); } traits.extend(m.crate_def_map[m.module_id].scope.traits()); } @@ -444,10 +447,12 @@ impl Scope { f(name.clone(), ScopeDef::ModuleDef(*def)); }); if let Some(prelude) = m.crate_def_map.prelude() { - let prelude_def_map = db.crate_def_map(prelude.krate); - prelude_def_map[prelude.module_id].scope.entries().for_each(|(name, res)| { - f(name.clone(), res.def.into()); - }); + let prelude_def_map = db.crate_def_map(prelude.krate()); + prelude_def_map[prelude.id.module_id].scope.entries().for_each( + |(name, res)| { + f(name.clone(), res.def.into()); + }, + ); } } Scope::GenericParams(gp) => { @@ -456,10 +461,10 @@ impl Scope { } } Scope::ImplBlockScope(i) => { - f(SELF_TYPE, ScopeDef::ImplSelfType(*i)); + f(name::SELF_TYPE, ScopeDef::ImplSelfType(*i)); } Scope::AdtScope(i) => { - f(SELF_TYPE, ScopeDef::AdtSelfType(*i)); + f(name::SELF_TYPE, ScopeDef::AdtSelfType(*i)); } Scope::ExprScope(e) => { e.expr_scopes.entries(e.scope_id).iter().for_each(|e| { diff --git a/crates/ra_hir/src/source_binder.rs b/crates/ra_hir/src/source_binder.rs index a907d6a9f9db..152bc71bd287 100644 --- a/crates/ra_hir/src/source_binder.rs +++ b/crates/ra_hir/src/source_binder.rs @@ -7,10 +7,12 @@ //! purely for "IDE needs". use std::sync::Arc; +use hir_def::path::known; +use hir_expand::name::AsName; use ra_db::FileId; use ra_syntax::{ ast::{self, AstNode}, - AstPtr, + match_ast, AstPtr, SyntaxKind::*, SyntaxNode, SyntaxNodePtr, TextRange, TextUnit, }; @@ -24,11 +26,10 @@ use crate::{ BodySourceMap, }, ids::LocationCtx, - path::known, resolve::{ScopeDef, TypeNs, ValueNs}, ty::method_resolution::implements_trait, - AsName, Const, DefWithBody, Either, Enum, FromSource, Function, HasBody, HirFileId, MacroDef, - Module, Name, Path, Resolver, Static, Struct, Ty, + Const, DefWithBody, Either, Enum, FromSource, Function, HasBody, HirFileId, MacroDef, Module, + Name, Path, Resolver, Static, Struct, Ty, }; fn try_get_resolver_for_node( @@ -36,24 +37,34 @@ fn try_get_resolver_for_node( file_id: FileId, node: &SyntaxNode, ) -> Option { - if let Some(module) = ast::Module::cast(node.clone()) { - let src = crate::Source { file_id: file_id.into(), ast: module }; - Some(crate::Module::from_declaration(db, src)?.resolver(db)) - } else if let Some(file) = ast::SourceFile::cast(node.clone()) { - let src = - crate::Source { file_id: file_id.into(), ast: crate::ModuleSource::SourceFile(file) }; - Some(crate::Module::from_definition(db, src)?.resolver(db)) - } else if let Some(s) = ast::StructDef::cast(node.clone()) { - let src = crate::Source { file_id: file_id.into(), ast: s }; - Some(Struct::from_source(db, src)?.resolver(db)) - } else if let Some(e) = ast::EnumDef::cast(node.clone()) { - let src = crate::Source { file_id: file_id.into(), ast: e }; - Some(Enum::from_source(db, src)?.resolver(db)) - } else if node.kind() == FN_DEF || node.kind() == CONST_DEF || node.kind() == STATIC_DEF { - Some(def_with_body_from_child_node(db, file_id, node)?.resolver(db)) - } else { - // FIXME add missing cases - None + match_ast! { + match node { + ast::Module(it) => { + let src = crate::Source { file_id: file_id.into(), ast: it }; + Some(crate::Module::from_declaration(db, src)?.resolver(db)) + }, + ast::SourceFile(it) => { + let src = + crate::Source { file_id: file_id.into(), ast: crate::ModuleSource::SourceFile(it) }; + Some(crate::Module::from_definition(db, src)?.resolver(db)) + }, + ast::StructDef(it) => { + let src = crate::Source { file_id: file_id.into(), ast: it }; + Some(Struct::from_source(db, src)?.resolver(db)) + }, + ast::EnumDef(it) => { + let src = crate::Source { file_id: file_id.into(), ast: it }; + Some(Enum::from_source(db, src)?.resolver(db)) + }, + _ => { + if node.kind() == FN_DEF || node.kind() == CONST_DEF || node.kind() == STATIC_DEF { + Some(def_with_body_from_child_node(db, file_id, node)?.resolver(db)) + } else { + // FIXME add missing cases + None + } + }, + } } } @@ -64,19 +75,17 @@ fn def_with_body_from_child_node( ) -> Option { let src = crate::ModuleSource::from_child_node(db, file_id, node); let module = Module::from_definition(db, crate::Source { file_id: file_id.into(), ast: src })?; - let ctx = LocationCtx::new(db, module, file_id.into()); + let ctx = LocationCtx::new(db, module.id, file_id.into()); node.ancestors().find_map(|node| { - if let Some(def) = ast::FnDef::cast(node.clone()) { - return Some(Function { id: ctx.to_def(&def) }.into()); + match_ast! { + match node { + ast::FnDef(def) => { Some(Function {id: ctx.to_def(&def) }.into()) }, + ast::ConstDef(def) => { Some(Const { id: ctx.to_def(&def) }.into()) }, + ast::StaticDef(def) => { Some(Static { id: ctx.to_def(&def) }.into()) }, + _ => { None }, + } } - if let Some(def) = ast::ConstDef::cast(node.clone()) { - return Some(Const { id: ctx.to_def(&def) }.into()); - } - if let Some(def) = ast::StaticDef::cast(node) { - return Some(Static { id: ctx.to_def(&def) }.into()); - } - None }) } diff --git a/crates/ra_hir/src/traits.rs b/crates/ra_hir/src/traits.rs index e39511518d10..1a45dacba92d 100644 --- a/crates/ra_hir/src/traits.rs +++ b/crates/ra_hir/src/traits.rs @@ -1,14 +1,15 @@ //! HIR for trait definitions. -use rustc_hash::FxHashMap; use std::sync::Arc; +use hir_expand::name::AsName; + use ra_syntax::ast::{self, NameOwner}; +use rustc_hash::FxHashMap; use crate::{ db::{AstDatabase, DefDatabase}, ids::LocationCtx, - name::AsName, AssocItem, Const, Function, HasSource, Module, Name, Trait, TypeAlias, }; @@ -27,7 +28,7 @@ impl TraitData { let src = tr.source(db); let name = src.ast.name().map(|n| n.as_name()); let module = tr.module(db); - let ctx = LocationCtx::new(db, module, src.file_id); + let ctx = LocationCtx::new(db, module.id, src.file_id); let auto = src.ast.is_auto(); let items = if let Some(item_list) = src.ast.item_list() { item_list diff --git a/crates/ra_hir/src/ty.rs b/crates/ra_hir/src/ty.rs index cc9746f6d752..d2bfcdc7dbcb 100644 --- a/crates/ra_hir/src/ty.rs +++ b/crates/ra_hir/src/ty.rs @@ -17,8 +17,8 @@ use std::sync::Arc; use std::{fmt, iter, mem}; use crate::{ - db::HirDatabase, expr::ExprId, type_ref::Mutability, util::make_mut_slice, Adt, Crate, - DefWithBody, GenericParams, HasGenericParams, Name, Trait, TypeAlias, + db::HirDatabase, expr::ExprId, util::make_mut_slice, Adt, Crate, DefWithBody, GenericParams, + HasGenericParams, Mutability, Name, Trait, TypeAlias, }; use display::{HirDisplay, HirFormatter}; diff --git a/crates/ra_hir/src/ty/autoderef.rs b/crates/ra_hir/src/ty/autoderef.rs index 02492ca14507..3645ee831c91 100644 --- a/crates/ra_hir/src/ty/autoderef.rs +++ b/crates/ra_hir/src/ty/autoderef.rs @@ -5,10 +5,11 @@ use std::iter::successors; +use hir_expand::name; use log::{info, warn}; use super::{traits::Solution, Canonical, Substs, Ty, TypeWalk}; -use crate::{db::HirDatabase, name, HasGenericParams, Resolver}; +use crate::{db::HirDatabase, HasGenericParams, Resolver}; const AUTODEREF_RECURSION_LIMIT: usize = 10; diff --git a/crates/ra_hir/src/ty/infer.rs b/crates/ra_hir/src/ty/infer.rs index ebaff998ee75..6694467a36cb 100644 --- a/crates/ra_hir/src/ty/infer.rs +++ b/crates/ra_hir/src/ty/infer.rs @@ -21,6 +21,11 @@ use std::sync::Arc; use ena::unify::{InPlaceUnificationTable, NoError, UnifyKey, UnifyValue}; use rustc_hash::FxHashMap; +use hir_def::{ + path::known, + type_ref::{Mutability, TypeRef}, +}; +use hir_expand::name; use ra_arena::map::ArenaMap; use ra_prof::profile; use test_utils::tested_by; @@ -37,11 +42,8 @@ use crate::{ db::HirDatabase, diagnostics::DiagnosticSink, expr::{BindingAnnotation, Body, ExprId, PatId}, - name, - path::known, resolve::{Resolver, TypeNs}, ty::infer::diagnostics::InferenceDiagnostic, - type_ref::{Mutability, TypeRef}, Adt, AssocItem, ConstData, DefWithBody, FnData, Function, HasBody, Path, StructField, }; diff --git a/crates/ra_hir/src/ty/infer/coerce.rs b/crates/ra_hir/src/ty/infer/coerce.rs index 0429a986640e..6ea135126a5c 100644 --- a/crates/ra_hir/src/ty/infer/coerce.rs +++ b/crates/ra_hir/src/ty/infer/coerce.rs @@ -14,8 +14,7 @@ use crate::{ lang_item::LangItemTarget, resolve::Resolver, ty::{autoderef, Substs, Ty, TypeCtor, TypeWalk}, - type_ref::Mutability, - Adt, + Adt, Mutability, }; impl<'a, D: HirDatabase> InferenceContext<'a, D> { diff --git a/crates/ra_hir/src/ty/infer/expr.rs b/crates/ra_hir/src/ty/infer/expr.rs index f8807c742881..fed52df39cef 100644 --- a/crates/ra_hir/src/ty/infer/expr.rs +++ b/crates/ra_hir/src/ty/infer/expr.rs @@ -3,14 +3,15 @@ use std::iter::{repeat, repeat_with}; use std::sync::Arc; +use hir_def::path::{GenericArg, GenericArgs}; +use hir_expand::name; + use super::{BindingMode, Expectation, InferenceContext, InferenceDiagnostic, TypeMismatch}; use crate::{ db::HirDatabase, expr::{self, Array, BinaryOp, Expr, ExprId, Literal, Statement, UnaryOp}, generics::{GenericParams, HasGenericParams}, - name, nameres::Namespace, - path::{GenericArg, GenericArgs}, ty::{ autoderef, method_resolution, op, primitive, CallableDef, InferTy, Mutability, Obligation, ProjectionPredicate, ProjectionTy, Substs, TraitRef, Ty, TypeCtor, TypeWalk, diff --git a/crates/ra_hir/src/ty/infer/path.rs b/crates/ra_hir/src/ty/infer/path.rs index db979353a09a..77aa35ce1330 100644 --- a/crates/ra_hir/src/ty/infer/path.rs +++ b/crates/ra_hir/src/ty/infer/path.rs @@ -1,5 +1,7 @@ //! Path expression resolution. +use hir_def::path::PathSegment; + use super::{ExprOrPatId, InferenceContext, TraitRef}; use crate::{ db::HirDatabase, @@ -131,7 +133,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { fn resolve_trait_assoc_item( &mut self, trait_ref: TraitRef, - segment: &crate::path::PathSegment, + segment: &PathSegment, id: ExprOrPatId, ) -> Option<(ValueNs, Option)> { let trait_ = trait_ref.trait_; @@ -170,7 +172,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { fn resolve_ty_assoc_item( &mut self, ty: Ty, - segment: &crate::path::PathSegment, + segment: &PathSegment, id: ExprOrPatId, ) -> Option<(ValueNs, Option)> { if let Ty::Unknown = ty { diff --git a/crates/ra_hir/src/ty/lower.rs b/crates/ra_hir/src/ty/lower.rs index 366556134b2a..dd7cd979ff47 100644 --- a/crates/ra_hir/src/ty/lower.rs +++ b/crates/ra_hir/src/ty/lower.rs @@ -8,6 +8,12 @@ use std::iter; use std::sync::Arc; +use hir_def::{ + builtin_type::BuiltinType, + path::{GenericArg, PathSegment}, + type_ref::{TypeBound, TypeRef}, +}; + use super::{ FnSig, GenericPredicate, ProjectionPredicate, ProjectionTy, Substs, TraitRef, Ty, TypeCtor, TypeWalk, @@ -18,13 +24,14 @@ use crate::{ generics::HasGenericParams, generics::{GenericDef, WherePredicate}, nameres::Namespace, - path::{GenericArg, PathSegment}, resolve::{Resolver, TypeNs}, - ty::Adt, - type_ref::{TypeBound, TypeRef}, + ty::{ + primitive::{FloatTy, IntTy}, + Adt, + }, util::make_mut_slice, - BuiltinType, Const, Enum, EnumVariant, Function, ModuleDef, Path, Static, Struct, StructField, - Trait, TypeAlias, Union, + Const, Enum, EnumVariant, Function, ModuleDef, Path, Static, Struct, StructField, Trait, + TypeAlias, Union, }; impl Ty { @@ -640,8 +647,10 @@ fn type_for_builtin(def: BuiltinType) -> Ty { BuiltinType::Char => TypeCtor::Char, BuiltinType::Bool => TypeCtor::Bool, BuiltinType::Str => TypeCtor::Str, - BuiltinType::Int(ty) => TypeCtor::Int(ty.into()), - BuiltinType::Float(ty) => TypeCtor::Float(ty.into()), + BuiltinType::Int { signedness, bitness } => { + TypeCtor::Int(IntTy { signedness, bitness }.into()) + } + BuiltinType::Float { bitness } => TypeCtor::Float(FloatTy { bitness }.into()), }) } diff --git a/crates/ra_hir/src/ty/method_resolution.rs b/crates/ra_hir/src/ty/method_resolution.rs index ad2ab560d8e5..eb69344f6a74 100644 --- a/crates/ra_hir/src/ty/method_resolution.rs +++ b/crates/ra_hir/src/ty/method_resolution.rs @@ -5,18 +5,17 @@ use std::sync::Arc; use arrayvec::ArrayVec; +use hir_def::CrateModuleId; use rustc_hash::FxHashMap; use super::{autoderef, lower, Canonical, InEnvironment, TraitEnvironment, TraitRef}; use crate::{ db::HirDatabase, impl_block::{ImplBlock, ImplId}, - nameres::CrateModuleId, resolve::Resolver, ty::primitive::{FloatBitness, UncertainFloatTy, UncertainIntTy}, ty::{Ty, TypeCtor}, - type_ref::Mutability, - AssocItem, Crate, Function, Module, Name, Trait, + AssocItem, Crate, Function, Module, Mutability, Name, Trait, }; /// This is used as a key for indexing impls. @@ -50,7 +49,7 @@ impl CrateImplBlocks { let fingerprint = TyFingerprint::for_impl(ty); fingerprint.and_then(|f| self.impls.get(&f)).into_iter().flat_map(|i| i.iter()).map( move |(module_id, impl_id)| { - let module = Module { krate: self.krate, module_id: *module_id }; + let module = Module::new(self.krate, *module_id); ImplBlock::from_id(module, *impl_id) }, ) @@ -62,7 +61,7 @@ impl CrateImplBlocks { ) -> impl Iterator + 'a { self.impls_by_trait.get(&tr).into_iter().flat_map(|i| i.iter()).map( move |(module_id, impl_id)| { - let module = Module { krate: self.krate, module_id: *module_id }; + let module = Module::new(self.krate, *module_id); ImplBlock::from_id(module, *impl_id) }, ) @@ -71,7 +70,7 @@ impl CrateImplBlocks { pub fn all_impls<'a>(&'a self) -> impl Iterator + 'a { self.impls.values().chain(self.impls_by_trait.values()).flat_map(|i| i.iter()).map( move |(module_id, impl_id)| { - let module = Module { krate: self.krate, module_id: *module_id }; + let module = Module::new(self.krate, *module_id); ImplBlock::from_id(module, *impl_id) }, ) @@ -90,14 +89,14 @@ impl CrateImplBlocks { self.impls_by_trait .entry(tr.trait_) .or_insert_with(Vec::new) - .push((module.module_id, impl_id)); + .push((module.id.module_id, impl_id)); } } else { if let Some(target_ty_fp) = TyFingerprint::for_impl(&target_ty) { self.impls .entry(target_ty_fp) .or_insert_with(Vec::new) - .push((module.module_id, impl_id)); + .push((module.id.module_id, impl_id)); } } } diff --git a/crates/ra_hir/src/ty/primitive.rs b/crates/ra_hir/src/ty/primitive.rs index 8966f9d1d502..1749752f10e9 100644 --- a/crates/ra_hir/src/ty/primitive.rs +++ b/crates/ra_hir/src/ty/primitive.rs @@ -2,27 +2,7 @@ use std::fmt; -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum Signedness { - Signed, - Unsigned, -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum IntBitness { - Xsize, - X8, - X16, - X32, - X64, - X128, -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum FloatBitness { - X32, - X64, -} +pub use hir_def::builtin_type::{FloatBitness, IntBitness, Signedness}; #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum UncertainIntTy { diff --git a/crates/ra_hir/src/ty/traits.rs b/crates/ra_hir/src/ty/traits.rs index 0cb5c37984b5..4f1eab1508af 100644 --- a/crates/ra_hir/src/ty/traits.rs +++ b/crates/ra_hir/src/ty/traits.rs @@ -1,7 +1,7 @@ //! Trait solving using Chalk. use std::sync::{Arc, Mutex}; -use chalk_ir::cast::Cast; +use chalk_ir::{cast::Cast, family::ChalkIr}; use log::debug; use ra_db::salsa; use ra_prof::profile; @@ -33,7 +33,7 @@ impl TraitSolver { fn solve( &self, db: &impl HirDatabase, - goal: &chalk_ir::UCanonical>, + goal: &chalk_ir::UCanonical>>, ) -> Option { let context = ChalkContext { db, krate: self.krate }; debug!("solve goal: {:?}", goal); @@ -196,7 +196,7 @@ pub(crate) fn trait_solve_query( } fn solution_from_chalk(db: &impl HirDatabase, solution: chalk_solve::Solution) -> Solution { - let convert_subst = |subst: chalk_ir::Canonical| { + let convert_subst = |subst: chalk_ir::Canonical>| { let value = subst .value .parameters diff --git a/crates/ra_hir/src/ty/traits/chalk.rs b/crates/ra_hir/src/ty/traits/chalk.rs index 00aaf65d92a6..39ef9218279e 100644 --- a/crates/ra_hir/src/ty/traits/chalk.rs +++ b/crates/ra_hir/src/ty/traits/chalk.rs @@ -4,11 +4,13 @@ use std::sync::Arc; use log::debug; use chalk_ir::{ - cast::Cast, Identifier, ImplId, Parameter, PlaceholderIndex, TypeId, TypeKindId, TypeName, - UniverseIndex, + cast::Cast, family::ChalkIr, Identifier, ImplId, Parameter, PlaceholderIndex, TypeId, + TypeKindId, TypeName, UniverseIndex, }; use chalk_rust_ir::{AssociatedTyDatum, ImplDatum, StructDatum, TraitDatum}; +use hir_expand::name; + use ra_db::salsa::{InternId, InternKey}; use super::{Canonical, ChalkContext, Impl, Obligation}; @@ -38,8 +40,8 @@ where } impl ToChalk for Ty { - type Chalk = chalk_ir::Ty; - fn to_chalk(self, db: &impl HirDatabase) -> chalk_ir::Ty { + type Chalk = chalk_ir::Ty; + fn to_chalk(self, db: &impl HirDatabase) -> chalk_ir::Ty { match self { Ty::Apply(apply_ty) => { let name = match apply_ty.ctor { @@ -62,21 +64,19 @@ impl ToChalk for Ty { chalk_ir::ProjectionTy { associated_ty_id, parameters }.cast() } Ty::Param { idx, .. } => { - PlaceholderIndex { ui: UniverseIndex::ROOT, idx: idx as usize }.to_ty() + PlaceholderIndex { ui: UniverseIndex::ROOT, idx: idx as usize }.to_ty::() } Ty::Bound(idx) => chalk_ir::Ty::BoundVar(idx as usize), Ty::Infer(_infer_ty) => panic!("uncanonicalized infer ty"), - // FIXME this is clearly incorrect, but probably not too incorrect - // and I'm not sure what to actually do with Ty::Unknown - // maybe an alternative would be `for T`? (meaningless in rust, but expressible in chalk's Ty) - // - // FIXME also dyn and impl Trait are currently handled like Unknown because Chalk doesn't have them yet + // FIXME use Chalk's Dyn/Opaque once the bugs with that are fixed Ty::Unknown | Ty::Dyn(_) | Ty::Opaque(_) => { - PlaceholderIndex { ui: UniverseIndex::ROOT, idx: usize::max_value() }.to_ty() + let parameters = Vec::new(); + let name = TypeName::Error; + chalk_ir::ApplicationTy { name, parameters }.cast() } } } - fn from_chalk(db: &impl HirDatabase, chalk: chalk_ir::Ty) -> Self { + fn from_chalk(db: &impl HirDatabase, chalk: chalk_ir::Ty) -> Self { match chalk { chalk_ir::Ty::Apply(apply_ty) => { // FIXME this is kind of hacky due to the fact that @@ -92,6 +92,7 @@ impl ToChalk for Ty { let parameters = from_chalk(db, apply_ty.parameters); Ty::Apply(ApplicationTy { ctor, parameters }) } + TypeName::Error => Ty::Unknown, // FIXME handle TypeKindId::Trait/Type here TypeName::TypeKindId(_) => unimplemented!(), TypeName::Placeholder(idx) => { @@ -108,18 +109,30 @@ impl ToChalk for Ty { chalk_ir::Ty::ForAll(_) => unimplemented!(), chalk_ir::Ty::BoundVar(idx) => Ty::Bound(idx as u32), chalk_ir::Ty::InferenceVar(_iv) => Ty::Unknown, + chalk_ir::Ty::Dyn(where_clauses) => { + assert_eq!(where_clauses.binders.len(), 1); + let predicates = + where_clauses.value.into_iter().map(|c| from_chalk(db, c)).collect(); + Ty::Dyn(predicates) + } + chalk_ir::Ty::Opaque(where_clauses) => { + assert_eq!(where_clauses.binders.len(), 1); + let predicates = + where_clauses.value.into_iter().map(|c| from_chalk(db, c)).collect(); + Ty::Opaque(predicates) + } } } } impl ToChalk for Substs { - type Chalk = Vec; + type Chalk = Vec>; - fn to_chalk(self, db: &impl HirDatabase) -> Vec { + fn to_chalk(self, db: &impl HirDatabase) -> Vec> { self.iter().map(|ty| ty.clone().to_chalk(db).cast()).collect() } - fn from_chalk(db: &impl HirDatabase, parameters: Vec) -> Substs { + fn from_chalk(db: &impl HirDatabase, parameters: Vec>) -> Substs { let tys = parameters .into_iter() .map(|p| match p { @@ -132,15 +145,15 @@ impl ToChalk for Substs { } impl ToChalk for TraitRef { - type Chalk = chalk_ir::TraitRef; + type Chalk = chalk_ir::TraitRef; - fn to_chalk(self: TraitRef, db: &impl HirDatabase) -> chalk_ir::TraitRef { + fn to_chalk(self: TraitRef, db: &impl HirDatabase) -> chalk_ir::TraitRef { let trait_id = self.trait_.to_chalk(db); let parameters = self.substs.to_chalk(db); chalk_ir::TraitRef { trait_id, parameters } } - fn from_chalk(db: &impl HirDatabase, trait_ref: chalk_ir::TraitRef) -> Self { + fn from_chalk(db: &impl HirDatabase, trait_ref: chalk_ir::TraitRef) -> Self { let trait_ = from_chalk(db, trait_ref.trait_id); let substs = from_chalk(db, trait_ref.parameters); TraitRef { trait_, substs } @@ -151,11 +164,11 @@ impl ToChalk for Trait { type Chalk = chalk_ir::TraitId; fn to_chalk(self, _db: &impl HirDatabase) -> chalk_ir::TraitId { - self.id.into() + chalk_ir::TraitId(id_to_chalk(self.id)) } fn from_chalk(_db: &impl HirDatabase, trait_id: chalk_ir::TraitId) -> Trait { - Trait { id: trait_id.into() } + Trait { id: id_from_chalk(trait_id.0) } } } @@ -187,18 +200,18 @@ impl ToChalk for TypeAlias { type Chalk = chalk_ir::TypeId; fn to_chalk(self, _db: &impl HirDatabase) -> chalk_ir::TypeId { - self.id.into() + chalk_ir::TypeId(id_to_chalk(self.id)) } - fn from_chalk(_db: &impl HirDatabase, impl_id: chalk_ir::TypeId) -> TypeAlias { - TypeAlias { id: impl_id.into() } + fn from_chalk(_db: &impl HirDatabase, type_alias_id: chalk_ir::TypeId) -> TypeAlias { + TypeAlias { id: id_from_chalk(type_alias_id.0) } } } impl ToChalk for GenericPredicate { - type Chalk = chalk_ir::QuantifiedWhereClause; + type Chalk = chalk_ir::QuantifiedWhereClause; - fn to_chalk(self, db: &impl HirDatabase) -> chalk_ir::QuantifiedWhereClause { + fn to_chalk(self, db: &impl HirDatabase) -> chalk_ir::QuantifiedWhereClause { match self { GenericPredicate::Implemented(trait_ref) => { make_binders(chalk_ir::WhereClause::Implemented(trait_ref.to_chalk(db)), 0) @@ -221,25 +234,40 @@ impl ToChalk for GenericPredicate { } fn from_chalk( - _db: &impl HirDatabase, - _where_clause: chalk_ir::QuantifiedWhereClause, + db: &impl HirDatabase, + where_clause: chalk_ir::QuantifiedWhereClause, ) -> GenericPredicate { - // This should never need to be called - unimplemented!() + match where_clause.value { + chalk_ir::WhereClause::Implemented(tr) => { + if tr.trait_id == UNKNOWN_TRAIT { + // FIXME we need an Error enum on the Chalk side to avoid this + return GenericPredicate::Error; + } + GenericPredicate::Implemented(from_chalk(db, tr)) + } + chalk_ir::WhereClause::ProjectionEq(projection_eq) => { + let projection_ty = from_chalk(db, projection_eq.projection); + let ty = from_chalk(db, projection_eq.ty); + GenericPredicate::Projection(super::ProjectionPredicate { projection_ty, ty }) + } + } } } impl ToChalk for ProjectionTy { - type Chalk = chalk_ir::ProjectionTy; + type Chalk = chalk_ir::ProjectionTy; - fn to_chalk(self, db: &impl HirDatabase) -> chalk_ir::ProjectionTy { + fn to_chalk(self, db: &impl HirDatabase) -> chalk_ir::ProjectionTy { chalk_ir::ProjectionTy { associated_ty_id: self.associated_ty.to_chalk(db), parameters: self.parameters.to_chalk(db), } } - fn from_chalk(db: &impl HirDatabase, projection_ty: chalk_ir::ProjectionTy) -> ProjectionTy { + fn from_chalk( + db: &impl HirDatabase, + projection_ty: chalk_ir::ProjectionTy, + ) -> ProjectionTy { ProjectionTy { associated_ty: from_chalk(db, projection_ty.associated_ty_id), parameters: from_chalk(db, projection_ty.parameters), @@ -248,31 +276,31 @@ impl ToChalk for ProjectionTy { } impl ToChalk for super::ProjectionPredicate { - type Chalk = chalk_ir::Normalize; + type Chalk = chalk_ir::Normalize; - fn to_chalk(self, db: &impl HirDatabase) -> chalk_ir::Normalize { + fn to_chalk(self, db: &impl HirDatabase) -> chalk_ir::Normalize { chalk_ir::Normalize { projection: self.projection_ty.to_chalk(db), ty: self.ty.to_chalk(db), } } - fn from_chalk(_db: &impl HirDatabase, _normalize: chalk_ir::Normalize) -> Self { + fn from_chalk(_db: &impl HirDatabase, _normalize: chalk_ir::Normalize) -> Self { unimplemented!() } } impl ToChalk for Obligation { - type Chalk = chalk_ir::DomainGoal; + type Chalk = chalk_ir::DomainGoal; - fn to_chalk(self, db: &impl HirDatabase) -> chalk_ir::DomainGoal { + fn to_chalk(self, db: &impl HirDatabase) -> chalk_ir::DomainGoal { match self { Obligation::Trait(tr) => tr.to_chalk(db).cast(), Obligation::Projection(pr) => pr.to_chalk(db).cast(), } } - fn from_chalk(_db: &impl HirDatabase, _goal: chalk_ir::DomainGoal) -> Self { + fn from_chalk(_db: &impl HirDatabase, _goal: chalk_ir::DomainGoal) -> Self { unimplemented!() } } @@ -296,16 +324,16 @@ where } impl ToChalk for Arc { - type Chalk = Arc; + type Chalk = chalk_ir::Environment; - fn to_chalk(self, db: &impl HirDatabase) -> Arc { + fn to_chalk(self, db: &impl HirDatabase) -> chalk_ir::Environment { let mut clauses = Vec::new(); for pred in &self.predicates { if pred.is_error() { // for env, we just ignore errors continue; } - let program_clause: chalk_ir::ProgramClause = pred.clone().to_chalk(db).cast(); + let program_clause: chalk_ir::ProgramClause = pred.clone().to_chalk(db).cast(); clauses.push(program_clause.into_from_env_clause()); } chalk_ir::Environment::new().add_clauses(clauses) @@ -313,13 +341,16 @@ impl ToChalk for Arc { fn from_chalk( _db: &impl HirDatabase, - _env: Arc, + _env: chalk_ir::Environment, ) -> Arc { unimplemented!() } } -impl ToChalk for super::InEnvironment { +impl ToChalk for super::InEnvironment +where + T::Chalk: chalk_ir::family::HasTypeFamily, +{ type Chalk = chalk_ir::InEnvironment; fn to_chalk(self, db: &impl HirDatabase) -> chalk_ir::InEnvironment { @@ -351,7 +382,7 @@ fn convert_where_clauses( db: &impl HirDatabase, def: GenericDef, substs: &Substs, -) -> Vec { +) -> Vec> { let generic_predicates = db.generic_predicates(def); let mut result = Vec::with_capacity(generic_predicates.len()); for pred in generic_predicates.iter() { @@ -384,7 +415,7 @@ where fn impls_for_trait( &self, trait_id: chalk_ir::TraitId, - parameters: &[Parameter], + parameters: &[Parameter], ) -> Vec { debug!("impls_for_trait {:?}", trait_id); if trait_id == UNKNOWN_TRAIT { @@ -430,13 +461,13 @@ where } fn split_projection<'p>( &self, - projection: &'p chalk_ir::ProjectionTy, - ) -> (Arc, &'p [Parameter], &'p [Parameter]) { + projection: &'p chalk_ir::ProjectionTy, + ) -> (Arc, &'p [Parameter], &'p [Parameter]) { debug!("split_projection {:?}", projection); // we don't support GATs, so I think this should always be correct currently (self.db.associated_ty_data(projection.associated_ty_id), &projection.parameters, &[]) } - fn custom_clauses(&self) -> Vec { + fn custom_clauses(&self) -> Vec> { vec![] } fn local_impls_to_coherence_check( @@ -508,7 +539,7 @@ pub(crate) fn trait_datum_query( let trait_ref = trait_.trait_ref(db).subst(&bound_vars).to_chalk(db); let flags = chalk_rust_ir::TraitFlags { auto: trait_.is_auto(db), - upstream: trait_.module(db).krate(db) != Some(krate), + upstream: trait_.module(db).krate() != krate, non_enumerable: true, // FIXME set these flags correctly marker: false, @@ -596,7 +627,7 @@ fn impl_block_datum( .target_trait_ref(db) .expect("FIXME handle unresolved impl block trait ref") .subst(&bound_vars); - let impl_type = if impl_block.module().krate(db) == Some(krate) { + let impl_type = if impl_block.module().krate() == krate { chalk_rust_ir::ImplType::Local } else { chalk_rust_ir::ImplType::External @@ -705,7 +736,7 @@ fn closure_fn_trait_impl_datum( substs: Substs::build_for_def(db, trait_).push(self_ty).push(arg_ty).build(), }; - let output_ty_id = fn_once_trait.associated_type_by_name(db, &crate::name::OUTPUT_TYPE)?; + let output_ty_id = fn_once_trait.associated_type_by_name(db, &name::OUTPUT_TYPE)?; let output_ty_value = chalk_rust_ir::AssociatedTyValue { associated_ty_id: output_ty_id.to_chalk(db), @@ -746,30 +777,6 @@ fn id_to_chalk(salsa_id: T) -> chalk_ir::RawId { chalk_ir::RawId { index: salsa_id.as_intern_id().as_u32() } } -impl From for crate::ids::TraitId { - fn from(trait_id: chalk_ir::TraitId) -> Self { - id_from_chalk(trait_id.0) - } -} - -impl From for chalk_ir::TraitId { - fn from(trait_id: crate::ids::TraitId) -> Self { - chalk_ir::TraitId(id_to_chalk(trait_id)) - } -} - -impl From for crate::ids::TypeAliasId { - fn from(type_id: chalk_ir::TypeId) -> Self { - id_from_chalk(type_id.0) - } -} - -impl From for chalk_ir::TypeId { - fn from(type_id: crate::ids::TypeAliasId) -> Self { - chalk_ir::TypeId(id_to_chalk(type_id)) - } -} - impl From for crate::ids::TypeCtorId { fn from(struct_id: chalk_ir::StructId) -> Self { id_from_chalk(struct_id.0) diff --git a/crates/ra_hir/src/type_alias.rs b/crates/ra_hir/src/type_alias.rs index 674a46102044..078e6295eaa3 100644 --- a/crates/ra_hir/src/type_alias.rs +++ b/crates/ra_hir/src/type_alias.rs @@ -2,12 +2,13 @@ use std::sync::Arc; +use hir_def::type_ref::TypeRef; +use hir_expand::name::{AsName, Name}; + use ra_syntax::ast::NameOwner; use crate::{ db::{AstDatabase, DefDatabase}, - name::{AsName, Name}, - type_ref::TypeRef, HasSource, TypeAlias, }; diff --git a/crates/ra_hir_def/Cargo.toml b/crates/ra_hir_def/Cargo.toml new file mode 100644 index 000000000000..746c907e80da --- /dev/null +++ b/crates/ra_hir_def/Cargo.toml @@ -0,0 +1,21 @@ +[package] +edition = "2018" +name = "ra_hir_def" +version = "0.1.0" +authors = ["rust-analyzer developers"] + +[dependencies] +log = "0.4.5" +once_cell = "1.0.1" +relative-path = "1.0.0" +rustc-hash = "1.0" + +ra_arena = { path = "../ra_arena" } +ra_db = { path = "../ra_db" } +ra_syntax = { path = "../ra_syntax" } +ra_prof = { path = "../ra_prof" } +hir_expand = { path = "../ra_hir_expand", package = "ra_hir_expand" } +test_utils = { path = "../test_utils" } +mbe = { path = "../ra_mbe", package = "ra_mbe" } +ra_cfg = { path = "../ra_cfg" } +tt = { path = "../ra_tt", package = "ra_tt" } diff --git a/crates/ra_hir/src/attr.rs b/crates/ra_hir_def/src/attr.rs similarity index 68% rename from crates/ra_hir/src/attr.rs rename to crates/ra_hir_def/src/attr.rs index bd159a56650c..0e961ca12aff 100644 --- a/crates/ra_hir/src/attr.rs +++ b/crates/ra_hir_def/src/attr.rs @@ -2,6 +2,7 @@ use std::sync::Arc; +use hir_expand::hygiene::Hygiene; use mbe::ast_to_token_tree; use ra_cfg::CfgOptions; use ra_syntax::{ @@ -10,10 +11,10 @@ use ra_syntax::{ }; use tt::Subtree; -use crate::{db::AstDatabase, path::Path, HirFileId, Source}; +use crate::path::Path; #[derive(Debug, Clone, PartialEq, Eq)] -pub(crate) struct Attr { +pub struct Attr { pub(crate) path: Path, pub(crate) input: Option, } @@ -25,11 +26,8 @@ pub enum AttrInput { } impl Attr { - pub(crate) fn from_src( - Source { file_id, ast }: Source, - db: &impl AstDatabase, - ) -> Option { - let path = Path::from_src(Source { file_id, ast: ast.path()? }, db)?; + pub(crate) fn from_src(ast: ast::Attr, hygiene: &Hygiene) -> Option { + let path = Path::from_src(ast.path()?, hygiene)?; let input = match ast.input() { None => None, Some(ast::AttrInput::Literal(lit)) => { @@ -45,26 +43,22 @@ impl Attr { Some(Attr { path, input }) } - pub(crate) fn from_attrs_owner( - file_id: HirFileId, - owner: &dyn AttrsOwner, - db: &impl AstDatabase, - ) -> Option> { + pub fn from_attrs_owner(owner: &dyn AttrsOwner, hygiene: &Hygiene) -> Option> { let mut attrs = owner.attrs().peekable(); if attrs.peek().is_none() { // Avoid heap allocation return None; } - Some(attrs.flat_map(|ast| Attr::from_src(Source { file_id, ast }, db)).collect()) + Some(attrs.flat_map(|ast| Attr::from_src(ast, hygiene)).collect()) } - pub(crate) fn is_simple_atom(&self, name: &str) -> bool { + pub fn is_simple_atom(&self, name: &str) -> bool { // FIXME: Avoid cloning self.path.as_ident().map_or(false, |s| s.to_string() == name) } // FIXME: handle cfg_attr :-) - pub(crate) fn as_cfg(&self) -> Option<&Subtree> { + pub fn as_cfg(&self) -> Option<&Subtree> { if !self.is_simple_atom("cfg") { return None; } @@ -74,7 +68,7 @@ impl Attr { } } - pub(crate) fn as_path(&self) -> Option<&SmolStr> { + pub fn as_path(&self) -> Option<&SmolStr> { if !self.is_simple_atom("path") { return None; } @@ -84,7 +78,7 @@ impl Attr { } } - pub(crate) fn is_cfg_enabled(&self, cfg_options: &CfgOptions) -> Option { + pub fn is_cfg_enabled(&self, cfg_options: &CfgOptions) -> Option { cfg_options.is_cfg_enabled(self.as_cfg()?) } } diff --git a/crates/ra_hir_def/src/builtin_type.rs b/crates/ra_hir_def/src/builtin_type.rs new file mode 100644 index 000000000000..12929caa9003 --- /dev/null +++ b/crates/ra_hir_def/src/builtin_type.rs @@ -0,0 +1,63 @@ +//! This module defines built-in types. +//! +//! A peculiarity of built-in types is that they are always available and are +//! not associated with any particular crate. + +use hir_expand::name::{self, Name}; + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum Signedness { + Signed, + Unsigned, +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum IntBitness { + Xsize, + X8, + X16, + X32, + X64, + X128, +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum FloatBitness { + X32, + X64, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum BuiltinType { + Char, + Bool, + Str, + Int { signedness: Signedness, bitness: IntBitness }, + Float { bitness: FloatBitness }, +} + +impl BuiltinType { + #[rustfmt::skip] + pub const ALL: &'static [(Name, BuiltinType)] = &[ + (name::CHAR, BuiltinType::Char), + (name::BOOL, BuiltinType::Bool), + (name::STR, BuiltinType::Str ), + + (name::ISIZE, BuiltinType::Int { signedness: Signedness::Signed, bitness: IntBitness::Xsize }), + (name::I8, BuiltinType::Int { signedness: Signedness::Signed, bitness: IntBitness::X8 }), + (name::I16, BuiltinType::Int { signedness: Signedness::Signed, bitness: IntBitness::X16 }), + (name::I32, BuiltinType::Int { signedness: Signedness::Signed, bitness: IntBitness::X32 }), + (name::I64, BuiltinType::Int { signedness: Signedness::Signed, bitness: IntBitness::X64 }), + (name::I128, BuiltinType::Int { signedness: Signedness::Signed, bitness: IntBitness::X128 }), + + (name::USIZE, BuiltinType::Int { signedness: Signedness::Unsigned, bitness: IntBitness::Xsize }), + (name::U8, BuiltinType::Int { signedness: Signedness::Unsigned, bitness: IntBitness::X8 }), + (name::U16, BuiltinType::Int { signedness: Signedness::Unsigned, bitness: IntBitness::X16 }), + (name::U32, BuiltinType::Int { signedness: Signedness::Unsigned, bitness: IntBitness::X32 }), + (name::U64, BuiltinType::Int { signedness: Signedness::Unsigned, bitness: IntBitness::X64 }), + (name::U128, BuiltinType::Int { signedness: Signedness::Unsigned, bitness: IntBitness::X128 }), + + (name::F32, BuiltinType::Float { bitness: FloatBitness::X32 }), + (name::F64, BuiltinType::Float { bitness: FloatBitness::X64 }), + ]; +} diff --git a/crates/ra_hir_def/src/db.rs b/crates/ra_hir_def/src/db.rs new file mode 100644 index 000000000000..b271636b07e7 --- /dev/null +++ b/crates/ra_hir_def/src/db.rs @@ -0,0 +1,40 @@ +//! Defines database & queries for name resolution. +use std::sync::Arc; + +use hir_expand::{db::AstDatabase, HirFileId}; +use ra_db::{salsa, SourceDatabase}; +use ra_syntax::ast; + +use crate::nameres::raw::{ImportSourceMap, RawItems}; + +#[salsa::query_group(InternDatabaseStorage)] +pub trait InternDatabase: SourceDatabase { + #[salsa::interned] + fn intern_function(&self, loc: crate::ItemLoc) -> crate::FunctionId; + #[salsa::interned] + fn intern_struct(&self, loc: crate::ItemLoc) -> crate::StructId; + #[salsa::interned] + fn intern_union(&self, loc: crate::ItemLoc) -> crate::UnionId; + #[salsa::interned] + fn intern_enum(&self, loc: crate::ItemLoc) -> crate::EnumId; + #[salsa::interned] + fn intern_const(&self, loc: crate::ItemLoc) -> crate::ConstId; + #[salsa::interned] + fn intern_static(&self, loc: crate::ItemLoc) -> crate::StaticId; + #[salsa::interned] + fn intern_trait(&self, loc: crate::ItemLoc) -> crate::TraitId; + #[salsa::interned] + fn intern_type_alias(&self, loc: crate::ItemLoc) -> crate::TypeAliasId; +} + +#[salsa::query_group(DefDatabase2Storage)] +pub trait DefDatabase2: InternDatabase + AstDatabase { + #[salsa::invoke(RawItems::raw_items_with_source_map_query)] + fn raw_items_with_source_map( + &self, + file_id: HirFileId, + ) -> (Arc, Arc); + + #[salsa::invoke(RawItems::raw_items_query)] + fn raw_items(&self, file_id: HirFileId) -> Arc; +} diff --git a/crates/ra_hir_def/src/lib.rs b/crates/ra_hir_def/src/lib.rs new file mode 100644 index 000000000000..93ad40005c7f --- /dev/null +++ b/crates/ra_hir_def/src/lib.rs @@ -0,0 +1,362 @@ +//! `hir_def` crate contains everything between macro expansion and type +//! inference. +//! +//! It defines various items (structs, enums, traits) which comprises Rust code, +//! as well as an algorithm for resolving paths to such entities. +//! +//! Note that `hir_def` is a work in progress, so not all of the above is +//! actually true. + +pub mod db; +pub mod attr; +pub mod path; +pub mod type_ref; +pub mod builtin_type; + +// FIXME: this should be private +pub mod nameres; + +use std::hash::{Hash, Hasher}; + +use hir_expand::{ast_id_map::FileAstId, db::AstDatabase, AstId, HirFileId}; +use ra_arena::{impl_arena_id, RawId}; +use ra_db::{salsa, CrateId, FileId}; +use ra_syntax::{ast, AstNode, SyntaxNode}; + +use crate::{builtin_type::BuiltinType, db::InternDatabase}; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct Source { + pub file_id: HirFileId, + pub ast: T, +} + +pub enum ModuleSource { + SourceFile(ast::SourceFile), + Module(ast::Module), +} + +impl ModuleSource { + pub fn new( + db: &impl db::DefDatabase2, + file_id: Option, + decl_id: Option>, + ) -> ModuleSource { + match (file_id, decl_id) { + (Some(file_id), _) => { + let source_file = db.parse(file_id).tree(); + ModuleSource::SourceFile(source_file) + } + (None, Some(item_id)) => { + let module = item_id.to_node(db); + assert!(module.item_list().is_some(), "expected inline module"); + ModuleSource::Module(module) + } + (None, None) => panic!(), + } + } + + // FIXME: this methods do not belong here + pub fn from_position( + db: &impl db::DefDatabase2, + position: ra_db::FilePosition, + ) -> ModuleSource { + let parse = db.parse(position.file_id); + match &ra_syntax::algo::find_node_at_offset::( + parse.tree().syntax(), + position.offset, + ) { + Some(m) if !m.has_semi() => ModuleSource::Module(m.clone()), + _ => { + let source_file = parse.tree(); + ModuleSource::SourceFile(source_file) + } + } + } + + pub fn from_child_node( + db: &impl db::DefDatabase2, + file_id: FileId, + child: &SyntaxNode, + ) -> ModuleSource { + if let Some(m) = child.ancestors().filter_map(ast::Module::cast).find(|it| !it.has_semi()) { + ModuleSource::Module(m) + } else { + let source_file = db.parse(file_id).tree(); + ModuleSource::SourceFile(source_file) + } + } + + pub fn from_file_id(db: &impl db::DefDatabase2, file_id: FileId) -> ModuleSource { + let source_file = db.parse(file_id).tree(); + ModuleSource::SourceFile(source_file) + } +} + +impl Source { + pub fn map U, U>(self, f: F) -> Source { + Source { file_id: self.file_id, ast: f(self.ast) } + } + pub fn file_syntax(&self, db: &impl AstDatabase) -> SyntaxNode { + db.parse_or_expand(self.file_id).expect("source created from invalid file") + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct ModuleId { + pub krate: CrateId, + pub module_id: CrateModuleId, +} + +/// An ID of a module, **local** to a specific crate +// FIXME: rename to `LocalModuleId`. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct CrateModuleId(RawId); +impl_arena_id!(CrateModuleId); + +macro_rules! impl_intern_key { + ($name:ident) => { + impl salsa::InternKey for $name { + fn from_intern_id(v: salsa::InternId) -> Self { + $name(v) + } + fn as_intern_id(&self) -> salsa::InternId { + self.0 + } + } + }; +} + +#[derive(Debug)] +pub struct ItemLoc { + pub(crate) module: ModuleId, + ast_id: AstId, +} + +impl PartialEq for ItemLoc { + fn eq(&self, other: &Self) -> bool { + self.module == other.module && self.ast_id == other.ast_id + } +} +impl Eq for ItemLoc {} +impl Hash for ItemLoc { + fn hash(&self, hasher: &mut H) { + self.module.hash(hasher); + self.ast_id.hash(hasher); + } +} + +impl Clone for ItemLoc { + fn clone(&self) -> ItemLoc { + ItemLoc { module: self.module, ast_id: self.ast_id } + } +} + +#[derive(Clone, Copy)] +pub struct LocationCtx { + db: DB, + module: ModuleId, + file_id: HirFileId, +} + +impl<'a, DB> LocationCtx<&'a DB> { + pub fn new(db: &'a DB, module: ModuleId, file_id: HirFileId) -> LocationCtx<&'a DB> { + LocationCtx { db, module, file_id } + } +} + +impl<'a, DB: AstDatabase + InternDatabase> LocationCtx<&'a DB> { + pub fn to_def(self, ast: &N) -> DEF + where + N: AstNode, + DEF: AstItemDef, + { + DEF::from_ast(self, ast) + } +} + +pub trait AstItemDef: salsa::InternKey + Clone { + fn intern(db: &impl InternDatabase, loc: ItemLoc) -> Self; + fn lookup_intern(self, db: &impl InternDatabase) -> ItemLoc; + + fn from_ast(ctx: LocationCtx<&(impl AstDatabase + InternDatabase)>, ast: &N) -> Self { + let items = ctx.db.ast_id_map(ctx.file_id); + let item_id = items.ast_id(ast); + Self::from_ast_id(ctx, item_id) + } + fn from_ast_id(ctx: LocationCtx<&impl InternDatabase>, ast_id: FileAstId) -> Self { + let loc = ItemLoc { module: ctx.module, ast_id: AstId::new(ctx.file_id, ast_id) }; + Self::intern(ctx.db, loc) + } + fn source(self, db: &(impl AstDatabase + InternDatabase)) -> Source { + let loc = self.lookup_intern(db); + let ast = loc.ast_id.to_node(db); + Source { file_id: loc.ast_id.file_id(), ast } + } + fn module(self, db: &impl InternDatabase) -> ModuleId { + let loc = self.lookup_intern(db); + loc.module + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct FunctionId(salsa::InternId); +impl_intern_key!(FunctionId); + +impl AstItemDef for FunctionId { + fn intern(db: &impl InternDatabase, loc: ItemLoc) -> Self { + db.intern_function(loc) + } + fn lookup_intern(self, db: &impl InternDatabase) -> ItemLoc { + db.lookup_intern_function(self) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct StructId(salsa::InternId); +impl_intern_key!(StructId); +impl AstItemDef for StructId { + fn intern(db: &impl InternDatabase, loc: ItemLoc) -> Self { + db.intern_struct(loc) + } + fn lookup_intern(self, db: &impl InternDatabase) -> ItemLoc { + db.lookup_intern_struct(self) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct UnionId(salsa::InternId); +impl_intern_key!(UnionId); +impl AstItemDef for UnionId { + fn intern(db: &impl InternDatabase, loc: ItemLoc) -> Self { + db.intern_union(loc) + } + fn lookup_intern(self, db: &impl InternDatabase) -> ItemLoc { + db.lookup_intern_union(self) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct EnumId(salsa::InternId); +impl_intern_key!(EnumId); +impl AstItemDef for EnumId { + fn intern(db: &impl InternDatabase, loc: ItemLoc) -> Self { + db.intern_enum(loc) + } + fn lookup_intern(self, db: &impl InternDatabase) -> ItemLoc { + db.lookup_intern_enum(self) + } +} + +// FIXME: rename to `VariantId`, only enums can ave variants +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct EnumVariantId { + parent: EnumId, + local_id: LocalEnumVariantId, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct LocalEnumVariantId(RawId); +impl_arena_id!(LocalEnumVariantId); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct ConstId(salsa::InternId); +impl_intern_key!(ConstId); +impl AstItemDef for ConstId { + fn intern(db: &impl InternDatabase, loc: ItemLoc) -> Self { + db.intern_const(loc) + } + fn lookup_intern(self, db: &impl InternDatabase) -> ItemLoc { + db.lookup_intern_const(self) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct StaticId(salsa::InternId); +impl_intern_key!(StaticId); +impl AstItemDef for StaticId { + fn intern(db: &impl InternDatabase, loc: ItemLoc) -> Self { + db.intern_static(loc) + } + fn lookup_intern(self, db: &impl InternDatabase) -> ItemLoc { + db.lookup_intern_static(self) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct TraitId(salsa::InternId); +impl_intern_key!(TraitId); +impl AstItemDef for TraitId { + fn intern(db: &impl InternDatabase, loc: ItemLoc) -> Self { + db.intern_trait(loc) + } + fn lookup_intern(self, db: &impl InternDatabase) -> ItemLoc { + db.lookup_intern_trait(self) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct TypeAliasId(salsa::InternId); +impl_intern_key!(TypeAliasId); +impl AstItemDef for TypeAliasId { + fn intern(db: &impl InternDatabase, loc: ItemLoc) -> Self { + db.intern_type_alias(loc) + } + fn lookup_intern(self, db: &impl InternDatabase) -> ItemLoc { + db.lookup_intern_type_alias(self) + } +} + +macro_rules! impl_froms { + ($e:ident: $($v:ident $(($($sv:ident),*))?),*) => { + $( + impl From<$v> for $e { + fn from(it: $v) -> $e { + $e::$v(it) + } + } + $($( + impl From<$sv> for $e { + fn from(it: $sv) -> $e { + $e::$v($v::$sv(it)) + } + } + )*)? + )* + } +} + +/// A Data Type +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum AdtId { + StructId(StructId), + UnionId(UnionId), + EnumId(EnumId), +} +impl_froms!(AdtId: StructId, UnionId, EnumId); + +/// The defs which can be visible in the module. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ModuleDefId { + ModuleId(ModuleId), + FunctionId(FunctionId), + AdtId(AdtId), + // Can't be directly declared, but can be imported. + EnumVariantId(EnumVariantId), + ConstId(ConstId), + StaticId(StaticId), + TraitId(TraitId), + TypeAliasId(TypeAliasId), + BuiltinType(BuiltinType), +} +impl_froms!( + ModuleDefId: ModuleId, + FunctionId, + AdtId(StructId, EnumId, UnionId), + EnumVariantId, + ConstId, + StaticId, + TraitId, + TypeAliasId, + BuiltinType +); diff --git a/crates/ra_hir_def/src/nameres.rs b/crates/ra_hir_def/src/nameres.rs new file mode 100644 index 000000000000..11ba8a7770d9 --- /dev/null +++ b/crates/ra_hir_def/src/nameres.rs @@ -0,0 +1,5 @@ +//! FIXME: write short doc here + +// FIXME: review privacy of submodules +pub mod raw; +pub mod mod_resolution; diff --git a/crates/ra_hir/src/nameres/mod_resolution.rs b/crates/ra_hir_def/src/nameres/mod_resolution.rs similarity index 74% rename from crates/ra_hir/src/nameres/mod_resolution.rs rename to crates/ra_hir_def/src/nameres/mod_resolution.rs index e8b8085142c7..7d7e2779aa20 100644 --- a/crates/ra_hir/src/nameres/mod_resolution.rs +++ b/crates/ra_hir_def/src/nameres/mod_resolution.rs @@ -1,12 +1,13 @@ //! This module resolves `mod foo;` declaration to file. +use hir_expand::name::Name; use ra_db::FileId; use ra_syntax::SmolStr; use relative_path::RelativePathBuf; -use crate::{db::DefDatabase, HirFileId, Name}; +use crate::{db::DefDatabase2, HirFileId}; #[derive(Clone, Debug)] -pub(super) struct ModDir { +pub struct ModDir { /// `.` for `mod.rs`, `lib.rs` /// `./foo` for `foo.rs` /// `./foo/bar` for `mod bar { mod x; }` nested in `foo.rs` @@ -16,24 +17,17 @@ pub(super) struct ModDir { } impl ModDir { - pub(super) fn root() -> ModDir { + pub fn root() -> ModDir { ModDir { path: RelativePathBuf::default(), root_non_dir_owner: false } } - pub(super) fn descend_into_definition( - &self, - name: &Name, - attr_path: Option<&SmolStr>, - ) -> ModDir { + pub fn descend_into_definition(&self, name: &Name, attr_path: Option<&SmolStr>) -> ModDir { let mut path = self.path.clone(); match attr_to_path(attr_path) { None => path.push(&name.to_string()), Some(attr_path) => { if self.root_non_dir_owner { - // Workaround for relative path API: turn `lib.rs` into ``. - if !path.pop() { - path = RelativePathBuf::default(); - } + assert!(path.pop()); } path.push(attr_path); } @@ -41,24 +35,20 @@ impl ModDir { ModDir { path, root_non_dir_owner: false } } - pub(super) fn resolve_declaration( + pub fn resolve_declaration( &self, - db: &impl DefDatabase, + db: &impl DefDatabase2, file_id: HirFileId, name: &Name, attr_path: Option<&SmolStr>, ) -> Result<(FileId, ModDir), RelativePathBuf> { - let empty_path = RelativePathBuf::default(); let file_id = file_id.original_file(db); let mut candidate_files = Vec::new(); match attr_to_path(attr_path) { Some(attr_path) => { - let base = if self.root_non_dir_owner { - self.path.parent().unwrap_or(&empty_path) - } else { - &self.path - }; + let base = + if self.root_non_dir_owner { self.path.parent().unwrap() } else { &self.path }; candidate_files.push(base.join(attr_path)) } None => { diff --git a/crates/ra_hir/src/nameres/raw.rs b/crates/ra_hir_def/src/nameres/raw.rs similarity index 84% rename from crates/ra_hir/src/nameres/raw.rs rename to crates/ra_hir_def/src/nameres/raw.rs index 57f2929c38ed..86c05d6028e7 100644 --- a/crates/ra_hir/src/nameres/raw.rs +++ b/crates/ra_hir_def/src/nameres/raw.rs @@ -2,18 +2,20 @@ use std::{ops::Index, sync::Arc}; +use hir_expand::{ + ast_id_map::AstIdMap, + db::AstDatabase, + either::Either, + hygiene::Hygiene, + name::{AsName, Name}, +}; use ra_arena::{impl_arena_id, map::ArenaMap, Arena, RawId}; use ra_syntax::{ ast::{self, AttrsOwner, NameOwner}, AstNode, AstPtr, SourceFile, }; -use test_utils::tested_by; -use crate::{ - attr::Attr, - db::{AstDatabase, DefDatabase}, - AsName, AstIdMap, Either, FileAstId, HirFileId, ModuleSource, Name, Path, Source, -}; +use crate::{attr::Attr, db::DefDatabase2, path::Path, FileAstId, HirFileId, ModuleSource, Source}; /// `RawItems` is a set of top-level items in a file (except for impls). /// @@ -37,10 +39,8 @@ pub struct ImportSourceMap { type ImportSourcePtr = Either, AstPtr>; type ImportSource = Either; -impl ImportSourcePtr { - fn to_node(self, file: &SourceFile) -> ImportSource { - self.map(|ptr| ptr.to_node(file.syntax()), |ptr| ptr.to_node(file.syntax())) - } +fn to_node(ptr: ImportSourcePtr, file: &SourceFile) -> ImportSource { + ptr.map(|ptr| ptr.to_node(file.syntax()), |ptr| ptr.to_node(file.syntax())) } impl ImportSourceMap { @@ -48,26 +48,26 @@ impl ImportSourceMap { self.map.insert(import, ptr) } - pub(crate) fn get(&self, source: &ModuleSource, import: ImportId) -> ImportSource { + pub fn get(&self, source: &ModuleSource, import: ImportId) -> ImportSource { let file = match source { ModuleSource::SourceFile(file) => file.clone(), ModuleSource::Module(m) => m.syntax().ancestors().find_map(SourceFile::cast).unwrap(), }; - self.map[import].to_node(&file) + to_node(self.map[import], &file) } } impl RawItems { pub(crate) fn raw_items_query( - db: &(impl DefDatabase + AstDatabase), + db: &(impl DefDatabase2 + AstDatabase), file_id: HirFileId, ) -> Arc { db.raw_items_with_source_map(file_id).0 } pub(crate) fn raw_items_with_source_map_query( - db: &(impl DefDatabase + AstDatabase), + db: &(impl DefDatabase2 + AstDatabase), file_id: HirFileId, ) -> (Arc, Arc) { let mut collector = RawItemsCollector { @@ -75,7 +75,7 @@ impl RawItems { source_ast_id_map: db.ast_id_map(file_id), source_map: ImportSourceMap::default(), file_id, - db, + hygiene: Hygiene::new(db, file_id), }; if let Some(node) = db.parse_or_expand(file_id) { if let Some(source_file) = ast::SourceFile::cast(node.clone()) { @@ -87,7 +87,7 @@ impl RawItems { (Arc::new(collector.raw_items), Arc::new(collector.source_map)) } - pub(super) fn items(&self) -> &[RawItem] { + pub fn items(&self) -> &[RawItem] { &self.items } } @@ -124,19 +124,19 @@ impl Index for RawItems { type Attrs = Option>; #[derive(Debug, PartialEq, Eq, Clone)] -pub(super) struct RawItem { +pub struct RawItem { attrs: Attrs, - pub(super) kind: RawItemKind, + pub kind: RawItemKind, } impl RawItem { - pub(super) fn attrs(&self) -> &[Attr] { + pub fn attrs(&self) -> &[Attr] { self.attrs.as_ref().map_or(&[], |it| &*it) } } #[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub(super) enum RawItemKind { +pub enum RawItemKind { Module(Module), Import(ImportId), Def(Def), @@ -144,11 +144,11 @@ pub(super) enum RawItemKind { } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub(super) struct Module(RawId); +pub struct Module(RawId); impl_arena_id!(Module); #[derive(Debug, PartialEq, Eq)] -pub(super) enum ModuleData { +pub enum ModuleData { Declaration { name: Name, ast_id: FileAstId }, Definition { name: Name, ast_id: FileAstId, items: Vec }, } @@ -159,26 +159,26 @@ impl_arena_id!(ImportId); #[derive(Debug, Clone, PartialEq, Eq)] pub struct ImportData { - pub(super) path: Path, - pub(super) alias: Option, - pub(super) is_glob: bool, - pub(super) is_prelude: bool, - pub(super) is_extern_crate: bool, - pub(super) is_macro_use: bool, + pub path: Path, + pub alias: Option, + pub is_glob: bool, + pub is_prelude: bool, + pub is_extern_crate: bool, + pub is_macro_use: bool, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub(super) struct Def(RawId); +pub struct Def(RawId); impl_arena_id!(Def); #[derive(Debug, PartialEq, Eq)] -pub(super) struct DefData { - pub(super) name: Name, - pub(super) kind: DefKind, +pub struct DefData { + pub name: Name, + pub kind: DefKind, } #[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub(super) enum DefKind { +pub enum DefKind { Function(FileAstId), Struct(FileAstId), Union(FileAstId), @@ -190,26 +190,26 @@ pub(super) enum DefKind { } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub(super) struct Macro(RawId); +pub struct Macro(RawId); impl_arena_id!(Macro); #[derive(Debug, PartialEq, Eq)] -pub(super) struct MacroData { - pub(super) ast_id: FileAstId, - pub(super) path: Path, - pub(super) name: Option, - pub(super) export: bool, +pub struct MacroData { + pub ast_id: FileAstId, + pub path: Path, + pub name: Option, + pub export: bool, } -struct RawItemsCollector { +struct RawItemsCollector { raw_items: RawItems, source_ast_id_map: Arc, source_map: ImportSourceMap, file_id: HirFileId, - db: DB, + hygiene: Hygiene, } -impl RawItemsCollector<&DB> { +impl RawItemsCollector { fn process_module(&mut self, current_module: Option, body: impl ast::ModuleItemOwner) { for item_or_macro in body.items_with_macros() { match item_or_macro { @@ -297,7 +297,8 @@ impl RawItemsCollector<&DB> { self.push_item(current_module, attrs, RawItemKind::Module(item)); return; } - tested_by!(name_res_works_for_broken_modules); + // FIXME: restore this mark once we complete hir splitting + // tested_by!(name_res_works_for_broken_modules); } fn add_use_item(&mut self, current_module: Option, use_item: ast::UseItem) { @@ -305,9 +306,10 @@ impl RawItemsCollector<&DB> { let is_prelude = use_item.has_atom_attr("prelude_import"); let attrs = self.parse_attrs(&use_item); + let mut buf = Vec::new(); Path::expand_use_item( Source { ast: use_item, file_id: self.file_id }, - self.db, + &self.hygiene, |path, use_tree, is_glob, alias| { let import_data = ImportData { path, @@ -317,14 +319,12 @@ impl RawItemsCollector<&DB> { is_extern_crate: false, is_macro_use: false, }; - self.push_import( - current_module, - attrs.clone(), - import_data, - Either::A(AstPtr::new(use_tree)), - ); + buf.push((import_data, Either::A(AstPtr::new(use_tree)))); }, - ) + ); + for (import_data, ptr) in buf { + self.push_import(current_module, attrs.clone(), import_data, ptr); + } } fn add_extern_crate_item( @@ -357,10 +357,7 @@ impl RawItemsCollector<&DB> { fn add_macro(&mut self, current_module: Option, m: ast::MacroCall) { let attrs = self.parse_attrs(&m); - let path = match m - .path() - .and_then(|path| Path::from_src(Source { ast: path, file_id: self.file_id }, self.db)) - { + let path = match m.path().and_then(|path| Path::from_src(path, &self.hygiene)) { Some(it) => it, _ => return, }; @@ -398,6 +395,6 @@ impl RawItemsCollector<&DB> { } fn parse_attrs(&self, item: &impl ast::AttrsOwner) -> Attrs { - Attr::from_attrs_owner(self.file_id, item, self.db) + Attr::from_attrs_owner(item, &self.hygiene) } } diff --git a/crates/ra_hir/src/path.rs b/crates/ra_hir_def/src/path.rs similarity index 81% rename from crates/ra_hir/src/path.rs rename to crates/ra_hir_def/src/path.rs index 394617e1a4c0..04039376f394 100644 --- a/crates/ra_hir/src/path.rs +++ b/crates/ra_hir_def/src/path.rs @@ -2,12 +2,18 @@ use std::{iter, sync::Arc}; +use hir_expand::{ + either::Either, + hygiene::Hygiene, + name::{self, AsName, Name}, +}; +use ra_db::CrateId; use ra_syntax::{ ast::{self, NameOwner, TypeAscriptionOwner}, AstNode, }; -use crate::{db::AstDatabase, name, type_ref::TypeRef, AsName, Crate, Name, Source}; +use crate::{type_ref::TypeRef, Source}; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Path { @@ -55,18 +61,18 @@ pub enum PathKind { // Type based path like `::foo` Type(Box), // `$crate` from macro expansion - DollarCrate(Crate), + DollarCrate(CrateId), } impl Path { /// Calls `cb` with all paths, represented by this use item. pub fn expand_use_item( item_src: Source, - db: &impl AstDatabase, + hygiene: &Hygiene, mut cb: impl FnMut(Path, &ast::UseTree, bool, Option), ) { if let Some(tree) = item_src.ast.use_tree() { - expand_use_tree(None, tree, &|| item_src.file_id.macro_crate(db), &mut cb); + expand_use_tree(None, tree, hygiene, &mut cb); } } @@ -83,17 +89,12 @@ impl Path { /// Converts an `ast::Path` to `Path`. Works with use trees. /// DEPRECATED: It does not handle `$crate` from macro call. pub fn from_ast(path: ast::Path) -> Option { - Path::parse(path, &|| None) + Path::from_src(path, &Hygiene::new_unhygienic()) } /// Converts an `ast::Path` to `Path`. Works with use trees. /// It correctly handles `$crate` based path from macro call. - pub fn from_src(source: Source, db: &impl AstDatabase) -> Option { - let file_id = source.file_id; - Path::parse(source.ast, &|| file_id.macro_crate(db)) - } - - fn parse(mut path: ast::Path, macro_crate: &impl Fn() -> Option) -> Option { + pub fn from_src(mut path: ast::Path, hygiene: &Hygiene) -> Option { let mut kind = PathKind::Plain; let mut segments = Vec::new(); loop { @@ -104,26 +105,28 @@ impl Path { } match segment.kind()? { - ast::PathSegmentKind::Name(name) => { - if name.text() == "$crate" { - if let Some(macro_crate) = macro_crate() { - kind = PathKind::DollarCrate(macro_crate); + ast::PathSegmentKind::Name(name_ref) => { + // FIXME: this should just return name + match hygiene.name_ref_to_name(name_ref) { + Either::A(name) => { + let args = segment + .type_arg_list() + .and_then(GenericArgs::from_ast) + .or_else(|| { + GenericArgs::from_fn_like_path_ast( + segment.param_list(), + segment.ret_type(), + ) + }) + .map(Arc::new); + let segment = PathSegment { name, args_and_bindings: args }; + segments.push(segment); + } + Either::B(crate_id) => { + kind = PathKind::DollarCrate(crate_id); break; } } - - let args = segment - .type_arg_list() - .and_then(GenericArgs::from_ast) - .or_else(|| { - GenericArgs::from_fn_like_path_ast( - segment.param_list(), - segment.ret_type(), - ) - }) - .map(Arc::new); - let segment = PathSegment { name: name.as_name(), args_and_bindings: args }; - segments.push(segment); } ast::PathSegmentKind::Type { type_ref, trait_ref } => { assert!(path.qualifier().is_none()); // this can only occur at the first segment @@ -137,7 +140,7 @@ impl Path { } // >::Foo desugars to Trait::Foo Some(trait_ref) => { - let path = Path::parse(trait_ref.path()?, macro_crate)?; + let path = Path::from_src(trait_ref.path()?, hygiene)?; kind = path.kind; let mut prefix_segments = path.segments; prefix_segments.reverse(); @@ -224,7 +227,7 @@ impl Path { } impl GenericArgs { - pub(crate) fn from_ast(node: ast::TypeArgList) -> Option { + pub fn from_ast(node: ast::TypeArgList) -> Option { let mut args = Vec::new(); for type_arg in node.type_args() { let type_ref = TypeRef::from_ast_opt(type_arg.type_ref()); @@ -288,8 +291,8 @@ impl From for Path { fn expand_use_tree( prefix: Option, tree: ast::UseTree, - macro_crate: &impl Fn() -> Option, - cb: &mut impl FnMut(Path, &ast::UseTree, bool, Option), + hygiene: &Hygiene, + cb: &mut dyn FnMut(Path, &ast::UseTree, bool, Option), ) { if let Some(use_tree_list) = tree.use_tree_list() { let prefix = match tree.path() { @@ -297,13 +300,13 @@ fn expand_use_tree( None => prefix, // E.g. `use something::{inner}` (prefix is `None`, path is `something`) // or `use something::{path::{inner::{innerer}}}` (prefix is `something::path`, path is `inner`) - Some(path) => match convert_path(prefix, path, macro_crate) { + Some(path) => match convert_path(prefix, path, hygiene) { Some(it) => Some(it), None => return, // FIXME: report errors somewhere }, }; for child_tree in use_tree_list.use_trees() { - expand_use_tree(prefix.clone(), child_tree, macro_crate, cb); + expand_use_tree(prefix.clone(), child_tree, hygiene, cb); } } else { let alias = tree.alias().and_then(|a| a.name()).map(|a| a.as_name()); @@ -320,7 +323,7 @@ fn expand_use_tree( } } } - if let Some(path) = convert_path(prefix, ast_path, macro_crate) { + if let Some(path) = convert_path(prefix, ast_path, hygiene) { let is_glob = tree.has_star(); cb(path, &tree, is_glob, alias) } @@ -330,37 +333,36 @@ fn expand_use_tree( } } -fn convert_path( - prefix: Option, - path: ast::Path, - macro_crate: &impl Fn() -> Option, -) -> Option { +fn convert_path(prefix: Option, path: ast::Path, hygiene: &Hygiene) -> Option { let prefix = if let Some(qual) = path.qualifier() { - Some(convert_path(prefix, qual, macro_crate)?) + Some(convert_path(prefix, qual, hygiene)?) } else { prefix }; let segment = path.segment()?; let res = match segment.kind()? { - ast::PathSegmentKind::Name(name) => { - if name.text() == "$crate" { - if let Some(krate) = macro_crate() { + ast::PathSegmentKind::Name(name_ref) => { + match hygiene.name_ref_to_name(name_ref) { + Either::A(name) => { + // no type args in use + let mut res = prefix.unwrap_or_else(|| Path { + kind: PathKind::Plain, + segments: Vec::with_capacity(1), + }); + res.segments.push(PathSegment { + name, + args_and_bindings: None, // no type args in use + }); + res + } + Either::B(crate_id) => { return Some(Path::from_simple_segments( - PathKind::DollarCrate(krate), + PathKind::DollarCrate(crate_id), iter::empty(), - )); + )) } } - - // no type args in use - let mut res = prefix - .unwrap_or_else(|| Path { kind: PathKind::Plain, segments: Vec::with_capacity(1) }); - res.segments.push(PathSegment { - name: name.as_name(), - args_and_bindings: None, // no type args in use - }); - res } ast::PathSegmentKind::CrateKw => { if prefix.is_some() { @@ -389,8 +391,9 @@ fn convert_path( } pub mod known { + use hir_expand::name; + use super::{Path, PathKind}; - use crate::name; pub fn std_iter_into_iterator() -> Path { Path::from_simple_segments( diff --git a/crates/ra_hir/src/type_ref.rs b/crates/ra_hir_def/src/type_ref.rs similarity index 96% rename from crates/ra_hir/src/type_ref.rs rename to crates/ra_hir_def/src/type_ref.rs index 2cf06b250444..8af061116de8 100644 --- a/crates/ra_hir/src/type_ref.rs +++ b/crates/ra_hir_def/src/type_ref.rs @@ -3,7 +3,7 @@ use ra_syntax::ast::{self, TypeAscriptionOwner, TypeBoundsOwner}; -use crate::Path; +use crate::path::Path; #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub enum Mutability { @@ -64,7 +64,7 @@ pub enum TypeBound { impl TypeRef { /// Converts an `ast::TypeRef` to a `hir::TypeRef`. - pub(crate) fn from_ast(node: ast::TypeRef) -> Self { + pub fn from_ast(node: ast::TypeRef) -> Self { match node { ast::TypeRef::ParenType(inner) => TypeRef::from_ast_opt(inner.type_ref()), ast::TypeRef::TupleType(inner) => { @@ -113,7 +113,7 @@ impl TypeRef { } } - pub(crate) fn from_ast_opt(node: Option) -> Self { + pub fn from_ast_opt(node: Option) -> Self { if let Some(node) = node { TypeRef::from_ast(node) } else { @@ -135,7 +135,7 @@ pub(crate) fn type_bounds_from_ast(type_bounds_opt: Option) } impl TypeBound { - pub(crate) fn from_ast(node: ast::TypeBound) -> Self { + pub fn from_ast(node: ast::TypeBound) -> Self { match node.kind() { ast::TypeBoundKind::PathType(path_type) => { let path = match path_type.path() { diff --git a/crates/ra_hir_expand/Cargo.toml b/crates/ra_hir_expand/Cargo.toml new file mode 100644 index 000000000000..9bf5b7918751 --- /dev/null +++ b/crates/ra_hir_expand/Cargo.toml @@ -0,0 +1,15 @@ +[package] +edition = "2018" +name = "ra_hir_expand" +version = "0.1.0" +authors = ["rust-analyzer developers"] + +[dependencies] +log = "0.4.5" + +ra_arena = { path = "../ra_arena" } +ra_db = { path = "../ra_db" } +ra_syntax = { path = "../ra_syntax" } +ra_prof = { path = "../ra_prof" } +tt = { path = "../ra_tt", package = "ra_tt" } +mbe = { path = "../ra_mbe", package = "ra_mbe" } diff --git a/crates/ra_hir/src/source_id.rs b/crates/ra_hir_expand/src/ast_id_map.rs similarity index 53% rename from crates/ra_hir/src/source_id.rs rename to crates/ra_hir_expand/src/ast_id_map.rs index a4dd99598c58..cb464c3ff17d 100644 --- a/crates/ra_hir/src/source_id.rs +++ b/crates/ra_hir_expand/src/ast_id_map.rs @@ -1,58 +1,21 @@ -//! FIXME: write short doc here +//! `AstIdMap` allows to create stable IDs for "large" syntax nodes like items +//! and macro calls. +//! +//! Specifically, it enumerates all items in a file and uses position of a an +//! item as an ID. That way, id's don't change unless the set of items itself +//! changes. use std::{ hash::{Hash, Hasher}, marker::PhantomData, - sync::Arc, }; use ra_arena::{impl_arena_id, Arena, RawId}; -use ra_syntax::{ast, AstNode, SyntaxNode, SyntaxNodePtr}; - -use crate::{db::AstDatabase, HirFileId}; - -/// `AstId` points to an AST node in any file. -/// -/// It is stable across reparses, and can be used as salsa key/value. -#[derive(Debug)] -pub(crate) struct AstId { - file_id: HirFileId, - file_ast_id: FileAstId, -} - -impl Clone for AstId { - fn clone(&self) -> AstId { - *self - } -} -impl Copy for AstId {} - -impl PartialEq for AstId { - fn eq(&self, other: &Self) -> bool { - (self.file_id, self.file_ast_id) == (other.file_id, other.file_ast_id) - } -} -impl Eq for AstId {} -impl Hash for AstId { - fn hash(&self, hasher: &mut H) { - (self.file_id, self.file_ast_id).hash(hasher); - } -} - -impl AstId { - pub(crate) fn file_id(&self) -> HirFileId { - self.file_id - } - - pub(crate) fn to_node(&self, db: &impl AstDatabase) -> N { - let syntax_node = db.ast_id_to_node(self.file_id, self.file_ast_id.raw); - N::cast(syntax_node).unwrap() - } -} +use ra_syntax::{ast, AstNode, AstPtr, SyntaxNode, SyntaxNodePtr}; /// `AstId` points to an AST node in a specific file. #[derive(Debug)] -pub(crate) struct FileAstId { +pub struct FileAstId { raw: ErasedFileAstId, _ty: PhantomData N>, } @@ -76,14 +39,8 @@ impl Hash for FileAstId { } } -impl FileAstId { - pub(crate) fn with_file_id(self, file_id: HirFileId) -> AstId { - AstId { file_id, file_ast_id: self } - } -} - #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct ErasedFileAstId(RawId); +struct ErasedFileAstId(RawId); impl_arena_id!(ErasedFileAstId); /// Maps items' `SyntaxNode`s to `ErasedFileAstId`s and back. @@ -93,39 +50,7 @@ pub struct AstIdMap { } impl AstIdMap { - pub(crate) fn ast_id_map_query(db: &impl AstDatabase, file_id: HirFileId) -> Arc { - let map = if let Some(node) = db.parse_or_expand(file_id) { - AstIdMap::from_source(&node) - } else { - AstIdMap::default() - }; - Arc::new(map) - } - - pub(crate) fn file_item_query( - db: &impl AstDatabase, - file_id: HirFileId, - ast_id: ErasedFileAstId, - ) -> SyntaxNode { - let node = db.parse_or_expand(file_id).unwrap(); - db.ast_id_map(file_id).arena[ast_id].to_node(&node) - } - - pub(crate) fn ast_id(&self, item: &N) -> FileAstId { - let ptr = SyntaxNodePtr::new(item.syntax()); - let raw = match self.arena.iter().find(|(_id, i)| **i == ptr) { - Some((it, _)) => it, - None => panic!( - "Can't find {:?} in AstIdMap:\n{:?}", - item.syntax(), - self.arena.iter().map(|(_id, i)| i).collect::>(), - ), - }; - - FileAstId { raw, _ty: PhantomData } - } - - fn from_source(node: &SyntaxNode) -> AstIdMap { + pub(crate) fn from_source(node: &SyntaxNode) -> AstIdMap { assert!(node.parent().is_none()); let mut res = AstIdMap { arena: Arena::default() }; // By walking the tree in bread-first order we make sure that parents @@ -142,6 +67,26 @@ impl AstIdMap { res } + pub fn ast_id(&self, item: &N) -> FileAstId { + let raw = self.erased_ast_id(item.syntax()); + FileAstId { raw, _ty: PhantomData } + } + fn erased_ast_id(&self, item: &SyntaxNode) -> ErasedFileAstId { + let ptr = SyntaxNodePtr::new(item); + match self.arena.iter().find(|(_id, i)| **i == ptr) { + Some((it, _)) => it, + None => panic!( + "Can't find {:?} in AstIdMap:\n{:?}", + item, + self.arena.iter().map(|(_id, i)| i).collect::>(), + ), + } + } + + pub(crate) fn get(&self, id: FileAstId) -> AstPtr { + self.arena[id.raw].cast::().unwrap() + } + fn alloc(&mut self, item: &SyntaxNode) -> ErasedFileAstId { self.arena.alloc(SyntaxNodePtr::new(item)) } diff --git a/crates/ra_hir_expand/src/db.rs b/crates/ra_hir_expand/src/db.rs new file mode 100644 index 000000000000..a4ee9a529a1c --- /dev/null +++ b/crates/ra_hir_expand/src/db.rs @@ -0,0 +1,104 @@ +//! Defines database & queries for macro expansion. + +use std::sync::Arc; + +use mbe::MacroRules; +use ra_db::{salsa, SourceDatabase}; +use ra_prof::profile; +use ra_syntax::{AstNode, Parse, SyntaxNode}; + +use crate::{ + ast_id_map::AstIdMap, HirFileId, HirFileIdRepr, MacroCallId, MacroCallLoc, MacroDefId, + MacroFile, MacroFileKind, +}; + +// FIXME: rename to ExpandDatabase +#[salsa::query_group(AstDatabaseStorage)] +pub trait AstDatabase: SourceDatabase { + fn ast_id_map(&self, file_id: HirFileId) -> Arc; + + #[salsa::transparent] + fn parse_or_expand(&self, file_id: HirFileId) -> Option; + + #[salsa::interned] + fn intern_macro(&self, macro_call: MacroCallLoc) -> MacroCallId; + fn macro_arg(&self, id: MacroCallId) -> Option>; + fn macro_def(&self, id: MacroDefId) -> Option>; + fn parse_macro(&self, macro_file: MacroFile) -> Option>; + fn macro_expand(&self, macro_call: MacroCallId) -> Result, String>; +} + +pub(crate) fn ast_id_map(db: &dyn AstDatabase, file_id: HirFileId) -> Arc { + let map = + db.parse_or_expand(file_id).map_or_else(AstIdMap::default, |it| AstIdMap::from_source(&it)); + Arc::new(map) +} + +pub(crate) fn macro_def(db: &dyn AstDatabase, id: MacroDefId) -> Option> { + let macro_call = id.ast_id.to_node(db); + let arg = macro_call.token_tree()?; + let (tt, _) = mbe::ast_to_token_tree(&arg).or_else(|| { + log::warn!("fail on macro_def to token tree: {:#?}", arg); + None + })?; + let rules = MacroRules::parse(&tt).ok().or_else(|| { + log::warn!("fail on macro_def parse: {:#?}", tt); + None + })?; + Some(Arc::new(rules)) +} + +pub(crate) fn macro_arg(db: &dyn AstDatabase, id: MacroCallId) -> Option> { + let loc = db.lookup_intern_macro(id); + let macro_call = loc.ast_id.to_node(db); + let arg = macro_call.token_tree()?; + let (tt, _) = mbe::ast_to_token_tree(&arg)?; + Some(Arc::new(tt)) +} + +pub(crate) fn macro_expand( + db: &dyn AstDatabase, + id: MacroCallId, +) -> Result, String> { + let loc = db.lookup_intern_macro(id); + let macro_arg = db.macro_arg(id).ok_or("Fail to args in to tt::TokenTree")?; + + let macro_rules = db.macro_def(loc.def).ok_or("Fail to find macro definition")?; + let tt = macro_rules.expand(¯o_arg).map_err(|err| format!("{:?}", err))?; + // Set a hard limit for the expanded tt + let count = tt.count(); + if count > 65536 { + return Err(format!("Total tokens count exceed limit : count = {}", count)); + } + Ok(Arc::new(tt)) +} + +pub(crate) fn parse_or_expand(db: &dyn AstDatabase, file_id: HirFileId) -> Option { + match file_id.0 { + HirFileIdRepr::FileId(file_id) => Some(db.parse(file_id).tree().syntax().clone()), + HirFileIdRepr::MacroFile(macro_file) => { + db.parse_macro(macro_file).map(|it| it.syntax_node()) + } + } +} + +pub(crate) fn parse_macro( + db: &dyn AstDatabase, + macro_file: MacroFile, +) -> Option> { + let _p = profile("parse_macro_query"); + let macro_call_id = macro_file.macro_call_id; + let tt = db + .macro_expand(macro_call_id) + .map_err(|err| { + // Note: + // The final goal we would like to make all parse_macro success, + // such that the following log will not call anyway. + log::warn!("fail on macro_parse: (reason: {})", err,); + }) + .ok()?; + match macro_file.macro_file_kind { + MacroFileKind::Items => mbe::token_tree_to_items(&tt).ok().map(Parse::to_syntax), + MacroFileKind::Expr => mbe::token_tree_to_expr(&tt).ok().map(Parse::to_syntax), + } +} diff --git a/crates/ra_hir/src/either.rs b/crates/ra_hir_expand/src/either.rs similarity index 100% rename from crates/ra_hir/src/either.rs rename to crates/ra_hir_expand/src/either.rs diff --git a/crates/ra_hir_expand/src/hygiene.rs b/crates/ra_hir_expand/src/hygiene.rs new file mode 100644 index 000000000000..77428ec99049 --- /dev/null +++ b/crates/ra_hir_expand/src/hygiene.rs @@ -0,0 +1,46 @@ +//! This modules handles hygiene information. +//! +//! Specifically, `ast` + `Hygiene` allows you to create a `Name`. Note that, at +//! this moment, this is horribly incomplete and handles only `$crate`. +use ra_db::CrateId; +use ra_syntax::ast; + +use crate::{ + db::AstDatabase, + either::Either, + name::{AsName, Name}, + HirFileId, HirFileIdRepr, +}; + +#[derive(Debug)] +pub struct Hygiene { + // This is what `$crate` expands to + def_crate: Option, +} + +impl Hygiene { + pub fn new(db: &impl AstDatabase, file_id: HirFileId) -> Hygiene { + let def_crate = match file_id.0 { + HirFileIdRepr::FileId(_) => None, + HirFileIdRepr::MacroFile(macro_file) => { + let loc = db.lookup_intern_macro(macro_file.macro_call_id); + Some(loc.def.krate) + } + }; + Hygiene { def_crate } + } + + pub fn new_unhygienic() -> Hygiene { + Hygiene { def_crate: None } + } + + // FIXME: this should just return name + pub fn name_ref_to_name(&self, name_ref: ast::NameRef) -> Either { + if let Some(def_crate) = self.def_crate { + if name_ref.text() == "$crate" { + return Either::B(def_crate); + } + } + Either::A(name_ref.as_name()) + } +} diff --git a/crates/ra_hir_expand/src/lib.rs b/crates/ra_hir_expand/src/lib.rs new file mode 100644 index 000000000000..5a0e5a19c533 --- /dev/null +++ b/crates/ra_hir_expand/src/lib.rs @@ -0,0 +1,153 @@ +//! `ra_hir_expand` deals with macro expansion. +//! +//! Specifically, it implements a concept of `MacroFile` -- a file whose syntax +//! tree originates not from the text of some `FileId`, but from some macro +//! expansion. + +pub mod db; +pub mod ast_id_map; +pub mod either; +pub mod name; +pub mod hygiene; + +use std::hash::{Hash, Hasher}; + +use ra_db::{salsa, CrateId, FileId}; +use ra_syntax::ast::{self, AstNode}; + +use crate::ast_id_map::FileAstId; + +/// Input to the analyzer is a set of files, where each file is identified by +/// `FileId` and contains source code. However, another source of source code in +/// Rust are macros: each macro can be thought of as producing a "temporary +/// file". To assign an id to such a file, we use the id of the macro call that +/// produced the file. So, a `HirFileId` is either a `FileId` (source code +/// written by user), or a `MacroCallId` (source code produced by macro). +/// +/// What is a `MacroCallId`? Simplifying, it's a `HirFileId` of a file +/// containing the call plus the offset of the macro call in the file. Note that +/// this is a recursive definition! However, the size_of of `HirFileId` is +/// finite (because everything bottoms out at the real `FileId`) and small +/// (`MacroCallId` uses the location interner). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct HirFileId(HirFileIdRepr); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +enum HirFileIdRepr { + FileId(FileId), + MacroFile(MacroFile), +} + +impl From for HirFileId { + fn from(id: FileId) -> Self { + HirFileId(HirFileIdRepr::FileId(id)) + } +} + +impl From for HirFileId { + fn from(id: MacroFile) -> Self { + HirFileId(HirFileIdRepr::MacroFile(id)) + } +} + +impl HirFileId { + /// For macro-expansion files, returns the file original source file the + /// expansion originated from. + pub fn original_file(self, db: &dyn db::AstDatabase) -> FileId { + match self.0 { + HirFileIdRepr::FileId(file_id) => file_id, + HirFileIdRepr::MacroFile(macro_file) => { + let loc = db.lookup_intern_macro(macro_file.macro_call_id); + loc.ast_id.file_id().original_file(db) + } + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct MacroFile { + macro_call_id: MacroCallId, + macro_file_kind: MacroFileKind, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum MacroFileKind { + Items, + Expr, +} + +/// `MacroCallId` identifies a particular macro invocation, like +/// `println!("Hello, {}", world)`. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct MacroCallId(salsa::InternId); +impl salsa::InternKey for MacroCallId { + fn from_intern_id(v: salsa::InternId) -> Self { + MacroCallId(v) + } + fn as_intern_id(&self) -> salsa::InternId { + self.0 + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct MacroDefId { + pub krate: CrateId, + pub ast_id: AstId, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct MacroCallLoc { + pub def: MacroDefId, + pub ast_id: AstId, +} + +impl MacroCallId { + pub fn as_file(self, kind: MacroFileKind) -> HirFileId { + let macro_file = MacroFile { macro_call_id: self, macro_file_kind: kind }; + macro_file.into() + } +} + +/// `AstId` points to an AST node in any file. +/// +/// It is stable across reparses, and can be used as salsa key/value. +// FIXME: isn't this just a `Source>` ? +#[derive(Debug)] +pub struct AstId { + file_id: HirFileId, + file_ast_id: FileAstId, +} + +impl Clone for AstId { + fn clone(&self) -> AstId { + *self + } +} +impl Copy for AstId {} + +impl PartialEq for AstId { + fn eq(&self, other: &Self) -> bool { + (self.file_id, self.file_ast_id) == (other.file_id, other.file_ast_id) + } +} +impl Eq for AstId {} +impl Hash for AstId { + fn hash(&self, hasher: &mut H) { + (self.file_id, self.file_ast_id).hash(hasher); + } +} + +impl AstId { + pub fn new(file_id: HirFileId, file_ast_id: FileAstId) -> AstId { + AstId { file_id, file_ast_id } + } + + pub fn file_id(&self) -> HirFileId { + self.file_id + } + + pub fn to_node(&self, db: &dyn db::AstDatabase) -> N { + let root = db.parse_or_expand(self.file_id).unwrap(); + db.ast_id_map(self.file_id).get(self.file_ast_id).to_node(&root) + } +} diff --git a/crates/ra_hir/src/name.rs b/crates/ra_hir_expand/src/name.rs similarity index 50% rename from crates/ra_hir/src/name.rs rename to crates/ra_hir_expand/src/name.rs index 1e0b8c3506ff..720896ee8ebf 100644 --- a/crates/ra_hir/src/name.rs +++ b/crates/ra_hir_expand/src/name.rs @@ -33,7 +33,7 @@ impl Name { Name(Repr::Text(text)) } - pub(crate) fn new_tuple_field(idx: usize) -> Name { + pub fn new_tuple_field(idx: usize) -> Name { Name(Repr::TupleField(idx)) } @@ -52,11 +52,11 @@ impl Name { } } - pub(crate) fn missing() -> Name { + pub fn missing() -> Name { Name::new_text("[missing name]".into()) } - pub(crate) fn as_tuple_index(&self) -> Option { + pub fn as_tuple_index(&self) -> Option { match self.0 { Repr::TupleField(idx) => Some(idx), _ => None, @@ -64,7 +64,7 @@ impl Name { } } -pub(crate) trait AsName { +pub trait AsName { fn as_name(&self) -> Name; } @@ -99,44 +99,44 @@ impl AsName for ra_db::Dependency { } // Primitives -pub(crate) const ISIZE: Name = Name::new_inline_ascii(5, b"isize"); -pub(crate) const I8: Name = Name::new_inline_ascii(2, b"i8"); -pub(crate) const I16: Name = Name::new_inline_ascii(3, b"i16"); -pub(crate) const I32: Name = Name::new_inline_ascii(3, b"i32"); -pub(crate) const I64: Name = Name::new_inline_ascii(3, b"i64"); -pub(crate) const I128: Name = Name::new_inline_ascii(4, b"i128"); -pub(crate) const USIZE: Name = Name::new_inline_ascii(5, b"usize"); -pub(crate) const U8: Name = Name::new_inline_ascii(2, b"u8"); -pub(crate) const U16: Name = Name::new_inline_ascii(3, b"u16"); -pub(crate) const U32: Name = Name::new_inline_ascii(3, b"u32"); -pub(crate) const U64: Name = Name::new_inline_ascii(3, b"u64"); -pub(crate) const U128: Name = Name::new_inline_ascii(4, b"u128"); -pub(crate) const F32: Name = Name::new_inline_ascii(3, b"f32"); -pub(crate) const F64: Name = Name::new_inline_ascii(3, b"f64"); -pub(crate) const BOOL: Name = Name::new_inline_ascii(4, b"bool"); -pub(crate) const CHAR: Name = Name::new_inline_ascii(4, b"char"); -pub(crate) const STR: Name = Name::new_inline_ascii(3, b"str"); +pub const ISIZE: Name = Name::new_inline_ascii(5, b"isize"); +pub const I8: Name = Name::new_inline_ascii(2, b"i8"); +pub const I16: Name = Name::new_inline_ascii(3, b"i16"); +pub const I32: Name = Name::new_inline_ascii(3, b"i32"); +pub const I64: Name = Name::new_inline_ascii(3, b"i64"); +pub const I128: Name = Name::new_inline_ascii(4, b"i128"); +pub const USIZE: Name = Name::new_inline_ascii(5, b"usize"); +pub const U8: Name = Name::new_inline_ascii(2, b"u8"); +pub const U16: Name = Name::new_inline_ascii(3, b"u16"); +pub const U32: Name = Name::new_inline_ascii(3, b"u32"); +pub const U64: Name = Name::new_inline_ascii(3, b"u64"); +pub const U128: Name = Name::new_inline_ascii(4, b"u128"); +pub const F32: Name = Name::new_inline_ascii(3, b"f32"); +pub const F64: Name = Name::new_inline_ascii(3, b"f64"); +pub const BOOL: Name = Name::new_inline_ascii(4, b"bool"); +pub const CHAR: Name = Name::new_inline_ascii(4, b"char"); +pub const STR: Name = Name::new_inline_ascii(3, b"str"); // Special names -pub(crate) const SELF_PARAM: Name = Name::new_inline_ascii(4, b"self"); -pub(crate) const SELF_TYPE: Name = Name::new_inline_ascii(4, b"Self"); -pub(crate) const MACRO_RULES: Name = Name::new_inline_ascii(11, b"macro_rules"); +pub const SELF_PARAM: Name = Name::new_inline_ascii(4, b"self"); +pub const SELF_TYPE: Name = Name::new_inline_ascii(4, b"Self"); +pub const MACRO_RULES: Name = Name::new_inline_ascii(11, b"macro_rules"); // Components of known path (value or mod name) -pub(crate) const STD: Name = Name::new_inline_ascii(3, b"std"); -pub(crate) const ITER: Name = Name::new_inline_ascii(4, b"iter"); -pub(crate) const OPS: Name = Name::new_inline_ascii(3, b"ops"); -pub(crate) const FUTURE: Name = Name::new_inline_ascii(6, b"future"); -pub(crate) const RESULT: Name = Name::new_inline_ascii(6, b"result"); -pub(crate) const BOXED: Name = Name::new_inline_ascii(5, b"boxed"); +pub const STD: Name = Name::new_inline_ascii(3, b"std"); +pub const ITER: Name = Name::new_inline_ascii(4, b"iter"); +pub const OPS: Name = Name::new_inline_ascii(3, b"ops"); +pub const FUTURE: Name = Name::new_inline_ascii(6, b"future"); +pub const RESULT: Name = Name::new_inline_ascii(6, b"result"); +pub const BOXED: Name = Name::new_inline_ascii(5, b"boxed"); // Components of known path (type name) -pub(crate) const INTO_ITERATOR_TYPE: Name = Name::new_inline_ascii(12, b"IntoIterator"); -pub(crate) const ITEM_TYPE: Name = Name::new_inline_ascii(4, b"Item"); -pub(crate) const TRY_TYPE: Name = Name::new_inline_ascii(3, b"Try"); -pub(crate) const OK_TYPE: Name = Name::new_inline_ascii(2, b"Ok"); -pub(crate) const FUTURE_TYPE: Name = Name::new_inline_ascii(6, b"Future"); -pub(crate) const RESULT_TYPE: Name = Name::new_inline_ascii(6, b"Result"); -pub(crate) const OUTPUT_TYPE: Name = Name::new_inline_ascii(6, b"Output"); -pub(crate) const TARGET_TYPE: Name = Name::new_inline_ascii(6, b"Target"); -pub(crate) const BOX_TYPE: Name = Name::new_inline_ascii(3, b"Box"); +pub const INTO_ITERATOR_TYPE: Name = Name::new_inline_ascii(12, b"IntoIterator"); +pub const ITEM_TYPE: Name = Name::new_inline_ascii(4, b"Item"); +pub const TRY_TYPE: Name = Name::new_inline_ascii(3, b"Try"); +pub const OK_TYPE: Name = Name::new_inline_ascii(2, b"Ok"); +pub const FUTURE_TYPE: Name = Name::new_inline_ascii(6, b"Future"); +pub const RESULT_TYPE: Name = Name::new_inline_ascii(6, b"Result"); +pub const OUTPUT_TYPE: Name = Name::new_inline_ascii(6, b"Output"); +pub const TARGET_TYPE: Name = Name::new_inline_ascii(6, b"Target"); +pub const BOX_TYPE: Name = Name::new_inline_ascii(3, b"Box"); diff --git a/crates/ra_ide_api/Cargo.toml b/crates/ra_ide_api/Cargo.toml index f66f0a6bade5..bf6ef12f3ffb 100644 --- a/crates/ra_ide_api/Cargo.toml +++ b/crates/ra_ide_api/Cargo.toml @@ -27,10 +27,13 @@ ra_db = { path = "../ra_db" } ra_cfg = { path = "../ra_cfg" } ra_fmt = { path = "../ra_fmt" } ra_prof = { path = "../ra_prof" } -hir = { path = "../ra_hir", package = "ra_hir" } test_utils = { path = "../test_utils" } ra_assists = { path = "../ra_assists" } +# ra_ide_api should depend only on the top-level `hir` package. if you need +# something from some `hir_xxx` subpackage, reexport the API via `hir`. +hir = { path = "../ra_hir", package = "ra_hir" } + [dev-dependencies] insta = "0.12.0" diff --git a/crates/ra_ide_api/src/call_info.rs b/crates/ra_ide_api/src/call_info.rs index 7d18be483c7b..3572825b57a6 100644 --- a/crates/ra_ide_api/src/call_info.rs +++ b/crates/ra_ide_api/src/call_info.rs @@ -2,9 +2,9 @@ use ra_db::SourceDatabase; use ra_syntax::{ - algo::find_node_at_offset, + algo::ancestors_at_offset, ast::{self, ArgListOwner}, - AstNode, SyntaxNode, TextUnit, + match_ast, AstNode, SyntaxNode, TextUnit, }; use test_utils::tested_by; @@ -20,24 +20,30 @@ pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option { //FIXME: apply subst let (callable_def, _subst) = analyzer.type_of(db, &expr.expr()?)?.as_callable()?; match callable_def { - hir::CallableDef::Function(it) => it, - //FIXME: handle other callables - _ => return None, + hir::CallableDef::Function(it) => { + (CallInfo::with_fn(db, it), it.data(db).has_self_param()) + } + hir::CallableDef::Struct(it) => (CallInfo::with_struct(db, it)?, false), + hir::CallableDef::EnumVariant(it) => (CallInfo::with_enum_variant(db, it)?, false), } } - FnCallNode::MethodCallExpr(expr) => analyzer.resolve_method_call(&expr)?, + FnCallNode::MethodCallExpr(expr) => { + let function = analyzer.resolve_method_call(&expr)?; + (CallInfo::with_fn(db, function), function.data(db).has_self_param()) + } + FnCallNode::MacroCallExpr(expr) => { + let macro_def = analyzer.resolve_macro_call(db, &expr)?; + (CallInfo::with_macro(db, macro_def)?, false) + } }; - let mut call_info = CallInfo::new(db, function); - // If we have a calling expression let's find which argument we are on let num_params = call_info.parameters().len(); - let has_self = function.data(db).has_self_param(); if num_params == 1 { if !has_self { @@ -75,20 +81,25 @@ pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option Option { - if let Some(expr) = find_node_at_offset::(syntax, offset) { - return Some(FnCallNode::CallExpr(expr)); - } - if let Some(expr) = find_node_at_offset::(syntax, offset) { - return Some(FnCallNode::MethodCallExpr(expr)); - } - None + ancestors_at_offset(syntax, offset).find_map(|node| { + match_ast! { + match node { + ast::CallExpr(it) => { Some(FnCallNode::CallExpr(it)) }, + ast::MethodCallExpr(it) => { Some(FnCallNode::MethodCallExpr(it)) }, + ast::MacroCall(it) => { Some(FnCallNode::MacroCallExpr(it)) }, + _ => { None }, + } + } + }) } fn name_ref(&self) -> Option { @@ -101,6 +112,8 @@ impl FnCallNode { FnCallNode::MethodCallExpr(call_expr) => { call_expr.syntax().children().filter_map(ast::NameRef::cast).nth(0) } + + FnCallNode::MacroCallExpr(call_expr) => call_expr.path()?.segment()?.name_ref(), } } @@ -108,17 +121,36 @@ impl FnCallNode { match self { FnCallNode::CallExpr(expr) => expr.arg_list(), FnCallNode::MethodCallExpr(expr) => expr.arg_list(), + FnCallNode::MacroCallExpr(_) => None, } } } impl CallInfo { - fn new(db: &RootDatabase, function: hir::Function) -> Self { + fn with_fn(db: &RootDatabase, function: hir::Function) -> Self { let signature = FunctionSignature::from_hir(db, function); CallInfo { signature, active_parameter: None } } + fn with_struct(db: &RootDatabase, st: hir::Struct) -> Option { + let signature = FunctionSignature::from_struct(db, st)?; + + Some(CallInfo { signature, active_parameter: None }) + } + + fn with_enum_variant(db: &RootDatabase, variant: hir::EnumVariant) -> Option { + let signature = FunctionSignature::from_enum_variant(db, variant)?; + + Some(CallInfo { signature, active_parameter: None }) + } + + fn with_macro(db: &RootDatabase, macro_def: hir::MacroDef) -> Option { + let signature = FunctionSignature::from_macro(db, macro_def)?; + + Some(CallInfo { signature, active_parameter: None }) + } + fn parameters(&self) -> &[String] { &self.signature.parameters } @@ -415,6 +447,7 @@ pub fn foo(mut r: WriteHandler<()>) { "#, ); + assert_eq!(info.label(), "fn finished(&mut self, ctx: &mut Self::Context)".to_string()); assert_eq!(info.parameters(), ["&mut self", "ctx: &mut Self::Context"]); assert_eq!(info.active_parameter, Some(1)); assert_eq!( @@ -438,4 +471,118 @@ By default this method stops actor's `Context`."# let call_info = analysis.call_info(position).unwrap(); assert!(call_info.is_none()); } + + #[test] + fn test_nested_method_in_lamba() { + let info = call_info( + r#"struct Foo; + +impl Foo { + fn bar(&self, _: u32) { } +} + +fn bar(_: u32) { } + +fn main() { + let foo = Foo; + std::thread::spawn(move || foo.bar(<|>)); +}"#, + ); + + assert_eq!(info.parameters(), ["&self", "_: u32"]); + assert_eq!(info.active_parameter, Some(1)); + assert_eq!(info.label(), "fn bar(&self, _: u32)"); + } + + #[test] + fn works_for_tuple_structs() { + let info = call_info( + r#" +/// A cool tuple struct +struct TS(u32, i32); +fn main() { + let s = TS(0, <|>); +}"#, + ); + + assert_eq!(info.label(), "struct TS(u32, i32) -> TS"); + assert_eq!(info.doc().map(|it| it.into()), Some("A cool tuple struct".to_string())); + assert_eq!(info.active_parameter, Some(1)); + } + + #[test] + #[should_panic] + fn cant_call_named_structs() { + let _ = call_info( + r#" +struct TS { x: u32, y: i32 } +fn main() { + let s = TS(<|>); +}"#, + ); + } + + #[test] + fn works_for_enum_variants() { + let info = call_info( + r#" +enum E { + /// A Variant + A(i32), + /// Another + B, + /// And C + C { a: i32, b: i32 } +} + +fn main() { + let a = E::A(<|>); +} + "#, + ); + + assert_eq!(info.label(), "E::A(0: i32)"); + assert_eq!(info.doc().map(|it| it.into()), Some("A Variant".to_string())); + assert_eq!(info.active_parameter, Some(0)); + } + + #[test] + #[should_panic] + fn cant_call_enum_records() { + let _ = call_info( + r#" +enum E { + /// A Variant + A(i32), + /// Another + B, + /// And C + C { a: i32, b: i32 } +} + +fn main() { + let a = E::C(<|>); +} + "#, + ); + } + + #[test] + fn fn_signature_for_macro() { + let info = call_info( + r#" +/// empty macro +macro_rules! foo { + () => {} +} + +fn f() { + foo!(<|>); +} + "#, + ); + + assert_eq!(info.label(), "foo!()"); + assert_eq!(info.doc().map(|it| it.into()), Some("empty macro".to_string())); + } } diff --git a/crates/ra_ide_api/src/change.rs b/crates/ra_ide_api/src/change.rs index 050249c0e680..39c5946c7f18 100644 --- a/crates/ra_ide_api/src/change.rs +++ b/crates/ra_ide_api/src/change.rs @@ -43,7 +43,7 @@ impl fmt::Debug for AnalysisChange { if !self.libraries_added.is_empty() { d.field("libraries_added", &self.libraries_added.len()); } - if !self.crate_graph.is_some() { + if !self.crate_graph.is_none() { d.field("crate_graph", &self.crate_graph); } d.finish() diff --git a/crates/ra_ide_api/src/completion/complete_path.rs b/crates/ra_ide_api/src/completion/complete_path.rs index 23dece73c2fa..a58fdc036fef 100644 --- a/crates/ra_ide_api/src/completion/complete_path.rs +++ b/crates/ra_ide_api/src/completion/complete_path.rs @@ -50,7 +50,7 @@ pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) { hir::ModuleDef::TypeAlias(a) => a.ty(ctx.db), _ => unreachable!(), }; - let krate = ctx.module.and_then(|m| m.krate(ctx.db)); + let krate = ctx.module.map(|m| m.krate()); if let Some(krate) = krate { ty.iterate_impl_items(ctx.db, krate, |item| { match item { @@ -67,7 +67,7 @@ pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) { }); } } - _ => return, + _ => {} }; } diff --git a/crates/ra_ide_api/src/completion/complete_postfix.rs b/crates/ra_ide_api/src/completion/complete_postfix.rs index 555cecb73a58..60ed3518b89a 100644 --- a/crates/ra_ide_api/src/completion/complete_postfix.rs +++ b/crates/ra_ide_api/src/completion/complete_postfix.rs @@ -9,16 +9,14 @@ use crate::{ }; use hir::{Ty, TypeCtor}; use ra_syntax::{ast::AstNode, TextRange, TextUnit}; -use ra_text_edit::TextEditBuilder; +use ra_text_edit::TextEdit; fn postfix_snippet(ctx: &CompletionContext, label: &str, detail: &str, snippet: &str) -> Builder { let edit = { let receiver_range = ctx.dot_receiver.as_ref().expect("no receiver available").syntax().text_range(); let delete_range = TextRange::from_to(receiver_range.start(), ctx.source_range().end()); - let mut builder = TextEditBuilder::default(); - builder.replace(delete_range, snippet.to_string()); - builder.finish() + TextEdit::replace(delete_range, snippet.to_string()) }; CompletionItem::new(CompletionKind::Postfix, ctx.source_range(), label) .detail(detail) diff --git a/crates/ra_ide_api/src/completion/completion_item.rs b/crates/ra_ide_api/src/completion/completion_item.rs index 3e6933bc1cdc..5c9c44704b3b 100644 --- a/crates/ra_ide_api/src/completion/completion_item.rs +++ b/crates/ra_ide_api/src/completion/completion_item.rs @@ -4,7 +4,7 @@ use std::fmt; use hir::Documentation; use ra_syntax::TextRange; -use ra_text_edit::{TextEdit, TextEditBuilder}; +use ra_text_edit::TextEdit; /// `CompletionItem` describes a single completion variant in the editor pop-up. /// It is basically a POD with various properties. To construct a @@ -192,12 +192,10 @@ impl Builder { let label = self.label; let text_edit = match self.text_edit { Some(it) => it, - None => { - let mut builder = TextEditBuilder::default(); - builder - .replace(self.source_range, self.insert_text.unwrap_or_else(|| label.clone())); - builder.finish() - } + None => TextEdit::replace( + self.source_range, + self.insert_text.unwrap_or_else(|| label.clone()), + ), }; CompletionItem { diff --git a/crates/ra_ide_api/src/completion/presentation.rs b/crates/ra_ide_api/src/completion/presentation.rs index aed4ce6d44bc..65bb639ed508 100644 --- a/crates/ra_ide_api/src/completion/presentation.rs +++ b/crates/ra_ide_api/src/completion/presentation.rs @@ -136,7 +136,7 @@ impl Completions { for (idx, s) in docs.match_indices(¯o_name) { let (before, after) = (&docs[..idx], &docs[idx + s.len()..]); // Ensure to match the full word - if after.starts_with("!") + if after.starts_with('!') && before .chars() .rev() @@ -164,27 +164,32 @@ impl Completions { name: Option, macro_: hir::MacroDef, ) { - let ast_node = macro_.source(ctx.db).ast; - if let Some(name) = name { - let detail = macro_label(&ast_node); + let name = match name { + Some(it) => it, + None => return, + }; - let docs = macro_.docs(ctx.db); + let ast_node = macro_.source(ctx.db).ast; + let detail = macro_label(&ast_node); + + let docs = macro_.docs(ctx.db); + let macro_declaration = format!("{}!", name); + + let mut builder = + CompletionItem::new(CompletionKind::Reference, ctx.source_range(), ¯o_declaration) + .kind(CompletionItemKind::Macro) + .set_documentation(docs.clone()) + .detail(detail); + + builder = if ctx.use_item_syntax.is_some() { + builder.insert_text(name) + } else { let macro_braces_to_insert = self.guess_macro_braces(&name, docs.as_ref().map_or("", |s| s.as_str())); - let macro_declaration = name + "!"; + builder.insert_snippet(macro_declaration + macro_braces_to_insert) + }; - let builder = CompletionItem::new( - CompletionKind::Reference, - ctx.source_range(), - ¯o_declaration, - ) - .kind(CompletionItemKind::Macro) - .set_documentation(docs) - .detail(detail) - .insert_snippet(macro_declaration + macro_braces_to_insert); - - self.add(builder); - } + self.add(builder); } fn add_function_with_name( @@ -220,7 +225,7 @@ impl Completions { } else { (format!("{}($0)", data.name()), format!("{}(…)", name)) }; - builder = builder.lookup_by(name.clone()).label(label).insert_snippet(snippet); + builder = builder.lookup_by(name).label(label).insert_snippet(snippet); } self.add(builder) @@ -281,10 +286,11 @@ fn has_non_default_type_params(def: hir::GenericDef, db: &db::RootDatabase) -> b #[cfg(test)] mod tests { - use crate::completion::{do_completion, CompletionItem, CompletionKind}; use insta::assert_debug_snapshot; use test_utils::covers; + use crate::completion::{do_completion, CompletionItem, CompletionKind}; + fn do_reference_completion(code: &str) -> Vec { do_completion(code, CompletionKind::Reference) } @@ -576,4 +582,34 @@ mod tests { "### ); } + + #[test] + fn dont_insert_macro_call_braces_in_use() { + assert_debug_snapshot!( + do_reference_completion( + r" + //- /main.rs + use foo::<|>; + + //- /foo/lib.rs + #[macro_export] + macro_rules frobnicate { + () => () + } + " + ), + @r###" + [ + CompletionItem { + label: "frobnicate!", + source_range: [9; 9), + delete: [9; 9), + insert: "frobnicate", + kind: Macro, + detail: "#[macro_export]\nmacro_rules! frobnicate", + }, + ] + "### + ) + } } diff --git a/crates/ra_ide_api/src/db.rs b/crates/ra_ide_api/src/db.rs index 9146b647a8e3..785e71808b7c 100644 --- a/crates/ra_ide_api/src/db.rs +++ b/crates/ra_ide_api/src/db.rs @@ -23,6 +23,7 @@ use crate::{ hir::db::InternDatabaseStorage, hir::db::AstDatabaseStorage, hir::db::DefDatabaseStorage, + hir::db::DefDatabase2Storage, hir::db::HirDatabaseStorage )] #[derive(Debug)] diff --git a/crates/ra_ide_api/src/diagnostics.rs b/crates/ra_ide_api/src/diagnostics.rs index 8743a3a79831..1f1f5cd742c9 100644 --- a/crates/ra_ide_api/src/diagnostics.rs +++ b/crates/ra_ide_api/src/diagnostics.rs @@ -85,10 +85,9 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec }) .on::(|d| { let node = d.ast(db); - let mut builder = TextEditBuilder::default(); let replacement = format!("Ok({})", node.syntax()); - builder.replace(node.syntax().text_range(), replacement); - let fix = SourceChange::source_file_edit_from("wrap with ok", file_id, builder.finish()); + let edit = TextEdit::replace(node.syntax().text_range(), replacement); + let fix = SourceChange::source_file_edit_from("wrap with ok", file_id, edit); res.borrow_mut().push(Diagnostic { range: d.highlight_range(), message: d.message(), @@ -152,9 +151,7 @@ fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement( let start = use_tree_list_node.prev_sibling_or_token()?.text_range().start(); let end = use_tree_list_node.text_range().end(); let range = TextRange::from_to(start, end); - let mut edit_builder = TextEditBuilder::default(); - edit_builder.delete(range); - return Some(edit_builder.finish()); + return Some(TextEdit::delete(range)); } None } diff --git a/crates/ra_ide_api/src/display/function_signature.rs b/crates/ra_ide_api/src/display/function_signature.rs index 43f022ccd46a..9075ca443c3f 100644 --- a/crates/ra_ide_api/src/display/function_signature.rs +++ b/crates/ra_ide_api/src/display/function_signature.rs @@ -2,7 +2,7 @@ use std::fmt::{self, Display}; -use hir::{Docs, Documentation, HasSource}; +use hir::{Docs, Documentation, HasSource, HirDisplay}; use join_to_string::join; use ra_syntax::ast::{self, AstNode, NameOwner, VisibilityOwner}; use std::convert::From; @@ -12,9 +12,18 @@ use crate::{ display::{generic_parameters, where_predicates}, }; +#[derive(Debug)] +pub enum CallableKind { + Function, + StructConstructor, + VariantConstructor, + Macro, +} + /// Contains information about a function signature #[derive(Debug)] pub struct FunctionSignature { + pub kind: CallableKind, /// Optional visibility pub visibility: Option, /// Name of the function @@ -42,6 +51,99 @@ impl FunctionSignature { let ast_node = function.source(db).ast; FunctionSignature::from(&ast_node).with_doc_opt(doc) } + + pub(crate) fn from_struct(db: &db::RootDatabase, st: hir::Struct) -> Option { + let node: ast::StructDef = st.source(db).ast; + match node.kind() { + ast::StructKind::Named(_) => return None, + _ => (), + }; + + let params = st + .fields(db) + .into_iter() + .map(|field: hir::StructField| { + let ty = field.ty(db); + format!("{}", ty.display(db)) + }) + .collect(); + + Some( + FunctionSignature { + kind: CallableKind::StructConstructor, + visibility: node.visibility().map(|n| n.syntax().text().to_string()), + name: node.name().map(|n| n.text().to_string()), + ret_type: node.name().map(|n| n.text().to_string()), + parameters: params, + generic_parameters: generic_parameters(&node), + where_predicates: where_predicates(&node), + doc: None, + } + .with_doc_opt(st.docs(db)), + ) + } + + pub(crate) fn from_enum_variant( + db: &db::RootDatabase, + variant: hir::EnumVariant, + ) -> Option { + let node: ast::EnumVariant = variant.source(db).ast; + match node.kind() { + ast::StructKind::Named(_) | ast::StructKind::Unit => return None, + _ => (), + }; + + let parent_name = match variant.parent_enum(db).name(db) { + Some(name) => name.to_string(), + None => "missing".into(), + }; + + let name = format!("{}::{}", parent_name, variant.name(db).unwrap()); + + let params = variant + .fields(db) + .into_iter() + .map(|field: hir::StructField| { + let name = field.name(db); + let ty = field.ty(db); + format!("{}: {}", name, ty.display(db)) + }) + .collect(); + + Some( + FunctionSignature { + kind: CallableKind::VariantConstructor, + visibility: None, + name: Some(name), + ret_type: None, + parameters: params, + generic_parameters: vec![], + where_predicates: vec![], + doc: None, + } + .with_doc_opt(variant.docs(db)), + ) + } + + pub(crate) fn from_macro(db: &db::RootDatabase, macro_def: hir::MacroDef) -> Option { + let node: ast::MacroCall = macro_def.source(db).ast; + + let params = vec![]; + + Some( + FunctionSignature { + kind: CallableKind::Macro, + visibility: None, + name: node.name().map(|n| n.text().to_string()), + ret_type: None, + parameters: params, + generic_parameters: vec![], + where_predicates: vec![], + doc: None, + } + .with_doc_opt(macro_def.docs(db)), + ) + } } impl From<&'_ ast::FnDef> for FunctionSignature { @@ -59,6 +161,7 @@ impl From<&'_ ast::FnDef> for FunctionSignature { } FunctionSignature { + kind: CallableKind::Function, visibility: node.visibility().map(|n| n.syntax().text().to_string()), name: node.name().map(|n| n.text().to_string()), ret_type: node @@ -81,7 +184,12 @@ impl Display for FunctionSignature { } if let Some(name) = &self.name { - write!(f, "fn {}", name)?; + match self.kind { + CallableKind::Function => write!(f, "fn {}", name)?, + CallableKind::StructConstructor => write!(f, "struct {}", name)?, + CallableKind::VariantConstructor => write!(f, "{}", name)?, + CallableKind::Macro => write!(f, "{}!", name)?, + } } if !self.generic_parameters.is_empty() { diff --git a/crates/ra_ide_api/src/extend_selection.rs b/crates/ra_ide_api/src/extend_selection.rs index 602757e92eb0..4b7bfc0b11e8 100644 --- a/crates/ra_ide_api/src/extend_selection.rs +++ b/crates/ra_ide_api/src/extend_selection.rs @@ -5,7 +5,7 @@ use ra_syntax::{ algo::find_covering_element, ast::{self, AstNode, AstToken}, Direction, NodeOrToken, - SyntaxKind::*, + SyntaxKind::{self, *}, SyntaxNode, SyntaxToken, TextRange, TextUnit, TokenAtOffset, T, }; @@ -29,10 +29,12 @@ fn try_extend_selection(root: &SyntaxNode, range: TextRange) -> Option(l: SyntaxToken, r: SyntaxToken) -> SyntaxToken { } } -/// Extend list item selection to include nearby comma and whitespace. +/// Extend list item selection to include nearby delimiter and whitespace. fn extend_list_item(node: &SyntaxNode) -> Option { fn is_single_line_ws(node: &SyntaxToken) -> bool { node.kind() == WHITESPACE && !node.text().contains('\n') } - fn nearby_comma(node: &SyntaxNode, dir: Direction) -> Option { + fn nearby_delimiter( + delimiter_kind: SyntaxKind, + node: &SyntaxNode, + dir: Direction, + ) -> Option { node.siblings_with_tokens(dir) .skip(1) .skip_while(|node| match node { @@ -161,19 +167,26 @@ fn extend_list_item(node: &SyntaxNode) -> Option { }) .next() .and_then(|it| it.into_token()) - .filter(|node| node.kind() == T![,]) + .filter(|node| node.kind() == delimiter_kind) } - if let Some(comma_node) = nearby_comma(node, Direction::Prev) { - return Some(TextRange::from_to(comma_node.text_range().start(), node.text_range().end())); + let delimiter = match node.kind() { + TYPE_BOUND => T![+], + _ => T![,], + }; + if let Some(delimiter_node) = nearby_delimiter(delimiter, node, Direction::Prev) { + return Some(TextRange::from_to( + delimiter_node.text_range().start(), + node.text_range().end(), + )); } - if let Some(comma_node) = nearby_comma(node, Direction::Next) { - // Include any following whitespace when comma if after list item. - let final_node = comma_node + if let Some(delimiter_node) = nearby_delimiter(delimiter, node, Direction::Next) { + // Include any following whitespace when delimiter is after list item. + let final_node = delimiter_node .next_sibling_or_token() .and_then(|it| it.into_token()) .filter(|node| is_single_line_ws(node)) - .unwrap_or(comma_node); + .unwrap_or(delimiter_node); return Some(TextRange::from_to(node.text_range().start(), final_node.text_range().end())); } @@ -387,4 +400,53 @@ fn bar(){} &["foo", "\" fn foo() {\""], ); } + + #[test] + fn test_extend_trait_bounds_list_in_where_clause() { + do_check( + r#" +fn foo() + where + R: req::Request + 'static, + R::Params: DeserializeOwned<|> + panic::UnwindSafe + 'static, + R::Result: Serialize + 'static, +"#, + &[ + "DeserializeOwned", + "DeserializeOwned + ", + "DeserializeOwned + panic::UnwindSafe + 'static", + "R::Params: DeserializeOwned + panic::UnwindSafe + 'static", + "R::Params: DeserializeOwned + panic::UnwindSafe + 'static,", + ], + ); + do_check(r#"fn foo() where T: <|>Copy"#, &["Copy"]); + do_check(r#"fn foo() where T: <|>Copy + Display"#, &["Copy", "Copy + "]); + do_check(r#"fn foo() where T: <|>Copy +Display"#, &["Copy", "Copy +"]); + do_check(r#"fn foo() where T: <|>Copy+Display"#, &["Copy", "Copy+"]); + do_check(r#"fn foo() where T: Copy + <|>Display"#, &["Display", "+ Display"]); + do_check(r#"fn foo() where T: Copy + <|>Display + Sync"#, &["Display", "+ Display"]); + do_check(r#"fn foo() where T: Copy +<|>Display"#, &["Display", "+Display"]); + } + + #[test] + fn test_extend_trait_bounds_list_inline() { + do_check(r#"fn fooCopy>() {}"#, &["Copy"]); + do_check(r#"fn fooCopy + Display>() {}"#, &["Copy", "Copy + "]); + do_check(r#"fn fooCopy +Display>() {}"#, &["Copy", "Copy +"]); + do_check(r#"fn fooCopy+Display>() {}"#, &["Copy", "Copy+"]); + do_check(r#"fn fooDisplay>() {}"#, &["Display", "+ Display"]); + do_check(r#"fn fooDisplay + Sync>() {}"#, &["Display", "+ Display"]); + do_check(r#"fn fooDisplay>() {}"#, &["Display", "+Display"]); + do_check( + r#"fn foo + Display, U: Copy>() {}"#, + &[ + "Copy", + "Copy + ", + "Copy + Display", + "T: Copy + Display", + "T: Copy + Display, ", + "", + ], + ); + } } diff --git a/crates/ra_ide_api/src/impls.rs b/crates/ra_ide_api/src/impls.rs index 7fc1b1efa88d..b899ed3a5f78 100644 --- a/crates/ra_ide_api/src/impls.rs +++ b/crates/ra_ide_api/src/impls.rs @@ -51,7 +51,7 @@ fn impls_for_def( } }; - let krate = module.krate(db)?; + let krate = module.krate(); let impls = db.impls_in_crate(krate); Some( @@ -72,7 +72,7 @@ fn impls_for_trait( let src = hir::Source { file_id: position.file_id.into(), ast: node.clone() }; let tr = hir::Trait::from_source(db, src)?; - let krate = module.krate(db)?; + let krate = module.krate(); let impls = db.impls_in_crate(krate); Some( diff --git a/crates/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs index 0832229fd17c..d0188da4493e 100644 --- a/crates/ra_ide_api/src/lib.rs +++ b/crates/ra_ide_api/src/lib.rs @@ -14,6 +14,7 @@ mod db; pub mod mock_analysis; mod symbol_index; mod change; +mod source_change; mod feature_flags; mod status; @@ -54,8 +55,6 @@ use ra_db::{ CheckCanceled, FileLoader, SourceDatabase, }; use ra_syntax::{SourceFile, TextRange, TextUnit}; -use ra_text_edit::TextEdit; -use relative_path::RelativePathBuf; use crate::{db::LineIndexDatabase, symbol_index::FileSymbol}; @@ -73,6 +72,7 @@ pub use crate::{ line_index_utils::translate_offset_with_edit, references::{ReferenceSearchResult, SearchScope}, runnables::{Runnable, RunnableKind}, + source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, syntax_highlighting::HighlightedRange, }; @@ -83,99 +83,6 @@ pub use ra_db::{ pub type Cancelable = Result; -#[derive(Debug)] -pub struct SourceChange { - pub label: String, - pub source_file_edits: Vec, - pub file_system_edits: Vec, - pub cursor_position: Option, -} - -impl SourceChange { - /// Creates a new SourceChange with the given label - /// from the edits. - pub(crate) fn from_edits>( - label: L, - source_file_edits: Vec, - file_system_edits: Vec, - ) -> Self { - SourceChange { - label: label.into(), - source_file_edits, - file_system_edits, - cursor_position: None, - } - } - - /// Creates a new SourceChange with the given label, - /// containing only the given `SourceFileEdits`. - pub(crate) fn source_file_edits>(label: L, edits: Vec) -> Self { - SourceChange { - label: label.into(), - source_file_edits: edits, - file_system_edits: vec![], - cursor_position: None, - } - } - - /// Creates a new SourceChange with the given label, - /// containing only the given `FileSystemEdits`. - pub(crate) fn file_system_edits>(label: L, edits: Vec) -> Self { - SourceChange { - label: label.into(), - source_file_edits: vec![], - file_system_edits: edits, - cursor_position: None, - } - } - - /// Creates a new SourceChange with the given label, - /// containing only a single `SourceFileEdit`. - pub(crate) fn source_file_edit>(label: L, edit: SourceFileEdit) -> Self { - SourceChange::source_file_edits(label, vec![edit]) - } - - /// Creates a new SourceChange with the given label - /// from the given `FileId` and `TextEdit` - pub(crate) fn source_file_edit_from>( - label: L, - file_id: FileId, - edit: TextEdit, - ) -> Self { - SourceChange::source_file_edit(label, SourceFileEdit { file_id, edit }) - } - - /// Creates a new SourceChange with the given label - /// from the given `FileId` and `TextEdit` - pub(crate) fn file_system_edit>(label: L, edit: FileSystemEdit) -> Self { - SourceChange::file_system_edits(label, vec![edit]) - } - - /// Sets the cursor position to the given `FilePosition` - pub(crate) fn with_cursor(mut self, cursor_position: FilePosition) -> Self { - self.cursor_position = Some(cursor_position); - self - } - - /// Sets the cursor position to the given `FilePosition` - pub(crate) fn with_cursor_opt(mut self, cursor_position: Option) -> Self { - self.cursor_position = cursor_position; - self - } -} - -#[derive(Debug)] -pub struct SourceFileEdit { - pub file_id: FileId, - pub edit: TextEdit, -} - -#[derive(Debug)] -pub enum FileSystemEdit { - CreateFile { source_root: SourceRootId, path: RelativePathBuf }, - MoveFile { src: FileId, dst_source_root: SourceRootId, dst_path: RelativePathBuf }, -} - #[derive(Debug)] pub struct Diagnostic { pub message: String, @@ -407,24 +314,20 @@ impl Analysis { self.with_db(|db| typing::on_enter(&db, position)) } - /// Returns an edit which should be applied after `=` was typed. Primarily, - /// this works when adding `let =`. - // FIXME: use a snippet completion instead of this hack here. - pub fn on_eq_typed(&self, position: FilePosition) -> Cancelable> { - self.with_db(|db| { - let parse = db.parse(position.file_id); - let file = parse.tree(); - let edit = typing::on_eq_typed(&file, position.offset)?; - Some(SourceChange::source_file_edit( - "add semicolon", - SourceFileEdit { edit, file_id: position.file_id }, - )) - }) - } - - /// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately. - pub fn on_dot_typed(&self, position: FilePosition) -> Cancelable> { - self.with_db(|db| typing::on_dot_typed(&db, position)) + /// Returns an edit which should be applied after a character was typed. + /// + /// This is useful for some on-the-fly fixups, like adding `;` to `let =` + /// automatically. + pub fn on_char_typed( + &self, + position: FilePosition, + char_typed: char, + ) -> Cancelable> { + // Fast path to not even parse the file. + if !typing::TRIGGER_CHARS.contains(char_typed) { + return Ok(None); + } + self.with_db(|db| typing::on_char_typed(&db, position, char_typed)) } /// Returns a tree representation of symbols in the file. Useful to draw a diff --git a/crates/ra_ide_api/src/parent_module.rs b/crates/ra_ide_api/src/parent_module.rs index 56650984995b..4c57566e2af9 100644 --- a/crates/ra_ide_api/src/parent_module.rs +++ b/crates/ra_ide_api/src/parent_module.rs @@ -27,10 +27,7 @@ pub(crate) fn crate_for(db: &RootDatabase, file_id: FileId) -> Vec { Some(it) => it, None => return Vec::new(), }; - let krate = match module.krate(db) { - Some(it) => it, - None => return Vec::new(), - }; + let krate = module.krate(); vec![krate.crate_id()] } diff --git a/crates/ra_ide_api/src/references/rename.rs b/crates/ra_ide_api/src/references/rename.rs index ee6e73e1bfdf..a8783d7a0dc4 100644 --- a/crates/ra_ide_api/src/references/rename.rs +++ b/crates/ra_ide_api/src/references/rename.rs @@ -3,6 +3,7 @@ use hir::ModuleSource; use ra_db::{SourceDatabase, SourceDatabaseExt}; use ra_syntax::{algo::find_node_at_offset, ast, AstNode, SyntaxNode}; +use ra_text_edit::TextEdit; use relative_path::{RelativePath, RelativePathBuf}; use crate::{ @@ -43,14 +44,7 @@ fn source_edit_from_file_id_range( range: TextRange, new_name: &str, ) -> SourceFileEdit { - SourceFileEdit { - file_id, - edit: { - let mut builder = ra_text_edit::TextEditBuilder::default(); - builder.replace(range, new_name.into()); - builder.finish() - }, - } + SourceFileEdit { file_id, edit: TextEdit::replace(range, new_name.into()) } } fn rename_mod( @@ -94,11 +88,7 @@ fn rename_mod( let edit = SourceFileEdit { file_id: position.file_id, - edit: { - let mut builder = ra_text_edit::TextEditBuilder::default(); - builder.replace(ast_name.syntax().text_range(), new_name.into()); - builder.finish() - }, + edit: TextEdit::replace(ast_name.syntax().text_range(), new_name.into()), }; source_file_edits.push(edit); @@ -126,12 +116,14 @@ fn rename_reference( #[cfg(test)] mod tests { + use insta::assert_debug_snapshot; + use ra_text_edit::TextEditBuilder; + use test_utils::assert_eq_text; + use crate::{ mock_analysis::analysis_and_position, mock_analysis::single_file_with_position, FileId, ReferenceSearchResult, }; - use insta::assert_debug_snapshot; - use test_utils::assert_eq_text; #[test] fn test_find_all_refs_for_local() { @@ -452,7 +444,7 @@ mod tests { fn test_rename(text: &str, new_name: &str, expected: &str) { let (analysis, position) = single_file_with_position(text); let source_change = analysis.rename(position, new_name).unwrap(); - let mut text_edit_builder = ra_text_edit::TextEditBuilder::default(); + let mut text_edit_builder = TextEditBuilder::default(); let mut file_id: Option = None; if let Some(change) = source_change { for edit in change.info.source_file_edits { diff --git a/crates/ra_ide_api/src/references/search_scope.rs b/crates/ra_ide_api/src/references/search_scope.rs index b6eb248b742b..f2789e0b2a43 100644 --- a/crates/ra_ide_api/src/references/search_scope.rs +++ b/crates/ra_ide_api/src/references/search_scope.rs @@ -111,8 +111,7 @@ impl NameDefinition { if vis.as_str() != "" { let source_root_id = db.file_source_root(file_id); let source_root = db.source_root(source_root_id); - let mut res = - source_root.walk().map(|id| (id.into(), None)).collect::>(); + let mut res = source_root.walk().map(|id| (id, None)).collect::>(); // FIXME: add "pub(in path)" @@ -120,7 +119,7 @@ impl NameDefinition { return SearchScope::new(res); } if vis.as_str() == "pub" { - let krate = self.container.krate(db).unwrap(); + let krate = self.container.krate(); let crate_graph = db.crate_graph(); for crate_id in crate_graph.iter() { let mut crate_deps = crate_graph.dependencies(crate_id); @@ -128,7 +127,7 @@ impl NameDefinition { let root_file = crate_graph.crate_root(crate_id); let source_root_id = db.file_source_root(root_file); let source_root = db.source_root(source_root_id); - res.extend(source_root.walk().map(|id| (id.into(), None))); + res.extend(source_root.walk().map(|id| (id, None))); } } return SearchScope::new(res); diff --git a/crates/ra_ide_api/src/runnables.rs b/crates/ra_ide_api/src/runnables.rs index 910883da7442..1b5c8deea0f8 100644 --- a/crates/ra_ide_api/src/runnables.rs +++ b/crates/ra_ide_api/src/runnables.rs @@ -4,7 +4,7 @@ use itertools::Itertools; use ra_db::SourceDatabase; use ra_syntax::{ ast::{self, AstNode, AttrsOwner, ModuleItemOwner, NameOwner}, - SyntaxNode, TextRange, + match_ast, SyntaxNode, TextRange, }; use crate::{db::RootDatabase, FileId}; @@ -29,12 +29,12 @@ pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec { } fn runnable(db: &RootDatabase, file_id: FileId, item: SyntaxNode) -> Option { - if let Some(fn_def) = ast::FnDef::cast(item.clone()) { - runnable_fn(fn_def) - } else if let Some(m) = ast::Module::cast(item) { - runnable_mod(db, file_id, m) - } else { - None + match_ast! { + match item { + ast::FnDef(it) => { runnable_fn(it) }, + ast::Module(it) => { runnable_mod(db, file_id, it) }, + _ => { None }, + } } } diff --git a/crates/ra_ide_api/src/source_change.rs b/crates/ra_ide_api/src/source_change.rs new file mode 100644 index 000000000000..4e63bbf6f0c8 --- /dev/null +++ b/crates/ra_ide_api/src/source_change.rs @@ -0,0 +1,119 @@ +//! This modules defines type to represent changes to the source code, that flow +//! from the server to the client. +//! +//! It can be viewed as a dual for `AnalysisChange`. + +use ra_text_edit::TextEdit; +use relative_path::RelativePathBuf; + +use crate::{FileId, FilePosition, SourceRootId, TextUnit}; + +#[derive(Debug)] +pub struct SourceChange { + pub label: String, + pub source_file_edits: Vec, + pub file_system_edits: Vec, + pub cursor_position: Option, +} + +impl SourceChange { + /// Creates a new SourceChange with the given label + /// from the edits. + pub(crate) fn from_edits>( + label: L, + source_file_edits: Vec, + file_system_edits: Vec, + ) -> Self { + SourceChange { + label: label.into(), + source_file_edits, + file_system_edits, + cursor_position: None, + } + } + + /// Creates a new SourceChange with the given label, + /// containing only the given `SourceFileEdits`. + pub(crate) fn source_file_edits>(label: L, edits: Vec) -> Self { + SourceChange { + label: label.into(), + source_file_edits: edits, + file_system_edits: vec![], + cursor_position: None, + } + } + + /// Creates a new SourceChange with the given label, + /// containing only the given `FileSystemEdits`. + pub(crate) fn file_system_edits>(label: L, edits: Vec) -> Self { + SourceChange { + label: label.into(), + source_file_edits: vec![], + file_system_edits: edits, + cursor_position: None, + } + } + + /// Creates a new SourceChange with the given label, + /// containing only a single `SourceFileEdit`. + pub(crate) fn source_file_edit>(label: L, edit: SourceFileEdit) -> Self { + SourceChange::source_file_edits(label, vec![edit]) + } + + /// Creates a new SourceChange with the given label + /// from the given `FileId` and `TextEdit` + pub(crate) fn source_file_edit_from>( + label: L, + file_id: FileId, + edit: TextEdit, + ) -> Self { + SourceChange::source_file_edit(label, SourceFileEdit { file_id, edit }) + } + + /// Creates a new SourceChange with the given label + /// from the given `FileId` and `TextEdit` + pub(crate) fn file_system_edit>(label: L, edit: FileSystemEdit) -> Self { + SourceChange::file_system_edits(label, vec![edit]) + } + + /// Sets the cursor position to the given `FilePosition` + pub(crate) fn with_cursor(mut self, cursor_position: FilePosition) -> Self { + self.cursor_position = Some(cursor_position); + self + } + + /// Sets the cursor position to the given `FilePosition` + pub(crate) fn with_cursor_opt(mut self, cursor_position: Option) -> Self { + self.cursor_position = cursor_position; + self + } +} + +#[derive(Debug)] +pub struct SourceFileEdit { + pub file_id: FileId, + pub edit: TextEdit, +} + +#[derive(Debug)] +pub enum FileSystemEdit { + CreateFile { source_root: SourceRootId, path: RelativePathBuf }, + MoveFile { src: FileId, dst_source_root: SourceRootId, dst_path: RelativePathBuf }, +} + +pub(crate) struct SingleFileChange { + pub label: String, + pub edit: TextEdit, + pub cursor_position: Option, +} + +impl SingleFileChange { + pub(crate) fn into_source_change(self, file_id: FileId) -> SourceChange { + SourceChange { + label: self.label, + source_file_edits: vec![SourceFileEdit { file_id, edit: self.edit }], + file_system_edits: Vec::new(), + cursor_position: self.cursor_position.map(|offset| FilePosition { file_id, offset }), + } + } +} diff --git a/crates/ra_ide_api/src/typing.rs b/crates/ra_ide_api/src/typing.rs index 2f5782012be8..d51132f73605 100644 --- a/crates/ra_ide_api/src/typing.rs +++ b/crates/ra_ide_api/src/typing.rs @@ -1,4 +1,17 @@ -//! FIXME: write short doc here +//! This module handles auto-magic editing actions applied together with users +//! edits. For example, if the user typed +//! +//! ```text +//! foo +//! .bar() +//! .baz() +//! | // <- cursor is here +//! ``` +//! +//! and types `.` next, we want to indent the dot. +//! +//! Language server executes such typing assists synchronously. That is, they +//! block user's typing and should be pretty fast for this reason! use ra_db::{FilePosition, SourceDatabase}; use ra_fmt::leading_indent; @@ -9,9 +22,9 @@ use ra_syntax::{ SyntaxKind::*, SyntaxToken, TextRange, TextUnit, TokenAtOffset, }; -use ra_text_edit::{TextEdit, TextEditBuilder}; +use ra_text_edit::TextEdit; -use crate::{db::RootDatabase, SourceChange, SourceFileEdit}; +use crate::{db::RootDatabase, source_change::SingleFileChange, SourceChange, SourceFileEdit}; pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option { let parse = db.parse(position.file_id); @@ -36,13 +49,12 @@ pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option Option { Some(text[pos..].into()) } -pub fn on_eq_typed(file: &SourceFile, eq_offset: TextUnit) -> Option { - assert_eq!(file.syntax().text().char_at(eq_offset), Some('=')); - let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), eq_offset)?; +pub(crate) const TRIGGER_CHARS: &str = ".=>"; + +pub(crate) fn on_char_typed( + db: &RootDatabase, + position: FilePosition, + char_typed: char, +) -> Option { + assert!(TRIGGER_CHARS.contains(char_typed)); + let file = &db.parse(position.file_id).tree(); + assert_eq!(file.syntax().text().char_at(position.offset), Some(char_typed)); + let single_file_change = on_char_typed_inner(file, position.offset, char_typed)?; + Some(single_file_change.into_source_change(position.file_id)) +} + +fn on_char_typed_inner( + file: &SourceFile, + offset: TextUnit, + char_typed: char, +) -> Option { + assert!(TRIGGER_CHARS.contains(char_typed)); + match char_typed { + '.' => on_dot_typed(file, offset), + '=' => on_eq_typed(file, offset), + '>' => on_arrow_typed(file, offset), + _ => unreachable!(), + } +} + +/// Returns an edit which should be applied after `=` was typed. Primarily, +/// this works when adding `let =`. +// FIXME: use a snippet completion instead of this hack here. +fn on_eq_typed(file: &SourceFile, offset: TextUnit) -> Option { + assert_eq!(file.syntax().text().char_at(offset), Some('=')); + let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?; if let_stmt.has_semi() { return None; } if let Some(expr) = let_stmt.initializer() { let expr_range = expr.syntax().text_range(); - if expr_range.contains(eq_offset) && eq_offset != expr_range.start() { + if expr_range.contains(offset) && offset != expr_range.start() { return None; } - if file.syntax().text().slice(eq_offset..expr_range.start()).contains_char('\n') { + if file.syntax().text().slice(offset..expr_range.start()).contains_char('\n') { return None; } } else { return None; } let offset = let_stmt.syntax().text_range().end(); - let mut edit = TextEditBuilder::default(); - edit.insert(offset, ";".to_string()); - Some(edit.finish()) + Some(SingleFileChange { + label: "add semicolon".to_string(), + edit: TextEdit::insert(offset, ";".to_string()), + cursor_position: None, + }) } -pub(crate) fn on_dot_typed(db: &RootDatabase, position: FilePosition) -> Option { - let parse = db.parse(position.file_id); - assert_eq!(parse.tree().syntax().text().char_at(position.offset), Some('.')); - - let whitespace = parse - .tree() - .syntax() - .token_at_offset(position.offset) - .left_biased() - .and_then(ast::Whitespace::cast)?; +/// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately. +fn on_dot_typed(file: &SourceFile, offset: TextUnit) -> Option { + assert_eq!(file.syntax().text().char_at(offset), Some('.')); + let whitespace = + file.syntax().token_at_offset(offset).left_biased().and_then(ast::Whitespace::cast)?; let current_indent = { let text = whitespace.text(); @@ -117,20 +157,36 @@ pub(crate) fn on_dot_typed(db: &RootDatabase, position: FilePosition) -> Option< if current_indent_len == target_indent_len { return None; } - let mut edit = TextEditBuilder::default(); - edit.replace( - TextRange::from_to(position.offset - current_indent_len, position.offset), - target_indent, - ); - let res = SourceChange::source_file_edit_from("reindent dot", position.file_id, edit.finish()) - .with_cursor(FilePosition { - offset: position.offset + target_indent_len - current_indent_len - + TextUnit::of_char('.'), - file_id: position.file_id, - }); + Some(SingleFileChange { + label: "reindent dot".to_string(), + edit: TextEdit::replace( + TextRange::from_to(offset - current_indent_len, offset), + target_indent, + ), + cursor_position: Some( + offset + target_indent_len - current_indent_len + TextUnit::of_char('.'), + ), + }) +} - Some(res) +/// Adds a space after an arrow when `fn foo() { ... }` is turned into `fn foo() -> { ... }` +fn on_arrow_typed(file: &SourceFile, offset: TextUnit) -> Option { + let file_text = file.syntax().text(); + assert_eq!(file_text.char_at(offset), Some('>')); + let after_arrow = offset + TextUnit::of_char('>'); + if file_text.char_at(after_arrow) != Some('{') { + return None; + } + if find_node_at_offset::(file.syntax(), offset).is_none() { + return None; + } + + Some(SingleFileChange { + label: "add space after return type".to_string(), + edit: TextEdit::insert(after_arrow, " ".to_string()), + cursor_position: Some(after_arrow), + }) } #[cfg(test)] @@ -141,239 +197,6 @@ mod tests { use super::*; - #[test] - fn test_on_eq_typed() { - fn type_eq(before: &str, after: &str) { - let (offset, before) = extract_offset(before); - let mut edit = TextEditBuilder::default(); - edit.insert(offset, "=".to_string()); - let before = edit.finish().apply(&before); - let parse = SourceFile::parse(&before); - if let Some(result) = on_eq_typed(&parse.tree(), offset) { - let actual = result.apply(&before); - assert_eq_text!(after, &actual); - } else { - assert_eq_text!(&before, after) - }; - } - - // do_check(r" - // fn foo() { - // let foo =<|> - // } - // ", r" - // fn foo() { - // let foo =; - // } - // "); - type_eq( - r" -fn foo() { - let foo <|> 1 + 1 -} -", - r" -fn foo() { - let foo = 1 + 1; -} -", - ); - // do_check(r" - // fn foo() { - // let foo =<|> - // let bar = 1; - // } - // ", r" - // fn foo() { - // let foo =; - // let bar = 1; - // } - // "); - } - - fn type_dot(before: &str, after: &str) { - let (offset, before) = extract_offset(before); - let mut edit = TextEditBuilder::default(); - edit.insert(offset, ".".to_string()); - let before = edit.finish().apply(&before); - let (analysis, file_id) = single_file(&before); - if let Some(result) = analysis.on_dot_typed(FilePosition { offset, file_id }).unwrap() { - assert_eq!(result.source_file_edits.len(), 1); - let actual = result.source_file_edits[0].edit.apply(&before); - assert_eq_text!(after, &actual); - } else { - assert_eq_text!(&before, after) - }; - } - - #[test] - fn indents_new_chain_call() { - type_dot( - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - self.child_impl(db, name) - <|> - } - ", - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - self.child_impl(db, name) - . - } - ", - ); - type_dot( - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - self.child_impl(db, name) - <|> - } - ", - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - self.child_impl(db, name) - . - } - ", - ) - } - - #[test] - fn indents_new_chain_call_with_semi() { - type_dot( - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - self.child_impl(db, name) - <|>; - } - ", - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - self.child_impl(db, name) - .; - } - ", - ); - type_dot( - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - self.child_impl(db, name) - <|>; - } - ", - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - self.child_impl(db, name) - .; - } - ", - ) - } - - #[test] - fn indents_continued_chain_call() { - type_dot( - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - self.child_impl(db, name) - .first() - <|> - } - ", - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - self.child_impl(db, name) - .first() - . - } - ", - ); - type_dot( - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - self.child_impl(db, name) - .first() - <|> - } - ", - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - self.child_impl(db, name) - .first() - . - } - ", - ); - } - - #[test] - fn indents_middle_of_chain_call() { - type_dot( - r" - fn source_impl() { - let var = enum_defvariant_list().unwrap() - <|> - .nth(92) - .unwrap(); - } - ", - r" - fn source_impl() { - let var = enum_defvariant_list().unwrap() - . - .nth(92) - .unwrap(); - } - ", - ); - type_dot( - r" - fn source_impl() { - let var = enum_defvariant_list().unwrap() - <|> - .nth(92) - .unwrap(); - } - ", - r" - fn source_impl() { - let var = enum_defvariant_list().unwrap() - . - .nth(92) - .unwrap(); - } - ", - ); - } - - #[test] - fn dont_indent_freestanding_dot() { - type_dot( - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - <|> - } - ", - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - . - } - ", - ); - type_dot( - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - <|> - } - ", - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - . - } - ", - ); - } - #[test] fn test_on_enter() { fn apply_on_enter(before: &str) -> Option { @@ -426,4 +249,214 @@ impl S { ); do_check_noop(r"<|>//! docz"); } + + fn do_type_char(char_typed: char, before: &str) -> Option<(String, SingleFileChange)> { + let (offset, before) = extract_offset(before); + let edit = TextEdit::insert(offset, char_typed.to_string()); + let before = edit.apply(&before); + let parse = SourceFile::parse(&before); + on_char_typed_inner(&parse.tree(), offset, char_typed) + .map(|it| (it.edit.apply(&before), it)) + } + + fn type_char(char_typed: char, before: &str, after: &str) { + let (actual, file_change) = do_type_char(char_typed, before) + .unwrap_or_else(|| panic!("typing `{}` did nothing", char_typed)); + + if after.contains("<|>") { + let (offset, after) = extract_offset(after); + assert_eq_text!(&after, &actual); + assert_eq!(file_change.cursor_position, Some(offset)) + } else { + assert_eq_text!(after, &actual); + } + } + + fn type_char_noop(char_typed: char, before: &str) { + let file_change = do_type_char(char_typed, before); + assert!(file_change.is_none()) + } + + #[test] + fn test_on_eq_typed() { + // do_check(r" + // fn foo() { + // let foo =<|> + // } + // ", r" + // fn foo() { + // let foo =; + // } + // "); + type_char( + '=', + r" +fn foo() { + let foo <|> 1 + 1 +} +", + r" +fn foo() { + let foo = 1 + 1; +} +", + ); + // do_check(r" + // fn foo() { + // let foo =<|> + // let bar = 1; + // } + // ", r" + // fn foo() { + // let foo =; + // let bar = 1; + // } + // "); + } + + #[test] + fn indents_new_chain_call() { + type_char( + '.', + r" + pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { + self.child_impl(db, name) + <|> + } + ", + r" + pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { + self.child_impl(db, name) + . + } + ", + ); + type_char_noop( + '.', + r" + pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { + self.child_impl(db, name) + <|> + } + ", + ) + } + + #[test] + fn indents_new_chain_call_with_semi() { + type_char( + '.', + r" + pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { + self.child_impl(db, name) + <|>; + } + ", + r" + pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { + self.child_impl(db, name) + .; + } + ", + ); + type_char_noop( + '.', + r" + pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { + self.child_impl(db, name) + <|>; + } + ", + ) + } + + #[test] + fn indents_continued_chain_call() { + type_char( + '.', + r" + pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { + self.child_impl(db, name) + .first() + <|> + } + ", + r" + pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { + self.child_impl(db, name) + .first() + . + } + ", + ); + type_char_noop( + '.', + r" + pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { + self.child_impl(db, name) + .first() + <|> + } + ", + ); + } + + #[test] + fn indents_middle_of_chain_call() { + type_char( + '.', + r" + fn source_impl() { + let var = enum_defvariant_list().unwrap() + <|> + .nth(92) + .unwrap(); + } + ", + r" + fn source_impl() { + let var = enum_defvariant_list().unwrap() + . + .nth(92) + .unwrap(); + } + ", + ); + type_char_noop( + '.', + r" + fn source_impl() { + let var = enum_defvariant_list().unwrap() + <|> + .nth(92) + .unwrap(); + } + ", + ); + } + + #[test] + fn dont_indent_freestanding_dot() { + type_char_noop( + '.', + r" + pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { + <|> + } + ", + ); + type_char_noop( + '.', + r" + pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { + <|> + } + ", + ); + } + + #[test] + fn adds_space_after_return_type() { + type_char('>', "fn foo() -<|>{ 92 }", "fn foo() -><|> { 92 }") + } } diff --git a/crates/ra_lsp_server/src/caps.rs b/crates/ra_lsp_server/src/caps.rs index 30bcbd7a820a..eea0965edbea 100644 --- a/crates/ra_lsp_server/src/caps.rs +++ b/crates/ra_lsp_server/src/caps.rs @@ -38,7 +38,7 @@ pub fn server_capabilities() -> ServerCapabilities { document_range_formatting_provider: None, document_on_type_formatting_provider: Some(DocumentOnTypeFormattingOptions { first_trigger_character: "=".to_string(), - more_trigger_character: Some(vec![".".to_string()]), + more_trigger_character: Some(vec![".".to_string(), ">".to_string()]), }), selection_range_provider: Some(GenericCapability::default()), folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)), diff --git a/crates/ra_lsp_server/src/config.rs b/crates/ra_lsp_server/src/config.rs index 579d4c6926b5..9871a3b37f39 100644 --- a/crates/ra_lsp_server/src/config.rs +++ b/crates/ra_lsp_server/src/config.rs @@ -1,4 +1,11 @@ -//! FIXME: write short doc here +//! Config used by the language server. +//! +//! We currently get this config from `initialize` LSP request, which is not the +//! best way to do it, but was the simplest thing we could implement. +//! +//! Of particular interest is the `feature_flags` hash map: while other fields +//! configure the server itself, feature flags are passed into analysis, and +//! tweak things like automatic insertion of `()` in completions. use rustc_hash::FxHashMap; @@ -72,10 +79,7 @@ mod test { assert_eq!(default, serde_json::from_str(r#"{}"#).unwrap()); assert_eq!( default, - serde_json::from_str( - r#"{"publishDecorations":null, "showWorkspaceLoaded":null, "lruCapacity":null}"# - ) - .unwrap() + serde_json::from_str(r#"{"publishDecorations":null, "lruCapacity":null}"#).unwrap() ); } } diff --git a/crates/ra_lsp_server/src/main_loop.rs b/crates/ra_lsp_server/src/main_loop.rs index 1a87706fee3f..379dab4384f1 100644 --- a/crates/ra_lsp_server/src/main_loop.rs +++ b/crates/ra_lsp_server/src/main_loop.rs @@ -196,7 +196,7 @@ pub fn main_loop( task_receiver.into_iter().for_each(|task| { on_task(task, &connection.sender, &mut loop_state.pending_requests, &mut world_state) }); - libdata_receiver.into_iter().for_each(|lib| drop(lib)); + libdata_receiver.into_iter().for_each(drop); log::info!("...tasks have finished"); log::info!("joining threadpool..."); drop(pool); diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs index a29971d107aa..20f9aee138fb 100644 --- a/crates/ra_lsp_server/src/main_loop/handlers.rs +++ b/crates/ra_lsp_server/src/main_loop/handlers.rs @@ -27,6 +27,7 @@ use crate::{ }; pub fn handle_analyzer_status(world: WorldSnapshot, _: ()) -> Result { + let _p = profile("handle_analyzer_status"); let mut buf = world.status(); writeln!(buf, "\n\nrequests:").unwrap(); let requests = world.latest_requests.read(); @@ -38,6 +39,7 @@ pub fn handle_analyzer_status(world: WorldSnapshot, _: ()) -> Result { } pub fn handle_syntax_tree(world: WorldSnapshot, params: req::SyntaxTreeParams) -> Result { + let _p = profile("handle_syntax_tree"); let id = params.text_document.try_conv_with(&world)?; let line_index = world.analysis().file_line_index(id)?; let text_range = params.range.map(|p| p.conv_with(&line_index)); @@ -132,6 +134,7 @@ pub fn handle_on_enter( } } +// Don't forget to add new trigger characters to `ServerCapabilities` in `caps.rs`. pub fn handle_on_type_formatting( world: WorldSnapshot, params: req::DocumentOnTypeFormattingParams, @@ -144,12 +147,17 @@ pub fn handle_on_type_formatting( // in `ra_ide_api`, the `on_type` invariant is that // `text.char_at(position) == typed_char`. position.offset = position.offset - TextUnit::of_char('.'); + let char_typed = params.ch.chars().next().unwrap_or('\0'); - let edit = match params.ch.as_str() { - "=" => world.analysis().on_eq_typed(position), - "." => world.analysis().on_dot_typed(position), - _ => return Ok(None), - }?; + // We have an assist that inserts ` ` after typing `->` in `fn foo() ->{`, + // but it requires precise cursor positioning to work, and one can't + // position the cursor with on_type formatting. So, let's just toggle this + // feature off here, hoping that we'll enable it one day, 😿. + if char_typed == '>' { + return Ok(None); + } + + let edit = world.analysis().on_char_typed(position, char_typed)?; let mut edit = match edit { Some(it) => it, None => return Ok(None), @@ -166,6 +174,7 @@ pub fn handle_document_symbol( world: WorldSnapshot, params: req::DocumentSymbolParams, ) -> Result> { + let _p = profile("handle_document_symbol"); let file_id = params.text_document.try_conv_with(&world)?; let line_index = world.analysis().file_line_index(file_id)?; @@ -204,6 +213,7 @@ pub fn handle_workspace_symbol( world: WorldSnapshot, params: req::WorkspaceSymbolParams, ) -> Result>> { + let _p = profile("handle_workspace_symbol"); let all_symbols = params.query.contains('#'); let libs = params.query.contains('*'); let query = { @@ -247,6 +257,7 @@ pub fn handle_goto_definition( world: WorldSnapshot, params: req::TextDocumentPositionParams, ) -> Result> { + let _p = profile("handle_goto_definition"); let position = params.try_conv_with(&world)?; let nav_info = match world.analysis().goto_definition(position)? { None => return Ok(None), @@ -260,6 +271,7 @@ pub fn handle_goto_implementation( world: WorldSnapshot, params: req::TextDocumentPositionParams, ) -> Result> { + let _p = profile("handle_goto_implementation"); let position = params.try_conv_with(&world)?; let nav_info = match world.analysis().goto_implementation(position)? { None => return Ok(None), @@ -273,6 +285,7 @@ pub fn handle_goto_type_definition( world: WorldSnapshot, params: req::TextDocumentPositionParams, ) -> Result> { + let _p = profile("handle_goto_type_definition"); let position = params.try_conv_with(&world)?; let nav_info = match world.analysis().goto_type_definition(position)? { None => return Ok(None), @@ -286,6 +299,7 @@ pub fn handle_parent_module( world: WorldSnapshot, params: req::TextDocumentPositionParams, ) -> Result> { + let _p = profile("handle_parent_module"); let position = params.try_conv_with(&world)?; world.analysis().parent_module(position)?.iter().try_conv_with_to_vec(&world) } @@ -294,6 +308,7 @@ pub fn handle_runnables( world: WorldSnapshot, params: req::RunnablesParams, ) -> Result> { + let _p = profile("handle_runnables"); let file_id = params.text_document.try_conv_with(&world)?; let line_index = world.analysis().file_line_index(file_id)?; let offset = params.position.map(|it| it.conv_with(&line_index)); @@ -335,6 +350,7 @@ pub fn handle_decorations( world: WorldSnapshot, params: TextDocumentIdentifier, ) -> Result> { + let _p = profile("handle_decorations"); let file_id = params.try_conv_with(&world)?; highlight(&world, file_id) } @@ -383,6 +399,7 @@ pub fn handle_folding_range( world: WorldSnapshot, params: FoldingRangeParams, ) -> Result>> { + let _p = profile("handle_folding_range"); let file_id = params.text_document.try_conv_with(&world)?; let folds = world.analysis().folding_ranges(file_id)?; let text = world.analysis().file_text(file_id)?; @@ -400,6 +417,7 @@ pub fn handle_signature_help( world: WorldSnapshot, params: req::TextDocumentPositionParams, ) -> Result> { + let _p = profile("handle_signature_help"); let position = params.try_conv_with(&world)?; if let Some(call_info) = world.analysis().call_info(position)? { let active_parameter = call_info.active_parameter.map(|it| it as i64); @@ -419,6 +437,7 @@ pub fn handle_hover( world: WorldSnapshot, params: req::TextDocumentPositionParams, ) -> Result> { + let _p = profile("handle_hover"); let position = params.try_conv_with(&world)?; let info = match world.analysis().hover(position)? { None => return Ok(None), @@ -517,6 +536,7 @@ pub fn handle_formatting( world: WorldSnapshot, params: DocumentFormattingParams, ) -> Result>> { + let _p = profile("handle_formatting"); let file_id = params.text_document.try_conv_with(&world)?; let file = world.analysis().file_text(file_id)?; @@ -639,6 +659,7 @@ pub fn handle_code_lens( world: WorldSnapshot, params: req::CodeLensParams, ) -> Result>> { + let _p = profile("handle_code_lens"); let file_id = params.text_document.try_conv_with(&world)?; let line_index = world.analysis().file_line_index(file_id)?; @@ -699,6 +720,7 @@ enum CodeLensResolveData { } pub fn handle_code_lens_resolve(world: WorldSnapshot, code_lens: CodeLens) -> Result { + let _p = profile("handle_code_lens_resolve"); let data = code_lens.data.unwrap(); let resolve = serde_json::from_value(data)?; match resolve { @@ -770,6 +792,7 @@ pub fn publish_diagnostics( world: &WorldSnapshot, file_id: FileId, ) -> Result { + let _p = profile("publish_diagnostics"); let uri = world.file_id_to_uri(file_id)?; let line_index = world.analysis().file_line_index(file_id)?; let diagnostics = world @@ -792,6 +815,7 @@ pub fn publish_decorations( world: &WorldSnapshot, file_id: FileId, ) -> Result { + let _p = profile("publish_decorations"); let uri = world.file_id_to_uri(file_id)?; Ok(req::PublishDecorationsParams { uri, decorations: highlight(&world, file_id)? }) } @@ -841,6 +865,7 @@ pub fn handle_inlay_hints( world: WorldSnapshot, params: InlayHintsParams, ) -> Result> { + let _p = profile("handle_inlay_hints"); let file_id = params.text_document.try_conv_with(&world)?; let analysis = world.analysis(); let line_index = analysis.file_line_index(file_id)?; diff --git a/crates/ra_mbe/src/mbe_expander/matcher.rs b/crates/ra_mbe/src/mbe_expander/matcher.rs index 0548e8512e66..33b9d483d2e1 100644 --- a/crates/ra_mbe/src/mbe_expander/matcher.rs +++ b/crates/ra_mbe/src/mbe_expander/matcher.rs @@ -123,7 +123,6 @@ fn match_subtree( } None => bindings.push_optional(name), } - () } Op::Repeat { subtree, kind, separator } => { match_repeat(bindings, subtree, kind, separator, src)? @@ -159,7 +158,7 @@ impl<'a> TtIter<'a> { pub(crate) fn expect_lifetime(&mut self) -> Result<&tt::Ident, ()> { let ident = self.expect_ident()?; // check if it start from "`" - if ident.text.chars().next() != Some('\'') { + if !ident.text.starts_with('\'') { return Err(()); } Ok(ident) diff --git a/crates/ra_mbe/src/syntax_bridge.rs b/crates/ra_mbe/src/syntax_bridge.rs index 1b543c84b869..592fcf527bb3 100644 --- a/crates/ra_mbe/src/syntax_bridge.rs +++ b/crates/ra_mbe/src/syntax_bridge.rs @@ -383,7 +383,7 @@ mod tests { "#, ); let expansion = expand(&rules, "literals!(foo);"); - let tts = &[expansion.clone().into()]; + let tts = &[expansion.into()]; let buffer = tt::buffer::TokenBuffer::new(tts); let mut tt_src = SubtreeTokenSource::new(&buffer); let mut tokens = vec![]; diff --git a/crates/ra_parser/src/grammar/expressions/atom.rs b/crates/ra_parser/src/grammar/expressions/atom.rs index 7454005c4e26..4952bd1891b5 100644 --- a/crates/ra_parser/src/grammar/expressions/atom.rs +++ b/crates/ra_parser/src/grammar/expressions/atom.rs @@ -56,7 +56,8 @@ pub(super) const ATOM_EXPR_FIRST: TokenSet = LIFETIME, ASYNC_KW, TRY_KW, - LOOP_KW + LOOP_KW, + FOR_KW, ]); const EXPR_RECOVERY_SET: TokenSet = token_set![LET_KW]; diff --git a/crates/ra_syntax/src/ast.rs b/crates/ra_syntax/src/ast.rs index 1b2ce921a51c..a12da5be2ab4 100644 --- a/crates/ra_syntax/src/ast.rs +++ b/crates/ra_syntax/src/ast.rs @@ -173,7 +173,7 @@ fn test_doc_comment_single_line_block_strips_suffix_whitespace() { .ok() .unwrap(); let module = file.syntax().descendants().find_map(Module::cast).unwrap(); - assert_eq!("this is mod foo", module.doc_comment_text().unwrap()); + assert_eq!("this is mod foo ", module.doc_comment_text().unwrap()); } #[test] @@ -191,7 +191,27 @@ fn test_doc_comment_multi_line_block_strips_suffix() { .ok() .unwrap(); let module = file.syntax().descendants().find_map(Module::cast).unwrap(); - assert_eq!(" this\n is\n mod foo", module.doc_comment_text().unwrap()); + assert_eq!( + " this\n is\n mod foo\n ", + module.doc_comment_text().unwrap() + ); +} + +#[test] +fn test_comments_preserve_trailing_whitespace() { + let file = SourceFile::parse( + r#" +/// Representation of a Realm. +/// In the specification these are called Realm Records. +struct Realm {}"#, + ) + .ok() + .unwrap(); + let def = file.syntax().descendants().find_map(StructDef::cast).unwrap(); + assert_eq!( + "Representation of a Realm. \nIn the specification these are called Realm Records.", + def.doc_comment_text().unwrap() + ); } #[test] diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs index 00422ea913ac..3d5f18bfae68 100644 --- a/crates/ra_syntax/src/ast/make.rs +++ b/crates/ra_syntax/src/ast/make.rs @@ -77,7 +77,7 @@ pub fn tuple_struct_pat( pub fn record_pat(path: ast::Path, pats: impl Iterator) -> ast::RecordPat { let pats_str = pats.map(|p| p.syntax().to_string()).join(", "); - return from_text(&format!("{}{{ {} }}", path.syntax(), pats_str)); + return from_text(&format!("{} {{ {} }}", path.syntax(), pats_str)); fn from_text(text: &str) -> ast::RecordPat { ast_from_text(&format!("fn f({}: ())", text)) @@ -129,11 +129,11 @@ pub fn where_clause(preds: impl Iterator) -> ast::WhereCl } pub fn if_expression(condition: &ast::Expr, statement: &str) -> ast::IfExpr { - return ast_from_text(&format!( + ast_from_text(&format!( "fn f() {{ if !{} {{\n {}\n}}\n}}", condition.syntax().text(), statement - )); + )) } fn ast_from_text(text: &str) -> N { diff --git a/crates/ra_syntax/src/ast/traits.rs b/crates/ra_syntax/src/ast/traits.rs index f275a49558d7..c2b005886a97 100644 --- a/crates/ra_syntax/src/ast/traits.rs +++ b/crates/ra_syntax/src/ast/traits.rs @@ -6,6 +6,7 @@ use itertools::Itertools; use crate::{ ast::{self, child_opt, children, AstChildren, AstNode, AstToken}, + match_ast, syntax_node::{SyntaxElementChildren, SyntaxNodeChildren}, }; @@ -68,11 +69,12 @@ impl Iterator for ItemOrMacroIter { fn next(&mut self) -> Option { loop { let n = self.0.next()?; - if let Some(item) = ast::ModuleItem::cast(n.clone()) { - return Some(ItemOrMacro::Item(item)); - } - if let Some(call) = ast::MacroCall::cast(n) { - return Some(ItemOrMacro::Macro(call)); + match_ast! { + match n { + ast::ModuleItem(it) => { return Some(ItemOrMacro::Item(it)) }, + ast::MacroCall(it) => { return Some(ItemOrMacro::Macro(it)) }, + _ => {}, + } } } } @@ -120,7 +122,7 @@ pub trait DocCommentsOwner: AstNode { has_comments = true; let prefix_len = comment.prefix().len(); - let line = comment.text().as_str(); + let line: &str = comment.text().as_str(); // Determine if the prefix or prefix + 1 char is stripped let pos = @@ -136,7 +138,10 @@ pub trait DocCommentsOwner: AstNode { line.len() }; - line[pos..end].trim_end().to_owned() + // Note that we do not trim the end of the line here + // since whitespace can have special meaning at the end + // of a line in markdown. + line[pos..end].to_owned() }) .join("\n"); diff --git a/crates/ra_syntax/src/lib.rs b/crates/ra_syntax/src/lib.rs index c315ba552814..5dcb6a95a663 100644 --- a/crates/ra_syntax/src/lib.rs +++ b/crates/ra_syntax/src/lib.rs @@ -160,6 +160,20 @@ impl SourceFile { } } +/// Matches a `SyntaxNode` against an `ast` type. +/// +/// # Example: +/// +/// ```ignore +/// match_ast! { +/// match node { +/// ast::CallExpr(it) => { ... }, +/// ast::MethodCallExpr(it) => { ... }, +/// ast::MacroCall(it) => { ... }, +/// _ => None, +/// } +/// } +/// ``` #[macro_export] macro_rules! match_ast { (match $node:ident { diff --git a/crates/ra_syntax/test_data/parser/err/0024_many_type_parens.txt b/crates/ra_syntax/test_data/parser/err/0024_many_type_parens.txt index 198daf7b41bf..0a93e11a5321 100644 --- a/crates/ra_syntax/test_data/parser/err/0024_many_type_parens.txt +++ b/crates/ra_syntax/test_data/parser/err/0024_many_type_parens.txt @@ -179,50 +179,47 @@ SOURCE_FILE@[0; 240) ERROR@[148; 149) PLUS@[148; 149) "+" WHITESPACE@[149; 150) " " - EXPR_STMT@[150; 151) - PAREN_EXPR@[150; 151) + EXPR_STMT@[150; 180) + TUPLE_EXPR@[150; 180) L_PAREN@[150; 151) "(" - EXPR_STMT@[151; 157) - FOR_EXPR@[151; 157) - FOR_KW@[151; 154) "for" - ERROR@[154; 155) - L_ANGLE@[154; 155) "<" - ERROR@[155; 157) - LIFETIME@[155; 157) "\'a" - EXPR_STMT@[157; 158) - ERROR@[157; 158) - R_ANGLE@[157; 158) ">" - WHITESPACE@[158; 159) " " - EXPR_STMT@[159; 180) - BIN_EXPR@[159; 180) - BIN_EXPR@[159; 178) - BIN_EXPR@[159; 169) - BIN_EXPR@[159; 167) - PATH_EXPR@[159; 164) - PATH@[159; 164) - PATH_SEGMENT@[159; 164) - NAME_REF@[159; 164) - IDENT@[159; 164) "Trait" - L_ANGLE@[164; 165) "<" - ERROR@[165; 167) - LIFETIME@[165; 167) "\'a" - R_ANGLE@[167; 168) ">" - ERROR@[168; 169) - R_PAREN@[168; 169) ")" - WHITESPACE@[169; 170) " " - PLUS@[170; 171) "+" - WHITESPACE@[171; 172) " " - PAREN_EXPR@[172; 178) - L_PAREN@[172; 173) "(" - PATH_EXPR@[173; 177) - PATH@[173; 177) - PATH_SEGMENT@[173; 177) - NAME_REF@[173; 177) - IDENT@[173; 177) "Copy" - R_PAREN@[177; 178) ")" - R_ANGLE@[178; 179) ">" - ERROR@[179; 180) - SEMI@[179; 180) ";" + BIN_EXPR@[151; 180) + BIN_EXPR@[151; 178) + BIN_EXPR@[151; 169) + BIN_EXPR@[151; 167) + BIN_EXPR@[151; 164) + FOR_EXPR@[151; 157) + FOR_KW@[151; 154) "for" + ERROR@[154; 155) + L_ANGLE@[154; 155) "<" + ERROR@[155; 157) + LIFETIME@[155; 157) "\'a" + R_ANGLE@[157; 158) ">" + WHITESPACE@[158; 159) " " + PATH_EXPR@[159; 164) + PATH@[159; 164) + PATH_SEGMENT@[159; 164) + NAME_REF@[159; 164) + IDENT@[159; 164) "Trait" + L_ANGLE@[164; 165) "<" + ERROR@[165; 167) + LIFETIME@[165; 167) "\'a" + R_ANGLE@[167; 168) ">" + ERROR@[168; 169) + R_PAREN@[168; 169) ")" + WHITESPACE@[169; 170) " " + PLUS@[170; 171) "+" + WHITESPACE@[171; 172) " " + PAREN_EXPR@[172; 178) + L_PAREN@[172; 173) "(" + PATH_EXPR@[173; 177) + PATH@[173; 177) + PATH_SEGMENT@[173; 177) + NAME_REF@[173; 177) + IDENT@[173; 177) "Copy" + R_PAREN@[177; 178) ")" + R_ANGLE@[178; 179) ">" + ERROR@[179; 180) + SEMI@[179; 180) ";" WHITESPACE@[180; 185) "\n " LET_STMT@[185; 235) LET_KW@[185; 188) "let" @@ -307,18 +304,16 @@ error 146: expected expression error 147: expected SEMI error 148: expected expression error 149: expected SEMI -error 151: expected expression -error 151: expected R_PAREN -error 151: expected SEMI error 154: expected pattern error 155: expected IN_KW error 155: expected expression error 157: expected a block -error 157: expected expression -error 158: expected SEMI error 165: expected expression error 168: expected expression error 179: expected expression +error 180: expected COMMA +error 180: expected expression +error 180: expected R_PAREN error 180: expected SEMI error 215: expected COMMA error 215: expected R_ANGLE diff --git a/crates/ra_syntax/test_data/parser/ok/0059_loops_in_parens.rs b/crates/ra_syntax/test_data/parser/ok/0059_loops_in_parens.rs new file mode 100644 index 000000000000..6e8b718aaf9a --- /dev/null +++ b/crates/ra_syntax/test_data/parser/ok/0059_loops_in_parens.rs @@ -0,0 +1,5 @@ +fn main() { + Some(for _ in [1].into_iter() {}); + Some(loop { break; }); + Some(while true {}); +} diff --git a/crates/ra_syntax/test_data/parser/ok/0059_loops_in_parens.txt b/crates/ra_syntax/test_data/parser/ok/0059_loops_in_parens.txt new file mode 100644 index 000000000000..c011187ead5e --- /dev/null +++ b/crates/ra_syntax/test_data/parser/ok/0059_loops_in_parens.txt @@ -0,0 +1,101 @@ +SOURCE_FILE@[0; 105) + FN_DEF@[0; 104) + FN_KW@[0; 2) "fn" + WHITESPACE@[2; 3) " " + NAME@[3; 7) + IDENT@[3; 7) "main" + PARAM_LIST@[7; 9) + L_PAREN@[7; 8) "(" + R_PAREN@[8; 9) ")" + WHITESPACE@[9; 10) " " + BLOCK_EXPR@[10; 104) + BLOCK@[10; 104) + L_CURLY@[10; 11) "{" + WHITESPACE@[11; 16) "\n " + EXPR_STMT@[16; 50) + CALL_EXPR@[16; 49) + PATH_EXPR@[16; 20) + PATH@[16; 20) + PATH_SEGMENT@[16; 20) + NAME_REF@[16; 20) + IDENT@[16; 20) "Some" + ARG_LIST@[20; 49) + L_PAREN@[20; 21) "(" + FOR_EXPR@[21; 48) + FOR_KW@[21; 24) "for" + WHITESPACE@[24; 25) " " + PLACEHOLDER_PAT@[25; 26) + UNDERSCORE@[25; 26) "_" + WHITESPACE@[26; 27) " " + IN_KW@[27; 29) "in" + WHITESPACE@[29; 30) " " + METHOD_CALL_EXPR@[30; 45) + ARRAY_EXPR@[30; 33) + L_BRACK@[30; 31) "[" + LITERAL@[31; 32) + INT_NUMBER@[31; 32) "1" + R_BRACK@[32; 33) "]" + DOT@[33; 34) "." + NAME_REF@[34; 43) + IDENT@[34; 43) "into_iter" + ARG_LIST@[43; 45) + L_PAREN@[43; 44) "(" + R_PAREN@[44; 45) ")" + WHITESPACE@[45; 46) " " + BLOCK_EXPR@[46; 48) + BLOCK@[46; 48) + L_CURLY@[46; 47) "{" + R_CURLY@[47; 48) "}" + R_PAREN@[48; 49) ")" + SEMI@[49; 50) ";" + WHITESPACE@[50; 55) "\n " + EXPR_STMT@[55; 77) + CALL_EXPR@[55; 76) + PATH_EXPR@[55; 59) + PATH@[55; 59) + PATH_SEGMENT@[55; 59) + NAME_REF@[55; 59) + IDENT@[55; 59) "Some" + ARG_LIST@[59; 76) + L_PAREN@[59; 60) "(" + LOOP_EXPR@[60; 75) + LOOP_KW@[60; 64) "loop" + WHITESPACE@[64; 65) " " + BLOCK_EXPR@[65; 75) + BLOCK@[65; 75) + L_CURLY@[65; 66) "{" + WHITESPACE@[66; 67) " " + EXPR_STMT@[67; 73) + BREAK_EXPR@[67; 72) + BREAK_KW@[67; 72) "break" + SEMI@[72; 73) ";" + WHITESPACE@[73; 74) " " + R_CURLY@[74; 75) "}" + R_PAREN@[75; 76) ")" + SEMI@[76; 77) ";" + WHITESPACE@[77; 82) "\n " + EXPR_STMT@[82; 102) + CALL_EXPR@[82; 101) + PATH_EXPR@[82; 86) + PATH@[82; 86) + PATH_SEGMENT@[82; 86) + NAME_REF@[82; 86) + IDENT@[82; 86) "Some" + ARG_LIST@[86; 101) + L_PAREN@[86; 87) "(" + WHILE_EXPR@[87; 100) + WHILE_KW@[87; 92) "while" + WHITESPACE@[92; 93) " " + CONDITION@[93; 97) + LITERAL@[93; 97) + TRUE_KW@[93; 97) "true" + WHITESPACE@[97; 98) " " + BLOCK_EXPR@[98; 100) + BLOCK@[98; 100) + L_CURLY@[98; 99) "{" + R_CURLY@[99; 100) "}" + R_PAREN@[100; 101) ")" + SEMI@[101; 102) ";" + WHITESPACE@[102; 103) "\n" + R_CURLY@[103; 104) "}" + WHITESPACE@[104; 105) "\n" diff --git a/crates/ra_text_edit/src/text_edit.rs b/crates/ra_text_edit/src/text_edit.rs index 0381ea000821..413c7d782eb5 100644 --- a/crates/ra_text_edit/src/text_edit.rs +++ b/crates/ra_text_edit/src/text_edit.rs @@ -32,6 +32,24 @@ impl TextEditBuilder { } impl TextEdit { + pub fn insert(offset: TextUnit, text: String) -> TextEdit { + let mut builder = TextEditBuilder::default(); + builder.insert(offset, text); + builder.finish() + } + + pub fn delete(range: TextRange) -> TextEdit { + let mut builder = TextEditBuilder::default(); + builder.delete(range); + builder.finish() + } + + pub fn replace(range: TextRange, replace_with: String) -> TextEdit { + let mut builder = TextEditBuilder::default(); + builder.replace(range, replace_with); + builder.finish() + } + pub(crate) fn from_atoms(mut atoms: Vec) -> TextEdit { atoms.sort_by_key(|a| (a.delete.start(), a.delete.end())); for (a1, a2) in atoms.iter().zip(atoms.iter().skip(1)) { diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs index c40943b63c57..1244ea8cf189 100644 --- a/crates/test_utils/src/lib.rs +++ b/crates/test_utils/src/lib.rs @@ -1,4 +1,10 @@ -//! FIXME: write short doc here +//! Assorted testing utilities. +//! +//! Most notable things are: +//! +//! * Rich text comparison, which outputs a diff. +//! * Extracting markup (mainly, `<|>` markers) out of fixture strings. +//! * marks (see the eponymous module). #[macro_use] pub mod marks; @@ -43,7 +49,7 @@ pub fn extract_offset(text: &str) -> (TextUnit, String) { } } -pub fn try_extract_offset(text: &str) -> Option<(TextUnit, String)> { +fn try_extract_offset(text: &str) -> Option<(TextUnit, String)> { let cursor_pos = text.find(CURSOR_MARKER)?; let mut new_text = String::with_capacity(text.len() - CURSOR_MARKER.len()); new_text.push_str(&text[..cursor_pos]); @@ -59,12 +65,34 @@ pub fn extract_range(text: &str) -> (TextRange, String) { } } -pub fn try_extract_range(text: &str) -> Option<(TextRange, String)> { +fn try_extract_range(text: &str) -> Option<(TextRange, String)> { let (start, text) = try_extract_offset(text)?; let (end, text) = try_extract_offset(&text)?; Some((TextRange::from_to(start, end), text)) } +pub enum RangeOrOffset { + Range(TextRange), + Offset(TextUnit), +} + +impl From for TextRange { + fn from(selection: RangeOrOffset) -> Self { + match selection { + RangeOrOffset::Range(it) => it, + RangeOrOffset::Offset(it) => TextRange::from_to(it, it), + } + } +} + +pub fn extract_range_or_offset(text: &str) -> (RangeOrOffset, String) { + if let Some((range, text)) = try_extract_range(text) { + return (RangeOrOffset::Range(range), text); + } + let (offset, text) = extract_offset(text); + (RangeOrOffset::Offset(offset), text) +} + /// Extracts ranges, marked with ` ` paris from the `text` pub fn extract_ranges(mut text: &str, tag: &str) -> (Vec, String) { let open = format!("<{}>", tag); diff --git a/docs/dev/README.md b/docs/dev/README.md index e5a7ea5f6f07..006518afc0ff 100644 --- a/docs/dev/README.md +++ b/docs/dev/README.md @@ -14,7 +14,7 @@ To learn more about how rust-analyzer works, see We also publish rustdoc docs to pages: -https://rust-analyzer.github.io/rust-analyzer/api-docs/ra_ide_api/ +https://rust-analyzer.github.io/rust-analyzer/ra_ide_api/ Various organizational and process issues are discussed in this document. diff --git a/docs/user/README.md b/docs/user/README.md index f1628d6a424c..eb1d5ed141ec 100644 --- a/docs/user/README.md +++ b/docs/user/README.md @@ -83,8 +83,6 @@ host. ### Settings * `rust-analyzer.highlightingOn`: enables experimental syntax highlighting -* `rust-analyzer.showWorkspaceLoadedNotification`: to ease troubleshooting, a - notification is shown by default when a workspace is loaded * `rust-analyzer.enableEnhancedTyping`: by default, rust-analyzer intercepts `Enter` key to make it easier to continue comments. Note that it may conflict with VIM emulation plugin. * `rust-analyzer.raLspServerPath`: path to `ra_lsp_server` executable @@ -102,6 +100,17 @@ host. * `rust-analyzer.trace.server`: enables internal logging * `rust-analyzer.trace.cargo-watch`: enables cargo-watch logging * `RUST_SRC_PATH`: environment variable that overwrites the sysroot +* `rust-analyzer.featureFlags` -- a JSON object to tweak fine-grained behavior: + ```js + { + // Show diagnostics produced by rust-analyzer itself. + "lsp.diagnostics": true, + // Automatically insert `()` and `<>` when completing functions and types. + "completion.insertion.add-call-parenthesis": true, + // Show notification when workspace is fully loaded + "notifications.workspace-loaded": true, + } + ``` ## Emacs @@ -173,7 +182,11 @@ Installation: "syntaxes": [ "Packages/Rust/Rust.sublime-syntax", "Packages/Rust Enhanced/RustEnhanced.sublime-syntax" - ] + ], + "initializationOptions": { + "featureFlags": { + } + }, } ``` diff --git a/docs/user/assists.md b/docs/user/assists.md new file mode 100644 index 000000000000..303353e74294 --- /dev/null +++ b/docs/user/assists.md @@ -0,0 +1,505 @@ +# Assists + +Cursor position or selection is signified by `┃` character. + + +## `add_derive` + +Adds a new `#[derive()]` clause to a struct or enum. + +```rust +// BEFORE +struct Point { + x: u32, + y: u32,┃ +} + +// AFTER +#[derive()] +struct Point { + x: u32, + y: u32, +} +``` + +## `add_explicit_type` + +Specify type for a let binding. + +```rust +// BEFORE +fn main() { + let x┃ = 92; +} + +// AFTER +fn main() { + let x: i32 = 92; +} +``` + +## `add_hash` + +Adds a hash to a raw string literal. + +```rust +// BEFORE +fn main() { + r#"Hello,┃ World!"#; +} + +// AFTER +fn main() { + r##"Hello, World!"##; +} +``` + +## `add_impl` + +Adds a new inherent impl for a type. + +```rust +// BEFORE +struct Ctx { + data: T,┃ +} + +// AFTER +struct Ctx { + data: T, +} + +impl Ctx { + +} +``` + +## `add_impl_default_members` + +Adds scaffold for overriding default impl members. + +```rust +// BEFORE +trait T { + Type X; + fn foo(&self); + fn bar(&self) {} +} + +impl T for () { + Type X = (); + fn foo(&self) {}┃ + +} + +// AFTER +trait T { + Type X; + fn foo(&self); + fn bar(&self) {} +} + +impl T for () { + Type X = (); + fn foo(&self) {} + fn bar(&self) {} + +} +``` + +## `add_impl_missing_members` + +Adds scaffold for required impl members. + +```rust +// BEFORE +trait T { + Type X; + fn foo(&self); + fn bar(&self) {} +} + +impl T for () {┃ + +} + +// AFTER +trait T { + Type X; + fn foo(&self); + fn bar(&self) {} +} + +impl T for () { + fn foo(&self) { unimplemented!() } + +} +``` + +## `add_import` + +Adds a use statement for a given fully-qualified path. + +```rust +// BEFORE +fn process(map: std::collections::┃HashMap) {} + +// AFTER +use std::collections::HashMap; + +fn process(map: HashMap) {} +``` + +## `apply_demorgan` + +Apply [De Morgan's law](https://en.wikipedia.org/wiki/De_Morgan%27s_laws). +This transforms expressions of the form `!l || !r` into `!(l && r)`. +This also works with `&&`. This assist can only be applied with the cursor +on either `||` or `&&`, with both operands being a negation of some kind. +This means something of the form `!x` or `x != y`. + +```rust +// BEFORE +fn main() { + if x != 4 ||┃ !y {} +} + +// AFTER +fn main() { + if !(x == 4 && y) {} +} +``` + +## `change_visibility` + +Adds or changes existing visibility specifier. + +```rust +// BEFORE +┃fn frobnicate() {} + +// AFTER +pub(crate) fn frobnicate() {} +``` + +## `convert_to_guarded_return` + +Replace a large conditional with a guarded return. + +```rust +// BEFORE +fn main() { + ┃if cond { + foo(); + bar(); + } +} + +// AFTER +fn main() { + if !cond { + return; + } + foo(); + bar(); +} +``` + +## `fill_match_arms` + +Adds missing clauses to a `match` expression. + +```rust +// BEFORE +enum Action { Move { distance: u32 }, Stop } + +fn handle(action: Action) { + match action { + ┃ + } +} + +// AFTER +enum Action { Move { distance: u32 }, Stop } + +fn handle(action: Action) { + match action { + Action::Move { distance } => (), + Action::Stop => (), + } +} +``` + +## `flip_binexpr` + +Flips operands of a binary expression. + +```rust +// BEFORE +fn main() { + let _ = 90 +┃ 2; +} + +// AFTER +fn main() { + let _ = 2 + 90; +} +``` + +## `flip_comma` + +Flips two comma-separated items. + +```rust +// BEFORE +fn main() { + ((1, 2),┃ (3, 4)); +} + +// AFTER +fn main() { + ((3, 4), (1, 2)); +} +``` + +## `flip_trait_bound` + +Flips two trait bounds. + +```rust +// BEFORE +fn foo() { } + +// AFTER +fn foo() { } +``` + +## `inline_local_variable` + +Inlines local variable. + +```rust +// BEFORE +fn main() { + let x┃ = 1 + 2; + x * 4; +} + +// AFTER +fn main() { + (1 + 2) * 4; +} +``` + +## `introduce_variable` + +Extracts subexpression into a variable. + +```rust +// BEFORE +fn main() { + ┃(1 + 2)┃ * 4; +} + +// AFTER +fn main() { + let var_name = (1 + 2); + var_name * 4; +} +``` + +## `make_raw_string` + +Adds `r#` to a plain string literal. + +```rust +// BEFORE +fn main() { + "Hello,┃ World!"; +} + +// AFTER +fn main() { + r#"Hello, World!"#; +} +``` + +## `make_usual_string` + +Turns a raw string into a plain string. + +```rust +// BEFORE +fn main() { + r#"Hello,┃ "World!""#; +} + +// AFTER +fn main() { + "Hello, \"World!\""; +} +``` + +## `merge_match_arms` + +Merges identical match arms. + +```rust +// BEFORE +enum Action { Move { distance: u32 }, Stop } + +fn handle(action: Action) { + match action { + ┃Action::Move(..) => foo(), + Action::Stop => foo(), + } +} + +// AFTER +enum Action { Move { distance: u32 }, Stop } + +fn handle(action: Action) { + match action { + Action::Move(..) | Action::Stop => foo(), + } +} +``` + +## `move_arm_cond_to_match_guard` + +Moves if expression from match arm body into a guard. + +```rust +// BEFORE +enum Action { Move { distance: u32 }, Stop } + +fn handle(action: Action) { + match action { + Action::Move { distance } => ┃if distance > 10 { foo() }, + _ => (), + } +} + +// AFTER +enum Action { Move { distance: u32 }, Stop } + +fn handle(action: Action) { + match action { + Action::Move { distance } if distance > 10 => foo(), + _ => (), + } +} +``` + +## `move_bounds_to_where_clause` + +Moves inline type bounds to a where clause. + +```rust +// BEFORE +fn apply U>(f: F, x: T) -> U { + f(x) +} + +// AFTER +fn apply(f: F, x: T) -> U where F: FnOnce(T) -> U { + f(x) +} +``` + +## `move_guard_to_arm_body` + +Moves match guard into match arm body. + +```rust +// BEFORE +enum Action { Move { distance: u32 }, Stop } + +fn handle(action: Action) { + match action { + Action::Move { distance } ┃if distance > 10 => foo(), + _ => (), + } +} + +// AFTER +enum Action { Move { distance: u32 }, Stop } + +fn handle(action: Action) { + match action { + Action::Move { distance } => if distance > 10 { foo() }, + _ => (), + } +} +``` + +## `remove_dbg` + +Removes `dbg!()` macro call. + +```rust +// BEFORE +fn main() { + ┃dbg!(92); +} + +// AFTER +fn main() { + 92; +} +``` + +## `remove_hash` + +Removes a hash from a raw string literal. + +```rust +// BEFORE +fn main() { + r#"Hello,┃ World!"#; +} + +// AFTER +fn main() { + r"Hello, World!"; +} +``` + +## `replace_if_let_with_match` + +Replaces `if let` with an else branch with a `match` expression. + +```rust +// BEFORE +enum Action { Move { distance: u32 }, Stop } + +fn handle(action: Action) { + ┃if let Action::Move { distance } = action { + foo(distance) + } else { + bar() + } +} + +// AFTER +enum Action { Move { distance: u32 }, Stop } + +fn handle(action: Action) { + match action { + Action::Move { distance } => foo(distance), + _ => bar(), + } +} +``` + +## `split_import` + +Wraps the tail of import into braces. + +```rust +// BEFORE +use std::┃collections::HashMap; + +// AFTER +use std::{collections::HashMap}; +``` diff --git a/docs/user/features.md b/docs/user/features.md index 8b7a8d7fc189..c160dd70bd95 100644 --- a/docs/user/features.md +++ b/docs/user/features.md @@ -97,424 +97,12 @@ Start `cargo watch` for live error highlighting. Will prompt to install if it's Stop `cargo watch` -### Code Actions (Assists) +### Assists (Code Actions) -These are triggered in a particular context via light bulb. We use custom code on -the VS Code side to be able to position cursor. `<|>` signifies cursor +Assists, or code actions, are small local refactorings, available in a particular context. +They are usually triggered by a shortcut or by clicking a light bulb icon in the editor. -- Add `#[derive]` - -```rust -// before: -struct Foo { - <|>x: i32 -} -// after: -#[derive(<|>)] -struct Foo { - x: i32 -} -``` - -- Add `impl` - -```rust -// before: -struct Foo<'a, T: Debug> { - <|>t: T -} -// after: -struct Foo<'a, T: Debug> { - t: T -} - -impl<'a, T: Debug> Foo<'a, T> { - <|> -} -``` - -- Add missing `impl` members - -```rust -// before: -trait Foo { - fn foo(&self); - fn bar(&self); - fn baz(&self); -} - -struct S; - -impl Foo for S { - fn bar(&self) {} - <|> -} - -// after: -trait Foo { - fn foo(&self); - fn bar(&self); - fn baz(&self); -} - -struct S; - -impl Foo for S { - fn bar(&self) {} - fn foo(&self) { unimplemented!() } - fn baz(&self) { unimplemented!() }<|> -} -``` - -- Apply [De Morgan's law](https://en.wikipedia.org/wiki/De_Morgan%27s_laws) - -```rust -// before: -fn example(x: bool) -> bool { - !x || !x -} - -// after: -fn example(x: bool) -> bool { - !(x && x) -} -``` - -- Import path - -```rust -// before: -impl std::fmt::Debug<|> for Foo { -} - -// after: -use std::fmt::Debug; - -impl Debug<|> for Foo { -} -``` - -- Change Visibility - -```rust -// before: -<|>fn foo() {} - -// after: -<|>pub(crate) fn foo() {} - -// after: -<|>pub fn foo() {} -``` - -- Fill match arms - -```rust -// before: -enum A { - As, - Bs, - Cs(String), - Ds(String, String), - Es{x: usize, y: usize} -} - -fn main() { - let a = A::As; - match a<|> {} -} - -// after: -enum A { - As, - Bs, - Cs(String), - Ds(String, String), - Es{x: usize, y: usize} -} - -fn main() { - let a = A::As; - match <|>a { - A::As => (), - A::Bs => (), - A::Cs(_) => (), - A::Ds(_, _) => (), - A::Es{x, y} => (), - } -} -``` - -- Fill struct fields - -```rust -// before: -struct S<'a, D> { - a: u32, - b: String, - c: (i32, i32), - d: D, - r: &'a str, -} - -fn main() { - let s = S<|> {} -} - -// after: -struct S<'a, D> { - a: u32, - b: String, - c: (i32, i32), - d: D, - r: &'a str, -} - -fn main() { - let s = <|>S { - a: (), - b: (), - c: (), - d: (), - r: (), - } -} -``` - -- Flip `,` - -```rust -// before: -fn foo(x: usize,<|> dim: (usize, usize)) {} -// after: -fn foo(dim: (usize, usize), x: usize) {} -``` - -- Introduce variable: - -```rust -// before: -fn foo() { - foo(<|>1 + 1<|>); -} - -// after: -fn foo() { - let var_name = 1 + 1; - foo(var_name); -} -``` - -- Inline local variable: - -```rust -// before: -fn foo() { - let a<|> = 1 + 1; - let b = a * 10; -} - -// after: -fn foo() { - let b = (1 + 1) * 10; -} -``` - -- Remove `dbg!` - -```rust -// before: -fn foo(n: usize) { - if let Some(_) = dbg!(n.<|>checked_sub(4)) { - // ... - } -} - -// after: -fn foo(n: usize) { - if let Some(_) = n.<|>checked_sub(4) { - // ... - } -} -``` - -- Replace if-let with match: - -```rust -// before: -impl VariantData { - pub fn is_struct(&self) -> bool { - if <|>let VariantData::Struct(..) = *self { - true - } else { - false - } - } -} - -// after: -impl VariantData { - pub fn is_struct(&self) -> bool { - <|>match *self { - VariantData::Struct(..) => true, - _ => false, - } - } -} -``` - -- Split import - -```rust -// before: -use crate:<|>:db::{RootDatabase, FileSymbol}; -// after: -use crate::{<|>db::{RootDatabase, FileSymbol}}; -``` - -- Flip binary expression - -```rust -// before: -fn foo() { - if 1 <<|> 2 { - println!("Who would have thought?"); - } -} -// after: -fn foo() { - if 2 ><|> 1 { - println!("Who would have thought?"); - } -} -``` - -- Add explicit type - -```rust -// before: -fn foo() { - let t<|> = (&2, Some(1)); -} -// after: -fn foo() { - let t<|>: (&i32, Option) = (&2, Some(1)); -} -``` - -- Move guard expression to match arm body -```rust -// before: -fn f() { - match x { - <|>y @ 4 | y @ 5 if y > 5 => true, - _ => false - } -} -// after: -fn f() { - match x { - y @ 4 | y @ 5 => if y > 5 { <|>true }, - _ => false - } -} -``` - -- Move if condition to match arm guard -```rust -// before: -fn f() { - let mut t = 'a'; - let chars = "abcd"; - match t { - '\r' => if chars.clone().next().is_some() { - t = 'e';<|> - false - }, - _ => true - } -} - -// after: -fn f() { - let mut t = 'a'; - let chars = "abcd"; - match t { - '\r' <|>if chars.clone().next().is_some() => { - t = 'e'; - false - }, - _ => true - } -} -``` - -- Move type bounds to where clause - -```rust -// before: -fn foo T>() {} - -// after: -fn foo() where T: u32, F: FnOnce(T) -> T {} -``` - -- Make raw string unescaped - -```rust -// before: -fn f() { - let s = <|>"ab\ncd"; -} - -// after: -fn f() { - let s = <|>r#"ab -cd"#; -} -``` - -- Make usual string - -```rust -// before: -fn f() { - let s = <|>r#"abcd"#; -} - -// after: -fn f() { - let s = <|>"abcd"; -} -``` - -- Add hash - -```rust -// before: -fn f() { - let s = <|>r"abcd"; -} - -// after: -fn f() { - let s = <|>r#"abcd"#; -} -``` - -- Remove hash - -```rust -// before: -fn f() { - let s = <|>r#"abcd"#; -} - -// after: -fn f() { - let s = <|>r"abcd"; -} -``` +See [assists.md](./assists.md) for the list of available assists. ### Magic Completions diff --git a/editors/code/package.json b/editors/code/package.json index 4b719aadaf26..ee997e58f5d0 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -229,11 +229,6 @@ "description": "A list of patterns for cargo-watch to ignore (will be passed as `--ignore`)", "default": [] }, - "rust-analyzer.showWorkspaceLoadedNotification": { - "type": "boolean", - "description": "Controls whether rust-analyzer displays a notification when a project is loaded.", - "default": false - }, "rust-analyzer.trace.server": { "type": "string", "scope": "window", diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index a3fe39098729..12823f31985b 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -21,7 +21,6 @@ export class Config { public rainbowHighlightingOn = false; public enableEnhancedTyping = true; public raLspServerPath = RA_LSP_DEBUG || 'ra_lsp_server'; - public showWorkspaceLoadedNotification = true; public lruCapacity: null | number = null; public displayInlayHints = true; public maxInlayHintLength: null | number = null; @@ -62,12 +61,6 @@ export class Config { ) as boolean; } - if (config.has('showWorkspaceLoadedNotification')) { - this.showWorkspaceLoadedNotification = config.get( - 'showWorkspaceLoadedNotification' - ) as boolean; - } - if (!this.highlightingOn && Server) { Server.highlighter.removeHighlights(); } diff --git a/editors/code/src/extension.ts b/editors/code/src/extension.ts index 1e1bc1a671c0..07a5c59e81e4 100644 --- a/editors/code/src/extension.ts +++ b/editors/code/src/extension.ts @@ -45,7 +45,7 @@ export function activate(context: vscode.ExtensionContext) { }); } catch (_) { vscode.window.showWarningMessage( - 'Enhanced typing feature is disabled because of incompatibility with VIM extension' + 'Enhanced typing feature is disabled because of incompatibility with VIM extension, consider turning off rust-analyzer.enableEnhancedTyping: https://github.com/rust-analyzer/rust-analyzer/blob/master/docs/user/README.md#settings' ); } } diff --git a/editors/code/src/server.ts b/editors/code/src/server.ts index ff50fcd994cf..a3ef21a16719 100644 --- a/editors/code/src/server.ts +++ b/editors/code/src/server.ts @@ -42,8 +42,6 @@ export class Server { documentSelector: [{ scheme: 'file', language: 'rust' }], initializationOptions: { publishDecorations: true, - showWorkspaceLoaded: - Server.config.showWorkspaceLoadedNotification, lruCapacity: Server.config.lruCapacity, excludeGlobs: Server.config.excludeGlobs, useClientWatching: Server.config.useClientWatching, diff --git a/xtask/src/codegen.rs b/xtask/src/codegen.rs index 948b867192b8..4ec8ab75af5c 100644 --- a/xtask/src/codegen.rs +++ b/xtask/src/codegen.rs @@ -7,12 +7,22 @@ mod gen_syntax; mod gen_parser_tests; +mod gen_assists_docs; -use std::{fs, path::Path}; +use std::{ + fs, + io::Write, + mem, + path::Path, + process::{Command, Stdio}, +}; -use crate::Result; +use crate::{project_root, Result}; -pub use self::{gen_parser_tests::generate_parser_tests, gen_syntax::generate_syntax}; +pub use self::{ + gen_assists_docs::generate_assists_docs, gen_parser_tests::generate_parser_tests, + gen_syntax::generate_syntax, +}; pub const GRAMMAR: &str = "crates/ra_syntax/src/grammar.ron"; const GRAMMAR_DIR: &str = "crates/ra_parser/src/grammar"; @@ -22,6 +32,10 @@ const ERR_INLINE_TESTS_DIR: &str = "crates/ra_syntax/test_data/parser/inline/err pub const SYNTAX_KINDS: &str = "crates/ra_parser/src/syntax_kind/generated.rs"; pub const AST: &str = "crates/ra_syntax/src/ast/generated.rs"; +const ASSISTS_DIR: &str = "crates/ra_assists/src/assists"; +const ASSISTS_TESTS: &str = "crates/ra_assists/src/doc_tests/generated.rs"; +const ASSISTS_DOCS: &str = "docs/user/assists.md"; + #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum Mode { Overwrite, @@ -30,7 +44,7 @@ pub enum Mode { /// A helper to update file on disk if it has changed. /// With verify = false, -pub fn update(path: &Path, contents: &str, mode: Mode) -> Result<()> { +fn update(path: &Path, contents: &str, mode: Mode) -> Result<()> { match fs::read_to_string(path) { Ok(ref old_contents) if old_contents == contents => { return Ok(()); @@ -44,3 +58,53 @@ pub fn update(path: &Path, contents: &str, mode: Mode) -> Result<()> { fs::write(path, contents)?; Ok(()) } + +fn reformat(text: impl std::fmt::Display) -> Result { + let mut rustfmt = Command::new("rustfmt") + .arg("--config-path") + .arg(project_root().join("rustfmt.toml")) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn()?; + write!(rustfmt.stdin.take().unwrap(), "{}", text)?; + let output = rustfmt.wait_with_output()?; + let stdout = String::from_utf8(output.stdout)?; + let preamble = "Generated file, do not edit by hand, see `crate/ra_tools/src/codegen`"; + Ok(format!("//! {}\n\n{}", preamble, stdout)) +} + +fn extract_comment_blocks(text: &str) -> Vec> { + do_extract_comment_blocks(text, false) +} + +fn extract_comment_blocks_with_empty_lines(text: &str) -> Vec> { + do_extract_comment_blocks(text, true) +} + +fn do_extract_comment_blocks(text: &str, allow_blocks_with_empty_lins: bool) -> Vec> { + let mut res = Vec::new(); + + let prefix = "// "; + let lines = text.lines().map(str::trim_start); + + let mut block = vec![]; + for line in lines { + if line == "//" && allow_blocks_with_empty_lins { + block.push(String::new()); + continue; + } + + let is_comment = line.starts_with(prefix); + if is_comment { + block.push(line[prefix.len()..].to_string()); + } else { + if !block.is_empty() { + res.push(mem::replace(&mut block, Vec::new())) + } + } + } + if !block.is_empty() { + res.push(mem::replace(&mut block, Vec::new())) + } + res +} diff --git a/xtask/src/codegen/gen_assists_docs.rs b/xtask/src/codegen/gen_assists_docs.rs new file mode 100644 index 000000000000..05afda8f1118 --- /dev/null +++ b/xtask/src/codegen/gen_assists_docs.rs @@ -0,0 +1,137 @@ +use std::{fs, path::Path}; + +use crate::{ + codegen::{self, extract_comment_blocks_with_empty_lines, Mode}, + project_root, Result, +}; + +pub fn generate_assists_docs(mode: Mode) -> Result<()> { + let assists = collect_assists()?; + generate_tests(&assists, mode)?; + generate_docs(&assists, mode)?; + Ok(()) +} + +#[derive(Debug)] +struct Assist { + id: String, + doc: String, + before: String, + after: String, +} + +fn collect_assists() -> Result> { + let mut res = Vec::new(); + for entry in fs::read_dir(project_root().join(codegen::ASSISTS_DIR))? { + let entry = entry?; + let path = entry.path(); + if path.is_file() { + collect_file(&mut res, path.as_path())?; + } + } + res.sort_by(|lhs, rhs| lhs.id.cmp(&rhs.id)); + return Ok(res); + + fn collect_file(acc: &mut Vec, path: &Path) -> Result<()> { + let text = fs::read_to_string(path)?; + let comment_blocks = extract_comment_blocks_with_empty_lines(&text); + + for block in comment_blocks { + // FIXME: doesn't support blank lines yet, need to tweak + // `extract_comment_blocks` for that. + let mut lines = block.iter(); + let first_line = lines.next().unwrap(); + if !first_line.starts_with("Assist: ") { + continue; + } + let id = first_line["Assist: ".len()..].to_string(); + assert!( + id.chars().all(|it| it.is_ascii_lowercase() || it == '_'), + "invalid assist id: {:?}", + id + ); + + let doc = take_until(lines.by_ref(), "```").trim().to_string(); + assert!( + doc.chars().next().unwrap().is_ascii_uppercase() && doc.ends_with('.'), + "\n\n{}: assist docs should be proper sentences, with capitalization and a full stop at the end.\n\n{}\n\n", + id, doc, + ); + + let before = take_until(lines.by_ref(), "```"); + + assert_eq!(lines.next().unwrap().as_str(), "->"); + assert_eq!(lines.next().unwrap().as_str(), "```"); + let after = take_until(lines.by_ref(), "```"); + acc.push(Assist { id, doc, before, after }) + } + + fn take_until<'a>(lines: impl Iterator, marker: &str) -> String { + let mut buf = Vec::new(); + for line in lines { + if line == marker { + break; + } + buf.push(line.clone()); + } + buf.join("\n") + } + Ok(()) + } +} + +fn generate_tests(assists: &[Assist], mode: Mode) -> Result<()> { + let mut buf = String::from("use super::check;\n"); + + for assist in assists.iter() { + let test = format!( + r######" +#[test] +fn doctest_{}() {{ + check( + "{}", +r#####" +{} +"#####, r#####" +{} +"#####) +}} +"######, + assist.id, assist.id, assist.before, assist.after + ); + + buf.push_str(&test) + } + let buf = codegen::reformat(buf)?; + codegen::update(&project_root().join(codegen::ASSISTS_TESTS), &buf, mode) +} + +fn generate_docs(assists: &[Assist], mode: Mode) -> Result<()> { + let mut buf = String::from( + "# Assists\n\nCursor position or selection is signified by `┃` character.\n\n", + ); + + for assist in assists { + let before = assist.before.replace("<|>", "┃"); // Unicode pseudo-graphics bar + let after = assist.after.replace("<|>", "┃"); + let docs = format!( + " +## `{}` + +{} + +```rust +// BEFORE +{} + +// AFTER +{} +``` +", + assist.id, assist.doc, before, after + ); + buf.push_str(&docs); + } + + codegen::update(&project_root().join(codegen::ASSISTS_DOCS), &buf, mode) +} diff --git a/xtask/src/codegen/gen_parser_tests.rs b/xtask/src/codegen/gen_parser_tests.rs index 0f550d94884d..d0f0f683b817 100644 --- a/xtask/src/codegen/gen_parser_tests.rs +++ b/xtask/src/codegen/gen_parser_tests.rs @@ -3,12 +3,12 @@ use std::{ collections::HashMap, - fs, + fs, iter, path::{Path, PathBuf}, }; use crate::{ - codegen::{self, update, Mode}, + codegen::{self, extract_comment_blocks, update, Mode}, project_root, Result, }; @@ -56,48 +56,29 @@ struct Tests { pub err: HashMap, } -fn collect_tests(s: &str) -> Vec<(usize, Test)> { - let mut res = vec![]; - let prefix = "// "; - let lines = s.lines().map(str::trim_start).enumerate(); - - let mut block = vec![]; - for (line_idx, line) in lines { - let is_comment = line.starts_with(prefix); - if is_comment { - block.push((line_idx, &line[prefix.len()..])); +fn collect_tests(s: &str) -> Vec { + let mut res = Vec::new(); + for comment_block in extract_comment_blocks(s) { + let first_line = &comment_block[0]; + let (name, ok) = if first_line.starts_with("test ") { + let name = first_line["test ".len()..].to_string(); + (name, true) + } else if first_line.starts_with("test_err ") { + let name = first_line["test_err ".len()..].to_string(); + (name, false) } else { - process_block(&mut res, &block); - block.clear(); - } - } - process_block(&mut res, &block); - return res; - - fn process_block(acc: &mut Vec<(usize, Test)>, block: &[(usize, &str)]) { - if block.is_empty() { - return; - } - let mut ok = true; - let mut block = block.iter(); - let (start_line, name) = loop { - match block.next() { - Some(&(idx, line)) if line.starts_with("test ") => { - break (idx, line["test ".len()..].to_string()); - } - Some(&(idx, line)) if line.starts_with("test_err ") => { - ok = false; - break (idx, line["test_err ".len()..].to_string()); - } - Some(_) => (), - None => return, - } + continue; }; - let text: String = - block.map(|(_, line)| *line).chain(std::iter::once("")).collect::>().join("\n"); + let text: String = comment_block[1..] + .iter() + .cloned() + .chain(iter::once(String::new())) + .collect::>() + .join("\n"); assert!(!text.trim().is_empty() && text.ends_with('\n')); - acc.push((start_line, Test { name, text, ok })) + res.push(Test { name, text, ok }) } + res } fn tests_from_dir(dir: &Path) -> Result { @@ -118,15 +99,13 @@ fn tests_from_dir(dir: &Path) -> Result { fn process_file(res: &mut Tests, path: &Path) -> Result<()> { let text = fs::read_to_string(path)?; - for (_, test) in collect_tests(&text) { + for test in collect_tests(&text) { if test.ok { if let Some(old_test) = res.ok.insert(test.name.clone(), test) { - Err(format!("Duplicate test: {}", old_test.name))? - } - } else { - if let Some(old_test) = res.err.insert(test.name.clone(), test) { - Err(format!("Duplicate test: {}", old_test.name))? + return Err(format!("Duplicate test: {}", old_test.name).into()); } + } else if let Some(old_test) = res.err.insert(test.name.clone(), test) { + return Err(format!("Duplicate test: {}", old_test.name).into()); } } Ok(()) diff --git a/xtask/src/codegen/gen_syntax.rs b/xtask/src/codegen/gen_syntax.rs index 6a81c0e4dfdb..88f2ac0e35e2 100644 --- a/xtask/src/codegen/gen_syntax.rs +++ b/xtask/src/codegen/gen_syntax.rs @@ -3,12 +3,7 @@ //! Specifically, it generates the `SyntaxKind` enum and a number of newtype //! wrappers around `SyntaxNode` which implement `ra_syntax::AstNode`. -use std::{ - collections::BTreeMap, - fs, - io::Write, - process::{Command, Stdio}, -}; +use std::{collections::BTreeMap, fs}; use proc_macro2::{Punct, Spacing}; use quote::{format_ident, quote}; @@ -163,7 +158,7 @@ fn generate_ast(grammar: &Grammar) -> Result { #(#nodes)* }; - let pretty = reformat(ast)?; + let pretty = codegen::reformat(ast)?; Ok(pretty) } @@ -276,21 +271,7 @@ fn generate_syntax_kinds(grammar: &Grammar) -> Result { } }; - reformat(ast) -} - -fn reformat(text: impl std::fmt::Display) -> Result { - let mut rustfmt = Command::new("rustfmt") - .arg("--config-path") - .arg(project_root().join("rustfmt.toml")) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn()?; - write!(rustfmt.stdin.take().unwrap(), "{}", text)?; - let output = rustfmt.wait_with_output()?; - let stdout = String::from_utf8(output.stdout)?; - let preamble = "Generated file, do not edit by hand, see `crate/ra_tools/src/codegen`"; - Ok(format!("//! {}\n\n{}", preamble, stdout)) + codegen::reformat(ast) } #[derive(Deserialize, Debug)] diff --git a/xtask/src/main.rs b/xtask/src/main.rs index db901ced2632..e04e45f15752 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1,5 +1,12 @@ -//! FIXME: write short doc here - +//! See https://github.com/matklad/cargo-xtask/. +//! +//! This binary defines various auxiliary build commands, which are not +//! expressible with just `cargo`. Notably, it provides `cargo xtask codegen` +//! for code generation and `cargo xtask install` for installation of +//! rust-analyzer server and client. +//! +//! This binary is integrated into the `cargo` command line by using an alias in +//! `.cargo/config`. mod help; use core::fmt::Write; @@ -53,7 +60,7 @@ fn main() -> Result<()> { matches.finish().or_else(handle_extra_flags)?; let opts = InstallOpt { client: if server { None } else { Some(ClientOpt::VsCode) }, - server: if client_code { None } else { Some(ServerOpt { jemalloc: jemalloc }) }, + server: if client_code { None } else { Some(ServerOpt { jemalloc }) }, }; install(opts)? } @@ -64,6 +71,7 @@ fn main() -> Result<()> { } codegen::generate_syntax(Mode::Overwrite)?; codegen::generate_parser_tests(Mode::Overwrite)?; + codegen::generate_assists_docs(Mode::Overwrite)?; } "format" => { if matches.contains(["-h", "--help"]) { @@ -158,6 +166,17 @@ fn fix_path_for_mac() -> Result<()> { } fn install_client(ClientOpt::VsCode: ClientOpt) -> Result<()> { + let npm_version = Cmd { + unix: r"npm --version", + windows: r"cmd.exe /c npm.cmd --version", + work_dir: "./editors/code", + } + .run(); + + if npm_version.is_err() { + eprintln!("\nERROR: `npm --version` failed, `npm` is required to build the VS Code plugin") + } + Cmd { unix: r"npm ci", windows: r"cmd.exe /c npm.cmd ci", work_dir: "./editors/code" }.run()?; Cmd { unix: r"npm run package", @@ -210,6 +229,7 @@ fn install_server(opts: ServerOpt) -> Result<()> { let mut old_rust = false; if let Ok(output) = run_with_output("cargo --version", ".") { if let Ok(stdout) = String::from_utf8(output.stdout) { + println!("{}", stdout); if !check_version(&stdout, REQUIRED_RUST_VERSION) { old_rust = true; } diff --git a/xtask/tests/tidy-tests/cli.rs b/xtask/tests/tidy-tests/cli.rs index 543c7d7c4538..573ffadbf8ee 100644 --- a/xtask/tests/tidy-tests/cli.rs +++ b/xtask/tests/tidy-tests/cli.rs @@ -18,6 +18,13 @@ fn generated_tests_are_fresh() { } } +#[test] +fn generated_assists_are_fresh() { + if let Err(error) = codegen::generate_assists_docs(Mode::Verify) { + panic!("{}. Please update assists by running `cargo xtask codegen`", error); + } +} + #[test] fn check_code_formatting() { if let Err(error) = run_rustfmt(Mode::Verify) { diff --git a/xtask/tests/tidy-tests/docs.rs b/xtask/tests/tidy-tests/docs.rs index fe5852bc6281..6a629ce63e8f 100644 --- a/xtask/tests/tidy-tests/docs.rs +++ b/xtask/tests/tidy-tests/docs.rs @@ -8,7 +8,9 @@ use walkdir::{DirEntry, WalkDir}; use xtask::project_root; fn is_exclude_dir(p: &Path) -> bool { - let exclude_dirs = ["tests", "test_data"]; + // Test hopefully don't really need comments, and for assists we already + // have special comments which are source of doc tests and user docs. + let exclude_dirs = ["tests", "test_data", "assists"]; let mut cur_path = p; while let Some(path) = cur_path.parent() { if exclude_dirs.iter().any(|dir| path.ends_with(dir)) { @@ -27,7 +29,7 @@ fn is_exclude_file(d: &DirEntry) -> bool { } fn is_hidden(entry: &DirEntry) -> bool { - entry.file_name().to_str().map(|s| s.starts_with(".")).unwrap_or(false) + entry.file_name().to_str().map(|s| s.starts_with('.')).unwrap_or(false) } #[test]