--- /dev/null
- version = "0.6.0"
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "addr2line"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "always-assert"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbf688625d06217d5b1bb0ea9d9c44a1635fd0ee3534466388d18203174f4d11"
+dependencies = [
+ "log",
+]
+
+[[package]]
+name = "ansi_term"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.62"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1485d4d2cc45e7b201ee3767015c96faa5904387c9d87c6efdd0fb511f12d305"
+
+[[package]]
+name = "anymap"
+version = "1.0.0-beta.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f1f8f5a6f3d50d89e3797d7593a50f96bb2aaa20ca0cc7be1fb673232c91d72"
+
+[[package]]
+name = "arbitrary"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a7924531f38b1970ff630f03eb20a2fde69db5c590c93b0f3482e95dcc5fd60"
+
+[[package]]
+name = "arrayvec"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "backtrace"
+version = "0.3.66"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
+
+[[package]]
+name = "base-db"
+version = "0.0.0"
+dependencies = [
+ "cfg",
+ "profile",
+ "rustc-hash",
+ "salsa",
+ "stdx",
+ "syntax",
+ "test-utils",
+ "tt",
+ "vfs",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "camino"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88ad0e1e3e88dd237a156ab9f571021b8a158caa0ae44b1968a241efb5144c1e"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "cargo-platform"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "cargo_metadata"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3abb7553d5b9b8421c6de7cb02606ff15e0c6eea7d8eadd75ef013fd636bec36"
+dependencies = [
+ "camino",
+ "cargo-platform",
+ "semver",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
+
+[[package]]
+name = "cfg"
+version = "0.0.0"
+dependencies = [
+ "arbitrary",
+ "derive_arbitrary",
+ "expect-test",
+ "mbe",
+ "oorandom",
+ "rustc-hash",
+ "syntax",
+ "tt",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chalk-derive"
+version = "0.84.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf29c109d57f8d57b0e7675391be37a9285d86dd93278bd5f14a0ad3c447a6c2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "chalk-ir"
+version = "0.84.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d391763027b5e50a5e15caf6d2857ec585fd68160367bbeac9e1804209620918"
+dependencies = [
+ "bitflags",
+ "chalk-derive",
+ "lazy_static",
+]
+
+[[package]]
+name = "chalk-recursive"
+version = "0.84.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "afafd92dcdc7fe0ea940ee94bdd8cc5bd18f4a4a84c593d6d7025fe16c150478"
+dependencies = [
+ "chalk-derive",
+ "chalk-ir",
+ "chalk-solve",
+ "rustc-hash",
+ "tracing",
+]
+
+[[package]]
+name = "chalk-solve"
+version = "0.84.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3af1d111f11c91c48ace02e93e470c5bae6d2631bd112e4545317da53660d7fc"
+dependencies = [
+ "chalk-derive",
+ "chalk-ir",
+ "ena",
+ "indexmap",
+ "itertools",
+ "petgraph",
+ "rustc-hash",
+ "tracing",
+]
+
+[[package]]
+name = "countme"
+version = "3.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636"
+dependencies = [
+ "dashmap",
+ "once_cell",
+ "rustc-hash",
+]
+
+[[package]]
+name = "cov-mark"
+version = "2.0.0-pre.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d48d8f76bd9331f19fe2aaf3821a9f9fb32c3963e1e3d6ce82a8c09cef7444a"
+
+[[package]]
+name = "crc32fast"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521"
+dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc"
+dependencies = [
+ "cfg-if",
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "045ebe27666471bb549370b4b0b3e51b07f56325befa4284db65fc89c02511b1"
+dependencies = [
+ "autocfg",
+ "cfg-if",
+ "crossbeam-utils",
+ "memoffset",
+ "once_cell",
+ "scopeguard",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+]
+
+[[package]]
+name = "dashmap"
+version = "5.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3495912c9c1ccf2e18976439f4443f3fee0fd61f424ff99fde6a66b15ecb448f"
+dependencies = [
+ "cfg-if",
+ "hashbrown",
+ "lock_api",
+ "parking_lot_core 0.9.3",
+]
+
+[[package]]
+name = "derive_arbitrary"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9a577516173adb681466d517d39bd468293bc2c2a16439375ef0f35bba45f3d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "dissimilar"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c97b9233581d84b8e1e689cdd3a47b6f69770084fc246e86a7f78b0d9c1d4a5"
+
+[[package]]
+name = "dot"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a74b6c4d4a1cff5f454164363c16b72fa12463ca6b31f4b5f2035a65fa3d5906"
+
+[[package]]
+name = "drop_bomb"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9bda8e21c04aca2ae33ffc2fd8c23134f3cac46db123ba97bd9d3f3b8a4a85e1"
+
+[[package]]
+name = "either"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
+
+[[package]]
+name = "ena"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7402b94a93c24e742487327a7cd839dc9d36fec9de9fb25b09f2dae459f36c3"
+dependencies = [
+ "log",
+]
+
+[[package]]
+name = "expect-test"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d4661aca38d826eb7c72fe128e4238220616de4c0cc00db7bfc38e2e1364dd3"
+dependencies = [
+ "dissimilar",
+ "once_cell",
+]
+
+[[package]]
+name = "filetime"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e94a7bbaa59354bc20dd75b67f23e2797b4490e9d6928203fb105c79e448c86c"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "windows-sys 0.36.1",
+]
+
+[[package]]
+name = "fixedbitset"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d"
+
+[[package]]
+name = "flate2"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "flycheck"
+version = "0.0.0"
+dependencies = [
+ "cargo_metadata",
+ "crossbeam-channel",
+ "jod-thread",
+ "paths",
+ "rustc-hash",
+ "serde",
+ "serde_json",
+ "stdx",
+ "toolchain",
+ "tracing",
+]
+
+[[package]]
+name = "form_urlencoded"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
+dependencies = [
+ "matches",
+ "percent-encoding",
+]
+
+[[package]]
+name = "fs_extra"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394"
+
+[[package]]
+name = "fsevent-sys"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "fst"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ab85b9b05e3978cc9a9cf8fea7f01b494e1a09ed3037e16ba39edc7a29eb61a"
+
+[[package]]
+name = "gimli"
+version = "0.26.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d"
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
+[[package]]
+name = "heck"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
+dependencies = [
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "hir"
+version = "0.0.0"
+dependencies = [
+ "arrayvec",
+ "base-db",
+ "cfg",
+ "either",
+ "hir-def",
+ "hir-expand",
+ "hir-ty",
+ "itertools",
+ "once_cell",
+ "profile",
+ "rustc-hash",
+ "smallvec",
+ "stdx",
+ "syntax",
+ "tt",
+]
+
+[[package]]
+name = "hir-def"
+version = "0.0.0"
+dependencies = [
+ "anymap",
+ "arrayvec",
+ "base-db",
+ "bitflags",
+ "cfg",
+ "cov-mark",
+ "dashmap",
+ "drop_bomb",
+ "either",
+ "expect-test",
+ "fst",
+ "hashbrown",
+ "hir-expand",
+ "indexmap",
+ "itertools",
+ "la-arena",
+ "limit",
+ "mbe",
+ "once_cell",
+ "profile",
+ "rustc-hash",
+ "smallvec",
+ "stdx",
+ "syntax",
+ "test-utils",
+ "tracing",
+ "tt",
+]
+
+[[package]]
+name = "hir-expand"
+version = "0.0.0"
+dependencies = [
+ "base-db",
+ "cfg",
+ "cov-mark",
+ "either",
+ "expect-test",
+ "hashbrown",
+ "itertools",
+ "la-arena",
+ "limit",
+ "mbe",
+ "profile",
+ "rustc-hash",
+ "smallvec",
+ "stdx",
+ "syntax",
+ "tracing",
+ "tt",
+]
+
+[[package]]
+name = "hir-ty"
+version = "0.0.0"
+dependencies = [
+ "arrayvec",
+ "base-db",
+ "chalk-ir",
+ "chalk-recursive",
+ "chalk-solve",
+ "cov-mark",
+ "ena",
+ "expect-test",
+ "hir-def",
+ "hir-expand",
+ "itertools",
+ "la-arena",
+ "limit",
+ "once_cell",
+ "profile",
+ "rustc-hash",
+ "scoped-tls",
+ "smallvec",
+ "stdx",
+ "syntax",
+ "test-utils",
+ "tracing",
+ "tracing-subscriber",
+ "tracing-tree",
+ "typed-arena",
+]
+
+[[package]]
+name = "home"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2456aef2e6b6a9784192ae780c0f15bc57df0e918585282325e8c8ac27737654"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "ide"
+version = "0.0.0"
+dependencies = [
+ "cfg",
+ "cov-mark",
+ "crossbeam-channel",
+ "dot",
+ "either",
+ "expect-test",
+ "hir",
+ "ide-assists",
+ "ide-completion",
+ "ide-db",
+ "ide-diagnostics",
+ "ide-ssr",
+ "itertools",
+ "oorandom",
+ "profile",
+ "pulldown-cmark",
+ "pulldown-cmark-to-cmark",
+ "stdx",
+ "syntax",
+ "test-utils",
+ "text-edit",
+ "toolchain",
+ "tracing",
+ "url",
+]
+
+[[package]]
+name = "ide-assists"
+version = "0.0.0"
+dependencies = [
+ "cov-mark",
+ "either",
+ "expect-test",
+ "hir",
+ "ide-db",
+ "itertools",
+ "profile",
+ "sourcegen",
+ "stdx",
+ "syntax",
+ "test-utils",
+ "text-edit",
+]
+
+[[package]]
+name = "ide-completion"
+version = "0.0.0"
+dependencies = [
+ "base-db",
+ "cov-mark",
+ "expect-test",
+ "hir",
+ "ide-db",
+ "itertools",
+ "once_cell",
+ "profile",
+ "smallvec",
+ "stdx",
+ "syntax",
+ "test-utils",
+ "text-edit",
+]
+
+[[package]]
+name = "ide-db"
+version = "0.0.0"
+dependencies = [
+ "arrayvec",
+ "base-db",
+ "cov-mark",
+ "either",
+ "expect-test",
+ "fst",
+ "hir",
+ "indexmap",
+ "itertools",
+ "limit",
+ "memchr",
+ "once_cell",
+ "parser",
+ "profile",
+ "rayon",
+ "rustc-hash",
+ "sourcegen",
+ "stdx",
+ "syntax",
+ "test-utils",
+ "text-edit",
+ "tracing",
+ "xshell",
+]
+
+[[package]]
+name = "ide-diagnostics"
+version = "0.0.0"
+dependencies = [
+ "cfg",
+ "cov-mark",
+ "either",
+ "expect-test",
+ "hir",
+ "ide-db",
+ "itertools",
+ "profile",
+ "serde_json",
+ "sourcegen",
+ "stdx",
+ "syntax",
+ "test-utils",
+ "text-edit",
+]
+
+[[package]]
+name = "ide-ssr"
+version = "0.0.0"
+dependencies = [
+ "cov-mark",
+ "expect-test",
+ "hir",
+ "ide-db",
+ "itertools",
+ "parser",
+ "stdx",
+ "syntax",
+ "test-utils",
+ "text-edit",
+]
+
+[[package]]
+name = "idna"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
+dependencies = [
+ "matches",
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+]
+
+[[package]]
+name = "inotify"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff"
+dependencies = [
+ "bitflags",
+ "inotify-sys",
+ "libc",
+]
+
+[[package]]
+name = "inotify-sys"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "instant"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "itertools"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754"
+
+[[package]]
+name = "jod-thread"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b23360e99b8717f20aaa4598f5a6541efbe30630039fbc7706cf954a87947ae"
+
+[[package]]
+name = "kqueue"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d6112e8f37b59803ac47a42d14f1f3a59bbf72fc6857ffc5be455e28a691f8e"
+dependencies = [
+ "kqueue-sys",
+ "libc",
+]
+
+[[package]]
+name = "kqueue-sys"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8367585489f01bc55dd27404dcf56b95e6da061a256a666ab23be9ba96a2e587"
+dependencies = [
+ "bitflags",
+ "libc",
+]
+
+[[package]]
+name = "la-arena"
+version = "0.3.0"
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.132"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5"
+
+[[package]]
+name = "libloading"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd"
+dependencies = [
+ "cfg-if",
+ "winapi",
+]
+
+[[package]]
+name = "libmimalloc-sys"
+version = "0.1.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11ca136052550448f55df7898c6dbe651c6b574fe38a0d9ea687a9f8088a2e2c"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "limit"
+version = "0.0.0"
+
+[[package]]
+name = "lock_api"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "lsp-server"
- version = "1.0.143"
++version = "0.7.0"
+dependencies = [
+ "crossbeam-channel",
+ "log",
+ "lsp-types",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "lsp-types"
+version = "0.93.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3bcfee315dde785ba887edb540b08765fd7df75a7d948844be6bf5712246734"
+dependencies = [
+ "bitflags",
+ "serde",
+ "serde_json",
+ "serde_repr",
+ "url",
+]
+
+[[package]]
+name = "matchers"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
+dependencies = [
+ "regex-automata",
+]
+
+[[package]]
+name = "matches"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
+
+[[package]]
+name = "mbe"
+version = "0.0.0"
+dependencies = [
+ "cov-mark",
+ "parser",
+ "rustc-hash",
+ "smallvec",
+ "stdx",
+ "syntax",
+ "test-utils",
+ "tracing",
+ "tt",
+]
+
+[[package]]
+name = "memchr"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+
+[[package]]
+name = "memmap2"
+version = "0.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95af15f345b17af2efc8ead6080fb8bc376f8cec1b35277b935637595fe77498"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "mimalloc"
+version = "0.1.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f64ad83c969af2e732e907564deb0d0ed393cec4af80776f77dd77a1a427698"
+dependencies = [
+ "libmimalloc-sys",
+]
+
+[[package]]
+name = "miniz_oxide"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "mio"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf"
+dependencies = [
+ "libc",
+ "log",
+ "wasi",
+ "windows-sys 0.36.1",
+]
+
+[[package]]
+name = "miow"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7377f7792b3afb6a3cba68daa54ca23c032137010460d667fda53a8d66be00e"
+dependencies = [
+ "windows-sys 0.28.0",
+]
+
+[[package]]
+name = "notify"
+version = "5.0.0-pre.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "530f6314d6904508082f4ea424a0275cf62d341e118b313663f266429cb19693"
+dependencies = [
+ "bitflags",
+ "crossbeam-channel",
+ "filetime",
+ "fsevent-sys",
+ "inotify",
+ "kqueue",
+ "libc",
+ "mio",
+ "walkdir",
+ "winapi",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "object"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e"
+
+[[package]]
+name = "oorandom"
+version = "11.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
+
+[[package]]
+name = "parking_lot"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
+dependencies = [
+ "instant",
+ "lock_api",
+ "parking_lot_core 0.8.5",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
+dependencies = [
+ "lock_api",
+ "parking_lot_core 0.9.3",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
+dependencies = [
+ "cfg-if",
+ "instant",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "winapi",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-sys 0.36.1",
+]
+
+[[package]]
+name = "parser"
+version = "0.0.0"
+dependencies = [
+ "drop_bomb",
+ "expect-test",
+ "limit",
+ "rustc-ap-rustc_lexer",
+ "sourcegen",
+]
+
+[[package]]
+name = "paste"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9423e2b32f7a043629287a536f21951e8c6a82482d0acb1eeebfc90bc2225b22"
+
+[[package]]
+name = "paths"
+version = "0.0.0"
+
+[[package]]
+name = "percent-encoding"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
+
+[[package]]
+name = "perf-event"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5396562cd2eaa828445d6d34258ae21ee1eb9d40fe626ca7f51c8dccb4af9d66"
+dependencies = [
+ "libc",
+ "perf-event-open-sys",
+]
+
+[[package]]
+name = "perf-event-open-sys"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce9bedf5da2c234fdf2391ede2b90fabf585355f33100689bc364a3ea558561a"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "petgraph"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7"
+dependencies = [
+ "fixedbitset",
+ "indexmap",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
+
+[[package]]
+name = "proc-macro-api"
+version = "0.0.0"
+dependencies = [
+ "memmap2",
+ "object",
+ "paths",
+ "profile",
+ "serde",
+ "serde_json",
+ "snap",
+ "stdx",
+ "tracing",
+ "tt",
+]
+
+[[package]]
+name = "proc-macro-srv"
+version = "0.0.0"
+dependencies = [
+ "expect-test",
+ "libloading",
+ "mbe",
+ "memmap2",
+ "object",
+ "paths",
+ "proc-macro-api",
+ "proc-macro-test",
+ "tt",
+]
+
+[[package]]
+name = "proc-macro-srv-cli"
+version = "0.0.0"
+dependencies = [
+ "proc-macro-srv",
+]
+
+[[package]]
+name = "proc-macro-test"
+version = "0.0.0"
+dependencies = [
+ "cargo_metadata",
+ "proc-macro-test-impl",
+ "toolchain",
+]
+
+[[package]]
+name = "proc-macro-test-impl"
+version = "0.0.0"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "profile"
+version = "0.0.0"
+dependencies = [
+ "cfg-if",
+ "countme",
+ "la-arena",
+ "libc",
+ "once_cell",
+ "perf-event",
+ "tikv-jemalloc-ctl",
+ "winapi",
+]
+
+[[package]]
+name = "project-model"
+version = "0.0.0"
+dependencies = [
+ "anyhow",
+ "base-db",
+ "cargo_metadata",
+ "cfg",
+ "expect-test",
+ "la-arena",
+ "paths",
+ "profile",
+ "rustc-hash",
+ "semver",
+ "serde",
+ "serde_json",
+ "stdx",
+ "toolchain",
+ "tracing",
+]
+
+[[package]]
+name = "protobuf"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ee4a7d8b91800c8f167a6268d1a1026607368e1adc84e98fe044aeb905302f7"
+dependencies = [
+ "once_cell",
+ "protobuf-support",
+ "thiserror",
+]
+
+[[package]]
+name = "protobuf-support"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ca157fe12fc7ee2e315f2f735e27df41b3d97cdd70ea112824dac1ffb08ee1c"
+dependencies = [
+ "thiserror",
+]
+
+[[package]]
+name = "pulldown-cmark"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d9cc634bc78768157b5cbfe988ffcd1dcba95cd2b2f03a88316c08c6d00ed63"
+dependencies = [
+ "bitflags",
+ "memchr",
+ "unicase",
+]
+
+[[package]]
+name = "pulldown-cmark-to-cmark"
+version = "10.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1353ac408192fa925228d3e60ff746167d03f4f7e54835d78ef79e08225d913"
+dependencies = [
+ "pulldown-cmark",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rayon"
+version = "1.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d"
+dependencies = [
+ "autocfg",
+ "crossbeam-deque",
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f"
+dependencies = [
+ "crossbeam-channel",
+ "crossbeam-deque",
+ "crossbeam-utils",
+ "num_cpus",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "regex"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
+dependencies = [
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
+dependencies = [
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
+
+[[package]]
+name = "rowan"
+version = "0.15.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e88acf7b001007e9e8c989fe7449f6601d909e5dd2c56399fc158977ad6c56e8"
+dependencies = [
+ "countme",
+ "hashbrown",
+ "memoffset",
+ "rustc-hash",
+ "text-size",
+]
+
+[[package]]
+name = "rust-analyzer"
+version = "0.0.0"
+dependencies = [
+ "always-assert",
+ "anyhow",
+ "cfg",
+ "crossbeam-channel",
+ "dissimilar",
+ "expect-test",
+ "flycheck",
+ "hir",
+ "hir-def",
+ "hir-ty",
+ "ide",
+ "ide-db",
+ "ide-ssr",
+ "itertools",
+ "jod-thread",
+ "lsp-server",
+ "lsp-types",
+ "mbe",
+ "mimalloc",
+ "num_cpus",
+ "oorandom",
+ "parking_lot 0.12.1",
+ "proc-macro-api",
+ "proc-macro-srv",
+ "profile",
+ "project-model",
+ "rayon",
+ "rustc-hash",
+ "scip",
+ "serde",
+ "serde_json",
+ "sourcegen",
+ "stdx",
+ "syntax",
+ "test-utils",
+ "threadpool",
+ "tikv-jemallocator",
+ "toolchain",
+ "tracing",
+ "tracing-log",
+ "tracing-subscriber",
+ "tracing-tree",
+ "tt",
+ "vfs",
+ "vfs-notify",
+ "winapi",
+ "xflags",
+ "xshell",
+]
+
+[[package]]
+name = "rustc-ap-rustc_lexer"
+version = "725.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f950742ef8a203aa7661aad3ab880438ddeb7f95d4b837c30d65db1a2c5df68e"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
+
+[[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
+[[package]]
+name = "ryu"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
+
+[[package]]
+name = "salsa"
+version = "0.17.0-pre.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b223dccb46c32753144d0b51290da7230bb4aedcd8379d6b4c9a474c18bf17a"
+dependencies = [
+ "crossbeam-utils",
+ "indexmap",
+ "lock_api",
+ "log",
+ "oorandom",
+ "parking_lot 0.11.2",
+ "rustc-hash",
+ "salsa-macros",
+ "smallvec",
+]
+
+[[package]]
+name = "salsa-macros"
+version = "0.17.0-pre.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac6c2e352df550bf019da7b16164ed2f7fa107c39653d1311d1bba42d1582ff7"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "scip"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2bfbb10286f69fad7c78db71004b7839bf957788359fe0c479f029f9849136b"
+dependencies = [
+ "protobuf",
+]
+
+[[package]]
+name = "scoped-tls"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
+
+[[package]]
+name = "scopeguard"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
+[[package]]
+name = "semver"
+version = "1.0.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93f6841e709003d68bb2deee8c343572bf446003ec20a583e76f7b15cebf3711"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde"
- checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553"
++version = "1.0.144"
+source = "registry+https://github.com/rust-lang/crates.io-index"
- version = "1.0.143"
++checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
- checksum = "d3d8e8de557aee63c26b85b947f5e59b690d0454c753f3adeb5cd7835ab88391"
++version = "1.0.144"
+source = "registry+https://github.com/rust-lang/crates.io-index"
- version = "1.0.83"
++checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
- checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7"
++version = "1.0.85"
+source = "registry+https://github.com/rust-lang/crates.io-index"
- version = "0.2.4"
++checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44"
+dependencies = [
+ "indexmap",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_repr"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "sharded-slab"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1"
+
+[[package]]
+name = "smol_str"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7475118a28b7e3a2e157ce0131ba8c5526ea96e90ee601d9f6bb2e286a35ab44"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "snap"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45456094d1983e2ee2a18fdfebce3189fa451699d0502cb8e3b49dba5ba41451"
+
+[[package]]
+name = "sourcegen"
+version = "0.0.0"
+dependencies = [
+ "xshell",
+]
+
+[[package]]
+name = "stdx"
+version = "0.0.0"
+dependencies = [
+ "always-assert",
+ "backtrace",
+ "libc",
+ "miow",
+ "winapi",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.102"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fcd952facd492f9be3ef0d0b7032a6e442ee9b361d4acc2b1d0c4aaa5f613a1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "synstructure"
+version = "0.12.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "unicode-xid",
+]
+
+[[package]]
+name = "syntax"
+version = "0.0.0"
+dependencies = [
+ "cov-mark",
+ "expect-test",
+ "indexmap",
+ "itertools",
+ "once_cell",
+ "parser",
+ "proc-macro2",
+ "profile",
+ "quote",
+ "rayon",
+ "rowan",
+ "rustc-ap-rustc_lexer",
+ "rustc-hash",
+ "smol_str",
+ "sourcegen",
+ "stdx",
+ "test-utils",
+ "text-edit",
+ "ungrammar",
+]
+
+[[package]]
+name = "test-utils"
+version = "0.0.0"
+dependencies = [
+ "dissimilar",
+ "profile",
+ "rustc-hash",
+ "stdx",
+ "text-size",
+]
+
+[[package]]
+name = "text-edit"
+version = "0.0.0"
+dependencies = [
+ "itertools",
+ "text-size",
+]
+
+[[package]]
+name = "text-size"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "288cb548dbe72b652243ea797201f3d481a0609a967980fcc5b2315ea811560a"
+
+[[package]]
+name = "thiserror"
+version = "1.0.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "threadpool"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa"
+dependencies = [
+ "num_cpus",
+]
+
+[[package]]
+name = "tikv-jemalloc-ctl"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e37706572f4b151dff7a0146e040804e9c26fe3a3118591112f05cf12a4216c1"
+dependencies = [
+ "libc",
+ "paste",
+ "tikv-jemalloc-sys",
+]
+
+[[package]]
+name = "tikv-jemalloc-sys"
+version = "0.5.1+5.3.0-patched"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "931e876f91fed0827f863a2d153897790da0b24d882c721a79cb3beb0b903261"
+dependencies = [
+ "cc",
+ "fs_extra",
+ "libc",
+]
+
+[[package]]
+name = "tikv-jemallocator"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20612db8a13a6c06d57ec83953694185a367e16945f66565e8028d2c0bd76979"
+dependencies = [
+ "libc",
+ "tikv-jemalloc-sys",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
+
+[[package]]
+name = "toolchain"
+version = "0.0.0"
+dependencies = [
+ "home",
+]
+
+[[package]]
+name = "tracing"
+version = "0.1.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307"
+dependencies = [
+ "cfg-if",
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7"
+dependencies = [
+ "once_cell",
+ "valuable",
+]
+
+[[package]]
+name = "tracing-log"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922"
+dependencies = [
+ "lazy_static",
+ "log",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60db860322da191b40952ad9affe65ea23e7dd6a5c442c2c42865810c6ab8e6b"
+dependencies = [
+ "matchers",
+ "once_cell",
+ "regex",
+ "sharded-slab",
+ "thread_local",
+ "tracing",
+ "tracing-core",
+ "tracing-log",
+]
+
+[[package]]
+name = "tracing-tree"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d07e90b329c621ade432823988574e820212648aa40e7a2497777d58de0fb453"
+dependencies = [
+ "ansi_term",
+ "atty",
+ "tracing-core",
+ "tracing-log",
+ "tracing-subscriber",
+]
+
+[[package]]
+name = "tt"
+version = "0.0.0"
+dependencies = [
+ "smol_str",
+ "stdx",
+]
+
+[[package]]
+name = "typed-arena"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0685c84d5d54d1c26f7d3eb96cd41550adb97baed141a761cf335d3d33bcd0ae"
+
+[[package]]
+name = "ungrammar"
+version = "1.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3e5df347f0bf3ec1d670aad6ca5c6a1859cd9ea61d2113125794654ccced68f"
+
+[[package]]
+name = "unicase"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
+dependencies = [
+ "version_check",
+]
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04"
+
+[[package]]
+name = "url"
+version = "2.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "matches",
+ "percent-encoding",
+ "serde",
+]
+
+[[package]]
+name = "valuable"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "vfs"
+version = "0.0.0"
+dependencies = [
+ "fst",
+ "indexmap",
+ "paths",
+ "rustc-hash",
+ "stdx",
+]
+
+[[package]]
+name = "vfs-notify"
+version = "0.0.0"
+dependencies = [
+ "crossbeam-channel",
+ "jod-thread",
+ "notify",
+ "paths",
+ "tracing",
+ "vfs",
+ "walkdir",
+]
+
+[[package]]
+name = "walkdir"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
+dependencies = [
+ "same-file",
+ "winapi",
+ "winapi-util",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-sys"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82ca39602d5cbfa692c4b67e3bcbb2751477355141c1ed434c94da4186836ff6"
+dependencies = [
+ "windows_aarch64_msvc 0.28.0",
+ "windows_i686_gnu 0.28.0",
+ "windows_i686_msvc 0.28.0",
+ "windows_x86_64_gnu 0.28.0",
+ "windows_x86_64_msvc 0.28.0",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
+dependencies = [
+ "windows_aarch64_msvc 0.36.1",
+ "windows_i686_gnu 0.36.1",
+ "windows_i686_msvc 0.36.1",
+ "windows_x86_64_gnu 0.36.1",
+ "windows_x86_64_msvc 0.36.1",
+]
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52695a41e536859d5308cc613b4a022261a274390b25bd29dfff4bf08505f3c2"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f54725ac23affef038fecb177de6c9bf065787c2f432f79e3c373da92f3e1d8a"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51d5158a43cc43623c0729d1ad6647e62fa384a3d135fd15108d37c683461f64"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc31f409f565611535130cfe7ee8e6655d3fa99c1c61013981e491921b5ce954"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f2b8c7cbd3bfdddd9ab98769f9746a7fad1bca236554cd032b78d768bc0e89f"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
+
+[[package]]
+name = "write-json"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06069a848f95fceae3e5e03c0ddc8cb78452b56654ee0c8e68f938cf790fb9e3"
+
+[[package]]
+name = "xflags"
- checksum = "3f14fe1ed41a5a2b5ef3f565586c4a8a559ee55d3953faab360a771135bdee00"
++version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
- version = "0.2.4"
++checksum = "cbf19f5031a1a812e96fede16f8161218883079946cea87619d3613db1efd268"
+dependencies = [
+ "xflags-macros",
+]
+
+[[package]]
+name = "xflags-macros"
- checksum = "45d11d5fc2a97287eded8b170ca80533b3c42646dd7fa386a5eb045817921022"
++version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "2afbd7f2039bb6cad2dd45f0c5dff49c0d4e26118398768b7a605524d4251809"
+
+[[package]]
+name = "xshell"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d47097dc5c85234b1e41851b3422dd6d19b3befdd35b4ae5ce386724aeca981"
+dependencies = [
+ "xshell-macros",
+]
+
+[[package]]
+name = "xshell-macros"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88301b56c26dd9bf5c43d858538f82d6f3f7764767defbc5d34e59459901c41a"
+
+[[package]]
+name = "xtask"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "flate2",
+ "write-json",
+ "xflags",
+ "xshell",
+]
--- /dev/null
- CrateOrigin::CratesIo { repo: None },
+//! A set of high-level utility fixture methods to use in tests.
+use std::{mem, str::FromStr, sync::Arc};
+
+use cfg::CfgOptions;
+use rustc_hash::FxHashMap;
+use test_utils::{
+ extract_range_or_offset, Fixture, RangeOrOffset, CURSOR_MARKER, ESCAPED_CURSOR_MARKER,
+};
+use tt::Subtree;
+use vfs::{file_set::FileSet, VfsPath};
+
+use crate::{
+ input::{CrateName, CrateOrigin, LangCrateOrigin},
+ Change, CrateDisplayName, CrateGraph, CrateId, Dependency, Edition, Env, FileId, FilePosition,
+ FileRange, ProcMacro, ProcMacroExpander, ProcMacroExpansionError, SourceDatabaseExt,
+ SourceRoot, SourceRootId,
+};
+
+pub const WORKSPACE: SourceRootId = SourceRootId(0);
+
+pub trait WithFixture: Default + SourceDatabaseExt + 'static {
+ fn with_single_file(ra_fixture: &str) -> (Self, FileId) {
+ let fixture = ChangeFixture::parse(ra_fixture);
+ let mut db = Self::default();
+ fixture.change.apply(&mut db);
+ assert_eq!(fixture.files.len(), 1);
+ (db, fixture.files[0])
+ }
+
+ fn with_many_files(ra_fixture: &str) -> (Self, Vec<FileId>) {
+ let fixture = ChangeFixture::parse(ra_fixture);
+ let mut db = Self::default();
+ fixture.change.apply(&mut db);
+ assert!(fixture.file_position.is_none());
+ (db, fixture.files)
+ }
+
+ fn with_files(ra_fixture: &str) -> Self {
+ let fixture = ChangeFixture::parse(ra_fixture);
+ let mut db = Self::default();
+ fixture.change.apply(&mut db);
+ assert!(fixture.file_position.is_none());
+ db
+ }
+
+ fn with_files_extra_proc_macros(
+ ra_fixture: &str,
+ proc_macros: Vec<(String, ProcMacro)>,
+ ) -> Self {
+ let fixture = ChangeFixture::parse_with_proc_macros(ra_fixture, proc_macros);
+ let mut db = Self::default();
+ fixture.change.apply(&mut db);
+ assert!(fixture.file_position.is_none());
+ db
+ }
+
+ fn with_position(ra_fixture: &str) -> (Self, FilePosition) {
+ let (db, file_id, range_or_offset) = Self::with_range_or_offset(ra_fixture);
+ let offset = range_or_offset.expect_offset();
+ (db, FilePosition { file_id, offset })
+ }
+
+ fn with_range(ra_fixture: &str) -> (Self, FileRange) {
+ let (db, file_id, range_or_offset) = Self::with_range_or_offset(ra_fixture);
+ let range = range_or_offset.expect_range();
+ (db, FileRange { file_id, range })
+ }
+
+ fn with_range_or_offset(ra_fixture: &str) -> (Self, FileId, RangeOrOffset) {
+ let fixture = ChangeFixture::parse(ra_fixture);
+ let mut db = Self::default();
+ fixture.change.apply(&mut db);
+ let (file_id, range_or_offset) = fixture
+ .file_position
+ .expect("Could not find file position in fixture. Did you forget to add an `$0`?");
+ (db, file_id, range_or_offset)
+ }
+
+ fn test_crate(&self) -> CrateId {
+ let crate_graph = self.crate_graph();
+ let mut it = crate_graph.iter();
+ let res = it.next().unwrap();
+ assert!(it.next().is_none());
+ res
+ }
+}
+
+impl<DB: SourceDatabaseExt + Default + 'static> WithFixture for DB {}
+
+pub struct ChangeFixture {
+ pub file_position: Option<(FileId, RangeOrOffset)>,
+ pub files: Vec<FileId>,
+ pub change: Change,
+}
+
+impl ChangeFixture {
+ pub fn parse(ra_fixture: &str) -> ChangeFixture {
+ Self::parse_with_proc_macros(ra_fixture, Vec::new())
+ }
+
+ pub fn parse_with_proc_macros(
+ ra_fixture: &str,
+ mut proc_macros: Vec<(String, ProcMacro)>,
+ ) -> ChangeFixture {
+ let (mini_core, proc_macro_names, fixture) = Fixture::parse(ra_fixture);
+ let mut change = Change::new();
+
+ let mut files = Vec::new();
+ let mut crate_graph = CrateGraph::default();
+ let mut crates = FxHashMap::default();
+ let mut crate_deps = Vec::new();
+ let mut default_crate_root: Option<FileId> = None;
+ let mut default_cfg = CfgOptions::default();
+
+ let mut file_set = FileSet::default();
+ let mut current_source_root_kind = SourceRootKind::Local;
+ let source_root_prefix = "/".to_string();
+ let mut file_id = FileId(0);
+ let mut roots = Vec::new();
+
+ let mut file_position = None;
+
+ for entry in fixture {
+ let text = if entry.text.contains(CURSOR_MARKER) {
+ if entry.text.contains(ESCAPED_CURSOR_MARKER) {
+ entry.text.replace(ESCAPED_CURSOR_MARKER, CURSOR_MARKER)
+ } else {
+ let (range_or_offset, text) = extract_range_or_offset(&entry.text);
+ assert!(file_position.is_none());
+ file_position = Some((file_id, range_or_offset));
+ text
+ }
+ } else {
+ entry.text.clone()
+ };
+
+ let meta = FileMeta::from(entry);
+ assert!(meta.path.starts_with(&source_root_prefix));
+ if !meta.deps.is_empty() {
+ assert!(meta.krate.is_some(), "can't specify deps without naming the crate")
+ }
+
+ if let Some(kind) = &meta.introduce_new_source_root {
+ let root = match current_source_root_kind {
+ SourceRootKind::Local => SourceRoot::new_local(mem::take(&mut file_set)),
+ SourceRootKind::Library => SourceRoot::new_library(mem::take(&mut file_set)),
+ };
+ roots.push(root);
+ current_source_root_kind = *kind;
+ }
+
+ if let Some((krate, origin, version)) = meta.krate {
+ let crate_name = CrateName::normalize_dashes(&krate);
+ let crate_id = crate_graph.add_crate_root(
+ file_id,
+ meta.edition,
+ Some(crate_name.clone().into()),
+ version,
+ meta.cfg.clone(),
+ meta.cfg,
+ meta.env,
+ Ok(Vec::new()),
+ false,
+ origin,
+ );
+ let prev = crates.insert(crate_name.clone(), crate_id);
+ assert!(prev.is_none());
+ for dep in meta.deps {
+ let prelude = meta.extern_prelude.contains(&dep);
+ let dep = CrateName::normalize_dashes(&dep);
+ crate_deps.push((crate_name.clone(), dep, prelude))
+ }
+ } else if meta.path == "/main.rs" || meta.path == "/lib.rs" {
+ assert!(default_crate_root.is_none());
+ default_crate_root = Some(file_id);
+ default_cfg = meta.cfg;
+ }
+
+ change.change_file(file_id, Some(Arc::new(text)));
+ let path = VfsPath::new_virtual_path(meta.path);
+ file_set.insert(file_id, path);
+ files.push(file_id);
+ file_id.0 += 1;
+ }
+
+ if crates.is_empty() {
+ let crate_root = default_crate_root
+ .expect("missing default crate root, specify a main.rs or lib.rs");
+ crate_graph.add_crate_root(
+ crate_root,
+ Edition::CURRENT,
+ Some(CrateName::new("test").unwrap().into()),
+ None,
+ default_cfg.clone(),
+ default_cfg,
+ Env::default(),
+ Ok(Vec::new()),
+ false,
- CrateOrigin::CratesIo { repo: None },
++ CrateOrigin::CratesIo { repo: None, name: None },
+ );
+ } else {
+ for (from, to, prelude) in crate_deps {
+ let from_id = crates[&from];
+ let to_id = crates[&to];
+ crate_graph
+ .add_dep(
+ from_id,
+ Dependency::with_prelude(CrateName::new(&to).unwrap(), to_id, prelude),
+ )
+ .unwrap();
+ }
+ }
+
+ if let Some(mini_core) = mini_core {
+ let core_file = file_id;
+ file_id.0 += 1;
+
+ let mut fs = FileSet::default();
+ fs.insert(core_file, VfsPath::new_virtual_path("/sysroot/core/lib.rs".to_string()));
+ roots.push(SourceRoot::new_library(fs));
+
+ change.change_file(core_file, Some(Arc::new(mini_core.source_code())));
+
+ let all_crates = crate_graph.crates_in_topological_order();
+
+ let core_crate = crate_graph.add_crate_root(
+ core_file,
+ Edition::Edition2021,
+ Some(CrateDisplayName::from_canonical_name("core".to_string())),
+ None,
+ CfgOptions::default(),
+ CfgOptions::default(),
+ Env::default(),
+ Ok(Vec::new()),
+ false,
+ CrateOrigin::Lang(LangCrateOrigin::Core),
+ );
+
+ for krate in all_crates {
+ crate_graph
+ .add_dep(krate, Dependency::new(CrateName::new("core").unwrap(), core_crate))
+ .unwrap();
+ }
+ }
+
+ if !proc_macro_names.is_empty() {
+ let proc_lib_file = file_id;
+ file_id.0 += 1;
+
+ proc_macros.extend(default_test_proc_macros());
+ let (proc_macro, source) = filter_test_proc_macros(&proc_macro_names, proc_macros);
+ let mut fs = FileSet::default();
+ fs.insert(
+ proc_lib_file,
+ VfsPath::new_virtual_path("/sysroot/proc_macros/lib.rs".to_string()),
+ );
+ roots.push(SourceRoot::new_library(fs));
+
+ change.change_file(proc_lib_file, Some(Arc::new(source)));
+
+ let all_crates = crate_graph.crates_in_topological_order();
+
+ let proc_macros_crate = crate_graph.add_crate_root(
+ proc_lib_file,
+ Edition::Edition2021,
+ Some(CrateDisplayName::from_canonical_name("proc_macros".to_string())),
+ None,
+ CfgOptions::default(),
+ CfgOptions::default(),
+ Env::default(),
+ Ok(proc_macro),
+ true,
- (version, CrateOrigin::CratesIo { repo: Some(url.to_owned()) })
++ CrateOrigin::CratesIo { repo: None, name: None },
+ );
+
+ for krate in all_crates {
+ crate_graph
+ .add_dep(
+ krate,
+ Dependency::new(CrateName::new("proc_macros").unwrap(), proc_macros_crate),
+ )
+ .unwrap();
+ }
+ }
+
+ let root = match current_source_root_kind {
+ SourceRootKind::Local => SourceRoot::new_local(mem::take(&mut file_set)),
+ SourceRootKind::Library => SourceRoot::new_library(mem::take(&mut file_set)),
+ };
+ roots.push(root);
+ change.set_roots(roots);
+ change.set_crate_graph(crate_graph);
+
+ ChangeFixture { file_position, files, change }
+ }
+}
+
+fn default_test_proc_macros() -> [(String, ProcMacro); 4] {
+ [
+ (
+ r#"
+#[proc_macro_attribute]
+pub fn identity(_attr: TokenStream, item: TokenStream) -> TokenStream {
+ item
+}
+"#
+ .into(),
+ ProcMacro {
+ name: "identity".into(),
+ kind: crate::ProcMacroKind::Attr,
+ expander: Arc::new(IdentityProcMacroExpander),
+ },
+ ),
+ (
+ r#"
+#[proc_macro_derive(DeriveIdentity)]
+pub fn derive_identity(item: TokenStream) -> TokenStream {
+ item
+}
+"#
+ .into(),
+ ProcMacro {
+ name: "DeriveIdentity".into(),
+ kind: crate::ProcMacroKind::CustomDerive,
+ expander: Arc::new(IdentityProcMacroExpander),
+ },
+ ),
+ (
+ r#"
+#[proc_macro_attribute]
+pub fn input_replace(attr: TokenStream, _item: TokenStream) -> TokenStream {
+ attr
+}
+"#
+ .into(),
+ ProcMacro {
+ name: "input_replace".into(),
+ kind: crate::ProcMacroKind::Attr,
+ expander: Arc::new(AttributeInputReplaceProcMacroExpander),
+ },
+ ),
+ (
+ r#"
+#[proc_macro]
+pub fn mirror(input: TokenStream) -> TokenStream {
+ input
+}
+"#
+ .into(),
+ ProcMacro {
+ name: "mirror".into(),
+ kind: crate::ProcMacroKind::FuncLike,
+ expander: Arc::new(MirrorProcMacroExpander),
+ },
+ ),
+ ]
+}
+
+fn filter_test_proc_macros(
+ proc_macro_names: &[String],
+ proc_macro_defs: Vec<(String, ProcMacro)>,
+) -> (Vec<ProcMacro>, String) {
+ // The source here is only required so that paths to the macros exist and are resolvable.
+ let mut source = String::new();
+ let mut proc_macros = Vec::new();
+
+ for (c, p) in proc_macro_defs {
+ if !proc_macro_names.iter().any(|name| name == &stdx::to_lower_snake_case(&p.name)) {
+ continue;
+ }
+ proc_macros.push(p);
+ source += &c;
+ }
+
+ (proc_macros, source)
+}
+
+#[derive(Debug, Clone, Copy)]
+enum SourceRootKind {
+ Local,
+ Library,
+}
+
+#[derive(Debug)]
+struct FileMeta {
+ path: String,
+ krate: Option<(String, CrateOrigin, Option<String>)>,
+ deps: Vec<String>,
+ extern_prelude: Vec<String>,
+ cfg: CfgOptions,
+ edition: Edition,
+ env: Env,
+ introduce_new_source_root: Option<SourceRootKind>,
+}
+
+fn parse_crate(crate_str: String) -> (String, CrateOrigin, Option<String>) {
+ if let Some((a, b)) = crate_str.split_once('@') {
+ let (version, origin) = match b.split_once(':') {
+ Some(("CratesIo", data)) => match data.split_once(',') {
+ Some((version, url)) => {
- _ => CrateOrigin::CratesIo { repo: None },
++ (version, CrateOrigin::CratesIo { repo: Some(url.to_owned()), name: None })
+ }
+ _ => panic!("Bad crates.io parameter: {}", data),
+ },
+ _ => panic!("Bad string for crate origin: {}", b),
+ };
+ (a.to_owned(), origin, Some(version.to_string()))
+ } else {
+ let crate_origin = match &*crate_str {
+ "std" => CrateOrigin::Lang(LangCrateOrigin::Std),
+ "core" => CrateOrigin::Lang(LangCrateOrigin::Core),
++ _ => CrateOrigin::CratesIo { repo: None, name: None },
+ };
+ (crate_str, crate_origin, None)
+ }
+}
+
+impl From<Fixture> for FileMeta {
+ fn from(f: Fixture) -> FileMeta {
+ let mut cfg = CfgOptions::default();
+ f.cfg_atoms.iter().for_each(|it| cfg.insert_atom(it.into()));
+ f.cfg_key_values.iter().for_each(|(k, v)| cfg.insert_key_value(k.into(), v.into()));
+ let deps = f.deps;
+ FileMeta {
+ path: f.path,
+ krate: f.krate.map(parse_crate),
+ extern_prelude: f.extern_prelude.unwrap_or_else(|| deps.clone()),
+ deps,
+ cfg,
+ edition: f.edition.as_ref().map_or(Edition::CURRENT, |v| Edition::from_str(v).unwrap()),
+ env: f.env.into_iter().collect(),
+ introduce_new_source_root: f.introduce_new_source_root.map(|kind| match &*kind {
+ "local" => SourceRootKind::Local,
+ "library" => SourceRootKind::Library,
+ invalid => panic!("invalid source root kind '{}'", invalid),
+ }),
+ }
+ }
+}
+
+// Identity mapping
+#[derive(Debug)]
+struct IdentityProcMacroExpander;
+impl ProcMacroExpander for IdentityProcMacroExpander {
+ fn expand(
+ &self,
+ subtree: &Subtree,
+ _: Option<&Subtree>,
+ _: &Env,
+ ) -> Result<Subtree, ProcMacroExpansionError> {
+ Ok(subtree.clone())
+ }
+}
+
+// Pastes the attribute input as its output
+#[derive(Debug)]
+struct AttributeInputReplaceProcMacroExpander;
+impl ProcMacroExpander for AttributeInputReplaceProcMacroExpander {
+ fn expand(
+ &self,
+ _: &Subtree,
+ attrs: Option<&Subtree>,
+ _: &Env,
+ ) -> Result<Subtree, ProcMacroExpansionError> {
+ attrs
+ .cloned()
+ .ok_or_else(|| ProcMacroExpansionError::Panic("Expected attribute input".into()))
+ }
+}
+
+#[derive(Debug)]
+struct MirrorProcMacroExpander;
+impl ProcMacroExpander for MirrorProcMacroExpander {
+ fn expand(
+ &self,
+ input: &Subtree,
+ _: Option<&Subtree>,
+ _: &Env,
+ ) -> Result<Subtree, ProcMacroExpansionError> {
+ fn traverse(input: &Subtree) -> Subtree {
+ let mut res = Subtree::default();
+ res.delimiter = input.delimiter;
+ for tt in input.token_trees.iter().rev() {
+ let tt = match tt {
+ tt::TokenTree::Leaf(leaf) => tt::TokenTree::Leaf(leaf.clone()),
+ tt::TokenTree::Subtree(sub) => tt::TokenTree::Subtree(traverse(sub)),
+ };
+ res.token_trees.push(tt);
+ }
+ res
+ }
+ Ok(traverse(input))
+ }
+}
--- /dev/null
- CratesIo { repo: Option<String> },
+//! This module specifies the input to rust-analyzer. In some sense, this is
+//! **the** most important module, because all other fancy stuff is strictly
+//! derived from this input.
+//!
+//! Note that neither this module, nor any other part of the analyzer's core do
+//! actual IO. See `vfs` and `project_model` in the `rust-analyzer` crate for how
+//! actual IO is done and lowered to input.
+
+use std::{fmt, ops, panic::RefUnwindSafe, str::FromStr, sync::Arc};
+
+use cfg::CfgOptions;
+use rustc_hash::FxHashMap;
+use stdx::hash::{NoHashHashMap, NoHashHashSet};
+use syntax::SmolStr;
+use tt::Subtree;
+use vfs::{file_set::FileSet, AnchoredPath, FileId, VfsPath};
+
+/// Files are grouped into source roots. A source root is a directory on the
+/// file systems which is watched for changes. Typically it corresponds to a
+/// Rust crate. Source roots *might* be nested: in this case, a file belongs to
+/// the nearest enclosing source root. Paths to files are always relative to a
+/// source root, and the analyzer does not know the root path of the source root at
+/// all. So, a file from one source root can't refer to a file in another source
+/// root by path.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub struct SourceRootId(pub u32);
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct SourceRoot {
+ /// Sysroot or crates.io library.
+ ///
+ /// Libraries are considered mostly immutable, this assumption is used to
+ /// optimize salsa's query structure
+ pub is_library: bool,
+ file_set: FileSet,
+}
+
+impl SourceRoot {
+ pub fn new_local(file_set: FileSet) -> SourceRoot {
+ SourceRoot { is_library: false, file_set }
+ }
+
+ pub fn new_library(file_set: FileSet) -> SourceRoot {
+ SourceRoot { is_library: true, file_set }
+ }
+
+ pub fn path_for_file(&self, file: &FileId) -> Option<&VfsPath> {
+ self.file_set.path_for_file(file)
+ }
+
+ pub fn file_for_path(&self, path: &VfsPath) -> Option<&FileId> {
+ self.file_set.file_for_path(path)
+ }
+
+ pub fn resolve_path(&self, path: AnchoredPath<'_>) -> Option<FileId> {
+ self.file_set.resolve_path(path)
+ }
+
+ pub fn iter(&self) -> impl Iterator<Item = FileId> + '_ {
+ self.file_set.iter()
+ }
+}
+
+/// `CrateGraph` is a bit of information which turns a set of text files into a
+/// number of Rust crates.
+///
+/// Each crate is defined by the `FileId` of its root module, the set of enabled
+/// `cfg` flags and the set of dependencies.
+///
+/// Note that, due to cfg's, there might be several crates for a single `FileId`!
+///
+/// For the purposes of analysis, a crate does not have a name. Instead, names
+/// are specified on dependency edges. That is, a crate might be known under
+/// different names in different dependent crates.
+///
+/// Note that `CrateGraph` is build-system agnostic: it's a concept of the Rust
+/// language proper, not a concept of the build system. In practice, we get
+/// `CrateGraph` by lowering `cargo metadata` output.
+///
+/// `CrateGraph` is `!Serialize` by design, see
+/// <https://github.com/rust-lang/rust-analyzer/blob/master/docs/dev/architecture.md#serialization>
+#[derive(Debug, Clone, Default /* Serialize, Deserialize */)]
+pub struct CrateGraph {
+ arena: NoHashHashMap<CrateId, CrateData>,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub struct CrateId(pub u32);
+
+impl stdx::hash::NoHashHashable for CrateId {}
+impl std::hash::Hash for CrateId {
+ fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+ self.0.hash(state);
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct CrateName(SmolStr);
+
+impl CrateName {
+ /// Creates a crate name, checking for dashes in the string provided.
+ /// Dashes are not allowed in the crate names,
+ /// hence the input string is returned as `Err` for those cases.
+ pub fn new(name: &str) -> Result<CrateName, &str> {
+ if name.contains('-') {
+ Err(name)
+ } else {
+ Ok(Self(SmolStr::new(name)))
+ }
+ }
+
+ /// Creates a crate name, unconditionally replacing the dashes with underscores.
+ pub fn normalize_dashes(name: &str) -> CrateName {
+ Self(SmolStr::new(name.replace('-', "_")))
+ }
+
+ pub fn as_smol_str(&self) -> &SmolStr {
+ &self.0
+ }
+}
+
+impl fmt::Display for CrateName {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ self.0.fmt(f)
+ }
+}
+
+impl ops::Deref for CrateName {
+ type Target = str;
+ fn deref(&self) -> &str {
+ &*self.0
+ }
+}
+
+/// Origin of the crates. It is used in emitting monikers.
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub enum CrateOrigin {
+ /// Crates that are from crates.io official registry,
- CrateOrigin::CratesIo { repo: None },
++ CratesIo { repo: Option<String>, name: Option<String> },
+ /// Crates that are provided by the language, like std, core, proc-macro, ...
+ Lang(LangCrateOrigin),
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum LangCrateOrigin {
+ Alloc,
+ Core,
+ ProcMacro,
+ Std,
+ Test,
+ Other,
+}
+
+impl From<&str> for LangCrateOrigin {
+ fn from(s: &str) -> Self {
+ match s {
+ "alloc" => LangCrateOrigin::Alloc,
+ "core" => LangCrateOrigin::Core,
+ "proc-macro" => LangCrateOrigin::ProcMacro,
+ "std" => LangCrateOrigin::Std,
+ "test" => LangCrateOrigin::Test,
+ _ => LangCrateOrigin::Other,
+ }
+ }
+}
+
+impl fmt::Display for LangCrateOrigin {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let text = match self {
+ LangCrateOrigin::Alloc => "alloc",
+ LangCrateOrigin::Core => "core",
+ LangCrateOrigin::ProcMacro => "proc_macro",
+ LangCrateOrigin::Std => "std",
+ LangCrateOrigin::Test => "test",
+ LangCrateOrigin::Other => "other",
+ };
+ f.write_str(text)
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct CrateDisplayName {
+ // The name we use to display various paths (with `_`).
+ crate_name: CrateName,
+ // The name as specified in Cargo.toml (with `-`).
+ canonical_name: String,
+}
+
+impl CrateDisplayName {
+ pub fn canonical_name(&self) -> &str {
+ &self.canonical_name
+ }
+ pub fn crate_name(&self) -> &CrateName {
+ &self.crate_name
+ }
+}
+
+impl From<CrateName> for CrateDisplayName {
+ fn from(crate_name: CrateName) -> CrateDisplayName {
+ let canonical_name = crate_name.to_string();
+ CrateDisplayName { crate_name, canonical_name }
+ }
+}
+
+impl fmt::Display for CrateDisplayName {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ self.crate_name.fmt(f)
+ }
+}
+
+impl ops::Deref for CrateDisplayName {
+ type Target = str;
+ fn deref(&self) -> &str {
+ &*self.crate_name
+ }
+}
+
+impl CrateDisplayName {
+ pub fn from_canonical_name(canonical_name: String) -> CrateDisplayName {
+ let crate_name = CrateName::normalize_dashes(&canonical_name);
+ CrateDisplayName { crate_name, canonical_name }
+ }
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+pub struct ProcMacroId(pub u32);
+
+#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
+pub enum ProcMacroKind {
+ CustomDerive,
+ FuncLike,
+ Attr,
+}
+
+pub trait ProcMacroExpander: fmt::Debug + Send + Sync + RefUnwindSafe {
+ fn expand(
+ &self,
+ subtree: &Subtree,
+ attrs: Option<&Subtree>,
+ env: &Env,
+ ) -> Result<Subtree, ProcMacroExpansionError>;
+}
+
+pub enum ProcMacroExpansionError {
+ Panic(String),
+ /// Things like "proc macro server was killed by OOM".
+ System(String),
+}
+
+pub type ProcMacroLoadResult = Result<Vec<ProcMacro>, String>;
+
+#[derive(Debug, Clone)]
+pub struct ProcMacro {
+ pub name: SmolStr,
+ pub kind: ProcMacroKind,
+ pub expander: Arc<dyn ProcMacroExpander>,
+}
+
+#[derive(Debug, Clone)]
+pub struct CrateData {
+ pub root_file_id: FileId,
+ pub edition: Edition,
+ pub version: Option<String>,
+ /// A name used in the package's project declaration: for Cargo projects,
+ /// its `[package].name` can be different for other project types or even
+ /// absent (a dummy crate for the code snippet, for example).
+ ///
+ /// For purposes of analysis, crates are anonymous (only names in
+ /// `Dependency` matters), this name should only be used for UI.
+ pub display_name: Option<CrateDisplayName>,
+ pub cfg_options: CfgOptions,
+ pub potential_cfg_options: CfgOptions,
+ pub env: Env,
+ pub dependencies: Vec<Dependency>,
+ pub proc_macro: ProcMacroLoadResult,
+ pub origin: CrateOrigin,
+ pub is_proc_macro: bool,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum Edition {
+ Edition2015,
+ Edition2018,
+ Edition2021,
+}
+
+impl Edition {
+ pub const CURRENT: Edition = Edition::Edition2018;
+}
+
+#[derive(Default, Debug, Clone, PartialEq, Eq)]
+pub struct Env {
+ entries: FxHashMap<String, String>,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct Dependency {
+ pub crate_id: CrateId,
+ pub name: CrateName,
+ prelude: bool,
+}
+
+impl Dependency {
+ pub fn new(name: CrateName, crate_id: CrateId) -> Self {
+ Self { name, crate_id, prelude: true }
+ }
+
+ pub fn with_prelude(name: CrateName, crate_id: CrateId, prelude: bool) -> Self {
+ Self { name, crate_id, prelude }
+ }
+
+ /// Whether this dependency is to be added to the depending crate's extern prelude.
+ pub fn is_prelude(&self) -> bool {
+ self.prelude
+ }
+}
+
+impl CrateGraph {
+ pub fn add_crate_root(
+ &mut self,
+ root_file_id: FileId,
+ edition: Edition,
+ display_name: Option<CrateDisplayName>,
+ version: Option<String>,
+ cfg_options: CfgOptions,
+ potential_cfg_options: CfgOptions,
+ env: Env,
+ proc_macro: ProcMacroLoadResult,
+ is_proc_macro: bool,
+ origin: CrateOrigin,
+ ) -> CrateId {
+ let data = CrateData {
+ root_file_id,
+ edition,
+ version,
+ display_name,
+ cfg_options,
+ potential_cfg_options,
+ env,
+ proc_macro,
+ dependencies: Vec::new(),
+ origin,
+ is_proc_macro,
+ };
+ let crate_id = CrateId(self.arena.len() as u32);
+ let prev = self.arena.insert(crate_id, data);
+ assert!(prev.is_none());
+ crate_id
+ }
+
+ pub fn add_dep(
+ &mut self,
+ from: CrateId,
+ dep: Dependency,
+ ) -> Result<(), CyclicDependenciesError> {
+ let _p = profile::span("add_dep");
+
+ // Check if adding a dep from `from` to `to` creates a cycle. To figure
+ // that out, look for a path in the *opposite* direction, from `to` to
+ // `from`.
+ if let Some(path) = self.find_path(&mut NoHashHashSet::default(), dep.crate_id, from) {
+ let path = path.into_iter().map(|it| (it, self[it].display_name.clone())).collect();
+ let err = CyclicDependenciesError { path };
+ assert!(err.from().0 == from && err.to().0 == dep.crate_id);
+ return Err(err);
+ }
+
+ self.arena.get_mut(&from).unwrap().add_dep(dep);
+ Ok(())
+ }
+
+ pub fn is_empty(&self) -> bool {
+ self.arena.is_empty()
+ }
+
+ pub fn iter(&self) -> impl Iterator<Item = CrateId> + '_ {
+ self.arena.keys().copied()
+ }
+
+ /// Returns an iterator over all transitive dependencies of the given crate,
+ /// including the crate itself.
+ pub fn transitive_deps(&self, of: CrateId) -> impl Iterator<Item = CrateId> {
+ let mut worklist = vec![of];
+ let mut deps = NoHashHashSet::default();
+
+ while let Some(krate) = worklist.pop() {
+ if !deps.insert(krate) {
+ continue;
+ }
+
+ worklist.extend(self[krate].dependencies.iter().map(|dep| dep.crate_id));
+ }
+
+ deps.into_iter()
+ }
+
+ /// Returns all transitive reverse dependencies of the given crate,
+ /// including the crate itself.
+ pub fn transitive_rev_deps(&self, of: CrateId) -> impl Iterator<Item = CrateId> {
+ let mut worklist = vec![of];
+ let mut rev_deps = NoHashHashSet::default();
+ rev_deps.insert(of);
+
+ let mut inverted_graph = NoHashHashMap::<_, Vec<_>>::default();
+ self.arena.iter().for_each(|(&krate, data)| {
+ data.dependencies
+ .iter()
+ .for_each(|dep| inverted_graph.entry(dep.crate_id).or_default().push(krate))
+ });
+
+ while let Some(krate) = worklist.pop() {
+ if let Some(krate_rev_deps) = inverted_graph.get(&krate) {
+ krate_rev_deps
+ .iter()
+ .copied()
+ .filter(|&rev_dep| rev_deps.insert(rev_dep))
+ .for_each(|rev_dep| worklist.push(rev_dep));
+ }
+ }
+
+ rev_deps.into_iter()
+ }
+
+ /// Returns all crates in the graph, sorted in topological order (ie. dependencies of a crate
+ /// come before the crate itself).
+ pub fn crates_in_topological_order(&self) -> Vec<CrateId> {
+ let mut res = Vec::new();
+ let mut visited = NoHashHashSet::default();
+
+ for krate in self.arena.keys().copied() {
+ go(self, &mut visited, &mut res, krate);
+ }
+
+ return res;
+
+ fn go(
+ graph: &CrateGraph,
+ visited: &mut NoHashHashSet<CrateId>,
+ res: &mut Vec<CrateId>,
+ source: CrateId,
+ ) {
+ if !visited.insert(source) {
+ return;
+ }
+ for dep in graph[source].dependencies.iter() {
+ go(graph, visited, res, dep.crate_id)
+ }
+ res.push(source)
+ }
+ }
+
+ // FIXME: this only finds one crate with the given root; we could have multiple
+ pub fn crate_id_for_crate_root(&self, file_id: FileId) -> Option<CrateId> {
+ let (&crate_id, _) =
+ self.arena.iter().find(|(_crate_id, data)| data.root_file_id == file_id)?;
+ Some(crate_id)
+ }
+
+ /// Extends this crate graph by adding a complete disjoint second crate
+ /// graph.
+ ///
+ /// The ids of the crates in the `other` graph are shifted by the return
+ /// amount.
+ pub fn extend(&mut self, other: CrateGraph) -> u32 {
+ let start = self.arena.len() as u32;
+ self.arena.extend(other.arena.into_iter().map(|(id, mut data)| {
+ let new_id = id.shift(start);
+ for dep in &mut data.dependencies {
+ dep.crate_id = dep.crate_id.shift(start);
+ }
+ (new_id, data)
+ }));
+ start
+ }
+
+ fn find_path(
+ &self,
+ visited: &mut NoHashHashSet<CrateId>,
+ from: CrateId,
+ to: CrateId,
+ ) -> Option<Vec<CrateId>> {
+ if !visited.insert(from) {
+ return None;
+ }
+
+ if from == to {
+ return Some(vec![to]);
+ }
+
+ for dep in &self[from].dependencies {
+ let crate_id = dep.crate_id;
+ if let Some(mut path) = self.find_path(visited, crate_id, to) {
+ path.push(from);
+ return Some(path);
+ }
+ }
+
+ None
+ }
+
+ // Work around for https://github.com/rust-lang/rust-analyzer/issues/6038.
+ // As hacky as it gets.
+ pub fn patch_cfg_if(&mut self) -> bool {
+ let cfg_if = self.hacky_find_crate("cfg_if");
+ let std = self.hacky_find_crate("std");
+ match (cfg_if, std) {
+ (Some(cfg_if), Some(std)) => {
+ self.arena.get_mut(&cfg_if).unwrap().dependencies.clear();
+ self.arena
+ .get_mut(&std)
+ .unwrap()
+ .dependencies
+ .push(Dependency::new(CrateName::new("cfg_if").unwrap(), cfg_if));
+ true
+ }
+ _ => false,
+ }
+ }
+
+ fn hacky_find_crate(&self, display_name: &str) -> Option<CrateId> {
+ self.iter().find(|it| self[*it].display_name.as_deref() == Some(display_name))
+ }
+}
+
+impl ops::Index<CrateId> for CrateGraph {
+ type Output = CrateData;
+ fn index(&self, crate_id: CrateId) -> &CrateData {
+ &self.arena[&crate_id]
+ }
+}
+
+impl CrateId {
+ fn shift(self, amount: u32) -> CrateId {
+ CrateId(self.0 + amount)
+ }
+}
+
+impl CrateData {
+ fn add_dep(&mut self, dep: Dependency) {
+ self.dependencies.push(dep)
+ }
+}
+
+impl FromStr for Edition {
+ type Err = ParseEditionError;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ let res = match s {
+ "2015" => Edition::Edition2015,
+ "2018" => Edition::Edition2018,
+ "2021" => Edition::Edition2021,
+ _ => return Err(ParseEditionError { invalid_input: s.to_string() }),
+ };
+ Ok(res)
+ }
+}
+
+impl fmt::Display for Edition {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.write_str(match self {
+ Edition::Edition2015 => "2015",
+ Edition::Edition2018 => "2018",
+ Edition::Edition2021 => "2021",
+ })
+ }
+}
+
+impl FromIterator<(String, String)> for Env {
+ fn from_iter<T: IntoIterator<Item = (String, String)>>(iter: T) -> Self {
+ Env { entries: FromIterator::from_iter(iter) }
+ }
+}
+
+impl Env {
+ pub fn set(&mut self, env: &str, value: String) {
+ self.entries.insert(env.to_owned(), value);
+ }
+
+ pub fn get(&self, env: &str) -> Option<String> {
+ self.entries.get(env).cloned()
+ }
+
+ pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
+ self.entries.iter().map(|(k, v)| (k.as_str(), v.as_str()))
+ }
+}
+
+#[derive(Debug)]
+pub struct ParseEditionError {
+ invalid_input: String,
+}
+
+impl fmt::Display for ParseEditionError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "invalid edition: {:?}", self.invalid_input)
+ }
+}
+
+impl std::error::Error for ParseEditionError {}
+
+#[derive(Debug)]
+pub struct CyclicDependenciesError {
+ path: Vec<(CrateId, Option<CrateDisplayName>)>,
+}
+
+impl CyclicDependenciesError {
+ fn from(&self) -> &(CrateId, Option<CrateDisplayName>) {
+ self.path.first().unwrap()
+ }
+ fn to(&self) -> &(CrateId, Option<CrateDisplayName>) {
+ self.path.last().unwrap()
+ }
+}
+
+impl fmt::Display for CyclicDependenciesError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let render = |(id, name): &(CrateId, Option<CrateDisplayName>)| match name {
+ Some(it) => format!("{}({:?})", it, id),
+ None => format!("{:?}", id),
+ };
+ let path = self.path.iter().rev().map(render).collect::<Vec<String>>().join(" -> ");
+ write!(
+ f,
+ "cyclic deps: {} -> {}, alternative path: {}",
+ render(self.from()),
+ render(self.to()),
+ path
+ )
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::CrateOrigin;
+
+ use super::{CfgOptions, CrateGraph, CrateName, Dependency, Edition::Edition2018, Env, FileId};
+
+ #[test]
+ fn detect_cyclic_dependency_indirect() {
+ let mut graph = CrateGraph::default();
+ let crate1 = graph.add_crate_root(
+ FileId(1u32),
+ Edition2018,
+ None,
+ None,
+ CfgOptions::default(),
+ CfgOptions::default(),
+ Env::default(),
+ Ok(Vec::new()),
+ false,
- CrateOrigin::CratesIo { repo: None },
++ CrateOrigin::CratesIo { repo: None, name: None },
+ );
+ let crate2 = graph.add_crate_root(
+ FileId(2u32),
+ Edition2018,
+ None,
+ None,
+ CfgOptions::default(),
+ CfgOptions::default(),
+ Env::default(),
+ Ok(Vec::new()),
+ false,
- CrateOrigin::CratesIo { repo: None },
++ CrateOrigin::CratesIo { repo: None, name: None },
+ );
+ let crate3 = graph.add_crate_root(
+ FileId(3u32),
+ Edition2018,
+ None,
+ None,
+ CfgOptions::default(),
+ CfgOptions::default(),
+ Env::default(),
+ Ok(Vec::new()),
+ false,
- CrateOrigin::CratesIo { repo: None },
++ CrateOrigin::CratesIo { repo: None, name: None },
+ );
+ assert!(graph
+ .add_dep(crate1, Dependency::new(CrateName::new("crate2").unwrap(), crate2))
+ .is_ok());
+ assert!(graph
+ .add_dep(crate2, Dependency::new(CrateName::new("crate3").unwrap(), crate3))
+ .is_ok());
+ assert!(graph
+ .add_dep(crate3, Dependency::new(CrateName::new("crate1").unwrap(), crate1))
+ .is_err());
+ }
+
+ #[test]
+ fn detect_cyclic_dependency_direct() {
+ let mut graph = CrateGraph::default();
+ let crate1 = graph.add_crate_root(
+ FileId(1u32),
+ Edition2018,
+ None,
+ None,
+ CfgOptions::default(),
+ CfgOptions::default(),
+ Env::default(),
+ Ok(Vec::new()),
+ false,
- CrateOrigin::CratesIo { repo: None },
++ CrateOrigin::CratesIo { repo: None, name: None },
+ );
+ let crate2 = graph.add_crate_root(
+ FileId(2u32),
+ Edition2018,
+ None,
+ None,
+ CfgOptions::default(),
+ CfgOptions::default(),
+ Env::default(),
+ Ok(Vec::new()),
+ false,
- CrateOrigin::CratesIo { repo: None },
++ CrateOrigin::CratesIo { repo: None, name: None },
+ );
+ assert!(graph
+ .add_dep(crate1, Dependency::new(CrateName::new("crate2").unwrap(), crate2))
+ .is_ok());
+ assert!(graph
+ .add_dep(crate2, Dependency::new(CrateName::new("crate2").unwrap(), crate2))
+ .is_err());
+ }
+
+ #[test]
+ fn it_works() {
+ let mut graph = CrateGraph::default();
+ let crate1 = graph.add_crate_root(
+ FileId(1u32),
+ Edition2018,
+ None,
+ None,
+ CfgOptions::default(),
+ CfgOptions::default(),
+ Env::default(),
+ Ok(Vec::new()),
+ false,
- CrateOrigin::CratesIo { repo: None },
++ CrateOrigin::CratesIo { repo: None, name: None },
+ );
+ let crate2 = graph.add_crate_root(
+ FileId(2u32),
+ Edition2018,
+ None,
+ None,
+ CfgOptions::default(),
+ CfgOptions::default(),
+ Env::default(),
+ Ok(Vec::new()),
+ false,
- CrateOrigin::CratesIo { repo: None },
++ CrateOrigin::CratesIo { repo: None, name: None },
+ );
+ let crate3 = graph.add_crate_root(
+ FileId(3u32),
+ Edition2018,
+ None,
+ None,
+ CfgOptions::default(),
+ CfgOptions::default(),
+ Env::default(),
+ Ok(Vec::new()),
+ false,
- CrateOrigin::CratesIo { repo: None },
++ CrateOrigin::CratesIo { repo: None, name: None },
+ );
+ assert!(graph
+ .add_dep(crate1, Dependency::new(CrateName::new("crate2").unwrap(), crate2))
+ .is_ok());
+ assert!(graph
+ .add_dep(crate2, Dependency::new(CrateName::new("crate3").unwrap(), crate3))
+ .is_ok());
+ }
+
+ #[test]
+ fn dashes_are_normalized() {
+ let mut graph = CrateGraph::default();
+ let crate1 = graph.add_crate_root(
+ FileId(1u32),
+ Edition2018,
+ None,
+ None,
+ CfgOptions::default(),
+ CfgOptions::default(),
+ Env::default(),
+ Ok(Vec::new()),
+ false,
- CrateOrigin::CratesIo { repo: None },
++ CrateOrigin::CratesIo { repo: None, name: None },
+ );
+ let crate2 = graph.add_crate_root(
+ FileId(2u32),
+ Edition2018,
+ None,
+ None,
+ CfgOptions::default(),
+ CfgOptions::default(),
+ Env::default(),
+ Ok(Vec::new()),
+ false,
++ CrateOrigin::CratesIo { repo: None, name: None },
+ );
+ assert!(graph
+ .add_dep(
+ crate1,
+ Dependency::new(CrateName::normalize_dashes("crate-name-with-dashes"), crate2)
+ )
+ .is_ok());
+ assert_eq!(
+ graph[crate1].dependencies,
+ vec![Dependency::new(CrateName::new("crate_name_with_dashes").unwrap(), crate2)]
+ );
+ }
+}
--- /dev/null
- while let Some(event) = self.next_event(&inbox) {
+//! Flycheck provides the functionality needed to run `cargo check` or
+//! another compatible command (f.x. clippy) in a background thread and provide
+//! LSP diagnostics based on the output of the command.
+
+#![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)]
+
+use std::{
+ fmt, io,
+ process::{ChildStderr, ChildStdout, Command, Stdio},
+ time::Duration,
+};
+
+use crossbeam_channel::{never, select, unbounded, Receiver, Sender};
+use paths::AbsPathBuf;
+use rustc_hash::FxHashMap;
+use serde::Deserialize;
+use stdx::{process::streaming_output, JodChild};
+
+pub use cargo_metadata::diagnostic::{
+ Applicability, Diagnostic, DiagnosticCode, DiagnosticLevel, DiagnosticSpan,
+ DiagnosticSpanMacroExpansion,
+};
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum FlycheckConfig {
+ CargoCommand {
+ command: String,
+ target_triple: Option<String>,
+ all_targets: bool,
+ no_default_features: bool,
+ all_features: bool,
+ features: Vec<String>,
+ extra_args: Vec<String>,
+ extra_env: FxHashMap<String, String>,
+ },
+ CustomCommand {
+ command: String,
+ args: Vec<String>,
+ extra_env: FxHashMap<String, String>,
+ },
+}
+
+impl fmt::Display for FlycheckConfig {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ FlycheckConfig::CargoCommand { command, .. } => write!(f, "cargo {}", command),
+ FlycheckConfig::CustomCommand { command, args, .. } => {
+ write!(f, "{} {}", command, args.join(" "))
+ }
+ }
+ }
+}
+
+/// Flycheck wraps the shared state and communication machinery used for
+/// running `cargo check` (or other compatible command) and providing
+/// diagnostics based on the output.
+/// The spawned thread is shut down when this struct is dropped.
+#[derive(Debug)]
+pub struct FlycheckHandle {
+ // XXX: drop order is significant
+ sender: Sender<Restart>,
+ _thread: jod_thread::JoinHandle,
+ id: usize,
+}
+
+impl FlycheckHandle {
+ pub fn spawn(
+ id: usize,
+ sender: Box<dyn Fn(Message) + Send>,
+ config: FlycheckConfig,
+ workspace_root: AbsPathBuf,
+ ) -> FlycheckHandle {
+ let actor = FlycheckActor::new(id, sender, config, workspace_root);
+ let (sender, receiver) = unbounded::<Restart>();
+ let thread = jod_thread::Builder::new()
+ .name("Flycheck".to_owned())
+ .spawn(move || actor.run(receiver))
+ .expect("failed to spawn thread");
+ FlycheckHandle { id, sender, _thread: thread }
+ }
+
+ /// Schedule a re-start of the cargo check worker.
+ pub fn restart(&self) {
+ self.sender.send(Restart::Yes).unwrap();
+ }
+
+ /// Stop this cargo check worker.
+ pub fn cancel(&self) {
+ self.sender.send(Restart::No).unwrap();
+ }
+
+ pub fn id(&self) -> usize {
+ self.id
+ }
+}
+
+pub enum Message {
+ /// Request adding a diagnostic with fixes included to a file
+ AddDiagnostic { id: usize, workspace_root: AbsPathBuf, diagnostic: Diagnostic },
+
+ /// Request check progress notification to client
+ Progress {
+ /// Flycheck instance ID
+ id: usize,
+ progress: Progress,
+ },
+}
+
+impl fmt::Debug for Message {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Message::AddDiagnostic { id, workspace_root, diagnostic } => f
+ .debug_struct("AddDiagnostic")
+ .field("id", id)
+ .field("workspace_root", workspace_root)
+ .field("diagnostic_code", &diagnostic.code.as_ref().map(|it| &it.code))
+ .finish(),
+ Message::Progress { id, progress } => {
+ f.debug_struct("Progress").field("id", id).field("progress", progress).finish()
+ }
+ }
+ }
+}
+
+#[derive(Debug)]
+pub enum Progress {
+ DidStart,
+ DidCheckCrate(String),
+ DidFinish(io::Result<()>),
+ DidCancel,
+ DidFailToRestart(String),
+}
+
+enum Restart {
+ Yes,
+ No,
+}
+
+struct FlycheckActor {
+ id: usize,
+ sender: Box<dyn Fn(Message) + Send>,
+ config: FlycheckConfig,
+ workspace_root: AbsPathBuf,
+ /// CargoHandle exists to wrap around the communication needed to be able to
+ /// run `cargo check` without blocking. Currently the Rust standard library
+ /// doesn't provide a way to read sub-process output without blocking, so we
+ /// have to wrap sub-processes output handling in a thread and pass messages
+ /// back over a channel.
+ cargo_handle: Option<CargoHandle>,
+}
+
+enum Event {
+ Restart(Restart),
+ CheckEvent(Option<CargoMessage>),
+}
+
+impl FlycheckActor {
+ fn new(
+ id: usize,
+ sender: Box<dyn Fn(Message) + Send>,
+ config: FlycheckConfig,
+ workspace_root: AbsPathBuf,
+ ) -> FlycheckActor {
+ tracing::info!(%id, ?workspace_root, "Spawning flycheck");
+ FlycheckActor { id, sender, config, workspace_root, cargo_handle: None }
+ }
+ fn progress(&self, progress: Progress) {
+ self.send(Message::Progress { id: self.id, progress });
+ }
+ fn next_event(&self, inbox: &Receiver<Restart>) -> Option<Event> {
+ let check_chan = self.cargo_handle.as_ref().map(|cargo| &cargo.receiver);
++ if let Ok(msg) = inbox.try_recv() {
++ // give restarts a preference so check outputs don't block a restart or stop
++ return Some(Event::Restart(msg));
++ }
+ select! {
+ recv(inbox) -> msg => msg.ok().map(Event::Restart),
+ recv(check_chan.unwrap_or(&never())) -> msg => Some(Event::CheckEvent(msg.ok())),
+ }
+ }
+ fn run(mut self, inbox: Receiver<Restart>) {
- while let Ok(_) = inbox.recv_timeout(Duration::from_millis(50)) {}
++ 'event: while let Some(event) = self.next_event(&inbox) {
+ match event {
+ Event::Restart(Restart::No) => {
+ self.cancel_check_process();
+ }
+ Event::Restart(Restart::Yes) => {
+ // Cancel the previously spawned process
+ self.cancel_check_process();
++ while let Ok(restart) = inbox.recv_timeout(Duration::from_millis(50)) {
++ // restart chained with a stop, so just cancel
++ if let Restart::No = restart {
++ continue 'event;
++ }
++ }
+
+ let command = self.check_command();
+ tracing::debug!(?command, "will restart flycheck");
+ match CargoHandle::spawn(command) {
+ Ok(cargo_handle) => {
+ tracing::debug!(
+ command = ?self.check_command(),
+ "did restart flycheck"
+ );
+ self.cargo_handle = Some(cargo_handle);
+ self.progress(Progress::DidStart);
+ }
+ Err(error) => {
+ self.progress(Progress::DidFailToRestart(format!(
+ "Failed to run the following command: {:?} error={}",
+ self.check_command(),
+ error
+ )));
+ }
+ }
+ }
+ Event::CheckEvent(None) => {
+ tracing::debug!(flycheck_id = self.id, "flycheck finished");
+
+ // Watcher finished
+ let cargo_handle = self.cargo_handle.take().unwrap();
+ let res = cargo_handle.join();
+ if res.is_err() {
+ tracing::error!(
+ "Flycheck failed to run the following command: {:?}",
+ self.check_command()
+ );
+ }
+ self.progress(Progress::DidFinish(res));
+ }
+ Event::CheckEvent(Some(message)) => match message {
+ CargoMessage::CompilerArtifact(msg) => {
+ self.progress(Progress::DidCheckCrate(msg.target.name));
+ }
+
+ CargoMessage::Diagnostic(msg) => {
+ self.send(Message::AddDiagnostic {
+ id: self.id,
+ workspace_root: self.workspace_root.clone(),
+ diagnostic: msg,
+ });
+ }
+ },
+ }
+ }
+ // If we rerun the thread, we need to discard the previous check results first
+ self.cancel_check_process();
+ }
+
+ fn cancel_check_process(&mut self) {
+ if let Some(cargo_handle) = self.cargo_handle.take() {
+ tracing::debug!(
+ command = ?self.check_command(),
+ "did cancel flycheck"
+ );
+ cargo_handle.cancel();
+ self.progress(Progress::DidCancel);
+ }
+ }
+
+ fn check_command(&self) -> Command {
+ let mut cmd = match &self.config {
+ FlycheckConfig::CargoCommand {
+ command,
+ target_triple,
+ no_default_features,
+ all_targets,
+ all_features,
+ extra_args,
+ features,
+ extra_env,
+ } => {
+ let mut cmd = Command::new(toolchain::cargo());
+ cmd.arg(command);
+ cmd.current_dir(&self.workspace_root);
+ cmd.args(&["--workspace", "--message-format=json", "--manifest-path"])
+ .arg(self.workspace_root.join("Cargo.toml").as_os_str());
+
+ if let Some(target) = target_triple {
+ cmd.args(&["--target", target.as_str()]);
+ }
+ if *all_targets {
+ cmd.arg("--all-targets");
+ }
+ if *all_features {
+ cmd.arg("--all-features");
+ } else {
+ if *no_default_features {
+ cmd.arg("--no-default-features");
+ }
+ if !features.is_empty() {
+ cmd.arg("--features");
+ cmd.arg(features.join(" "));
+ }
+ }
+ cmd.args(extra_args);
+ cmd.envs(extra_env);
+ cmd
+ }
+ FlycheckConfig::CustomCommand { command, args, extra_env } => {
+ let mut cmd = Command::new(command);
+ cmd.args(args);
+ cmd.envs(extra_env);
+ cmd
+ }
+ };
+ cmd.current_dir(&self.workspace_root);
+ cmd
+ }
+
+ fn send(&self, check_task: Message) {
+ (self.sender)(check_task);
+ }
+}
+
+/// A handle to a cargo process used for fly-checking.
+struct CargoHandle {
+ /// The handle to the actual cargo process. As we cannot cancel directly from with
+ /// a read syscall dropping and therefor terminating the process is our best option.
+ child: JodChild,
+ thread: jod_thread::JoinHandle<io::Result<(bool, String)>>,
+ receiver: Receiver<CargoMessage>,
+}
+
+impl CargoHandle {
+ fn spawn(mut command: Command) -> std::io::Result<CargoHandle> {
+ command.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::null());
+ let mut child = JodChild::spawn(command)?;
+
+ let stdout = child.stdout.take().unwrap();
+ let stderr = child.stderr.take().unwrap();
+
+ let (sender, receiver) = unbounded();
+ let actor = CargoActor::new(sender, stdout, stderr);
+ let thread = jod_thread::Builder::new()
+ .name("CargoHandle".to_owned())
+ .spawn(move || actor.run())
+ .expect("failed to spawn thread");
+ Ok(CargoHandle { child, thread, receiver })
+ }
+
+ fn cancel(mut self) {
+ let _ = self.child.kill();
+ let _ = self.child.wait();
+ }
+
+ fn join(mut self) -> io::Result<()> {
+ let _ = self.child.kill();
+ let exit_status = self.child.wait()?;
+ let (read_at_least_one_message, error) = self.thread.join()?;
+ if read_at_least_one_message || exit_status.success() {
+ Ok(())
+ } else {
+ Err(io::Error::new(io::ErrorKind::Other, format!(
+ "Cargo watcher failed, the command produced no valid metadata (exit code: {:?}):\n{}",
+ exit_status, error
+ )))
+ }
+ }
+}
+
+struct CargoActor {
+ sender: Sender<CargoMessage>,
+ stdout: ChildStdout,
+ stderr: ChildStderr,
+}
+
+impl CargoActor {
+ fn new(sender: Sender<CargoMessage>, stdout: ChildStdout, stderr: ChildStderr) -> CargoActor {
+ CargoActor { sender, stdout, stderr }
+ }
+
+ fn run(self) -> io::Result<(bool, String)> {
+ // We manually read a line at a time, instead of using serde's
+ // stream deserializers, because the deserializer cannot recover
+ // from an error, resulting in it getting stuck, because we try to
+ // be resilient against failures.
+ //
+ // Because cargo only outputs one JSON object per line, we can
+ // simply skip a line if it doesn't parse, which just ignores any
+ // erroneous output.
+
+ let mut error = String::new();
+ let mut read_at_least_one_message = false;
+ let output = streaming_output(
+ self.stdout,
+ self.stderr,
+ &mut |line| {
+ read_at_least_one_message = true;
+
+ // Try to deserialize a message from Cargo or Rustc.
+ let mut deserializer = serde_json::Deserializer::from_str(line);
+ deserializer.disable_recursion_limit();
+ if let Ok(message) = JsonMessage::deserialize(&mut deserializer) {
+ match message {
+ // Skip certain kinds of messages to only spend time on what's useful
+ JsonMessage::Cargo(message) => match message {
+ cargo_metadata::Message::CompilerArtifact(artifact)
+ if !artifact.fresh =>
+ {
+ self.sender.send(CargoMessage::CompilerArtifact(artifact)).unwrap();
+ }
+ cargo_metadata::Message::CompilerMessage(msg) => {
+ self.sender.send(CargoMessage::Diagnostic(msg.message)).unwrap();
+ }
+ _ => (),
+ },
+ JsonMessage::Rustc(message) => {
+ self.sender.send(CargoMessage::Diagnostic(message)).unwrap();
+ }
+ }
+ }
+ },
+ &mut |line| {
+ error.push_str(line);
+ error.push('\n');
+ },
+ );
+ match output {
+ Ok(_) => Ok((read_at_least_one_message, error)),
+ Err(e) => Err(io::Error::new(e.kind(), format!("{:?}: {}", e, error))),
+ }
+ }
+}
+
+enum CargoMessage {
+ CompilerArtifact(cargo_metadata::Artifact),
+ Diagnostic(Diagnostic),
+}
+
+#[derive(Deserialize)]
+#[serde(untagged)]
+enum JsonMessage {
+ Cargo(cargo_metadata::Message),
+ Rustc(Diagnostic),
+}
--- /dev/null
- use std::sync::Arc;
+//! Defines hir-level representation of structs, enums and unions
+
- InFile,
++use std::{num::NonZeroU32, sync::Arc};
+
+use base_db::CrateId;
+use either::Either;
+use hir_expand::{
+ name::{AsName, Name},
- item_tree::{AttrOwner, Field, Fields, ItemTree, ModItem, RawVisibilityId},
++ HirFileId, InFile,
+};
+use la_arena::{Arena, ArenaMap};
+use syntax::ast::{self, HasName, HasVisibility};
+use tt::{Delimiter, DelimiterKind, Leaf, Subtree, TokenTree};
+
+use crate::{
+ body::{CfgExpander, LowerCtx},
++ builtin_type::{BuiltinInt, BuiltinUint},
+ db::DefDatabase,
+ intern::Interned,
- EnumId, LocalEnumVariantId, LocalFieldId, Lookup, ModuleId, StructId, UnionId, VariantId,
++ item_tree::{AttrOwner, Field, FieldAstId, Fields, ItemTree, ModItem, RawVisibilityId},
++ nameres::diagnostics::DefDiagnostic,
+ src::HasChildSource,
+ src::HasSource,
+ trace::Trace,
+ type_ref::TypeRef,
+ visibility::RawVisibility,
- pub repr: Option<ReprKind>,
++ EnumId, LocalEnumVariantId, LocalFieldId, LocalModuleId, Lookup, ModuleId, StructId, UnionId,
++ VariantId,
+};
+use cfg::CfgOptions;
+
+/// Note that we use `StructData` for unions as well!
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct StructData {
+ pub name: Name,
+ pub variant_data: Arc<VariantData>,
- #[derive(Debug, Clone, PartialEq, Eq)]
++ pub repr: Option<ReprData>,
+ pub visibility: RawVisibility,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct EnumData {
+ pub name: Name,
+ pub variants: Arena<EnumVariantData>,
++ pub repr: Option<ReprData>,
+ pub visibility: RawVisibility,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct EnumVariantData {
+ pub name: Name,
+ pub variant_data: Arc<VariantData>,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum VariantData {
+ Record(Arena<FieldData>),
+ Tuple(Arena<FieldData>),
+ Unit,
+}
+
+/// A single field of an enum variant or struct
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct FieldData {
+ pub name: Name,
+ pub type_ref: Interned<TypeRef>,
+ pub visibility: RawVisibility,
+}
+
- Packed,
- Other,
++#[derive(Copy, Debug, Clone, PartialEq, Eq)]
+pub enum ReprKind {
- ) -> Option<ReprKind> {
++ C,
++ BuiltinInt { builtin: Either<BuiltinInt, BuiltinUint>, is_c: bool },
++ Transparent,
++ Default,
++}
++
++#[derive(Copy, Debug, Clone, PartialEq, Eq)]
++pub struct ReprData {
++ pub kind: ReprKind,
++ pub packed: bool,
++ pub align: Option<NonZeroU32>,
+}
+
+fn repr_from_value(
+ db: &dyn DefDatabase,
+ krate: CrateId,
+ item_tree: &ItemTree,
+ of: AttrOwner,
- fn parse_repr_tt(tt: &Subtree) -> Option<ReprKind> {
++) -> Option<ReprData> {
+ item_tree.attrs(db, krate, of).by_key("repr").tt_values().find_map(parse_repr_tt)
+}
+
- let mut it = tt.token_trees.iter();
- match it.next()? {
- TokenTree::Leaf(Leaf::Ident(ident)) if ident.text == "packed" => Some(ReprKind::Packed),
- _ => Some(ReprKind::Other),
++fn parse_repr_tt(tt: &Subtree) -> Option<ReprData> {
+ match tt.delimiter {
+ Some(Delimiter { kind: DelimiterKind::Parenthesis, .. }) => {}
+ _ => return None,
+ }
+
- let variant_data = lower_fields(db, krate, &item_tree, &cfg_options, &strukt.fields, None);
- Arc::new(StructData {
- name: strukt.name.clone(),
- variant_data: Arc::new(variant_data),
- repr,
- visibility: item_tree[strukt.visibility].clone(),
- })
++ let mut data = ReprData { kind: ReprKind::Default, packed: false, align: None };
++
++ let mut tts = tt.token_trees.iter().peekable();
++ while let Some(tt) = tts.next() {
++ if let TokenTree::Leaf(Leaf::Ident(ident)) = tt {
++ match &*ident.text {
++ "packed" => {
++ data.packed = true;
++ if let Some(TokenTree::Subtree(_)) = tts.peek() {
++ tts.next();
++ }
++ }
++ "align" => {
++ if let Some(TokenTree::Subtree(tt)) = tts.peek() {
++ tts.next();
++ if let Some(TokenTree::Leaf(Leaf::Literal(lit))) = tt.token_trees.first() {
++ if let Ok(align) = lit.text.parse() {
++ data.align = Some(align);
++ }
++ }
++ }
++ }
++ "C" => {
++ if let ReprKind::BuiltinInt { is_c, .. } = &mut data.kind {
++ *is_c = true;
++ } else {
++ data.kind = ReprKind::C;
++ }
++ }
++ "transparent" => data.kind = ReprKind::Transparent,
++ repr => {
++ let is_c = matches!(data.kind, ReprKind::C);
++ if let Some(builtin) = BuiltinInt::from_suffix(repr)
++ .map(Either::Left)
++ .or_else(|| BuiltinUint::from_suffix(repr).map(Either::Right))
++ {
++ data.kind = ReprKind::BuiltinInt { builtin, is_c };
++ }
++ }
++ }
++ }
+ }
++
++ Some(data)
+}
+
+impl StructData {
+ pub(crate) fn struct_data_query(db: &dyn DefDatabase, id: StructId) -> Arc<StructData> {
++ db.struct_data_with_diagnostics(id).0
++ }
++
++ pub(crate) fn struct_data_with_diagnostics_query(
++ db: &dyn DefDatabase,
++ id: StructId,
++ ) -> (Arc<StructData>, Arc<[DefDiagnostic]>) {
+ let loc = id.lookup(db);
+ let krate = loc.container.krate;
+ let item_tree = loc.id.item_tree(db);
+ let repr = repr_from_value(db, krate, &item_tree, ModItem::from(loc.id.value).into());
+ let cfg_options = db.crate_graph()[loc.container.krate].cfg_options.clone();
+
+ let strukt = &item_tree[loc.id.value];
- let variant_data = lower_fields(db, krate, &item_tree, &cfg_options, &union.fields, None);
-
- Arc::new(StructData {
- name: union.name.clone(),
- variant_data: Arc::new(variant_data),
- repr,
- visibility: item_tree[union.visibility].clone(),
- })
++ let (variant_data, diagnostics) = lower_fields(
++ db,
++ krate,
++ loc.id.file_id(),
++ loc.container.local_id,
++ &item_tree,
++ &cfg_options,
++ &strukt.fields,
++ None,
++ );
++ (
++ Arc::new(StructData {
++ name: strukt.name.clone(),
++ variant_data: Arc::new(variant_data),
++ repr,
++ visibility: item_tree[strukt.visibility].clone(),
++ }),
++ diagnostics.into(),
++ )
+ }
++
+ pub(crate) fn union_data_query(db: &dyn DefDatabase, id: UnionId) -> Arc<StructData> {
++ db.union_data_with_diagnostics(id).0
++ }
++
++ pub(crate) fn union_data_with_diagnostics_query(
++ db: &dyn DefDatabase,
++ id: UnionId,
++ ) -> (Arc<StructData>, Arc<[DefDiagnostic]>) {
+ let loc = id.lookup(db);
+ let krate = loc.container.krate;
+ let item_tree = loc.id.item_tree(db);
+ let repr = repr_from_value(db, krate, &item_tree, ModItem::from(loc.id.value).into());
+ let cfg_options = db.crate_graph()[loc.container.krate].cfg_options.clone();
+
+ let union = &item_tree[loc.id.value];
- if item_tree.attrs(db, krate, tree_id.into()).is_cfg_enabled(&cfg_options) {
- let var = &item_tree[tree_id];
- let var_data = lower_fields(
++ let (variant_data, diagnostics) = lower_fields(
++ db,
++ krate,
++ loc.id.file_id(),
++ loc.container.local_id,
++ &item_tree,
++ &cfg_options,
++ &union.fields,
++ None,
++ );
++ (
++ Arc::new(StructData {
++ name: union.name.clone(),
++ variant_data: Arc::new(variant_data),
++ repr,
++ visibility: item_tree[union.visibility].clone(),
++ }),
++ diagnostics.into(),
++ )
+ }
+}
+
+impl EnumData {
+ pub(crate) fn enum_data_query(db: &dyn DefDatabase, e: EnumId) -> Arc<EnumData> {
++ db.enum_data_with_diagnostics(e).0
++ }
++
++ pub(crate) fn enum_data_with_diagnostics_query(
++ db: &dyn DefDatabase,
++ e: EnumId,
++ ) -> (Arc<EnumData>, Arc<[DefDiagnostic]>) {
+ let loc = e.lookup(db);
+ let krate = loc.container.krate;
+ let item_tree = loc.id.item_tree(db);
+ let cfg_options = db.crate_graph()[krate].cfg_options.clone();
++ let repr = repr_from_value(db, krate, &item_tree, ModItem::from(loc.id.value).into());
+
+ let enum_ = &item_tree[loc.id.value];
+ let mut variants = Arena::new();
++ let mut diagnostics = Vec::new();
+ for tree_id in enum_.variants.clone() {
- Arc::new(EnumData {
- name: enum_.name.clone(),
- variants,
- visibility: item_tree[enum_.visibility].clone(),
- })
++ let attrs = item_tree.attrs(db, krate, tree_id.into());
++ let var = &item_tree[tree_id];
++ if attrs.is_cfg_enabled(&cfg_options) {
++ let (var_data, field_diagnostics) = lower_fields(
+ db,
+ krate,
++ loc.id.file_id(),
++ loc.container.local_id,
+ &item_tree,
+ &cfg_options,
+ &var.fields,
+ Some(enum_.visibility),
+ );
++ diagnostics.extend(field_diagnostics);
+
+ variants.alloc(EnumVariantData {
+ name: var.name.clone(),
+ variant_data: Arc::new(var_data),
+ });
++ } else {
++ diagnostics.push(DefDiagnostic::unconfigured_code(
++ loc.container.local_id,
++ InFile::new(loc.id.file_id(), var.ast_id.upcast()),
++ attrs.cfg().unwrap(),
++ cfg_options.clone(),
++ ))
+ }
+ }
+
- ) -> VariantData {
++ (
++ Arc::new(EnumData {
++ name: enum_.name.clone(),
++ variants,
++ repr,
++ visibility: item_tree[enum_.visibility].clone(),
++ }),
++ diagnostics.into(),
++ )
+ }
+
+ pub fn variant(&self, name: &Name) -> Option<LocalEnumVariantId> {
+ let (id, _) = self.variants.iter().find(|(_id, data)| &data.name == name)?;
+ Some(id)
+ }
++
++ pub fn variant_body_type(&self) -> Either<BuiltinInt, BuiltinUint> {
++ match self.repr {
++ Some(ReprData { kind: ReprKind::BuiltinInt { builtin, .. }, .. }) => builtin,
++ _ => Either::Left(BuiltinInt::Isize),
++ }
++ }
+}
+
+impl HasChildSource<LocalEnumVariantId> for EnumId {
+ type Value = ast::Variant;
+ fn child_source(
+ &self,
+ db: &dyn DefDatabase,
+ ) -> InFile<ArenaMap<LocalEnumVariantId, Self::Value>> {
+ let src = self.lookup(db).source(db);
+ let mut trace = Trace::new_for_map();
+ lower_enum(db, &mut trace, &src, self.lookup(db).container);
+ src.with_value(trace.into_map())
+ }
+}
+
+fn lower_enum(
+ db: &dyn DefDatabase,
+ trace: &mut Trace<EnumVariantData, ast::Variant>,
+ ast: &InFile<ast::Enum>,
+ module_id: ModuleId,
+) {
+ let expander = CfgExpander::new(db, ast.file_id, module_id.krate);
+ let variants = ast
+ .value
+ .variant_list()
+ .into_iter()
+ .flat_map(|it| it.variants())
+ .filter(|var| expander.is_cfg_enabled(db, var));
+ for var in variants {
+ trace.alloc(
+ || var.clone(),
+ || EnumVariantData {
+ name: var.name().map_or_else(Name::missing, |it| it.as_name()),
+ variant_data: Arc::new(VariantData::new(db, ast.with_value(var.kind()), module_id)),
+ },
+ );
+ }
+}
+
+impl VariantData {
+ fn new(db: &dyn DefDatabase, flavor: InFile<ast::StructKind>, module_id: ModuleId) -> Self {
+ let mut expander = CfgExpander::new(db, flavor.file_id, module_id.krate);
+ let mut trace = Trace::new_for_arena();
+ match lower_struct(db, &mut expander, &mut trace, &flavor) {
+ StructKind::Tuple => VariantData::Tuple(trace.into_arena()),
+ StructKind::Record => VariantData::Record(trace.into_arena()),
+ StructKind::Unit => VariantData::Unit,
+ }
+ }
+
+ pub fn fields(&self) -> &Arena<FieldData> {
+ const EMPTY: &Arena<FieldData> = &Arena::new();
+ match &self {
+ VariantData::Record(fields) | VariantData::Tuple(fields) => fields,
+ _ => EMPTY,
+ }
+ }
+
+ pub fn field(&self, name: &Name) -> Option<LocalFieldId> {
+ self.fields().iter().find_map(|(id, data)| if &data.name == name { Some(id) } else { None })
+ }
+
+ pub fn kind(&self) -> StructKind {
+ match self {
+ VariantData::Record(_) => StructKind::Record,
+ VariantData::Tuple(_) => StructKind::Tuple,
+ VariantData::Unit => StructKind::Unit,
+ }
+ }
+}
+
+impl HasChildSource<LocalFieldId> for VariantId {
+ type Value = Either<ast::TupleField, ast::RecordField>;
+
+ fn child_source(&self, db: &dyn DefDatabase) -> InFile<ArenaMap<LocalFieldId, Self::Value>> {
+ let (src, module_id) = match self {
+ VariantId::EnumVariantId(it) => {
+ // I don't really like the fact that we call into parent source
+ // here, this might add to more queries then necessary.
+ let src = it.parent.child_source(db);
+ (src.map(|map| map[it.local_id].kind()), it.parent.lookup(db).container)
+ }
+ VariantId::StructId(it) => {
+ (it.lookup(db).source(db).map(|it| it.kind()), it.lookup(db).container)
+ }
+ VariantId::UnionId(it) => (
+ it.lookup(db).source(db).map(|it| {
+ it.record_field_list()
+ .map(ast::StructKind::Record)
+ .unwrap_or(ast::StructKind::Unit)
+ }),
+ it.lookup(db).container,
+ ),
+ };
+ let mut expander = CfgExpander::new(db, src.file_id, module_id.krate);
+ let mut trace = Trace::new_for_map();
+ lower_struct(db, &mut expander, &mut trace, &src);
+ src.with_value(trace.into_map())
+ }
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum StructKind {
+ Tuple,
+ Record,
+ Unit,
+}
+
+fn lower_struct(
+ db: &dyn DefDatabase,
+ expander: &mut CfgExpander,
+ trace: &mut Trace<FieldData, Either<ast::TupleField, ast::RecordField>>,
+ ast: &InFile<ast::StructKind>,
+) -> StructKind {
+ let ctx = LowerCtx::new(db, ast.file_id);
+
+ match &ast.value {
+ ast::StructKind::Tuple(fl) => {
+ for (i, fd) in fl.fields().enumerate() {
+ if !expander.is_cfg_enabled(db, &fd) {
+ continue;
+ }
+
+ trace.alloc(
+ || Either::Left(fd.clone()),
+ || FieldData {
+ name: Name::new_tuple_field(i),
+ type_ref: Interned::new(TypeRef::from_ast_opt(&ctx, fd.ty())),
+ visibility: RawVisibility::from_ast(db, ast.with_value(fd.visibility())),
+ },
+ );
+ }
+ StructKind::Tuple
+ }
+ ast::StructKind::Record(fl) => {
+ for fd in fl.fields() {
+ if !expander.is_cfg_enabled(db, &fd) {
+ continue;
+ }
+
+ trace.alloc(
+ || Either::Right(fd.clone()),
+ || FieldData {
+ name: fd.name().map(|n| n.as_name()).unwrap_or_else(Name::missing),
+ type_ref: Interned::new(TypeRef::from_ast_opt(&ctx, fd.ty())),
+ visibility: RawVisibility::from_ast(db, ast.with_value(fd.visibility())),
+ },
+ );
+ }
+ StructKind::Record
+ }
+ ast::StructKind::Unit => StructKind::Unit,
+ }
+}
+
+fn lower_fields(
+ db: &dyn DefDatabase,
+ krate: CrateId,
++ current_file_id: HirFileId,
++ container: LocalModuleId,
+ item_tree: &ItemTree,
+ cfg_options: &CfgOptions,
+ fields: &Fields,
+ override_visibility: Option<RawVisibilityId>,
- if item_tree.attrs(db, krate, field_id.into()).is_cfg_enabled(cfg_options) {
- arena.alloc(lower_field(item_tree, &item_tree[field_id], override_visibility));
++) -> (VariantData, Vec<DefDiagnostic>) {
++ let mut diagnostics = Vec::new();
+ match fields {
+ Fields::Record(flds) => {
+ let mut arena = Arena::new();
+ for field_id in flds.clone() {
- VariantData::Record(arena)
++ let attrs = item_tree.attrs(db, krate, field_id.into());
++ let field = &item_tree[field_id];
++ if attrs.is_cfg_enabled(cfg_options) {
++ arena.alloc(lower_field(item_tree, field, override_visibility));
++ } else {
++ diagnostics.push(DefDiagnostic::unconfigured_code(
++ container,
++ InFile::new(
++ current_file_id,
++ match field.ast_id {
++ FieldAstId::Record(it) => it.upcast(),
++ FieldAstId::Tuple(it) => it.upcast(),
++ },
++ ),
++ attrs.cfg().unwrap(),
++ cfg_options.clone(),
++ ))
+ }
+ }
- if item_tree.attrs(db, krate, field_id.into()).is_cfg_enabled(cfg_options) {
- arena.alloc(lower_field(item_tree, &item_tree[field_id], override_visibility));
++ (VariantData::Record(arena), diagnostics)
+ }
+ Fields::Tuple(flds) => {
+ let mut arena = Arena::new();
+ for field_id in flds.clone() {
- VariantData::Tuple(arena)
++ let attrs = item_tree.attrs(db, krate, field_id.into());
++ let field = &item_tree[field_id];
++ if attrs.is_cfg_enabled(cfg_options) {
++ arena.alloc(lower_field(item_tree, field, override_visibility));
++ } else {
++ diagnostics.push(DefDiagnostic::unconfigured_code(
++ container,
++ InFile::new(
++ current_file_id,
++ match field.ast_id {
++ FieldAstId::Record(it) => it.upcast(),
++ FieldAstId::Tuple(it) => it.upcast(),
++ },
++ ),
++ attrs.cfg().unwrap(),
++ cfg_options.clone(),
++ ))
+ }
+ }
- Fields::Unit => VariantData::Unit,
++ (VariantData::Tuple(arena), diagnostics)
+ }
++ Fields::Unit => (VariantData::Unit, diagnostics),
+ }
+}
+
+fn lower_field(
+ item_tree: &ItemTree,
+ field: &Field,
+ override_visibility: Option<RawVisibilityId>,
+) -> FieldData {
+ FieldData {
+ name: field.name.clone(),
+ type_ref: field.type_ref.clone(),
+ visibility: item_tree[override_visibility.unwrap_or(field.visibility)].clone(),
+ }
+}
--- /dev/null
- src::HasSource,
+//! Defines `Body`: a lowered representation of bodies of functions, statics and
+//! consts.
+mod lower;
+#[cfg(test)]
+mod tests;
+pub mod scope;
+mod pretty;
+
+use std::{ops::Index, sync::Arc};
+
+use base_db::CrateId;
+use cfg::{CfgExpr, CfgOptions};
+use drop_bomb::DropBomb;
+use either::Either;
+use hir_expand::{hygiene::Hygiene, ExpandError, ExpandResult, HirFileId, InFile, MacroCallId};
+use la_arena::{Arena, ArenaMap};
+use limit::Limit;
+use profile::Count;
+use rustc_hash::FxHashMap;
+use syntax::{ast, AstPtr, SyntaxNodePtr};
+
+use crate::{
+ attr::{Attrs, RawAttrs},
+ db::DefDatabase,
+ expr::{dummy_expr_id, Expr, ExprId, Label, LabelId, Pat, PatId},
+ item_scope::BuiltinShadowMode,
+ macro_id_to_def_id,
+ nameres::DefMap,
+ path::{ModPath, Path},
++ src::{HasChildSource, HasSource},
+ AsMacroCall, BlockId, DefWithBodyId, HasModule, LocalModuleId, Lookup, MacroId, ModuleId,
+ UnresolvedMacro,
+};
+
+pub use lower::LowerCtx;
+
+/// A subset of Expander that only deals with cfg attributes. We only need it to
+/// avoid cyclic queries in crate def map during enum processing.
+#[derive(Debug)]
+pub(crate) struct CfgExpander {
+ cfg_options: CfgOptions,
+ hygiene: Hygiene,
+ krate: CrateId,
+}
+
+#[derive(Debug)]
+pub struct Expander {
+ cfg_expander: CfgExpander,
+ def_map: Arc<DefMap>,
+ current_file_id: HirFileId,
+ module: LocalModuleId,
+ recursion_limit: usize,
+}
+
+impl CfgExpander {
+ pub(crate) fn new(
+ db: &dyn DefDatabase,
+ current_file_id: HirFileId,
+ krate: CrateId,
+ ) -> CfgExpander {
+ let hygiene = Hygiene::new(db.upcast(), current_file_id);
+ let cfg_options = db.crate_graph()[krate].cfg_options.clone();
+ CfgExpander { cfg_options, hygiene, krate }
+ }
+
+ pub(crate) fn parse_attrs(&self, db: &dyn DefDatabase, owner: &dyn ast::HasAttrs) -> Attrs {
+ RawAttrs::new(db, owner, &self.hygiene).filter(db, self.krate)
+ }
+
+ pub(crate) fn is_cfg_enabled(&self, db: &dyn DefDatabase, owner: &dyn ast::HasAttrs) -> bool {
+ let attrs = self.parse_attrs(db, owner);
+ attrs.is_cfg_enabled(&self.cfg_options)
+ }
+}
+
+impl Expander {
+ pub fn new(db: &dyn DefDatabase, current_file_id: HirFileId, module: ModuleId) -> Expander {
+ let cfg_expander = CfgExpander::new(db, current_file_id, module.krate);
+ let def_map = module.def_map(db);
+ Expander {
+ cfg_expander,
+ def_map,
+ current_file_id,
+ module: module.local_id,
+ recursion_limit: 0,
+ }
+ }
+
+ pub fn enter_expand<T: ast::AstNode>(
+ &mut self,
+ db: &dyn DefDatabase,
+ macro_call: ast::MacroCall,
+ ) -> Result<ExpandResult<Option<(Mark, T)>>, UnresolvedMacro> {
+ if self.recursion_limit(db).check(self.recursion_limit + 1).is_err() {
+ cov_mark::hit!(your_stack_belongs_to_me);
+ return Ok(ExpandResult::only_err(ExpandError::Other(
+ "reached recursion limit during macro expansion".into(),
+ )));
+ }
+
+ let macro_call = InFile::new(self.current_file_id, ¯o_call);
+
+ let resolver =
+ |path| self.resolve_path_as_macro(db, &path).map(|it| macro_id_to_def_id(db, it));
+
+ let mut err = None;
+ let call_id =
+ macro_call.as_call_id_with_errors(db, self.def_map.krate(), resolver, &mut |e| {
+ err.get_or_insert(e);
+ })?;
+ let call_id = match call_id {
+ Ok(it) => it,
+ Err(_) => {
+ return Ok(ExpandResult { value: None, err });
+ }
+ };
+
+ Ok(self.enter_expand_inner(db, call_id, err))
+ }
+
+ pub fn enter_expand_id<T: ast::AstNode>(
+ &mut self,
+ db: &dyn DefDatabase,
+ call_id: MacroCallId,
+ ) -> ExpandResult<Option<(Mark, T)>> {
+ self.enter_expand_inner(db, call_id, None)
+ }
+
+ fn enter_expand_inner<T: ast::AstNode>(
+ &mut self,
+ db: &dyn DefDatabase,
+ call_id: MacroCallId,
+ mut err: Option<ExpandError>,
+ ) -> ExpandResult<Option<(Mark, T)>> {
+ if err.is_none() {
+ err = db.macro_expand_error(call_id);
+ }
+
+ let file_id = call_id.as_file();
+
+ let raw_node = match db.parse_or_expand(file_id) {
+ Some(it) => it,
+ None => {
+ // Only `None` if the macro expansion produced no usable AST.
+ if err.is_none() {
+ tracing::warn!("no error despite `parse_or_expand` failing");
+ }
+
+ return ExpandResult::only_err(err.unwrap_or_else(|| {
+ ExpandError::Other("failed to parse macro invocation".into())
+ }));
+ }
+ };
+
+ let node = match T::cast(raw_node) {
+ Some(it) => it,
+ None => {
+ // This can happen without being an error, so only forward previous errors.
+ return ExpandResult { value: None, err };
+ }
+ };
+
+ tracing::debug!("macro expansion {:#?}", node.syntax());
+
+ self.recursion_limit += 1;
+ let mark =
+ Mark { file_id: self.current_file_id, bomb: DropBomb::new("expansion mark dropped") };
+ self.cfg_expander.hygiene = Hygiene::new(db.upcast(), file_id);
+ self.current_file_id = file_id;
+
+ ExpandResult { value: Some((mark, node)), err }
+ }
+
+ pub fn exit(&mut self, db: &dyn DefDatabase, mut mark: Mark) {
+ self.cfg_expander.hygiene = Hygiene::new(db.upcast(), mark.file_id);
+ self.current_file_id = mark.file_id;
+ self.recursion_limit -= 1;
+ mark.bomb.defuse();
+ }
+
+ pub(crate) fn to_source<T>(&self, value: T) -> InFile<T> {
+ InFile { file_id: self.current_file_id, value }
+ }
+
+ pub(crate) fn parse_attrs(&self, db: &dyn DefDatabase, owner: &dyn ast::HasAttrs) -> Attrs {
+ self.cfg_expander.parse_attrs(db, owner)
+ }
+
+ pub(crate) fn cfg_options(&self) -> &CfgOptions {
+ &self.cfg_expander.cfg_options
+ }
+
+ pub fn current_file_id(&self) -> HirFileId {
+ self.current_file_id
+ }
+
+ fn parse_path(&mut self, db: &dyn DefDatabase, path: ast::Path) -> Option<Path> {
+ let ctx = LowerCtx::with_hygiene(db, &self.cfg_expander.hygiene);
+ Path::from_src(path, &ctx)
+ }
+
+ fn resolve_path_as_macro(&self, db: &dyn DefDatabase, path: &ModPath) -> Option<MacroId> {
+ self.def_map.resolve_path(db, self.module, path, BuiltinShadowMode::Other).0.take_macros()
+ }
+
+ fn recursion_limit(&self, db: &dyn DefDatabase) -> Limit {
+ let limit = db.crate_limits(self.cfg_expander.krate).recursion_limit as _;
+
+ #[cfg(not(test))]
+ return Limit::new(limit);
+
+ // Without this, `body::tests::your_stack_belongs_to_me` stack-overflows in debug
+ #[cfg(test)]
+ return Limit::new(std::cmp::min(32, limit));
+ }
+}
+
+#[derive(Debug)]
+pub struct Mark {
+ file_id: HirFileId,
+ bomb: DropBomb,
+}
+
+/// The body of an item (function, const etc.).
+#[derive(Debug, Eq, PartialEq)]
+pub struct Body {
+ pub exprs: Arena<Expr>,
+ pub pats: Arena<Pat>,
+ pub or_pats: FxHashMap<PatId, Arc<[PatId]>>,
+ pub labels: Arena<Label>,
+ /// The patterns for the function's parameters. While the parameter types are
+ /// part of the function signature, the patterns are not (they don't change
+ /// the external type of the function).
+ ///
+ /// If this `Body` is for the body of a constant, this will just be
+ /// empty.
+ pub params: Vec<PatId>,
+ /// The `ExprId` of the actual body expression.
+ pub body_expr: ExprId,
+ /// Block expressions in this body that may contain inner items.
+ block_scopes: Vec<BlockId>,
+ _c: Count<Self>,
+}
+
+pub type ExprPtr = AstPtr<ast::Expr>;
+pub type ExprSource = InFile<ExprPtr>;
+
+pub type PatPtr = Either<AstPtr<ast::Pat>, AstPtr<ast::SelfParam>>;
+pub type PatSource = InFile<PatPtr>;
+
+pub type LabelPtr = AstPtr<ast::Label>;
+pub type LabelSource = InFile<LabelPtr>;
+
+pub type FieldPtr = AstPtr<ast::RecordExprField>;
+pub type FieldSource = InFile<FieldPtr>;
+
+/// An item body together with the mapping from syntax nodes to HIR expression
+/// IDs. This is needed to go from e.g. a position in a file to the HIR
+/// expression containing it; but for type inference etc., we want to operate on
+/// a structure that is agnostic to the actual positions of expressions in the
+/// file, so that we don't recompute types whenever some whitespace is typed.
+///
+/// One complication here is that, due to macro expansion, a single `Body` might
+/// be spread across several files. So, for each ExprId and PatId, we record
+/// both the HirFileId and the position inside the file. However, we only store
+/// AST -> ExprId mapping for non-macro files, as it is not clear how to handle
+/// this properly for macros.
+#[derive(Default, Debug, Eq, PartialEq)]
+pub struct BodySourceMap {
+ expr_map: FxHashMap<ExprSource, ExprId>,
+ expr_map_back: ArenaMap<ExprId, ExprSource>,
+
+ pat_map: FxHashMap<PatSource, PatId>,
+ pat_map_back: ArenaMap<PatId, PatSource>,
+
+ label_map: FxHashMap<LabelSource, LabelId>,
+ label_map_back: ArenaMap<LabelId, LabelSource>,
+
+ /// We don't create explicit nodes for record fields (`S { record_field: 92 }`).
+ /// Instead, we use id of expression (`92`) to identify the field.
+ field_map: FxHashMap<FieldSource, ExprId>,
+ field_map_back: FxHashMap<ExprId, FieldSource>,
+
+ expansions: FxHashMap<InFile<AstPtr<ast::MacroCall>>, HirFileId>,
+
+ /// Diagnostics accumulated during body lowering. These contain `AstPtr`s and so are stored in
+ /// the source map (since they're just as volatile).
+ diagnostics: Vec<BodyDiagnostic>,
+}
+
+#[derive(Default, Debug, Eq, PartialEq, Clone, Copy)]
+pub struct SyntheticSyntax;
+
+#[derive(Debug, Eq, PartialEq)]
+pub enum BodyDiagnostic {
+ InactiveCode { node: InFile<SyntaxNodePtr>, cfg: CfgExpr, opts: CfgOptions },
+ MacroError { node: InFile<AstPtr<ast::MacroCall>>, message: String },
+ UnresolvedProcMacro { node: InFile<AstPtr<ast::MacroCall>>, krate: CrateId },
+ UnresolvedMacroCall { node: InFile<AstPtr<ast::MacroCall>>, path: ModPath },
+}
+
+impl Body {
+ pub(crate) fn body_with_source_map_query(
+ db: &dyn DefDatabase,
+ def: DefWithBodyId,
+ ) -> (Arc<Body>, Arc<BodySourceMap>) {
+ let _p = profile::span("body_with_source_map_query");
+ let mut params = None;
+
+ let (file_id, module, body) = match def {
+ DefWithBodyId::FunctionId(f) => {
+ let f = f.lookup(db);
+ let src = f.source(db);
+ params = src.value.param_list();
+ (src.file_id, f.module(db), src.value.body().map(ast::Expr::from))
+ }
+ DefWithBodyId::ConstId(c) => {
+ let c = c.lookup(db);
+ let src = c.source(db);
+ (src.file_id, c.module(db), src.value.body())
+ }
+ DefWithBodyId::StaticId(s) => {
+ let s = s.lookup(db);
+ let src = s.source(db);
+ (src.file_id, s.module(db), src.value.body())
+ }
++ DefWithBodyId::VariantId(v) => {
++ let e = v.parent.lookup(db);
++ let src = v.parent.child_source(db);
++ let variant = &src.value[v.local_id];
++ (src.file_id, e.container, variant.expr())
++ }
+ };
+ let expander = Expander::new(db, file_id, module);
+ let (mut body, source_map) = Body::new(db, expander, params, body);
+ body.shrink_to_fit();
+ (Arc::new(body), Arc::new(source_map))
+ }
+
+ pub(crate) fn body_query(db: &dyn DefDatabase, def: DefWithBodyId) -> Arc<Body> {
+ db.body_with_source_map(def).0
+ }
+
+ /// Returns an iterator over all block expressions in this body that define inner items.
+ pub fn blocks<'a>(
+ &'a self,
+ db: &'a dyn DefDatabase,
+ ) -> impl Iterator<Item = (BlockId, Arc<DefMap>)> + '_ {
+ self.block_scopes
+ .iter()
+ .map(move |&block| (block, db.block_def_map(block).expect("block ID without DefMap")))
+ }
+
+ pub fn pattern_representative(&self, pat: PatId) -> PatId {
+ self.or_pats.get(&pat).and_then(|pats| pats.first().copied()).unwrap_or(pat)
+ }
+
+ /// Retrieves all ident patterns this pattern shares the ident with.
+ pub fn ident_patterns_for<'slf>(&'slf self, pat: &'slf PatId) -> &'slf [PatId] {
+ match self.or_pats.get(pat) {
+ Some(pats) => &**pats,
+ None => std::slice::from_ref(pat),
+ }
+ }
+
+ pub fn pretty_print(&self, db: &dyn DefDatabase, owner: DefWithBodyId) -> String {
+ pretty::print_body_hir(db, self, owner)
+ }
+
+ fn new(
+ db: &dyn DefDatabase,
+ expander: Expander,
+ params: Option<ast::ParamList>,
+ body: Option<ast::Expr>,
+ ) -> (Body, BodySourceMap) {
+ lower::lower(db, expander, params, body)
+ }
+
+ fn shrink_to_fit(&mut self) {
+ let Self { _c: _, body_expr: _, block_scopes, or_pats, exprs, labels, params, pats } = self;
+ block_scopes.shrink_to_fit();
+ or_pats.shrink_to_fit();
+ exprs.shrink_to_fit();
+ labels.shrink_to_fit();
+ params.shrink_to_fit();
+ pats.shrink_to_fit();
+ }
+}
+
+impl Default for Body {
+ fn default() -> Self {
+ Self {
+ body_expr: dummy_expr_id(),
+ exprs: Default::default(),
+ pats: Default::default(),
+ or_pats: Default::default(),
+ labels: Default::default(),
+ params: Default::default(),
+ block_scopes: Default::default(),
+ _c: Default::default(),
+ }
+ }
+}
+
+impl Index<ExprId> for Body {
+ type Output = Expr;
+
+ fn index(&self, expr: ExprId) -> &Expr {
+ &self.exprs[expr]
+ }
+}
+
+impl Index<PatId> for Body {
+ type Output = Pat;
+
+ fn index(&self, pat: PatId) -> &Pat {
+ &self.pats[pat]
+ }
+}
+
+impl Index<LabelId> for Body {
+ type Output = Label;
+
+ fn index(&self, label: LabelId) -> &Label {
+ &self.labels[label]
+ }
+}
+
+// FIXME: Change `node_` prefix to something more reasonable.
+// Perhaps `expr_syntax` and `expr_id`?
+impl BodySourceMap {
+ pub fn expr_syntax(&self, expr: ExprId) -> Result<ExprSource, SyntheticSyntax> {
+ self.expr_map_back.get(expr).cloned().ok_or(SyntheticSyntax)
+ }
+
+ pub fn node_expr(&self, node: InFile<&ast::Expr>) -> Option<ExprId> {
+ let src = node.map(AstPtr::new);
+ self.expr_map.get(&src).cloned()
+ }
+
+ pub fn node_macro_file(&self, node: InFile<&ast::MacroCall>) -> Option<HirFileId> {
+ let src = node.map(AstPtr::new);
+ self.expansions.get(&src).cloned()
+ }
+
+ pub fn pat_syntax(&self, pat: PatId) -> Result<PatSource, SyntheticSyntax> {
+ self.pat_map_back.get(pat).cloned().ok_or(SyntheticSyntax)
+ }
+
+ pub fn node_pat(&self, node: InFile<&ast::Pat>) -> Option<PatId> {
+ let src = node.map(|it| Either::Left(AstPtr::new(it)));
+ self.pat_map.get(&src).cloned()
+ }
+
+ pub fn node_self_param(&self, node: InFile<&ast::SelfParam>) -> Option<PatId> {
+ let src = node.map(|it| Either::Right(AstPtr::new(it)));
+ self.pat_map.get(&src).cloned()
+ }
+
+ pub fn label_syntax(&self, label: LabelId) -> LabelSource {
+ self.label_map_back[label].clone()
+ }
+
+ pub fn node_label(&self, node: InFile<&ast::Label>) -> Option<LabelId> {
+ let src = node.map(AstPtr::new);
+ self.label_map.get(&src).cloned()
+ }
+
+ pub fn field_syntax(&self, expr: ExprId) -> FieldSource {
+ self.field_map_back[&expr].clone()
+ }
+
+ pub fn node_field(&self, node: InFile<&ast::RecordExprField>) -> Option<ExprId> {
+ let src = node.map(AstPtr::new);
+ self.field_map.get(&src).cloned()
+ }
+
+ pub fn macro_expansion_expr(&self, node: InFile<&ast::MacroExpr>) -> Option<ExprId> {
+ let src = node.map(AstPtr::new).map(AstPtr::upcast::<ast::MacroExpr>).map(AstPtr::upcast);
+ self.expr_map.get(&src).copied()
+ }
+
+ /// Get a reference to the body source map's diagnostics.
+ pub fn diagnostics(&self) -> &[BodyDiagnostic] {
+ &self.diagnostics
+ }
+}
--- /dev/null
- dummy_expr_id, Array, BindingAnnotation, Expr, ExprId, FloatTypeWrapper, Label, LabelId,
- Literal, MatchArm, Pat, PatId, RecordFieldPat, RecordLitField, Statement,
+//! Transforms `ast::Expr` into an equivalent `hir_def::expr::Expr`
+//! representation.
+
+use std::{mem, sync::Arc};
+
+use either::Either;
+use hir_expand::{
+ ast_id_map::AstIdMap,
+ hygiene::Hygiene,
+ name::{name, AsName, Name},
+ AstId, ExpandError, HirFileId, InFile,
+};
+use la_arena::Arena;
+use once_cell::unsync::OnceCell;
+use profile::Count;
+use rustc_hash::FxHashMap;
+use syntax::{
+ ast::{
+ self, ArrayExprKind, AstChildren, HasArgList, HasLoopBody, HasName, LiteralKind,
+ SlicePatComponents,
+ },
+ AstNode, AstPtr, SyntaxNodePtr,
+};
+
+use crate::{
+ adt::StructKind,
+ body::{Body, BodySourceMap, Expander, ExprPtr, LabelPtr, LabelSource, PatPtr},
+ body::{BodyDiagnostic, ExprSource, PatSource},
+ builtin_type::{BuiltinFloat, BuiltinInt, BuiltinUint},
+ db::DefDatabase,
+ expr::{
++ dummy_expr_id, Array, BindingAnnotation, ClosureKind, Expr, ExprId, FloatTypeWrapper,
++ Label, LabelId, Literal, MatchArm, Movability, Pat, PatId, RecordFieldPat, RecordLitField,
++ Statement,
+ },
+ intern::Interned,
+ item_scope::BuiltinShadowMode,
+ path::{GenericArgs, Path},
+ type_ref::{Mutability, Rawness, TypeRef},
+ AdtId, BlockLoc, ModuleDefId, UnresolvedMacro,
+};
+
+pub struct LowerCtx<'a> {
+ pub db: &'a dyn DefDatabase,
+ hygiene: Hygiene,
+ ast_id_map: Option<(HirFileId, OnceCell<Arc<AstIdMap>>)>,
+}
+
+impl<'a> LowerCtx<'a> {
+ pub fn new(db: &'a dyn DefDatabase, file_id: HirFileId) -> Self {
+ LowerCtx {
+ db,
+ hygiene: Hygiene::new(db.upcast(), file_id),
+ ast_id_map: Some((file_id, OnceCell::new())),
+ }
+ }
+
+ pub fn with_hygiene(db: &'a dyn DefDatabase, hygiene: &Hygiene) -> Self {
+ LowerCtx { db, hygiene: hygiene.clone(), ast_id_map: None }
+ }
+
+ pub(crate) fn hygiene(&self) -> &Hygiene {
+ &self.hygiene
+ }
+
+ pub(crate) fn lower_path(&self, ast: ast::Path) -> Option<Path> {
+ Path::from_src(ast, self)
+ }
+
+ pub(crate) fn ast_id<N: AstNode>(&self, db: &dyn DefDatabase, item: &N) -> Option<AstId<N>> {
+ let &(file_id, ref ast_id_map) = self.ast_id_map.as_ref()?;
+ let ast_id_map = ast_id_map.get_or_init(|| db.ast_id_map(file_id));
+ Some(InFile::new(file_id, ast_id_map.ast_id(item)))
+ }
+}
+
+pub(super) fn lower(
+ db: &dyn DefDatabase,
+ expander: Expander,
+ params: Option<ast::ParamList>,
+ body: Option<ast::Expr>,
+) -> (Body, BodySourceMap) {
+ ExprCollector {
+ db,
+ source_map: BodySourceMap::default(),
+ ast_id_map: db.ast_id_map(expander.current_file_id),
+ body: Body {
+ exprs: Arena::default(),
+ pats: Arena::default(),
+ labels: Arena::default(),
+ params: Vec::new(),
+ body_expr: dummy_expr_id(),
+ block_scopes: Vec::new(),
+ _c: Count::new(),
+ or_pats: Default::default(),
+ },
+ expander,
+ name_to_pat_grouping: Default::default(),
+ is_lowering_inside_or_pat: false,
+ is_lowering_assignee_expr: false,
++ is_lowering_generator: false,
+ }
+ .collect(params, body)
+}
+
+struct ExprCollector<'a> {
+ db: &'a dyn DefDatabase,
+ expander: Expander,
+ ast_id_map: Arc<AstIdMap>,
+ body: Body,
+ source_map: BodySourceMap,
+ // a poor-mans union-find?
+ name_to_pat_grouping: FxHashMap<Name, Vec<PatId>>,
+ is_lowering_inside_or_pat: bool,
+ is_lowering_assignee_expr: bool,
++ is_lowering_generator: bool,
+}
+
+impl ExprCollector<'_> {
+ fn collect(
+ mut self,
+ param_list: Option<ast::ParamList>,
+ body: Option<ast::Expr>,
+ ) -> (Body, BodySourceMap) {
+ if let Some(param_list) = param_list {
+ if let Some(self_param) = param_list.self_param() {
+ let ptr = AstPtr::new(&self_param);
+ let param_pat = self.alloc_pat(
+ Pat::Bind {
+ name: name![self],
+ mode: BindingAnnotation::new(
+ self_param.mut_token().is_some() && self_param.amp_token().is_none(),
+ false,
+ ),
+ subpat: None,
+ },
+ Either::Right(ptr),
+ );
+ self.body.params.push(param_pat);
+ }
+
+ for pat in param_list.params().filter_map(|param| param.pat()) {
+ let param_pat = self.collect_pat(pat);
+ self.body.params.push(param_pat);
+ }
+ };
+
+ self.body.body_expr = self.collect_expr_opt(body);
+ (self.body, self.source_map)
+ }
+
+ fn ctx(&self) -> LowerCtx<'_> {
+ LowerCtx::new(self.db, self.expander.current_file_id)
+ }
+
+ fn alloc_expr(&mut self, expr: Expr, ptr: ExprPtr) -> ExprId {
+ let src = self.expander.to_source(ptr);
+ let id = self.make_expr(expr, src.clone());
+ self.source_map.expr_map.insert(src, id);
+ id
+ }
+ // desugared exprs don't have ptr, that's wrong and should be fixed
+ // somehow.
+ fn alloc_expr_desugared(&mut self, expr: Expr) -> ExprId {
+ self.body.exprs.alloc(expr)
+ }
+ fn missing_expr(&mut self) -> ExprId {
+ self.alloc_expr_desugared(Expr::Missing)
+ }
+ fn make_expr(&mut self, expr: Expr, src: ExprSource) -> ExprId {
+ let id = self.body.exprs.alloc(expr);
+ self.source_map.expr_map_back.insert(id, src);
+ id
+ }
+
+ fn alloc_pat(&mut self, pat: Pat, ptr: PatPtr) -> PatId {
+ let src = self.expander.to_source(ptr);
+ let id = self.make_pat(pat, src.clone());
+ self.source_map.pat_map.insert(src, id);
+ id
+ }
+ fn missing_pat(&mut self) -> PatId {
+ self.body.pats.alloc(Pat::Missing)
+ }
+ fn make_pat(&mut self, pat: Pat, src: PatSource) -> PatId {
+ let id = self.body.pats.alloc(pat);
+ self.source_map.pat_map_back.insert(id, src);
+ id
+ }
+
+ fn alloc_label(&mut self, label: Label, ptr: LabelPtr) -> LabelId {
+ let src = self.expander.to_source(ptr);
+ let id = self.make_label(label, src.clone());
+ self.source_map.label_map.insert(src, id);
+ id
+ }
+ fn make_label(&mut self, label: Label, src: LabelSource) -> LabelId {
+ let id = self.body.labels.alloc(label);
+ self.source_map.label_map_back.insert(id, src);
+ id
+ }
+
+ fn collect_expr(&mut self, expr: ast::Expr) -> ExprId {
+ self.maybe_collect_expr(expr).unwrap_or_else(|| self.missing_expr())
+ }
+
+ /// Returns `None` if and only if the expression is `#[cfg]`d out.
+ fn maybe_collect_expr(&mut self, expr: ast::Expr) -> Option<ExprId> {
+ let syntax_ptr = AstPtr::new(&expr);
+ self.check_cfg(&expr)?;
+
+ Some(match expr {
+ ast::Expr::IfExpr(e) => {
+ let then_branch = self.collect_block_opt(e.then_branch());
+
+ let else_branch = e.else_branch().map(|b| match b {
+ ast::ElseBranch::Block(it) => self.collect_block(it),
+ ast::ElseBranch::IfExpr(elif) => {
+ let expr: ast::Expr = ast::Expr::cast(elif.syntax().clone()).unwrap();
+ self.collect_expr(expr)
+ }
+ });
+
+ let condition = self.collect_expr_opt(e.condition());
+
+ self.alloc_expr(Expr::If { condition, then_branch, else_branch }, syntax_ptr)
+ }
+ ast::Expr::LetExpr(e) => {
+ let pat = self.collect_pat_opt(e.pat());
+ let expr = self.collect_expr_opt(e.expr());
+ self.alloc_expr(Expr::Let { pat, expr }, syntax_ptr)
+ }
+ ast::Expr::BlockExpr(e) => match e.modifier() {
+ Some(ast::BlockModifier::Try(_)) => {
+ let body = self.collect_block(e);
+ self.alloc_expr(Expr::TryBlock { body }, syntax_ptr)
+ }
+ Some(ast::BlockModifier::Unsafe(_)) => {
+ let body = self.collect_block(e);
+ self.alloc_expr(Expr::Unsafe { body }, syntax_ptr)
+ }
+ // FIXME: we need to record these effects somewhere...
+ Some(ast::BlockModifier::Label(label)) => {
+ let label = self.collect_label(label);
+ let res = self.collect_block(e);
+ match &mut self.body.exprs[res] {
+ Expr::Block { label: block_label, .. } => {
+ *block_label = Some(label);
+ }
+ _ => unreachable!(),
+ }
+ res
+ }
+ Some(ast::BlockModifier::Async(_)) => {
+ let body = self.collect_block(e);
+ self.alloc_expr(Expr::Async { body }, syntax_ptr)
+ }
+ Some(ast::BlockModifier::Const(_)) => {
+ let body = self.collect_block(e);
+ self.alloc_expr(Expr::Const { body }, syntax_ptr)
+ }
+ None => self.collect_block(e),
+ },
+ ast::Expr::LoopExpr(e) => {
+ let label = e.label().map(|label| self.collect_label(label));
+ let body = self.collect_block_opt(e.loop_body());
+ self.alloc_expr(Expr::Loop { body, label }, syntax_ptr)
+ }
+ ast::Expr::WhileExpr(e) => {
+ let label = e.label().map(|label| self.collect_label(label));
+ let body = self.collect_block_opt(e.loop_body());
+
+ let condition = self.collect_expr_opt(e.condition());
+
+ self.alloc_expr(Expr::While { condition, body, label }, syntax_ptr)
+ }
+ ast::Expr::ForExpr(e) => {
+ let label = e.label().map(|label| self.collect_label(label));
+ let iterable = self.collect_expr_opt(e.iterable());
+ let pat = self.collect_pat_opt(e.pat());
+ let body = self.collect_block_opt(e.loop_body());
+ self.alloc_expr(Expr::For { iterable, pat, body, label }, syntax_ptr)
+ }
+ ast::Expr::CallExpr(e) => {
+ let callee = self.collect_expr_opt(e.expr());
+ let args = if let Some(arg_list) = e.arg_list() {
+ arg_list.args().filter_map(|e| self.maybe_collect_expr(e)).collect()
+ } else {
+ Box::default()
+ };
+ self.alloc_expr(
+ Expr::Call { callee, args, is_assignee_expr: self.is_lowering_assignee_expr },
+ syntax_ptr,
+ )
+ }
+ ast::Expr::MethodCallExpr(e) => {
+ let receiver = self.collect_expr_opt(e.receiver());
+ let args = if let Some(arg_list) = e.arg_list() {
+ arg_list.args().filter_map(|e| self.maybe_collect_expr(e)).collect()
+ } else {
+ Box::default()
+ };
+ let method_name = e.name_ref().map(|nr| nr.as_name()).unwrap_or_else(Name::missing);
+ let generic_args = e
+ .generic_arg_list()
+ .and_then(|it| GenericArgs::from_ast(&self.ctx(), it))
+ .map(Box::new);
+ self.alloc_expr(
+ Expr::MethodCall { receiver, method_name, args, generic_args },
+ syntax_ptr,
+ )
+ }
+ ast::Expr::MatchExpr(e) => {
+ let expr = self.collect_expr_opt(e.expr());
+ let arms = if let Some(match_arm_list) = e.match_arm_list() {
+ match_arm_list
+ .arms()
+ .filter_map(|arm| {
+ self.check_cfg(&arm).map(|()| MatchArm {
+ pat: self.collect_pat_opt(arm.pat()),
+ expr: self.collect_expr_opt(arm.expr()),
+ guard: arm
+ .guard()
+ .map(|guard| self.collect_expr_opt(guard.condition())),
+ })
+ })
+ .collect()
+ } else {
+ Box::default()
+ };
+ self.alloc_expr(Expr::Match { expr, arms }, syntax_ptr)
+ }
+ ast::Expr::PathExpr(e) => {
+ let path = e
+ .path()
+ .and_then(|path| self.expander.parse_path(self.db, path))
+ .map(Expr::Path)
+ .unwrap_or(Expr::Missing);
+ self.alloc_expr(path, syntax_ptr)
+ }
+ ast::Expr::ContinueExpr(e) => self.alloc_expr(
+ Expr::Continue { label: e.lifetime().map(|l| Name::new_lifetime(&l)) },
+ syntax_ptr,
+ ),
+ ast::Expr::BreakExpr(e) => {
+ let expr = e.expr().map(|e| self.collect_expr(e));
+ self.alloc_expr(
+ Expr::Break { expr, label: e.lifetime().map(|l| Name::new_lifetime(&l)) },
+ syntax_ptr,
+ )
+ }
+ ast::Expr::ParenExpr(e) => {
+ let inner = self.collect_expr_opt(e.expr());
+ // make the paren expr point to the inner expression as well
+ let src = self.expander.to_source(syntax_ptr);
+ self.source_map.expr_map.insert(src, inner);
+ inner
+ }
+ ast::Expr::ReturnExpr(e) => {
+ let expr = e.expr().map(|e| self.collect_expr(e));
+ self.alloc_expr(Expr::Return { expr }, syntax_ptr)
+ }
+ ast::Expr::YieldExpr(e) => {
++ self.is_lowering_generator = true;
+ let expr = e.expr().map(|e| self.collect_expr(e));
+ self.alloc_expr(Expr::Yield { expr }, syntax_ptr)
+ }
+ ast::Expr::RecordExpr(e) => {
+ let path =
+ e.path().and_then(|path| self.expander.parse_path(self.db, path)).map(Box::new);
+ let is_assignee_expr = self.is_lowering_assignee_expr;
+ let record_lit = if let Some(nfl) = e.record_expr_field_list() {
+ let fields = nfl
+ .fields()
+ .filter_map(|field| {
+ self.check_cfg(&field)?;
+
+ let name = field.field_name()?.as_name();
+
+ let expr = match field.expr() {
+ Some(e) => self.collect_expr(e),
+ None => self.missing_expr(),
+ };
+ let src = self.expander.to_source(AstPtr::new(&field));
+ self.source_map.field_map.insert(src.clone(), expr);
+ self.source_map.field_map_back.insert(expr, src);
+ Some(RecordLitField { name, expr })
+ })
+ .collect();
+ let spread = nfl.spread().map(|s| self.collect_expr(s));
+ let ellipsis = nfl.dotdot_token().is_some();
+ Expr::RecordLit { path, fields, spread, ellipsis, is_assignee_expr }
+ } else {
+ Expr::RecordLit {
+ path,
+ fields: Box::default(),
+ spread: None,
+ ellipsis: false,
+ is_assignee_expr,
+ }
+ };
+
+ self.alloc_expr(record_lit, syntax_ptr)
+ }
+ ast::Expr::FieldExpr(e) => {
+ let expr = self.collect_expr_opt(e.expr());
+ let name = match e.field_access() {
+ Some(kind) => kind.as_name(),
+ _ => Name::missing(),
+ };
+ self.alloc_expr(Expr::Field { expr, name }, syntax_ptr)
+ }
+ ast::Expr::AwaitExpr(e) => {
+ let expr = self.collect_expr_opt(e.expr());
+ self.alloc_expr(Expr::Await { expr }, syntax_ptr)
+ }
+ ast::Expr::TryExpr(e) => {
+ let expr = self.collect_expr_opt(e.expr());
+ self.alloc_expr(Expr::Try { expr }, syntax_ptr)
+ }
+ ast::Expr::CastExpr(e) => {
+ let expr = self.collect_expr_opt(e.expr());
+ let type_ref = Interned::new(TypeRef::from_ast_opt(&self.ctx(), e.ty()));
+ self.alloc_expr(Expr::Cast { expr, type_ref }, syntax_ptr)
+ }
+ ast::Expr::RefExpr(e) => {
+ let expr = self.collect_expr_opt(e.expr());
+ let raw_tok = e.raw_token().is_some();
+ let mutability = if raw_tok {
+ if e.mut_token().is_some() {
+ Mutability::Mut
+ } else if e.const_token().is_some() {
+ Mutability::Shared
+ } else {
+ unreachable!("parser only remaps to raw_token() if matching mutability token follows")
+ }
+ } else {
+ Mutability::from_mutable(e.mut_token().is_some())
+ };
+ let rawness = Rawness::from_raw(raw_tok);
+ self.alloc_expr(Expr::Ref { expr, rawness, mutability }, syntax_ptr)
+ }
+ ast::Expr::PrefixExpr(e) => {
+ let expr = self.collect_expr_opt(e.expr());
+ match e.op_kind() {
+ Some(op) => self.alloc_expr(Expr::UnaryOp { expr, op }, syntax_ptr),
+ None => self.alloc_expr(Expr::Missing, syntax_ptr),
+ }
+ }
+ ast::Expr::ClosureExpr(e) => {
+ let mut args = Vec::new();
+ let mut arg_types = Vec::new();
+ if let Some(pl) = e.param_list() {
+ for param in pl.params() {
+ let pat = self.collect_pat_opt(param.pat());
+ let type_ref =
+ param.ty().map(|it| Interned::new(TypeRef::from_ast(&self.ctx(), it)));
+ args.push(pat);
+ arg_types.push(type_ref);
+ }
+ }
+ let ret_type = e
+ .ret_type()
+ .and_then(|r| r.ty())
+ .map(|it| Interned::new(TypeRef::from_ast(&self.ctx(), it)));
++
++ let prev_is_lowering_generator = self.is_lowering_generator;
++ self.is_lowering_generator = false;
++
+ let body = self.collect_expr_opt(e.body());
++
++ let closure_kind = if self.is_lowering_generator {
++ let movability = if e.static_token().is_some() {
++ Movability::Static
++ } else {
++ Movability::Movable
++ };
++ ClosureKind::Generator(movability)
++ } else {
++ ClosureKind::Closure
++ };
++ self.is_lowering_generator = prev_is_lowering_generator;
++
+ self.alloc_expr(
+ Expr::Closure {
+ args: args.into(),
+ arg_types: arg_types.into(),
+ ret_type,
+ body,
++ closure_kind,
+ },
+ syntax_ptr,
+ )
+ }
+ ast::Expr::BinExpr(e) => {
+ let op = e.op_kind();
+ if let Some(ast::BinaryOp::Assignment { op: None }) = op {
+ self.is_lowering_assignee_expr = true;
+ }
+ let lhs = self.collect_expr_opt(e.lhs());
+ self.is_lowering_assignee_expr = false;
+ let rhs = self.collect_expr_opt(e.rhs());
+ self.alloc_expr(Expr::BinaryOp { lhs, rhs, op }, syntax_ptr)
+ }
+ ast::Expr::TupleExpr(e) => {
+ let exprs = e.fields().map(|expr| self.collect_expr(expr)).collect();
+ self.alloc_expr(
+ Expr::Tuple { exprs, is_assignee_expr: self.is_lowering_assignee_expr },
+ syntax_ptr,
+ )
+ }
+ ast::Expr::BoxExpr(e) => {
+ let expr = self.collect_expr_opt(e.expr());
+ self.alloc_expr(Expr::Box { expr }, syntax_ptr)
+ }
+
+ ast::Expr::ArrayExpr(e) => {
+ let kind = e.kind();
+
+ match kind {
+ ArrayExprKind::ElementList(e) => {
+ let elements = e.map(|expr| self.collect_expr(expr)).collect();
+ self.alloc_expr(
+ Expr::Array(Array::ElementList {
+ elements,
+ is_assignee_expr: self.is_lowering_assignee_expr,
+ }),
+ syntax_ptr,
+ )
+ }
+ ArrayExprKind::Repeat { initializer, repeat } => {
+ let initializer = self.collect_expr_opt(initializer);
+ let repeat = self.collect_expr_opt(repeat);
+ self.alloc_expr(
+ Expr::Array(Array::Repeat { initializer, repeat }),
+ syntax_ptr,
+ )
+ }
+ }
+ }
+
+ ast::Expr::Literal(e) => self.alloc_expr(Expr::Literal(e.kind().into()), syntax_ptr),
+ ast::Expr::IndexExpr(e) => {
+ let base = self.collect_expr_opt(e.base());
+ let index = self.collect_expr_opt(e.index());
+ self.alloc_expr(Expr::Index { base, index }, syntax_ptr)
+ }
+ ast::Expr::RangeExpr(e) => {
+ let lhs = e.start().map(|lhs| self.collect_expr(lhs));
+ let rhs = e.end().map(|rhs| self.collect_expr(rhs));
+ match e.op_kind() {
+ Some(range_type) => {
+ self.alloc_expr(Expr::Range { lhs, rhs, range_type }, syntax_ptr)
+ }
+ None => self.alloc_expr(Expr::Missing, syntax_ptr),
+ }
+ }
+ ast::Expr::MacroExpr(e) => {
+ let e = e.macro_call()?;
+ let macro_ptr = AstPtr::new(&e);
+ let id = self.collect_macro_call(e, macro_ptr, true, |this, expansion| {
+ expansion.map(|it| this.collect_expr(it))
+ });
+ match id {
+ Some(id) => {
+ // Make the macro-call point to its expanded expression so we can query
+ // semantics on syntax pointers to the macro
+ let src = self.expander.to_source(syntax_ptr);
+ self.source_map.expr_map.insert(src, id);
+ id
+ }
+ None => self.alloc_expr(Expr::Missing, syntax_ptr),
+ }
+ }
+ ast::Expr::UnderscoreExpr(_) => self.alloc_expr(Expr::Underscore, syntax_ptr),
+ })
+ }
+
+ fn collect_macro_call<F, T, U>(
+ &mut self,
+ mcall: ast::MacroCall,
+ syntax_ptr: AstPtr<ast::MacroCall>,
+ record_diagnostics: bool,
+ collector: F,
+ ) -> U
+ where
+ F: FnOnce(&mut Self, Option<T>) -> U,
+ T: ast::AstNode,
+ {
+ // File containing the macro call. Expansion errors will be attached here.
+ let outer_file = self.expander.current_file_id;
+
+ let macro_call_ptr = self.expander.to_source(AstPtr::new(&mcall));
+ let res = self.expander.enter_expand(self.db, mcall);
+
+ let res = match res {
+ Ok(res) => res,
+ Err(UnresolvedMacro { path }) => {
+ if record_diagnostics {
+ self.source_map.diagnostics.push(BodyDiagnostic::UnresolvedMacroCall {
+ node: InFile::new(outer_file, syntax_ptr),
+ path,
+ });
+ }
+ return collector(self, None);
+ }
+ };
+
+ if record_diagnostics {
+ match &res.err {
+ Some(ExpandError::UnresolvedProcMacro(krate)) => {
+ self.source_map.diagnostics.push(BodyDiagnostic::UnresolvedProcMacro {
+ node: InFile::new(outer_file, syntax_ptr),
+ krate: *krate,
+ });
+ }
+ Some(err) => {
+ self.source_map.diagnostics.push(BodyDiagnostic::MacroError {
+ node: InFile::new(outer_file, syntax_ptr),
+ message: err.to_string(),
+ });
+ }
+ None => {}
+ }
+ }
+
+ match res.value {
+ Some((mark, expansion)) => {
+ self.source_map.expansions.insert(macro_call_ptr, self.expander.current_file_id);
+ let prev_ast_id_map = mem::replace(
+ &mut self.ast_id_map,
+ self.db.ast_id_map(self.expander.current_file_id),
+ );
+
+ let id = collector(self, Some(expansion));
+ self.ast_id_map = prev_ast_id_map;
+ self.expander.exit(self.db, mark);
+ id
+ }
+ None => collector(self, None),
+ }
+ }
+
+ fn collect_expr_opt(&mut self, expr: Option<ast::Expr>) -> ExprId {
+ match expr {
+ Some(expr) => self.collect_expr(expr),
+ None => self.missing_expr(),
+ }
+ }
+
+ fn collect_macro_as_stmt(
+ &mut self,
+ statements: &mut Vec<Statement>,
+ mac: ast::MacroExpr,
+ ) -> Option<ExprId> {
+ let mac_call = mac.macro_call()?;
+ let syntax_ptr = AstPtr::new(&ast::Expr::from(mac));
+ let macro_ptr = AstPtr::new(&mac_call);
+ let expansion = self.collect_macro_call(
+ mac_call,
+ macro_ptr,
+ false,
+ |this, expansion: Option<ast::MacroStmts>| match expansion {
+ Some(expansion) => {
+ expansion.statements().for_each(|stmt| this.collect_stmt(statements, stmt));
+ expansion.expr().and_then(|expr| match expr {
+ ast::Expr::MacroExpr(mac) => this.collect_macro_as_stmt(statements, mac),
+ expr => Some(this.collect_expr(expr)),
+ })
+ }
+ None => None,
+ },
+ );
+ match expansion {
+ Some(tail) => {
+ // Make the macro-call point to its expanded expression so we can query
+ // semantics on syntax pointers to the macro
+ let src = self.expander.to_source(syntax_ptr);
+ self.source_map.expr_map.insert(src, tail);
+ Some(tail)
+ }
+ None => None,
+ }
+ }
+
+ fn collect_stmt(&mut self, statements: &mut Vec<Statement>, s: ast::Stmt) {
+ match s {
+ ast::Stmt::LetStmt(stmt) => {
+ if self.check_cfg(&stmt).is_none() {
+ return;
+ }
+ let pat = self.collect_pat_opt(stmt.pat());
+ let type_ref =
+ stmt.ty().map(|it| Interned::new(TypeRef::from_ast(&self.ctx(), it)));
+ let initializer = stmt.initializer().map(|e| self.collect_expr(e));
+ let else_branch = stmt
+ .let_else()
+ .and_then(|let_else| let_else.block_expr())
+ .map(|block| self.collect_block(block));
+ statements.push(Statement::Let { pat, type_ref, initializer, else_branch });
+ }
+ ast::Stmt::ExprStmt(stmt) => {
+ let expr = stmt.expr();
+ match &expr {
+ Some(expr) if self.check_cfg(expr).is_none() => return,
+ _ => (),
+ }
+ let has_semi = stmt.semicolon_token().is_some();
+ // Note that macro could be expanded to multiple statements
+ if let Some(ast::Expr::MacroExpr(mac)) = expr {
+ if let Some(expr) = self.collect_macro_as_stmt(statements, mac) {
+ statements.push(Statement::Expr { expr, has_semi })
+ }
+ } else {
+ let expr = self.collect_expr_opt(expr);
+ statements.push(Statement::Expr { expr, has_semi });
+ }
+ }
+ ast::Stmt::Item(_item) => (),
+ }
+ }
+
+ fn collect_block(&mut self, block: ast::BlockExpr) -> ExprId {
+ let file_local_id = self.ast_id_map.ast_id(&block);
+ let ast_id = AstId::new(self.expander.current_file_id, file_local_id);
+ let block_loc =
+ BlockLoc { ast_id, module: self.expander.def_map.module_id(self.expander.module) };
+ let block_id = self.db.intern_block(block_loc);
+
+ let (module, def_map) = match self.db.block_def_map(block_id) {
+ Some(def_map) => {
+ self.body.block_scopes.push(block_id);
+ (def_map.root(), def_map)
+ }
+ None => (self.expander.module, self.expander.def_map.clone()),
+ };
+ let prev_def_map = mem::replace(&mut self.expander.def_map, def_map);
+ let prev_local_module = mem::replace(&mut self.expander.module, module);
+
+ let mut statements = Vec::new();
+ block.statements().for_each(|s| self.collect_stmt(&mut statements, s));
+ let tail = block.tail_expr().and_then(|e| match e {
+ ast::Expr::MacroExpr(mac) => self.collect_macro_as_stmt(&mut statements, mac),
+ expr => self.maybe_collect_expr(expr),
+ });
+ let tail = tail.or_else(|| {
+ let stmt = statements.pop()?;
+ if let Statement::Expr { expr, has_semi: false } = stmt {
+ return Some(expr);
+ }
+ statements.push(stmt);
+ None
+ });
+
+ let syntax_node_ptr = AstPtr::new(&block.into());
+ let expr_id = self.alloc_expr(
+ Expr::Block {
+ id: block_id,
+ statements: statements.into_boxed_slice(),
+ tail,
+ label: None,
+ },
+ syntax_node_ptr,
+ );
+
+ self.expander.def_map = prev_def_map;
+ self.expander.module = prev_local_module;
+ expr_id
+ }
+
+ fn collect_block_opt(&mut self, expr: Option<ast::BlockExpr>) -> ExprId {
+ match expr {
+ Some(block) => self.collect_block(block),
+ None => self.missing_expr(),
+ }
+ }
+
+ fn collect_label(&mut self, ast_label: ast::Label) -> LabelId {
+ let label = Label {
+ name: ast_label.lifetime().as_ref().map_or_else(Name::missing, Name::new_lifetime),
+ };
+ self.alloc_label(label, AstPtr::new(&ast_label))
+ }
+
+ fn collect_pat(&mut self, pat: ast::Pat) -> PatId {
+ let pat_id = self.collect_pat_(pat);
+ for (_, pats) in self.name_to_pat_grouping.drain() {
+ let pats = Arc::<[_]>::from(pats);
+ self.body.or_pats.extend(pats.iter().map(|&pat| (pat, pats.clone())));
+ }
+ self.is_lowering_inside_or_pat = false;
+ pat_id
+ }
+
+ fn collect_pat_opt(&mut self, pat: Option<ast::Pat>) -> PatId {
+ match pat {
+ Some(pat) => self.collect_pat(pat),
+ None => self.missing_pat(),
+ }
+ }
+
+ fn collect_pat_(&mut self, pat: ast::Pat) -> PatId {
+ let pattern = match &pat {
+ ast::Pat::IdentPat(bp) => {
+ let name = bp.name().map(|nr| nr.as_name()).unwrap_or_else(Name::missing);
+
+ let key = self.is_lowering_inside_or_pat.then(|| name.clone());
+ let annotation =
+ BindingAnnotation::new(bp.mut_token().is_some(), bp.ref_token().is_some());
+ let subpat = bp.pat().map(|subpat| self.collect_pat_(subpat));
+ let pattern = if annotation == BindingAnnotation::Unannotated && subpat.is_none() {
+ // This could also be a single-segment path pattern. To
+ // decide that, we need to try resolving the name.
+ let (resolved, _) = self.expander.def_map.resolve_path(
+ self.db,
+ self.expander.module,
+ &name.clone().into(),
+ BuiltinShadowMode::Other,
+ );
+ match resolved.take_values() {
+ Some(ModuleDefId::ConstId(_)) => Pat::Path(name.into()),
+ Some(ModuleDefId::EnumVariantId(_)) => {
+ // this is only really valid for unit variants, but
+ // shadowing other enum variants with a pattern is
+ // an error anyway
+ Pat::Path(name.into())
+ }
+ Some(ModuleDefId::AdtId(AdtId::StructId(s)))
+ if self.db.struct_data(s).variant_data.kind() != StructKind::Record =>
+ {
+ // Funnily enough, record structs *can* be shadowed
+ // by pattern bindings (but unit or tuple structs
+ // can't).
+ Pat::Path(name.into())
+ }
+ // shadowing statics is an error as well, so we just ignore that case here
+ _ => Pat::Bind { name, mode: annotation, subpat },
+ }
+ } else {
+ Pat::Bind { name, mode: annotation, subpat }
+ };
+
+ let ptr = AstPtr::new(&pat);
+ let pat = self.alloc_pat(pattern, Either::Left(ptr));
+ if let Some(key) = key {
+ self.name_to_pat_grouping.entry(key).or_default().push(pat);
+ }
+ return pat;
+ }
+ ast::Pat::TupleStructPat(p) => {
+ let path =
+ p.path().and_then(|path| self.expander.parse_path(self.db, path)).map(Box::new);
+ let (args, ellipsis) = self.collect_tuple_pat(p.fields());
+ Pat::TupleStruct { path, args, ellipsis }
+ }
+ ast::Pat::RefPat(p) => {
+ let pat = self.collect_pat_opt(p.pat());
+ let mutability = Mutability::from_mutable(p.mut_token().is_some());
+ Pat::Ref { pat, mutability }
+ }
+ ast::Pat::PathPat(p) => {
+ let path =
+ p.path().and_then(|path| self.expander.parse_path(self.db, path)).map(Box::new);
+ path.map(Pat::Path).unwrap_or(Pat::Missing)
+ }
+ ast::Pat::OrPat(p) => {
+ self.is_lowering_inside_or_pat = true;
+ let pats = p.pats().map(|p| self.collect_pat_(p)).collect();
+ Pat::Or(pats)
+ }
+ ast::Pat::ParenPat(p) => return self.collect_pat_opt_(p.pat()),
+ ast::Pat::TuplePat(p) => {
+ let (args, ellipsis) = self.collect_tuple_pat(p.fields());
+ Pat::Tuple { args, ellipsis }
+ }
+ ast::Pat::WildcardPat(_) => Pat::Wild,
+ ast::Pat::RecordPat(p) => {
+ let path =
+ p.path().and_then(|path| self.expander.parse_path(self.db, path)).map(Box::new);
+ let args = p
+ .record_pat_field_list()
+ .expect("every struct should have a field list")
+ .fields()
+ .filter_map(|f| {
+ let ast_pat = f.pat()?;
+ let pat = self.collect_pat_(ast_pat);
+ let name = f.field_name()?.as_name();
+ Some(RecordFieldPat { name, pat })
+ })
+ .collect();
+
+ let ellipsis = p
+ .record_pat_field_list()
+ .expect("every struct should have a field list")
+ .rest_pat()
+ .is_some();
+
+ Pat::Record { path, args, ellipsis }
+ }
+ ast::Pat::SlicePat(p) => {
+ let SlicePatComponents { prefix, slice, suffix } = p.components();
+
+ // FIXME properly handle `RestPat`
+ Pat::Slice {
+ prefix: prefix.into_iter().map(|p| self.collect_pat_(p)).collect(),
+ slice: slice.map(|p| self.collect_pat_(p)),
+ suffix: suffix.into_iter().map(|p| self.collect_pat_(p)).collect(),
+ }
+ }
+ ast::Pat::LiteralPat(lit) => {
+ if let Some(ast_lit) = lit.literal() {
+ let expr = Expr::Literal(ast_lit.kind().into());
+ let expr_ptr = AstPtr::new(&ast::Expr::Literal(ast_lit));
+ let expr_id = self.alloc_expr(expr, expr_ptr);
+ Pat::Lit(expr_id)
+ } else {
+ Pat::Missing
+ }
+ }
+ ast::Pat::RestPat(_) => {
+ // `RestPat` requires special handling and should not be mapped
+ // to a Pat. Here we are using `Pat::Missing` as a fallback for
+ // when `RestPat` is mapped to `Pat`, which can easily happen
+ // when the source code being analyzed has a malformed pattern
+ // which includes `..` in a place where it isn't valid.
+
+ Pat::Missing
+ }
+ ast::Pat::BoxPat(boxpat) => {
+ let inner = self.collect_pat_opt_(boxpat.pat());
+ Pat::Box { inner }
+ }
+ ast::Pat::ConstBlockPat(const_block_pat) => {
+ if let Some(expr) = const_block_pat.block_expr() {
+ let expr_id = self.collect_block(expr);
+ Pat::ConstBlock(expr_id)
+ } else {
+ Pat::Missing
+ }
+ }
+ ast::Pat::MacroPat(mac) => match mac.macro_call() {
+ Some(call) => {
+ let macro_ptr = AstPtr::new(&call);
+ let src = self.expander.to_source(Either::Left(AstPtr::new(&pat)));
+ let pat =
+ self.collect_macro_call(call, macro_ptr, true, |this, expanded_pat| {
+ this.collect_pat_opt_(expanded_pat)
+ });
+ self.source_map.pat_map.insert(src, pat);
+ return pat;
+ }
+ None => Pat::Missing,
+ },
+ // FIXME: implement
+ ast::Pat::RangePat(_) => Pat::Missing,
+ };
+ let ptr = AstPtr::new(&pat);
+ self.alloc_pat(pattern, Either::Left(ptr))
+ }
+
+ fn collect_pat_opt_(&mut self, pat: Option<ast::Pat>) -> PatId {
+ match pat {
+ Some(pat) => self.collect_pat_(pat),
+ None => self.missing_pat(),
+ }
+ }
+
+ fn collect_tuple_pat(&mut self, args: AstChildren<ast::Pat>) -> (Box<[PatId]>, Option<usize>) {
+ // Find the location of the `..`, if there is one. Note that we do not
+ // consider the possibility of there being multiple `..` here.
+ let ellipsis = args.clone().position(|p| matches!(p, ast::Pat::RestPat(_)));
+ // We want to skip the `..` pattern here, since we account for it above.
+ let args = args
+ .filter(|p| !matches!(p, ast::Pat::RestPat(_)))
+ .map(|p| self.collect_pat_(p))
+ .collect();
+
+ (args, ellipsis)
+ }
+
+ /// Returns `None` (and emits diagnostics) when `owner` if `#[cfg]`d out, and `Some(())` when
+ /// not.
+ fn check_cfg(&mut self, owner: &dyn ast::HasAttrs) -> Option<()> {
+ match self.expander.parse_attrs(self.db, owner).cfg() {
+ Some(cfg) => {
+ if self.expander.cfg_options().check(&cfg) != Some(false) {
+ return Some(());
+ }
+
+ self.source_map.diagnostics.push(BodyDiagnostic::InactiveCode {
+ node: InFile::new(
+ self.expander.current_file_id,
+ SyntaxNodePtr::new(owner.syntax()),
+ ),
+ cfg,
+ opts: self.expander.cfg_options().clone(),
+ });
+
+ None
+ }
+ None => Some(()),
+ }
+ }
+}
+
+impl From<ast::LiteralKind> for Literal {
+ fn from(ast_lit_kind: ast::LiteralKind) -> Self {
+ match ast_lit_kind {
+ // FIXME: these should have actual values filled in, but unsure on perf impact
+ LiteralKind::IntNumber(lit) => {
+ if let builtin @ Some(_) = lit.suffix().and_then(BuiltinFloat::from_suffix) {
+ Literal::Float(
+ FloatTypeWrapper::new(lit.float_value().unwrap_or(Default::default())),
+ builtin,
+ )
+ } else if let builtin @ Some(_) = lit.suffix().and_then(BuiltinInt::from_suffix) {
+ Literal::Int(lit.value().unwrap_or(0) as i128, builtin)
+ } else {
+ let builtin = lit.suffix().and_then(BuiltinUint::from_suffix);
+ Literal::Uint(lit.value().unwrap_or(0), builtin)
+ }
+ }
+ LiteralKind::FloatNumber(lit) => {
+ let ty = lit.suffix().and_then(BuiltinFloat::from_suffix);
+ Literal::Float(FloatTypeWrapper::new(lit.value().unwrap_or(Default::default())), ty)
+ }
+ LiteralKind::ByteString(bs) => {
+ let text = bs.value().map(Box::from).unwrap_or_else(Default::default);
+ Literal::ByteString(text)
+ }
+ LiteralKind::String(s) => {
+ let text = s.value().map(Box::from).unwrap_or_else(Default::default);
+ Literal::String(text)
+ }
+ LiteralKind::Byte(b) => {
+ Literal::Uint(b.value().unwrap_or_default() as u128, Some(BuiltinUint::U8))
+ }
+ LiteralKind::Char(c) => Literal::Char(c.value().unwrap_or_default()),
+ LiteralKind::Bool(val) => Literal::Bool(val),
+ }
+ }
+}
--- /dev/null
- expr::{Array, BindingAnnotation, Literal, Statement},
+//! A pretty-printer for HIR.
+
+use std::fmt::{self, Write};
+
++use syntax::ast::HasName;
++
+use crate::{
- Expr::Closure { args, arg_types, ret_type, body } => {
++ expr::{Array, BindingAnnotation, ClosureKind, Literal, Movability, Statement},
+ pretty::{print_generic_args, print_path, print_type_ref},
+ type_ref::TypeRef,
+};
+
+use super::*;
+
+pub(super) fn print_body_hir(db: &dyn DefDatabase, body: &Body, owner: DefWithBodyId) -> String {
+ let needs_semi;
+ let header = match owner {
+ DefWithBodyId::FunctionId(it) => {
+ needs_semi = false;
+ let item_tree_id = it.lookup(db).id;
+ format!("fn {}(…) ", item_tree_id.item_tree(db)[item_tree_id.value].name)
+ }
+ DefWithBodyId::StaticId(it) => {
+ needs_semi = true;
+ let item_tree_id = it.lookup(db).id;
+ format!("static {} = ", item_tree_id.item_tree(db)[item_tree_id.value].name)
+ }
+ DefWithBodyId::ConstId(it) => {
+ needs_semi = true;
+ let item_tree_id = it.lookup(db).id;
+ let name = match &item_tree_id.item_tree(db)[item_tree_id.value].name {
+ Some(name) => name.to_string(),
+ None => "_".to_string(),
+ };
+ format!("const {} = ", name)
+ }
++ DefWithBodyId::VariantId(it) => {
++ needs_semi = false;
++ let src = it.parent.child_source(db);
++ let variant = &src.value[it.local_id];
++ let name = match &variant.name() {
++ Some(name) => name.to_string(),
++ None => "_".to_string(),
++ };
++ format!("{}", name)
++ }
+ };
+
+ let mut p = Printer { body, buf: header, indent_level: 0, needs_indent: false };
+ p.print_expr(body.body_expr);
+ if needs_semi {
+ p.buf.push(';');
+ }
+ p.buf
+}
+
+macro_rules! w {
+ ($dst:expr, $($arg:tt)*) => {
+ { let _ = write!($dst, $($arg)*); }
+ };
+}
+
+macro_rules! wln {
+ ($dst:expr) => {
+ { let _ = writeln!($dst); }
+ };
+ ($dst:expr, $($arg:tt)*) => {
+ { let _ = writeln!($dst, $($arg)*); }
+ };
+}
+
+struct Printer<'a> {
+ body: &'a Body,
+ buf: String,
+ indent_level: usize,
+ needs_indent: bool,
+}
+
+impl<'a> Write for Printer<'a> {
+ fn write_str(&mut self, s: &str) -> fmt::Result {
+ for line in s.split_inclusive('\n') {
+ if self.needs_indent {
+ match self.buf.chars().rev().skip_while(|ch| *ch == ' ').next() {
+ Some('\n') | None => {}
+ _ => self.buf.push('\n'),
+ }
+ self.buf.push_str(&" ".repeat(self.indent_level));
+ self.needs_indent = false;
+ }
+
+ self.buf.push_str(line);
+ self.needs_indent = line.ends_with('\n');
+ }
+
+ Ok(())
+ }
+}
+
+impl<'a> Printer<'a> {
+ fn indented(&mut self, f: impl FnOnce(&mut Self)) {
+ self.indent_level += 1;
+ wln!(self);
+ f(self);
+ self.indent_level -= 1;
+ self.buf = self.buf.trim_end_matches('\n').to_string();
+ }
+
+ fn whitespace(&mut self) {
+ match self.buf.chars().next_back() {
+ None | Some('\n' | ' ') => {}
+ _ => self.buf.push(' '),
+ }
+ }
+
+ fn newline(&mut self) {
+ match self.buf.chars().rev().skip_while(|ch| *ch == ' ').next() {
+ Some('\n') | None => {}
+ _ => writeln!(self).unwrap(),
+ }
+ }
+
+ fn print_expr(&mut self, expr: ExprId) {
+ let expr = &self.body[expr];
+
+ match expr {
+ Expr::Missing => w!(self, "�"),
+ Expr::Underscore => w!(self, "_"),
+ Expr::Path(path) => self.print_path(path),
+ Expr::If { condition, then_branch, else_branch } => {
+ w!(self, "if ");
+ self.print_expr(*condition);
+ w!(self, " ");
+ self.print_expr(*then_branch);
+ if let Some(els) = *else_branch {
+ w!(self, " else ");
+ self.print_expr(els);
+ }
+ }
+ Expr::Let { pat, expr } => {
+ w!(self, "let ");
+ self.print_pat(*pat);
+ w!(self, " = ");
+ self.print_expr(*expr);
+ }
+ Expr::Loop { body, label } => {
+ if let Some(lbl) = label {
+ w!(self, "{}: ", self.body[*lbl].name);
+ }
+ w!(self, "loop ");
+ self.print_expr(*body);
+ }
+ Expr::While { condition, body, label } => {
+ if let Some(lbl) = label {
+ w!(self, "{}: ", self.body[*lbl].name);
+ }
+ w!(self, "while ");
+ self.print_expr(*condition);
+ self.print_expr(*body);
+ }
+ Expr::For { iterable, pat, body, label } => {
+ if let Some(lbl) = label {
+ w!(self, "{}: ", self.body[*lbl].name);
+ }
+ w!(self, "for ");
+ self.print_pat(*pat);
+ w!(self, " in ");
+ self.print_expr(*iterable);
+ self.print_expr(*body);
+ }
+ Expr::Call { callee, args, is_assignee_expr: _ } => {
+ self.print_expr(*callee);
+ w!(self, "(");
+ if !args.is_empty() {
+ self.indented(|p| {
+ for arg in &**args {
+ p.print_expr(*arg);
+ wln!(p, ",");
+ }
+ });
+ }
+ w!(self, ")");
+ }
+ Expr::MethodCall { receiver, method_name, args, generic_args } => {
+ self.print_expr(*receiver);
+ w!(self, ".{}", method_name);
+ if let Some(args) = generic_args {
+ w!(self, "::<");
+ print_generic_args(args, self).unwrap();
+ w!(self, ">");
+ }
+ w!(self, "(");
+ if !args.is_empty() {
+ self.indented(|p| {
+ for arg in &**args {
+ p.print_expr(*arg);
+ wln!(p, ",");
+ }
+ });
+ }
+ w!(self, ")");
+ }
+ Expr::Match { expr, arms } => {
+ w!(self, "match ");
+ self.print_expr(*expr);
+ w!(self, " {{");
+ self.indented(|p| {
+ for arm in &**arms {
+ p.print_pat(arm.pat);
+ if let Some(guard) = arm.guard {
+ w!(p, " if ");
+ p.print_expr(guard);
+ }
+ w!(p, " => ");
+ p.print_expr(arm.expr);
+ wln!(p, ",");
+ }
+ });
+ wln!(self, "}}");
+ }
+ Expr::Continue { label } => {
+ w!(self, "continue");
+ if let Some(label) = label {
+ w!(self, " {}", label);
+ }
+ }
+ Expr::Break { expr, label } => {
+ w!(self, "break");
+ if let Some(label) = label {
+ w!(self, " {}", label);
+ }
+ if let Some(expr) = expr {
+ self.whitespace();
+ self.print_expr(*expr);
+ }
+ }
+ Expr::Return { expr } => {
+ w!(self, "return");
+ if let Some(expr) = expr {
+ self.whitespace();
+ self.print_expr(*expr);
+ }
+ }
+ Expr::Yield { expr } => {
+ w!(self, "yield");
+ if let Some(expr) = expr {
+ self.whitespace();
+ self.print_expr(*expr);
+ }
+ }
+ Expr::RecordLit { path, fields, spread, ellipsis, is_assignee_expr: _ } => {
+ match path {
+ Some(path) => self.print_path(path),
+ None => w!(self, "�"),
+ }
+
+ w!(self, "{{");
+ self.indented(|p| {
+ for field in &**fields {
+ w!(p, "{}: ", field.name);
+ p.print_expr(field.expr);
+ wln!(p, ",");
+ }
+ if let Some(spread) = spread {
+ w!(p, "..");
+ p.print_expr(*spread);
+ wln!(p);
+ }
+ if *ellipsis {
+ wln!(p, "..");
+ }
+ });
+ w!(self, "}}");
+ }
+ Expr::Field { expr, name } => {
+ self.print_expr(*expr);
+ w!(self, ".{}", name);
+ }
+ Expr::Await { expr } => {
+ self.print_expr(*expr);
+ w!(self, ".await");
+ }
+ Expr::Try { expr } => {
+ self.print_expr(*expr);
+ w!(self, "?");
+ }
+ Expr::TryBlock { body } => {
+ w!(self, "try ");
+ self.print_expr(*body);
+ }
+ Expr::Async { body } => {
+ w!(self, "async ");
+ self.print_expr(*body);
+ }
+ Expr::Const { body } => {
+ w!(self, "const ");
+ self.print_expr(*body);
+ }
+ Expr::Cast { expr, type_ref } => {
+ self.print_expr(*expr);
+ w!(self, " as ");
+ self.print_type_ref(type_ref);
+ }
+ Expr::Ref { expr, rawness, mutability } => {
+ w!(self, "&");
+ if rawness.is_raw() {
+ w!(self, "raw ");
+ }
+ if mutability.is_mut() {
+ w!(self, "mut ");
+ }
+ self.print_expr(*expr);
+ }
+ Expr::Box { expr } => {
+ w!(self, "box ");
+ self.print_expr(*expr);
+ }
+ Expr::UnaryOp { expr, op } => {
+ let op = match op {
+ ast::UnaryOp::Deref => "*",
+ ast::UnaryOp::Not => "!",
+ ast::UnaryOp::Neg => "-",
+ };
+ w!(self, "{}", op);
+ self.print_expr(*expr);
+ }
+ Expr::BinaryOp { lhs, rhs, op } => {
+ let (bra, ket) = match op {
+ None | Some(ast::BinaryOp::Assignment { .. }) => ("", ""),
+ _ => ("(", ")"),
+ };
+ w!(self, "{}", bra);
+ self.print_expr(*lhs);
+ w!(self, "{} ", ket);
+ match op {
+ Some(op) => w!(self, "{}", op),
+ None => w!(self, "�"), // :)
+ }
+ w!(self, " {}", bra);
+ self.print_expr(*rhs);
+ w!(self, "{}", ket);
+ }
+ Expr::Range { lhs, rhs, range_type } => {
+ if let Some(lhs) = lhs {
+ w!(self, "(");
+ self.print_expr(*lhs);
+ w!(self, ") ");
+ }
+ let range = match range_type {
+ ast::RangeOp::Exclusive => "..",
+ ast::RangeOp::Inclusive => "..=",
+ };
+ w!(self, "{}", range);
+ if let Some(rhs) = rhs {
+ w!(self, "(");
+ self.print_expr(*rhs);
+ w!(self, ") ");
+ }
+ }
+ Expr::Index { base, index } => {
+ self.print_expr(*base);
+ w!(self, "[");
+ self.print_expr(*index);
+ w!(self, "]");
+ }
++ Expr::Closure { args, arg_types, ret_type, body, closure_kind } => {
++ if let ClosureKind::Generator(Movability::Static) = closure_kind {
++ w!(self, "static ");
++ }
+ w!(self, "|");
+ for (i, (pat, ty)) in args.iter().zip(arg_types.iter()).enumerate() {
+ if i != 0 {
+ w!(self, ", ");
+ }
+ self.print_pat(*pat);
+ if let Some(ty) = ty {
+ w!(self, ": ");
+ self.print_type_ref(ty);
+ }
+ }
+ w!(self, "|");
+ if let Some(ret_ty) = ret_type {
+ w!(self, " -> ");
+ self.print_type_ref(ret_ty);
+ }
+ self.whitespace();
+ self.print_expr(*body);
+ }
+ Expr::Tuple { exprs, is_assignee_expr: _ } => {
+ w!(self, "(");
+ for expr in exprs.iter() {
+ self.print_expr(*expr);
+ w!(self, ", ");
+ }
+ w!(self, ")");
+ }
+ Expr::Unsafe { body } => {
+ w!(self, "unsafe ");
+ self.print_expr(*body);
+ }
+ Expr::Array(arr) => {
+ w!(self, "[");
+ if !matches!(arr, Array::ElementList { elements, .. } if elements.is_empty()) {
+ self.indented(|p| match arr {
+ Array::ElementList { elements, is_assignee_expr: _ } => {
+ for elem in elements.iter() {
+ p.print_expr(*elem);
+ w!(p, ", ");
+ }
+ }
+ Array::Repeat { initializer, repeat } => {
+ p.print_expr(*initializer);
+ w!(p, "; ");
+ p.print_expr(*repeat);
+ }
+ });
+ self.newline();
+ }
+ w!(self, "]");
+ }
+ Expr::Literal(lit) => self.print_literal(lit),
+ Expr::Block { id: _, statements, tail, label } => {
+ self.whitespace();
+ if let Some(lbl) = label {
+ w!(self, "{}: ", self.body[*lbl].name);
+ }
+ w!(self, "{{");
+ if !statements.is_empty() || tail.is_some() {
+ self.indented(|p| {
+ for stmt in &**statements {
+ p.print_stmt(stmt);
+ }
+ if let Some(tail) = tail {
+ p.print_expr(*tail);
+ }
+ p.newline();
+ });
+ }
+ w!(self, "}}");
+ }
+ }
+ }
+
+ fn print_pat(&mut self, pat: PatId) {
+ let pat = &self.body[pat];
+
+ match pat {
+ Pat::Missing => w!(self, "�"),
+ Pat::Wild => w!(self, "_"),
+ Pat::Tuple { args, ellipsis } => {
+ w!(self, "(");
+ for (i, pat) in args.iter().enumerate() {
+ if i != 0 {
+ w!(self, ", ");
+ }
+ if *ellipsis == Some(i) {
+ w!(self, ".., ");
+ }
+ self.print_pat(*pat);
+ }
+ w!(self, ")");
+ }
+ Pat::Or(pats) => {
+ for (i, pat) in pats.iter().enumerate() {
+ if i != 0 {
+ w!(self, " | ");
+ }
+ self.print_pat(*pat);
+ }
+ }
+ Pat::Record { path, args, ellipsis } => {
+ match path {
+ Some(path) => self.print_path(path),
+ None => w!(self, "�"),
+ }
+
+ w!(self, " {{");
+ self.indented(|p| {
+ for arg in args.iter() {
+ w!(p, "{}: ", arg.name);
+ p.print_pat(arg.pat);
+ wln!(p, ",");
+ }
+ if *ellipsis {
+ wln!(p, "..");
+ }
+ });
+ w!(self, "}}");
+ }
+ Pat::Range { start, end } => {
+ self.print_expr(*start);
+ w!(self, "...");
+ self.print_expr(*end);
+ }
+ Pat::Slice { prefix, slice, suffix } => {
+ w!(self, "[");
+ for pat in prefix.iter() {
+ self.print_pat(*pat);
+ w!(self, ", ");
+ }
+ if let Some(pat) = slice {
+ self.print_pat(*pat);
+ w!(self, ", ");
+ }
+ for pat in suffix.iter() {
+ self.print_pat(*pat);
+ w!(self, ", ");
+ }
+ w!(self, "]");
+ }
+ Pat::Path(path) => self.print_path(path),
+ Pat::Lit(expr) => self.print_expr(*expr),
+ Pat::Bind { mode, name, subpat } => {
+ let mode = match mode {
+ BindingAnnotation::Unannotated => "",
+ BindingAnnotation::Mutable => "mut ",
+ BindingAnnotation::Ref => "ref ",
+ BindingAnnotation::RefMut => "ref mut ",
+ };
+ w!(self, "{}{}", mode, name);
+ if let Some(pat) = subpat {
+ self.whitespace();
+ self.print_pat(*pat);
+ }
+ }
+ Pat::TupleStruct { path, args, ellipsis } => {
+ match path {
+ Some(path) => self.print_path(path),
+ None => w!(self, "�"),
+ }
+ w!(self, "(");
+ for (i, arg) in args.iter().enumerate() {
+ if i != 0 {
+ w!(self, ", ");
+ }
+ if *ellipsis == Some(i) {
+ w!(self, ", ..");
+ }
+ self.print_pat(*arg);
+ }
+ w!(self, ")");
+ }
+ Pat::Ref { pat, mutability } => {
+ w!(self, "&");
+ if mutability.is_mut() {
+ w!(self, "mut ");
+ }
+ self.print_pat(*pat);
+ }
+ Pat::Box { inner } => {
+ w!(self, "box ");
+ self.print_pat(*inner);
+ }
+ Pat::ConstBlock(c) => {
+ w!(self, "const ");
+ self.print_expr(*c);
+ }
+ }
+ }
+
+ fn print_stmt(&mut self, stmt: &Statement) {
+ match stmt {
+ Statement::Let { pat, type_ref, initializer, else_branch } => {
+ w!(self, "let ");
+ self.print_pat(*pat);
+ if let Some(ty) = type_ref {
+ w!(self, ": ");
+ self.print_type_ref(ty);
+ }
+ if let Some(init) = initializer {
+ w!(self, " = ");
+ self.print_expr(*init);
+ }
+ if let Some(els) = else_branch {
+ w!(self, " else ");
+ self.print_expr(*els);
+ }
+ wln!(self, ";");
+ }
+ Statement::Expr { expr, has_semi } => {
+ self.print_expr(*expr);
+ if *has_semi {
+ w!(self, ";");
+ }
+ wln!(self);
+ }
+ }
+ }
+
+ fn print_literal(&mut self, literal: &Literal) {
+ match literal {
+ Literal::String(it) => w!(self, "{:?}", it),
+ Literal::ByteString(it) => w!(self, "\"{}\"", it.escape_ascii()),
+ Literal::Char(it) => w!(self, "'{}'", it.escape_debug()),
+ Literal::Bool(it) => w!(self, "{}", it),
+ Literal::Int(i, suffix) => {
+ w!(self, "{}", i);
+ if let Some(suffix) = suffix {
+ w!(self, "{}", suffix);
+ }
+ }
+ Literal::Uint(i, suffix) => {
+ w!(self, "{}", i);
+ if let Some(suffix) = suffix {
+ w!(self, "{}", suffix);
+ }
+ }
+ Literal::Float(f, suffix) => {
+ w!(self, "{}", f);
+ if let Some(suffix) = suffix {
+ w!(self, "{}", suffix);
+ }
+ }
+ }
+ }
+
+ fn print_type_ref(&mut self, ty: &TypeRef) {
+ print_type_ref(ty, self).unwrap();
+ }
+
+ fn print_path(&mut self, path: &Path) {
+ print_path(path, self).unwrap();
+ }
+}
--- /dev/null
+//! When *constructing* `hir`, we start at some parent syntax node and recursively
+//! lower the children.
+//!
+//! This modules allows one to go in the opposite direction: start with a syntax
+//! node for a *child*, and get its hir.
+
+use either::Either;
+use hir_expand::HirFileId;
+use syntax::ast::HasDocComments;
+
+use crate::{
+ db::DefDatabase,
+ dyn_map::DynMap,
+ item_scope::ItemScope,
+ keys,
+ src::{HasChildSource, HasSource},
+ AdtId, AssocItemId, DefWithBodyId, EnumId, EnumVariantId, FieldId, ImplId, Lookup, MacroId,
+ ModuleDefId, ModuleId, TraitId, VariantId,
+};
+
+pub trait ChildBySource {
+ fn child_by_source(&self, db: &dyn DefDatabase, file_id: HirFileId) -> DynMap {
+ let mut res = DynMap::default();
+ self.child_by_source_to(db, &mut res, file_id);
+ res
+ }
+ fn child_by_source_to(&self, db: &dyn DefDatabase, map: &mut DynMap, file_id: HirFileId);
+}
+
+impl ChildBySource for TraitId {
+ fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId) {
+ let data = db.trait_data(*self);
+
+ data.attribute_calls().filter(|(ast_id, _)| ast_id.file_id == file_id).for_each(
+ |(ast_id, call_id)| {
+ res[keys::ATTR_MACRO_CALL].insert(ast_id.to_node(db.upcast()), call_id);
+ },
+ );
+ data.items.iter().for_each(|&(_, item)| {
+ add_assoc_item(db, res, file_id, item);
+ });
+ }
+}
+
+impl ChildBySource for ImplId {
+ fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId) {
+ let data = db.impl_data(*self);
+ data.attribute_calls().filter(|(ast_id, _)| ast_id.file_id == file_id).for_each(
+ |(ast_id, call_id)| {
+ res[keys::ATTR_MACRO_CALL].insert(ast_id.to_node(db.upcast()), call_id);
+ },
+ );
+ data.items.iter().for_each(|&item| {
+ add_assoc_item(db, res, file_id, item);
+ });
+ }
+}
+
+fn add_assoc_item(db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId, item: AssocItemId) {
+ match item {
+ AssocItemId::FunctionId(func) => {
+ let loc = func.lookup(db);
+ if loc.id.file_id() == file_id {
+ res[keys::FUNCTION].insert(loc.source(db).value, func)
+ }
+ }
+ AssocItemId::ConstId(konst) => {
+ let loc = konst.lookup(db);
+ if loc.id.file_id() == file_id {
+ res[keys::CONST].insert(loc.source(db).value, konst)
+ }
+ }
+ AssocItemId::TypeAliasId(ty) => {
+ let loc = ty.lookup(db);
+ if loc.id.file_id() == file_id {
+ res[keys::TYPE_ALIAS].insert(loc.source(db).value, ty)
+ }
+ }
+ }
+}
+
+impl ChildBySource for ModuleId {
+ fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId) {
+ let def_map = self.def_map(db);
+ let module_data = &def_map[self.local_id];
+ module_data.scope.child_by_source_to(db, res, file_id);
+ }
+}
+
+impl ChildBySource for ItemScope {
+ fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId) {
+ self.declarations().for_each(|item| add_module_def(db, res, file_id, item));
+ self.impls().for_each(|imp| add_impl(db, res, file_id, imp));
+ self.unnamed_consts().for_each(|konst| {
+ let loc = konst.lookup(db);
+ if loc.id.file_id() == file_id {
+ res[keys::CONST].insert(loc.source(db).value, konst);
+ }
+ });
+ self.attr_macro_invocs().filter(|(id, _)| id.file_id == file_id).for_each(
+ |(ast_id, call_id)| {
+ res[keys::ATTR_MACRO_CALL].insert(ast_id.to_node(db.upcast()), call_id);
+ },
+ );
+ self.legacy_macros().for_each(|(_, ids)| {
+ ids.iter().for_each(|&id| {
+ if let MacroId::MacroRulesId(id) = id {
+ let loc = id.lookup(db);
+ if loc.id.file_id() == file_id {
+ res[keys::MACRO_RULES].insert(loc.source(db).value, id);
+ }
+ }
+ })
+ });
+ self.derive_macro_invocs().filter(|(id, _)| id.file_id == file_id).for_each(
+ |(ast_id, calls)| {
+ let adt = ast_id.to_node(db.upcast());
+ calls.for_each(|(attr_id, call_id, calls)| {
+ if let Some(Either::Left(attr)) =
+ adt.doc_comments_and_attrs().nth(attr_id.ast_index as usize)
+ {
+ res[keys::DERIVE_MACRO_CALL].insert(attr, (attr_id, call_id, calls.into()));
+ }
+ });
+ },
+ );
+
+ fn add_module_def(
+ db: &dyn DefDatabase,
+ map: &mut DynMap,
+ file_id: HirFileId,
+ item: ModuleDefId,
+ ) {
+ macro_rules! insert {
+ ($map:ident[$key:path].$insert:ident($id:ident)) => {{
+ let loc = $id.lookup(db);
+ if loc.id.file_id() == file_id {
+ $map[$key].$insert(loc.source(db).value, $id)
+ }
+ }};
+ }
+ match item {
+ ModuleDefId::FunctionId(id) => insert!(map[keys::FUNCTION].insert(id)),
+ ModuleDefId::ConstId(id) => insert!(map[keys::CONST].insert(id)),
+ ModuleDefId::StaticId(id) => insert!(map[keys::STATIC].insert(id)),
+ ModuleDefId::TypeAliasId(id) => insert!(map[keys::TYPE_ALIAS].insert(id)),
+ ModuleDefId::TraitId(id) => insert!(map[keys::TRAIT].insert(id)),
+ ModuleDefId::AdtId(adt) => match adt {
+ AdtId::StructId(id) => insert!(map[keys::STRUCT].insert(id)),
+ AdtId::UnionId(id) => insert!(map[keys::UNION].insert(id)),
+ AdtId::EnumId(id) => insert!(map[keys::ENUM].insert(id)),
+ },
+ ModuleDefId::MacroId(id) => match id {
+ MacroId::Macro2Id(id) => insert!(map[keys::MACRO2].insert(id)),
+ MacroId::MacroRulesId(id) => insert!(map[keys::MACRO_RULES].insert(id)),
+ MacroId::ProcMacroId(id) => insert!(map[keys::PROC_MACRO].insert(id)),
+ },
+ ModuleDefId::ModuleId(_)
+ | ModuleDefId::EnumVariantId(_)
+ | ModuleDefId::BuiltinType(_) => (),
+ }
+ }
+ fn add_impl(db: &dyn DefDatabase, map: &mut DynMap, file_id: HirFileId, imp: ImplId) {
+ let loc = imp.lookup(db);
+ if loc.id.file_id() == file_id {
+ map[keys::IMPL].insert(loc.source(db).value, imp)
+ }
+ }
+ }
+}
+
+impl ChildBySource for VariantId {
+ fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, _: HirFileId) {
+ let arena_map = self.child_source(db);
+ let arena_map = arena_map.as_ref();
+ let parent = *self;
+ for (local_id, source) in arena_map.value.iter() {
+ let id = FieldId { parent, local_id };
+ match source.clone() {
+ Either::Left(source) => res[keys::TUPLE_FIELD].insert(source, id),
+ Either::Right(source) => res[keys::RECORD_FIELD].insert(source, id),
+ }
+ }
+ }
+}
+
+impl ChildBySource for EnumId {
+ fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, _: HirFileId) {
+ let arena_map = self.child_source(db);
+ let arena_map = arena_map.as_ref();
+ for (local_id, source) in arena_map.value.iter() {
+ let id = EnumVariantId { parent: *self, local_id };
+ res[keys::VARIANT].insert(source.clone(), id)
+ }
+ }
+}
+
+impl ChildBySource for DefWithBodyId {
+ fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId) {
+ let body = db.body(*self);
++ if let &DefWithBodyId::VariantId(v) = self {
++ VariantId::EnumVariantId(v).child_by_source_to(db, res, file_id)
++ }
++
+ for (_, def_map) in body.blocks(db) {
+ // All block expressions are merged into the same map, because they logically all add
+ // inner items to the containing `DefWithBodyId`.
+ def_map[def_map.root()].scope.child_by_source_to(db, res, file_id);
+ }
+ }
+}
--- /dev/null
- ) -> (Arc<TraitData>, Arc<Vec<DefDiagnostic>>) {
+//! Contains basic data about various HIR declarations.
+
+use std::sync::Arc;
+
+use hir_expand::{name::Name, AstId, ExpandResult, HirFileId, InFile, MacroCallId, MacroDefKind};
+use smallvec::SmallVec;
+use syntax::ast;
+
+use crate::{
+ attr::Attrs,
+ body::{Expander, Mark},
+ db::DefDatabase,
+ intern::Interned,
+ item_tree::{self, AssocItem, FnFlags, ItemTree, ItemTreeId, ModItem, Param, TreeId},
+ nameres::{
+ attr_resolution::ResolvedAttr, diagnostics::DefDiagnostic, proc_macro::ProcMacroKind,
+ DefMap,
+ },
+ type_ref::{TraitRef, TypeBound, TypeRef},
+ visibility::RawVisibility,
+ AssocItemId, AstIdWithPath, ConstId, ConstLoc, FunctionId, FunctionLoc, HasModule, ImplId,
+ Intern, ItemContainerId, ItemLoc, Lookup, Macro2Id, MacroRulesId, ModuleId, ProcMacroId,
+ StaticId, TraitId, TypeAliasId, TypeAliasLoc,
+};
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct FunctionData {
+ pub name: Name,
+ pub params: Vec<(Option<Name>, Interned<TypeRef>)>,
+ pub ret_type: Interned<TypeRef>,
+ pub async_ret_type: Option<Interned<TypeRef>>,
+ pub attrs: Attrs,
+ pub visibility: RawVisibility,
+ pub abi: Option<Interned<str>>,
+ pub legacy_const_generics_indices: Box<[u32]>,
+ flags: FnFlags,
+}
+
+impl FunctionData {
+ pub(crate) fn fn_data_query(db: &dyn DefDatabase, func: FunctionId) -> Arc<FunctionData> {
+ let loc = func.lookup(db);
+ let krate = loc.container.module(db).krate;
+ let crate_graph = db.crate_graph();
+ let cfg_options = &crate_graph[krate].cfg_options;
+ let item_tree = loc.id.item_tree(db);
+ let func = &item_tree[loc.id.value];
+ let visibility = if let ItemContainerId::TraitId(trait_id) = loc.container {
+ db.trait_data(trait_id).visibility.clone()
+ } else {
+ item_tree[func.visibility].clone()
+ };
+
+ let enabled_params = func
+ .params
+ .clone()
+ .filter(|¶m| item_tree.attrs(db, krate, param.into()).is_cfg_enabled(cfg_options));
+
+ // If last cfg-enabled param is a `...` param, it's a varargs function.
+ let is_varargs = enabled_params
+ .clone()
+ .next_back()
+ .map_or(false, |param| matches!(item_tree[param], Param::Varargs));
+
+ let mut flags = func.flags;
+ if is_varargs {
+ flags |= FnFlags::IS_VARARGS;
+ }
+ if flags.contains(FnFlags::HAS_SELF_PARAM) {
+ // If there's a self param in the syntax, but it is cfg'd out, remove the flag.
+ let is_cfgd_out = match func.params.clone().next() {
+ Some(param) => {
+ !item_tree.attrs(db, krate, param.into()).is_cfg_enabled(cfg_options)
+ }
+ None => {
+ stdx::never!("fn HAS_SELF_PARAM but no parameters allocated");
+ true
+ }
+ };
+ if is_cfgd_out {
+ cov_mark::hit!(cfgd_out_self_param);
+ flags.remove(FnFlags::HAS_SELF_PARAM);
+ }
+ }
+
+ let legacy_const_generics_indices = item_tree
+ .attrs(db, krate, ModItem::from(loc.id.value).into())
+ .by_key("rustc_legacy_const_generics")
+ .tt_values()
+ .next()
+ .map(parse_rustc_legacy_const_generics)
+ .unwrap_or_default();
+
+ Arc::new(FunctionData {
+ name: func.name.clone(),
+ params: enabled_params
+ .clone()
+ .filter_map(|id| match &item_tree[id] {
+ Param::Normal(name, ty) => Some((name.clone(), ty.clone())),
+ Param::Varargs => None,
+ })
+ .collect(),
+ ret_type: func.ret_type.clone(),
+ async_ret_type: func.async_ret_type.clone(),
+ attrs: item_tree.attrs(db, krate, ModItem::from(loc.id.value).into()),
+ visibility,
+ abi: func.abi.clone(),
+ legacy_const_generics_indices,
+ flags,
+ })
+ }
+
+ pub fn has_body(&self) -> bool {
+ self.flags.contains(FnFlags::HAS_BODY)
+ }
+
+ /// True if the first param is `self`. This is relevant to decide whether this
+ /// can be called as a method.
+ pub fn has_self_param(&self) -> bool {
+ self.flags.contains(FnFlags::HAS_SELF_PARAM)
+ }
+
+ pub fn has_default_kw(&self) -> bool {
+ self.flags.contains(FnFlags::HAS_DEFAULT_KW)
+ }
+
+ pub fn has_const_kw(&self) -> bool {
+ self.flags.contains(FnFlags::HAS_CONST_KW)
+ }
+
+ pub fn has_async_kw(&self) -> bool {
+ self.flags.contains(FnFlags::HAS_ASYNC_KW)
+ }
+
+ pub fn has_unsafe_kw(&self) -> bool {
+ self.flags.contains(FnFlags::HAS_UNSAFE_KW)
+ }
+
+ pub fn is_varargs(&self) -> bool {
+ self.flags.contains(FnFlags::IS_VARARGS)
+ }
+}
+
+fn parse_rustc_legacy_const_generics(tt: &tt::Subtree) -> Box<[u32]> {
+ let mut indices = Vec::new();
+ for args in tt.token_trees.chunks(2) {
+ match &args[0] {
+ tt::TokenTree::Leaf(tt::Leaf::Literal(lit)) => match lit.text.parse() {
+ Ok(index) => indices.push(index),
+ Err(_) => break,
+ },
+ _ => break,
+ }
+
+ if let Some(comma) = args.get(1) {
+ match comma {
+ tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if punct.char == ',' => {}
+ _ => break,
+ }
+ }
+ }
+
+ indices.into_boxed_slice()
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct TypeAliasData {
+ pub name: Name,
+ pub type_ref: Option<Interned<TypeRef>>,
+ pub visibility: RawVisibility,
+ pub is_extern: bool,
+ /// Bounds restricting the type alias itself (eg. `type Ty: Bound;` in a trait or impl).
+ pub bounds: Vec<Interned<TypeBound>>,
+}
+
+impl TypeAliasData {
+ pub(crate) fn type_alias_data_query(
+ db: &dyn DefDatabase,
+ typ: TypeAliasId,
+ ) -> Arc<TypeAliasData> {
+ let loc = typ.lookup(db);
+ let item_tree = loc.id.item_tree(db);
+ let typ = &item_tree[loc.id.value];
+ let visibility = if let ItemContainerId::TraitId(trait_id) = loc.container {
+ db.trait_data(trait_id).visibility.clone()
+ } else {
+ item_tree[typ.visibility].clone()
+ };
+
+ Arc::new(TypeAliasData {
+ name: typ.name.clone(),
+ type_ref: typ.type_ref.clone(),
+ visibility,
+ is_extern: matches!(loc.container, ItemContainerId::ExternBlockId(_)),
+ bounds: typ.bounds.to_vec(),
+ })
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct TraitData {
+ pub name: Name,
+ pub items: Vec<(Name, AssocItemId)>,
+ pub is_auto: bool,
+ pub is_unsafe: bool,
+ pub visibility: RawVisibility,
+ /// Whether the trait has `#[rust_skip_array_during_method_dispatch]`. `hir_ty` will ignore
+ /// method calls to this trait's methods when the receiver is an array and the crate edition is
+ /// 2015 or 2018.
+ pub skip_array_during_method_dispatch: bool,
+ // box it as the vec is usually empty anyways
+ pub attribute_calls: Option<Box<Vec<(AstId<ast::Item>, MacroCallId)>>>,
+}
+
+impl TraitData {
+ pub(crate) fn trait_data_query(db: &dyn DefDatabase, tr: TraitId) -> Arc<TraitData> {
+ db.trait_data_with_diagnostics(tr).0
+ }
+
+ pub(crate) fn trait_data_with_diagnostics_query(
+ db: &dyn DefDatabase,
+ tr: TraitId,
- Arc::new(diagnostics),
++ ) -> (Arc<TraitData>, Arc<[DefDiagnostic]>) {
+ let tr_loc @ ItemLoc { container: module_id, id: tree_id } = tr.lookup(db);
+ let item_tree = tree_id.item_tree(db);
+ let tr_def = &item_tree[tree_id.value];
+ let _cx = stdx::panic_context::enter(format!(
+ "trait_data_query({:?} -> {:?} -> {:?})",
+ tr, tr_loc, tr_def
+ ));
+ let name = tr_def.name.clone();
+ let is_auto = tr_def.is_auto;
+ let is_unsafe = tr_def.is_unsafe;
+ let visibility = item_tree[tr_def.visibility].clone();
+ let skip_array_during_method_dispatch = item_tree
+ .attrs(db, module_id.krate(), ModItem::from(tree_id.value).into())
+ .by_key("rustc_skip_array_during_method_dispatch")
+ .exists();
+
+ let mut collector =
+ AssocItemCollector::new(db, module_id, tree_id.file_id(), ItemContainerId::TraitId(tr));
+ collector.collect(&item_tree, tree_id.tree_id(), &tr_def.items);
+ let (items, attribute_calls, diagnostics) = collector.finish();
+
+ (
+ Arc::new(TraitData {
+ name,
+ attribute_calls,
+ items,
+ is_auto,
+ is_unsafe,
+ visibility,
+ skip_array_during_method_dispatch,
+ }),
- ) -> (Arc<ImplData>, Arc<Vec<DefDiagnostic>>) {
++ diagnostics.into(),
+ )
+ }
+
+ pub fn associated_types(&self) -> impl Iterator<Item = TypeAliasId> + '_ {
+ self.items.iter().filter_map(|(_name, item)| match item {
+ AssocItemId::TypeAliasId(t) => Some(*t),
+ _ => None,
+ })
+ }
+
+ pub fn associated_type_by_name(&self, name: &Name) -> Option<TypeAliasId> {
+ self.items.iter().find_map(|(item_name, item)| match item {
+ AssocItemId::TypeAliasId(t) if item_name == name => Some(*t),
+ _ => None,
+ })
+ }
+
+ pub fn method_by_name(&self, name: &Name) -> Option<FunctionId> {
+ self.items.iter().find_map(|(item_name, item)| match item {
+ AssocItemId::FunctionId(t) if item_name == name => Some(*t),
+ _ => None,
+ })
+ }
+
+ pub fn attribute_calls(&self) -> impl Iterator<Item = (AstId<ast::Item>, MacroCallId)> + '_ {
+ self.attribute_calls.iter().flat_map(|it| it.iter()).copied()
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct ImplData {
+ pub target_trait: Option<Interned<TraitRef>>,
+ pub self_ty: Interned<TypeRef>,
+ pub items: Vec<AssocItemId>,
+ pub is_negative: bool,
+ // box it as the vec is usually empty anyways
+ pub attribute_calls: Option<Box<Vec<(AstId<ast::Item>, MacroCallId)>>>,
+}
+
+impl ImplData {
+ pub(crate) fn impl_data_query(db: &dyn DefDatabase, id: ImplId) -> Arc<ImplData> {
+ db.impl_data_with_diagnostics(id).0
+ }
+
+ pub(crate) fn impl_data_with_diagnostics_query(
+ db: &dyn DefDatabase,
+ id: ImplId,
- Arc::new(diagnostics),
++ ) -> (Arc<ImplData>, Arc<[DefDiagnostic]>) {
+ let _p = profile::span("impl_data_with_diagnostics_query");
+ let ItemLoc { container: module_id, id: tree_id } = id.lookup(db);
+
+ let item_tree = tree_id.item_tree(db);
+ let impl_def = &item_tree[tree_id.value];
+ let target_trait = impl_def.target_trait.clone();
+ let self_ty = impl_def.self_ty.clone();
+ let is_negative = impl_def.is_negative;
+
+ let mut collector =
+ AssocItemCollector::new(db, module_id, tree_id.file_id(), ItemContainerId::ImplId(id));
+ collector.collect(&item_tree, tree_id.tree_id(), &impl_def.items);
+
+ let (items, attribute_calls, diagnostics) = collector.finish();
+ let items = items.into_iter().map(|(_, item)| item).collect();
+
+ (
+ Arc::new(ImplData { target_trait, self_ty, items, is_negative, attribute_calls }),
++ diagnostics.into(),
+ )
+ }
+
+ pub fn attribute_calls(&self) -> impl Iterator<Item = (AstId<ast::Item>, MacroCallId)> + '_ {
+ self.attribute_calls.iter().flat_map(|it| it.iter()).copied()
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct Macro2Data {
+ pub name: Name,
+ pub visibility: RawVisibility,
+}
+
+impl Macro2Data {
+ pub(crate) fn macro2_data_query(db: &dyn DefDatabase, makro: Macro2Id) -> Arc<Macro2Data> {
+ let loc = makro.lookup(db);
+ let item_tree = loc.id.item_tree(db);
+ let makro = &item_tree[loc.id.value];
+
+ Arc::new(Macro2Data {
+ name: makro.name.clone(),
+ visibility: item_tree[makro.visibility].clone(),
+ })
+ }
+}
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct MacroRulesData {
+ pub name: Name,
+ pub macro_export: bool,
+}
+
+impl MacroRulesData {
+ pub(crate) fn macro_rules_data_query(
+ db: &dyn DefDatabase,
+ makro: MacroRulesId,
+ ) -> Arc<MacroRulesData> {
+ let loc = makro.lookup(db);
+ let item_tree = loc.id.item_tree(db);
+ let makro = &item_tree[loc.id.value];
+
+ let macro_export = item_tree
+ .attrs(db, loc.container.krate(), ModItem::from(loc.id.value).into())
+ .by_key("macro_export")
+ .exists();
+
+ Arc::new(MacroRulesData { name: makro.name.clone(), macro_export })
+ }
+}
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct ProcMacroData {
+ pub name: Name,
+ /// Derive helpers, if this is a derive
+ pub helpers: Option<Box<[Name]>>,
+}
+
+impl ProcMacroData {
+ pub(crate) fn proc_macro_data_query(
+ db: &dyn DefDatabase,
+ makro: ProcMacroId,
+ ) -> Arc<ProcMacroData> {
+ let loc = makro.lookup(db);
+ let item_tree = loc.id.item_tree(db);
+ let makro = &item_tree[loc.id.value];
+
+ let (name, helpers) = if let Some(def) = item_tree
+ .attrs(db, loc.container.krate(), ModItem::from(loc.id.value).into())
+ .parse_proc_macro_decl(&makro.name)
+ {
+ (
+ def.name,
+ match def.kind {
+ ProcMacroKind::CustomDerive { helpers } => Some(helpers),
+ ProcMacroKind::FnLike | ProcMacroKind::Attr => None,
+ },
+ )
+ } else {
+ // eeeh...
+ stdx::never!("proc macro declaration is not a proc macro");
+ (makro.name.clone(), None)
+ };
+ Arc::new(ProcMacroData { name, helpers })
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct ConstData {
+ /// `None` for `const _: () = ();`
+ pub name: Option<Name>,
+ pub type_ref: Interned<TypeRef>,
+ pub visibility: RawVisibility,
+}
+
+impl ConstData {
+ pub(crate) fn const_data_query(db: &dyn DefDatabase, konst: ConstId) -> Arc<ConstData> {
+ let loc = konst.lookup(db);
+ let item_tree = loc.id.item_tree(db);
+ let konst = &item_tree[loc.id.value];
+ let visibility = if let ItemContainerId::TraitId(trait_id) = loc.container {
+ db.trait_data(trait_id).visibility.clone()
+ } else {
+ item_tree[konst.visibility].clone()
+ };
+
+ Arc::new(ConstData {
+ name: konst.name.clone(),
+ type_ref: konst.type_ref.clone(),
+ visibility,
+ })
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct StaticData {
+ pub name: Name,
+ pub type_ref: Interned<TypeRef>,
+ pub visibility: RawVisibility,
+ pub mutable: bool,
+ pub is_extern: bool,
+}
+
+impl StaticData {
+ pub(crate) fn static_data_query(db: &dyn DefDatabase, konst: StaticId) -> Arc<StaticData> {
+ let loc = konst.lookup(db);
+ let item_tree = loc.id.item_tree(db);
+ let statik = &item_tree[loc.id.value];
+
+ Arc::new(StaticData {
+ name: statik.name.clone(),
+ type_ref: statik.type_ref.clone(),
+ visibility: item_tree[statik.visibility].clone(),
+ mutable: statik.mutable,
+ is_extern: matches!(loc.container, ItemContainerId::ExternBlockId(_)),
+ })
+ }
+}
+
+struct AssocItemCollector<'a> {
+ db: &'a dyn DefDatabase,
+ module_id: ModuleId,
+ def_map: Arc<DefMap>,
+ inactive_diagnostics: Vec<DefDiagnostic>,
+ container: ItemContainerId,
+ expander: Expander,
+
+ items: Vec<(Name, AssocItemId)>,
+ attr_calls: Vec<(AstId<ast::Item>, MacroCallId)>,
+}
+
+impl<'a> AssocItemCollector<'a> {
+ fn new(
+ db: &'a dyn DefDatabase,
+ module_id: ModuleId,
+ file_id: HirFileId,
+ container: ItemContainerId,
+ ) -> Self {
+ Self {
+ db,
+ module_id,
+ def_map: module_id.def_map(db),
+ container,
+ expander: Expander::new(db, file_id, module_id),
+ items: Vec::new(),
+ attr_calls: Vec::new(),
+ inactive_diagnostics: Vec::new(),
+ }
+ }
+
+ fn finish(
+ self,
+ ) -> (
+ Vec<(Name, AssocItemId)>,
+ Option<Box<Vec<(AstId<ast::Item>, MacroCallId)>>>,
+ Vec<DefDiagnostic>,
+ ) {
+ (
+ self.items,
+ if self.attr_calls.is_empty() { None } else { Some(Box::new(self.attr_calls)) },
+ self.inactive_diagnostics,
+ )
+ }
+
+ // FIXME: proc-macro diagnostics
+ fn collect(&mut self, item_tree: &ItemTree, tree_id: TreeId, assoc_items: &[AssocItem]) {
+ let container = self.container;
+ self.items.reserve(assoc_items.len());
+
+ 'items: for &item in assoc_items {
+ let attrs = item_tree.attrs(self.db, self.module_id.krate, ModItem::from(item).into());
+ if !attrs.is_cfg_enabled(self.expander.cfg_options()) {
+ self.inactive_diagnostics.push(DefDiagnostic::unconfigured_code(
+ self.module_id.local_id,
+ InFile::new(self.expander.current_file_id(), item.ast_id(&item_tree).upcast()),
+ attrs.cfg().unwrap(),
+ self.expander.cfg_options().clone(),
+ ));
+ continue;
+ }
+
+ 'attrs: for attr in &*attrs {
+ let ast_id =
+ AstId::new(self.expander.current_file_id(), item.ast_id(&item_tree).upcast());
+ let ast_id_with_path = AstIdWithPath { path: (*attr.path).clone(), ast_id };
+
+ if let Ok(ResolvedAttr::Macro(call_id)) = self.def_map.resolve_attr_macro(
+ self.db,
+ self.module_id.local_id,
+ ast_id_with_path,
+ attr,
+ ) {
+ self.attr_calls.push((ast_id, call_id));
+ // If proc attribute macro expansion is disabled, skip expanding it here
+ if !self.db.enable_proc_attr_macros() {
+ continue 'attrs;
+ }
+ let loc = self.db.lookup_intern_macro_call(call_id);
+ if let MacroDefKind::ProcMacro(exp, ..) = loc.def.kind {
+ // If there's no expander for the proc macro (e.g. the
+ // proc macro is ignored, or building the proc macro
+ // crate failed), skip expansion like we would if it was
+ // disabled. This is analogous to the handling in
+ // `DefCollector::collect_macros`.
+ if exp.is_dummy() {
+ continue 'attrs;
+ }
+ }
+ match self.expander.enter_expand_id::<ast::MacroItems>(self.db, call_id) {
+ ExpandResult { value: Some((mark, _)), .. } => {
+ self.collect_macro_items(mark);
+ continue 'items;
+ }
+ ExpandResult { .. } => {}
+ }
+ }
+ }
+
+ match item {
+ AssocItem::Function(id) => {
+ let item = &item_tree[id];
+
+ let def =
+ FunctionLoc { container, id: ItemTreeId::new(tree_id, id) }.intern(self.db);
+ self.items.push((item.name.clone(), def.into()));
+ }
+ AssocItem::Const(id) => {
+ let item = &item_tree[id];
+
+ let name = match item.name.clone() {
+ Some(name) => name,
+ None => continue,
+ };
+ let def =
+ ConstLoc { container, id: ItemTreeId::new(tree_id, id) }.intern(self.db);
+ self.items.push((name, def.into()));
+ }
+ AssocItem::TypeAlias(id) => {
+ let item = &item_tree[id];
+
+ let def = TypeAliasLoc { container, id: ItemTreeId::new(tree_id, id) }
+ .intern(self.db);
+ self.items.push((item.name.clone(), def.into()));
+ }
+ AssocItem::MacroCall(call) => {
+ if let Some(root) = self.db.parse_or_expand(self.expander.current_file_id()) {
+ let call = &item_tree[call];
+
+ let ast_id_map = self.db.ast_id_map(self.expander.current_file_id());
+ let call = ast_id_map.get(call.ast_id).to_node(&root);
+ let _cx = stdx::panic_context::enter(format!(
+ "collect_items MacroCall: {}",
+ call
+ ));
+ let res = self.expander.enter_expand::<ast::MacroItems>(self.db, call);
+
+ if let Ok(ExpandResult { value: Some((mark, _)), .. }) = res {
+ self.collect_macro_items(mark);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ fn collect_macro_items(&mut self, mark: Mark) {
+ let tree_id = item_tree::TreeId::new(self.expander.current_file_id(), None);
+ let item_tree = tree_id.item_tree(self.db);
+ let iter: SmallVec<[_; 2]> =
+ item_tree.top_level_items().iter().filter_map(ModItem::as_assoc_item).collect();
+
+ self.collect(&item_tree, tree_id, &iter);
+
+ self.expander.exit(self.db, mark);
+ }
+}
--- /dev/null
- fn impl_data_with_diagnostics(&self, e: ImplId) -> (Arc<ImplData>, Arc<Vec<DefDiagnostic>>);
+//! Defines database & queries for name resolution.
+use std::sync::Arc;
+
+use base_db::{salsa, CrateId, SourceDatabase, Upcast};
+use either::Either;
+use hir_expand::{db::AstDatabase, HirFileId};
+use la_arena::ArenaMap;
+use syntax::{ast, AstPtr, SmolStr};
+
+use crate::{
+ adt::{EnumData, StructData},
+ attr::{Attrs, AttrsWithOwner},
+ body::{scope::ExprScopes, Body, BodySourceMap},
+ data::{
+ ConstData, FunctionData, ImplData, Macro2Data, MacroRulesData, ProcMacroData, StaticData,
+ TraitData, TypeAliasData,
+ },
+ generics::GenericParams,
+ import_map::ImportMap,
+ intern::Interned,
+ item_tree::{AttrOwner, ItemTree},
+ lang_item::{LangItemTarget, LangItems},
+ nameres::{diagnostics::DefDiagnostic, DefMap},
+ visibility::{self, Visibility},
+ AttrDefId, BlockId, BlockLoc, ConstId, ConstLoc, DefWithBodyId, EnumId, EnumLoc, ExternBlockId,
+ ExternBlockLoc, FunctionId, FunctionLoc, GenericDefId, ImplId, ImplLoc, LocalEnumVariantId,
+ LocalFieldId, Macro2Id, Macro2Loc, MacroRulesId, MacroRulesLoc, ProcMacroId, ProcMacroLoc,
+ StaticId, StaticLoc, StructId, StructLoc, TraitId, TraitLoc, TypeAliasId, TypeAliasLoc,
+ UnionId, UnionLoc, VariantId,
+};
+
+#[salsa::query_group(InternDatabaseStorage)]
+pub trait InternDatabase: SourceDatabase {
+ #[salsa::interned]
+ fn intern_function(&self, loc: FunctionLoc) -> FunctionId;
+ #[salsa::interned]
+ fn intern_struct(&self, loc: StructLoc) -> StructId;
+ #[salsa::interned]
+ fn intern_union(&self, loc: UnionLoc) -> UnionId;
+ #[salsa::interned]
+ fn intern_enum(&self, loc: EnumLoc) -> EnumId;
+ #[salsa::interned]
+ fn intern_const(&self, loc: ConstLoc) -> ConstId;
+ #[salsa::interned]
+ fn intern_static(&self, loc: StaticLoc) -> StaticId;
+ #[salsa::interned]
+ fn intern_trait(&self, loc: TraitLoc) -> TraitId;
+ #[salsa::interned]
+ fn intern_type_alias(&self, loc: TypeAliasLoc) -> TypeAliasId;
+ #[salsa::interned]
+ fn intern_impl(&self, loc: ImplLoc) -> ImplId;
+ #[salsa::interned]
+ fn intern_extern_block(&self, loc: ExternBlockLoc) -> ExternBlockId;
+ #[salsa::interned]
+ fn intern_block(&self, loc: BlockLoc) -> BlockId;
+ #[salsa::interned]
+ fn intern_macro2(&self, loc: Macro2Loc) -> Macro2Id;
+ #[salsa::interned]
+ fn intern_proc_macro(&self, loc: ProcMacroLoc) -> ProcMacroId;
+ #[salsa::interned]
+ fn intern_macro_rules(&self, loc: MacroRulesLoc) -> MacroRulesId;
+}
+
+#[salsa::query_group(DefDatabaseStorage)]
+pub trait DefDatabase: InternDatabase + AstDatabase + Upcast<dyn AstDatabase> {
+ #[salsa::input]
+ fn enable_proc_attr_macros(&self) -> bool;
+
+ #[salsa::invoke(ItemTree::file_item_tree_query)]
+ fn file_item_tree(&self, file_id: HirFileId) -> Arc<ItemTree>;
+
+ #[salsa::invoke(crate_def_map_wait)]
+ #[salsa::transparent]
+ fn crate_def_map(&self, krate: CrateId) -> Arc<DefMap>;
+
+ #[salsa::invoke(DefMap::crate_def_map_query)]
+ fn crate_def_map_query(&self, krate: CrateId) -> Arc<DefMap>;
+
+ /// Computes the block-level `DefMap`, returning `None` when `block` doesn't contain any inner
+ /// items directly.
+ ///
+ /// For example:
+ ///
+ /// ```
+ /// fn f() { // (0)
+ /// { // (1)
+ /// fn inner() {}
+ /// }
+ /// }
+ /// ```
+ ///
+ /// The `block_def_map` for block 0 would return `None`, while `block_def_map` of block 1 would
+ /// return a `DefMap` containing `inner`.
+ #[salsa::invoke(DefMap::block_def_map_query)]
+ fn block_def_map(&self, block: BlockId) -> Option<Arc<DefMap>>;
+
+ #[salsa::invoke(StructData::struct_data_query)]
+ fn struct_data(&self, id: StructId) -> Arc<StructData>;
+
++ #[salsa::invoke(StructData::struct_data_with_diagnostics_query)]
++ fn struct_data_with_diagnostics(&self, id: StructId)
++ -> (Arc<StructData>, Arc<[DefDiagnostic]>);
++
+ #[salsa::invoke(StructData::union_data_query)]
+ fn union_data(&self, id: UnionId) -> Arc<StructData>;
+
++ #[salsa::invoke(StructData::union_data_with_diagnostics_query)]
++ fn union_data_with_diagnostics(&self, id: UnionId) -> (Arc<StructData>, Arc<[DefDiagnostic]>);
++
+ #[salsa::invoke(EnumData::enum_data_query)]
+ fn enum_data(&self, e: EnumId) -> Arc<EnumData>;
+
++ #[salsa::invoke(EnumData::enum_data_with_diagnostics_query)]
++ fn enum_data_with_diagnostics(&self, e: EnumId) -> (Arc<EnumData>, Arc<[DefDiagnostic]>);
++
+ #[salsa::invoke(ImplData::impl_data_query)]
+ fn impl_data(&self, e: ImplId) -> Arc<ImplData>;
+
+ #[salsa::invoke(ImplData::impl_data_with_diagnostics_query)]
- fn trait_data_with_diagnostics(&self, tr: TraitId)
- -> (Arc<TraitData>, Arc<Vec<DefDiagnostic>>);
++ fn impl_data_with_diagnostics(&self, e: ImplId) -> (Arc<ImplData>, Arc<[DefDiagnostic]>);
+
+ #[salsa::invoke(TraitData::trait_data_query)]
+ fn trait_data(&self, e: TraitId) -> Arc<TraitData>;
+
+ #[salsa::invoke(TraitData::trait_data_with_diagnostics_query)]
++ fn trait_data_with_diagnostics(&self, tr: TraitId) -> (Arc<TraitData>, Arc<[DefDiagnostic]>);
+
+ #[salsa::invoke(TypeAliasData::type_alias_data_query)]
+ fn type_alias_data(&self, e: TypeAliasId) -> Arc<TypeAliasData>;
+
+ #[salsa::invoke(FunctionData::fn_data_query)]
+ fn function_data(&self, func: FunctionId) -> Arc<FunctionData>;
+
+ #[salsa::invoke(ConstData::const_data_query)]
+ fn const_data(&self, konst: ConstId) -> Arc<ConstData>;
+
+ #[salsa::invoke(StaticData::static_data_query)]
+ fn static_data(&self, konst: StaticId) -> Arc<StaticData>;
+
+ #[salsa::invoke(Macro2Data::macro2_data_query)]
+ fn macro2_data(&self, makro: Macro2Id) -> Arc<Macro2Data>;
+
+ #[salsa::invoke(MacroRulesData::macro_rules_data_query)]
+ fn macro_rules_data(&self, makro: MacroRulesId) -> Arc<MacroRulesData>;
+
+ #[salsa::invoke(ProcMacroData::proc_macro_data_query)]
+ fn proc_macro_data(&self, makro: ProcMacroId) -> Arc<ProcMacroData>;
+
+ #[salsa::invoke(Body::body_with_source_map_query)]
+ fn body_with_source_map(&self, def: DefWithBodyId) -> (Arc<Body>, Arc<BodySourceMap>);
+
+ #[salsa::invoke(Body::body_query)]
+ fn body(&self, def: DefWithBodyId) -> Arc<Body>;
+
+ #[salsa::invoke(ExprScopes::expr_scopes_query)]
+ fn expr_scopes(&self, def: DefWithBodyId) -> Arc<ExprScopes>;
+
+ #[salsa::invoke(GenericParams::generic_params_query)]
+ fn generic_params(&self, def: GenericDefId) -> Interned<GenericParams>;
+
+ #[salsa::invoke(Attrs::variants_attrs_query)]
+ fn variants_attrs(&self, def: EnumId) -> Arc<ArenaMap<LocalEnumVariantId, Attrs>>;
+
+ #[salsa::invoke(Attrs::fields_attrs_query)]
+ fn fields_attrs(&self, def: VariantId) -> Arc<ArenaMap<LocalFieldId, Attrs>>;
+
+ #[salsa::invoke(crate::attr::variants_attrs_source_map)]
+ fn variants_attrs_source_map(
+ &self,
+ def: EnumId,
+ ) -> Arc<ArenaMap<LocalEnumVariantId, AstPtr<ast::Variant>>>;
+
+ #[salsa::invoke(crate::attr::fields_attrs_source_map)]
+ fn fields_attrs_source_map(
+ &self,
+ def: VariantId,
+ ) -> Arc<ArenaMap<LocalFieldId, Either<AstPtr<ast::TupleField>, AstPtr<ast::RecordField>>>>;
+
+ #[salsa::invoke(AttrsWithOwner::attrs_query)]
+ fn attrs(&self, def: AttrDefId) -> AttrsWithOwner;
+
+ #[salsa::invoke(LangItems::crate_lang_items_query)]
+ fn crate_lang_items(&self, krate: CrateId) -> Arc<LangItems>;
+
+ #[salsa::invoke(LangItems::lang_item_query)]
+ fn lang_item(&self, start_crate: CrateId, item: SmolStr) -> Option<LangItemTarget>;
+
+ #[salsa::invoke(ImportMap::import_map_query)]
+ fn import_map(&self, krate: CrateId) -> Arc<ImportMap>;
+
+ #[salsa::invoke(visibility::field_visibilities_query)]
+ fn field_visibilities(&self, var: VariantId) -> Arc<ArenaMap<LocalFieldId, Visibility>>;
+
+ // FIXME: unify function_visibility and const_visibility?
+ #[salsa::invoke(visibility::function_visibility_query)]
+ fn function_visibility(&self, def: FunctionId) -> Visibility;
+
+ #[salsa::invoke(visibility::const_visibility_query)]
+ fn const_visibility(&self, def: ConstId) -> Visibility;
+
+ #[salsa::transparent]
+ fn crate_limits(&self, crate_id: CrateId) -> CrateLimits;
+
+ fn crate_supports_no_std(&self, crate_id: CrateId) -> bool;
+}
+
+fn crate_def_map_wait(db: &dyn DefDatabase, krate: CrateId) -> Arc<DefMap> {
+ let _p = profile::span("crate_def_map:wait");
+ db.crate_def_map_query(krate)
+}
+
+pub struct CrateLimits {
+ /// The maximum depth for potentially infinitely-recursive compile-time operations like macro expansion or auto-dereference.
+ pub recursion_limit: u32,
+}
+
+fn crate_limits(db: &dyn DefDatabase, crate_id: CrateId) -> CrateLimits {
+ let def_map = db.crate_def_map(crate_id);
+
+ CrateLimits {
+ // 128 is the default in rustc.
+ recursion_limit: def_map.recursion_limit().unwrap_or(128),
+ }
+}
+
+fn crate_supports_no_std(db: &dyn DefDatabase, crate_id: CrateId) -> bool {
+ let file = db.crate_graph()[crate_id].root_file_id;
+ let item_tree = db.file_item_tree(file.into());
+ let attrs = item_tree.raw_attrs(AttrOwner::TopLevel);
+ for attr in &**attrs {
+ match attr.path().as_ident().and_then(|id| id.as_text()) {
+ Some(ident) if ident == "no_std" => return true,
+ Some(ident) if ident == "cfg_attr" => {}
+ _ => continue,
+ }
+
+ // This is a `cfg_attr`; check if it could possibly expand to `no_std`.
+ // Syntax is: `#[cfg_attr(condition(cfg, style), attr0, attr1, <...>)]`
+ let tt = match attr.token_tree_value() {
+ Some(tt) => &tt.token_trees,
+ None => continue,
+ };
+
+ let segments = tt.split(|tt| match tt {
+ tt::TokenTree::Leaf(tt::Leaf::Punct(p)) if p.char == ',' => true,
+ _ => false,
+ });
+ for output in segments.skip(1) {
+ match output {
+ [tt::TokenTree::Leaf(tt::Leaf::Ident(ident))] if ident.text == "no_std" => {
+ return true
+ }
+ _ => {}
+ }
+ }
+ }
+
+ false
+}
--- /dev/null
+//! This module describes hir-level representation of expressions.
+//!
+//! This representation is:
+//!
+//! 1. Identity-based. Each expression has an `id`, so we can distinguish
+//! between different `1` in `1 + 1`.
+//! 2. Independent of syntax. Though syntactic provenance information can be
+//! attached separately via id-based side map.
+//! 3. Unresolved. Paths are stored as sequences of names, and not as defs the
+//! names refer to.
+//! 4. Desugared. There's no `if let`.
+//!
+//! See also a neighboring `body` module.
+
+use std::fmt;
+
+use hir_expand::name::Name;
+use la_arena::{Idx, RawIdx};
+
+use crate::{
+ builtin_type::{BuiltinFloat, BuiltinInt, BuiltinUint},
+ intern::Interned,
+ path::{GenericArgs, Path},
+ type_ref::{Mutability, Rawness, TypeRef},
+ BlockId,
+};
+
+pub use syntax::ast::{ArithOp, BinaryOp, CmpOp, LogicOp, Ordering, RangeOp, UnaryOp};
+
+pub type ExprId = Idx<Expr>;
+
+/// FIXME: this is a hacky function which should be removed
+pub(crate) fn dummy_expr_id() -> ExprId {
+ ExprId::from_raw(RawIdx::from(u32::MAX))
+}
+
+pub type PatId = Idx<Pat>;
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct Label {
+ pub name: Name,
+}
+pub type LabelId = Idx<Label>;
+
+// We convert float values into bits and that's how we don't need to deal with f32 and f64.
+// For PartialEq, bits comparison should work, as ordering is not important
+// https://github.com/rust-lang/rust-analyzer/issues/12380#issuecomment-1137284360
+#[derive(Default, Debug, Clone, Eq, PartialEq)]
+pub struct FloatTypeWrapper(u64);
+
+impl FloatTypeWrapper {
+ pub fn new(value: f64) -> Self {
+ Self(value.to_bits())
+ }
+}
+
+impl fmt::Display for FloatTypeWrapper {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{:?}", f64::from_bits(self.0))
+ }
+}
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub enum Literal {
+ String(Box<str>),
+ ByteString(Box<[u8]>),
+ Char(char),
+ Bool(bool),
+ Int(i128, Option<BuiltinInt>),
+ Uint(u128, Option<BuiltinUint>),
+ // Here we are using a wrapper around float because f32 and f64 do not implement Eq, so they
+ // could not be used directly here, to understand how the wrapper works go to definition of
+ // FloatTypeWrapper
+ Float(FloatTypeWrapper, Option<BuiltinFloat>),
+}
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub enum Expr {
+ /// This is produced if the syntax tree does not have a required expression piece.
+ Missing,
+ Path(Path),
+ If {
+ condition: ExprId,
+ then_branch: ExprId,
+ else_branch: Option<ExprId>,
+ },
+ Let {
+ pat: PatId,
+ expr: ExprId,
+ },
+ Block {
+ id: BlockId,
+ statements: Box<[Statement]>,
+ tail: Option<ExprId>,
+ label: Option<LabelId>,
+ },
+ Loop {
+ body: ExprId,
+ label: Option<LabelId>,
+ },
+ While {
+ condition: ExprId,
+ body: ExprId,
+ label: Option<LabelId>,
+ },
+ For {
+ iterable: ExprId,
+ pat: PatId,
+ body: ExprId,
+ label: Option<LabelId>,
+ },
+ Call {
+ callee: ExprId,
+ args: Box<[ExprId]>,
+ is_assignee_expr: bool,
+ },
+ MethodCall {
+ receiver: ExprId,
+ method_name: Name,
+ args: Box<[ExprId]>,
+ generic_args: Option<Box<GenericArgs>>,
+ },
+ Match {
+ expr: ExprId,
+ arms: Box<[MatchArm]>,
+ },
+ Continue {
+ label: Option<Name>,
+ },
+ Break {
+ expr: Option<ExprId>,
+ label: Option<Name>,
+ },
+ Return {
+ expr: Option<ExprId>,
+ },
+ Yield {
+ expr: Option<ExprId>,
+ },
+ RecordLit {
+ path: Option<Box<Path>>,
+ fields: Box<[RecordLitField]>,
+ spread: Option<ExprId>,
+ ellipsis: bool,
+ is_assignee_expr: bool,
+ },
+ Field {
+ expr: ExprId,
+ name: Name,
+ },
+ Await {
+ expr: ExprId,
+ },
+ Try {
+ expr: ExprId,
+ },
+ TryBlock {
+ body: ExprId,
+ },
+ Async {
+ body: ExprId,
+ },
+ Const {
+ body: ExprId,
+ },
+ Cast {
+ expr: ExprId,
+ type_ref: Interned<TypeRef>,
+ },
+ Ref {
+ expr: ExprId,
+ rawness: Rawness,
+ mutability: Mutability,
+ },
+ Box {
+ expr: ExprId,
+ },
+ UnaryOp {
+ expr: ExprId,
+ op: UnaryOp,
+ },
+ BinaryOp {
+ lhs: ExprId,
+ rhs: ExprId,
+ op: Option<BinaryOp>,
+ },
+ Range {
+ lhs: Option<ExprId>,
+ rhs: Option<ExprId>,
+ range_type: RangeOp,
+ },
+ Index {
+ base: ExprId,
+ index: ExprId,
+ },
+ Closure {
+ args: Box<[PatId]>,
+ arg_types: Box<[Option<Interned<TypeRef>>]>,
+ ret_type: Option<Interned<TypeRef>>,
+ body: ExprId,
++ closure_kind: ClosureKind,
+ },
+ Tuple {
+ exprs: Box<[ExprId]>,
+ is_assignee_expr: bool,
+ },
+ Unsafe {
+ body: ExprId,
+ },
+ Array(Array),
+ Literal(Literal),
+ Underscore,
+}
+
++#[derive(Debug, Clone, Copy, PartialEq, Eq)]
++pub enum ClosureKind {
++ Closure,
++ Generator(Movability),
++}
++
++#[derive(Debug, Clone, Copy, PartialEq, Eq)]
++pub enum Movability {
++ Static,
++ Movable,
++}
++
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub enum Array {
+ ElementList { elements: Box<[ExprId]>, is_assignee_expr: bool },
+ Repeat { initializer: ExprId, repeat: ExprId },
+}
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct MatchArm {
+ pub pat: PatId,
+ pub guard: Option<ExprId>,
+ pub expr: ExprId,
+}
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct RecordLitField {
+ pub name: Name,
+ pub expr: ExprId,
+}
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub enum Statement {
+ Let {
+ pat: PatId,
+ type_ref: Option<Interned<TypeRef>>,
+ initializer: Option<ExprId>,
+ else_branch: Option<ExprId>,
+ },
+ Expr {
+ expr: ExprId,
+ has_semi: bool,
+ },
+}
+
+impl Expr {
+ pub fn walk_child_exprs(&self, mut f: impl FnMut(ExprId)) {
+ match self {
+ Expr::Missing => {}
+ Expr::Path(_) => {}
+ Expr::If { condition, then_branch, else_branch } => {
+ f(*condition);
+ f(*then_branch);
+ if let &Some(else_branch) = else_branch {
+ f(else_branch);
+ }
+ }
+ Expr::Let { expr, .. } => {
+ f(*expr);
+ }
+ Expr::Block { statements, tail, .. } => {
+ for stmt in statements.iter() {
+ match stmt {
+ Statement::Let { initializer, .. } => {
+ if let &Some(expr) = initializer {
+ f(expr);
+ }
+ }
+ Statement::Expr { expr: expression, .. } => f(*expression),
+ }
+ }
+ if let &Some(expr) = tail {
+ f(expr);
+ }
+ }
+ Expr::TryBlock { body }
+ | Expr::Unsafe { body }
+ | Expr::Async { body }
+ | Expr::Const { body } => f(*body),
+ Expr::Loop { body, .. } => f(*body),
+ Expr::While { condition, body, .. } => {
+ f(*condition);
+ f(*body);
+ }
+ Expr::For { iterable, body, .. } => {
+ f(*iterable);
+ f(*body);
+ }
+ Expr::Call { callee, args, .. } => {
+ f(*callee);
+ args.iter().copied().for_each(f);
+ }
+ Expr::MethodCall { receiver, args, .. } => {
+ f(*receiver);
+ args.iter().copied().for_each(f);
+ }
+ Expr::Match { expr, arms } => {
+ f(*expr);
+ arms.iter().map(|arm| arm.expr).for_each(f);
+ }
+ Expr::Continue { .. } => {}
+ Expr::Break { expr, .. } | Expr::Return { expr } | Expr::Yield { expr } => {
+ if let &Some(expr) = expr {
+ f(expr);
+ }
+ }
+ Expr::RecordLit { fields, spread, .. } => {
+ for field in fields.iter() {
+ f(field.expr);
+ }
+ if let &Some(expr) = spread {
+ f(expr);
+ }
+ }
+ Expr::Closure { body, .. } => {
+ f(*body);
+ }
+ Expr::BinaryOp { lhs, rhs, .. } => {
+ f(*lhs);
+ f(*rhs);
+ }
+ Expr::Range { lhs, rhs, .. } => {
+ if let &Some(lhs) = rhs {
+ f(lhs);
+ }
+ if let &Some(rhs) = lhs {
+ f(rhs);
+ }
+ }
+ Expr::Index { base, index } => {
+ f(*base);
+ f(*index);
+ }
+ Expr::Field { expr, .. }
+ | Expr::Await { expr }
+ | Expr::Try { expr }
+ | Expr::Cast { expr, .. }
+ | Expr::Ref { expr, .. }
+ | Expr::UnaryOp { expr, .. }
+ | Expr::Box { expr } => {
+ f(*expr);
+ }
+ Expr::Tuple { exprs, .. } => exprs.iter().copied().for_each(f),
+ Expr::Array(a) => match a {
+ Array::ElementList { elements, .. } => elements.iter().copied().for_each(f),
+ Array::Repeat { initializer, repeat } => {
+ f(*initializer);
+ f(*repeat)
+ }
+ },
+ Expr::Literal(_) => {}
+ Expr::Underscore => {}
+ }
+ }
+}
+
+/// Explicit binding annotations given in the HIR for a binding. Note
+/// that this is not the final binding *mode* that we infer after type
+/// inference.
+#[derive(Clone, PartialEq, Eq, Debug, Copy)]
+pub enum BindingAnnotation {
+ /// No binding annotation given: this means that the final binding mode
+ /// will depend on whether we have skipped through a `&` reference
+ /// when matching. For example, the `x` in `Some(x)` will have binding
+ /// mode `None`; if you do `let Some(x) = &Some(22)`, it will
+ /// ultimately be inferred to be by-reference.
+ Unannotated,
+
+ /// Annotated with `mut x` -- could be either ref or not, similar to `None`.
+ Mutable,
+
+ /// Annotated as `ref`, like `ref x`
+ Ref,
+
+ /// Annotated as `ref mut x`.
+ RefMut,
+}
+
+impl BindingAnnotation {
+ pub fn new(is_mutable: bool, is_ref: bool) -> Self {
+ match (is_mutable, is_ref) {
+ (true, true) => BindingAnnotation::RefMut,
+ (false, true) => BindingAnnotation::Ref,
+ (true, false) => BindingAnnotation::Mutable,
+ (false, false) => BindingAnnotation::Unannotated,
+ }
+ }
+}
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct RecordFieldPat {
+ pub name: Name,
+ pub pat: PatId,
+}
+
+/// Close relative to rustc's hir::PatKind
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub enum Pat {
+ Missing,
+ Wild,
+ Tuple { args: Box<[PatId]>, ellipsis: Option<usize> },
+ Or(Box<[PatId]>),
+ Record { path: Option<Box<Path>>, args: Box<[RecordFieldPat]>, ellipsis: bool },
+ Range { start: ExprId, end: ExprId },
+ Slice { prefix: Box<[PatId]>, slice: Option<PatId>, suffix: Box<[PatId]> },
+ Path(Box<Path>),
+ Lit(ExprId),
+ Bind { mode: BindingAnnotation, name: Name, subpat: Option<PatId> },
+ TupleStruct { path: Option<Box<Path>>, args: Box<[PatId]>, ellipsis: Option<usize> },
+ Ref { pat: PatId, mutability: Mutability },
+ Box { inner: PatId },
+ ConstBlock(ExprId),
+}
+
+impl Pat {
+ pub fn walk_child_pats(&self, mut f: impl FnMut(PatId)) {
+ match self {
+ Pat::Range { .. }
+ | Pat::Lit(..)
+ | Pat::Path(..)
+ | Pat::ConstBlock(..)
+ | Pat::Wild
+ | Pat::Missing => {}
+ Pat::Bind { subpat, .. } => {
+ subpat.iter().copied().for_each(f);
+ }
+ Pat::Or(args) | Pat::Tuple { args, .. } | Pat::TupleStruct { args, .. } => {
+ args.iter().copied().for_each(f);
+ }
+ Pat::Ref { pat, .. } => f(*pat),
+ Pat::Slice { prefix, slice, suffix } => {
+ let total_iter = prefix.iter().chain(slice.iter()).chain(suffix.iter());
+ total_iter.copied().for_each(f);
+ }
+ Pat::Record { args, .. } => {
+ args.iter().map(|f| f.pat).for_each(f);
+ }
+ Pat::Box { inner } => f(*inner),
+ }
+ }
+}
--- /dev/null
- from,
+//! An algorithm to find a path to refer to a certain item.
+
+use std::{cmp::Ordering, iter};
+
+use hir_expand::name::{known, AsName, Name};
+use rustc_hash::FxHashSet;
+
+use crate::{
+ db::DefDatabase,
+ item_scope::ItemInNs,
+ nameres::DefMap,
+ path::{ModPath, PathKind},
+ visibility::Visibility,
+ ModuleDefId, ModuleId,
+};
+
+/// Find a path that can be used to refer to a certain item. This can depend on
+/// *from where* you're referring to the item, hence the `from` parameter.
+pub fn find_path(
+ db: &dyn DefDatabase,
+ item: ItemInNs,
+ from: ModuleId,
+ prefer_no_std: bool,
+) -> Option<ModPath> {
+ let _p = profile::span("find_path");
+ find_path_inner(db, item, from, None, prefer_no_std)
+}
+
+pub fn find_path_prefixed(
+ db: &dyn DefDatabase,
+ item: ItemInNs,
+ from: ModuleId,
+ prefix_kind: PrefixKind,
+ prefer_no_std: bool,
+) -> Option<ModPath> {
+ let _p = profile::span("find_path_prefixed");
+ find_path_inner(db, item, from, Some(prefix_kind), prefer_no_std)
+}
+
+const MAX_PATH_LEN: usize = 15;
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum PrefixKind {
+ /// Causes paths to always start with either `self`, `super`, `crate` or a crate-name.
+ /// This is the same as plain, just that paths will start with `self` iprepended f the path
+ /// starts with an identifier that is not a crate.
+ BySelf,
+ /// Causes paths to ignore imports in the local module.
+ Plain,
+ /// Causes paths to start with `crate` where applicable, effectively forcing paths to be absolute.
+ ByCrate,
+}
+
+impl PrefixKind {
+ #[inline]
+ fn prefix(self) -> PathKind {
+ match self {
+ PrefixKind::BySelf => PathKind::Super(0),
+ PrefixKind::Plain => PathKind::Plain,
+ PrefixKind::ByCrate => PathKind::Crate,
+ }
+ }
+
+ #[inline]
+ fn is_absolute(&self) -> bool {
+ self == &PrefixKind::ByCrate
+ }
+}
+
+/// Attempts to find a path to refer to the given `item` visible from the `from` ModuleId
+fn find_path_inner(
+ db: &dyn DefDatabase,
+ item: ItemInNs,
+ from: ModuleId,
+ prefixed: Option<PrefixKind>,
+ prefer_no_std: bool,
+) -> Option<ModPath> {
+ // - if the item is a builtin, it's in scope
+ if let ItemInNs::Types(ModuleDefId::BuiltinType(builtin)) = item {
+ return Some(ModPath::from_segments(PathKind::Plain, Some(builtin.as_name())));
+ }
+
+ let def_map = from.def_map(db);
+ let crate_root = def_map.crate_root(db);
+ // - if the item is a module, jump straight to module search
+ if let ItemInNs::Types(ModuleDefId::ModuleId(module_id)) = item {
+ let mut visited_modules = FxHashSet::default();
+ return find_path_for_module(
+ db,
+ &def_map,
+ &mut visited_modules,
+ crate_root,
+ from,
+ module_id,
+ MAX_PATH_LEN,
+ prefixed,
+ prefer_no_std || db.crate_supports_no_std(crate_root.krate),
+ );
+ }
+
+ // - if the item is already in scope, return the name under which it is
+ let scope_name = find_in_scope(db, &def_map, from, item);
+ if prefixed.is_none() {
+ if let Some(scope_name) = scope_name {
+ return Some(ModPath::from_segments(PathKind::Plain, Some(scope_name)));
+ }
+ }
+
+ // - if the item is in the prelude, return the name from there
+ if let Some(value) = find_in_prelude(db, &crate_root.def_map(db), item, from) {
+ return value;
+ }
+
+ if let Some(ModuleDefId::EnumVariantId(variant)) = item.as_module_def_id() {
+ // - if the item is an enum variant, refer to it via the enum
+ if let Some(mut path) = find_path_inner(
+ db,
+ ItemInNs::Types(variant.parent.into()),
+ from,
+ prefixed,
+ prefer_no_std,
+ ) {
+ let data = db.enum_data(variant.parent);
+ path.push_segment(data.variants[variant.local_id].name.clone());
+ return Some(path);
+ }
+ // If this doesn't work, it seems we have no way of referring to the
+ // enum; that's very weird, but there might still be a reexport of the
+ // variant somewhere
+ }
+
+ let mut visited_modules = FxHashSet::default();
+
+ calculate_best_path(
+ db,
+ &def_map,
+ &mut visited_modules,
+ crate_root,
+ MAX_PATH_LEN,
+ item,
+ from,
+ prefixed,
+ prefer_no_std || db.crate_supports_no_std(crate_root.krate),
+ scope_name,
+ )
+}
+
+fn find_path_for_module(
+ db: &dyn DefDatabase,
+ def_map: &DefMap,
+ visited_modules: &mut FxHashSet<ModuleId>,
+ crate_root: ModuleId,
+ from: ModuleId,
+ module_id: ModuleId,
+ max_len: usize,
+ prefixed: Option<PrefixKind>,
+ prefer_no_std: bool,
+) -> Option<ModPath> {
+ if max_len == 0 {
+ return None;
+ }
+
+ // Base cases:
+ // - if the item is already in scope, return the name under which it is
+ let scope_name = find_in_scope(db, def_map, from, ItemInNs::Types(module_id.into()));
+ if prefixed.is_none() {
+ if let Some(scope_name) = scope_name {
+ return Some(ModPath::from_segments(PathKind::Plain, Some(scope_name)));
+ }
+ }
+
+ // - if the item is the crate root, return `crate`
+ if module_id == crate_root {
+ return Some(ModPath::from_segments(PathKind::Crate, None));
+ }
+
+ // - if relative paths are fine, check if we are searching for a parent
+ if prefixed.filter(PrefixKind::is_absolute).is_none() {
+ if let modpath @ Some(_) = find_self_super(&def_map, module_id, from) {
+ return modpath;
+ }
+ }
+
+ // - if the item is the crate root of a dependency crate, return the name from the extern prelude
+ let root_def_map = crate_root.def_map(db);
+ for (name, &def_id) in root_def_map.extern_prelude() {
+ if module_id == def_id {
+ let name = scope_name.unwrap_or_else(|| name.clone());
+
+ let name_already_occupied_in_type_ns = def_map
+ .with_ancestor_maps(db, from.local_id, &mut |def_map, local_id| {
+ def_map[local_id]
+ .scope
+ .type_(&name)
+ .filter(|&(id, _)| id != ModuleDefId::ModuleId(def_id))
+ })
+ .is_some();
+ let kind = if name_already_occupied_in_type_ns {
+ cov_mark::hit!(ambiguous_crate_start);
+ PathKind::Abs
+ } else {
+ PathKind::Plain
+ };
+ return Some(ModPath::from_segments(kind, Some(name)));
+ }
+ }
+
+ if let Some(value) = find_in_prelude(db, &root_def_map, ItemInNs::Types(module_id.into()), from)
+ {
+ return value;
+ }
+ calculate_best_path(
+ db,
+ def_map,
+ visited_modules,
+ crate_root,
+ max_len,
+ ItemInNs::Types(module_id.into()),
+ from,
+ prefixed,
+ prefer_no_std,
+ scope_name,
+ )
+}
+
+fn find_in_scope(
+ db: &dyn DefDatabase,
+ def_map: &DefMap,
+ from: ModuleId,
+ item: ItemInNs,
+) -> Option<Name> {
+ def_map.with_ancestor_maps(db, from.local_id, &mut |def_map, local_id| {
+ def_map[local_id].scope.name_of(item).map(|(name, _)| name.clone())
+ })
+}
+
+fn find_in_prelude(
+ db: &dyn DefDatabase,
+ root_def_map: &DefMap,
+ item: ItemInNs,
+ from: ModuleId,
+) -> Option<Option<ModPath>> {
+ if let Some(prelude_module) = root_def_map.prelude() {
+ // Preludes in block DefMaps are ignored, only the crate DefMap is searched
+ let prelude_def_map = prelude_module.def_map(db);
+ let prelude_scope = &prelude_def_map[prelude_module.local_id].scope;
+ if let Some((name, vis)) = prelude_scope.name_of(item) {
+ if vis.is_visible_from(db, from) {
+ return Some(Some(ModPath::from_segments(PathKind::Plain, Some(name.clone()))));
+ }
+ }
+ }
+ None
+}
+
+fn find_self_super(def_map: &DefMap, item: ModuleId, from: ModuleId) -> Option<ModPath> {
+ if item == from {
+ // - if the item is the module we're in, use `self`
+ Some(ModPath::from_segments(PathKind::Super(0), None))
+ } else if let Some(parent_id) = def_map[from.local_id].parent {
+ // - if the item is the parent module, use `super` (this is not used recursively, since `super::super` is ugly)
+ let parent_id = def_map.module_id(parent_id);
+ if item == parent_id {
+ Some(ModPath::from_segments(PathKind::Super(1), None))
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+}
+
+fn calculate_best_path(
+ db: &dyn DefDatabase,
+ def_map: &DefMap,
+ visited_modules: &mut FxHashSet<ModuleId>,
+ crate_root: ModuleId,
+ max_len: usize,
+ item: ItemInNs,
+ from: ModuleId,
+ mut prefixed: Option<PrefixKind>,
+ prefer_no_std: bool,
+ scope_name: Option<Name>,
+) -> Option<ModPath> {
+ if max_len <= 1 {
+ return None;
+ }
+ let mut best_path = None;
+ // Recursive case:
+ // - otherwise, look for modules containing (reexporting) it and import it from one of those
+ if item.krate(db) == Some(from.krate) {
+ let mut best_path_len = max_len;
+ // Item was defined in the same crate that wants to import it. It cannot be found in any
+ // dependency in this case.
+ for (module_id, name) in find_local_import_locations(db, item, from) {
+ if !visited_modules.insert(module_id) {
+ cov_mark::hit!(recursive_imports);
+ continue;
+ }
+ if let Some(mut path) = find_path_for_module(
+ db,
+ def_map,
+ visited_modules,
+ crate_root,
+ from,
+ module_id,
+ best_path_len - 1,
+ prefixed,
+ prefer_no_std,
+ ) {
+ path.push_segment(name);
+
+ let new_path = match best_path {
+ Some(best_path) => select_best_path(best_path, path, prefer_no_std),
+ None => path,
+ };
+ best_path_len = new_path.len();
+ best_path = Some(new_path);
+ }
+ }
+ } else {
+ // Item was defined in some upstream crate. This means that it must be exported from one,
+ // too (unless we can't name it at all). It could *also* be (re)exported by the same crate
+ // that wants to import it here, but we always prefer to use the external path here.
+
+ let crate_graph = db.crate_graph();
+ let extern_paths = crate_graph[from.krate].dependencies.iter().filter_map(|dep| {
+ let import_map = db.import_map(dep.crate_id);
+ import_map.import_info_for(item).and_then(|info| {
+ // Determine best path for containing module and append last segment from `info`.
+ // FIXME: we should guide this to look up the path locally, or from the same crate again?
+ let mut path = find_path_for_module(
+ db,
+ def_map,
+ visited_modules,
+ crate_root,
++ from,
+ info.container,
+ max_len - 1,
+ prefixed,
+ prefer_no_std,
+ )?;
+ cov_mark::hit!(partially_imported);
+ path.push_segment(info.path.segments.last()?.clone());
+ Some(path)
+ })
+ });
+
+ for path in extern_paths {
+ let new_path = match best_path {
+ Some(best_path) => select_best_path(best_path, path, prefer_no_std),
+ None => path,
+ };
+ best_path = Some(new_path);
+ }
+ }
+ if let Some(module) = item.module(db) {
+ if module.def_map(db).block_id().is_some() && prefixed.is_some() {
+ cov_mark::hit!(prefixed_in_block_expression);
+ prefixed = Some(PrefixKind::Plain);
+ }
+ }
+ match prefixed.map(PrefixKind::prefix) {
+ Some(prefix) => best_path.or_else(|| {
+ scope_name.map(|scope_name| ModPath::from_segments(prefix, Some(scope_name)))
+ }),
+ None => best_path,
+ }
+}
+
+fn select_best_path(old_path: ModPath, new_path: ModPath, prefer_no_std: bool) -> ModPath {
+ const STD_CRATES: [Name; 3] = [known::std, known::core, known::alloc];
+ match (old_path.segments().first(), new_path.segments().first()) {
+ (Some(old), Some(new)) if STD_CRATES.contains(old) && STD_CRATES.contains(new) => {
+ let rank = match prefer_no_std {
+ false => |name: &Name| match name {
+ name if name == &known::core => 0,
+ name if name == &known::alloc => 0,
+ name if name == &known::std => 1,
+ _ => unreachable!(),
+ },
+ true => |name: &Name| match name {
+ name if name == &known::core => 2,
+ name if name == &known::alloc => 1,
+ name if name == &known::std => 0,
+ _ => unreachable!(),
+ },
+ };
+ let nrank = rank(new);
+ let orank = rank(old);
+ match nrank.cmp(&orank) {
+ Ordering::Less => old_path,
+ Ordering::Equal => {
+ if new_path.len() < old_path.len() {
+ new_path
+ } else {
+ old_path
+ }
+ }
+ Ordering::Greater => new_path,
+ }
+ }
+ _ => {
+ if new_path.len() < old_path.len() {
+ new_path
+ } else {
+ old_path
+ }
+ }
+ }
+}
+
+// FIXME: Remove allocations
+/// Finds locations in `from.krate` from which `item` can be imported by `from`.
+fn find_local_import_locations(
+ db: &dyn DefDatabase,
+ item: ItemInNs,
+ from: ModuleId,
+) -> Vec<(ModuleId, Name)> {
+ let _p = profile::span("find_local_import_locations");
+
+ // `from` can import anything below `from` with visibility of at least `from`, and anything
+ // above `from` with any visibility. That means we do not need to descend into private siblings
+ // of `from` (and similar).
+
+ let def_map = from.def_map(db);
+
+ // Compute the initial worklist. We start with all direct child modules of `from` as well as all
+ // of its (recursive) parent modules.
+ let data = &def_map[from.local_id];
+ let mut worklist =
+ data.children.values().map(|child| def_map.module_id(*child)).collect::<Vec<_>>();
+ // FIXME: do we need to traverse out of block expressions here?
+ for ancestor in iter::successors(from.containing_module(db), |m| m.containing_module(db)) {
+ worklist.push(ancestor);
+ }
+
+ let def_map = def_map.crate_root(db).def_map(db);
+
+ let mut seen: FxHashSet<_> = FxHashSet::default();
+
+ let mut locations = Vec::new();
+ while let Some(module) = worklist.pop() {
+ if !seen.insert(module) {
+ continue; // already processed this module
+ }
+
+ let ext_def_map;
+ let data = if module.krate == from.krate {
+ if module.block.is_some() {
+ // Re-query the block's DefMap
+ ext_def_map = module.def_map(db);
+ &ext_def_map[module.local_id]
+ } else {
+ // Reuse the root DefMap
+ &def_map[module.local_id]
+ }
+ } else {
+ // The crate might reexport a module defined in another crate.
+ ext_def_map = module.def_map(db);
+ &ext_def_map[module.local_id]
+ };
+
+ if let Some((name, vis)) = data.scope.name_of(item) {
+ if vis.is_visible_from(db, from) {
+ let is_private = match vis {
+ Visibility::Module(private_to) => private_to.local_id == module.local_id,
+ Visibility::Public => false,
+ };
+ let is_original_def = match item.as_module_def_id() {
+ Some(module_def_id) => data.scope.declarations().any(|it| it == module_def_id),
+ None => false,
+ };
+
+ // Ignore private imports. these could be used if we are
+ // in a submodule of this module, but that's usually not
+ // what the user wants; and if this module can import
+ // the item and we're a submodule of it, so can we.
+ // Also this keeps the cached data smaller.
+ if !is_private || is_original_def {
+ locations.push((module, name.clone()));
+ }
+ }
+ }
+
+ // Descend into all modules visible from `from`.
+ for (ty, vis) in data.scope.types() {
+ if let ModuleDefId::ModuleId(module) = ty {
+ if vis.is_visible_from(db, from) {
+ worklist.push(module);
+ }
+ }
+ }
+ }
+
+ locations
+}
+
+#[cfg(test)]
+mod tests {
+ use base_db::fixture::WithFixture;
+ use hir_expand::hygiene::Hygiene;
+ use syntax::ast::AstNode;
+
+ use crate::test_db::TestDB;
+
+ use super::*;
+
+ /// `code` needs to contain a cursor marker; checks that `find_path` for the
+ /// item the `path` refers to returns that same path when called from the
+ /// module the cursor is in.
+ fn check_found_path_(ra_fixture: &str, path: &str, prefix_kind: Option<PrefixKind>) {
+ let (db, pos) = TestDB::with_position(ra_fixture);
+ let module = db.module_at_position(pos);
+ let parsed_path_file = syntax::SourceFile::parse(&format!("use {};", path));
+ let ast_path =
+ parsed_path_file.syntax_node().descendants().find_map(syntax::ast::Path::cast).unwrap();
+ let mod_path = ModPath::from_src(&db, ast_path, &Hygiene::new_unhygienic()).unwrap();
+
+ let def_map = module.def_map(&db);
+ let resolved = def_map
+ .resolve_path(
+ &db,
+ module.local_id,
+ &mod_path,
+ crate::item_scope::BuiltinShadowMode::Module,
+ )
+ .0
+ .take_types()
+ .unwrap();
+
+ let found_path =
+ find_path_inner(&db, ItemInNs::Types(resolved), module, prefix_kind, false);
+ assert_eq!(found_path, Some(mod_path), "{:?}", prefix_kind);
+ }
+
+ fn check_found_path(
+ ra_fixture: &str,
+ unprefixed: &str,
+ prefixed: &str,
+ absolute: &str,
+ self_prefixed: &str,
+ ) {
+ check_found_path_(ra_fixture, unprefixed, None);
+ check_found_path_(ra_fixture, prefixed, Some(PrefixKind::Plain));
+ check_found_path_(ra_fixture, absolute, Some(PrefixKind::ByCrate));
+ check_found_path_(ra_fixture, self_prefixed, Some(PrefixKind::BySelf));
+ }
+
+ #[test]
+ fn same_module() {
+ check_found_path(
+ r#"
+struct S;
+$0
+ "#,
+ "S",
+ "S",
+ "crate::S",
+ "self::S",
+ );
+ }
+
+ #[test]
+ fn enum_variant() {
+ check_found_path(
+ r#"
+enum E { A }
+$0
+ "#,
+ "E::A",
+ "E::A",
+ "crate::E::A",
+ "self::E::A",
+ );
+ }
+
+ #[test]
+ fn sub_module() {
+ check_found_path(
+ r#"
+mod foo {
+ pub struct S;
+}
+$0
+ "#,
+ "foo::S",
+ "foo::S",
+ "crate::foo::S",
+ "self::foo::S",
+ );
+ }
+
+ #[test]
+ fn super_module() {
+ check_found_path(
+ r#"
+//- /main.rs
+mod foo;
+//- /foo.rs
+mod bar;
+struct S;
+//- /foo/bar.rs
+$0
+ "#,
+ "super::S",
+ "super::S",
+ "crate::foo::S",
+ "super::S",
+ );
+ }
+
+ #[test]
+ fn self_module() {
+ check_found_path(
+ r#"
+//- /main.rs
+mod foo;
+//- /foo.rs
+$0
+ "#,
+ "self",
+ "self",
+ "crate::foo",
+ "self",
+ );
+ }
+
+ #[test]
+ fn crate_root() {
+ check_found_path(
+ r#"
+//- /main.rs
+mod foo;
+//- /foo.rs
+$0
+ "#,
+ "crate",
+ "crate",
+ "crate",
+ "crate",
+ );
+ }
+
+ #[test]
+ fn same_crate() {
+ check_found_path(
+ r#"
+//- /main.rs
+mod foo;
+struct S;
+//- /foo.rs
+$0
+ "#,
+ "crate::S",
+ "crate::S",
+ "crate::S",
+ "crate::S",
+ );
+ }
+
+ #[test]
+ fn different_crate() {
+ check_found_path(
+ r#"
+//- /main.rs crate:main deps:std
+$0
+//- /std.rs crate:std
+pub struct S;
+ "#,
+ "std::S",
+ "std::S",
+ "std::S",
+ "std::S",
+ );
+ }
+
+ #[test]
+ fn different_crate_renamed() {
+ check_found_path(
+ r#"
+//- /main.rs crate:main deps:std
+extern crate std as std_renamed;
+$0
+//- /std.rs crate:std
+pub struct S;
+ "#,
+ "std_renamed::S",
+ "std_renamed::S",
+ "std_renamed::S",
+ "std_renamed::S",
+ );
+ }
+
+ #[test]
+ fn partially_imported() {
+ cov_mark::check!(partially_imported);
+ // Tests that short paths are used even for external items, when parts of the path are
+ // already in scope.
+ check_found_path(
+ r#"
+//- /main.rs crate:main deps:syntax
+
+use syntax::ast;
+$0
+
+//- /lib.rs crate:syntax
+pub mod ast {
+ pub enum ModuleItem {
+ A, B, C,
+ }
+}
+ "#,
+ "ast::ModuleItem",
+ "syntax::ast::ModuleItem",
+ "syntax::ast::ModuleItem",
+ "syntax::ast::ModuleItem",
+ );
+
+ check_found_path(
+ r#"
+//- /main.rs crate:main deps:syntax
+$0
+
+//- /lib.rs crate:syntax
+pub mod ast {
+ pub enum ModuleItem {
+ A, B, C,
+ }
+}
+ "#,
+ "syntax::ast::ModuleItem",
+ "syntax::ast::ModuleItem",
+ "syntax::ast::ModuleItem",
+ "syntax::ast::ModuleItem",
+ );
+ }
+
+ #[test]
+ fn same_crate_reexport() {
+ check_found_path(
+ r#"
+mod bar {
+ mod foo { pub(super) struct S; }
+ pub(crate) use foo::*;
+}
+$0
+ "#,
+ "bar::S",
+ "bar::S",
+ "crate::bar::S",
+ "self::bar::S",
+ );
+ }
+
+ #[test]
+ fn same_crate_reexport_rename() {
+ check_found_path(
+ r#"
+mod bar {
+ mod foo { pub(super) struct S; }
+ pub(crate) use foo::S as U;
+}
+$0
+ "#,
+ "bar::U",
+ "bar::U",
+ "crate::bar::U",
+ "self::bar::U",
+ );
+ }
+
+ #[test]
+ fn different_crate_reexport() {
+ check_found_path(
+ r#"
+//- /main.rs crate:main deps:std
+$0
+//- /std.rs crate:std deps:core
+pub use core::S;
+//- /core.rs crate:core
+pub struct S;
+ "#,
+ "std::S",
+ "std::S",
+ "std::S",
+ "std::S",
+ );
+ }
+
+ #[test]
+ fn prelude() {
+ check_found_path(
+ r#"
+//- /main.rs crate:main deps:std
+$0
+//- /std.rs crate:std
+pub mod prelude {
+ pub mod rust_2018 {
+ pub struct S;
+ }
+}
+ "#,
+ "S",
+ "S",
+ "S",
+ "S",
+ );
+ }
+
+ #[test]
+ fn enum_variant_from_prelude() {
+ let code = r#"
+//- /main.rs crate:main deps:std
+$0
+//- /std.rs crate:std
+pub mod prelude {
+ pub mod rust_2018 {
+ pub enum Option<T> { Some(T), None }
+ pub use Option::*;
+ }
+}
+ "#;
+ check_found_path(code, "None", "None", "None", "None");
+ check_found_path(code, "Some", "Some", "Some", "Some");
+ }
+
+ #[test]
+ fn shortest_path() {
+ check_found_path(
+ r#"
+//- /main.rs
+pub mod foo;
+pub mod baz;
+struct S;
+$0
+//- /foo.rs
+pub mod bar { pub struct S; }
+//- /baz.rs
+pub use crate::foo::bar::S;
+ "#,
+ "baz::S",
+ "baz::S",
+ "crate::baz::S",
+ "self::baz::S",
+ );
+ }
+
+ #[test]
+ fn discount_private_imports() {
+ check_found_path(
+ r#"
+//- /main.rs
+mod foo;
+pub mod bar { pub struct S; }
+use bar::S;
+//- /foo.rs
+$0
+ "#,
+ // crate::S would be shorter, but using private imports seems wrong
+ "crate::bar::S",
+ "crate::bar::S",
+ "crate::bar::S",
+ "crate::bar::S",
+ );
+ }
+
+ #[test]
+ fn import_cycle() {
+ check_found_path(
+ r#"
+//- /main.rs
+pub mod foo;
+pub mod bar;
+pub mod baz;
+//- /bar.rs
+$0
+//- /foo.rs
+pub use super::baz;
+pub struct S;
+//- /baz.rs
+pub use super::foo;
+ "#,
+ "crate::foo::S",
+ "crate::foo::S",
+ "crate::foo::S",
+ "crate::foo::S",
+ );
+ }
+
+ #[test]
+ fn prefer_std_paths_over_alloc() {
+ check_found_path(
+ r#"
+//- /main.rs crate:main deps:alloc,std
+$0
+
+//- /std.rs crate:std deps:alloc
+pub mod sync {
+ pub use alloc::sync::Arc;
+}
+
+//- /zzz.rs crate:alloc
+pub mod sync {
+ pub struct Arc;
+}
+ "#,
+ "std::sync::Arc",
+ "std::sync::Arc",
+ "std::sync::Arc",
+ "std::sync::Arc",
+ );
+ }
+
+ #[test]
+ fn prefer_core_paths_over_std() {
+ check_found_path(
+ r#"
+//- /main.rs crate:main deps:core,std
+#![no_std]
+
+$0
+
+//- /std.rs crate:std deps:core
+
+pub mod fmt {
+ pub use core::fmt::Error;
+}
+
+//- /zzz.rs crate:core
+
+pub mod fmt {
+ pub struct Error;
+}
+ "#,
+ "core::fmt::Error",
+ "core::fmt::Error",
+ "core::fmt::Error",
+ "core::fmt::Error",
+ );
+
+ // Should also work (on a best-effort basis) if `no_std` is conditional.
+ check_found_path(
+ r#"
+//- /main.rs crate:main deps:core,std
+#![cfg_attr(not(test), no_std)]
+
+$0
+
+//- /std.rs crate:std deps:core
+
+pub mod fmt {
+ pub use core::fmt::Error;
+}
+
+//- /zzz.rs crate:core
+
+pub mod fmt {
+ pub struct Error;
+}
+ "#,
+ "core::fmt::Error",
+ "core::fmt::Error",
+ "core::fmt::Error",
+ "core::fmt::Error",
+ );
+ }
+
+ #[test]
+ fn prefer_alloc_paths_over_std() {
+ check_found_path(
+ r#"
+//- /main.rs crate:main deps:alloc,std
+#![no_std]
+
+$0
+
+//- /std.rs crate:std deps:alloc
+
+pub mod sync {
+ pub use alloc::sync::Arc;
+}
+
+//- /zzz.rs crate:alloc
+
+pub mod sync {
+ pub struct Arc;
+}
+ "#,
+ "alloc::sync::Arc",
+ "alloc::sync::Arc",
+ "alloc::sync::Arc",
+ "alloc::sync::Arc",
+ );
+ }
+
+ #[test]
+ fn prefer_shorter_paths_if_not_alloc() {
+ check_found_path(
+ r#"
+//- /main.rs crate:main deps:megaalloc,std
+$0
+
+//- /std.rs crate:std deps:megaalloc
+pub mod sync {
+ pub use megaalloc::sync::Arc;
+}
+
+//- /zzz.rs crate:megaalloc
+pub struct Arc;
+ "#,
+ "megaalloc::Arc",
+ "megaalloc::Arc",
+ "megaalloc::Arc",
+ "megaalloc::Arc",
+ );
+ }
+
+ #[test]
+ fn builtins_are_in_scope() {
+ let code = r#"
+$0
+
+pub mod primitive {
+ pub use u8;
+}
+ "#;
+ check_found_path(code, "u8", "u8", "u8", "u8");
+ check_found_path(code, "u16", "u16", "u16", "u16");
+ }
+
+ #[test]
+ fn inner_items() {
+ check_found_path(
+ r#"
+fn main() {
+ struct Inner {}
+ $0
+}
+ "#,
+ "Inner",
+ "Inner",
+ "Inner",
+ "Inner",
+ );
+ }
+
+ #[test]
+ fn inner_items_from_outer_scope() {
+ check_found_path(
+ r#"
+fn main() {
+ struct Struct {}
+ {
+ $0
+ }
+}
+ "#,
+ "Struct",
+ "Struct",
+ "Struct",
+ "Struct",
+ );
+ }
+
+ #[test]
+ fn inner_items_from_inner_module() {
+ cov_mark::check!(prefixed_in_block_expression);
+ check_found_path(
+ r#"
+fn main() {
+ mod module {
+ struct Struct {}
+ }
+ {
+ $0
+ }
+}
+ "#,
+ "module::Struct",
+ "module::Struct",
+ "module::Struct",
+ "module::Struct",
+ );
+ }
+
+ #[test]
+ fn outer_items_with_inner_items_present() {
+ check_found_path(
+ r#"
+mod module {
+ pub struct CompleteMe;
+}
+
+fn main() {
+ fn inner() {}
+ $0
+}
+ "#,
+ // FIXME: these could use fewer/better prefixes
+ "module::CompleteMe",
+ "crate::module::CompleteMe",
+ "crate::module::CompleteMe",
+ "crate::module::CompleteMe",
+ )
+ }
+
+ #[test]
+ fn from_inside_module() {
+ // This worked correctly, but the test suite logic was broken.
+ cov_mark::check!(submodule_in_testdb);
+ check_found_path(
+ r#"
+mod baz {
+ pub struct Foo {}
+}
+
+mod bar {
+ fn bar() {
+ $0
+ }
+}
+ "#,
+ "crate::baz::Foo",
+ "crate::baz::Foo",
+ "crate::baz::Foo",
+ "crate::baz::Foo",
+ )
+ }
+
+ #[test]
+ fn from_inside_module_with_inner_items() {
+ check_found_path(
+ r#"
+mod baz {
+ pub struct Foo {}
+}
+
+mod bar {
+ fn bar() {
+ fn inner() {}
+ $0
+ }
+}
+ "#,
+ "crate::baz::Foo",
+ "crate::baz::Foo",
+ "crate::baz::Foo",
+ "crate::baz::Foo",
+ )
+ }
+
+ #[test]
+ fn recursive_pub_mod_reexport() {
+ cov_mark::check!(recursive_imports);
+ check_found_path(
+ r#"
+fn main() {
+ let _ = 22_i32.as_name$0();
+}
+
+pub mod name {
+ pub trait AsName {
+ fn as_name(&self) -> String;
+ }
+ impl AsName for i32 {
+ fn as_name(&self) -> String {
+ format!("Name: {}", self)
+ }
+ }
+ pub use crate::name;
+}
+"#,
+ "name::AsName",
+ "name::AsName",
+ "crate::name::AsName",
+ "self::name::AsName",
+ );
+ }
+
+ #[test]
+ fn extern_crate() {
+ check_found_path(
+ r#"
+//- /main.rs crate:main deps:dep
+$0
+//- /dep.rs crate:dep
+"#,
+ "dep",
+ "dep",
+ "dep",
+ "dep",
+ );
+
+ check_found_path(
+ r#"
+//- /main.rs crate:main deps:dep
+fn f() {
+ fn inner() {}
+ $0
+}
+//- /dep.rs crate:dep
+"#,
+ "dep",
+ "dep",
+ "dep",
+ "dep",
+ );
+ }
+
+ #[test]
+ fn prelude_with_inner_items() {
+ check_found_path(
+ r#"
+//- /main.rs crate:main deps:std
+fn f() {
+ fn inner() {}
+ $0
+}
+//- /std.rs crate:std
+pub mod prelude {
+ pub mod rust_2018 {
+ pub enum Option { None }
+ pub use Option::*;
+ }
+}
+ "#,
+ "None",
+ "None",
+ "None",
+ "None",
+ );
+ }
+}
--- /dev/null
+//! A simplified AST that only contains items.
+//!
+//! This is the primary IR used throughout `hir_def`. It is the input to the name resolution
+//! algorithm, as well as to the queries defined in `adt.rs`, `data.rs`, and most things in
+//! `attr.rs`.
+//!
+//! `ItemTree`s are built per `HirFileId`, from the syntax tree of the parsed file. This means that
+//! they are crate-independent: they don't know which `#[cfg]`s are active or which module they
+//! belong to, since those concepts don't exist at this level (a single `ItemTree` might be part of
+//! multiple crates, or might be included into the same crate twice via `#[path]`).
+//!
+//! One important purpose of this layer is to provide an "invalidation barrier" for incremental
+//! computations: when typing inside an item body, the `ItemTree` of the modified file is typically
+//! unaffected, so we don't have to recompute name resolution results or item data (see `data.rs`).
+//!
+//! The `ItemTree` for the currently open file can be displayed by using the VS Code command
+//! "rust-analyzer: Debug ItemTree".
+//!
+//! Compared to rustc's architecture, `ItemTree` has properties from both rustc's AST and HIR: many
+//! syntax-level Rust features are already desugared to simpler forms in the `ItemTree`, but name
+//! resolution has not yet been performed. `ItemTree`s are per-file, while rustc's AST and HIR are
+//! per-crate, because we are interested in incrementally computing it.
+//!
+//! The representation of items in the `ItemTree` should generally mirror the surface syntax: it is
+//! usually a bad idea to desugar a syntax-level construct to something that is structurally
+//! different here. Name resolution needs to be able to process attributes and expand macros
+//! (including attribute macros), and having a 1-to-1 mapping between syntax and the `ItemTree`
+//! avoids introducing subtle bugs.
+//!
+//! In general, any item in the `ItemTree` stores its `AstId`, which allows mapping it back to its
+//! surface syntax.
+
+mod lower;
+mod pretty;
+#[cfg(test)]
+mod tests;
+
+use std::{
+ fmt::{self, Debug},
+ hash::{Hash, Hasher},
+ marker::PhantomData,
+ ops::Index,
+ sync::Arc,
+};
+
+use ast::{AstNode, HasName, StructKind};
+use base_db::CrateId;
+use either::Either;
+use hir_expand::{
+ ast_id_map::FileAstId,
+ hygiene::Hygiene,
+ name::{name, AsName, Name},
+ ExpandTo, HirFileId, InFile,
+};
+use la_arena::{Arena, Idx, IdxRange, RawIdx};
+use profile::Count;
+use rustc_hash::FxHashMap;
+use smallvec::SmallVec;
+use stdx::never;
+use syntax::{ast, match_ast, SyntaxKind};
+
+use crate::{
+ attr::{Attrs, RawAttrs},
+ db::DefDatabase,
+ generics::GenericParams,
+ intern::Interned,
+ path::{path, AssociatedTypeBinding, GenericArgs, ImportAlias, ModPath, Path, PathKind},
+ type_ref::{Mutability, TraitRef, TypeBound, TypeRef},
+ visibility::RawVisibility,
+ BlockId,
+};
+
+#[derive(Copy, Clone, Eq, PartialEq)]
+pub struct RawVisibilityId(u32);
+
+impl RawVisibilityId {
+ pub const PUB: Self = RawVisibilityId(u32::max_value());
+ pub const PRIV: Self = RawVisibilityId(u32::max_value() - 1);
+ pub const PUB_CRATE: Self = RawVisibilityId(u32::max_value() - 2);
+}
+
+impl fmt::Debug for RawVisibilityId {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let mut f = f.debug_tuple("RawVisibilityId");
+ match *self {
+ Self::PUB => f.field(&"pub"),
+ Self::PRIV => f.field(&"pub(self)"),
+ Self::PUB_CRATE => f.field(&"pub(crate)"),
+ _ => f.field(&self.0),
+ };
+ f.finish()
+ }
+}
+
+/// The item tree of a source file.
+#[derive(Debug, Default, Eq, PartialEq)]
+pub struct ItemTree {
+ _c: Count<Self>,
+
+ top_level: SmallVec<[ModItem; 1]>,
+ attrs: FxHashMap<AttrOwner, RawAttrs>,
+
+ data: Option<Box<ItemTreeData>>,
+}
+
+impl ItemTree {
+ pub(crate) fn file_item_tree_query(db: &dyn DefDatabase, file_id: HirFileId) -> Arc<ItemTree> {
+ let _p = profile::span("file_item_tree_query").detail(|| format!("{:?}", file_id));
+ let syntax = match db.parse_or_expand(file_id) {
+ Some(node) => node,
+ None => return Default::default(),
+ };
+ if never!(syntax.kind() == SyntaxKind::ERROR) {
+ // FIXME: not 100% sure why these crop up, but return an empty tree to avoid a panic
+ return Default::default();
+ }
+
+ let ctx = lower::Ctx::new(db, file_id);
+ let mut top_attrs = None;
+ let mut item_tree = match_ast! {
+ match syntax {
+ ast::SourceFile(file) => {
+ top_attrs = Some(RawAttrs::new(db, &file, ctx.hygiene()));
+ ctx.lower_module_items(&file)
+ },
+ ast::MacroItems(items) => {
+ ctx.lower_module_items(&items)
+ },
+ ast::MacroStmts(stmts) => {
+ // The produced statements can include items, which should be added as top-level
+ // items.
+ ctx.lower_macro_stmts(stmts)
+ },
+ _ => {
+ panic!("cannot create item tree from {:?} {}", syntax, syntax);
+ },
+ }
+ };
+
+ if let Some(attrs) = top_attrs {
+ item_tree.attrs.insert(AttrOwner::TopLevel, attrs);
+ }
+ item_tree.shrink_to_fit();
+ Arc::new(item_tree)
+ }
+
+ /// Returns an iterator over all items located at the top level of the `HirFileId` this
+ /// `ItemTree` was created from.
+ pub fn top_level_items(&self) -> &[ModItem] {
+ &self.top_level
+ }
+
+ /// Returns the inner attributes of the source file.
+ pub fn top_level_attrs(&self, db: &dyn DefDatabase, krate: CrateId) -> Attrs {
+ self.attrs.get(&AttrOwner::TopLevel).unwrap_or(&RawAttrs::EMPTY).clone().filter(db, krate)
+ }
+
+ pub(crate) fn raw_attrs(&self, of: AttrOwner) -> &RawAttrs {
+ self.attrs.get(&of).unwrap_or(&RawAttrs::EMPTY)
+ }
+
+ pub(crate) fn attrs(&self, db: &dyn DefDatabase, krate: CrateId, of: AttrOwner) -> Attrs {
+ self.raw_attrs(of).clone().filter(db, krate)
+ }
+
+ pub fn pretty_print(&self) -> String {
+ pretty::print_item_tree(self)
+ }
+
+ fn data(&self) -> &ItemTreeData {
+ self.data.as_ref().expect("attempted to access data of empty ItemTree")
+ }
+
+ fn data_mut(&mut self) -> &mut ItemTreeData {
+ self.data.get_or_insert_with(Box::default)
+ }
+
+ fn block_item_tree(db: &dyn DefDatabase, block: BlockId) -> Arc<ItemTree> {
+ let loc = db.lookup_intern_block(block);
+ let block = loc.ast_id.to_node(db.upcast());
+ let ctx = lower::Ctx::new(db, loc.ast_id.file_id);
+ Arc::new(ctx.lower_block(&block))
+ }
+
+ fn shrink_to_fit(&mut self) {
+ if let Some(data) = &mut self.data {
+ let ItemTreeData {
+ imports,
+ extern_crates,
+ extern_blocks,
+ functions,
+ params,
+ structs,
+ fields,
+ unions,
+ enums,
+ variants,
+ consts,
+ statics,
+ traits,
+ impls,
+ type_aliases,
+ mods,
+ macro_calls,
+ macro_rules,
+ macro_defs,
+ vis,
+ } = &mut **data;
+
+ imports.shrink_to_fit();
+ extern_crates.shrink_to_fit();
+ extern_blocks.shrink_to_fit();
+ functions.shrink_to_fit();
+ params.shrink_to_fit();
+ structs.shrink_to_fit();
+ fields.shrink_to_fit();
+ unions.shrink_to_fit();
+ enums.shrink_to_fit();
+ variants.shrink_to_fit();
+ consts.shrink_to_fit();
+ statics.shrink_to_fit();
+ traits.shrink_to_fit();
+ impls.shrink_to_fit();
+ type_aliases.shrink_to_fit();
+ mods.shrink_to_fit();
+ macro_calls.shrink_to_fit();
+ macro_rules.shrink_to_fit();
+ macro_defs.shrink_to_fit();
+
+ vis.arena.shrink_to_fit();
+ }
+ }
+}
+
+#[derive(Default, Debug, Eq, PartialEq)]
+struct ItemVisibilities {
+ arena: Arena<RawVisibility>,
+}
+
+impl ItemVisibilities {
+ fn alloc(&mut self, vis: RawVisibility) -> RawVisibilityId {
+ match &vis {
+ RawVisibility::Public => RawVisibilityId::PUB,
+ RawVisibility::Module(path) if path.segments().is_empty() => match &path.kind {
+ PathKind::Super(0) => RawVisibilityId::PRIV,
+ PathKind::Crate => RawVisibilityId::PUB_CRATE,
+ _ => RawVisibilityId(self.arena.alloc(vis).into_raw().into()),
+ },
+ _ => RawVisibilityId(self.arena.alloc(vis).into_raw().into()),
+ }
+ }
+}
+
+static VIS_PUB: RawVisibility = RawVisibility::Public;
+static VIS_PRIV: RawVisibility = RawVisibility::Module(ModPath::from_kind(PathKind::Super(0)));
+static VIS_PUB_CRATE: RawVisibility = RawVisibility::Module(ModPath::from_kind(PathKind::Crate));
+
+#[derive(Default, Debug, Eq, PartialEq)]
+struct ItemTreeData {
+ imports: Arena<Import>,
+ extern_crates: Arena<ExternCrate>,
+ extern_blocks: Arena<ExternBlock>,
+ functions: Arena<Function>,
+ params: Arena<Param>,
+ structs: Arena<Struct>,
+ fields: Arena<Field>,
+ unions: Arena<Union>,
+ enums: Arena<Enum>,
+ variants: Arena<Variant>,
+ consts: Arena<Const>,
+ statics: Arena<Static>,
+ traits: Arena<Trait>,
+ impls: Arena<Impl>,
+ type_aliases: Arena<TypeAlias>,
+ mods: Arena<Mod>,
+ macro_calls: Arena<MacroCall>,
+ macro_rules: Arena<MacroRules>,
+ macro_defs: Arena<MacroDef>,
+
+ vis: ItemVisibilities,
+}
+
+#[derive(Debug, Eq, PartialEq, Hash)]
+pub enum AttrOwner {
+ /// Attributes on an item.
+ ModItem(ModItem),
+ /// Inner attributes of the source file.
+ TopLevel,
+
+ Variant(Idx<Variant>),
+ Field(Idx<Field>),
+ Param(Idx<Param>),
+}
+
+macro_rules! from_attrs {
+ ( $( $var:ident($t:ty) ),+ ) => {
+ $(
+ impl From<$t> for AttrOwner {
+ fn from(t: $t) -> AttrOwner {
+ AttrOwner::$var(t)
+ }
+ }
+ )+
+ };
+}
+
+from_attrs!(ModItem(ModItem), Variant(Idx<Variant>), Field(Idx<Field>), Param(Idx<Param>));
+
+/// Trait implemented by all item nodes in the item tree.
+pub trait ItemTreeNode: Clone {
+ type Source: AstNode + Into<ast::Item>;
+
+ fn ast_id(&self) -> FileAstId<Self::Source>;
+
+ /// Looks up an instance of `Self` in an item tree.
+ fn lookup(tree: &ItemTree, index: Idx<Self>) -> &Self;
+
+ /// Downcasts a `ModItem` to a `FileItemTreeId` specific to this type.
+ fn id_from_mod_item(mod_item: ModItem) -> Option<FileItemTreeId<Self>>;
+
+ /// Upcasts a `FileItemTreeId` to a generic `ModItem`.
+ fn id_to_mod_item(id: FileItemTreeId<Self>) -> ModItem;
+}
+
+pub struct FileItemTreeId<N: ItemTreeNode> {
+ index: Idx<N>,
+ _p: PhantomData<N>,
+}
+
+impl<N: ItemTreeNode> Clone for FileItemTreeId<N> {
+ fn clone(&self) -> Self {
+ Self { index: self.index, _p: PhantomData }
+ }
+}
+impl<N: ItemTreeNode> Copy for FileItemTreeId<N> {}
+
+impl<N: ItemTreeNode> PartialEq for FileItemTreeId<N> {
+ fn eq(&self, other: &FileItemTreeId<N>) -> bool {
+ self.index == other.index
+ }
+}
+impl<N: ItemTreeNode> Eq for FileItemTreeId<N> {}
+
+impl<N: ItemTreeNode> Hash for FileItemTreeId<N> {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.index.hash(state)
+ }
+}
+
+impl<N: ItemTreeNode> fmt::Debug for FileItemTreeId<N> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ self.index.fmt(f)
+ }
+}
+
+/// Identifies a particular [`ItemTree`].
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
+pub struct TreeId {
+ file: HirFileId,
+ block: Option<BlockId>,
+}
+
+impl TreeId {
+ pub(crate) fn new(file: HirFileId, block: Option<BlockId>) -> Self {
+ Self { file, block }
+ }
+
+ pub(crate) fn item_tree(&self, db: &dyn DefDatabase) -> Arc<ItemTree> {
+ match self.block {
+ Some(block) => ItemTree::block_item_tree(db, block),
+ None => db.file_item_tree(self.file),
+ }
+ }
+
+ pub(crate) fn file_id(self) -> HirFileId {
+ self.file
+ }
+
+ pub(crate) fn is_block(self) -> bool {
+ self.block.is_some()
+ }
+}
+
+#[derive(Debug)]
+pub struct ItemTreeId<N: ItemTreeNode> {
+ tree: TreeId,
+ pub value: FileItemTreeId<N>,
+}
+
+impl<N: ItemTreeNode> ItemTreeId<N> {
+ pub fn new(tree: TreeId, idx: FileItemTreeId<N>) -> Self {
+ Self { tree, value: idx }
+ }
+
+ pub fn file_id(self) -> HirFileId {
+ self.tree.file
+ }
+
+ pub fn tree_id(self) -> TreeId {
+ self.tree
+ }
+
+ pub fn item_tree(self, db: &dyn DefDatabase) -> Arc<ItemTree> {
+ self.tree.item_tree(db)
+ }
+}
+
+impl<N: ItemTreeNode> Copy for ItemTreeId<N> {}
+impl<N: ItemTreeNode> Clone for ItemTreeId<N> {
+ fn clone(&self) -> Self {
+ *self
+ }
+}
+
+impl<N: ItemTreeNode> PartialEq for ItemTreeId<N> {
+ fn eq(&self, other: &Self) -> bool {
+ self.tree == other.tree && self.value == other.value
+ }
+}
+
+impl<N: ItemTreeNode> Eq for ItemTreeId<N> {}
+
+impl<N: ItemTreeNode> Hash for ItemTreeId<N> {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.tree.hash(state);
+ self.value.hash(state);
+ }
+}
+
+macro_rules! mod_items {
+ ( $( $typ:ident in $fld:ident -> $ast:ty ),+ $(,)? ) => {
+ #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+ pub enum ModItem {
+ $(
+ $typ(FileItemTreeId<$typ>),
+ )+
+ }
+
+ $(
+ impl From<FileItemTreeId<$typ>> for ModItem {
+ fn from(id: FileItemTreeId<$typ>) -> ModItem {
+ ModItem::$typ(id)
+ }
+ }
+ )+
+
+ $(
+ impl ItemTreeNode for $typ {
+ type Source = $ast;
+
+ fn ast_id(&self) -> FileAstId<Self::Source> {
+ self.ast_id
+ }
+
+ fn lookup(tree: &ItemTree, index: Idx<Self>) -> &Self {
+ &tree.data().$fld[index]
+ }
+
+ fn id_from_mod_item(mod_item: ModItem) -> Option<FileItemTreeId<Self>> {
+ match mod_item {
+ ModItem::$typ(id) => Some(id),
+ _ => None,
+ }
+ }
+
+ fn id_to_mod_item(id: FileItemTreeId<Self>) -> ModItem {
+ ModItem::$typ(id)
+ }
+ }
+
+ impl Index<Idx<$typ>> for ItemTree {
+ type Output = $typ;
+
+ fn index(&self, index: Idx<$typ>) -> &Self::Output {
+ &self.data().$fld[index]
+ }
+ }
+ )+
+ };
+}
+
+mod_items! {
+ Import in imports -> ast::Use,
+ ExternCrate in extern_crates -> ast::ExternCrate,
+ ExternBlock in extern_blocks -> ast::ExternBlock,
+ Function in functions -> ast::Fn,
+ Struct in structs -> ast::Struct,
+ Union in unions -> ast::Union,
+ Enum in enums -> ast::Enum,
+ Const in consts -> ast::Const,
+ Static in statics -> ast::Static,
+ Trait in traits -> ast::Trait,
+ Impl in impls -> ast::Impl,
+ TypeAlias in type_aliases -> ast::TypeAlias,
+ Mod in mods -> ast::Module,
+ MacroCall in macro_calls -> ast::MacroCall,
+ MacroRules in macro_rules -> ast::MacroRules,
+ MacroDef in macro_defs -> ast::MacroDef,
+}
+
+macro_rules! impl_index {
+ ( $($fld:ident: $t:ty),+ $(,)? ) => {
+ $(
+ impl Index<Idx<$t>> for ItemTree {
+ type Output = $t;
+
+ fn index(&self, index: Idx<$t>) -> &Self::Output {
+ &self.data().$fld[index]
+ }
+ }
+ )+
+ };
+}
+
+impl_index!(fields: Field, variants: Variant, params: Param);
+
+impl Index<RawVisibilityId> for ItemTree {
+ type Output = RawVisibility;
+ fn index(&self, index: RawVisibilityId) -> &Self::Output {
+ match index {
+ RawVisibilityId::PRIV => &VIS_PRIV,
+ RawVisibilityId::PUB => &VIS_PUB,
+ RawVisibilityId::PUB_CRATE => &VIS_PUB_CRATE,
+ _ => &self.data().vis.arena[Idx::from_raw(index.0.into())],
+ }
+ }
+}
+
+impl<N: ItemTreeNode> Index<FileItemTreeId<N>> for ItemTree {
+ type Output = N;
+ fn index(&self, id: FileItemTreeId<N>) -> &N {
+ N::lookup(self, id.index)
+ }
+}
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct Import {
+ pub visibility: RawVisibilityId,
+ pub ast_id: FileAstId<ast::Use>,
+ pub use_tree: UseTree,
+}
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct UseTree {
+ pub index: Idx<ast::UseTree>,
+ kind: UseTreeKind,
+}
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub enum UseTreeKind {
+ /// ```
+ /// use path::to::Item;
+ /// use path::to::Item as Renamed;
+ /// use path::to::Trait as _;
+ /// ```
+ Single { path: Interned<ModPath>, alias: Option<ImportAlias> },
+
+ /// ```
+ /// use *; // (invalid, but can occur in nested tree)
+ /// use path::*;
+ /// ```
+ Glob { path: Option<Interned<ModPath>> },
+
+ /// ```
+ /// use prefix::{self, Item, ...};
+ /// ```
+ Prefixed { prefix: Option<Interned<ModPath>>, list: Box<[UseTree]> },
+}
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct ExternCrate {
+ pub name: Name,
+ pub alias: Option<ImportAlias>,
+ pub visibility: RawVisibilityId,
+ pub ast_id: FileAstId<ast::ExternCrate>,
+}
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct ExternBlock {
+ pub abi: Option<Interned<str>>,
+ pub ast_id: FileAstId<ast::ExternBlock>,
+ pub children: Box<[ModItem]>,
+}
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct Function {
+ pub name: Name,
+ pub visibility: RawVisibilityId,
+ pub explicit_generic_params: Interned<GenericParams>,
+ pub abi: Option<Interned<str>>,
+ pub params: IdxRange<Param>,
+ pub ret_type: Interned<TypeRef>,
+ pub async_ret_type: Option<Interned<TypeRef>>,
+ pub ast_id: FileAstId<ast::Fn>,
+ pub(crate) flags: FnFlags,
+}
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub enum Param {
+ Normal(Option<Name>, Interned<TypeRef>),
+ Varargs,
+}
+
+bitflags::bitflags! {
+ #[derive(Default)]
+ pub(crate) struct FnFlags: u8 {
+ const HAS_SELF_PARAM = 1 << 0;
+ const HAS_BODY = 1 << 1;
+ const HAS_DEFAULT_KW = 1 << 2;
+ const HAS_CONST_KW = 1 << 3;
+ const HAS_ASYNC_KW = 1 << 4;
+ const HAS_UNSAFE_KW = 1 << 5;
+ const IS_VARARGS = 1 << 6;
+ }
+}
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct Struct {
+ pub name: Name,
+ pub visibility: RawVisibilityId,
+ pub generic_params: Interned<GenericParams>,
+ pub fields: Fields,
+ pub ast_id: FileAstId<ast::Struct>,
+}
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct Union {
+ pub name: Name,
+ pub visibility: RawVisibilityId,
+ pub generic_params: Interned<GenericParams>,
+ pub fields: Fields,
+ pub ast_id: FileAstId<ast::Union>,
+}
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct Enum {
+ pub name: Name,
+ pub visibility: RawVisibilityId,
+ pub generic_params: Interned<GenericParams>,
+ pub variants: IdxRange<Variant>,
+ pub ast_id: FileAstId<ast::Enum>,
+}
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct Const {
+ /// `None` for `const _: () = ();`
+ pub name: Option<Name>,
+ pub visibility: RawVisibilityId,
+ pub type_ref: Interned<TypeRef>,
+ pub ast_id: FileAstId<ast::Const>,
+}
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct Static {
+ pub name: Name,
+ pub visibility: RawVisibilityId,
+ pub mutable: bool,
+ pub type_ref: Interned<TypeRef>,
+ pub ast_id: FileAstId<ast::Static>,
+}
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct Trait {
+ pub name: Name,
+ pub visibility: RawVisibilityId,
+ pub generic_params: Interned<GenericParams>,
+ pub is_auto: bool,
+ pub is_unsafe: bool,
+ pub items: Box<[AssocItem]>,
+ pub ast_id: FileAstId<ast::Trait>,
+}
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct Impl {
+ pub generic_params: Interned<GenericParams>,
+ pub target_trait: Option<Interned<TraitRef>>,
+ pub self_ty: Interned<TypeRef>,
+ pub is_negative: bool,
+ pub items: Box<[AssocItem]>,
+ pub ast_id: FileAstId<ast::Impl>,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct TypeAlias {
+ pub name: Name,
+ pub visibility: RawVisibilityId,
+ /// Bounds on the type alias itself. Only valid in trait declarations, eg. `type Assoc: Copy;`.
+ pub bounds: Box<[Interned<TypeBound>]>,
+ pub generic_params: Interned<GenericParams>,
+ pub type_ref: Option<Interned<TypeRef>>,
+ pub ast_id: FileAstId<ast::TypeAlias>,
+}
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct Mod {
+ pub name: Name,
+ pub visibility: RawVisibilityId,
+ pub kind: ModKind,
+ pub ast_id: FileAstId<ast::Module>,
+}
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub enum ModKind {
+ /// `mod m { ... }`
+ Inline { items: Box<[ModItem]> },
+
+ /// `mod m;`
+ Outline,
+}
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct MacroCall {
+ /// Path to the called macro.
+ pub path: Interned<ModPath>,
+ pub ast_id: FileAstId<ast::MacroCall>,
+ pub expand_to: ExpandTo,
+}
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct MacroRules {
+ /// The name of the declared macro.
+ pub name: Name,
+ pub ast_id: FileAstId<ast::MacroRules>,
+}
+
+/// "Macros 2.0" macro definition.
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct MacroDef {
+ pub name: Name,
+ pub visibility: RawVisibilityId,
+ pub ast_id: FileAstId<ast::MacroDef>,
+}
+
+impl Import {
+ /// Maps a `UseTree` contained in this import back to its AST node.
+ pub fn use_tree_to_ast(
+ &self,
+ db: &dyn DefDatabase,
+ file_id: HirFileId,
+ index: Idx<ast::UseTree>,
+ ) -> ast::UseTree {
+ // Re-lower the AST item and get the source map.
+ // Note: The AST unwraps are fine, since if they fail we should have never obtained `index`.
+ let ast = InFile::new(file_id, self.ast_id).to_node(db.upcast());
+ let ast_use_tree = ast.use_tree().expect("missing `use_tree`");
+ let hygiene = Hygiene::new(db.upcast(), file_id);
+ let (_, source_map) =
+ lower::lower_use_tree(db, &hygiene, ast_use_tree).expect("failed to lower use tree");
+ source_map[index].clone()
+ }
+}
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum ImportKind {
+ /// The `ModPath` is imported normally.
+ Plain,
+ /// This is a glob-import of all names in the `ModPath`.
+ Glob,
+ /// This is a `some::path::self` import, which imports `some::path` only in type namespace.
+ TypeOnly,
+}
+
+impl UseTree {
+ /// Expands the `UseTree` into individually imported `ModPath`s.
+ pub fn expand(
+ &self,
+ mut cb: impl FnMut(Idx<ast::UseTree>, ModPath, ImportKind, Option<ImportAlias>),
+ ) {
+ self.expand_impl(None, &mut cb)
+ }
+
+ fn expand_impl(
+ &self,
+ prefix: Option<ModPath>,
+ cb: &mut dyn FnMut(Idx<ast::UseTree>, ModPath, ImportKind, Option<ImportAlias>),
+ ) {
+ fn concat_mod_paths(
+ prefix: Option<ModPath>,
+ path: &ModPath,
+ ) -> Option<(ModPath, ImportKind)> {
+ match (prefix, &path.kind) {
+ (None, _) => Some((path.clone(), ImportKind::Plain)),
+ (Some(mut prefix), PathKind::Plain) => {
+ for segment in path.segments() {
+ prefix.push_segment(segment.clone());
+ }
+ Some((prefix, ImportKind::Plain))
+ }
+ (Some(mut prefix), PathKind::Super(n))
+ if *n > 0 && prefix.segments().is_empty() =>
+ {
+ // `super::super` + `super::rest`
+ match &mut prefix.kind {
+ PathKind::Super(m) => {
+ cov_mark::hit!(concat_super_mod_paths);
+ *m += *n;
+ for segment in path.segments() {
+ prefix.push_segment(segment.clone());
+ }
+ Some((prefix, ImportKind::Plain))
+ }
+ _ => None,
+ }
+ }
+ (Some(prefix), PathKind::Super(0)) if path.segments().is_empty() => {
+ // `some::path::self` == `some::path`
+ Some((prefix, ImportKind::TypeOnly))
+ }
+ (Some(_), _) => None,
+ }
+ }
+
+ match &self.kind {
+ UseTreeKind::Single { path, alias } => {
+ if let Some((path, kind)) = concat_mod_paths(prefix, path) {
+ cb(self.index, path, kind, alias.clone());
+ }
+ }
+ UseTreeKind::Glob { path: Some(path) } => {
+ if let Some((path, _)) = concat_mod_paths(prefix, path) {
+ cb(self.index, path, ImportKind::Glob, None);
+ }
+ }
+ UseTreeKind::Glob { path: None } => {
+ if let Some(prefix) = prefix {
+ cb(self.index, prefix, ImportKind::Glob, None);
+ }
+ }
+ UseTreeKind::Prefixed { prefix: additional_prefix, list } => {
+ let prefix = match additional_prefix {
+ Some(path) => match concat_mod_paths(prefix, path) {
+ Some((path, ImportKind::Plain)) => Some(path),
+ _ => return,
+ },
+ None => prefix,
+ };
+ for tree in &**list {
+ tree.expand_impl(prefix.clone(), cb);
+ }
+ }
+ }
+ }
+}
+
+macro_rules! impl_froms {
+ ($e:ident { $($v:ident ($t:ty)),* $(,)? }) => {
+ $(
+ impl From<$t> for $e {
+ fn from(it: $t) -> $e {
+ $e::$v(it)
+ }
+ }
+ )*
+ }
+}
+
+impl ModItem {
+ pub fn as_assoc_item(&self) -> Option<AssocItem> {
+ match self {
+ ModItem::Import(_)
+ | ModItem::ExternCrate(_)
+ | ModItem::ExternBlock(_)
+ | ModItem::Struct(_)
+ | ModItem::Union(_)
+ | ModItem::Enum(_)
+ | ModItem::Static(_)
+ | ModItem::Trait(_)
+ | ModItem::Impl(_)
+ | ModItem::Mod(_)
+ | ModItem::MacroRules(_)
+ | ModItem::MacroDef(_) => None,
+ ModItem::MacroCall(call) => Some(AssocItem::MacroCall(*call)),
+ ModItem::Const(konst) => Some(AssocItem::Const(*konst)),
+ ModItem::TypeAlias(alias) => Some(AssocItem::TypeAlias(*alias)),
+ ModItem::Function(func) => Some(AssocItem::Function(*func)),
+ }
+ }
+
+ pub fn downcast<N: ItemTreeNode>(self) -> Option<FileItemTreeId<N>> {
+ N::id_from_mod_item(self)
+ }
+
+ pub fn ast_id(&self, tree: &ItemTree) -> FileAstId<ast::Item> {
+ match self {
+ ModItem::Import(it) => tree[it.index].ast_id().upcast(),
+ ModItem::ExternCrate(it) => tree[it.index].ast_id().upcast(),
+ ModItem::ExternBlock(it) => tree[it.index].ast_id().upcast(),
+ ModItem::Function(it) => tree[it.index].ast_id().upcast(),
+ ModItem::Struct(it) => tree[it.index].ast_id().upcast(),
+ ModItem::Union(it) => tree[it.index].ast_id().upcast(),
+ ModItem::Enum(it) => tree[it.index].ast_id().upcast(),
+ ModItem::Const(it) => tree[it.index].ast_id().upcast(),
+ ModItem::Static(it) => tree[it.index].ast_id().upcast(),
+ ModItem::Trait(it) => tree[it.index].ast_id().upcast(),
+ ModItem::Impl(it) => tree[it.index].ast_id().upcast(),
+ ModItem::TypeAlias(it) => tree[it.index].ast_id().upcast(),
+ ModItem::Mod(it) => tree[it.index].ast_id().upcast(),
+ ModItem::MacroCall(it) => tree[it.index].ast_id().upcast(),
+ ModItem::MacroRules(it) => tree[it.index].ast_id().upcast(),
+ ModItem::MacroDef(it) => tree[it.index].ast_id().upcast(),
+ }
+ }
+}
+
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+pub enum AssocItem {
+ Function(FileItemTreeId<Function>),
+ TypeAlias(FileItemTreeId<TypeAlias>),
+ Const(FileItemTreeId<Const>),
+ MacroCall(FileItemTreeId<MacroCall>),
+}
+
+impl_froms!(AssocItem {
+ Function(FileItemTreeId<Function>),
+ TypeAlias(FileItemTreeId<TypeAlias>),
+ Const(FileItemTreeId<Const>),
+ MacroCall(FileItemTreeId<MacroCall>),
+});
+
+impl From<AssocItem> for ModItem {
+ fn from(item: AssocItem) -> Self {
+ match item {
+ AssocItem::Function(it) => it.into(),
+ AssocItem::TypeAlias(it) => it.into(),
+ AssocItem::Const(it) => it.into(),
+ AssocItem::MacroCall(it) => it.into(),
+ }
+ }
+}
+
+impl AssocItem {
+ pub fn ast_id(self, tree: &ItemTree) -> FileAstId<ast::AssocItem> {
+ match self {
+ AssocItem::Function(id) => tree[id].ast_id.upcast(),
+ AssocItem::TypeAlias(id) => tree[id].ast_id.upcast(),
+ AssocItem::Const(id) => tree[id].ast_id.upcast(),
+ AssocItem::MacroCall(id) => tree[id].ast_id.upcast(),
+ }
+ }
+}
+
+#[derive(Debug, Eq, PartialEq)]
+pub struct Variant {
+ pub name: Name,
+ pub fields: Fields,
++ pub ast_id: FileAstId<ast::Variant>,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum Fields {
+ Record(IdxRange<Field>),
+ Tuple(IdxRange<Field>),
+ Unit,
+}
+
++#[derive(Debug, Clone, PartialEq, Eq)]
++pub enum FieldAstId {
++ Record(FileAstId<ast::RecordField>),
++ Tuple(FileAstId<ast::TupleField>),
++}
++
+/// A single field of an enum variant or struct
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct Field {
+ pub name: Name,
+ pub type_ref: Interned<TypeRef>,
+ pub visibility: RawVisibilityId,
++ pub ast_id: FieldAstId,
+}
--- /dev/null
- let res = Field { name, type_ref, visibility };
+//! AST -> `ItemTree` lowering code.
+
+use std::{collections::hash_map::Entry, sync::Arc};
+
+use hir_expand::{ast_id_map::AstIdMap, hygiene::Hygiene, HirFileId};
+use syntax::ast::{self, HasModuleItem};
+
+use crate::{
+ generics::{GenericParams, TypeParamData, TypeParamProvenance},
+ type_ref::{LifetimeRef, TraitBoundModifier, TraitRef},
+};
+
+use super::*;
+
+fn id<N: ItemTreeNode>(index: Idx<N>) -> FileItemTreeId<N> {
+ FileItemTreeId { index, _p: PhantomData }
+}
+
+pub(super) struct Ctx<'a> {
+ db: &'a dyn DefDatabase,
+ tree: ItemTree,
+ source_ast_id_map: Arc<AstIdMap>,
+ body_ctx: crate::body::LowerCtx<'a>,
+}
+
+impl<'a> Ctx<'a> {
+ pub(super) fn new(db: &'a dyn DefDatabase, file: HirFileId) -> Self {
+ Self {
+ db,
+ tree: ItemTree::default(),
+ source_ast_id_map: db.ast_id_map(file),
+ body_ctx: crate::body::LowerCtx::new(db, file),
+ }
+ }
+
+ pub(super) fn hygiene(&self) -> &Hygiene {
+ self.body_ctx.hygiene()
+ }
+
+ pub(super) fn lower_module_items(mut self, item_owner: &dyn HasModuleItem) -> ItemTree {
+ self.tree.top_level =
+ item_owner.items().flat_map(|item| self.lower_mod_item(&item)).collect();
+ self.tree
+ }
+
+ pub(super) fn lower_macro_stmts(mut self, stmts: ast::MacroStmts) -> ItemTree {
+ self.tree.top_level = stmts
+ .statements()
+ .filter_map(|stmt| {
+ match stmt {
+ ast::Stmt::Item(item) => Some(item),
+ // Macro calls can be both items and expressions. The syntax library always treats
+ // them as expressions here, so we undo that.
+ ast::Stmt::ExprStmt(es) => match es.expr()? {
+ ast::Expr::MacroExpr(expr) => {
+ cov_mark::hit!(macro_call_in_macro_stmts_is_added_to_item_tree);
+ Some(expr.macro_call()?.into())
+ }
+ _ => None,
+ },
+ _ => None,
+ }
+ })
+ .flat_map(|item| self.lower_mod_item(&item))
+ .collect();
+
+ if let Some(ast::Expr::MacroExpr(tail_macro)) = stmts.expr() {
+ if let Some(call) = tail_macro.macro_call() {
+ cov_mark::hit!(macro_stmt_with_trailing_macro_expr);
+ if let Some(mod_item) = self.lower_mod_item(&call.into()) {
+ self.tree.top_level.push(mod_item);
+ }
+ }
+ }
+
+ self.tree
+ }
+
+ pub(super) fn lower_block(mut self, block: &ast::BlockExpr) -> ItemTree {
+ self.tree.top_level = block
+ .statements()
+ .filter_map(|stmt| match stmt {
+ ast::Stmt::Item(item) => self.lower_mod_item(&item),
+ // Macro calls can be both items and expressions. The syntax library always treats
+ // them as expressions here, so we undo that.
+ ast::Stmt::ExprStmt(es) => match es.expr()? {
+ ast::Expr::MacroExpr(expr) => self.lower_mod_item(&expr.macro_call()?.into()),
+ _ => None,
+ },
+ _ => None,
+ })
+ .collect();
+
+ self.tree
+ }
+
+ fn data(&mut self) -> &mut ItemTreeData {
+ self.tree.data_mut()
+ }
+
+ fn lower_mod_item(&mut self, item: &ast::Item) -> Option<ModItem> {
+ let attrs = RawAttrs::new(self.db, item, self.hygiene());
+ let item: ModItem = match item {
+ ast::Item::Struct(ast) => self.lower_struct(ast)?.into(),
+ ast::Item::Union(ast) => self.lower_union(ast)?.into(),
+ ast::Item::Enum(ast) => self.lower_enum(ast)?.into(),
+ ast::Item::Fn(ast) => self.lower_function(ast)?.into(),
+ ast::Item::TypeAlias(ast) => self.lower_type_alias(ast)?.into(),
+ ast::Item::Static(ast) => self.lower_static(ast)?.into(),
+ ast::Item::Const(ast) => self.lower_const(ast).into(),
+ ast::Item::Module(ast) => self.lower_module(ast)?.into(),
+ ast::Item::Trait(ast) => self.lower_trait(ast)?.into(),
+ ast::Item::Impl(ast) => self.lower_impl(ast)?.into(),
+ ast::Item::Use(ast) => self.lower_use(ast)?.into(),
+ ast::Item::ExternCrate(ast) => self.lower_extern_crate(ast)?.into(),
+ ast::Item::MacroCall(ast) => self.lower_macro_call(ast)?.into(),
+ ast::Item::MacroRules(ast) => self.lower_macro_rules(ast)?.into(),
+ ast::Item::MacroDef(ast) => self.lower_macro_def(ast)?.into(),
+ ast::Item::ExternBlock(ast) => self.lower_extern_block(ast).into(),
+ };
+
+ self.add_attrs(item.into(), attrs);
+
+ Some(item)
+ }
+
+ fn add_attrs(&mut self, item: AttrOwner, attrs: RawAttrs) {
+ match self.tree.attrs.entry(item) {
+ Entry::Occupied(mut entry) => {
+ *entry.get_mut() = entry.get().merge(attrs);
+ }
+ Entry::Vacant(entry) => {
+ entry.insert(attrs);
+ }
+ }
+ }
+
+ fn lower_assoc_item(&mut self, item: &ast::AssocItem) -> Option<AssocItem> {
+ match item {
+ ast::AssocItem::Fn(ast) => self.lower_function(ast).map(Into::into),
+ ast::AssocItem::TypeAlias(ast) => self.lower_type_alias(ast).map(Into::into),
+ ast::AssocItem::Const(ast) => Some(self.lower_const(ast).into()),
+ ast::AssocItem::MacroCall(ast) => self.lower_macro_call(ast).map(Into::into),
+ }
+ }
+
+ fn lower_struct(&mut self, strukt: &ast::Struct) -> Option<FileItemTreeId<Struct>> {
+ let visibility = self.lower_visibility(strukt);
+ let name = strukt.name()?.as_name();
+ let generic_params = self.lower_generic_params(GenericsOwner::Struct, strukt);
+ let fields = self.lower_fields(&strukt.kind());
+ let ast_id = self.source_ast_id_map.ast_id(strukt);
+ let res = Struct { name, visibility, generic_params, fields, ast_id };
+ Some(id(self.data().structs.alloc(res)))
+ }
+
+ fn lower_fields(&mut self, strukt_kind: &ast::StructKind) -> Fields {
+ match strukt_kind {
+ ast::StructKind::Record(it) => {
+ let range = self.lower_record_fields(it);
+ Fields::Record(range)
+ }
+ ast::StructKind::Tuple(it) => {
+ let range = self.lower_tuple_fields(it);
+ Fields::Tuple(range)
+ }
+ ast::StructKind::Unit => Fields::Unit,
+ }
+ }
+
+ fn lower_record_fields(&mut self, fields: &ast::RecordFieldList) -> IdxRange<Field> {
+ let start = self.next_field_idx();
+ for field in fields.fields() {
+ if let Some(data) = self.lower_record_field(&field) {
+ let idx = self.data().fields.alloc(data);
+ self.add_attrs(idx.into(), RawAttrs::new(self.db, &field, self.hygiene()));
+ }
+ }
+ let end = self.next_field_idx();
+ IdxRange::new(start..end)
+ }
+
+ fn lower_record_field(&mut self, field: &ast::RecordField) -> Option<Field> {
+ let name = field.name()?.as_name();
+ let visibility = self.lower_visibility(field);
+ let type_ref = self.lower_type_ref_opt(field.ty());
- Field { name, type_ref, visibility }
++ let ast_id = FieldAstId::Record(self.source_ast_id_map.ast_id(field));
++ let res = Field { name, type_ref, visibility, ast_id };
+ Some(res)
+ }
+
+ fn lower_tuple_fields(&mut self, fields: &ast::TupleFieldList) -> IdxRange<Field> {
+ let start = self.next_field_idx();
+ for (i, field) in fields.fields().enumerate() {
+ let data = self.lower_tuple_field(i, &field);
+ let idx = self.data().fields.alloc(data);
+ self.add_attrs(idx.into(), RawAttrs::new(self.db, &field, self.hygiene()));
+ }
+ let end = self.next_field_idx();
+ IdxRange::new(start..end)
+ }
+
+ fn lower_tuple_field(&mut self, idx: usize, field: &ast::TupleField) -> Field {
+ let name = Name::new_tuple_field(idx);
+ let visibility = self.lower_visibility(field);
+ let type_ref = self.lower_type_ref_opt(field.ty());
- let res = Variant { name, fields };
++ let ast_id = FieldAstId::Tuple(self.source_ast_id_map.ast_id(field));
++ Field { name, type_ref, visibility, ast_id }
+ }
+
+ fn lower_union(&mut self, union: &ast::Union) -> Option<FileItemTreeId<Union>> {
+ let visibility = self.lower_visibility(union);
+ let name = union.name()?.as_name();
+ let generic_params = self.lower_generic_params(GenericsOwner::Union, union);
+ let fields = match union.record_field_list() {
+ Some(record_field_list) => self.lower_fields(&StructKind::Record(record_field_list)),
+ None => Fields::Record(IdxRange::new(self.next_field_idx()..self.next_field_idx())),
+ };
+ let ast_id = self.source_ast_id_map.ast_id(union);
+ let res = Union { name, visibility, generic_params, fields, ast_id };
+ Some(id(self.data().unions.alloc(res)))
+ }
+
+ fn lower_enum(&mut self, enum_: &ast::Enum) -> Option<FileItemTreeId<Enum>> {
+ let visibility = self.lower_visibility(enum_);
+ let name = enum_.name()?.as_name();
+ let generic_params = self.lower_generic_params(GenericsOwner::Enum, enum_);
+ let variants = match &enum_.variant_list() {
+ Some(variant_list) => self.lower_variants(variant_list),
+ None => IdxRange::new(self.next_variant_idx()..self.next_variant_idx()),
+ };
+ let ast_id = self.source_ast_id_map.ast_id(enum_);
+ let res = Enum { name, visibility, generic_params, variants, ast_id };
+ Some(id(self.data().enums.alloc(res)))
+ }
+
+ fn lower_variants(&mut self, variants: &ast::VariantList) -> IdxRange<Variant> {
+ let start = self.next_variant_idx();
+ for variant in variants.variants() {
+ if let Some(data) = self.lower_variant(&variant) {
+ let idx = self.data().variants.alloc(data);
+ self.add_attrs(idx.into(), RawAttrs::new(self.db, &variant, self.hygiene()));
+ }
+ }
+ let end = self.next_variant_idx();
+ IdxRange::new(start..end)
+ }
+
+ fn lower_variant(&mut self, variant: &ast::Variant) -> Option<Variant> {
+ let name = variant.name()?.as_name();
+ let fields = self.lower_fields(&variant.kind());
++ let ast_id = self.source_ast_id_map.ast_id(variant);
++ let res = Variant { name, fields, ast_id };
+ Some(res)
+ }
+
+ fn lower_function(&mut self, func: &ast::Fn) -> Option<FileItemTreeId<Function>> {
+ let visibility = self.lower_visibility(func);
+ let name = func.name()?.as_name();
+
+ let mut has_self_param = false;
+ let start_param = self.next_param_idx();
+ if let Some(param_list) = func.param_list() {
+ if let Some(self_param) = param_list.self_param() {
+ let self_type = match self_param.ty() {
+ Some(type_ref) => TypeRef::from_ast(&self.body_ctx, type_ref),
+ None => {
+ let self_type = TypeRef::Path(name![Self].into());
+ match self_param.kind() {
+ ast::SelfParamKind::Owned => self_type,
+ ast::SelfParamKind::Ref => TypeRef::Reference(
+ Box::new(self_type),
+ self_param.lifetime().as_ref().map(LifetimeRef::new),
+ Mutability::Shared,
+ ),
+ ast::SelfParamKind::MutRef => TypeRef::Reference(
+ Box::new(self_type),
+ self_param.lifetime().as_ref().map(LifetimeRef::new),
+ Mutability::Mut,
+ ),
+ }
+ }
+ };
+ let ty = Interned::new(self_type);
+ let idx = self.data().params.alloc(Param::Normal(None, ty));
+ self.add_attrs(idx.into(), RawAttrs::new(self.db, &self_param, self.hygiene()));
+ has_self_param = true;
+ }
+ for param in param_list.params() {
+ let idx = match param.dotdotdot_token() {
+ Some(_) => self.data().params.alloc(Param::Varargs),
+ None => {
+ let type_ref = TypeRef::from_ast_opt(&self.body_ctx, param.ty());
+ let ty = Interned::new(type_ref);
+ let mut pat = param.pat();
+ // FIXME: This really shouldn't be here, in fact FunctionData/ItemTree's function shouldn't know about
+ // pattern names at all
+ let name = 'name: loop {
+ match pat {
+ Some(ast::Pat::RefPat(ref_pat)) => pat = ref_pat.pat(),
+ Some(ast::Pat::IdentPat(ident)) => {
+ break 'name ident.name().map(|it| it.as_name())
+ }
+ _ => break 'name None,
+ }
+ };
+ self.data().params.alloc(Param::Normal(name, ty))
+ }
+ };
+ self.add_attrs(idx.into(), RawAttrs::new(self.db, ¶m, self.hygiene()));
+ }
+ }
+ let end_param = self.next_param_idx();
+ let params = IdxRange::new(start_param..end_param);
+
+ let ret_type = match func.ret_type() {
+ Some(rt) => match rt.ty() {
+ Some(type_ref) => TypeRef::from_ast(&self.body_ctx, type_ref),
+ None if rt.thin_arrow_token().is_some() => TypeRef::Error,
+ None => TypeRef::unit(),
+ },
+ None => TypeRef::unit(),
+ };
+
+ let (ret_type, async_ret_type) = if func.async_token().is_some() {
+ let async_ret_type = ret_type.clone();
+ let future_impl = desugar_future_path(ret_type);
+ let ty_bound = Interned::new(TypeBound::Path(future_impl, TraitBoundModifier::None));
+ (TypeRef::ImplTrait(vec![ty_bound]), Some(async_ret_type))
+ } else {
+ (ret_type, None)
+ };
+
+ let abi = func.abi().map(lower_abi);
+
+ let ast_id = self.source_ast_id_map.ast_id(func);
+
+ let mut flags = FnFlags::default();
+ if func.body().is_some() {
+ flags |= FnFlags::HAS_BODY;
+ }
+ if has_self_param {
+ flags |= FnFlags::HAS_SELF_PARAM;
+ }
+ if func.default_token().is_some() {
+ flags |= FnFlags::HAS_DEFAULT_KW;
+ }
+ if func.const_token().is_some() {
+ flags |= FnFlags::HAS_CONST_KW;
+ }
+ if func.async_token().is_some() {
+ flags |= FnFlags::HAS_ASYNC_KW;
+ }
+ if func.unsafe_token().is_some() {
+ flags |= FnFlags::HAS_UNSAFE_KW;
+ }
+
+ let mut res = Function {
+ name,
+ visibility,
+ explicit_generic_params: Interned::new(GenericParams::default()),
+ abi,
+ params,
+ ret_type: Interned::new(ret_type),
+ async_ret_type: async_ret_type.map(Interned::new),
+ ast_id,
+ flags,
+ };
+ res.explicit_generic_params =
+ self.lower_generic_params(GenericsOwner::Function(&res), func);
+
+ Some(id(self.data().functions.alloc(res)))
+ }
+
+ fn lower_type_alias(
+ &mut self,
+ type_alias: &ast::TypeAlias,
+ ) -> Option<FileItemTreeId<TypeAlias>> {
+ let name = type_alias.name()?.as_name();
+ let type_ref = type_alias.ty().map(|it| self.lower_type_ref(&it));
+ let visibility = self.lower_visibility(type_alias);
+ let bounds = self.lower_type_bounds(type_alias);
+ let generic_params = self.lower_generic_params(GenericsOwner::TypeAlias, type_alias);
+ let ast_id = self.source_ast_id_map.ast_id(type_alias);
+ let res = TypeAlias {
+ name,
+ visibility,
+ bounds: bounds.into_boxed_slice(),
+ generic_params,
+ type_ref,
+ ast_id,
+ };
+ Some(id(self.data().type_aliases.alloc(res)))
+ }
+
+ fn lower_static(&mut self, static_: &ast::Static) -> Option<FileItemTreeId<Static>> {
+ let name = static_.name()?.as_name();
+ let type_ref = self.lower_type_ref_opt(static_.ty());
+ let visibility = self.lower_visibility(static_);
+ let mutable = static_.mut_token().is_some();
+ let ast_id = self.source_ast_id_map.ast_id(static_);
+ let res = Static { name, visibility, mutable, type_ref, ast_id };
+ Some(id(self.data().statics.alloc(res)))
+ }
+
+ fn lower_const(&mut self, konst: &ast::Const) -> FileItemTreeId<Const> {
+ let name = konst.name().map(|it| it.as_name());
+ let type_ref = self.lower_type_ref_opt(konst.ty());
+ let visibility = self.lower_visibility(konst);
+ let ast_id = self.source_ast_id_map.ast_id(konst);
+ let res = Const { name, visibility, type_ref, ast_id };
+ id(self.data().consts.alloc(res))
+ }
+
+ fn lower_module(&mut self, module: &ast::Module) -> Option<FileItemTreeId<Mod>> {
+ let name = module.name()?.as_name();
+ let visibility = self.lower_visibility(module);
+ let kind = if module.semicolon_token().is_some() {
+ ModKind::Outline
+ } else {
+ ModKind::Inline {
+ items: module
+ .item_list()
+ .map(|list| list.items().flat_map(|item| self.lower_mod_item(&item)).collect())
+ .unwrap_or_else(|| {
+ cov_mark::hit!(name_res_works_for_broken_modules);
+ Box::new([]) as Box<[_]>
+ }),
+ }
+ };
+ let ast_id = self.source_ast_id_map.ast_id(module);
+ let res = Mod { name, visibility, kind, ast_id };
+ Some(id(self.data().mods.alloc(res)))
+ }
+
+ fn lower_trait(&mut self, trait_def: &ast::Trait) -> Option<FileItemTreeId<Trait>> {
+ let name = trait_def.name()?.as_name();
+ let visibility = self.lower_visibility(trait_def);
+ let generic_params = self.lower_generic_params(GenericsOwner::Trait(trait_def), trait_def);
+ let is_auto = trait_def.auto_token().is_some();
+ let is_unsafe = trait_def.unsafe_token().is_some();
+ let items = trait_def.assoc_item_list().map(|list| {
+ list.assoc_items()
+ .filter_map(|item| {
+ let attrs = RawAttrs::new(self.db, &item, self.hygiene());
+ self.lower_assoc_item(&item).map(|item| {
+ self.add_attrs(ModItem::from(item).into(), attrs);
+ item
+ })
+ })
+ .collect()
+ });
+ let ast_id = self.source_ast_id_map.ast_id(trait_def);
+ let res = Trait {
+ name,
+ visibility,
+ generic_params,
+ is_auto,
+ is_unsafe,
+ items: items.unwrap_or_default(),
+ ast_id,
+ };
+ Some(id(self.data().traits.alloc(res)))
+ }
+
+ fn lower_impl(&mut self, impl_def: &ast::Impl) -> Option<FileItemTreeId<Impl>> {
+ let generic_params = self.lower_generic_params(GenericsOwner::Impl, impl_def);
+ // FIXME: If trait lowering fails, due to a non PathType for example, we treat this impl
+ // as if it was an non-trait impl. Ideally we want to create a unique missing ref that only
+ // equals itself.
+ let target_trait = impl_def.trait_().and_then(|tr| self.lower_trait_ref(&tr));
+ let self_ty = self.lower_type_ref(&impl_def.self_ty()?);
+ let is_negative = impl_def.excl_token().is_some();
+
+ // We cannot use `assoc_items()` here as that does not include macro calls.
+ let items = impl_def
+ .assoc_item_list()
+ .into_iter()
+ .flat_map(|it| it.assoc_items())
+ .filter_map(|item| {
+ let assoc = self.lower_assoc_item(&item)?;
+ let attrs = RawAttrs::new(self.db, &item, self.hygiene());
+ self.add_attrs(ModItem::from(assoc).into(), attrs);
+ Some(assoc)
+ })
+ .collect();
+ let ast_id = self.source_ast_id_map.ast_id(impl_def);
+ let res = Impl { generic_params, target_trait, self_ty, is_negative, items, ast_id };
+ Some(id(self.data().impls.alloc(res)))
+ }
+
+ fn lower_use(&mut self, use_item: &ast::Use) -> Option<FileItemTreeId<Import>> {
+ let visibility = self.lower_visibility(use_item);
+ let ast_id = self.source_ast_id_map.ast_id(use_item);
+ let (use_tree, _) = lower_use_tree(self.db, self.hygiene(), use_item.use_tree()?)?;
+
+ let res = Import { visibility, ast_id, use_tree };
+ Some(id(self.data().imports.alloc(res)))
+ }
+
+ fn lower_extern_crate(
+ &mut self,
+ extern_crate: &ast::ExternCrate,
+ ) -> Option<FileItemTreeId<ExternCrate>> {
+ let name = extern_crate.name_ref()?.as_name();
+ let alias = extern_crate.rename().map(|a| {
+ a.name().map(|it| it.as_name()).map_or(ImportAlias::Underscore, ImportAlias::Alias)
+ });
+ let visibility = self.lower_visibility(extern_crate);
+ let ast_id = self.source_ast_id_map.ast_id(extern_crate);
+
+ let res = ExternCrate { name, alias, visibility, ast_id };
+ Some(id(self.data().extern_crates.alloc(res)))
+ }
+
+ fn lower_macro_call(&mut self, m: &ast::MacroCall) -> Option<FileItemTreeId<MacroCall>> {
+ let path = Interned::new(ModPath::from_src(self.db.upcast(), m.path()?, self.hygiene())?);
+ let ast_id = self.source_ast_id_map.ast_id(m);
+ let expand_to = hir_expand::ExpandTo::from_call_site(m);
+ let res = MacroCall { path, ast_id, expand_to };
+ Some(id(self.data().macro_calls.alloc(res)))
+ }
+
+ fn lower_macro_rules(&mut self, m: &ast::MacroRules) -> Option<FileItemTreeId<MacroRules>> {
+ let name = m.name().map(|it| it.as_name())?;
+ let ast_id = self.source_ast_id_map.ast_id(m);
+
+ let res = MacroRules { name, ast_id };
+ Some(id(self.data().macro_rules.alloc(res)))
+ }
+
+ fn lower_macro_def(&mut self, m: &ast::MacroDef) -> Option<FileItemTreeId<MacroDef>> {
+ let name = m.name().map(|it| it.as_name())?;
+
+ let ast_id = self.source_ast_id_map.ast_id(m);
+ let visibility = self.lower_visibility(m);
+
+ let res = MacroDef { name, ast_id, visibility };
+ Some(id(self.data().macro_defs.alloc(res)))
+ }
+
+ fn lower_extern_block(&mut self, block: &ast::ExternBlock) -> FileItemTreeId<ExternBlock> {
+ let ast_id = self.source_ast_id_map.ast_id(block);
+ let abi = block.abi().map(lower_abi);
+ let children: Box<[_]> = block.extern_item_list().map_or(Box::new([]), |list| {
+ list.extern_items()
+ .filter_map(|item| {
+ // Note: All items in an `extern` block need to be lowered as if they're outside of one
+ // (in other words, the knowledge that they're in an extern block must not be used).
+ // This is because an extern block can contain macros whose ItemTree's top-level items
+ // should be considered to be in an extern block too.
+ let attrs = RawAttrs::new(self.db, &item, self.hygiene());
+ let id: ModItem = match item {
+ ast::ExternItem::Fn(ast) => self.lower_function(&ast)?.into(),
+ ast::ExternItem::Static(ast) => self.lower_static(&ast)?.into(),
+ ast::ExternItem::TypeAlias(ty) => self.lower_type_alias(&ty)?.into(),
+ ast::ExternItem::MacroCall(call) => self.lower_macro_call(&call)?.into(),
+ };
+ self.add_attrs(id.into(), attrs);
+ Some(id)
+ })
+ .collect()
+ });
+
+ let res = ExternBlock { abi, ast_id, children };
+ id(self.data().extern_blocks.alloc(res))
+ }
+
+ fn lower_generic_params(
+ &mut self,
+ owner: GenericsOwner<'_>,
+ node: &dyn ast::HasGenericParams,
+ ) -> Interned<GenericParams> {
+ let mut generics = GenericParams::default();
+ match owner {
+ GenericsOwner::Function(_)
+ | GenericsOwner::Struct
+ | GenericsOwner::Enum
+ | GenericsOwner::Union
+ | GenericsOwner::TypeAlias => {
+ generics.fill(&self.body_ctx, node);
+ }
+ GenericsOwner::Trait(trait_def) => {
+ // traits get the Self type as an implicit first type parameter
+ generics.type_or_consts.alloc(
+ TypeParamData {
+ name: Some(name![Self]),
+ default: None,
+ provenance: TypeParamProvenance::TraitSelf,
+ }
+ .into(),
+ );
+ // 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(name![Self].into());
+ generics.fill_bounds(&self.body_ctx, trait_def, Either::Left(self_param));
+ generics.fill(&self.body_ctx, node);
+ }
+ GenericsOwner::Impl => {
+ // Note that we don't add `Self` here: in `impl`s, `Self` is not a
+ // type-parameter, but rather is a type-alias for impl's target
+ // type, so this is handled by the resolver.
+ generics.fill(&self.body_ctx, node);
+ }
+ }
+
+ generics.shrink_to_fit();
+ Interned::new(generics)
+ }
+
+ fn lower_type_bounds(&mut self, node: &dyn ast::HasTypeBounds) -> Vec<Interned<TypeBound>> {
+ match node.type_bound_list() {
+ Some(bound_list) => bound_list
+ .bounds()
+ .map(|it| Interned::new(TypeBound::from_ast(&self.body_ctx, it)))
+ .collect(),
+ None => Vec::new(),
+ }
+ }
+
+ fn lower_visibility(&mut self, item: &dyn ast::HasVisibility) -> RawVisibilityId {
+ let vis = RawVisibility::from_ast_with_hygiene(self.db, item.visibility(), self.hygiene());
+ self.data().vis.alloc(vis)
+ }
+
+ fn lower_trait_ref(&mut self, trait_ref: &ast::Type) -> Option<Interned<TraitRef>> {
+ let trait_ref = TraitRef::from_ast(&self.body_ctx, trait_ref.clone())?;
+ Some(Interned::new(trait_ref))
+ }
+
+ fn lower_type_ref(&mut self, type_ref: &ast::Type) -> Interned<TypeRef> {
+ let tyref = TypeRef::from_ast(&self.body_ctx, type_ref.clone());
+ Interned::new(tyref)
+ }
+
+ fn lower_type_ref_opt(&mut self, type_ref: Option<ast::Type>) -> Interned<TypeRef> {
+ match type_ref.map(|ty| self.lower_type_ref(&ty)) {
+ Some(it) => it,
+ None => Interned::new(TypeRef::Error),
+ }
+ }
+
+ fn next_field_idx(&self) -> Idx<Field> {
+ Idx::from_raw(RawIdx::from(
+ self.tree.data.as_ref().map_or(0, |data| data.fields.len() as u32),
+ ))
+ }
+ fn next_variant_idx(&self) -> Idx<Variant> {
+ Idx::from_raw(RawIdx::from(
+ self.tree.data.as_ref().map_or(0, |data| data.variants.len() as u32),
+ ))
+ }
+ fn next_param_idx(&self) -> Idx<Param> {
+ Idx::from_raw(RawIdx::from(
+ self.tree.data.as_ref().map_or(0, |data| data.params.len() as u32),
+ ))
+ }
+}
+
+fn desugar_future_path(orig: TypeRef) -> Path {
+ let path = path![core::future::Future];
+ let mut generic_args: Vec<_> =
+ std::iter::repeat(None).take(path.segments().len() - 1).collect();
+ let mut last = GenericArgs::empty();
+ let binding =
+ AssociatedTypeBinding { name: name![Output], type_ref: Some(orig), bounds: Vec::new() };
+ last.bindings.push(binding);
+ generic_args.push(Some(Interned::new(last)));
+
+ Path::from_known_path(path, generic_args)
+}
+
+enum GenericsOwner<'a> {
+ /// We need access to the partially-lowered `Function` for lowering `impl Trait` in argument
+ /// position.
+ Function(&'a Function),
+ Struct,
+ Enum,
+ Union,
+ /// The `TraitDef` is needed to fill the source map for the implicit `Self` parameter.
+ Trait(&'a ast::Trait),
+ TypeAlias,
+ Impl,
+}
+
+fn lower_abi(abi: ast::Abi) -> Interned<str> {
+ // FIXME: Abi::abi() -> Option<SyntaxToken>?
+ match abi.syntax().last_token() {
+ Some(tok) if tok.kind() == SyntaxKind::STRING => {
+ // FIXME: Better way to unescape?
+ Interned::new_str(tok.text().trim_matches('"'))
+ }
+ _ => {
+ // `extern` default to be `extern "C"`.
+ Interned::new_str("C")
+ }
+ }
+}
+
+struct UseTreeLowering<'a> {
+ db: &'a dyn DefDatabase,
+ hygiene: &'a Hygiene,
+ mapping: Arena<ast::UseTree>,
+}
+
+impl UseTreeLowering<'_> {
+ fn lower_use_tree(&mut self, tree: ast::UseTree) -> Option<UseTree> {
+ if let Some(use_tree_list) = tree.use_tree_list() {
+ let prefix = match tree.path() {
+ // E.g. use something::{{{inner}}};
+ None => None,
+ // 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 ModPath::from_src(self.db.upcast(), path, self.hygiene) {
+ Some(it) => Some(it),
+ None => return None, // FIXME: report errors somewhere
+ }
+ }
+ };
+
+ let list =
+ use_tree_list.use_trees().filter_map(|tree| self.lower_use_tree(tree)).collect();
+
+ Some(
+ self.use_tree(
+ UseTreeKind::Prefixed { prefix: prefix.map(Interned::new), list },
+ tree,
+ ),
+ )
+ } else {
+ let is_glob = tree.star_token().is_some();
+ let path = match tree.path() {
+ Some(path) => Some(ModPath::from_src(self.db.upcast(), path, self.hygiene)?),
+ None => None,
+ };
+ let alias = tree.rename().map(|a| {
+ a.name().map(|it| it.as_name()).map_or(ImportAlias::Underscore, ImportAlias::Alias)
+ });
+ if alias.is_some() && is_glob {
+ return None;
+ }
+
+ match (path, alias, is_glob) {
+ (path, None, true) => {
+ if path.is_none() {
+ cov_mark::hit!(glob_enum_group);
+ }
+ Some(self.use_tree(UseTreeKind::Glob { path: path.map(Interned::new) }, tree))
+ }
+ // Globs can't be renamed
+ (_, Some(_), true) | (None, None, false) => None,
+ // `bla::{ as Name}` is invalid
+ (None, Some(_), false) => None,
+ (Some(path), alias, false) => Some(
+ self.use_tree(UseTreeKind::Single { path: Interned::new(path), alias }, tree),
+ ),
+ }
+ }
+ }
+
+ fn use_tree(&mut self, kind: UseTreeKind, ast: ast::UseTree) -> UseTree {
+ let index = self.mapping.alloc(ast);
+ UseTree { index, kind }
+ }
+}
+
+pub(super) fn lower_use_tree(
+ db: &dyn DefDatabase,
+ hygiene: &Hygiene,
+ tree: ast::UseTree,
+) -> Option<(UseTree, Arena<ast::UseTree>)> {
+ let mut lowering = UseTreeLowering { db, hygiene, mapping: Arena::new() };
+ let tree = lowering.lower_use_tree(tree)?;
+ Some((tree, lowering.mapping))
+}
--- /dev/null
- let Field { visibility, name, type_ref } = &this.tree[field];
+//! `ItemTree` debug printer.
+
+use std::fmt::{self, Write};
+
+use crate::{
+ attr::RawAttrs,
+ generics::{TypeOrConstParamData, WherePredicate, WherePredicateTypeTarget},
+ pretty::{print_path, print_type_bounds, print_type_ref},
+ visibility::RawVisibility,
+};
+
+use super::*;
+
+pub(super) fn print_item_tree(tree: &ItemTree) -> String {
+ let mut p = Printer { tree, buf: String::new(), indent_level: 0, needs_indent: true };
+
+ if let Some(attrs) = tree.attrs.get(&AttrOwner::TopLevel) {
+ p.print_attrs(attrs, true);
+ }
+ p.blank();
+
+ for item in tree.top_level_items() {
+ p.print_mod_item(*item);
+ }
+
+ let mut s = p.buf.trim_end_matches('\n').to_string();
+ s.push('\n');
+ s
+}
+
+macro_rules! w {
+ ($dst:expr, $($arg:tt)*) => {
+ { let _ = write!($dst, $($arg)*); }
+ };
+}
+
+macro_rules! wln {
+ ($dst:expr) => {
+ { let _ = writeln!($dst); }
+ };
+ ($dst:expr, $($arg:tt)*) => {
+ { let _ = writeln!($dst, $($arg)*); }
+ };
+}
+
+struct Printer<'a> {
+ tree: &'a ItemTree,
+ buf: String,
+ indent_level: usize,
+ needs_indent: bool,
+}
+
+impl<'a> Printer<'a> {
+ fn indented(&mut self, f: impl FnOnce(&mut Self)) {
+ self.indent_level += 1;
+ wln!(self);
+ f(self);
+ self.indent_level -= 1;
+ self.buf = self.buf.trim_end_matches('\n').to_string();
+ }
+
+ /// Ensures that a blank line is output before the next text.
+ fn blank(&mut self) {
+ let mut iter = self.buf.chars().rev().fuse();
+ match (iter.next(), iter.next()) {
+ (Some('\n'), Some('\n') | None) | (None, None) => {}
+ (Some('\n'), Some(_)) => {
+ self.buf.push('\n');
+ }
+ (Some(_), _) => {
+ self.buf.push('\n');
+ self.buf.push('\n');
+ }
+ (None, Some(_)) => unreachable!(),
+ }
+ }
+
+ fn whitespace(&mut self) {
+ match self.buf.chars().next_back() {
+ None | Some('\n' | ' ') => {}
+ _ => self.buf.push(' '),
+ }
+ }
+
+ fn print_attrs(&mut self, attrs: &RawAttrs, inner: bool) {
+ let inner = if inner { "!" } else { "" };
+ for attr in &**attrs {
+ wln!(
+ self,
+ "#{}[{}{}]",
+ inner,
+ attr.path,
+ attr.input.as_ref().map(|it| it.to_string()).unwrap_or_default(),
+ );
+ }
+ }
+
+ fn print_attrs_of(&mut self, of: impl Into<AttrOwner>) {
+ if let Some(attrs) = self.tree.attrs.get(&of.into()) {
+ self.print_attrs(attrs, false);
+ }
+ }
+
+ fn print_visibility(&mut self, vis: RawVisibilityId) {
+ match &self.tree[vis] {
+ RawVisibility::Module(path) => w!(self, "pub({}) ", path),
+ RawVisibility::Public => w!(self, "pub "),
+ };
+ }
+
+ fn print_fields(&mut self, fields: &Fields) {
+ match fields {
+ Fields::Record(fields) => {
+ self.whitespace();
+ w!(self, "{{");
+ self.indented(|this| {
+ for field in fields.clone() {
- let Field { visibility, name, type_ref } = &this.tree[field];
++ let Field { visibility, name, type_ref, ast_id: _ } = &this.tree[field];
+ this.print_attrs_of(field);
+ this.print_visibility(*visibility);
+ w!(this, "{}: ", name);
+ this.print_type_ref(type_ref);
+ wln!(this, ",");
+ }
+ });
+ w!(self, "}}");
+ }
+ Fields::Tuple(fields) => {
+ w!(self, "(");
+ self.indented(|this| {
+ for field in fields.clone() {
- let Variant { name, fields } = &this.tree[variant];
++ let Field { visibility, name, type_ref, ast_id: _ } = &this.tree[field];
+ this.print_attrs_of(field);
+ this.print_visibility(*visibility);
+ w!(this, "{}: ", name);
+ this.print_type_ref(type_ref);
+ wln!(this, ",");
+ }
+ });
+ w!(self, ")");
+ }
+ Fields::Unit => {}
+ }
+ }
+
+ fn print_fields_and_where_clause(&mut self, fields: &Fields, params: &GenericParams) {
+ match fields {
+ Fields::Record(_) => {
+ if self.print_where_clause(params) {
+ wln!(self);
+ }
+ self.print_fields(fields);
+ }
+ Fields::Unit => {
+ self.print_where_clause(params);
+ self.print_fields(fields);
+ }
+ Fields::Tuple(_) => {
+ self.print_fields(fields);
+ self.print_where_clause(params);
+ }
+ }
+ }
+
+ fn print_use_tree(&mut self, use_tree: &UseTree) {
+ match &use_tree.kind {
+ UseTreeKind::Single { path, alias } => {
+ w!(self, "{}", path);
+ if let Some(alias) = alias {
+ w!(self, " as {}", alias);
+ }
+ }
+ UseTreeKind::Glob { path } => {
+ if let Some(path) = path {
+ w!(self, "{}::", path);
+ }
+ w!(self, "*");
+ }
+ UseTreeKind::Prefixed { prefix, list } => {
+ if let Some(prefix) = prefix {
+ w!(self, "{}::", prefix);
+ }
+ w!(self, "{{");
+ for (i, tree) in list.iter().enumerate() {
+ if i != 0 {
+ w!(self, ", ");
+ }
+ self.print_use_tree(tree);
+ }
+ w!(self, "}}");
+ }
+ }
+ }
+
+ fn print_mod_item(&mut self, item: ModItem) {
+ self.print_attrs_of(item);
+
+ match item {
+ ModItem::Import(it) => {
+ let Import { visibility, use_tree, ast_id: _ } = &self.tree[it];
+ self.print_visibility(*visibility);
+ w!(self, "use ");
+ self.print_use_tree(use_tree);
+ wln!(self, ";");
+ }
+ ModItem::ExternCrate(it) => {
+ let ExternCrate { name, alias, visibility, ast_id: _ } = &self.tree[it];
+ self.print_visibility(*visibility);
+ w!(self, "extern crate {}", name);
+ if let Some(alias) = alias {
+ w!(self, " as {}", alias);
+ }
+ wln!(self, ";");
+ }
+ ModItem::ExternBlock(it) => {
+ let ExternBlock { abi, ast_id: _, children } = &self.tree[it];
+ w!(self, "extern ");
+ if let Some(abi) = abi {
+ w!(self, "\"{}\" ", abi);
+ }
+ w!(self, "{{");
+ self.indented(|this| {
+ for child in &**children {
+ this.print_mod_item(*child);
+ }
+ });
+ wln!(self, "}}");
+ }
+ ModItem::Function(it) => {
+ let Function {
+ name,
+ visibility,
+ explicit_generic_params,
+ abi,
+ params,
+ ret_type,
+ async_ret_type: _,
+ ast_id: _,
+ flags,
+ } = &self.tree[it];
+ self.print_visibility(*visibility);
+ if flags.contains(FnFlags::HAS_DEFAULT_KW) {
+ w!(self, "default ");
+ }
+ if flags.contains(FnFlags::HAS_CONST_KW) {
+ w!(self, "const ");
+ }
+ if flags.contains(FnFlags::HAS_ASYNC_KW) {
+ w!(self, "async ");
+ }
+ if flags.contains(FnFlags::HAS_UNSAFE_KW) {
+ w!(self, "unsafe ");
+ }
+ if let Some(abi) = abi {
+ w!(self, "extern \"{}\" ", abi);
+ }
+ w!(self, "fn {}", name);
+ self.print_generic_params(explicit_generic_params);
+ w!(self, "(");
+ if !params.is_empty() {
+ self.indented(|this| {
+ for (i, param) in params.clone().enumerate() {
+ this.print_attrs_of(param);
+ match &this.tree[param] {
+ Param::Normal(name, ty) => {
+ match name {
+ Some(name) => w!(this, "{}: ", name),
+ None => w!(this, "_: "),
+ }
+ this.print_type_ref(ty);
+ w!(this, ",");
+ if flags.contains(FnFlags::HAS_SELF_PARAM) && i == 0 {
+ wln!(this, " // self");
+ } else {
+ wln!(this);
+ }
+ }
+ Param::Varargs => {
+ wln!(this, "...");
+ }
+ };
+ }
+ });
+ }
+ w!(self, ") -> ");
+ self.print_type_ref(ret_type);
+ self.print_where_clause(explicit_generic_params);
+ if flags.contains(FnFlags::HAS_BODY) {
+ wln!(self, " {{ ... }}");
+ } else {
+ wln!(self, ";");
+ }
+ }
+ ModItem::Struct(it) => {
+ let Struct { visibility, name, fields, generic_params, ast_id: _ } = &self.tree[it];
+ self.print_visibility(*visibility);
+ w!(self, "struct {}", name);
+ self.print_generic_params(generic_params);
+ self.print_fields_and_where_clause(fields, generic_params);
+ if matches!(fields, Fields::Record(_)) {
+ wln!(self);
+ } else {
+ wln!(self, ";");
+ }
+ }
+ ModItem::Union(it) => {
+ let Union { name, visibility, fields, generic_params, ast_id: _ } = &self.tree[it];
+ self.print_visibility(*visibility);
+ w!(self, "union {}", name);
+ self.print_generic_params(generic_params);
+ self.print_fields_and_where_clause(fields, generic_params);
+ if matches!(fields, Fields::Record(_)) {
+ wln!(self);
+ } else {
+ wln!(self, ";");
+ }
+ }
+ ModItem::Enum(it) => {
+ let Enum { name, visibility, variants, generic_params, ast_id: _ } = &self.tree[it];
+ self.print_visibility(*visibility);
+ w!(self, "enum {}", name);
+ self.print_generic_params(generic_params);
+ self.print_where_clause_and_opening_brace(generic_params);
+ self.indented(|this| {
+ for variant in variants.clone() {
++ let Variant { name, fields, ast_id: _ } = &this.tree[variant];
+ this.print_attrs_of(variant);
+ w!(this, "{}", name);
+ this.print_fields(fields);
+ wln!(this, ",");
+ }
+ });
+ wln!(self, "}}");
+ }
+ ModItem::Const(it) => {
+ let Const { name, visibility, type_ref, ast_id: _ } = &self.tree[it];
+ self.print_visibility(*visibility);
+ w!(self, "const ");
+ match name {
+ Some(name) => w!(self, "{}", name),
+ None => w!(self, "_"),
+ }
+ w!(self, ": ");
+ self.print_type_ref(type_ref);
+ wln!(self, " = _;");
+ }
+ ModItem::Static(it) => {
+ let Static { name, visibility, mutable, type_ref, ast_id: _ } = &self.tree[it];
+ self.print_visibility(*visibility);
+ w!(self, "static ");
+ if *mutable {
+ w!(self, "mut ");
+ }
+ w!(self, "{}: ", name);
+ self.print_type_ref(type_ref);
+ w!(self, " = _;");
+ wln!(self);
+ }
+ ModItem::Trait(it) => {
+ let Trait {
+ name,
+ visibility,
+ is_auto,
+ is_unsafe,
+ items,
+ generic_params,
+ ast_id: _,
+ } = &self.tree[it];
+ self.print_visibility(*visibility);
+ if *is_unsafe {
+ w!(self, "unsafe ");
+ }
+ if *is_auto {
+ w!(self, "auto ");
+ }
+ w!(self, "trait {}", name);
+ self.print_generic_params(generic_params);
+ self.print_where_clause_and_opening_brace(generic_params);
+ self.indented(|this| {
+ for item in &**items {
+ this.print_mod_item((*item).into());
+ }
+ });
+ wln!(self, "}}");
+ }
+ ModItem::Impl(it) => {
+ let Impl { target_trait, self_ty, is_negative, items, generic_params, ast_id: _ } =
+ &self.tree[it];
+ w!(self, "impl");
+ self.print_generic_params(generic_params);
+ w!(self, " ");
+ if *is_negative {
+ w!(self, "!");
+ }
+ if let Some(tr) = target_trait {
+ self.print_path(&tr.path);
+ w!(self, " for ");
+ }
+ self.print_type_ref(self_ty);
+ self.print_where_clause_and_opening_brace(generic_params);
+ self.indented(|this| {
+ for item in &**items {
+ this.print_mod_item((*item).into());
+ }
+ });
+ wln!(self, "}}");
+ }
+ ModItem::TypeAlias(it) => {
+ let TypeAlias { name, visibility, bounds, type_ref, generic_params, ast_id: _ } =
+ &self.tree[it];
+ self.print_visibility(*visibility);
+ w!(self, "type {}", name);
+ self.print_generic_params(generic_params);
+ if !bounds.is_empty() {
+ w!(self, ": ");
+ self.print_type_bounds(bounds);
+ }
+ if let Some(ty) = type_ref {
+ w!(self, " = ");
+ self.print_type_ref(ty);
+ }
+ self.print_where_clause(generic_params);
+ w!(self, ";");
+ wln!(self);
+ }
+ ModItem::Mod(it) => {
+ let Mod { name, visibility, kind, ast_id: _ } = &self.tree[it];
+ self.print_visibility(*visibility);
+ w!(self, "mod {}", name);
+ match kind {
+ ModKind::Inline { items } => {
+ w!(self, " {{");
+ self.indented(|this| {
+ for item in &**items {
+ this.print_mod_item(*item);
+ }
+ });
+ wln!(self, "}}");
+ }
+ ModKind::Outline => {
+ wln!(self, ";");
+ }
+ }
+ }
+ ModItem::MacroCall(it) => {
+ let MacroCall { path, ast_id: _, expand_to: _ } = &self.tree[it];
+ wln!(self, "{}!(...);", path);
+ }
+ ModItem::MacroRules(it) => {
+ let MacroRules { name, ast_id: _ } = &self.tree[it];
+ wln!(self, "macro_rules! {} {{ ... }}", name);
+ }
+ ModItem::MacroDef(it) => {
+ let MacroDef { name, visibility, ast_id: _ } = &self.tree[it];
+ self.print_visibility(*visibility);
+ wln!(self, "macro {} {{ ... }}", name);
+ }
+ }
+
+ self.blank();
+ }
+
+ fn print_type_ref(&mut self, type_ref: &TypeRef) {
+ print_type_ref(type_ref, self).unwrap();
+ }
+
+ fn print_type_bounds(&mut self, bounds: &[Interned<TypeBound>]) {
+ print_type_bounds(bounds, self).unwrap();
+ }
+
+ fn print_path(&mut self, path: &Path) {
+ print_path(path, self).unwrap();
+ }
+
+ fn print_generic_params(&mut self, params: &GenericParams) {
+ if params.type_or_consts.is_empty() && params.lifetimes.is_empty() {
+ return;
+ }
+
+ w!(self, "<");
+ let mut first = true;
+ for (_, lt) in params.lifetimes.iter() {
+ if !first {
+ w!(self, ", ");
+ }
+ first = false;
+ w!(self, "{}", lt.name);
+ }
+ for (idx, x) in params.type_or_consts.iter() {
+ if !first {
+ w!(self, ", ");
+ }
+ first = false;
+ match x {
+ TypeOrConstParamData::TypeParamData(ty) => match &ty.name {
+ Some(name) => w!(self, "{}", name),
+ None => w!(self, "_anon_{}", idx.into_raw()),
+ },
+ TypeOrConstParamData::ConstParamData(konst) => {
+ w!(self, "const {}: ", konst.name);
+ self.print_type_ref(&konst.ty);
+ }
+ }
+ }
+ w!(self, ">");
+ }
+
+ fn print_where_clause_and_opening_brace(&mut self, params: &GenericParams) {
+ if self.print_where_clause(params) {
+ w!(self, "\n{{");
+ } else {
+ self.whitespace();
+ w!(self, "{{");
+ }
+ }
+
+ fn print_where_clause(&mut self, params: &GenericParams) -> bool {
+ if params.where_predicates.is_empty() {
+ return false;
+ }
+
+ w!(self, "\nwhere");
+ self.indented(|this| {
+ for (i, pred) in params.where_predicates.iter().enumerate() {
+ if i != 0 {
+ wln!(this, ",");
+ }
+
+ let (target, bound) = match pred {
+ WherePredicate::TypeBound { target, bound } => (target, bound),
+ WherePredicate::Lifetime { target, bound } => {
+ wln!(this, "{}: {},", target.name, bound.name);
+ continue;
+ }
+ WherePredicate::ForLifetime { lifetimes, target, bound } => {
+ w!(this, "for<");
+ for (i, lt) in lifetimes.iter().enumerate() {
+ if i != 0 {
+ w!(this, ", ");
+ }
+ w!(this, "{}", lt);
+ }
+ w!(this, "> ");
+ (target, bound)
+ }
+ };
+
+ match target {
+ WherePredicateTypeTarget::TypeRef(ty) => this.print_type_ref(ty),
+ WherePredicateTypeTarget::TypeOrConstParam(id) => {
+ match ¶ms.type_or_consts[*id].name() {
+ Some(name) => w!(this, "{}", name),
+ None => w!(this, "_anon_{}", id.into_raw()),
+ }
+ }
+ }
+ w!(this, ": ");
+ this.print_type_bounds(std::slice::from_ref(bound));
+ }
+ });
+ true
+ }
+}
+
+impl<'a> Write for Printer<'a> {
+ fn write_str(&mut self, s: &str) -> fmt::Result {
+ for line in s.split_inclusive('\n') {
+ if self.needs_indent {
+ match self.buf.chars().last() {
+ Some('\n') | None => {}
+ _ => self.buf.push('\n'),
+ }
+ self.buf.push_str(&" ".repeat(self.indent_level));
+ self.needs_indent = false;
+ }
+
+ self.buf.push_str(line);
+ self.needs_indent = line.ends_with('\n');
+ }
+
+ Ok(())
+ }
+}
--- /dev/null
+//! `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.
+
+#![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)]
+
+#[allow(unused)]
+macro_rules! eprintln {
+ ($($tt:tt)*) => { stdx::eprintln!($($tt)*) };
+}
+
+pub mod db;
+
+pub mod attr;
+pub mod path;
+pub mod type_ref;
+pub mod builtin_type;
+pub mod builtin_attr;
+pub mod per_ns;
+pub mod item_scope;
+
+pub mod dyn_map;
+pub mod keys;
+
+pub mod item_tree;
+pub mod intern;
+
+pub mod adt;
+pub mod data;
+pub mod generics;
+pub mod lang_item;
+
+pub mod expr;
+pub mod body;
+pub mod resolver;
+
+mod trace;
+pub mod nameres;
+
+pub mod src;
+pub mod child_by_source;
+
+pub mod visibility;
+pub mod find_path;
+pub mod import_map;
+
+#[cfg(test)]
+mod test_db;
+#[cfg(test)]
+mod macro_expansion_tests;
+mod pretty;
+
+use std::{
+ hash::{Hash, Hasher},
+ sync::Arc,
+};
+
+use attr::Attr;
+use base_db::{impl_intern_key, salsa, CrateId, ProcMacroKind};
+use hir_expand::{
+ ast_id_map::FileAstId,
+ builtin_attr_macro::BuiltinAttrExpander,
+ builtin_derive_macro::BuiltinDeriveExpander,
+ builtin_fn_macro::{BuiltinFnLikeExpander, EagerExpander},
+ eager::{expand_eager_macro, ErrorEmitted, ErrorSink},
+ hygiene::Hygiene,
+ proc_macro::ProcMacroExpander,
+ AstId, ExpandError, ExpandTo, HirFileId, InFile, MacroCallId, MacroCallKind, MacroDefId,
+ MacroDefKind, UnresolvedMacro,
+};
+use item_tree::ExternBlock;
+use la_arena::Idx;
+use nameres::DefMap;
+use stdx::impl_from;
+use syntax::ast;
+
+use crate::{
+ adt::VariantData,
+ attr::AttrId,
+ builtin_type::BuiltinType,
+ item_tree::{
+ Const, Enum, Function, Impl, ItemTreeId, ItemTreeNode, MacroDef, MacroRules, ModItem,
+ Static, Struct, Trait, TypeAlias, Union,
+ },
+};
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct ModuleId {
+ krate: CrateId,
+ /// If this `ModuleId` was derived from a `DefMap` for a block expression, this stores the
+ /// `BlockId` of that block expression. If `None`, this module is part of the crate-level
+ /// `DefMap` of `krate`.
+ block: Option<BlockId>,
+ /// The module's ID in its originating `DefMap`.
+ pub local_id: LocalModuleId,
+}
+
+impl ModuleId {
+ pub fn def_map(&self, db: &dyn db::DefDatabase) -> Arc<DefMap> {
+ match self.block {
+ Some(block) => {
+ db.block_def_map(block).unwrap_or_else(|| {
+ // NOTE: This should be unreachable - all `ModuleId`s come from their `DefMap`s,
+ // so the `DefMap` here must exist.
+ unreachable!("no `block_def_map` for `ModuleId` {:?}", self);
+ })
+ }
+ None => db.crate_def_map(self.krate),
+ }
+ }
+
+ pub fn krate(&self) -> CrateId {
+ self.krate
+ }
+
+ pub fn containing_module(&self, db: &dyn db::DefDatabase) -> Option<ModuleId> {
+ self.def_map(db).containing_module(self.local_id)
+ }
+
+ pub fn containing_block(&self) -> Option<BlockId> {
+ self.block
+ }
+}
+
+/// An ID of a module, **local** to a specific crate
+pub type LocalModuleId = Idx<nameres::ModuleData>;
+
+#[derive(Debug)]
+pub struct ItemLoc<N: ItemTreeNode> {
+ pub container: ModuleId,
+ pub id: ItemTreeId<N>,
+}
+
+impl<N: ItemTreeNode> Clone for ItemLoc<N> {
+ fn clone(&self) -> Self {
+ Self { container: self.container, id: self.id }
+ }
+}
+
+impl<N: ItemTreeNode> Copy for ItemLoc<N> {}
+
+impl<N: ItemTreeNode> PartialEq for ItemLoc<N> {
+ fn eq(&self, other: &Self) -> bool {
+ self.container == other.container && self.id == other.id
+ }
+}
+
+impl<N: ItemTreeNode> Eq for ItemLoc<N> {}
+
+impl<N: ItemTreeNode> Hash for ItemLoc<N> {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.container.hash(state);
+ self.id.hash(state);
+ }
+}
+
+#[derive(Debug)]
+pub struct AssocItemLoc<N: ItemTreeNode> {
+ pub container: ItemContainerId,
+ pub id: ItemTreeId<N>,
+}
+
+impl<N: ItemTreeNode> Clone for AssocItemLoc<N> {
+ fn clone(&self) -> Self {
+ Self { container: self.container, id: self.id }
+ }
+}
+
+impl<N: ItemTreeNode> Copy for AssocItemLoc<N> {}
+
+impl<N: ItemTreeNode> PartialEq for AssocItemLoc<N> {
+ fn eq(&self, other: &Self) -> bool {
+ self.container == other.container && self.id == other.id
+ }
+}
+
+impl<N: ItemTreeNode> Eq for AssocItemLoc<N> {}
+
+impl<N: ItemTreeNode> Hash for AssocItemLoc<N> {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.container.hash(state);
+ self.id.hash(state);
+ }
+}
+
+macro_rules! impl_intern {
+ ($id:ident, $loc:ident, $intern:ident, $lookup:ident) => {
+ impl_intern_key!($id);
+
+ impl Intern for $loc {
+ type ID = $id;
+ fn intern(self, db: &dyn db::DefDatabase) -> $id {
+ db.$intern(self)
+ }
+ }
+
+ impl Lookup for $id {
+ type Data = $loc;
+ fn lookup(&self, db: &dyn db::DefDatabase) -> $loc {
+ db.$lookup(*self)
+ }
+ }
+ };
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct FunctionId(salsa::InternId);
+type FunctionLoc = AssocItemLoc<Function>;
+impl_intern!(FunctionId, FunctionLoc, intern_function, lookup_intern_function);
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub struct StructId(salsa::InternId);
+type StructLoc = ItemLoc<Struct>;
+impl_intern!(StructId, StructLoc, intern_struct, lookup_intern_struct);
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub struct UnionId(salsa::InternId);
+pub type UnionLoc = ItemLoc<Union>;
+impl_intern!(UnionId, UnionLoc, intern_union, lookup_intern_union);
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub struct EnumId(salsa::InternId);
+pub type EnumLoc = ItemLoc<Enum>;
+impl_intern!(EnumId, EnumLoc, intern_enum, lookup_intern_enum);
+
+// FIXME: rename to `VariantId`, only enums can ave variants
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct EnumVariantId {
+ pub parent: EnumId,
+ pub local_id: LocalEnumVariantId,
+}
+
+pub type LocalEnumVariantId = Idx<adt::EnumVariantData>;
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct FieldId {
+ pub parent: VariantId,
+ pub local_id: LocalFieldId,
+}
+
+pub type LocalFieldId = Idx<adt::FieldData>;
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct ConstId(salsa::InternId);
+type ConstLoc = AssocItemLoc<Const>;
+impl_intern!(ConstId, ConstLoc, intern_const, lookup_intern_const);
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct StaticId(salsa::InternId);
+pub type StaticLoc = AssocItemLoc<Static>;
+impl_intern!(StaticId, StaticLoc, intern_static, lookup_intern_static);
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct TraitId(salsa::InternId);
+pub type TraitLoc = ItemLoc<Trait>;
+impl_intern!(TraitId, TraitLoc, intern_trait, lookup_intern_trait);
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct TypeAliasId(salsa::InternId);
+type TypeAliasLoc = AssocItemLoc<TypeAlias>;
+impl_intern!(TypeAliasId, TypeAliasLoc, intern_type_alias, lookup_intern_type_alias);
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
+pub struct ImplId(salsa::InternId);
+type ImplLoc = ItemLoc<Impl>;
+impl_intern!(ImplId, ImplLoc, intern_impl, lookup_intern_impl);
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
+pub struct ExternBlockId(salsa::InternId);
+type ExternBlockLoc = ItemLoc<ExternBlock>;
+impl_intern!(ExternBlockId, ExternBlockLoc, intern_extern_block, lookup_intern_extern_block);
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum MacroExpander {
+ Declarative,
+ BuiltIn(BuiltinFnLikeExpander),
+ BuiltInAttr(BuiltinAttrExpander),
+ BuiltInDerive(BuiltinDeriveExpander),
+ BuiltInEager(EagerExpander),
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
+pub struct Macro2Id(salsa::InternId);
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct Macro2Loc {
+ pub container: ModuleId,
+ pub id: ItemTreeId<MacroDef>,
+ pub expander: MacroExpander,
+}
+impl_intern!(Macro2Id, Macro2Loc, intern_macro2, lookup_intern_macro2);
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
+pub struct MacroRulesId(salsa::InternId);
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct MacroRulesLoc {
+ pub container: ModuleId,
+ pub id: ItemTreeId<MacroRules>,
+ pub local_inner: bool,
+ pub expander: MacroExpander,
+}
+impl_intern!(MacroRulesId, MacroRulesLoc, intern_macro_rules, lookup_intern_macro_rules);
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
+pub struct ProcMacroId(salsa::InternId);
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct ProcMacroLoc {
+ // FIXME: this should be a crate? or just a crate-root module
+ pub container: ModuleId,
+ pub id: ItemTreeId<Function>,
+ pub expander: ProcMacroExpander,
+ pub kind: ProcMacroKind,
+}
+impl_intern!(ProcMacroId, ProcMacroLoc, intern_proc_macro, lookup_intern_proc_macro);
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
+pub struct BlockId(salsa::InternId);
+#[derive(Debug, Hash, PartialEq, Eq, Clone)]
+pub struct BlockLoc {
+ ast_id: AstId<ast::BlockExpr>,
+ /// The containing module.
+ module: ModuleId,
+}
+impl_intern!(BlockId, BlockLoc, intern_block, lookup_intern_block);
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct TypeOrConstParamId {
+ pub parent: GenericDefId,
+ pub local_id: LocalTypeOrConstParamId,
+}
+
+/// A TypeOrConstParamId with an invariant that it actually belongs to a type
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct TypeParamId(TypeOrConstParamId);
+
+impl TypeParamId {
+ pub fn parent(&self) -> GenericDefId {
+ self.0.parent
+ }
+ pub fn local_id(&self) -> LocalTypeOrConstParamId {
+ self.0.local_id
+ }
+}
+
+impl TypeParamId {
+ /// Caller should check if this toc id really belongs to a type
+ pub fn from_unchecked(x: TypeOrConstParamId) -> Self {
+ Self(x)
+ }
+}
+
+impl From<TypeParamId> for TypeOrConstParamId {
+ fn from(x: TypeParamId) -> Self {
+ x.0
+ }
+}
+
+/// A TypeOrConstParamId with an invariant that it actually belongs to a const
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct ConstParamId(TypeOrConstParamId);
+
+impl ConstParamId {
+ pub fn parent(&self) -> GenericDefId {
+ self.0.parent
+ }
+ pub fn local_id(&self) -> LocalTypeOrConstParamId {
+ self.0.local_id
+ }
+}
+
+impl ConstParamId {
+ /// Caller should check if this toc id really belongs to a const
+ pub fn from_unchecked(x: TypeOrConstParamId) -> Self {
+ Self(x)
+ }
+}
+
+impl From<ConstParamId> for TypeOrConstParamId {
+ fn from(x: ConstParamId) -> Self {
+ x.0
+ }
+}
+
+pub type LocalTypeOrConstParamId = Idx<generics::TypeOrConstParamData>;
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct LifetimeParamId {
+ pub parent: GenericDefId,
+ pub local_id: LocalLifetimeParamId,
+}
+pub type LocalLifetimeParamId = Idx<generics::LifetimeParamData>;
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum ItemContainerId {
+ ExternBlockId(ExternBlockId),
+ ModuleId(ModuleId),
+ ImplId(ImplId),
+ TraitId(TraitId),
+}
+impl_from!(ModuleId for ItemContainerId);
+
+/// A Data Type
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub enum AdtId {
+ StructId(StructId),
+ UnionId(UnionId),
+ EnumId(EnumId),
+}
+impl_from!(StructId, UnionId, EnumId for AdtId);
+
+/// A macro
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub enum MacroId {
+ Macro2Id(Macro2Id),
+ MacroRulesId(MacroRulesId),
+ ProcMacroId(ProcMacroId),
+}
+impl_from!(Macro2Id, MacroRulesId, ProcMacroId for MacroId);
+
+impl MacroId {
+ pub fn is_attribute(self, db: &dyn db::DefDatabase) -> bool {
+ match self {
+ MacroId::ProcMacroId(it) => it.lookup(db).kind == ProcMacroKind::Attr,
+ _ => false,
+ }
+ }
+}
+
+/// A generic param
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub enum GenericParamId {
+ TypeParamId(TypeParamId),
+ ConstParamId(ConstParamId),
+ LifetimeParamId(LifetimeParamId),
+}
+impl_from!(TypeParamId, LifetimeParamId, ConstParamId for GenericParamId);
+
+/// 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),
+ MacroId(MacroId),
+}
+impl_from!(
+ MacroId(Macro2Id, MacroRulesId, ProcMacroId),
+ ModuleId,
+ FunctionId,
+ AdtId(StructId, EnumId, UnionId),
+ EnumVariantId,
+ ConstId,
+ StaticId,
+ TraitId,
+ TypeAliasId,
+ BuiltinType
+ for ModuleDefId
+);
+
+/// The defs which have a body.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum DefWithBodyId {
+ FunctionId(FunctionId),
+ StaticId(StaticId),
+ ConstId(ConstId),
++ VariantId(EnumVariantId),
+}
+
+impl_from!(FunctionId, ConstId, StaticId for DefWithBodyId);
+
++impl From<EnumVariantId> for DefWithBodyId {
++ fn from(id: EnumVariantId) -> Self {
++ DefWithBodyId::VariantId(id)
++ }
++}
++
+impl DefWithBodyId {
+ pub fn as_generic_def_id(self) -> Option<GenericDefId> {
+ match self {
+ DefWithBodyId::FunctionId(f) => Some(f.into()),
+ DefWithBodyId::StaticId(_) => None,
+ DefWithBodyId::ConstId(c) => Some(c.into()),
++ DefWithBodyId::VariantId(c) => Some(c.into()),
+ }
+ }
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+pub enum AssocItemId {
+ FunctionId(FunctionId),
+ ConstId(ConstId),
+ TypeAliasId(TypeAliasId),
+}
+// FIXME: not every function, ... is actually an assoc item. maybe we should make
+// sure that you can only turn actual assoc items into AssocItemIds. This would
+// require not implementing From, and instead having some checked way of
+// casting them, and somehow making the constructors private, which would be annoying.
+impl_from!(FunctionId, ConstId, TypeAliasId for AssocItemId);
+
+#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
+pub enum GenericDefId {
+ FunctionId(FunctionId),
+ AdtId(AdtId),
+ TraitId(TraitId),
+ TypeAliasId(TypeAliasId),
+ ImplId(ImplId),
+ // enum variants cannot have generics themselves, but their parent enums
+ // can, and this makes some code easier to write
+ EnumVariantId(EnumVariantId),
+ // consts can have type parameters from their parents (i.e. associated consts of traits)
+ ConstId(ConstId),
+}
+impl_from!(
+ FunctionId,
+ AdtId(StructId, EnumId, UnionId),
+ TraitId,
+ TypeAliasId,
+ ImplId,
+ EnumVariantId,
+ ConstId
+ for GenericDefId
+);
+
+impl From<AssocItemId> for GenericDefId {
+ fn from(item: AssocItemId) -> Self {
+ match item {
+ AssocItemId::FunctionId(f) => f.into(),
+ AssocItemId::ConstId(c) => c.into(),
+ AssocItemId::TypeAliasId(t) => t.into(),
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub enum AttrDefId {
+ ModuleId(ModuleId),
+ FieldId(FieldId),
+ AdtId(AdtId),
+ FunctionId(FunctionId),
+ EnumVariantId(EnumVariantId),
+ StaticId(StaticId),
+ ConstId(ConstId),
+ TraitId(TraitId),
+ TypeAliasId(TypeAliasId),
+ MacroId(MacroId),
+ ImplId(ImplId),
+ GenericParamId(GenericParamId),
+ ExternBlockId(ExternBlockId),
+}
+
+impl_from!(
+ ModuleId,
+ FieldId,
+ AdtId(StructId, EnumId, UnionId),
+ EnumVariantId,
+ StaticId,
+ ConstId,
+ FunctionId,
+ TraitId,
+ TypeAliasId,
+ MacroId(Macro2Id, MacroRulesId, ProcMacroId),
+ ImplId,
+ GenericParamId
+ for AttrDefId
+);
+
+impl From<ItemContainerId> for AttrDefId {
+ fn from(acid: ItemContainerId) -> Self {
+ match acid {
+ ItemContainerId::ModuleId(mid) => AttrDefId::ModuleId(mid),
+ ItemContainerId::ImplId(iid) => AttrDefId::ImplId(iid),
+ ItemContainerId::TraitId(tid) => AttrDefId::TraitId(tid),
+ ItemContainerId::ExternBlockId(id) => AttrDefId::ExternBlockId(id),
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum VariantId {
+ EnumVariantId(EnumVariantId),
+ StructId(StructId),
+ UnionId(UnionId),
+}
+impl_from!(EnumVariantId, StructId, UnionId for VariantId);
+
+impl VariantId {
+ pub fn variant_data(self, db: &dyn db::DefDatabase) -> Arc<VariantData> {
+ match self {
+ VariantId::StructId(it) => db.struct_data(it).variant_data.clone(),
+ VariantId::UnionId(it) => db.union_data(it).variant_data.clone(),
+ VariantId::EnumVariantId(it) => {
+ db.enum_data(it.parent).variants[it.local_id].variant_data.clone()
+ }
+ }
+ }
+
+ pub fn file_id(self, db: &dyn db::DefDatabase) -> HirFileId {
+ match self {
+ VariantId::EnumVariantId(it) => it.parent.lookup(db).id.file_id(),
+ VariantId::StructId(it) => it.lookup(db).id.file_id(),
+ VariantId::UnionId(it) => it.lookup(db).id.file_id(),
+ }
+ }
+
+ pub fn adt_id(self) -> AdtId {
+ match self {
+ VariantId::EnumVariantId(it) => it.parent.into(),
+ VariantId::StructId(it) => it.into(),
+ VariantId::UnionId(it) => it.into(),
+ }
+ }
+}
+
+trait Intern {
+ type ID;
+ fn intern(self, db: &dyn db::DefDatabase) -> Self::ID;
+}
+
+pub trait Lookup {
+ type Data;
+ fn lookup(&self, db: &dyn db::DefDatabase) -> Self::Data;
+}
+
+pub trait HasModule {
+ fn module(&self, db: &dyn db::DefDatabase) -> ModuleId;
+}
+
+impl HasModule for ItemContainerId {
+ fn module(&self, db: &dyn db::DefDatabase) -> ModuleId {
+ match *self {
+ ItemContainerId::ModuleId(it) => it,
+ ItemContainerId::ImplId(it) => it.lookup(db).container,
+ ItemContainerId::TraitId(it) => it.lookup(db).container,
+ ItemContainerId::ExternBlockId(it) => it.lookup(db).container,
+ }
+ }
+}
+
+impl<N: ItemTreeNode> HasModule for AssocItemLoc<N> {
+ fn module(&self, db: &dyn db::DefDatabase) -> ModuleId {
+ self.container.module(db)
+ }
+}
+
+impl HasModule for AdtId {
+ fn module(&self, db: &dyn db::DefDatabase) -> ModuleId {
+ match self {
+ AdtId::StructId(it) => it.lookup(db).container,
+ AdtId::UnionId(it) => it.lookup(db).container,
+ AdtId::EnumId(it) => it.lookup(db).container,
+ }
+ }
+}
+
+impl HasModule for VariantId {
+ fn module(&self, db: &dyn db::DefDatabase) -> ModuleId {
+ match self {
+ VariantId::EnumVariantId(it) => it.parent.lookup(db).container,
+ VariantId::StructId(it) => it.lookup(db).container,
+ VariantId::UnionId(it) => it.lookup(db).container,
+ }
+ }
+}
+
+impl HasModule for MacroId {
+ fn module(&self, db: &dyn db::DefDatabase) -> ModuleId {
+ match self {
+ MacroId::MacroRulesId(it) => it.lookup(db).container,
+ MacroId::Macro2Id(it) => it.lookup(db).container,
+ MacroId::ProcMacroId(it) => it.lookup(db).container,
+ }
+ }
+}
+
+impl HasModule for DefWithBodyId {
+ fn module(&self, db: &dyn db::DefDatabase) -> ModuleId {
+ match self {
+ DefWithBodyId::FunctionId(it) => it.lookup(db).module(db),
+ DefWithBodyId::StaticId(it) => it.lookup(db).module(db),
+ DefWithBodyId::ConstId(it) => it.lookup(db).module(db),
++ DefWithBodyId::VariantId(it) => it.parent.lookup(db).container,
+ }
+ }
+}
+
+impl DefWithBodyId {
+ pub fn as_mod_item(self, db: &dyn db::DefDatabase) -> ModItem {
+ match self {
+ DefWithBodyId::FunctionId(it) => it.lookup(db).id.value.into(),
+ DefWithBodyId::StaticId(it) => it.lookup(db).id.value.into(),
+ DefWithBodyId::ConstId(it) => it.lookup(db).id.value.into(),
++ DefWithBodyId::VariantId(it) => it.parent.lookup(db).id.value.into(),
+ }
+ }
+}
+
+impl HasModule for GenericDefId {
+ fn module(&self, db: &dyn db::DefDatabase) -> ModuleId {
+ match self {
+ GenericDefId::FunctionId(it) => it.lookup(db).module(db),
+ GenericDefId::AdtId(it) => it.module(db),
+ GenericDefId::TraitId(it) => it.lookup(db).container,
+ GenericDefId::TypeAliasId(it) => it.lookup(db).module(db),
+ GenericDefId::ImplId(it) => it.lookup(db).container,
+ GenericDefId::EnumVariantId(it) => it.parent.lookup(db).container,
+ GenericDefId::ConstId(it) => it.lookup(db).module(db),
+ }
+ }
+}
+
+impl HasModule for TypeAliasId {
+ fn module(&self, db: &dyn db::DefDatabase) -> ModuleId {
+ self.lookup(db).module(db)
+ }
+}
+
+impl HasModule for TraitId {
+ fn module(&self, db: &dyn db::DefDatabase) -> ModuleId {
+ self.lookup(db).container
+ }
+}
+
+impl ModuleDefId {
+ /// Returns the module containing `self` (or `self`, if `self` is itself a module).
+ ///
+ /// Returns `None` if `self` refers to a primitive type.
+ pub fn module(&self, db: &dyn db::DefDatabase) -> Option<ModuleId> {
+ Some(match self {
+ ModuleDefId::ModuleId(id) => *id,
+ ModuleDefId::FunctionId(id) => id.lookup(db).module(db),
+ ModuleDefId::AdtId(id) => id.module(db),
+ ModuleDefId::EnumVariantId(id) => id.parent.lookup(db).container,
+ ModuleDefId::ConstId(id) => id.lookup(db).container.module(db),
+ ModuleDefId::StaticId(id) => id.lookup(db).module(db),
+ ModuleDefId::TraitId(id) => id.lookup(db).container,
+ ModuleDefId::TypeAliasId(id) => id.lookup(db).module(db),
+ ModuleDefId::MacroId(id) => id.module(db),
+ ModuleDefId::BuiltinType(_) => return None,
+ })
+ }
+}
+
+impl AttrDefId {
+ pub fn krate(&self, db: &dyn db::DefDatabase) -> CrateId {
+ match self {
+ AttrDefId::ModuleId(it) => it.krate,
+ AttrDefId::FieldId(it) => it.parent.module(db).krate,
+ AttrDefId::AdtId(it) => it.module(db).krate,
+ AttrDefId::FunctionId(it) => it.lookup(db).module(db).krate,
+ AttrDefId::EnumVariantId(it) => it.parent.lookup(db).container.krate,
+ AttrDefId::StaticId(it) => it.lookup(db).module(db).krate,
+ AttrDefId::ConstId(it) => it.lookup(db).module(db).krate,
+ AttrDefId::TraitId(it) => it.lookup(db).container.krate,
+ AttrDefId::TypeAliasId(it) => it.lookup(db).module(db).krate,
+ AttrDefId::ImplId(it) => it.lookup(db).container.krate,
+ AttrDefId::ExternBlockId(it) => it.lookup(db).container.krate,
+ AttrDefId::GenericParamId(it) => {
+ match it {
+ GenericParamId::TypeParamId(it) => it.parent(),
+ GenericParamId::ConstParamId(it) => it.parent(),
+ GenericParamId::LifetimeParamId(it) => it.parent,
+ }
+ .module(db)
+ .krate
+ }
+ AttrDefId::MacroId(it) => it.module(db).krate,
+ }
+ }
+}
+
+/// A helper trait for converting to MacroCallId
+pub trait AsMacroCall {
+ fn as_call_id(
+ &self,
+ db: &dyn db::DefDatabase,
+ krate: CrateId,
+ resolver: impl Fn(path::ModPath) -> Option<MacroDefId>,
+ ) -> Option<MacroCallId> {
+ self.as_call_id_with_errors(db, krate, resolver, &mut |_| ()).ok()?.ok()
+ }
+
+ fn as_call_id_with_errors(
+ &self,
+ db: &dyn db::DefDatabase,
+ krate: CrateId,
+ resolver: impl Fn(path::ModPath) -> Option<MacroDefId>,
+ error_sink: &mut dyn FnMut(ExpandError),
+ ) -> Result<Result<MacroCallId, ErrorEmitted>, UnresolvedMacro>;
+}
+
+impl AsMacroCall for InFile<&ast::MacroCall> {
+ fn as_call_id_with_errors(
+ &self,
+ db: &dyn db::DefDatabase,
+ krate: CrateId,
+ resolver: impl Fn(path::ModPath) -> Option<MacroDefId>,
+ mut error_sink: &mut dyn FnMut(ExpandError),
+ ) -> Result<Result<MacroCallId, ErrorEmitted>, UnresolvedMacro> {
+ let expands_to = hir_expand::ExpandTo::from_call_site(self.value);
+ let ast_id = AstId::new(self.file_id, db.ast_id_map(self.file_id).ast_id(self.value));
+ let h = Hygiene::new(db.upcast(), self.file_id);
+ let path =
+ self.value.path().and_then(|path| path::ModPath::from_src(db.upcast(), path, &h));
+
+ let path = match error_sink
+ .option(path, || ExpandError::Other("malformed macro invocation".into()))
+ {
+ Ok(path) => path,
+ Err(error) => {
+ return Ok(Err(error));
+ }
+ };
+
+ macro_call_as_call_id(
+ db,
+ &AstIdWithPath::new(ast_id.file_id, ast_id.value, path),
+ expands_to,
+ krate,
+ resolver,
+ error_sink,
+ )
+ }
+}
+
+/// Helper wrapper for `AstId` with `ModPath`
+#[derive(Clone, Debug, Eq, PartialEq)]
+struct AstIdWithPath<T: ast::AstNode> {
+ ast_id: AstId<T>,
+ path: path::ModPath,
+}
+
+impl<T: ast::AstNode> AstIdWithPath<T> {
+ fn new(file_id: HirFileId, ast_id: FileAstId<T>, path: path::ModPath) -> AstIdWithPath<T> {
+ AstIdWithPath { ast_id: AstId::new(file_id, ast_id), path }
+ }
+}
+
+fn macro_call_as_call_id(
+ db: &dyn db::DefDatabase,
+ call: &AstIdWithPath<ast::MacroCall>,
+ expand_to: ExpandTo,
+ krate: CrateId,
+ resolver: impl Fn(path::ModPath) -> Option<MacroDefId>,
+ error_sink: &mut dyn FnMut(ExpandError),
+) -> Result<Result<MacroCallId, ErrorEmitted>, UnresolvedMacro> {
+ let def =
+ resolver(call.path.clone()).ok_or_else(|| UnresolvedMacro { path: call.path.clone() })?;
+
+ let res = if let MacroDefKind::BuiltInEager(..) = def.kind {
+ let macro_call = InFile::new(call.ast_id.file_id, call.ast_id.to_node(db.upcast()));
+
+ expand_eager_macro(db.upcast(), krate, macro_call, def, &resolver, error_sink)?
+ } else {
+ Ok(def.as_lazy_macro(
+ db.upcast(),
+ krate,
+ MacroCallKind::FnLike { ast_id: call.ast_id, expand_to },
+ ))
+ };
+ Ok(res)
+}
+
+pub fn macro_id_to_def_id(db: &dyn db::DefDatabase, id: MacroId) -> MacroDefId {
+ match id {
+ MacroId::Macro2Id(it) => {
+ let loc = it.lookup(db);
+
+ let item_tree = loc.id.item_tree(db);
+ let makro = &item_tree[loc.id.value];
+ let in_file = |m: FileAstId<ast::MacroDef>| InFile::new(loc.id.file_id(), m.upcast());
+ MacroDefId {
+ krate: loc.container.krate,
+ kind: match loc.expander {
+ MacroExpander::Declarative => MacroDefKind::Declarative(in_file(makro.ast_id)),
+ MacroExpander::BuiltIn(it) => MacroDefKind::BuiltIn(it, in_file(makro.ast_id)),
+ MacroExpander::BuiltInAttr(it) => {
+ MacroDefKind::BuiltInAttr(it, in_file(makro.ast_id))
+ }
+ MacroExpander::BuiltInDerive(it) => {
+ MacroDefKind::BuiltInDerive(it, in_file(makro.ast_id))
+ }
+ MacroExpander::BuiltInEager(it) => {
+ MacroDefKind::BuiltInEager(it, in_file(makro.ast_id))
+ }
+ },
+ local_inner: false,
+ }
+ }
+ MacroId::MacroRulesId(it) => {
+ let loc = it.lookup(db);
+
+ let item_tree = loc.id.item_tree(db);
+ let makro = &item_tree[loc.id.value];
+ let in_file = |m: FileAstId<ast::MacroRules>| InFile::new(loc.id.file_id(), m.upcast());
+ MacroDefId {
+ krate: loc.container.krate,
+ kind: match loc.expander {
+ MacroExpander::Declarative => MacroDefKind::Declarative(in_file(makro.ast_id)),
+ MacroExpander::BuiltIn(it) => MacroDefKind::BuiltIn(it, in_file(makro.ast_id)),
+ MacroExpander::BuiltInAttr(it) => {
+ MacroDefKind::BuiltInAttr(it, in_file(makro.ast_id))
+ }
+ MacroExpander::BuiltInDerive(it) => {
+ MacroDefKind::BuiltInDerive(it, in_file(makro.ast_id))
+ }
+ MacroExpander::BuiltInEager(it) => {
+ MacroDefKind::BuiltInEager(it, in_file(makro.ast_id))
+ }
+ },
+ local_inner: loc.local_inner,
+ }
+ }
+ MacroId::ProcMacroId(it) => {
+ let loc = it.lookup(db);
+
+ let item_tree = loc.id.item_tree(db);
+ let makro = &item_tree[loc.id.value];
+ MacroDefId {
+ krate: loc.container.krate,
+ kind: MacroDefKind::ProcMacro(
+ loc.expander,
+ loc.kind,
+ InFile::new(loc.id.file_id(), makro.ast_id),
+ ),
+ local_inner: false,
+ }
+ }
+ }
+}
+
+fn derive_macro_as_call_id(
+ db: &dyn db::DefDatabase,
+ item_attr: &AstIdWithPath<ast::Adt>,
+ derive_attr: AttrId,
+ derive_pos: u32,
+ krate: CrateId,
+ resolver: impl Fn(path::ModPath) -> Option<(MacroId, MacroDefId)>,
+) -> Result<(MacroId, MacroDefId, MacroCallId), UnresolvedMacro> {
+ let (macro_id, def_id) = resolver(item_attr.path.clone())
+ .ok_or_else(|| UnresolvedMacro { path: item_attr.path.clone() })?;
+ let call_id = def_id.as_lazy_macro(
+ db.upcast(),
+ krate,
+ MacroCallKind::Derive {
+ ast_id: item_attr.ast_id,
+ derive_index: derive_pos,
+ derive_attr_index: derive_attr.ast_index,
+ },
+ );
+ Ok((macro_id, def_id, call_id))
+}
+
+fn attr_macro_as_call_id(
+ db: &dyn db::DefDatabase,
+ item_attr: &AstIdWithPath<ast::Item>,
+ macro_attr: &Attr,
+ krate: CrateId,
+ def: MacroDefId,
+ is_derive: bool,
+) -> MacroCallId {
+ let mut arg = match macro_attr.input.as_deref() {
+ Some(attr::AttrInput::TokenTree(tt, map)) => (tt.clone(), map.clone()),
+ _ => Default::default(),
+ };
+
+ // The parentheses are always disposed here.
+ arg.0.delimiter = None;
+
+ let res = def.as_lazy_macro(
+ db.upcast(),
+ krate,
+ MacroCallKind::Attr {
+ ast_id: item_attr.ast_id,
+ attr_args: Arc::new(arg),
+ invoc_attr_index: macro_attr.id.ast_index,
+ is_derive,
+ },
+ );
+ res
+}
--- /dev/null
- let ast_id = InFile::new(self.file_id(), ast_id);
+//! The core of the module-level name resolution algorithm.
+//!
+//! `DefCollector::collect` contains the fixed-point iteration loop which
+//! resolves imports and expands macros.
+
+use std::{iter, mem};
+
+use base_db::{CrateId, Edition, FileId};
+use cfg::{CfgExpr, CfgOptions};
+use either::Either;
+use hir_expand::{
+ ast_id_map::FileAstId,
+ builtin_attr_macro::find_builtin_attr,
+ builtin_derive_macro::find_builtin_derive,
+ builtin_fn_macro::find_builtin_macro,
+ name::{name, AsName, Name},
+ proc_macro::ProcMacroExpander,
+ ExpandTo, HirFileId, InFile, MacroCallId, MacroCallKind, MacroCallLoc, MacroDefId,
+ MacroDefKind,
+};
+use itertools::{izip, Itertools};
+use la_arena::Idx;
+use limit::Limit;
+use rustc_hash::{FxHashMap, FxHashSet};
+use stdx::always;
+use syntax::{ast, SmolStr};
+
+use crate::{
+ attr::{Attr, AttrId, Attrs},
+ attr_macro_as_call_id,
+ db::DefDatabase,
+ derive_macro_as_call_id,
+ item_scope::{ImportType, PerNsGlobImports},
+ item_tree::{
+ self, Fields, FileItemTreeId, ImportKind, ItemTree, ItemTreeId, ItemTreeNode, MacroCall,
+ MacroDef, MacroRules, Mod, ModItem, ModKind, TreeId,
+ },
+ macro_call_as_call_id, macro_id_to_def_id,
+ nameres::{
+ diagnostics::DefDiagnostic,
+ mod_resolution::ModDir,
+ path_resolution::ReachedFixedPoint,
+ proc_macro::{ProcMacroDef, ProcMacroKind},
+ BuiltinShadowMode, DefMap, ModuleData, ModuleOrigin, ResolveMode,
+ },
+ path::{ImportAlias, ModPath, PathKind},
+ per_ns::PerNs,
+ visibility::{RawVisibility, Visibility},
+ AdtId, AstId, AstIdWithPath, ConstLoc, EnumLoc, EnumVariantId, ExternBlockLoc, FunctionId,
+ FunctionLoc, ImplLoc, Intern, ItemContainerId, LocalModuleId, Macro2Id, Macro2Loc,
+ MacroExpander, MacroId, MacroRulesId, MacroRulesLoc, ModuleDefId, ModuleId, ProcMacroId,
+ ProcMacroLoc, StaticLoc, StructLoc, TraitLoc, TypeAliasLoc, UnionLoc, UnresolvedMacro,
+};
+
+static GLOB_RECURSION_LIMIT: Limit = Limit::new(100);
+static EXPANSION_DEPTH_LIMIT: Limit = Limit::new(128);
+static FIXED_POINT_LIMIT: Limit = Limit::new(8192);
+
+pub(super) fn collect_defs(db: &dyn DefDatabase, mut def_map: DefMap, tree_id: TreeId) -> DefMap {
+ let crate_graph = db.crate_graph();
+
+ let mut deps = FxHashMap::default();
+ // populate external prelude and dependency list
+ let krate = &crate_graph[def_map.krate];
+ for dep in &krate.dependencies {
+ tracing::debug!("crate dep {:?} -> {:?}", dep.name, dep.crate_id);
+ let dep_def_map = db.crate_def_map(dep.crate_id);
+ let dep_root = dep_def_map.module_id(dep_def_map.root);
+
+ deps.insert(dep.as_name(), dep_root.into());
+
+ if dep.is_prelude() && !tree_id.is_block() {
+ def_map.extern_prelude.insert(dep.as_name(), dep_root);
+ }
+ }
+
+ let cfg_options = &krate.cfg_options;
+ let proc_macros = match &krate.proc_macro {
+ Ok(proc_macros) => {
+ proc_macros
+ .iter()
+ .enumerate()
+ .map(|(idx, it)| {
+ // FIXME: a hacky way to create a Name from string.
+ let name = tt::Ident { text: it.name.clone(), id: tt::TokenId::unspecified() };
+ (
+ name.as_name(),
+ ProcMacroExpander::new(def_map.krate, base_db::ProcMacroId(idx as u32)),
+ )
+ })
+ .collect()
+ }
+ Err(e) => {
+ def_map.proc_macro_loading_error = Some(e.clone().into_boxed_str());
+ Vec::new()
+ }
+ };
+ let is_proc_macro = krate.is_proc_macro;
+
+ let mut collector = DefCollector {
+ db,
+ def_map,
+ deps,
+ glob_imports: FxHashMap::default(),
+ unresolved_imports: Vec::new(),
+ indeterminate_imports: Vec::new(),
+ unresolved_macros: Vec::new(),
+ mod_dirs: FxHashMap::default(),
+ cfg_options,
+ proc_macros,
+ from_glob_import: Default::default(),
+ skip_attrs: Default::default(),
+ is_proc_macro,
+ };
+ if tree_id.is_block() {
+ collector.seed_with_inner(tree_id);
+ } else {
+ collector.seed_with_top_level();
+ }
+ collector.collect();
+ let mut def_map = collector.finish();
+ def_map.shrink_to_fit();
+ def_map
+}
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+enum PartialResolvedImport {
+ /// None of any namespaces is resolved
+ Unresolved,
+ /// One of namespaces is resolved
+ Indeterminate(PerNs),
+ /// All namespaces are resolved, OR it comes from other crate
+ Resolved(PerNs),
+}
+
+impl PartialResolvedImport {
+ fn namespaces(self) -> PerNs {
+ match self {
+ PartialResolvedImport::Unresolved => PerNs::none(),
+ PartialResolvedImport::Indeterminate(ns) | PartialResolvedImport::Resolved(ns) => ns,
+ }
+ }
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+enum ImportSource {
+ Import { id: ItemTreeId<item_tree::Import>, use_tree: Idx<ast::UseTree> },
+ ExternCrate(ItemTreeId<item_tree::ExternCrate>),
+}
+
+#[derive(Debug, Eq, PartialEq)]
+struct Import {
+ path: ModPath,
+ alias: Option<ImportAlias>,
+ visibility: RawVisibility,
+ kind: ImportKind,
+ is_prelude: bool,
+ is_extern_crate: bool,
+ is_macro_use: bool,
+ source: ImportSource,
+}
+
+impl Import {
+ fn from_use(
+ db: &dyn DefDatabase,
+ krate: CrateId,
+ tree: &ItemTree,
+ id: ItemTreeId<item_tree::Import>,
+ ) -> Vec<Self> {
+ let it = &tree[id.value];
+ let attrs = &tree.attrs(db, krate, ModItem::from(id.value).into());
+ let visibility = &tree[it.visibility];
+ let is_prelude = attrs.by_key("prelude_import").exists();
+
+ let mut res = Vec::new();
+ it.use_tree.expand(|idx, path, kind, alias| {
+ res.push(Self {
+ path,
+ alias,
+ visibility: visibility.clone(),
+ kind,
+ is_prelude,
+ is_extern_crate: false,
+ is_macro_use: false,
+ source: ImportSource::Import { id, use_tree: idx },
+ });
+ });
+ res
+ }
+
+ fn from_extern_crate(
+ db: &dyn DefDatabase,
+ krate: CrateId,
+ tree: &ItemTree,
+ id: ItemTreeId<item_tree::ExternCrate>,
+ ) -> Self {
+ let it = &tree[id.value];
+ let attrs = &tree.attrs(db, krate, ModItem::from(id.value).into());
+ let visibility = &tree[it.visibility];
+ Self {
+ path: ModPath::from_segments(PathKind::Plain, iter::once(it.name.clone())),
+ alias: it.alias.clone(),
+ visibility: visibility.clone(),
+ kind: ImportKind::Plain,
+ is_prelude: false,
+ is_extern_crate: true,
+ is_macro_use: attrs.by_key("macro_use").exists(),
+ source: ImportSource::ExternCrate(id),
+ }
+ }
+}
+
+#[derive(Debug, Eq, PartialEq)]
+struct ImportDirective {
+ module_id: LocalModuleId,
+ import: Import,
+ status: PartialResolvedImport,
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+struct MacroDirective {
+ module_id: LocalModuleId,
+ depth: usize,
+ kind: MacroDirectiveKind,
+ container: ItemContainerId,
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+enum MacroDirectiveKind {
+ FnLike { ast_id: AstIdWithPath<ast::MacroCall>, expand_to: ExpandTo },
+ Derive { ast_id: AstIdWithPath<ast::Adt>, derive_attr: AttrId, derive_pos: usize },
+ Attr { ast_id: AstIdWithPath<ast::Item>, attr: Attr, mod_item: ModItem, tree: TreeId },
+}
+
+/// Walks the tree of module recursively
+struct DefCollector<'a> {
+ db: &'a dyn DefDatabase,
+ def_map: DefMap,
+ deps: FxHashMap<Name, ModuleId>,
+ glob_imports: FxHashMap<LocalModuleId, Vec<(LocalModuleId, Visibility)>>,
+ unresolved_imports: Vec<ImportDirective>,
+ indeterminate_imports: Vec<ImportDirective>,
+ unresolved_macros: Vec<MacroDirective>,
+ mod_dirs: FxHashMap<LocalModuleId, ModDir>,
+ cfg_options: &'a CfgOptions,
+ /// List of procedural macros defined by this crate. This is read from the dynamic library
+ /// built by the build system, and is the list of proc. macros we can actually expand. It is
+ /// empty when proc. macro support is disabled (in which case we still do name resolution for
+ /// them).
+ proc_macros: Vec<(Name, ProcMacroExpander)>,
+ is_proc_macro: bool,
+ from_glob_import: PerNsGlobImports,
+ /// If we fail to resolve an attribute on a `ModItem`, we fall back to ignoring the attribute.
+ /// This map is used to skip all attributes up to and including the one that failed to resolve,
+ /// in order to not expand them twice.
+ ///
+ /// This also stores the attributes to skip when we resolve derive helpers and non-macro
+ /// non-builtin attributes in general.
+ skip_attrs: FxHashMap<InFile<ModItem>, AttrId>,
+}
+
+impl DefCollector<'_> {
+ fn seed_with_top_level(&mut self) {
+ let _p = profile::span("seed_with_top_level");
+
+ let file_id = self.db.crate_graph()[self.def_map.krate].root_file_id;
+ let item_tree = self.db.file_item_tree(file_id.into());
+ let module_id = self.def_map.root;
+
+ let attrs = item_tree.top_level_attrs(self.db, self.def_map.krate);
+ if attrs.cfg().map_or(true, |cfg| self.cfg_options.check(&cfg) != Some(false)) {
+ self.inject_prelude(&attrs);
+
+ // Process other crate-level attributes.
+ for attr in &*attrs {
+ let attr_name = match attr.path.as_ident() {
+ Some(name) => name,
+ None => continue,
+ };
+
+ if *attr_name == hir_expand::name![recursion_limit] {
+ if let Some(limit) = attr.string_value() {
+ if let Ok(limit) = limit.parse() {
+ self.def_map.recursion_limit = Some(limit);
+ }
+ }
+ continue;
+ }
+
+ if *attr_name == hir_expand::name![crate_type] {
+ if let Some("proc-macro") = attr.string_value().map(SmolStr::as_str) {
+ self.is_proc_macro = true;
+ }
+ continue;
+ }
+
+ if *attr_name == hir_expand::name![feature] {
+ let features =
+ attr.parse_path_comma_token_tree().into_iter().flatten().filter_map(
+ |feat| match feat.segments() {
+ [name] => Some(name.to_smol_str()),
+ _ => None,
+ },
+ );
+ self.def_map.unstable_features.extend(features);
+ }
+
+ let attr_is_register_like = *attr_name == hir_expand::name![register_attr]
+ || *attr_name == hir_expand::name![register_tool];
+ if !attr_is_register_like {
+ continue;
+ }
+
+ let registered_name = match attr.single_ident_value() {
+ Some(ident) => ident.as_name(),
+ _ => continue,
+ };
+
+ if *attr_name == hir_expand::name![register_attr] {
+ self.def_map.registered_attrs.push(registered_name.to_smol_str());
+ cov_mark::hit!(register_attr);
+ } else {
+ self.def_map.registered_tools.push(registered_name.to_smol_str());
+ cov_mark::hit!(register_tool);
+ }
+ }
+
+ ModCollector {
+ def_collector: self,
+ macro_depth: 0,
+ module_id,
+ tree_id: TreeId::new(file_id.into(), None),
+ item_tree: &item_tree,
+ mod_dir: ModDir::root(),
+ }
+ .collect_in_top_module(item_tree.top_level_items());
+ }
+ }
+
+ fn seed_with_inner(&mut self, tree_id: TreeId) {
+ let item_tree = tree_id.item_tree(self.db);
+ let module_id = self.def_map.root;
+
+ let is_cfg_enabled = item_tree
+ .top_level_attrs(self.db, self.def_map.krate)
+ .cfg()
+ .map_or(true, |cfg| self.cfg_options.check(&cfg) != Some(false));
+ if is_cfg_enabled {
+ ModCollector {
+ def_collector: self,
+ macro_depth: 0,
+ module_id,
+ tree_id,
+ item_tree: &item_tree,
+ mod_dir: ModDir::root(),
+ }
+ .collect_in_top_module(item_tree.top_level_items());
+ }
+ }
+
+ fn resolution_loop(&mut self) {
+ let _p = profile::span("DefCollector::resolution_loop");
+
+ // main name resolution fixed-point loop.
+ let mut i = 0;
+ 'resolve_attr: loop {
+ 'resolve_macros: loop {
+ self.db.unwind_if_cancelled();
+
+ {
+ let _p = profile::span("resolve_imports loop");
+
+ 'resolve_imports: loop {
+ if self.resolve_imports() == ReachedFixedPoint::Yes {
+ break 'resolve_imports;
+ }
+ }
+ }
+ if self.resolve_macros() == ReachedFixedPoint::Yes {
+ break 'resolve_macros;
+ }
+
+ i += 1;
+ if FIXED_POINT_LIMIT.check(i).is_err() {
+ tracing::error!("name resolution is stuck");
+ break 'resolve_attr;
+ }
+ }
+
+ if self.reseed_with_unresolved_attribute() == ReachedFixedPoint::Yes {
+ break 'resolve_attr;
+ }
+ }
+ }
+
+ fn collect(&mut self) {
+ let _p = profile::span("DefCollector::collect");
+
+ self.resolution_loop();
+
+ // Resolve all indeterminate resolved imports again
+ // As some of the macros will expand newly import shadowing partial resolved imports
+ // FIXME: We maybe could skip this, if we handle the indeterminate imports in `resolve_imports`
+ // correctly
+ let partial_resolved = self.indeterminate_imports.drain(..).map(|directive| {
+ ImportDirective { status: PartialResolvedImport::Unresolved, ..directive }
+ });
+ self.unresolved_imports.extend(partial_resolved);
+ self.resolve_imports();
+
+ let unresolved_imports = mem::take(&mut self.unresolved_imports);
+ // show unresolved imports in completion, etc
+ for directive in &unresolved_imports {
+ self.record_resolved_import(directive);
+ }
+ self.unresolved_imports = unresolved_imports;
+
+ if self.is_proc_macro {
+ // A crate exporting procedural macros is not allowed to export anything else.
+ //
+ // Additionally, while the proc macro entry points must be `pub`, they are not publicly
+ // exported in type/value namespace. This function reduces the visibility of all items
+ // in the crate root that aren't proc macros.
+ let root = self.def_map.root;
+ let module_id = self.def_map.module_id(root);
+ let root = &mut self.def_map.modules[root];
+ root.scope.censor_non_proc_macros(module_id);
+ }
+ }
+
+ /// When the fixed-point loop reaches a stable state, we might still have
+ /// some unresolved attributes left over. This takes one of them, and feeds
+ /// the item it's applied to back into name resolution.
+ ///
+ /// This effectively ignores the fact that the macro is there and just treats the items as
+ /// normal code.
+ ///
+ /// This improves UX for unresolved attributes, and replicates the
+ /// behavior before we supported proc. attribute macros.
+ fn reseed_with_unresolved_attribute(&mut self) -> ReachedFixedPoint {
+ cov_mark::hit!(unresolved_attribute_fallback);
+
+ let unresolved_attr =
+ self.unresolved_macros.iter().enumerate().find_map(|(idx, directive)| match &directive
+ .kind
+ {
+ MacroDirectiveKind::Attr { ast_id, mod_item, attr, tree } => {
+ self.def_map.diagnostics.push(DefDiagnostic::unresolved_macro_call(
+ directive.module_id,
+ MacroCallKind::Attr {
+ ast_id: ast_id.ast_id,
+ attr_args: Default::default(),
+ invoc_attr_index: attr.id.ast_index,
+ is_derive: false,
+ },
+ attr.path().clone(),
+ ));
+
+ self.skip_attrs.insert(ast_id.ast_id.with_value(*mod_item), attr.id);
+
+ Some((idx, directive, *mod_item, *tree))
+ }
+ _ => None,
+ });
+
+ match unresolved_attr {
+ Some((pos, &MacroDirective { module_id, depth, container, .. }, mod_item, tree_id)) => {
+ let item_tree = &tree_id.item_tree(self.db);
+ let mod_dir = self.mod_dirs[&module_id].clone();
+ ModCollector {
+ def_collector: self,
+ macro_depth: depth,
+ module_id,
+ tree_id,
+ item_tree,
+ mod_dir,
+ }
+ .collect(&[mod_item], container);
+
+ self.unresolved_macros.swap_remove(pos);
+ // Continue name resolution with the new data.
+ ReachedFixedPoint::No
+ }
+ None => ReachedFixedPoint::Yes,
+ }
+ }
+
+ fn inject_prelude(&mut self, crate_attrs: &Attrs) {
+ // See compiler/rustc_builtin_macros/src/standard_library_imports.rs
+
+ if crate_attrs.by_key("no_core").exists() {
+ // libcore does not get a prelude.
+ return;
+ }
+
+ let krate = if crate_attrs.by_key("no_std").exists() {
+ name![core]
+ } else {
+ let std = name![std];
+ if self.def_map.extern_prelude().any(|(name, _)| *name == std) {
+ std
+ } else {
+ // If `std` does not exist for some reason, fall back to core. This mostly helps
+ // keep r-a's own tests minimal.
+ name![core]
+ }
+ };
+
+ let edition = match self.def_map.edition {
+ Edition::Edition2015 => name![rust_2015],
+ Edition::Edition2018 => name![rust_2018],
+ Edition::Edition2021 => name![rust_2021],
+ };
+
+ let path_kind = match self.def_map.edition {
+ Edition::Edition2015 => PathKind::Plain,
+ _ => PathKind::Abs,
+ };
+ let path =
+ ModPath::from_segments(path_kind, [krate.clone(), name![prelude], edition].into_iter());
+ // Fall back to the older `std::prelude::v1` for compatibility with Rust <1.52.0
+ // FIXME remove this fallback
+ let fallback_path =
+ ModPath::from_segments(path_kind, [krate, name![prelude], name![v1]].into_iter());
+
+ for path in &[path, fallback_path] {
+ let (per_ns, _) = self.def_map.resolve_path(
+ self.db,
+ self.def_map.root,
+ path,
+ BuiltinShadowMode::Other,
+ );
+
+ match per_ns.types {
+ Some((ModuleDefId::ModuleId(m), _)) => {
+ self.def_map.prelude = Some(m);
+ break;
+ }
+ types => {
+ tracing::debug!(
+ "could not resolve prelude path `{}` to module (resolved to {:?})",
+ path,
+ types
+ );
+ }
+ }
+ }
+ }
+
+ /// Adds a definition of procedural macro `name` to the root module.
+ ///
+ /// # Notes on procedural macro resolution
+ ///
+ /// Procedural macro functionality is provided by the build system: It has to build the proc
+ /// macro and pass the resulting dynamic library to rust-analyzer.
+ ///
+ /// When procedural macro support is enabled, the list of proc macros exported by a crate is
+ /// known before we resolve names in the crate. This list is stored in `self.proc_macros` and is
+ /// derived from the dynamic library.
+ ///
+ /// However, we *also* would like to be able to at least *resolve* macros on our own, without
+ /// help by the build system. So, when the macro isn't found in `self.proc_macros`, we instead
+ /// use a dummy expander that always errors. This comes with the drawback of macros potentially
+ /// going out of sync with what the build system sees (since we resolve using VFS state, but
+ /// Cargo builds only on-disk files). We could and probably should add diagnostics for that.
+ fn export_proc_macro(
+ &mut self,
+ def: ProcMacroDef,
+ id: ItemTreeId<item_tree::Function>,
+ fn_id: FunctionId,
+ module_id: ModuleId,
+ ) {
+ let kind = def.kind.to_basedb_kind();
+ let (expander, kind) = match self.proc_macros.iter().find(|(n, _)| n == &def.name) {
+ Some(&(_, expander)) => (expander, kind),
+ None => (ProcMacroExpander::dummy(self.def_map.krate), kind),
+ };
+
+ let proc_macro_id =
+ ProcMacroLoc { container: module_id, id, expander, kind }.intern(self.db);
+ self.define_proc_macro(def.name.clone(), proc_macro_id);
+ if let ProcMacroKind::CustomDerive { helpers } = def.kind {
+ self.def_map
+ .exported_derives
+ .insert(macro_id_to_def_id(self.db, proc_macro_id.into()), helpers);
+ }
+ self.def_map.fn_proc_macro_mapping.insert(fn_id, proc_macro_id);
+ }
+
+ /// Define a macro with `macro_rules`.
+ ///
+ /// It will define the macro in legacy textual scope, and if it has `#[macro_export]`,
+ /// then it is also defined in the root module scope.
+ /// You can `use` or invoke it by `crate::macro_name` anywhere, before or after the definition.
+ ///
+ /// It is surprising that the macro will never be in the current module scope.
+ /// These code fails with "unresolved import/macro",
+ /// ```rust,compile_fail
+ /// mod m { macro_rules! foo { () => {} } }
+ /// use m::foo as bar;
+ /// ```
+ ///
+ /// ```rust,compile_fail
+ /// macro_rules! foo { () => {} }
+ /// self::foo!();
+ /// crate::foo!();
+ /// ```
+ ///
+ /// Well, this code compiles, because the plain path `foo` in `use` is searched
+ /// in the legacy textual scope only.
+ /// ```rust
+ /// macro_rules! foo { () => {} }
+ /// use foo as bar;
+ /// ```
+ fn define_macro_rules(
+ &mut self,
+ module_id: LocalModuleId,
+ name: Name,
+ macro_: MacroRulesId,
+ export: bool,
+ ) {
+ // Textual scoping
+ self.define_legacy_macro(module_id, name.clone(), macro_.into());
+
+ // Module scoping
+ // In Rust, `#[macro_export]` macros are unconditionally visible at the
+ // crate root, even if the parent modules is **not** visible.
+ if export {
+ let module_id = self.def_map.root;
+ self.def_map.modules[module_id].scope.declare(macro_.into());
+ self.update(
+ module_id,
+ &[(Some(name), PerNs::macros(macro_.into(), Visibility::Public))],
+ Visibility::Public,
+ ImportType::Named,
+ );
+ }
+ }
+
+ /// Define a legacy textual scoped macro in module
+ ///
+ /// We use a map `legacy_macros` to store all legacy textual scoped macros visible per module.
+ /// It will clone all macros from parent legacy scope, whose definition is prior to
+ /// the definition of current module.
+ /// And also, `macro_use` on a module will import all legacy macros visible inside to
+ /// current legacy scope, with possible shadowing.
+ fn define_legacy_macro(&mut self, module_id: LocalModuleId, name: Name, mac: MacroId) {
+ // Always shadowing
+ self.def_map.modules[module_id].scope.define_legacy_macro(name, mac);
+ }
+
+ /// Define a macro 2.0 macro
+ ///
+ /// The scoped of macro 2.0 macro is equal to normal function
+ fn define_macro_def(
+ &mut self,
+ module_id: LocalModuleId,
+ name: Name,
+ macro_: Macro2Id,
+ vis: &RawVisibility,
+ ) {
+ let vis =
+ self.def_map.resolve_visibility(self.db, module_id, vis).unwrap_or(Visibility::Public);
+ self.def_map.modules[module_id].scope.declare(macro_.into());
+ self.update(
+ module_id,
+ &[(Some(name), PerNs::macros(macro_.into(), Visibility::Public))],
+ vis,
+ ImportType::Named,
+ );
+ }
+
+ /// Define a proc macro
+ ///
+ /// A proc macro is similar to normal macro scope, but it would not visible in legacy textual scoped.
+ /// And unconditionally exported.
+ fn define_proc_macro(&mut self, name: Name, macro_: ProcMacroId) {
+ let module_id = self.def_map.root;
+ self.def_map.modules[module_id].scope.declare(macro_.into());
+ self.update(
+ module_id,
+ &[(Some(name), PerNs::macros(macro_.into(), Visibility::Public))],
+ Visibility::Public,
+ ImportType::Named,
+ );
+ }
+
+ /// Import macros from `#[macro_use] extern crate`.
+ fn import_macros_from_extern_crate(
+ &mut self,
+ current_module_id: LocalModuleId,
+ extern_crate: &item_tree::ExternCrate,
+ ) {
+ tracing::debug!(
+ "importing macros from extern crate: {:?} ({:?})",
+ extern_crate,
+ self.def_map.edition,
+ );
+
+ if let Some(m) = self.resolve_extern_crate(&extern_crate.name) {
+ if m == self.def_map.module_id(current_module_id) {
+ cov_mark::hit!(ignore_macro_use_extern_crate_self);
+ return;
+ }
+
+ cov_mark::hit!(macro_rules_from_other_crates_are_visible_with_macro_use);
+ self.import_all_macros_exported(current_module_id, m.krate);
+ }
+ }
+
+ /// Import all exported macros from another crate
+ ///
+ /// Exported macros are just all macros in the root module scope.
+ /// Note that it contains not only all `#[macro_export]` macros, but also all aliases
+ /// created by `use` in the root module, ignoring the visibility of `use`.
+ fn import_all_macros_exported(&mut self, current_module_id: LocalModuleId, krate: CrateId) {
+ let def_map = self.db.crate_def_map(krate);
+ for (name, def) in def_map[def_map.root].scope.macros() {
+ // `#[macro_use]` brings macros into legacy scope. Yes, even non-`macro_rules!` macros.
+ self.define_legacy_macro(current_module_id, name.clone(), def);
+ }
+ }
+
+ /// Tries to resolve every currently unresolved import.
+ fn resolve_imports(&mut self) -> ReachedFixedPoint {
+ let mut res = ReachedFixedPoint::Yes;
+ let imports = mem::take(&mut self.unresolved_imports);
+
+ self.unresolved_imports = imports
+ .into_iter()
+ .filter_map(|mut directive| {
+ directive.status = self.resolve_import(directive.module_id, &directive.import);
+ match directive.status {
+ PartialResolvedImport::Indeterminate(_) => {
+ self.record_resolved_import(&directive);
+ self.indeterminate_imports.push(directive);
+ res = ReachedFixedPoint::No;
+ None
+ }
+ PartialResolvedImport::Resolved(_) => {
+ self.record_resolved_import(&directive);
+ res = ReachedFixedPoint::No;
+ None
+ }
+ PartialResolvedImport::Unresolved => Some(directive),
+ }
+ })
+ .collect();
+ res
+ }
+
+ fn resolve_import(&self, module_id: LocalModuleId, import: &Import) -> PartialResolvedImport {
+ let _p = profile::span("resolve_import").detail(|| format!("{}", import.path));
+ tracing::debug!("resolving import: {:?} ({:?})", import, self.def_map.edition);
+ if import.is_extern_crate {
+ let name = import
+ .path
+ .as_ident()
+ .expect("extern crate should have been desugared to one-element path");
+
+ let res = self.resolve_extern_crate(name);
+
+ match res {
+ Some(res) => {
+ PartialResolvedImport::Resolved(PerNs::types(res.into(), Visibility::Public))
+ }
+ None => PartialResolvedImport::Unresolved,
+ }
+ } else {
+ let res = self.def_map.resolve_path_fp_with_macro(
+ self.db,
+ ResolveMode::Import,
+ module_id,
+ &import.path,
+ BuiltinShadowMode::Module,
+ );
+
+ let def = res.resolved_def;
+ if res.reached_fixedpoint == ReachedFixedPoint::No || def.is_none() {
+ return PartialResolvedImport::Unresolved;
+ }
+
+ if let Some(krate) = res.krate {
+ if krate != self.def_map.krate {
+ return PartialResolvedImport::Resolved(
+ def.filter_visibility(|v| matches!(v, Visibility::Public)),
+ );
+ }
+ }
+
+ // Check whether all namespace is resolved
+ if def.take_types().is_some()
+ && def.take_values().is_some()
+ && def.take_macros().is_some()
+ {
+ PartialResolvedImport::Resolved(def)
+ } else {
+ PartialResolvedImport::Indeterminate(def)
+ }
+ }
+ }
+
+ fn resolve_extern_crate(&self, name: &Name) -> Option<ModuleId> {
+ if *name == name!(self) {
+ cov_mark::hit!(extern_crate_self_as);
+ let root = match self.def_map.block {
+ Some(_) => {
+ let def_map = self.def_map.crate_root(self.db).def_map(self.db);
+ def_map.module_id(def_map.root())
+ }
+ None => self.def_map.module_id(self.def_map.root()),
+ };
+ Some(root)
+ } else {
+ self.deps.get(name).copied()
+ }
+ }
+
+ fn record_resolved_import(&mut self, directive: &ImportDirective) {
+ let _p = profile::span("record_resolved_import");
+
+ let module_id = directive.module_id;
+ let import = &directive.import;
+ let mut def = directive.status.namespaces();
+ let vis = self
+ .def_map
+ .resolve_visibility(self.db, module_id, &directive.import.visibility)
+ .unwrap_or(Visibility::Public);
+
+ match import.kind {
+ ImportKind::Plain | ImportKind::TypeOnly => {
+ let name = match &import.alias {
+ Some(ImportAlias::Alias(name)) => Some(name),
+ Some(ImportAlias::Underscore) => None,
+ None => match import.path.segments().last() {
+ Some(last_segment) => Some(last_segment),
+ None => {
+ cov_mark::hit!(bogus_paths);
+ return;
+ }
+ },
+ };
+
+ if import.kind == ImportKind::TypeOnly {
+ def.values = None;
+ def.macros = None;
+ }
+
+ tracing::debug!("resolved import {:?} ({:?}) to {:?}", name, import, def);
+
+ // extern crates in the crate root are special-cased to insert entries into the extern prelude: rust-lang/rust#54658
+ if import.is_extern_crate
+ && self.def_map.block.is_none()
+ && module_id == self.def_map.root
+ {
+ if let (Some(ModuleDefId::ModuleId(def)), Some(name)) = (def.take_types(), name)
+ {
+ self.def_map.extern_prelude.insert(name.clone(), def);
+ }
+ }
+
+ self.update(module_id, &[(name.cloned(), def)], vis, ImportType::Named);
+ }
+ ImportKind::Glob => {
+ tracing::debug!("glob import: {:?}", import);
+ match def.take_types() {
+ Some(ModuleDefId::ModuleId(m)) => {
+ if import.is_prelude {
+ // Note: This dodgily overrides the injected prelude. The rustc
+ // implementation seems to work the same though.
+ cov_mark::hit!(std_prelude);
+ self.def_map.prelude = Some(m);
+ } else if m.krate != self.def_map.krate {
+ cov_mark::hit!(glob_across_crates);
+ // glob import from other crate => we can just import everything once
+ let item_map = m.def_map(self.db);
+ let scope = &item_map[m.local_id].scope;
+
+ // Module scoped macros is included
+ let items = scope
+ .resolutions()
+ // only keep visible names...
+ .map(|(n, res)| {
+ (n, res.filter_visibility(|v| v.is_visible_from_other_crate()))
+ })
+ .filter(|(_, res)| !res.is_none())
+ .collect::<Vec<_>>();
+
+ self.update(module_id, &items, vis, ImportType::Glob);
+ } else {
+ // glob import from same crate => we do an initial
+ // import, and then need to propagate any further
+ // additions
+ let def_map;
+ let scope = if m.block == self.def_map.block_id() {
+ &self.def_map[m.local_id].scope
+ } else {
+ def_map = m.def_map(self.db);
+ &def_map[m.local_id].scope
+ };
+
+ // Module scoped macros is included
+ let items = scope
+ .resolutions()
+ // only keep visible names...
+ .map(|(n, res)| {
+ (
+ n,
+ res.filter_visibility(|v| {
+ v.is_visible_from_def_map(
+ self.db,
+ &self.def_map,
+ module_id,
+ )
+ }),
+ )
+ })
+ .filter(|(_, res)| !res.is_none())
+ .collect::<Vec<_>>();
+
+ self.update(module_id, &items, vis, ImportType::Glob);
+ // record the glob import in case we add further items
+ let glob = self.glob_imports.entry(m.local_id).or_default();
+ if !glob.iter().any(|(mid, _)| *mid == module_id) {
+ glob.push((module_id, vis));
+ }
+ }
+ }
+ Some(ModuleDefId::AdtId(AdtId::EnumId(e))) => {
+ cov_mark::hit!(glob_enum);
+ // glob import from enum => just import all the variants
+
+ // XXX: urgh, so this works by accident! Here, we look at
+ // the enum data, and, in theory, this might require us to
+ // look back at the crate_def_map, creating a cycle. For
+ // example, `enum E { crate::some_macro!(); }`. Luckily, the
+ // only kind of macro that is allowed inside enum is a
+ // `cfg_macro`, and we don't need to run name resolution for
+ // it, but this is sheer luck!
+ let enum_data = self.db.enum_data(e);
+ let resolutions = enum_data
+ .variants
+ .iter()
+ .map(|(local_id, variant_data)| {
+ let name = variant_data.name.clone();
+ let variant = EnumVariantId { parent: e, local_id };
+ let res = PerNs::both(variant.into(), variant.into(), vis);
+ (Some(name), res)
+ })
+ .collect::<Vec<_>>();
+ self.update(module_id, &resolutions, vis, ImportType::Glob);
+ }
+ Some(d) => {
+ tracing::debug!("glob import {:?} from non-module/enum {:?}", import, d);
+ }
+ None => {
+ tracing::debug!("glob import {:?} didn't resolve as type", import);
+ }
+ }
+ }
+ }
+ }
+
+ fn update(
+ &mut self,
+ module_id: LocalModuleId,
+ resolutions: &[(Option<Name>, PerNs)],
+ vis: Visibility,
+ import_type: ImportType,
+ ) {
+ self.db.unwind_if_cancelled();
+ self.update_recursive(module_id, resolutions, vis, import_type, 0)
+ }
+
+ fn update_recursive(
+ &mut self,
+ module_id: LocalModuleId,
+ resolutions: &[(Option<Name>, PerNs)],
+ // All resolutions are imported with this visibility; the visibilities in
+ // the `PerNs` values are ignored and overwritten
+ vis: Visibility,
+ import_type: ImportType,
+ depth: usize,
+ ) {
+ if GLOB_RECURSION_LIMIT.check(depth).is_err() {
+ // prevent stack overflows (but this shouldn't be possible)
+ panic!("infinite recursion in glob imports!");
+ }
+ let mut changed = false;
+
+ for (name, res) in resolutions {
+ match name {
+ Some(name) => {
+ let scope = &mut self.def_map.modules[module_id].scope;
+ changed |= scope.push_res_with_import(
+ &mut self.from_glob_import,
+ (module_id, name.clone()),
+ res.with_visibility(vis),
+ import_type,
+ );
+ }
+ None => {
+ let tr = match res.take_types() {
+ Some(ModuleDefId::TraitId(tr)) => tr,
+ Some(other) => {
+ tracing::debug!("non-trait `_` import of {:?}", other);
+ continue;
+ }
+ None => continue,
+ };
+ let old_vis = self.def_map.modules[module_id].scope.unnamed_trait_vis(tr);
+ let should_update = match old_vis {
+ None => true,
+ Some(old_vis) => {
+ let max_vis = old_vis.max(vis, &self.def_map).unwrap_or_else(|| {
+ panic!("`Tr as _` imports with unrelated visibilities {:?} and {:?} (trait {:?})", old_vis, vis, tr);
+ });
+
+ if max_vis == old_vis {
+ false
+ } else {
+ cov_mark::hit!(upgrade_underscore_visibility);
+ true
+ }
+ }
+ };
+
+ if should_update {
+ changed = true;
+ self.def_map.modules[module_id].scope.push_unnamed_trait(tr, vis);
+ }
+ }
+ }
+ }
+
+ if !changed {
+ return;
+ }
+ let glob_imports = self
+ .glob_imports
+ .get(&module_id)
+ .into_iter()
+ .flatten()
+ .filter(|(glob_importing_module, _)| {
+ // we know all resolutions have the same visibility (`vis`), so we
+ // just need to check that once
+ vis.is_visible_from_def_map(self.db, &self.def_map, *glob_importing_module)
+ })
+ .cloned()
+ .collect::<Vec<_>>();
+
+ for (glob_importing_module, glob_import_vis) in glob_imports {
+ self.update_recursive(
+ glob_importing_module,
+ resolutions,
+ glob_import_vis,
+ ImportType::Glob,
+ depth + 1,
+ );
+ }
+ }
+
+ fn resolve_macros(&mut self) -> ReachedFixedPoint {
+ let mut macros = mem::take(&mut self.unresolved_macros);
+ let mut resolved = Vec::new();
+ let mut push_resolved = |directive: &MacroDirective, call_id| {
+ resolved.push((directive.module_id, directive.depth, directive.container, call_id));
+ };
+ let mut res = ReachedFixedPoint::Yes;
+ macros.retain(|directive| {
+ let resolver = |path| {
+ let resolved_res = self.def_map.resolve_path_fp_with_macro(
+ self.db,
+ ResolveMode::Other,
+ directive.module_id,
+ &path,
+ BuiltinShadowMode::Module,
+ );
+ resolved_res
+ .resolved_def
+ .take_macros()
+ .map(|it| (it, macro_id_to_def_id(self.db, it)))
+ };
+ let resolver_def_id = |path| resolver(path).map(|(_, it)| it);
+
+ match &directive.kind {
+ MacroDirectiveKind::FnLike { ast_id, expand_to } => {
+ let call_id = macro_call_as_call_id(
+ self.db,
+ ast_id,
+ *expand_to,
+ self.def_map.krate,
+ &resolver_def_id,
+ &mut |_err| (),
+ );
+ if let Ok(Ok(call_id)) = call_id {
+ push_resolved(directive, call_id);
+ res = ReachedFixedPoint::No;
+ return false;
+ }
+ }
+ MacroDirectiveKind::Derive { ast_id, derive_attr, derive_pos } => {
+ let id = derive_macro_as_call_id(
+ self.db,
+ ast_id,
+ *derive_attr,
+ *derive_pos as u32,
+ self.def_map.krate,
+ &resolver,
+ );
+
+ if let Ok((macro_id, def_id, call_id)) = id {
+ self.def_map.modules[directive.module_id].scope.set_derive_macro_invoc(
+ ast_id.ast_id,
+ call_id,
+ *derive_attr,
+ *derive_pos,
+ );
+ // Record its helper attributes.
+ if def_id.krate != self.def_map.krate {
+ let def_map = self.db.crate_def_map(def_id.krate);
+ if let Some(helpers) = def_map.exported_derives.get(&def_id) {
+ self.def_map
+ .derive_helpers_in_scope
+ .entry(ast_id.ast_id.map(|it| it.upcast()))
+ .or_default()
+ .extend(izip!(
+ helpers.iter().cloned(),
+ iter::repeat(macro_id),
+ iter::repeat(call_id),
+ ));
+ }
+ }
+
+ push_resolved(directive, call_id);
+ res = ReachedFixedPoint::No;
+ return false;
+ }
+ }
+ MacroDirectiveKind::Attr { ast_id: file_ast_id, mod_item, attr, tree } => {
+ let &AstIdWithPath { ast_id, ref path } = file_ast_id;
+ let file_id = ast_id.file_id;
+
+ let mut recollect_without = |collector: &mut Self| {
+ // Remove the original directive since we resolved it.
+ let mod_dir = collector.mod_dirs[&directive.module_id].clone();
+ collector.skip_attrs.insert(InFile::new(file_id, *mod_item), attr.id);
+
+ let item_tree = tree.item_tree(self.db);
+ ModCollector {
+ def_collector: collector,
+ macro_depth: directive.depth,
+ module_id: directive.module_id,
+ tree_id: *tree,
+ item_tree: &item_tree,
+ mod_dir,
+ }
+ .collect(&[*mod_item], directive.container);
+ res = ReachedFixedPoint::No;
+ false
+ };
+
+ if let Some(ident) = path.as_ident() {
+ if let Some(helpers) = self.def_map.derive_helpers_in_scope.get(&ast_id) {
+ if helpers.iter().any(|(it, ..)| it == ident) {
+ cov_mark::hit!(resolved_derive_helper);
+ // Resolved to derive helper. Collect the item's attributes again,
+ // starting after the derive helper.
+ return recollect_without(self);
+ }
+ }
+ }
+
+ let def = match resolver_def_id(path.clone()) {
+ Some(def) if def.is_attribute() => def,
+ _ => return true,
+ };
+ if matches!(
+ def,
+ MacroDefId { kind:MacroDefKind::BuiltInAttr(expander, _),.. }
+ if expander.is_derive()
+ ) {
+ // Resolved to `#[derive]`
+
+ let item_tree = tree.item_tree(self.db);
+ let ast_adt_id: FileAstId<ast::Adt> = match *mod_item {
+ ModItem::Struct(strukt) => item_tree[strukt].ast_id().upcast(),
+ ModItem::Union(union) => item_tree[union].ast_id().upcast(),
+ ModItem::Enum(enum_) => item_tree[enum_].ast_id().upcast(),
+ _ => {
+ let diag = DefDiagnostic::invalid_derive_target(
+ directive.module_id,
+ ast_id,
+ attr.id,
+ );
+ self.def_map.diagnostics.push(diag);
+ return recollect_without(self);
+ }
+ };
+ let ast_id = ast_id.with_value(ast_adt_id);
+
+ match attr.parse_path_comma_token_tree() {
+ Some(derive_macros) => {
+ let mut len = 0;
+ for (idx, path) in derive_macros.enumerate() {
+ let ast_id = AstIdWithPath::new(file_id, ast_id.value, path);
+ self.unresolved_macros.push(MacroDirective {
+ module_id: directive.module_id,
+ depth: directive.depth + 1,
+ kind: MacroDirectiveKind::Derive {
+ ast_id,
+ derive_attr: attr.id,
+ derive_pos: idx,
+ },
+ container: directive.container,
+ });
+ len = idx;
+ }
+
+ // We treat the #[derive] macro as an attribute call, but we do not resolve it for nameres collection.
+ // This is just a trick to be able to resolve the input to derives as proper paths.
+ // Check the comment in [`builtin_attr_macro`].
+ let call_id = attr_macro_as_call_id(
+ self.db,
+ file_ast_id,
+ attr,
+ self.def_map.krate,
+ def,
+ true,
+ );
+ self.def_map.modules[directive.module_id]
+ .scope
+ .init_derive_attribute(ast_id, attr.id, call_id, len + 1);
+ }
+ None => {
+ let diag = DefDiagnostic::malformed_derive(
+ directive.module_id,
+ ast_id,
+ attr.id,
+ );
+ self.def_map.diagnostics.push(diag);
+ }
+ }
+
+ return recollect_without(self);
+ }
+
+ // Not resolved to a derive helper or the derive attribute, so try to treat as a normal attribute.
+ let call_id = attr_macro_as_call_id(
+ self.db,
+ file_ast_id,
+ attr,
+ self.def_map.krate,
+ def,
+ false,
+ );
+ let loc: MacroCallLoc = self.db.lookup_intern_macro_call(call_id);
+
+ // If proc attribute macro expansion is disabled, skip expanding it here
+ if !self.db.enable_proc_attr_macros() {
+ self.def_map.diagnostics.push(DefDiagnostic::unresolved_proc_macro(
+ directive.module_id,
+ loc.kind,
+ loc.def.krate,
+ ));
+ return recollect_without(self);
+ }
+
+ // Skip #[test]/#[bench] expansion, which would merely result in more memory usage
+ // due to duplicating functions into macro expansions
+ if matches!(
+ loc.def.kind,
+ MacroDefKind::BuiltInAttr(expander, _)
+ if expander.is_test() || expander.is_bench()
+ ) {
+ return recollect_without(self);
+ }
+
+ if let MacroDefKind::ProcMacro(exp, ..) = loc.def.kind {
+ if exp.is_dummy() {
+ // If there's no expander for the proc macro (e.g.
+ // because proc macros are disabled, or building the
+ // proc macro crate failed), report this and skip
+ // expansion like we would if it was disabled
+ self.def_map.diagnostics.push(DefDiagnostic::unresolved_proc_macro(
+ directive.module_id,
+ loc.kind,
+ loc.def.krate,
+ ));
+
+ return recollect_without(self);
+ }
+ }
+
+ self.def_map.modules[directive.module_id]
+ .scope
+ .add_attr_macro_invoc(ast_id, call_id);
+
+ push_resolved(directive, call_id);
+ res = ReachedFixedPoint::No;
+ return false;
+ }
+ }
+
+ true
+ });
+ // Attribute resolution can add unresolved macro invocations, so concatenate the lists.
+ macros.extend(mem::take(&mut self.unresolved_macros));
+ self.unresolved_macros = macros;
+
+ for (module_id, depth, container, macro_call_id) in resolved {
+ self.collect_macro_expansion(module_id, macro_call_id, depth, container);
+ }
+
+ res
+ }
+
+ fn collect_macro_expansion(
+ &mut self,
+ module_id: LocalModuleId,
+ macro_call_id: MacroCallId,
+ depth: usize,
+ container: ItemContainerId,
+ ) {
+ if EXPANSION_DEPTH_LIMIT.check(depth).is_err() {
+ cov_mark::hit!(macro_expansion_overflow);
+ tracing::warn!("macro expansion is too deep");
+ return;
+ }
+ let file_id = macro_call_id.as_file();
+
+ // First, fetch the raw expansion result for purposes of error reporting. This goes through
+ // `macro_expand_error` to avoid depending on the full expansion result (to improve
+ // incrementality).
+ let loc: MacroCallLoc = self.db.lookup_intern_macro_call(macro_call_id);
+ let err = self.db.macro_expand_error(macro_call_id);
+ if let Some(err) = err {
+ let diag = match err {
+ hir_expand::ExpandError::UnresolvedProcMacro(krate) => {
+ always!(krate == loc.def.krate);
+ // Missing proc macros are non-fatal, so they are handled specially.
+ DefDiagnostic::unresolved_proc_macro(module_id, loc.kind.clone(), loc.def.krate)
+ }
+ _ => DefDiagnostic::macro_error(module_id, loc.kind.clone(), err.to_string()),
+ };
+
+ self.def_map.diagnostics.push(diag);
+ }
+
+ // Then, fetch and process the item tree. This will reuse the expansion result from above.
+ let item_tree = self.db.file_item_tree(file_id);
+ let mod_dir = self.mod_dirs[&module_id].clone();
+ ModCollector {
+ def_collector: &mut *self,
+ macro_depth: depth,
+ tree_id: TreeId::new(file_id, None),
+ module_id,
+ item_tree: &item_tree,
+ mod_dir,
+ }
+ .collect(item_tree.top_level_items(), container);
+ }
+
+ fn finish(mut self) -> DefMap {
+ // Emit diagnostics for all remaining unexpanded macros.
+
+ let _p = profile::span("DefCollector::finish");
+
+ for directive in &self.unresolved_macros {
+ match &directive.kind {
+ MacroDirectiveKind::FnLike { ast_id, expand_to } => {
+ let macro_call_as_call_id = macro_call_as_call_id(
+ self.db,
+ ast_id,
+ *expand_to,
+ self.def_map.krate,
+ |path| {
+ let resolved_res = self.def_map.resolve_path_fp_with_macro(
+ self.db,
+ ResolveMode::Other,
+ directive.module_id,
+ &path,
+ BuiltinShadowMode::Module,
+ );
+ resolved_res
+ .resolved_def
+ .take_macros()
+ .map(|it| macro_id_to_def_id(self.db, it))
+ },
+ &mut |_| (),
+ );
+ if let Err(UnresolvedMacro { path }) = macro_call_as_call_id {
+ self.def_map.diagnostics.push(DefDiagnostic::unresolved_macro_call(
+ directive.module_id,
+ MacroCallKind::FnLike { ast_id: ast_id.ast_id, expand_to: *expand_to },
+ path,
+ ));
+ }
+ }
+ MacroDirectiveKind::Derive { ast_id, derive_attr, derive_pos } => {
+ self.def_map.diagnostics.push(DefDiagnostic::unresolved_macro_call(
+ directive.module_id,
+ MacroCallKind::Derive {
+ ast_id: ast_id.ast_id,
+ derive_attr_index: derive_attr.ast_index,
+ derive_index: *derive_pos as u32,
+ },
+ ast_id.path.clone(),
+ ));
+ }
+ // These are diagnosed by `reseed_with_unresolved_attribute`, as that function consumes them
+ MacroDirectiveKind::Attr { .. } => {}
+ }
+ }
+
+ // Emit diagnostics for all remaining unresolved imports.
+
+ // We'd like to avoid emitting a diagnostics avalanche when some `extern crate` doesn't
+ // resolve. We first emit diagnostics for unresolved extern crates and collect the missing
+ // crate names. Then we emit diagnostics for unresolved imports, but only if the import
+ // doesn't start with an unresolved crate's name. Due to renaming and reexports, this is a
+ // heuristic, but it works in practice.
+ let mut diagnosed_extern_crates = FxHashSet::default();
+ for directive in &self.unresolved_imports {
+ if let ImportSource::ExternCrate(krate) = directive.import.source {
+ let item_tree = krate.item_tree(self.db);
+ let extern_crate = &item_tree[krate.value];
+
+ diagnosed_extern_crates.insert(extern_crate.name.clone());
+
+ self.def_map.diagnostics.push(DefDiagnostic::unresolved_extern_crate(
+ directive.module_id,
+ InFile::new(krate.file_id(), extern_crate.ast_id),
+ ));
+ }
+ }
+
+ for directive in &self.unresolved_imports {
+ if let ImportSource::Import { id: import, use_tree } = directive.import.source {
+ if matches!(
+ (directive.import.path.segments().first(), &directive.import.path.kind),
+ (Some(krate), PathKind::Plain | PathKind::Abs) if diagnosed_extern_crates.contains(krate)
+ ) {
+ continue;
+ }
+
+ self.def_map.diagnostics.push(DefDiagnostic::unresolved_import(
+ directive.module_id,
+ import,
+ use_tree,
+ ));
+ }
+ }
+
+ self.def_map
+ }
+}
+
+/// Walks a single module, populating defs, imports and macros
+struct ModCollector<'a, 'b> {
+ def_collector: &'a mut DefCollector<'b>,
+ macro_depth: usize,
+ module_id: LocalModuleId,
+ tree_id: TreeId,
+ item_tree: &'a ItemTree,
+ mod_dir: ModDir,
+}
+
+impl ModCollector<'_, '_> {
+ fn collect_in_top_module(&mut self, items: &[ModItem]) {
+ let module = self.def_collector.def_map.module_id(self.module_id);
+ self.collect(items, module.into())
+ }
+
+ fn collect(&mut self, items: &[ModItem], container: ItemContainerId) {
+ let krate = self.def_collector.def_map.krate;
+
+ // Note: don't assert that inserted value is fresh: it's simply not true
+ // for macros.
+ self.def_collector.mod_dirs.insert(self.module_id, self.mod_dir.clone());
+
+ // Prelude module is always considered to be `#[macro_use]`.
+ if let Some(prelude_module) = self.def_collector.def_map.prelude {
+ if prelude_module.krate != krate {
+ cov_mark::hit!(prelude_is_macro_use);
+ self.def_collector.import_all_macros_exported(self.module_id, prelude_module.krate);
+ }
+ }
+
+ // This should be processed eagerly instead of deferred to resolving.
+ // `#[macro_use] extern crate` is hoisted to imports macros before collecting
+ // any other items.
+ for &item in items {
+ let attrs = self.item_tree.attrs(self.def_collector.db, krate, item.into());
+ if attrs.cfg().map_or(true, |cfg| self.is_cfg_enabled(&cfg)) {
+ if let ModItem::ExternCrate(id) = item {
+ let import = &self.item_tree[id];
+ let attrs = self.item_tree.attrs(
+ self.def_collector.db,
+ krate,
+ ModItem::from(id).into(),
+ );
+ if attrs.by_key("macro_use").exists() {
+ self.def_collector.import_macros_from_extern_crate(self.module_id, import);
+ }
+ }
+ }
+ }
+
+ for &item in items {
+ let attrs = self.item_tree.attrs(self.def_collector.db, krate, item.into());
+ if let Some(cfg) = attrs.cfg() {
+ if !self.is_cfg_enabled(&cfg) {
+ self.emit_unconfigured_diagnostic(item, &cfg);
+ continue;
+ }
+ }
+
+ if let Err(()) = self.resolve_attributes(&attrs, item, container) {
+ // Do not process the item. It has at least one non-builtin attribute, so the
+ // fixed-point algorithm is required to resolve the rest of them.
+ continue;
+ }
+
+ let db = self.def_collector.db;
+ let module = self.def_collector.def_map.module_id(self.module_id);
+ let def_map = &mut self.def_collector.def_map;
+ let update_def =
+ |def_collector: &mut DefCollector<'_>, id, name: &Name, vis, has_constructor| {
+ def_collector.def_map.modules[self.module_id].scope.declare(id);
+ def_collector.update(
+ self.module_id,
+ &[(Some(name.clone()), PerNs::from_def(id, vis, has_constructor))],
+ vis,
+ ImportType::Named,
+ )
+ };
+ let resolve_vis = |def_map: &DefMap, visibility| {
+ def_map
+ .resolve_visibility(db, self.module_id, visibility)
+ .unwrap_or(Visibility::Public)
+ };
+
+ match item {
+ ModItem::Mod(m) => self.collect_module(m, &attrs),
+ ModItem::Import(import_id) => {
+ let imports = Import::from_use(
+ db,
+ krate,
+ self.item_tree,
+ ItemTreeId::new(self.tree_id, import_id),
+ );
+ self.def_collector.unresolved_imports.extend(imports.into_iter().map(
+ |import| ImportDirective {
+ module_id: self.module_id,
+ import,
+ status: PartialResolvedImport::Unresolved,
+ },
+ ));
+ }
+ ModItem::ExternCrate(import_id) => {
+ self.def_collector.unresolved_imports.push(ImportDirective {
+ module_id: self.module_id,
+ import: Import::from_extern_crate(
+ db,
+ krate,
+ self.item_tree,
+ ItemTreeId::new(self.tree_id, import_id),
+ ),
+ status: PartialResolvedImport::Unresolved,
+ })
+ }
+ ModItem::ExternBlock(block) => self.collect(
+ &self.item_tree[block].children,
+ ItemContainerId::ExternBlockId(
+ ExternBlockLoc {
+ container: module,
+ id: ItemTreeId::new(self.tree_id, block),
+ }
+ .intern(db),
+ ),
+ ),
+ ModItem::MacroCall(mac) => self.collect_macro_call(&self.item_tree[mac], container),
+ ModItem::MacroRules(id) => self.collect_macro_rules(id, module),
+ ModItem::MacroDef(id) => self.collect_macro_def(id, module),
+ ModItem::Impl(imp) => {
+ let impl_id =
+ ImplLoc { container: module, id: ItemTreeId::new(self.tree_id, imp) }
+ .intern(db);
+ self.def_collector.def_map.modules[self.module_id].scope.define_impl(impl_id)
+ }
+ ModItem::Function(id) => {
+ let it = &self.item_tree[id];
+ let fn_id =
+ FunctionLoc { container, id: ItemTreeId::new(self.tree_id, id) }.intern(db);
+
+ let vis = resolve_vis(def_map, &self.item_tree[it.visibility]);
+ if self.def_collector.is_proc_macro {
+ if self.module_id == def_map.root {
+ if let Some(proc_macro) = attrs.parse_proc_macro_decl(&it.name) {
+ let crate_root = def_map.module_id(def_map.root);
+ self.def_collector.export_proc_macro(
+ proc_macro,
+ ItemTreeId::new(self.tree_id, id),
+ fn_id,
+ crate_root,
+ );
+ }
+ }
+ }
+
+ update_def(self.def_collector, fn_id.into(), &it.name, vis, false);
+ }
+ ModItem::Struct(id) => {
+ let it = &self.item_tree[id];
+
+ let vis = resolve_vis(def_map, &self.item_tree[it.visibility]);
+ update_def(
+ self.def_collector,
+ StructLoc { container: module, id: ItemTreeId::new(self.tree_id, id) }
+ .intern(db)
+ .into(),
+ &it.name,
+ vis,
+ !matches!(it.fields, Fields::Record(_)),
+ );
+ }
+ ModItem::Union(id) => {
+ let it = &self.item_tree[id];
+
+ let vis = resolve_vis(def_map, &self.item_tree[it.visibility]);
+ update_def(
+ self.def_collector,
+ UnionLoc { container: module, id: ItemTreeId::new(self.tree_id, id) }
+ .intern(db)
+ .into(),
+ &it.name,
+ vis,
+ false,
+ );
+ }
+ ModItem::Enum(id) => {
+ let it = &self.item_tree[id];
+
+ let vis = resolve_vis(def_map, &self.item_tree[it.visibility]);
+ update_def(
+ self.def_collector,
+ EnumLoc { container: module, id: ItemTreeId::new(self.tree_id, id) }
+ .intern(db)
+ .into(),
+ &it.name,
+ vis,
+ false,
+ );
+ }
+ ModItem::Const(id) => {
+ let it = &self.item_tree[id];
+ let const_id =
+ ConstLoc { container, id: ItemTreeId::new(self.tree_id, id) }.intern(db);
+
+ match &it.name {
+ Some(name) => {
+ let vis = resolve_vis(def_map, &self.item_tree[it.visibility]);
+ update_def(self.def_collector, const_id.into(), name, vis, false);
+ }
+ None => {
+ // const _: T = ...;
+ self.def_collector.def_map.modules[self.module_id]
+ .scope
+ .define_unnamed_const(const_id);
+ }
+ }
+ }
+ ModItem::Static(id) => {
+ let it = &self.item_tree[id];
+
+ let vis = resolve_vis(def_map, &self.item_tree[it.visibility]);
+ update_def(
+ self.def_collector,
+ StaticLoc { container, id: ItemTreeId::new(self.tree_id, id) }
+ .intern(db)
+ .into(),
+ &it.name,
+ vis,
+ false,
+ );
+ }
+ ModItem::Trait(id) => {
+ let it = &self.item_tree[id];
+
+ let vis = resolve_vis(def_map, &self.item_tree[it.visibility]);
+ update_def(
+ self.def_collector,
+ TraitLoc { container: module, id: ItemTreeId::new(self.tree_id, id) }
+ .intern(db)
+ .into(),
+ &it.name,
+ vis,
+ false,
+ );
+ }
+ ModItem::TypeAlias(id) => {
+ let it = &self.item_tree[id];
+
+ let vis = resolve_vis(def_map, &self.item_tree[it.visibility]);
+ update_def(
+ self.def_collector,
+ TypeAliasLoc { container, id: ItemTreeId::new(self.tree_id, id) }
+ .intern(db)
+ .into(),
+ &it.name,
+ vis,
+ false,
+ );
+ }
+ }
+ }
+ }
+
+ fn collect_module(&mut self, module_id: FileItemTreeId<Mod>, attrs: &Attrs) {
+ let path_attr = attrs.by_key("path").string_value();
+ let is_macro_use = attrs.by_key("macro_use").exists();
+ let module = &self.item_tree[module_id];
+ match &module.kind {
+ // inline module, just recurse
+ ModKind::Inline { items } => {
+ let module_id = self.push_child_module(
+ module.name.clone(),
+ AstId::new(self.file_id(), module.ast_id),
+ None,
+ &self.item_tree[module.visibility],
+ module_id,
+ );
+
+ if let Some(mod_dir) = self.mod_dir.descend_into_definition(&module.name, path_attr)
+ {
+ ModCollector {
+ def_collector: &mut *self.def_collector,
+ macro_depth: self.macro_depth,
+ module_id,
+ tree_id: self.tree_id,
+ item_tree: self.item_tree,
+ mod_dir,
+ }
+ .collect_in_top_module(&*items);
+ if is_macro_use {
+ self.import_all_legacy_macros(module_id);
+ }
+ }
+ }
+ // out of line module, resolve, parse and recurse
+ ModKind::Outline => {
+ let ast_id = AstId::new(self.tree_id.file_id(), module.ast_id);
+ let db = self.def_collector.db;
+ match self.mod_dir.resolve_declaration(db, self.file_id(), &module.name, path_attr)
+ {
+ Ok((file_id, is_mod_rs, mod_dir)) => {
+ let item_tree = db.file_item_tree(file_id.into());
+ let krate = self.def_collector.def_map.krate;
+ let is_enabled = item_tree
+ .top_level_attrs(db, krate)
+ .cfg()
+ .map_or(true, |cfg| self.is_cfg_enabled(&cfg));
+ if is_enabled {
+ let module_id = self.push_child_module(
+ module.name.clone(),
+ ast_id,
+ Some((file_id, is_mod_rs)),
+ &self.item_tree[module.visibility],
+ module_id,
+ );
+ ModCollector {
+ def_collector: self.def_collector,
+ macro_depth: self.macro_depth,
+ module_id,
+ tree_id: TreeId::new(file_id.into(), None),
+ item_tree: &item_tree,
+ mod_dir,
+ }
+ .collect_in_top_module(item_tree.top_level_items());
+ let is_macro_use = is_macro_use
+ || item_tree
+ .top_level_attrs(db, krate)
+ .by_key("macro_use")
+ .exists();
+ if is_macro_use {
+ self.import_all_legacy_macros(module_id);
+ }
+ }
+ }
+ Err(candidates) => {
+ self.push_child_module(
+ module.name.clone(),
+ ast_id,
+ None,
+ &self.item_tree[module.visibility],
+ module_id,
+ );
+ self.def_collector.def_map.diagnostics.push(
+ DefDiagnostic::unresolved_module(self.module_id, ast_id, candidates),
+ );
+ }
+ };
+ }
+ }
+ }
+
+ fn push_child_module(
+ &mut self,
+ name: Name,
+ declaration: AstId<ast::Module>,
+ definition: Option<(FileId, bool)>,
+ visibility: &crate::visibility::RawVisibility,
+ mod_tree_id: FileItemTreeId<Mod>,
+ ) -> LocalModuleId {
+ let def_map = &mut self.def_collector.def_map;
+ let vis = def_map
+ .resolve_visibility(self.def_collector.db, self.module_id, visibility)
+ .unwrap_or(Visibility::Public);
+ let modules = &mut def_map.modules;
+ let origin = match definition {
+ None => ModuleOrigin::Inline {
+ definition: declaration,
+ definition_tree_id: ItemTreeId::new(self.tree_id, mod_tree_id),
+ },
+ Some((definition, is_mod_rs)) => ModuleOrigin::File {
+ declaration,
+ definition,
+ is_mod_rs,
+ declaration_tree_id: ItemTreeId::new(self.tree_id, mod_tree_id),
+ },
+ };
+
+ let res = modules.alloc(ModuleData::new(origin, vis));
+ modules[res].parent = Some(self.module_id);
+ for (name, mac) in modules[self.module_id].scope.collect_legacy_macros() {
+ for &mac in &mac {
+ modules[res].scope.define_legacy_macro(name.clone(), mac);
+ }
+ }
+ modules[self.module_id].children.insert(name.clone(), res);
+
+ let module = def_map.module_id(res);
+ let def = ModuleDefId::from(module);
+
+ def_map.modules[self.module_id].scope.declare(def);
+ self.def_collector.update(
+ self.module_id,
+ &[(Some(name), PerNs::from_def(def, vis, false))],
+ vis,
+ ImportType::Named,
+ );
+ res
+ }
+
+ /// Resolves attributes on an item.
+ ///
+ /// Returns `Err` when some attributes could not be resolved to builtins and have been
+ /// registered as unresolved.
+ ///
+ /// If `ignore_up_to` is `Some`, attributes preceding and including that attribute will be
+ /// assumed to be resolved already.
+ fn resolve_attributes(
+ &mut self,
+ attrs: &Attrs,
+ mod_item: ModItem,
+ container: ItemContainerId,
+ ) -> Result<(), ()> {
+ let mut ignore_up_to =
+ self.def_collector.skip_attrs.get(&InFile::new(self.file_id(), mod_item)).copied();
+ let iter = attrs
+ .iter()
+ .dedup_by(|a, b| {
+ // FIXME: this should not be required, all attributes on an item should have a
+ // unique ID!
+ // Still, this occurs because `#[cfg_attr]` can "expand" to multiple attributes:
+ // #[cfg_attr(not(off), unresolved, unresolved)]
+ // struct S;
+ // We should come up with a different way to ID attributes.
+ a.id == b.id
+ })
+ .skip_while(|attr| match ignore_up_to {
+ Some(id) if attr.id == id => {
+ ignore_up_to = None;
+ true
+ }
+ Some(_) => true,
+ None => false,
+ });
+
+ for attr in iter {
+ if self.def_collector.def_map.is_builtin_or_registered_attr(&attr.path) {
+ continue;
+ }
+ tracing::debug!("non-builtin attribute {}", attr.path);
+
+ let ast_id = AstIdWithPath::new(
+ self.file_id(),
+ mod_item.ast_id(self.item_tree),
+ attr.path.as_ref().clone(),
+ );
+ self.def_collector.unresolved_macros.push(MacroDirective {
+ module_id: self.module_id,
+ depth: self.macro_depth + 1,
+ kind: MacroDirectiveKind::Attr {
+ ast_id,
+ attr: attr.clone(),
+ mod_item,
+ tree: self.tree_id,
+ },
+ container,
+ });
+
+ return Err(());
+ }
+
+ Ok(())
+ }
+
+ fn collect_macro_rules(&mut self, id: FileItemTreeId<MacroRules>, module: ModuleId) {
+ let krate = self.def_collector.def_map.krate;
+ let mac = &self.item_tree[id];
+ let attrs = self.item_tree.attrs(self.def_collector.db, krate, ModItem::from(id).into());
+ let ast_id = InFile::new(self.file_id(), mac.ast_id.upcast());
+
+ let export_attr = attrs.by_key("macro_export");
+
+ let is_export = export_attr.exists();
+ let local_inner = if is_export {
+ export_attr.tt_values().flat_map(|it| &it.token_trees).any(|it| match it {
+ tt::TokenTree::Leaf(tt::Leaf::Ident(ident)) => {
+ ident.text.contains("local_inner_macros")
+ }
+ _ => false,
+ })
+ } else {
+ false
+ };
+
+ // Case 1: builtin macros
+ let expander = if attrs.by_key("rustc_builtin_macro").exists() {
+ // `#[rustc_builtin_macro = "builtin_name"]` overrides the `macro_rules!` name.
+ let name;
+ let name = match attrs.by_key("rustc_builtin_macro").string_value() {
+ Some(it) => {
+ // FIXME: a hacky way to create a Name from string.
+ name = tt::Ident { text: it.clone(), id: tt::TokenId::unspecified() }.as_name();
+ &name
+ }
+ None => {
+ let explicit_name =
+ attrs.by_key("rustc_builtin_macro").tt_values().next().and_then(|tt| {
+ match tt.token_trees.first() {
+ Some(tt::TokenTree::Leaf(tt::Leaf::Ident(name))) => Some(name),
+ _ => None,
+ }
+ });
+ match explicit_name {
+ Some(ident) => {
+ name = ident.as_name();
+ &name
+ }
+ None => &mac.name,
+ }
+ }
+ };
+ match find_builtin_macro(name) {
+ Some(Either::Left(it)) => MacroExpander::BuiltIn(it),
+ Some(Either::Right(it)) => MacroExpander::BuiltInEager(it),
+ None => {
+ self.def_collector
+ .def_map
+ .diagnostics
+ .push(DefDiagnostic::unimplemented_builtin_macro(self.module_id, ast_id));
+ return;
+ }
+ }
+ } else {
+ // Case 2: normal `macro_rules!` macro
+ MacroExpander::Declarative
+ };
+
+ let macro_id = MacroRulesLoc {
+ container: module,
+ id: ItemTreeId::new(self.tree_id, id),
+ local_inner,
+ expander,
+ }
+ .intern(self.def_collector.db);
+ self.def_collector.define_macro_rules(
+ self.module_id,
+ mac.name.clone(),
+ macro_id,
+ is_export,
+ );
+ }
+
+ fn collect_macro_def(&mut self, id: FileItemTreeId<MacroDef>, module: ModuleId) {
+ let krate = self.def_collector.def_map.krate;
+ let mac = &self.item_tree[id];
+ let ast_id = InFile::new(self.file_id(), mac.ast_id.upcast());
+
+ // Case 1: builtin macros
+ let attrs = self.item_tree.attrs(self.def_collector.db, krate, ModItem::from(id).into());
+ let expander = if attrs.by_key("rustc_builtin_macro").exists() {
+ if let Some(expander) = find_builtin_macro(&mac.name) {
+ match expander {
+ Either::Left(it) => MacroExpander::BuiltIn(it),
+ Either::Right(it) => MacroExpander::BuiltInEager(it),
+ }
+ } else if let Some(expander) = find_builtin_derive(&mac.name) {
+ MacroExpander::BuiltInDerive(expander)
+ } else if let Some(expander) = find_builtin_attr(&mac.name) {
+ MacroExpander::BuiltInAttr(expander)
+ } else {
+ self.def_collector
+ .def_map
+ .diagnostics
+ .push(DefDiagnostic::unimplemented_builtin_macro(self.module_id, ast_id));
+ return;
+ }
+ } else {
+ // Case 2: normal `macro`
+ MacroExpander::Declarative
+ };
+
+ let macro_id =
+ Macro2Loc { container: module, id: ItemTreeId::new(self.tree_id, id), expander }
+ .intern(self.def_collector.db);
+ self.def_collector.define_macro_def(
+ self.module_id,
+ mac.name.clone(),
+ macro_id,
+ &self.item_tree[mac.visibility],
+ );
+ }
+
+ fn collect_macro_call(&mut self, mac: &MacroCall, container: ItemContainerId) {
+ let ast_id = AstIdWithPath::new(self.file_id(), mac.ast_id, ModPath::clone(&mac.path));
+
+ // Case 1: try to resolve in legacy scope and expand macro_rules
+ let mut error = None;
+ match macro_call_as_call_id(
+ self.def_collector.db,
+ &ast_id,
+ mac.expand_to,
+ self.def_collector.def_map.krate,
+ |path| {
+ path.as_ident().and_then(|name| {
+ self.def_collector.def_map.with_ancestor_maps(
+ self.def_collector.db,
+ self.module_id,
+ &mut |map, module| {
+ map[module]
+ .scope
+ .get_legacy_macro(name)
+ .and_then(|it| it.last())
+ .map(|&it| macro_id_to_def_id(self.def_collector.db, it.into()))
+ },
+ )
+ })
+ },
+ &mut |err| {
+ error.get_or_insert(err);
+ },
+ ) {
+ Ok(Ok(macro_call_id)) => {
+ // Legacy macros need to be expanded immediately, so that any macros they produce
+ // are in scope.
+ self.def_collector.collect_macro_expansion(
+ self.module_id,
+ macro_call_id,
+ self.macro_depth + 1,
+ container,
+ );
+
+ if let Some(err) = error {
+ self.def_collector.def_map.diagnostics.push(DefDiagnostic::macro_error(
+ self.module_id,
+ MacroCallKind::FnLike { ast_id: ast_id.ast_id, expand_to: mac.expand_to },
+ err.to_string(),
+ ));
+ }
+
+ return;
+ }
+ Ok(Err(_)) => {
+ // Built-in macro failed eager expansion.
+
+ self.def_collector.def_map.diagnostics.push(DefDiagnostic::macro_error(
+ self.module_id,
+ MacroCallKind::FnLike { ast_id: ast_id.ast_id, expand_to: mac.expand_to },
+ error.unwrap().to_string(),
+ ));
+ return;
+ }
+ Err(UnresolvedMacro { .. }) => (),
+ }
+
+ // Case 2: resolve in module scope, expand during name resolution.
+ self.def_collector.unresolved_macros.push(MacroDirective {
+ module_id: self.module_id,
+ depth: self.macro_depth + 1,
+ kind: MacroDirectiveKind::FnLike { ast_id, expand_to: mac.expand_to },
+ container,
+ });
+ }
+
+ fn import_all_legacy_macros(&mut self, module_id: LocalModuleId) {
+ let macros = self.def_collector.def_map[module_id].scope.collect_legacy_macros();
+ for (name, macs) in macros {
+ macs.last().map(|&mac| {
+ self.def_collector.define_legacy_macro(self.module_id, name.clone(), mac)
+ });
+ }
+ }
+
+ fn is_cfg_enabled(&self, cfg: &CfgExpr) -> bool {
+ self.def_collector.cfg_options.check(cfg) != Some(false)
+ }
+
+ fn emit_unconfigured_diagnostic(&mut self, item: ModItem, cfg: &CfgExpr) {
+ let ast_id = item.ast_id(self.item_tree);
+
++ let ast_id = InFile::new(self.file_id(), ast_id.upcast());
+ self.def_collector.def_map.diagnostics.push(DefDiagnostic::unconfigured_code(
+ self.module_id,
+ ast_id,
+ cfg.clone(),
+ self.def_collector.cfg_options.clone(),
+ ));
+ }
+
+ fn file_id(&self) -> HirFileId {
+ self.tree_id.file_id()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::{db::DefDatabase, test_db::TestDB};
+ use base_db::{fixture::WithFixture, SourceDatabase};
+
+ use super::*;
+
+ fn do_collect_defs(db: &dyn DefDatabase, def_map: DefMap) -> DefMap {
+ let mut collector = DefCollector {
+ db,
+ def_map,
+ deps: FxHashMap::default(),
+ glob_imports: FxHashMap::default(),
+ unresolved_imports: Vec::new(),
+ indeterminate_imports: Vec::new(),
+ unresolved_macros: Vec::new(),
+ mod_dirs: FxHashMap::default(),
+ cfg_options: &CfgOptions::default(),
+ proc_macros: Default::default(),
+ from_glob_import: Default::default(),
+ skip_attrs: Default::default(),
+ is_proc_macro: false,
+ };
+ collector.seed_with_top_level();
+ collector.collect();
+ collector.def_map
+ }
+
+ fn do_resolve(not_ra_fixture: &str) -> DefMap {
+ let (db, file_id) = TestDB::with_single_file(not_ra_fixture);
+ let krate = db.test_crate();
+
+ let edition = db.crate_graph()[krate].edition;
+ let module_origin = ModuleOrigin::CrateRoot { definition: file_id };
+ let def_map =
+ DefMap::empty(krate, edition, ModuleData::new(module_origin, Visibility::Public));
+ do_collect_defs(&db, def_map)
+ }
+
+ #[test]
+ fn test_macro_expand_will_stop_1() {
+ do_resolve(
+ r#"
+macro_rules! foo {
+ ($($ty:ty)*) => { foo!($($ty)*); }
+}
+foo!(KABOOM);
+"#,
+ );
+ do_resolve(
+ r#"
+macro_rules! foo {
+ ($($ty:ty)*) => { foo!(() $($ty)*); }
+}
+foo!(KABOOM);
+"#,
+ );
+ }
+
+ #[ignore]
+ #[test]
+ fn test_macro_expand_will_stop_2() {
+ // FIXME: this test does succeed, but takes quite a while: 90 seconds in
+ // the release mode. That's why the argument is not an ra_fixture --
+ // otherwise injection highlighting gets stuck.
+ //
+ // We need to find a way to fail this faster.
+ do_resolve(
+ r#"
+macro_rules! foo {
+ ($($ty:ty)*) => { foo!($($ty)* $($ty)*); }
+}
+foo!(KABOOM);
+"#,
+ );
+ }
+}
--- /dev/null
- use syntax::ast;
+//! Diagnostics emitted during DefMap construction.
+
+use base_db::CrateId;
+use cfg::{CfgExpr, CfgOptions};
+use hir_expand::MacroCallKind;
+use la_arena::Idx;
- UnconfiguredCode { ast: AstId<ast::Item>, cfg: CfgExpr, opts: CfgOptions },
++use syntax::ast::{self, AnyHasAttrs};
+
+use crate::{
+ attr::AttrId,
+ item_tree::{self, ItemTreeId},
+ nameres::LocalModuleId,
+ path::ModPath,
+ AstId,
+};
+
+#[derive(Debug, PartialEq, Eq)]
+pub enum DefDiagnosticKind {
+ UnresolvedModule { ast: AstId<ast::Module>, candidates: Box<[String]> },
+
+ UnresolvedExternCrate { ast: AstId<ast::ExternCrate> },
+
+ UnresolvedImport { id: ItemTreeId<item_tree::Import>, index: Idx<ast::UseTree> },
+
- ast: AstId<ast::Item>,
++ UnconfiguredCode { ast: AstId<AnyHasAttrs>, cfg: CfgExpr, opts: CfgOptions },
+
+ UnresolvedProcMacro { ast: MacroCallKind, krate: CrateId },
+
+ UnresolvedMacroCall { ast: MacroCallKind, path: ModPath },
+
+ MacroError { ast: MacroCallKind, message: String },
+
+ UnimplementedBuiltinMacro { ast: AstId<ast::Macro> },
+
+ InvalidDeriveTarget { ast: AstId<ast::Item>, id: u32 },
+
+ MalformedDerive { ast: AstId<ast::Adt>, id: u32 },
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct DefDiagnostic {
+ pub in_module: LocalModuleId,
+ pub kind: DefDiagnosticKind,
+}
+
+impl DefDiagnostic {
+ pub(super) fn unresolved_module(
+ container: LocalModuleId,
+ declaration: AstId<ast::Module>,
+ candidates: Box<[String]>,
+ ) -> Self {
+ Self {
+ in_module: container,
+ kind: DefDiagnosticKind::UnresolvedModule { ast: declaration, candidates },
+ }
+ }
+
+ pub(super) fn unresolved_extern_crate(
+ container: LocalModuleId,
+ declaration: AstId<ast::ExternCrate>,
+ ) -> Self {
+ Self {
+ in_module: container,
+ kind: DefDiagnosticKind::UnresolvedExternCrate { ast: declaration },
+ }
+ }
+
+ pub(super) fn unresolved_import(
+ container: LocalModuleId,
+ id: ItemTreeId<item_tree::Import>,
+ index: Idx<ast::UseTree>,
+ ) -> Self {
+ Self { in_module: container, kind: DefDiagnosticKind::UnresolvedImport { id, index } }
+ }
+
+ pub fn unconfigured_code(
+ container: LocalModuleId,
++ ast: AstId<ast::AnyHasAttrs>,
+ cfg: CfgExpr,
+ opts: CfgOptions,
+ ) -> Self {
+ Self { in_module: container, kind: DefDiagnosticKind::UnconfiguredCode { ast, cfg, opts } }
+ }
+
+ pub(super) fn unresolved_proc_macro(
+ container: LocalModuleId,
+ ast: MacroCallKind,
+ krate: CrateId,
+ ) -> Self {
+ Self { in_module: container, kind: DefDiagnosticKind::UnresolvedProcMacro { ast, krate } }
+ }
+
+ pub(super) fn macro_error(
+ container: LocalModuleId,
+ ast: MacroCallKind,
+ message: String,
+ ) -> Self {
+ Self { in_module: container, kind: DefDiagnosticKind::MacroError { ast, message } }
+ }
+
+ pub(super) fn unresolved_macro_call(
+ container: LocalModuleId,
+ ast: MacroCallKind,
+ path: ModPath,
+ ) -> Self {
+ Self { in_module: container, kind: DefDiagnosticKind::UnresolvedMacroCall { ast, path } }
+ }
+
+ pub(super) fn unimplemented_builtin_macro(
+ container: LocalModuleId,
+ ast: AstId<ast::Macro>,
+ ) -> Self {
+ Self { in_module: container, kind: DefDiagnosticKind::UnimplementedBuiltinMacro { ast } }
+ }
+
+ pub(super) fn invalid_derive_target(
+ container: LocalModuleId,
+ ast: AstId<ast::Item>,
+ id: AttrId,
+ ) -> Self {
+ Self {
+ in_module: container,
+ kind: DefDiagnosticKind::InvalidDeriveTarget { ast, id: id.ast_index },
+ }
+ }
+
+ pub(super) fn malformed_derive(
+ container: LocalModuleId,
+ ast: AstId<ast::Adt>,
+ id: AttrId,
+ ) -> Self {
+ Self {
+ in_module: container,
+ kind: DefDiagnosticKind::MalformedDerive { ast, id: id.ast_index },
+ }
+ }
+}
--- /dev/null
+//! Name resolution façade.
+use std::{hash::BuildHasherDefault, sync::Arc};
+
+use base_db::CrateId;
+use hir_expand::name::{name, Name};
+use indexmap::IndexMap;
+use rustc_hash::FxHashSet;
+use smallvec::{smallvec, SmallVec};
+
+use crate::{
+ body::scope::{ExprScopes, ScopeId},
+ builtin_type::BuiltinType,
+ db::DefDatabase,
+ expr::{ExprId, LabelId, PatId},
+ generics::{GenericParams, TypeOrConstParamData},
+ intern::Interned,
+ item_scope::{BuiltinShadowMode, BUILTIN_SCOPE},
+ nameres::DefMap,
+ path::{ModPath, PathKind},
+ per_ns::PerNs,
+ visibility::{RawVisibility, Visibility},
+ AdtId, AssocItemId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId, ExternBlockId,
+ FunctionId, GenericDefId, GenericParamId, HasModule, ImplId, ItemContainerId, LifetimeParamId,
+ LocalModuleId, Lookup, Macro2Id, MacroId, MacroRulesId, ModuleDefId, ModuleId, ProcMacroId,
+ StaticId, StructId, TraitId, TypeAliasId, TypeOrConstParamId, TypeParamId, VariantId,
+};
+
+#[derive(Debug, Clone)]
+pub struct Resolver {
+ /// The stack of scopes, where the inner-most scope is the last item.
+ ///
+ /// When using, you generally want to process the scopes in reverse order,
+ /// there's `scopes` *method* for that.
+ scopes: Vec<Scope>,
+ module_scope: ModuleItemMap,
+}
+
+#[derive(Debug, Clone)]
+struct ModuleItemMap {
+ def_map: Arc<DefMap>,
+ module_id: LocalModuleId,
+}
+
+#[derive(Debug, Clone)]
+struct ExprScope {
+ owner: DefWithBodyId,
+ expr_scopes: Arc<ExprScopes>,
+ scope_id: ScopeId,
+}
+
+#[derive(Debug, Clone)]
+enum Scope {
+ /// All the items and imported names of a module
+ BlockScope(ModuleItemMap),
+ /// Brings the generic parameters of an item into scope
+ GenericParams { def: GenericDefId, params: Interned<GenericParams> },
+ /// Brings `Self` in `impl` block into scope
+ ImplDefScope(ImplId),
+ /// Brings `Self` in enum, struct and union definitions into scope
+ AdtScope(AdtId),
+ /// Local bindings
+ ExprScope(ExprScope),
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub enum TypeNs {
+ SelfType(ImplId),
+ GenericParam(TypeParamId),
+ AdtId(AdtId),
+ AdtSelfType(AdtId),
+ // Yup, enum variants are added to the types ns, but any usage of variant as
+ // type is an error.
+ EnumVariantId(EnumVariantId),
+ TypeAliasId(TypeAliasId),
+ BuiltinType(BuiltinType),
+ TraitId(TraitId),
+ // Module belong to type ns, but the resolver is used when all module paths
+ // are fully resolved.
+ // ModuleId(ModuleId)
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub enum ResolveValueResult {
+ ValueNs(ValueNs),
+ Partial(TypeNs, usize),
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub enum ValueNs {
+ ImplSelf(ImplId),
+ LocalBinding(PatId),
+ FunctionId(FunctionId),
+ ConstId(ConstId),
+ StaticId(StaticId),
+ StructId(StructId),
+ EnumVariantId(EnumVariantId),
+ GenericParam(ConstParamId),
+}
+
+impl Resolver {
+ /// Resolve known trait from std, like `std::futures::Future`
+ pub fn resolve_known_trait(&self, db: &dyn DefDatabase, path: &ModPath) -> Option<TraitId> {
+ let res = self.resolve_module_path(db, path, BuiltinShadowMode::Other).take_types()?;
+ match res {
+ ModuleDefId::TraitId(it) => Some(it),
+ _ => None,
+ }
+ }
+
+ /// Resolve known struct from std, like `std::boxed::Box`
+ pub fn resolve_known_struct(&self, db: &dyn DefDatabase, path: &ModPath) -> Option<StructId> {
+ let res = self.resolve_module_path(db, path, BuiltinShadowMode::Other).take_types()?;
+ match res {
+ ModuleDefId::AdtId(AdtId::StructId(it)) => Some(it),
+ _ => None,
+ }
+ }
+
+ /// Resolve known enum from std, like `std::result::Result`
+ pub fn resolve_known_enum(&self, db: &dyn DefDatabase, path: &ModPath) -> Option<EnumId> {
+ let res = self.resolve_module_path(db, path, BuiltinShadowMode::Other).take_types()?;
+ match res {
+ ModuleDefId::AdtId(AdtId::EnumId(it)) => Some(it),
+ _ => None,
+ }
+ }
+
+ pub fn resolve_module_path_in_items(&self, db: &dyn DefDatabase, path: &ModPath) -> PerNs {
+ self.resolve_module_path(db, path, BuiltinShadowMode::Module)
+ }
+
+ // FIXME: This shouldn't exist
+ pub fn resolve_module_path_in_trait_assoc_items(
+ &self,
+ db: &dyn DefDatabase,
+ path: &ModPath,
+ ) -> Option<PerNs> {
+ let (item_map, module) = self.item_scope();
+ let (module_res, idx) = item_map.resolve_path(db, module, path, BuiltinShadowMode::Module);
+ match module_res.take_types()? {
+ ModuleDefId::TraitId(it) => {
+ let idx = idx?;
+ let unresolved = &path.segments()[idx..];
+ let assoc = match unresolved {
+ [it] => it,
+ _ => return None,
+ };
+ let &(_, assoc) = db.trait_data(it).items.iter().find(|(n, _)| n == assoc)?;
+ Some(match assoc {
+ AssocItemId::FunctionId(it) => PerNs::values(it.into(), Visibility::Public),
+ AssocItemId::ConstId(it) => PerNs::values(it.into(), Visibility::Public),
+ AssocItemId::TypeAliasId(it) => PerNs::types(it.into(), Visibility::Public),
+ })
+ }
+ _ => None,
+ }
+ }
+
+ pub fn resolve_path_in_type_ns(
+ &self,
+ db: &dyn DefDatabase,
+ path: &ModPath,
+ ) -> Option<(TypeNs, Option<usize>)> {
+ let first_name = path.segments().first()?;
+ let skip_to_mod = path.kind != PathKind::Plain;
+ if skip_to_mod {
+ return self.module_scope.resolve_path_in_type_ns(db, path);
+ }
+
+ let remaining_idx = || if path.segments().len() == 1 { None } else { Some(1) };
+
+ for scope in self.scopes() {
+ match scope {
+ Scope::ExprScope(_) => continue,
+ Scope::GenericParams { params, def } => {
+ if let Some(id) = params.find_type_by_name(first_name, *def) {
+ return Some((TypeNs::GenericParam(id), remaining_idx()));
+ }
+ }
+ &Scope::ImplDefScope(impl_) => {
+ if first_name == &name![Self] {
+ return Some((TypeNs::SelfType(impl_), remaining_idx()));
+ }
+ }
+ &Scope::AdtScope(adt) => {
+ if first_name == &name![Self] {
+ return Some((TypeNs::AdtSelfType(adt), remaining_idx()));
+ }
+ }
+ Scope::BlockScope(m) => {
+ if let Some(res) = m.resolve_path_in_type_ns(db, path) {
+ return Some(res);
+ }
+ }
+ }
+ }
+ self.module_scope.resolve_path_in_type_ns(db, path)
+ }
+
+ pub fn resolve_path_in_type_ns_fully(
+ &self,
+ db: &dyn DefDatabase,
+ path: &ModPath,
+ ) -> Option<TypeNs> {
+ let (res, unresolved) = self.resolve_path_in_type_ns(db, path)?;
+ if unresolved.is_some() {
+ return None;
+ }
+ Some(res)
+ }
+
+ pub fn resolve_visibility(
+ &self,
+ db: &dyn DefDatabase,
+ visibility: &RawVisibility,
+ ) -> Option<Visibility> {
+ match visibility {
+ RawVisibility::Module(_) => {
+ let (item_map, module) = self.item_scope();
+ item_map.resolve_visibility(db, module, visibility)
+ }
+ RawVisibility::Public => Some(Visibility::Public),
+ }
+ }
+
+ pub fn resolve_path_in_value_ns(
+ &self,
+ db: &dyn DefDatabase,
+ path: &ModPath,
+ ) -> Option<ResolveValueResult> {
+ let n_segments = path.segments().len();
+ let tmp = name![self];
+ let first_name = if path.is_self() { &tmp } else { path.segments().first()? };
+ let skip_to_mod = path.kind != PathKind::Plain && !path.is_self();
+ if skip_to_mod {
+ return self.module_scope.resolve_path_in_value_ns(db, path);
+ }
+
+ for scope in self.scopes() {
+ match scope {
+ Scope::ExprScope(_) if n_segments > 1 => continue,
+ Scope::ExprScope(scope) => {
+ let entry = scope
+ .expr_scopes
+ .entries(scope.scope_id)
+ .iter()
+ .find(|entry| entry.name() == first_name);
+
+ if let Some(e) = entry {
+ return Some(ResolveValueResult::ValueNs(ValueNs::LocalBinding(e.pat())));
+ }
+ }
+ Scope::GenericParams { params, def } if n_segments > 1 => {
+ if let Some(id) = params.find_type_by_name(first_name, *def) {
+ let ty = TypeNs::GenericParam(id);
+ return Some(ResolveValueResult::Partial(ty, 1));
+ }
+ }
+ Scope::GenericParams { .. } if n_segments != 1 => continue,
+ Scope::GenericParams { params, def } => {
+ if let Some(id) = params.find_const_by_name(first_name, *def) {
+ let val = ValueNs::GenericParam(id);
+ return Some(ResolveValueResult::ValueNs(val));
+ }
+ }
+
+ &Scope::ImplDefScope(impl_) => {
+ if first_name == &name![Self] {
+ return Some(if n_segments > 1 {
+ ResolveValueResult::Partial(TypeNs::SelfType(impl_), 1)
+ } else {
+ ResolveValueResult::ValueNs(ValueNs::ImplSelf(impl_))
+ });
+ }
+ }
+ // bare `Self` doesn't work in the value namespace in a struct/enum definition
+ Scope::AdtScope(_) if n_segments == 1 => continue,
+ Scope::AdtScope(adt) => {
+ if first_name == &name![Self] {
+ let ty = TypeNs::AdtSelfType(*adt);
+ return Some(ResolveValueResult::Partial(ty, 1));
+ }
+ }
+
+ Scope::BlockScope(m) => {
+ if let Some(def) = m.resolve_path_in_value_ns(db, path) {
+ return Some(def);
+ }
+ }
+ }
+ }
+
+ if let res @ Some(_) = self.module_scope.resolve_path_in_value_ns(db, path) {
+ return res;
+ }
+
+ // If a path of the shape `u16::from_le_bytes` failed to resolve at all, then we fall back
+ // to resolving to the primitive type, to allow this to still work in the presence of
+ // `use core::u16;`.
+ if path.kind == PathKind::Plain && path.segments().len() > 1 {
+ if let Some(builtin) = BuiltinType::by_name(&path.segments()[0]) {
+ return Some(ResolveValueResult::Partial(TypeNs::BuiltinType(builtin), 1));
+ }
+ }
+
+ None
+ }
+
+ pub fn resolve_path_in_value_ns_fully(
+ &self,
+ db: &dyn DefDatabase,
+ path: &ModPath,
+ ) -> Option<ValueNs> {
+ match self.resolve_path_in_value_ns(db, path)? {
+ ResolveValueResult::ValueNs(it) => Some(it),
+ ResolveValueResult::Partial(..) => None,
+ }
+ }
+
+ pub fn resolve_path_as_macro(&self, db: &dyn DefDatabase, path: &ModPath) -> Option<MacroId> {
+ let (item_map, module) = self.item_scope();
+ item_map.resolve_path(db, module, path, BuiltinShadowMode::Other).0.take_macros()
+ }
+
+ /// Returns a set of names available in the current scope.
+ ///
+ /// Note that this is a somewhat fuzzy concept -- internally, the compiler
+ /// doesn't necessary follow a strict scoping discipline. Rather, it just
+ /// tells for each ident what it resolves to.
+ ///
+ /// A good example is something like `str::from_utf8`. From scopes point of
+ /// view, this code is erroneous -- both `str` module and `str` type occupy
+ /// the same type namespace.
+ ///
+ /// We don't try to model that super-correctly -- this functionality is
+ /// primarily exposed for completions.
+ ///
+ /// Note that in Rust one name can be bound to several items:
+ ///
+ /// ```
+ /// macro_rules! t { () => (()) }
+ /// type t = t!();
+ /// const t: t = t!()
+ /// ```
+ ///
+ /// That's why we return a multimap.
+ ///
+ /// The shadowing is accounted for: in
+ ///
+ /// ```
+ /// let x = 92;
+ /// {
+ /// let x = 92;
+ /// $0
+ /// }
+ /// ```
+ ///
+ /// there will be only one entry for `x` in the result.
+ ///
+ /// The result is ordered *roughly* from the innermost scope to the
+ /// outermost: when the name is introduced in two namespaces in two scopes,
+ /// we use the position of the first scope.
+ pub fn names_in_scope(
+ &self,
+ db: &dyn DefDatabase,
+ ) -> FxIndexMap<Name, SmallVec<[ScopeDef; 1]>> {
+ let mut res = ScopeNames::default();
+ for scope in self.scopes() {
+ scope.process_names(&mut res, db);
+ }
+ let ModuleItemMap { ref def_map, module_id } = self.module_scope;
+ // FIXME: should we provide `self` here?
+ // f(
+ // Name::self_param(),
+ // PerNs::types(Resolution::Def {
+ // def: m.module.into(),
+ // }),
+ // );
+ def_map[module_id].scope.entries().for_each(|(name, def)| {
+ res.add_per_ns(name, def);
+ });
+ def_map[module_id].scope.legacy_macros().for_each(|(name, macs)| {
+ macs.iter().for_each(|&mac| {
+ res.add(name, ScopeDef::ModuleDef(ModuleDefId::MacroId(MacroId::from(mac))));
+ })
+ });
+ def_map.extern_prelude().for_each(|(name, &def)| {
+ res.add(name, ScopeDef::ModuleDef(ModuleDefId::ModuleId(def)));
+ });
+ BUILTIN_SCOPE.iter().for_each(|(name, &def)| {
+ res.add_per_ns(name, def);
+ });
+ if let Some(prelude) = def_map.prelude() {
+ let prelude_def_map = prelude.def_map(db);
+ for (name, def) in prelude_def_map[prelude.local_id].scope.entries() {
+ res.add_per_ns(name, def)
+ }
+ }
+ res.map
+ }
+
+ pub fn traits_in_scope(&self, db: &dyn DefDatabase) -> FxHashSet<TraitId> {
+ let mut traits = FxHashSet::default();
+
+ for scope in self.scopes() {
+ match scope {
+ Scope::BlockScope(m) => traits.extend(m.def_map[m.module_id].scope.traits()),
+ &Scope::ImplDefScope(impl_) => {
+ if let Some(target_trait) = &db.impl_data(impl_).target_trait {
+ if let Some(TypeNs::TraitId(trait_)) =
+ self.resolve_path_in_type_ns_fully(db, target_trait.path.mod_path())
+ {
+ traits.insert(trait_);
+ }
+ }
+ }
+ _ => (),
+ }
+ }
+
+ // Fill in the prelude traits
+ if let Some(prelude) = self.module_scope.def_map.prelude() {
+ let prelude_def_map = prelude.def_map(db);
+ traits.extend(prelude_def_map[prelude.local_id].scope.traits());
+ }
+ // Fill in module visible traits
+ traits.extend(self.module_scope.def_map[self.module_scope.module_id].scope.traits());
+ traits
+ }
+
+ pub fn module(&self) -> ModuleId {
+ let (def_map, local_id) = self.item_scope();
+ def_map.module_id(local_id)
+ }
+
+ pub fn krate(&self) -> CrateId {
+ self.module_scope.def_map.krate()
+ }
+
+ pub fn def_map(&self) -> &DefMap {
+ self.item_scope().0
+ }
+
+ pub fn where_predicates_in_scope(
+ &self,
+ ) -> impl Iterator<Item = &crate::generics::WherePredicate> {
+ self.scopes()
+ .filter_map(|scope| match scope {
+ Scope::GenericParams { params, .. } => Some(params),
+ _ => None,
+ })
+ .flat_map(|params| params.where_predicates.iter())
+ }
+
+ pub fn generic_def(&self) -> Option<GenericDefId> {
+ self.scopes().find_map(|scope| match scope {
+ Scope::GenericParams { def, .. } => Some(*def),
+ _ => None,
+ })
+ }
+
+ pub fn body_owner(&self) -> Option<DefWithBodyId> {
+ self.scopes().find_map(|scope| match scope {
+ Scope::ExprScope(it) => Some(it.owner),
+ _ => None,
+ })
+ }
+}
+
+impl Resolver {
+ fn scopes(&self) -> impl Iterator<Item = &Scope> {
+ self.scopes.iter().rev()
+ }
+
+ fn resolve_module_path(
+ &self,
+ db: &dyn DefDatabase,
+ path: &ModPath,
+ shadow: BuiltinShadowMode,
+ ) -> PerNs {
+ let (item_map, module) = self.item_scope();
+ let (module_res, segment_index) = item_map.resolve_path(db, module, path, shadow);
+ if segment_index.is_some() {
+ return PerNs::none();
+ }
+ module_res
+ }
+
+ /// The innermost block scope that contains items or the module scope that contains this resolver.
+ fn item_scope(&self) -> (&DefMap, LocalModuleId) {
+ self.scopes()
+ .find_map(|scope| match scope {
+ Scope::BlockScope(m) => Some((&*m.def_map, m.module_id)),
+ _ => None,
+ })
+ .unwrap_or((&self.module_scope.def_map, self.module_scope.module_id))
+ }
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum ScopeDef {
+ ModuleDef(ModuleDefId),
+ Unknown,
+ ImplSelfType(ImplId),
+ AdtSelfType(AdtId),
+ GenericParam(GenericParamId),
+ Local(PatId),
+ Label(LabelId),
+}
+
+impl Scope {
+ fn process_names(&self, acc: &mut ScopeNames, db: &dyn DefDatabase) {
+ match self {
+ Scope::BlockScope(m) => {
+ m.def_map[m.module_id].scope.entries().for_each(|(name, def)| {
+ acc.add_per_ns(name, def);
+ });
+ m.def_map[m.module_id].scope.legacy_macros().for_each(|(name, macs)| {
+ macs.iter().for_each(|&mac| {
+ acc.add(
+ name,
+ ScopeDef::ModuleDef(ModuleDefId::MacroId(MacroId::from(mac))),
+ );
+ })
+ });
+ }
+ Scope::GenericParams { params, def: parent } => {
+ let parent = *parent;
+ for (local_id, param) in params.type_or_consts.iter() {
+ if let Some(name) = ¶m.name() {
+ let id = TypeOrConstParamId { parent, local_id };
+ let data = &db.generic_params(parent).type_or_consts[local_id];
+ acc.add(
+ name,
+ ScopeDef::GenericParam(match data {
+ TypeOrConstParamData::TypeParamData(_) => {
+ GenericParamId::TypeParamId(TypeParamId::from_unchecked(id))
+ }
+ TypeOrConstParamData::ConstParamData(_) => {
+ GenericParamId::ConstParamId(ConstParamId::from_unchecked(id))
+ }
+ }),
+ );
+ }
+ }
+ for (local_id, param) in params.lifetimes.iter() {
+ let id = LifetimeParamId { parent, local_id };
+ acc.add(¶m.name, ScopeDef::GenericParam(id.into()))
+ }
+ }
+ Scope::ImplDefScope(i) => {
+ acc.add(&name![Self], ScopeDef::ImplSelfType(*i));
+ }
+ Scope::AdtScope(i) => {
+ acc.add(&name![Self], ScopeDef::AdtSelfType(*i));
+ }
+ Scope::ExprScope(scope) => {
+ if let Some((label, name)) = scope.expr_scopes.label(scope.scope_id) {
+ acc.add(&name, ScopeDef::Label(label))
+ }
+ scope.expr_scopes.entries(scope.scope_id).iter().for_each(|e| {
+ acc.add_local(e.name(), e.pat());
+ });
+ }
+ }
+ }
+}
+
+// needs arbitrary_self_types to be a method... or maybe move to the def?
+pub fn resolver_for_expr(db: &dyn DefDatabase, owner: DefWithBodyId, expr_id: ExprId) -> Resolver {
+ let scopes = db.expr_scopes(owner);
+ resolver_for_scope(db, owner, scopes.scope_for(expr_id))
+}
+
+pub fn resolver_for_scope(
+ db: &dyn DefDatabase,
+ owner: DefWithBodyId,
+ scope_id: Option<ScopeId>,
+) -> Resolver {
+ let mut r = owner.resolver(db);
+ let scopes = db.expr_scopes(owner);
+ let scope_chain = scopes.scope_chain(scope_id).collect::<Vec<_>>();
+ r.scopes.reserve(scope_chain.len());
+
+ for scope in scope_chain.into_iter().rev() {
+ if let Some(block) = scopes.block(scope) {
+ if let Some(def_map) = db.block_def_map(block) {
+ let root = def_map.root();
+ r = r.push_block_scope(def_map, root);
+ // FIXME: This adds as many module scopes as there are blocks, but resolving in each
+ // already traverses all parents, so this is O(n²). I think we could only store the
+ // innermost module scope instead?
+ }
+ }
+
+ r = r.push_expr_scope(owner, Arc::clone(&scopes), scope);
+ }
+ r
+}
+
+impl Resolver {
+ fn push_scope(mut self, scope: Scope) -> Resolver {
+ self.scopes.push(scope);
+ self
+ }
+
+ fn push_generic_params_scope(self, db: &dyn DefDatabase, def: GenericDefId) -> Resolver {
+ let params = db.generic_params(def);
+ self.push_scope(Scope::GenericParams { def, params })
+ }
+
+ fn push_impl_def_scope(self, impl_def: ImplId) -> Resolver {
+ self.push_scope(Scope::ImplDefScope(impl_def))
+ }
+
+ fn push_block_scope(self, def_map: Arc<DefMap>, module_id: LocalModuleId) -> Resolver {
+ self.push_scope(Scope::BlockScope(ModuleItemMap { def_map, module_id }))
+ }
+
+ fn push_expr_scope(
+ self,
+ owner: DefWithBodyId,
+ expr_scopes: Arc<ExprScopes>,
+ scope_id: ScopeId,
+ ) -> Resolver {
+ self.push_scope(Scope::ExprScope(ExprScope { owner, expr_scopes, scope_id }))
+ }
+}
+
+impl ModuleItemMap {
+ fn resolve_path_in_value_ns(
+ &self,
+ db: &dyn DefDatabase,
+ path: &ModPath,
+ ) -> Option<ResolveValueResult> {
+ let (module_def, idx) =
+ self.def_map.resolve_path_locally(db, self.module_id, path, BuiltinShadowMode::Other);
+ match idx {
+ None => {
+ let value = to_value_ns(module_def)?;
+ Some(ResolveValueResult::ValueNs(value))
+ }
+ Some(idx) => {
+ let ty = match module_def.take_types()? {
+ ModuleDefId::AdtId(it) => TypeNs::AdtId(it),
+ ModuleDefId::TraitId(it) => TypeNs::TraitId(it),
+ ModuleDefId::TypeAliasId(it) => TypeNs::TypeAliasId(it),
+ ModuleDefId::BuiltinType(it) => TypeNs::BuiltinType(it),
+
+ ModuleDefId::ModuleId(_)
+ | ModuleDefId::FunctionId(_)
+ | ModuleDefId::EnumVariantId(_)
+ | ModuleDefId::ConstId(_)
+ | ModuleDefId::MacroId(_)
+ | ModuleDefId::StaticId(_) => return None,
+ };
+ Some(ResolveValueResult::Partial(ty, idx))
+ }
+ }
+ }
+
+ fn resolve_path_in_type_ns(
+ &self,
+ db: &dyn DefDatabase,
+ path: &ModPath,
+ ) -> Option<(TypeNs, Option<usize>)> {
+ let (module_def, idx) =
+ self.def_map.resolve_path_locally(db, self.module_id, path, BuiltinShadowMode::Other);
+ let res = to_type_ns(module_def)?;
+ Some((res, idx))
+ }
+}
+
+fn to_value_ns(per_ns: PerNs) -> Option<ValueNs> {
+ let res = match per_ns.take_values()? {
+ ModuleDefId::FunctionId(it) => ValueNs::FunctionId(it),
+ ModuleDefId::AdtId(AdtId::StructId(it)) => ValueNs::StructId(it),
+ ModuleDefId::EnumVariantId(it) => ValueNs::EnumVariantId(it),
+ ModuleDefId::ConstId(it) => ValueNs::ConstId(it),
+ ModuleDefId::StaticId(it) => ValueNs::StaticId(it),
+
+ ModuleDefId::AdtId(AdtId::EnumId(_) | AdtId::UnionId(_))
+ | ModuleDefId::TraitId(_)
+ | ModuleDefId::TypeAliasId(_)
+ | ModuleDefId::BuiltinType(_)
+ | ModuleDefId::MacroId(_)
+ | ModuleDefId::ModuleId(_) => return None,
+ };
+ Some(res)
+}
+
+fn to_type_ns(per_ns: PerNs) -> Option<TypeNs> {
+ let res = match per_ns.take_types()? {
+ ModuleDefId::AdtId(it) => TypeNs::AdtId(it),
+ ModuleDefId::EnumVariantId(it) => TypeNs::EnumVariantId(it),
+
+ ModuleDefId::TypeAliasId(it) => TypeNs::TypeAliasId(it),
+ ModuleDefId::BuiltinType(it) => TypeNs::BuiltinType(it),
+
+ ModuleDefId::TraitId(it) => TypeNs::TraitId(it),
+
+ ModuleDefId::FunctionId(_)
+ | ModuleDefId::ConstId(_)
+ | ModuleDefId::MacroId(_)
+ | ModuleDefId::StaticId(_)
+ | ModuleDefId::ModuleId(_) => return None,
+ };
+ Some(res)
+}
+
+type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<rustc_hash::FxHasher>>;
+#[derive(Default)]
+struct ScopeNames {
+ map: FxIndexMap<Name, SmallVec<[ScopeDef; 1]>>,
+}
+
+impl ScopeNames {
+ fn add(&mut self, name: &Name, def: ScopeDef) {
+ let set = self.map.entry(name.clone()).or_default();
+ if !set.contains(&def) {
+ set.push(def)
+ }
+ }
+ fn add_per_ns(&mut self, name: &Name, def: PerNs) {
+ if let &Some((ty, _)) = &def.types {
+ self.add(name, ScopeDef::ModuleDef(ty))
+ }
+ if let &Some((def, _)) = &def.values {
+ self.add(name, ScopeDef::ModuleDef(def))
+ }
+ if let &Some((mac, _)) = &def.macros {
+ self.add(name, ScopeDef::ModuleDef(ModuleDefId::MacroId(mac)))
+ }
+ if def.is_none() {
+ self.add(name, ScopeDef::Unknown)
+ }
+ }
+ fn add_local(&mut self, name: &Name, pat: PatId) {
+ let set = self.map.entry(name.clone()).or_default();
+ // XXX: hack, account for local (and only local) shadowing.
+ //
+ // This should be somewhat more principled and take namespaces into
+ // accounts, but, alas, scoping rules are a hoax. `str` type and `str`
+ // module can be both available in the same scope.
+ if set.iter().any(|it| matches!(it, &ScopeDef::Local(_))) {
+ cov_mark::hit!(shadowing_shows_single_completion);
+ return;
+ }
+ set.push(ScopeDef::Local(pat))
+ }
+}
+
+pub trait HasResolver: Copy {
+ /// Builds a resolver for type references inside this def.
+ fn resolver(self, db: &dyn DefDatabase) -> Resolver;
+}
+
+impl HasResolver for ModuleId {
+ fn resolver(self, db: &dyn DefDatabase) -> Resolver {
+ let mut def_map = self.def_map(db);
+ let mut modules: SmallVec<[_; 1]> = smallvec![];
+ let mut module_id = self.local_id;
+ while let Some(parent) = def_map.parent() {
+ modules.push((def_map, module_id));
+ def_map = parent.def_map(db);
+ module_id = parent.local_id;
+ }
+ let mut resolver = Resolver {
+ scopes: Vec::with_capacity(modules.len()),
+ module_scope: ModuleItemMap { def_map, module_id },
+ };
+ for (def_map, module) in modules.into_iter().rev() {
+ resolver = resolver.push_block_scope(def_map, module);
+ }
+ resolver
+ }
+}
+
+impl HasResolver for TraitId {
+ fn resolver(self, db: &dyn DefDatabase) -> Resolver {
+ self.lookup(db).container.resolver(db).push_generic_params_scope(db, self.into())
+ }
+}
+
+impl<T: Into<AdtId> + Copy> HasResolver for T {
+ fn resolver(self, db: &dyn DefDatabase) -> Resolver {
+ let def = self.into();
+ def.module(db)
+ .resolver(db)
+ .push_generic_params_scope(db, def.into())
+ .push_scope(Scope::AdtScope(def))
+ }
+}
+
+impl HasResolver for FunctionId {
+ fn resolver(self, db: &dyn DefDatabase) -> Resolver {
+ self.lookup(db).container.resolver(db).push_generic_params_scope(db, self.into())
+ }
+}
+
+impl HasResolver for ConstId {
+ fn resolver(self, db: &dyn DefDatabase) -> Resolver {
+ self.lookup(db).container.resolver(db)
+ }
+}
+
+impl HasResolver for StaticId {
+ fn resolver(self, db: &dyn DefDatabase) -> Resolver {
+ self.lookup(db).container.resolver(db)
+ }
+}
+
+impl HasResolver for TypeAliasId {
+ fn resolver(self, db: &dyn DefDatabase) -> Resolver {
+ self.lookup(db).container.resolver(db).push_generic_params_scope(db, self.into())
+ }
+}
+
+impl HasResolver for ImplId {
+ fn resolver(self, db: &dyn DefDatabase) -> Resolver {
+ self.lookup(db)
+ .container
+ .resolver(db)
+ .push_generic_params_scope(db, self.into())
+ .push_impl_def_scope(self)
+ }
+}
+
+impl HasResolver for ExternBlockId {
+ fn resolver(self, db: &dyn DefDatabase) -> Resolver {
+ // Same as parent's
+ self.lookup(db).container.resolver(db)
+ }
+}
+
+impl HasResolver for DefWithBodyId {
+ fn resolver(self, db: &dyn DefDatabase) -> Resolver {
+ match self {
+ DefWithBodyId::ConstId(c) => c.resolver(db),
+ DefWithBodyId::FunctionId(f) => f.resolver(db),
+ DefWithBodyId::StaticId(s) => s.resolver(db),
++ DefWithBodyId::VariantId(v) => v.parent.resolver(db),
+ }
+ }
+}
+
+impl HasResolver for ItemContainerId {
+ fn resolver(self, db: &dyn DefDatabase) -> Resolver {
+ match self {
+ ItemContainerId::ModuleId(it) => it.resolver(db),
+ ItemContainerId::TraitId(it) => it.resolver(db),
+ ItemContainerId::ImplId(it) => it.resolver(db),
+ ItemContainerId::ExternBlockId(it) => it.resolver(db),
+ }
+ }
+}
+
+impl HasResolver for GenericDefId {
+ fn resolver(self, db: &dyn DefDatabase) -> Resolver {
+ match self {
+ GenericDefId::FunctionId(inner) => inner.resolver(db),
+ GenericDefId::AdtId(adt) => adt.resolver(db),
+ GenericDefId::TraitId(inner) => inner.resolver(db),
+ GenericDefId::TypeAliasId(inner) => inner.resolver(db),
+ GenericDefId::ImplId(inner) => inner.resolver(db),
+ GenericDefId::EnumVariantId(inner) => inner.parent.resolver(db),
+ GenericDefId::ConstId(inner) => inner.resolver(db),
+ }
+ }
+}
+
+impl HasResolver for VariantId {
+ fn resolver(self, db: &dyn DefDatabase) -> Resolver {
+ match self {
+ VariantId::EnumVariantId(it) => it.parent.resolver(db),
+ VariantId::StructId(it) => it.resolver(db),
+ VariantId::UnionId(it) => it.resolver(db),
+ }
+ }
+}
+
+impl HasResolver for MacroId {
+ fn resolver(self, db: &dyn DefDatabase) -> Resolver {
+ match self {
+ MacroId::Macro2Id(it) => it.resolver(db),
+ MacroId::MacroRulesId(it) => it.resolver(db),
+ MacroId::ProcMacroId(it) => it.resolver(db),
+ }
+ }
+}
+
+impl HasResolver for Macro2Id {
+ fn resolver(self, db: &dyn DefDatabase) -> Resolver {
+ self.lookup(db).container.resolver(db)
+ }
+}
+
+impl HasResolver for ProcMacroId {
+ fn resolver(self, db: &dyn DefDatabase) -> Resolver {
+ self.lookup(db).container.resolver(db)
+ }
+}
+
+impl HasResolver for MacroRulesId {
+ fn resolver(self, db: &dyn DefDatabase) -> Resolver {
+ self.lookup(db).container.resolver(db)
+ }
+}
--- /dev/null
- if ast::Item::can_cast(kind) || ast::BlockExpr::can_cast(kind) {
+//! `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::{
+ any::type_name,
+ fmt,
+ hash::{BuildHasher, BuildHasherDefault, Hash, Hasher},
+ marker::PhantomData,
+};
+
+use la_arena::{Arena, Idx};
+use profile::Count;
+use rustc_hash::FxHasher;
+use syntax::{ast, AstNode, AstPtr, SyntaxNode, SyntaxNodePtr};
+
+/// `AstId` points to an AST node in a specific file.
+pub struct FileAstId<N: AstNode> {
+ raw: ErasedFileAstId,
+ _ty: PhantomData<fn() -> N>,
+}
+
+impl<N: AstNode> Clone for FileAstId<N> {
+ fn clone(&self) -> FileAstId<N> {
+ *self
+ }
+}
+impl<N: AstNode> Copy for FileAstId<N> {}
+
+impl<N: AstNode> PartialEq for FileAstId<N> {
+ fn eq(&self, other: &Self) -> bool {
+ self.raw == other.raw
+ }
+}
+impl<N: AstNode> Eq for FileAstId<N> {}
+impl<N: AstNode> Hash for FileAstId<N> {
+ fn hash<H: Hasher>(&self, hasher: &mut H) {
+ self.raw.hash(hasher);
+ }
+}
+
+impl<N: AstNode> fmt::Debug for FileAstId<N> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "FileAstId::<{}>({})", type_name::<N>(), self.raw.into_raw())
+ }
+}
+
+impl<N: AstNode> FileAstId<N> {
+ // Can't make this a From implementation because of coherence
+ pub fn upcast<M: AstNode>(self) -> FileAstId<M>
+ where
+ N: Into<M>,
+ {
+ FileAstId { raw: self.raw, _ty: PhantomData }
+ }
+}
+
+type ErasedFileAstId = Idx<SyntaxNodePtr>;
+
+/// Maps items' `SyntaxNode`s to `ErasedFileAstId`s and back.
+#[derive(Default)]
+pub struct AstIdMap {
+ /// Maps stable id to unstable ptr.
+ arena: Arena<SyntaxNodePtr>,
+ /// Reverse: map ptr to id.
+ map: hashbrown::HashMap<Idx<SyntaxNodePtr>, (), ()>,
+ _c: Count<Self>,
+}
+
+impl fmt::Debug for AstIdMap {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_struct("AstIdMap").field("arena", &self.arena).finish()
+ }
+}
+
+impl PartialEq for AstIdMap {
+ fn eq(&self, other: &Self) -> bool {
+ self.arena == other.arena
+ }
+}
+impl Eq for AstIdMap {}
+
+impl AstIdMap {
+ pub(crate) fn from_source(node: &SyntaxNode) -> AstIdMap {
+ assert!(node.parent().is_none());
+ let mut res = AstIdMap::default();
+ // By walking the tree in breadth-first order we make sure that parents
+ // get lower ids then children. That is, adding a new child does not
+ // change parent's id. This means that, say, adding a new function to a
+ // trait does not change ids of top-level items, which helps caching.
+ bdfs(node, |it| {
+ let kind = it.kind();
++ if ast::Item::can_cast(kind)
++ || ast::BlockExpr::can_cast(kind)
++ || ast::Variant::can_cast(kind)
++ || ast::RecordField::can_cast(kind)
++ || ast::TupleField::can_cast(kind)
++ {
+ res.alloc(&it);
+ true
+ } else {
+ false
+ }
+ });
+ res.map = hashbrown::HashMap::with_capacity_and_hasher(res.arena.len(), ());
+ for (idx, ptr) in res.arena.iter() {
+ let hash = hash_ptr(ptr);
+ match res.map.raw_entry_mut().from_hash(hash, |idx2| *idx2 == idx) {
+ hashbrown::hash_map::RawEntryMut::Occupied(_) => unreachable!(),
+ hashbrown::hash_map::RawEntryMut::Vacant(entry) => {
+ entry.insert_with_hasher(hash, idx, (), |&idx| hash_ptr(&res.arena[idx]));
+ }
+ }
+ }
+ res
+ }
+
+ pub fn ast_id<N: AstNode>(&self, item: &N) -> FileAstId<N> {
+ 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);
+ let hash = hash_ptr(&ptr);
+ match self.map.raw_entry().from_hash(hash, |&idx| self.arena[idx] == ptr) {
+ Some((&idx, &())) => idx,
+ None => panic!(
+ "Can't find {:?} in AstIdMap:\n{:?}",
+ item,
+ self.arena.iter().map(|(_id, i)| i).collect::<Vec<_>>(),
+ ),
+ }
+ }
+
+ pub fn get<N: AstNode>(&self, id: FileAstId<N>) -> AstPtr<N> {
+ AstPtr::try_from_raw(self.arena[id.raw].clone()).unwrap()
+ }
+
+ fn alloc(&mut self, item: &SyntaxNode) -> ErasedFileAstId {
+ self.arena.alloc(SyntaxNodePtr::new(item))
+ }
+}
+
+fn hash_ptr(ptr: &SyntaxNodePtr) -> u64 {
+ let mut hasher = BuildHasherDefault::<FxHasher>::default().build_hasher();
+ ptr.hash(&mut hasher);
+ hasher.finish()
+}
+
+/// Walks the subtree in bdfs order, calling `f` for each node. What is bdfs
+/// order? It is a mix of breadth-first and depth first orders. Nodes for which
+/// `f` returns true are visited breadth-first, all the other nodes are explored
+/// depth-first.
+///
+/// In other words, the size of the bfs queue is bound by the number of "true"
+/// nodes.
+fn bdfs(node: &SyntaxNode, mut f: impl FnMut(SyntaxNode) -> bool) {
+ let mut curr_layer = vec![node.clone()];
+ let mut next_layer = vec![];
+ while !curr_layer.is_empty() {
+ curr_layer.drain(..).for_each(|node| {
+ let mut preorder = node.preorder();
+ while let Some(event) = preorder.next() {
+ match event {
+ syntax::WalkEvent::Enter(node) => {
+ if f(node.clone()) {
+ next_layer.extend(node.children());
+ preorder.skip_subtree();
+ }
+ }
+ syntax::WalkEvent::Leave(_) => {}
+ }
+ }
+ });
+ std::mem::swap(&mut curr_layer, &mut next_layer);
+ }
+}
--- /dev/null
+//! `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.
+
+#![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)]
+
+pub mod db;
+pub mod ast_id_map;
+pub mod name;
+pub mod hygiene;
+pub mod builtin_attr_macro;
+pub mod builtin_derive_macro;
+pub mod builtin_fn_macro;
+pub mod proc_macro;
+pub mod quote;
+pub mod eager;
+pub mod mod_path;
+mod fixup;
+
+pub use mbe::{Origin, ValueResult};
+
+use std::{fmt, hash::Hash, iter, sync::Arc};
+
+use base_db::{impl_intern_key, salsa, CrateId, FileId, FileRange, ProcMacroKind};
+use either::Either;
+use syntax::{
+ algo::{self, skip_trivia_token},
+ ast::{self, AstNode, HasDocComments},
+ Direction, SyntaxNode, SyntaxToken,
+};
+
+use crate::{
+ ast_id_map::FileAstId,
+ builtin_attr_macro::BuiltinAttrExpander,
+ builtin_derive_macro::BuiltinDeriveExpander,
+ builtin_fn_macro::{BuiltinFnLikeExpander, EagerExpander},
+ db::TokenExpander,
+ mod_path::ModPath,
+ proc_macro::ProcMacroExpander,
+};
+
+pub type ExpandResult<T> = ValueResult<T, ExpandError>;
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub enum ExpandError {
+ UnresolvedProcMacro(CrateId),
+ Mbe(mbe::ExpandError),
+ Other(Box<str>),
+}
+
+impl From<mbe::ExpandError> for ExpandError {
+ fn from(mbe: mbe::ExpandError) -> Self {
+ Self::Mbe(mbe)
+ }
+}
+
+impl fmt::Display for ExpandError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ ExpandError::UnresolvedProcMacro(_) => f.write_str("unresolved proc-macro"),
+ ExpandError::Mbe(it) => it.fmt(f),
+ ExpandError::Other(it) => f.write_str(it),
+ }
+ }
+}
+
+/// 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 interning. You can check details here:
+/// <https://en.wikipedia.org/wiki/String_interning>).
+#[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<FileId> for HirFileId {
+ fn from(id: FileId) -> Self {
+ HirFileId(HirFileIdRepr::FileId(id))
+ }
+}
+
+impl From<MacroFile> for HirFileId {
+ fn from(id: MacroFile) -> Self {
+ HirFileId(HirFileIdRepr::MacroFile(id))
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct MacroFile {
+ pub macro_call_id: MacroCallId,
+}
+
+/// `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 def: MacroDefId,
+ pub(crate) krate: CrateId,
+ eager: Option<EagerCallInfo>,
+ pub kind: MacroCallKind,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct MacroDefId {
+ pub krate: CrateId,
+ pub kind: MacroDefKind,
+ pub local_inner: bool,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum MacroDefKind {
+ Declarative(AstId<ast::Macro>),
+ BuiltIn(BuiltinFnLikeExpander, AstId<ast::Macro>),
+ BuiltInAttr(BuiltinAttrExpander, AstId<ast::Macro>),
+ BuiltInDerive(BuiltinDeriveExpander, AstId<ast::Macro>),
+ BuiltInEager(EagerExpander, AstId<ast::Macro>),
+ ProcMacro(ProcMacroExpander, ProcMacroKind, AstId<ast::Fn>),
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+struct EagerCallInfo {
+ /// NOTE: This can be *either* the expansion result, *or* the argument to the eager macro!
+ arg_or_expansion: Arc<tt::Subtree>,
+ included_file: Option<FileId>,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub enum MacroCallKind {
+ FnLike {
+ ast_id: AstId<ast::MacroCall>,
+ expand_to: ExpandTo,
+ },
+ Derive {
+ ast_id: AstId<ast::Adt>,
+ /// Syntactical index of the invoking `#[derive]` attribute.
+ ///
+ /// Outer attributes are counted first, then inner attributes. This does not support
+ /// out-of-line modules, which may have attributes spread across 2 files!
+ derive_attr_index: u32,
+ /// Index of the derive macro in the derive attribute
+ derive_index: u32,
+ },
+ Attr {
+ ast_id: AstId<ast::Item>,
+ attr_args: Arc<(tt::Subtree, mbe::TokenMap)>,
+ /// Syntactical index of the invoking `#[attribute]`.
+ ///
+ /// Outer attributes are counted first, then inner attributes. This does not support
+ /// out-of-line modules, which may have attributes spread across 2 files!
+ invoc_attr_index: u32,
+ /// Whether this attribute is the `#[derive]` attribute.
+ is_derive: bool,
+ },
+}
+
+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 {
+ let mut file_id = self;
+ loop {
+ match file_id.0 {
+ HirFileIdRepr::FileId(id) => break id,
+ HirFileIdRepr::MacroFile(MacroFile { macro_call_id }) => {
+ let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_call_id);
+ file_id = match loc.eager {
+ Some(EagerCallInfo { included_file: Some(file), .. }) => file.into(),
+ _ => loc.kind.file_id(),
+ };
+ }
+ }
+ }
+ }
+
+ pub fn expansion_level(self, db: &dyn db::AstDatabase) -> u32 {
+ let mut level = 0;
+ let mut curr = self;
+ while let HirFileIdRepr::MacroFile(macro_file) = curr.0 {
+ let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id);
+
+ level += 1;
+ curr = loc.kind.file_id();
+ }
+ level
+ }
+
+ /// If this is a macro call, returns the syntax node of the call.
+ pub fn call_node(self, db: &dyn db::AstDatabase) -> Option<InFile<SyntaxNode>> {
+ match self.0 {
+ HirFileIdRepr::FileId(_) => None,
+ HirFileIdRepr::MacroFile(macro_file) => {
+ let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id);
+ Some(loc.kind.to_node(db))
+ }
+ }
+ }
+
+ /// If this is a macro call, returns the syntax node of the very first macro call this file resides in.
+ pub fn original_call_node(self, db: &dyn db::AstDatabase) -> Option<(FileId, SyntaxNode)> {
+ let mut call = match self.0 {
+ HirFileIdRepr::FileId(_) => return None,
+ HirFileIdRepr::MacroFile(MacroFile { macro_call_id }) => {
+ db.lookup_intern_macro_call(macro_call_id).kind.to_node(db)
+ }
+ };
+ loop {
+ match call.file_id.0 {
+ HirFileIdRepr::FileId(file_id) => break Some((file_id, call.value)),
+ HirFileIdRepr::MacroFile(MacroFile { macro_call_id }) => {
+ call = db.lookup_intern_macro_call(macro_call_id).kind.to_node(db);
+ }
+ }
+ }
+ }
+
+ /// Return expansion information if it is a macro-expansion file
+ pub fn expansion_info(self, db: &dyn db::AstDatabase) -> Option<ExpansionInfo> {
+ match self.0 {
+ HirFileIdRepr::FileId(_) => None,
+ HirFileIdRepr::MacroFile(macro_file) => {
+ let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id);
+
+ let arg_tt = loc.kind.arg(db)?;
+
+ let macro_def = db.macro_def(loc.def).ok()?;
+ let (parse, exp_map) = db.parse_macro_expansion(macro_file).value?;
+ let macro_arg = db.macro_arg(macro_file.macro_call_id)?;
+
+ let def = loc.def.ast_id().left().and_then(|id| {
+ let def_tt = match id.to_node(db) {
+ ast::Macro::MacroRules(mac) => mac.token_tree()?,
+ ast::Macro::MacroDef(_)
+ if matches!(*macro_def, TokenExpander::BuiltinAttr(_)) =>
+ {
+ return None
+ }
+ ast::Macro::MacroDef(mac) => mac.body()?,
+ };
+ Some(InFile::new(id.file_id, def_tt))
+ });
+ let attr_input_or_mac_def = def.or_else(|| match loc.kind {
+ MacroCallKind::Attr { ast_id, invoc_attr_index, .. } => {
+ let tt = ast_id
+ .to_node(db)
+ .doc_comments_and_attrs()
+ .nth(invoc_attr_index as usize)
+ .and_then(Either::left)?
+ .token_tree()?;
+ Some(InFile::new(ast_id.file_id, tt))
+ }
+ _ => None,
+ });
+
+ Some(ExpansionInfo {
+ expanded: InFile::new(self, parse.syntax_node()),
+ arg: InFile::new(loc.kind.file_id(), arg_tt),
+ attr_input_or_mac_def,
+ macro_arg_shift: mbe::Shift::new(¯o_arg.0),
+ macro_arg,
+ macro_def,
+ exp_map,
+ })
+ }
+ }
+ }
+
+ /// Indicate it is macro file generated for builtin derive
+ pub fn is_builtin_derive(&self, db: &dyn db::AstDatabase) -> Option<InFile<ast::Attr>> {
+ match self.0 {
+ HirFileIdRepr::FileId(_) => None,
+ HirFileIdRepr::MacroFile(macro_file) => {
+ let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id);
+ let attr = match loc.def.kind {
+ MacroDefKind::BuiltInDerive(..) => loc.kind.to_node(db),
+ _ => return None,
+ };
+ Some(attr.with_value(ast::Attr::cast(attr.value.clone())?))
+ }
+ }
+ }
+
+ pub fn is_custom_derive(&self, db: &dyn db::AstDatabase) -> bool {
+ match self.0 {
+ HirFileIdRepr::FileId(_) => false,
+ HirFileIdRepr::MacroFile(macro_file) => {
+ let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id);
+ matches!(loc.def.kind, MacroDefKind::ProcMacro(_, ProcMacroKind::CustomDerive, _))
+ }
+ }
+ }
+
+ /// Return whether this file is an include macro
+ pub fn is_include_macro(&self, db: &dyn db::AstDatabase) -> bool {
+ match self.0 {
+ HirFileIdRepr::MacroFile(macro_file) => {
+ let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id);
+ matches!(loc.eager, Some(EagerCallInfo { included_file: Some(_), .. }))
+ }
+ _ => false,
+ }
+ }
+
+ /// Return whether this file is an attr macro
+ pub fn is_attr_macro(&self, db: &dyn db::AstDatabase) -> bool {
+ match self.0 {
+ HirFileIdRepr::MacroFile(macro_file) => {
+ let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id);
+ matches!(loc.kind, MacroCallKind::Attr { .. })
+ }
+ _ => false,
+ }
+ }
+
+ /// Return whether this file is the pseudo expansion of the derive attribute.
+ /// See [`crate::builtin_attr_macro::derive_attr_expand`].
+ pub fn is_derive_attr_pseudo_expansion(&self, db: &dyn db::AstDatabase) -> bool {
+ match self.0 {
+ HirFileIdRepr::MacroFile(macro_file) => {
+ let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id);
+ matches!(loc.kind, MacroCallKind::Attr { is_derive: true, .. })
+ }
+ _ => false,
+ }
+ }
+
+ pub fn is_macro(self) -> bool {
+ matches!(self.0, HirFileIdRepr::MacroFile(_))
+ }
+
+ pub fn macro_file(self) -> Option<MacroFile> {
+ match self.0 {
+ HirFileIdRepr::FileId(_) => None,
+ HirFileIdRepr::MacroFile(m) => Some(m),
+ }
+ }
+}
+
+impl MacroDefId {
+ pub fn as_lazy_macro(
+ self,
+ db: &dyn db::AstDatabase,
+ krate: CrateId,
+ kind: MacroCallKind,
+ ) -> MacroCallId {
+ db.intern_macro_call(MacroCallLoc { def: self, krate, eager: None, kind })
+ }
+
+ pub fn ast_id(&self) -> Either<AstId<ast::Macro>, AstId<ast::Fn>> {
+ let id = match self.kind {
+ MacroDefKind::ProcMacro(.., id) => return Either::Right(id),
+ MacroDefKind::Declarative(id)
+ | MacroDefKind::BuiltIn(_, id)
+ | MacroDefKind::BuiltInAttr(_, id)
+ | MacroDefKind::BuiltInDerive(_, id)
+ | MacroDefKind::BuiltInEager(_, id) => id,
+ };
+ Either::Left(id)
+ }
+
+ pub fn is_proc_macro(&self) -> bool {
+ matches!(self.kind, MacroDefKind::ProcMacro(..))
+ }
+
+ pub fn is_attribute(&self) -> bool {
+ matches!(
+ self.kind,
+ MacroDefKind::BuiltInAttr(..) | MacroDefKind::ProcMacro(_, ProcMacroKind::Attr, _)
+ )
+ }
+}
+
+// FIXME: attribute indices do not account for `cfg_attr`, which means that we'll strip the whole
+// `cfg_attr` instead of just one of the attributes it expands to
+
+impl MacroCallKind {
+ /// Returns the file containing the macro invocation.
+ fn file_id(&self) -> HirFileId {
+ match *self {
+ MacroCallKind::FnLike { ast_id: InFile { file_id, .. }, .. }
+ | MacroCallKind::Derive { ast_id: InFile { file_id, .. }, .. }
+ | MacroCallKind::Attr { ast_id: InFile { file_id, .. }, .. } => file_id,
+ }
+ }
+
+ pub fn to_node(&self, db: &dyn db::AstDatabase) -> InFile<SyntaxNode> {
+ match self {
+ MacroCallKind::FnLike { ast_id, .. } => {
+ ast_id.with_value(ast_id.to_node(db).syntax().clone())
+ }
+ MacroCallKind::Derive { ast_id, derive_attr_index, .. } => {
+ // FIXME: handle `cfg_attr`
+ ast_id.with_value(ast_id.to_node(db)).map(|it| {
+ it.doc_comments_and_attrs()
+ .nth(*derive_attr_index as usize)
+ .and_then(|it| match it {
+ Either::Left(attr) => Some(attr.syntax().clone()),
+ Either::Right(_) => None,
+ })
+ .unwrap_or_else(|| it.syntax().clone())
+ })
+ }
+ MacroCallKind::Attr { ast_id, is_derive: true, invoc_attr_index, .. } => {
+ // FIXME: handle `cfg_attr`
+ ast_id.with_value(ast_id.to_node(db)).map(|it| {
+ it.doc_comments_and_attrs()
+ .nth(*invoc_attr_index as usize)
+ .and_then(|it| match it {
+ Either::Left(attr) => Some(attr.syntax().clone()),
+ Either::Right(_) => None,
+ })
+ .unwrap_or_else(|| it.syntax().clone())
+ })
+ }
+ MacroCallKind::Attr { ast_id, .. } => {
+ ast_id.with_value(ast_id.to_node(db).syntax().clone())
+ }
+ }
+ }
+
+ /// Returns the original file range that best describes the location of this macro call.
+ ///
+ /// Unlike `MacroCallKind::original_call_range`, this also spans the item of attributes and derives.
+ pub fn original_call_range_with_body(self, db: &dyn db::AstDatabase) -> FileRange {
+ let mut kind = self;
+ let file_id = loop {
+ match kind.file_id().0 {
+ HirFileIdRepr::MacroFile(file) => {
+ kind = db.lookup_intern_macro_call(file.macro_call_id).kind;
+ }
+ HirFileIdRepr::FileId(file_id) => break file_id,
+ }
+ };
+
+ let range = match kind {
+ MacroCallKind::FnLike { ast_id, .. } => ast_id.to_node(db).syntax().text_range(),
+ MacroCallKind::Derive { ast_id, .. } => ast_id.to_node(db).syntax().text_range(),
+ MacroCallKind::Attr { ast_id, .. } => ast_id.to_node(db).syntax().text_range(),
+ };
+
+ FileRange { range, file_id }
+ }
+
+ /// Returns the original file range that best describes the location of this macro call.
+ ///
+ /// Here we try to roughly match what rustc does to improve diagnostics: fn-like macros
+ /// get the whole `ast::MacroCall`, attribute macros get the attribute's range, and derives
+ /// get only the specific derive that is being referred to.
+ pub fn original_call_range(self, db: &dyn db::AstDatabase) -> FileRange {
+ let mut kind = self;
+ let file_id = loop {
+ match kind.file_id().0 {
+ HirFileIdRepr::MacroFile(file) => {
+ kind = db.lookup_intern_macro_call(file.macro_call_id).kind;
+ }
+ HirFileIdRepr::FileId(file_id) => break file_id,
+ }
+ };
+
+ let range = match kind {
+ MacroCallKind::FnLike { ast_id, .. } => ast_id.to_node(db).syntax().text_range(),
+ MacroCallKind::Derive { ast_id, derive_attr_index, .. } => {
+ // FIXME: should be the range of the macro name, not the whole derive
+ ast_id
+ .to_node(db)
+ .doc_comments_and_attrs()
+ .nth(derive_attr_index as usize)
+ .expect("missing derive")
+ .expect_left("derive is a doc comment?")
+ .syntax()
+ .text_range()
+ }
+ MacroCallKind::Attr { ast_id, invoc_attr_index, .. } => ast_id
+ .to_node(db)
+ .doc_comments_and_attrs()
+ .nth(invoc_attr_index as usize)
+ .expect("missing attribute")
+ .expect_left("attribute macro is a doc comment?")
+ .syntax()
+ .text_range(),
+ };
+
+ FileRange { range, file_id }
+ }
+
+ fn arg(&self, db: &dyn db::AstDatabase) -> Option<SyntaxNode> {
+ match self {
+ MacroCallKind::FnLike { ast_id, .. } => {
+ Some(ast_id.to_node(db).token_tree()?.syntax().clone())
+ }
+ MacroCallKind::Derive { ast_id, .. } => Some(ast_id.to_node(db).syntax().clone()),
+ MacroCallKind::Attr { ast_id, .. } => Some(ast_id.to_node(db).syntax().clone()),
+ }
+ }
+
+ fn expand_to(&self) -> ExpandTo {
+ match self {
+ MacroCallKind::FnLike { expand_to, .. } => *expand_to,
+ MacroCallKind::Derive { .. } => ExpandTo::Items,
+ MacroCallKind::Attr { is_derive: true, .. } => ExpandTo::Statements,
+ MacroCallKind::Attr { .. } => ExpandTo::Items, // is this always correct?
+ }
+ }
+}
+
+impl MacroCallId {
+ pub fn as_file(self) -> HirFileId {
+ MacroFile { macro_call_id: self }.into()
+ }
+}
+
+/// ExpansionInfo mainly describes how to map text range between src and expanded macro
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct ExpansionInfo {
+ expanded: InFile<SyntaxNode>,
+ /// The argument TokenTree or item for attributes
+ arg: InFile<SyntaxNode>,
+ /// The `macro_rules!` or attribute input.
+ attr_input_or_mac_def: Option<InFile<ast::TokenTree>>,
+
+ macro_def: Arc<TokenExpander>,
+ macro_arg: Arc<(tt::Subtree, mbe::TokenMap, fixup::SyntaxFixupUndoInfo)>,
+ /// A shift built from `macro_arg`'s subtree, relevant for attributes as the item is the macro arg
+ /// and as such we need to shift tokens if they are part of an attributes input instead of their item.
+ macro_arg_shift: mbe::Shift,
+ exp_map: Arc<mbe::TokenMap>,
+}
+
+impl ExpansionInfo {
+ pub fn expanded(&self) -> InFile<SyntaxNode> {
+ self.expanded.clone()
+ }
+
+ pub fn call_node(&self) -> Option<InFile<SyntaxNode>> {
+ Some(self.arg.with_value(self.arg.value.parent()?))
+ }
+
+ /// Map a token down from macro input into the macro expansion.
+ ///
+ /// The inner workings of this function differ slightly depending on the type of macro we are dealing with:
+ /// - declarative:
+ /// For declarative macros, we need to accommodate for the macro definition site(which acts as a second unchanging input)
+ /// , as tokens can mapped in and out of it.
+ /// To do this we shift all ids in the expansion by the maximum id of the definition site giving us an easy
+ /// way to map all the tokens.
+ /// - attribute:
+ /// Attributes have two different inputs, the input tokentree in the attribute node and the item
+ /// the attribute is annotating. Similarly as for declarative macros we need to do a shift here
+ /// as well. Currently this is done by shifting the attribute input by the maximum id of the item.
+ /// - function-like and derives:
+ /// Both of these only have one simple call site input so no special handling is required here.
+ pub fn map_token_down(
+ &self,
+ db: &dyn db::AstDatabase,
+ item: Option<ast::Item>,
+ token: InFile<&SyntaxToken>,
+ ) -> Option<impl Iterator<Item = InFile<SyntaxToken>> + '_> {
+ assert_eq!(token.file_id, self.arg.file_id);
+ let token_id_in_attr_input = if let Some(item) = item {
+ // check if we are mapping down in an attribute input
+ // this is a special case as attributes can have two inputs
+ let call_id = self.expanded.file_id.macro_file()?.macro_call_id;
+ let loc = db.lookup_intern_macro_call(call_id);
+
+ let token_range = token.value.text_range();
+ match &loc.kind {
+ MacroCallKind::Attr { attr_args, invoc_attr_index, is_derive, .. } => {
+ let attr = item
+ .doc_comments_and_attrs()
+ .nth(*invoc_attr_index as usize)
+ .and_then(Either::left)?;
+ match attr.token_tree() {
+ Some(token_tree)
+ if token_tree.syntax().text_range().contains_range(token_range) =>
+ {
+ let attr_input_start =
+ token_tree.left_delimiter_token()?.text_range().start();
+ let relative_range =
+ token.value.text_range().checked_sub(attr_input_start)?;
+ // shift by the item's tree's max id
+ let token_id = attr_args.1.token_by_range(relative_range)?;
+ let token_id = if *is_derive {
+ // we do not shift for `#[derive]`, as we only need to downmap the derive attribute tokens
+ token_id
+ } else {
+ self.macro_arg_shift.shift(token_id)
+ };
+ Some(token_id)
+ }
+ _ => None,
+ }
+ }
+ _ => None,
+ }
+ } else {
+ None
+ };
+
+ let token_id = match token_id_in_attr_input {
+ Some(token_id) => token_id,
+ // the token is not inside an attribute's input so do the lookup in the macro_arg as usual
+ None => {
+ let relative_range =
+ token.value.text_range().checked_sub(self.arg.value.text_range().start())?;
+ let token_id = self.macro_arg.1.token_by_range(relative_range)?;
+ // conditionally shift the id by a declaratives macro definition
+ self.macro_def.map_id_down(token_id)
+ }
+ };
+
+ let tokens = self
+ .exp_map
+ .ranges_by_token(token_id, token.value.kind())
+ .flat_map(move |range| self.expanded.value.covering_element(range).into_token());
+
+ Some(tokens.map(move |token| self.expanded.with_value(token)))
+ }
+
+ /// Map a token up out of the expansion it resides in into the arguments of the macro call of the expansion.
+ pub fn map_token_up(
+ &self,
+ db: &dyn db::AstDatabase,
+ token: InFile<&SyntaxToken>,
+ ) -> Option<(InFile<SyntaxToken>, Origin)> {
+ // Fetch the id through its text range,
+ let token_id = self.exp_map.token_by_range(token.value.text_range())?;
+ // conditionally unshifting the id to accommodate for macro-rules def site
+ let (mut token_id, origin) = self.macro_def.map_id_up(token_id);
+
+ let call_id = self.expanded.file_id.macro_file()?.macro_call_id;
+ let loc = db.lookup_intern_macro_call(call_id);
+
+ // Attributes are a bit special for us, they have two inputs, the input tokentree and the annotated item.
+ let (token_map, tt) = match &loc.kind {
+ MacroCallKind::Attr { attr_args, is_derive: true, .. } => {
+ (&attr_args.1, self.attr_input_or_mac_def.clone()?.syntax().cloned())
+ }
+ MacroCallKind::Attr { attr_args, .. } => {
+ // try unshifting the the token id, if unshifting fails, the token resides in the non-item attribute input
+ // note that the `TokenExpander::map_id_up` earlier only unshifts for declarative macros, so we don't double unshift with this
+ match self.macro_arg_shift.unshift(token_id) {
+ Some(unshifted) => {
+ token_id = unshifted;
+ (&attr_args.1, self.attr_input_or_mac_def.clone()?.syntax().cloned())
+ }
+ None => (&self.macro_arg.1, self.arg.clone()),
+ }
+ }
+ _ => match origin {
+ mbe::Origin::Call => (&self.macro_arg.1, self.arg.clone()),
+ mbe::Origin::Def => match (&*self.macro_def, &self.attr_input_or_mac_def) {
+ (TokenExpander::DeclarativeMacro { def_site_token_map, .. }, Some(tt)) => {
+ (def_site_token_map, tt.syntax().cloned())
+ }
+ _ => panic!("`Origin::Def` used with non-`macro_rules!` macro"),
+ },
+ },
+ };
+
+ let range = token_map.first_range_by_token(token_id, token.value.kind())?;
+ let token =
+ tt.value.covering_element(range + tt.value.text_range().start()).into_token()?;
+ Some((tt.with_value(token), origin))
+ }
+}
+
+/// `AstId` points to an AST node in any file.
+///
+/// It is stable across reparses, and can be used as salsa key/value.
+pub type AstId<N> = InFile<FileAstId<N>>;
+
+impl<N: AstNode> AstId<N> {
+ 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.value).to_node(&root)
+ }
+}
+
+/// `InFile<T>` stores a value of `T` inside a particular file/syntax tree.
+///
+/// Typical usages are:
+///
+/// * `InFile<SyntaxNode>` -- syntax node in a file
+/// * `InFile<ast::FnDef>` -- ast node in a file
+/// * `InFile<TextSize>` -- offset in a file
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
+pub struct InFile<T> {
+ pub file_id: HirFileId,
+ pub value: T,
+}
+
+impl<T> InFile<T> {
+ pub fn new(file_id: HirFileId, value: T) -> InFile<T> {
+ InFile { file_id, value }
+ }
+
+ pub fn with_value<U>(&self, value: U) -> InFile<U> {
+ InFile::new(self.file_id, value)
+ }
+
+ pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> InFile<U> {
+ InFile::new(self.file_id, f(self.value))
+ }
+
+ pub fn as_ref(&self) -> InFile<&T> {
+ self.with_value(&self.value)
+ }
+
+ pub fn file_syntax(&self, db: &dyn db::AstDatabase) -> SyntaxNode {
+ db.parse_or_expand(self.file_id).expect("source created from invalid file")
+ }
+}
+
+impl<T: Clone> InFile<&T> {
+ pub fn cloned(&self) -> InFile<T> {
+ self.with_value(self.value.clone())
+ }
+}
+
+impl<T> InFile<Option<T>> {
+ pub fn transpose(self) -> Option<InFile<T>> {
+ let value = self.value?;
+ Some(InFile::new(self.file_id, value))
+ }
+}
+
+impl<'a> InFile<&'a SyntaxNode> {
+ pub fn ancestors_with_macros(
+ self,
+ db: &dyn db::AstDatabase,
+ ) -> impl Iterator<Item = InFile<SyntaxNode>> + Clone + '_ {
+ iter::successors(Some(self.cloned()), move |node| match node.value.parent() {
+ Some(parent) => Some(node.with_value(parent)),
+ None => node.file_id.call_node(db),
+ })
+ }
+
+ /// Skips the attributed item that caused the macro invocation we are climbing up
+ pub fn ancestors_with_macros_skip_attr_item(
+ self,
+ db: &dyn db::AstDatabase,
+ ) -> impl Iterator<Item = InFile<SyntaxNode>> + '_ {
+ let succ = move |node: &InFile<SyntaxNode>| match node.value.parent() {
+ Some(parent) => Some(node.with_value(parent)),
+ None => {
+ let parent_node = node.file_id.call_node(db)?;
+ if node.file_id.is_attr_macro(db) {
+ // macro call was an attributed item, skip it
+ // FIXME: does this fail if this is a direct expansion of another macro?
+ parent_node.map(|node| node.parent()).transpose()
+ } else {
+ Some(parent_node)
+ }
+ }
+ };
+ iter::successors(succ(&self.cloned()), succ)
+ }
+
+ /// Falls back to the macro call range if the node cannot be mapped up fully.
+ ///
+ /// For attributes and derives, this will point back to the attribute only.
+ /// For the entire item `InFile::use original_file_range_full`.
+ pub fn original_file_range(self, db: &dyn db::AstDatabase) -> FileRange {
+ match self.file_id.0 {
+ HirFileIdRepr::FileId(file_id) => FileRange { file_id, range: self.value.text_range() },
+ HirFileIdRepr::MacroFile(mac_file) => {
+ if let Some(res) = self.original_file_range_opt(db) {
+ return res;
+ }
+ // Fall back to whole macro call.
+ let loc = db.lookup_intern_macro_call(mac_file.macro_call_id);
+ loc.kind.original_call_range(db)
+ }
+ }
+ }
+
+ /// Attempts to map the syntax node back up its macro calls.
+ pub fn original_file_range_opt(self, db: &dyn db::AstDatabase) -> Option<FileRange> {
+ match ascend_node_border_tokens(db, self) {
+ Some(InFile { file_id, value: (first, last) }) => {
+ let original_file = file_id.original_file(db);
+ let range = first.text_range().cover(last.text_range());
+ if file_id != original_file.into() {
+ tracing::error!("Failed mapping up more for {:?}", range);
+ return None;
+ }
+ Some(FileRange { file_id: original_file, range })
+ }
+ _ if !self.file_id.is_macro() => Some(FileRange {
+ file_id: self.file_id.original_file(db),
+ range: self.value.text_range(),
+ }),
+ _ => None,
+ }
+ }
++
++ pub fn original_syntax_node(self, db: &dyn db::AstDatabase) -> Option<InFile<SyntaxNode>> {
++ // This kind of upmapping can only be achieved in attribute expanded files,
++ // as we don't have node inputs otherwise and therefor can't find an `N` node in the input
++ if !self.file_id.is_macro() {
++ return Some(self.map(Clone::clone));
++ } else if !self.file_id.is_attr_macro(db) {
++ return None;
++ }
++
++ if let Some(InFile { file_id, value: (first, last) }) = ascend_node_border_tokens(db, self)
++ {
++ if file_id.is_macro() {
++ let range = first.text_range().cover(last.text_range());
++ tracing::error!("Failed mapping out of macro file for {:?}", range);
++ return None;
++ }
++ // FIXME: This heuristic is brittle and with the right macro may select completely unrelated nodes
++ let anc = algo::least_common_ancestor(&first.parent()?, &last.parent()?)?;
++ let kind = self.value.kind();
++ let value = anc.ancestors().find(|it| it.kind() == kind)?;
++ return Some(InFile::new(file_id, value));
++ }
++ None
++ }
+}
+
+impl InFile<SyntaxToken> {
+ pub fn upmap(self, db: &dyn db::AstDatabase) -> Option<InFile<SyntaxToken>> {
+ let expansion = self.file_id.expansion_info(db)?;
+ expansion.map_token_up(db, self.as_ref()).map(|(it, _)| it)
+ }
+
+ /// Falls back to the macro call range if the node cannot be mapped up fully.
+ pub fn original_file_range(self, db: &dyn db::AstDatabase) -> FileRange {
+ match self.file_id.0 {
+ HirFileIdRepr::FileId(file_id) => FileRange { file_id, range: self.value.text_range() },
+ HirFileIdRepr::MacroFile(mac_file) => {
+ if let Some(res) = self.original_file_range_opt(db) {
+ return res;
+ }
+ // Fall back to whole macro call.
+ let loc = db.lookup_intern_macro_call(mac_file.macro_call_id);
+ loc.kind.original_call_range(db)
+ }
+ }
+ }
+
+ /// Attempts to map the syntax node back up its macro calls.
+ pub fn original_file_range_opt(self, db: &dyn db::AstDatabase) -> Option<FileRange> {
+ match self.file_id.0 {
+ HirFileIdRepr::FileId(file_id) => {
+ Some(FileRange { file_id, range: self.value.text_range() })
+ }
+ HirFileIdRepr::MacroFile(_) => {
+ let expansion = self.file_id.expansion_info(db)?;
+ let InFile { file_id, value } = ascend_call_token(db, &expansion, self)?;
+ let original_file = file_id.original_file(db);
+ if file_id != original_file.into() {
+ return None;
+ }
+ Some(FileRange { file_id: original_file, range: value.text_range() })
+ }
+ }
+ }
+
+ pub fn ancestors_with_macros(
+ self,
+ db: &dyn db::AstDatabase,
+ ) -> impl Iterator<Item = InFile<SyntaxNode>> + '_ {
+ self.value.parent().into_iter().flat_map({
+ let file_id = self.file_id;
+ move |parent| InFile::new(file_id, &parent).ancestors_with_macros(db)
+ })
+ }
+}
+
+fn ascend_node_border_tokens(
+ db: &dyn db::AstDatabase,
+ InFile { file_id, value: node }: InFile<&SyntaxNode>,
+) -> Option<InFile<(SyntaxToken, SyntaxToken)>> {
+ let expansion = file_id.expansion_info(db)?;
+
+ let first_token = |node: &SyntaxNode| skip_trivia_token(node.first_token()?, Direction::Next);
+ let last_token = |node: &SyntaxNode| skip_trivia_token(node.last_token()?, Direction::Prev);
+
+ let first = first_token(node)?;
+ let last = last_token(node)?;
+ let first = ascend_call_token(db, &expansion, InFile::new(file_id, first))?;
+ let last = ascend_call_token(db, &expansion, InFile::new(file_id, last))?;
+ (first.file_id == last.file_id).then(|| InFile::new(first.file_id, (first.value, last.value)))
+}
+
+fn ascend_call_token(
+ db: &dyn db::AstDatabase,
+ expansion: &ExpansionInfo,
+ token: InFile<SyntaxToken>,
+) -> Option<InFile<SyntaxToken>> {
+ let mut mapping = expansion.map_token_up(db, token.as_ref())?;
+ while let (mapped, Origin::Call) = mapping {
+ match mapped.file_id.expansion_info(db) {
+ Some(info) => mapping = info.map_token_up(db, mapped.as_ref())?,
+ None => return Some(mapped),
+ }
+ }
+ None
+}
+
+impl<N: AstNode> InFile<N> {
+ pub fn descendants<T: AstNode>(self) -> impl Iterator<Item = InFile<T>> {
+ self.value.syntax().descendants().filter_map(T::cast).map(move |n| self.with_value(n))
+ }
+
+ pub fn original_ast_node(self, db: &dyn db::AstDatabase) -> Option<InFile<N>> {
+ // This kind of upmapping can only be achieved in attribute expanded files,
+ // as we don't have node inputs otherwise and therefor can't find an `N` node in the input
+ if !self.file_id.is_macro() {
+ return Some(self);
+ } else if !self.file_id.is_attr_macro(db) {
+ return None;
+ }
+
+ if let Some(InFile { file_id, value: (first, last) }) =
+ ascend_node_border_tokens(db, self.syntax())
+ {
+ if file_id.is_macro() {
+ let range = first.text_range().cover(last.text_range());
+ tracing::error!("Failed mapping out of macro file for {:?}", range);
+ return None;
+ }
+ // FIXME: This heuristic is brittle and with the right macro may select completely unrelated nodes
+ let anc = algo::least_common_ancestor(&first.parent()?, &last.parent()?)?;
+ let value = anc.ancestors().find_map(N::cast)?;
+ return Some(InFile::new(file_id, value));
+ }
+ None
+ }
+
+ pub fn syntax(&self) -> InFile<&SyntaxNode> {
+ self.with_value(self.value.syntax())
+ }
+}
+
+/// In Rust, macros expand token trees to token trees. When we want to turn a
+/// token tree into an AST node, we need to figure out what kind of AST node we
+/// want: something like `foo` can be a type, an expression, or a pattern.
+///
+/// Naively, one would think that "what this expands to" is a property of a
+/// particular macro: macro `m1` returns an item, while macro `m2` returns an
+/// expression, etc. That's not the case -- macros are polymorphic in the
+/// result, and can expand to any type of the AST node.
+///
+/// What defines the actual AST node is the syntactic context of the macro
+/// invocation. As a contrived example, in `let T![*] = T![*];` the first `T`
+/// expands to a pattern, while the second one expands to an expression.
+///
+/// `ExpandTo` captures this bit of information about a particular macro call
+/// site.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum ExpandTo {
+ Statements,
+ Items,
+ Pattern,
+ Type,
+ Expr,
+}
+
+impl ExpandTo {
+ pub fn from_call_site(call: &ast::MacroCall) -> ExpandTo {
+ use syntax::SyntaxKind::*;
+
+ let syn = call.syntax();
+
+ let parent = match syn.parent() {
+ Some(it) => it,
+ None => return ExpandTo::Statements,
+ };
+
+ // FIXME: macros in statement position are treated as expression statements, they should
+ // probably be their own statement kind. The *grand*parent indicates what's valid.
+ if parent.kind() == MACRO_EXPR
+ && parent
+ .parent()
+ .map_or(false, |p| matches!(p.kind(), EXPR_STMT | STMT_LIST | MACRO_STMTS))
+ {
+ return ExpandTo::Statements;
+ }
+
+ match parent.kind() {
+ MACRO_ITEMS | SOURCE_FILE | ITEM_LIST => ExpandTo::Items,
+ MACRO_STMTS | EXPR_STMT | STMT_LIST => ExpandTo::Statements,
+ MACRO_PAT => ExpandTo::Pattern,
+ MACRO_TYPE => ExpandTo::Type,
+
+ ARG_LIST | ARRAY_EXPR | AWAIT_EXPR | BIN_EXPR | BREAK_EXPR | CALL_EXPR | CAST_EXPR
+ | CLOSURE_EXPR | FIELD_EXPR | FOR_EXPR | IF_EXPR | INDEX_EXPR | LET_EXPR
+ | MATCH_ARM | MATCH_EXPR | MATCH_GUARD | METHOD_CALL_EXPR | PAREN_EXPR | PATH_EXPR
+ | PREFIX_EXPR | RANGE_EXPR | RECORD_EXPR_FIELD | REF_EXPR | RETURN_EXPR | TRY_EXPR
+ | TUPLE_EXPR | WHILE_EXPR | MACRO_EXPR => ExpandTo::Expr,
+ _ => {
+ // Unknown , Just guess it is `Items`
+ ExpandTo::Items
+ }
+ }
+ }
+}
+
+#[derive(Debug)]
+pub struct UnresolvedMacro {
+ pub path: ModPath,
+}
--- /dev/null
+//! See [`Name`].
+
+use std::fmt;
+
+use syntax::{ast, SmolStr, SyntaxKind};
+
+/// `Name` is a wrapper around string, which is used in hir for both references
+/// and declarations. In theory, names should also carry hygiene info, but we are
+/// not there yet!
+///
+/// Note that `Name` holds and prints escaped name i.e. prefixed with "r#" when it
+/// is a raw identifier. Use [`unescaped()`][Name::unescaped] when you need the
+/// name without "r#".
+#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub struct Name(Repr);
+
+/// Wrapper of `Name` to print the name without "r#" even when it is a raw identifier.
+#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub struct UnescapedName<'a>(&'a Name);
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
+enum Repr {
+ Text(SmolStr),
+ TupleField(usize),
+}
+
+impl fmt::Display for Name {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match &self.0 {
+ Repr::Text(text) => fmt::Display::fmt(&text, f),
+ Repr::TupleField(idx) => fmt::Display::fmt(&idx, f),
+ }
+ }
+}
+
+fn is_raw_identifier(name: &str) -> bool {
+ let is_keyword = SyntaxKind::from_keyword(name).is_some();
+ is_keyword && !matches!(name, "self" | "crate" | "super" | "Self")
+}
+
+impl<'a> fmt::Display for UnescapedName<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match &self.0 .0 {
+ Repr::Text(text) => {
+ let text = text.strip_prefix("r#").unwrap_or(text);
+ fmt::Display::fmt(&text, f)
+ }
+ Repr::TupleField(idx) => fmt::Display::fmt(&idx, f),
+ }
+ }
+}
+
+impl<'a> UnescapedName<'a> {
+ /// Returns the textual representation of this name as a [`SmolStr`]. Prefer using this over
+ /// [`ToString::to_string`] if possible as this conversion is cheaper in the general case.
+ pub fn to_smol_str(&self) -> SmolStr {
+ match &self.0 .0 {
+ Repr::Text(it) => {
+ if let Some(stripped) = it.strip_prefix("r#") {
+ SmolStr::new(stripped)
+ } else {
+ it.clone()
+ }
+ }
+ Repr::TupleField(it) => SmolStr::new(&it.to_string()),
+ }
+ }
+}
+
+impl Name {
+ /// Note: this is private to make creating name from random string hard.
+ /// Hopefully, this should allow us to integrate hygiene cleaner in the
+ /// future, and to switch to interned representation of names.
+ const fn new_text(text: SmolStr) -> Name {
+ Name(Repr::Text(text))
+ }
+
+ pub fn new_tuple_field(idx: usize) -> Name {
+ Name(Repr::TupleField(idx))
+ }
+
+ pub fn new_lifetime(lt: &ast::Lifetime) -> Name {
+ Self::new_text(lt.text().into())
+ }
+
+ /// Shortcut to create inline plain text name
+ const fn new_inline(text: &str) -> Name {
+ Name::new_text(SmolStr::new_inline(text))
+ }
+
+ /// Resolve a name from the text of token.
+ fn resolve(raw_text: &str) -> Name {
+ match raw_text.strip_prefix("r#") {
+ // When `raw_text` starts with "r#" but the name does not coincide with any
+ // keyword, we never need the prefix so we strip it.
+ Some(text) if !is_raw_identifier(text) => Name::new_text(SmolStr::new(text)),
+ // Keywords (in the current edition) *can* be used as a name in earlier editions of
+ // Rust, e.g. "try" in Rust 2015. Even in such cases, we keep track of them in their
+ // escaped form.
+ None if is_raw_identifier(raw_text) => {
+ Name::new_text(SmolStr::from_iter(["r#", raw_text]))
+ }
+ _ => Name::new_text(raw_text.into()),
+ }
+ }
+
+ /// A fake name for things missing in the source code.
+ ///
+ /// For example, `impl Foo for {}` should be treated as a trait impl for a
+ /// type with a missing name. Similarly, `struct S { : u32 }` should have a
+ /// single field with a missing name.
+ ///
+ /// Ideally, we want a `gensym` semantics for missing names -- each missing
+ /// name is equal only to itself. It's not clear how to implement this in
+ /// salsa though, so we punt on that bit for a moment.
+ pub const fn missing() -> Name {
+ Name::new_inline("[missing name]")
+ }
+
+ /// Returns the tuple index this name represents if it is a tuple field.
+ pub fn as_tuple_index(&self) -> Option<usize> {
+ match self.0 {
+ Repr::TupleField(idx) => Some(idx),
+ _ => None,
+ }
+ }
+
+ /// Returns the text this name represents if it isn't a tuple field.
+ pub fn as_text(&self) -> Option<SmolStr> {
+ match &self.0 {
+ Repr::Text(it) => Some(it.clone()),
+ _ => None,
+ }
+ }
+
+ /// Returns the textual representation of this name as a [`SmolStr`].
+ /// Prefer using this over [`ToString::to_string`] if possible as this conversion is cheaper in
+ /// the general case.
+ pub fn to_smol_str(&self) -> SmolStr {
+ match &self.0 {
+ Repr::Text(it) => it.clone(),
+ Repr::TupleField(it) => SmolStr::new(&it.to_string()),
+ }
+ }
+
+ pub fn unescaped(&self) -> UnescapedName<'_> {
+ UnescapedName(self)
+ }
+
+ pub fn is_escaped(&self) -> bool {
+ match &self.0 {
+ Repr::Text(it) => it.starts_with("r#"),
+ Repr::TupleField(_) => false,
+ }
+ }
+}
+
+pub trait AsName {
+ fn as_name(&self) -> Name;
+}
+
+impl AsName for ast::NameRef {
+ fn as_name(&self) -> Name {
+ match self.as_tuple_field() {
+ Some(idx) => Name::new_tuple_field(idx),
+ None => Name::resolve(&self.text()),
+ }
+ }
+}
+
+impl AsName for ast::Name {
+ fn as_name(&self) -> Name {
+ Name::resolve(&self.text())
+ }
+}
+
+impl AsName for ast::NameOrNameRef {
+ fn as_name(&self) -> Name {
+ match self {
+ ast::NameOrNameRef::Name(it) => it.as_name(),
+ ast::NameOrNameRef::NameRef(it) => it.as_name(),
+ }
+ }
+}
+
+impl AsName for tt::Ident {
+ fn as_name(&self) -> Name {
+ Name::resolve(&self.text)
+ }
+}
+
+impl AsName for ast::FieldKind {
+ fn as_name(&self) -> Name {
+ match self {
+ ast::FieldKind::Name(nr) => nr.as_name(),
+ ast::FieldKind::Index(idx) => {
+ let idx = idx.text().parse::<usize>().unwrap_or(0);
+ Name::new_tuple_field(idx)
+ }
+ }
+ }
+}
+
+impl AsName for base_db::Dependency {
+ fn as_name(&self) -> Name {
+ Name::new_text(SmolStr::new(&*self.name))
+ }
+}
+
+pub mod known {
+ macro_rules! known_names {
+ ($($ident:ident),* $(,)?) => {
+ $(
+ #[allow(bad_style)]
+ pub const $ident: super::Name =
+ super::Name::new_inline(stringify!($ident));
+ )*
+ };
+ }
+
+ known_names!(
+ // Primitives
+ isize,
+ i8,
+ i16,
+ i32,
+ i64,
+ i128,
+ usize,
+ u8,
+ u16,
+ u32,
+ u64,
+ u128,
+ f32,
+ f64,
+ bool,
+ char,
+ str,
+ // Special names
+ macro_rules,
+ doc,
+ cfg,
+ cfg_attr,
+ register_attr,
+ register_tool,
+ // Components of known path (value or mod name)
+ std,
+ core,
+ alloc,
+ iter,
+ ops,
+ future,
+ result,
+ boxed,
+ option,
+ prelude,
+ rust_2015,
+ rust_2018,
+ rust_2021,
+ v1,
+ // Components of known path (type name)
+ Iterator,
+ IntoIterator,
+ Item,
++ IntoIter,
+ Try,
+ Ok,
+ Future,
+ IntoFuture,
+ Result,
+ Option,
+ Output,
+ Target,
+ Box,
+ RangeFrom,
+ RangeFull,
+ RangeInclusive,
+ RangeToInclusive,
+ RangeTo,
+ Range,
+ Neg,
+ Not,
+ None,
+ Index,
+ // Components of known path (function name)
+ filter_map,
+ next,
+ iter_mut,
+ len,
+ is_empty,
+ new,
+ // Builtin macros
+ asm,
+ assert,
+ column,
+ compile_error,
+ concat_idents,
+ concat_bytes,
+ concat,
+ const_format_args,
+ core_panic,
+ env,
+ file,
+ format_args_nl,
+ format_args,
+ global_asm,
+ include_bytes,
+ include_str,
+ include,
+ line,
+ llvm_asm,
+ log_syntax,
+ module_path,
+ option_env,
+ std_panic,
+ stringify,
+ trace_macros,
+ unreachable,
+ // Builtin derives
+ Copy,
+ Clone,
+ Default,
+ Debug,
+ Hash,
+ Ord,
+ PartialOrd,
+ Eq,
+ PartialEq,
+ // Builtin attributes
+ bench,
+ cfg_accessible,
+ cfg_eval,
+ crate_type,
+ derive,
+ global_allocator,
+ test,
+ test_case,
+ recursion_limit,
+ feature,
+ // Safe intrinsics
+ abort,
+ add_with_overflow,
+ black_box,
+ bitreverse,
+ bswap,
+ caller_location,
+ ctlz,
+ ctpop,
+ cttz,
+ discriminant_value,
+ forget,
+ likely,
+ maxnumf32,
+ maxnumf64,
+ min_align_of_val,
+ min_align_of,
+ minnumf32,
+ minnumf64,
+ mul_with_overflow,
+ needs_drop,
+ ptr_guaranteed_eq,
+ ptr_guaranteed_ne,
+ rotate_left,
+ rotate_right,
+ rustc_peek,
+ saturating_add,
+ saturating_sub,
+ size_of_val,
+ size_of,
+ sub_with_overflow,
+ type_id,
+ type_name,
+ unlikely,
+ variant_count,
+ wrapping_add,
+ wrapping_mul,
+ wrapping_sub,
+ // known methods of lang items
+ eq,
+ ne,
+ ge,
+ gt,
+ le,
+ lt,
+ // lang items
+ add_assign,
+ add,
+ bitand_assign,
+ bitand,
+ bitor_assign,
+ bitor,
+ bitxor_assign,
+ bitxor,
+ branch,
+ deref_mut,
+ deref,
+ div_assign,
+ div,
+ fn_mut,
+ fn_once,
+ future_trait,
+ index,
+ index_mut,
+ into_future,
+ mul_assign,
+ mul,
+ neg,
+ not,
+ owned_box,
+ partial_ord,
+ poll,
+ r#fn,
+ rem_assign,
+ rem,
+ shl_assign,
+ shl,
+ shr_assign,
+ shr,
+ sub_assign,
+ sub,
+ );
+
+ // self/Self cannot be used as an identifier
+ pub const SELF_PARAM: super::Name = super::Name::new_inline("self");
+ pub const SELF_TYPE: super::Name = super::Name::new_inline("Self");
+
+ pub const STATIC_LIFETIME: super::Name = super::Name::new_inline("'static");
+
+ #[macro_export]
+ macro_rules! name {
+ (self) => {
+ $crate::name::known::SELF_PARAM
+ };
+ (Self) => {
+ $crate::name::known::SELF_TYPE
+ };
+ ('static) => {
+ $crate::name::known::STATIC_LIFETIME
+ };
+ ($ident:ident) => {
+ $crate::name::known::$ident
+ };
+ }
+}
+
+pub use crate::name;
--- /dev/null
- let b = TyBuilder::assoc_type_projection(db, target);
+//! In certain situations, rust automatically inserts derefs as necessary: for
+//! example, field accesses `foo.bar` still work when `foo` is actually a
+//! reference to a type with the field `bar`. This is an approximation of the
+//! logic in rustc (which lives in rustc_hir_analysis/check/autoderef.rs).
+
+use std::sync::Arc;
+
+use chalk_ir::cast::Cast;
+use hir_expand::name::name;
+use limit::Limit;
+use syntax::SmolStr;
+
+use crate::{
+ db::HirDatabase, infer::unify::InferenceTable, Canonical, Goal, Interner, ProjectionTyExt,
+ TraitEnvironment, Ty, TyBuilder, TyKind,
+};
+
+static AUTODEREF_RECURSION_LIMIT: Limit = Limit::new(10);
+
+pub(crate) enum AutoderefKind {
+ Builtin,
+ Overloaded,
+}
+
+pub(crate) struct Autoderef<'a, 'db> {
+ pub(crate) table: &'a mut InferenceTable<'db>,
+ ty: Ty,
+ at_start: bool,
+ steps: Vec<(AutoderefKind, Ty)>,
+}
+
+impl<'a, 'db> Autoderef<'a, 'db> {
+ pub(crate) fn new(table: &'a mut InferenceTable<'db>, ty: Ty) -> Self {
+ let ty = table.resolve_ty_shallow(&ty);
+ Autoderef { table, ty, at_start: true, steps: Vec::new() }
+ }
+
+ pub(crate) fn step_count(&self) -> usize {
+ self.steps.len()
+ }
+
+ pub(crate) fn steps(&self) -> &[(AutoderefKind, Ty)] {
+ &self.steps
+ }
+
+ pub(crate) fn final_ty(&self) -> Ty {
+ self.ty.clone()
+ }
+}
+
+impl Iterator for Autoderef<'_, '_> {
+ type Item = (Ty, usize);
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if self.at_start {
+ self.at_start = false;
+ return Some((self.ty.clone(), 0));
+ }
+
+ if AUTODEREF_RECURSION_LIMIT.check(self.steps.len() + 1).is_err() {
+ return None;
+ }
+
+ let (kind, new_ty) = autoderef_step(self.table, self.ty.clone())?;
+
+ self.steps.push((kind, self.ty.clone()));
+ self.ty = new_ty;
+
+ Some((self.ty.clone(), self.step_count()))
+ }
+}
+
+pub(crate) fn autoderef_step(
+ table: &mut InferenceTable<'_>,
+ ty: Ty,
+) -> Option<(AutoderefKind, Ty)> {
+ if let Some(derefed) = builtin_deref(&ty) {
+ Some((AutoderefKind::Builtin, table.resolve_ty_shallow(derefed)))
+ } else {
+ Some((AutoderefKind::Overloaded, deref_by_trait(table, ty)?))
+ }
+}
+
+// FIXME: replace uses of this with Autoderef above
+pub fn autoderef<'a>(
+ db: &'a dyn HirDatabase,
+ env: Arc<TraitEnvironment>,
+ ty: Canonical<Ty>,
+) -> impl Iterator<Item = Canonical<Ty>> + 'a {
+ let mut table = InferenceTable::new(db, env);
+ let ty = table.instantiate_canonical(ty);
+ let mut autoderef = Autoderef::new(&mut table, ty);
+ let mut v = Vec::new();
+ while let Some((ty, _steps)) = autoderef.next() {
+ v.push(autoderef.table.canonicalize(ty).value);
+ }
+ v.into_iter()
+}
+
+pub(crate) fn deref(table: &mut InferenceTable<'_>, ty: Ty) -> Option<Ty> {
+ let _p = profile::span("deref");
+ autoderef_step(table, ty).map(|(_, ty)| ty)
+}
+
+fn builtin_deref(ty: &Ty) -> Option<&Ty> {
+ match ty.kind(Interner) {
+ TyKind::Ref(.., ty) | TyKind::Raw(.., ty) => Some(ty),
+ _ => None,
+ }
+}
+
+fn deref_by_trait(table: &mut InferenceTable<'_>, ty: Ty) -> Option<Ty> {
+ let _p = profile::span("deref_by_trait");
+ if table.resolve_ty_shallow(&ty).inference_var(Interner).is_some() {
+ // don't try to deref unknown variables
+ return None;
+ }
+
+ let db = table.db;
+ let deref_trait = db
+ .lang_item(table.trait_env.krate, SmolStr::new_inline("deref"))
+ .and_then(|l| l.as_trait())?;
+ let target = db.trait_data(deref_trait).associated_type_by_name(&name![Target])?;
+
+ let projection = {
- b.push(ty).build()
++ let b = TyBuilder::subst_for_def(db, deref_trait, None);
+ if b.remaining() != 1 {
+ // the Target type + Deref trait should only have one generic parameter,
+ // namely Deref's Self type
+ return None;
+ }
++ let deref_subst = b.push(ty).build();
++ TyBuilder::assoc_type_projection(db, target, Some(deref_subst)).build()
+ };
+
+ // Check that the type implements Deref at all
+ let trait_ref = projection.trait_ref(db);
+ let implements_goal: Goal = trait_ref.cast(Interner);
+ table.try_obligation(implements_goal.clone())?;
+
+ table.register_obligation(implements_goal);
+
+ let result = table.normalize_projection_ty(projection);
+ Some(table.resolve_ty_shallow(&result))
+}
--- /dev/null
- AdtId, BoundVar, DebruijnIndex, Scalar,
+//! `TyBuilder`, a helper for building instances of `Ty` and related types.
+
+use std::iter;
+
+use chalk_ir::{
+ cast::{Cast, CastTo, Caster},
+ fold::TypeFoldable,
+ interner::HasInterner,
- builtin_type::BuiltinType, generics::TypeOrConstParamData, ConstParamId, GenericDefId, TraitId,
- TypeAliasId,
++ AdtId, DebruijnIndex, Scalar,
+};
+use hir_def::{
- to_assoc_type_id, to_chalk_trait_id, utils::generics, Binders, CallableSig, ConstData,
- ConstValue, GenericArg, GenericArgData, Interner, ProjectionTy, Substitution, TraitRef, Ty,
- TyDefId, TyExt, TyKind, ValueTyDefId,
++ builtin_type::BuiltinType, generics::TypeOrConstParamData, ConstParamId, DefWithBodyId,
++ GenericDefId, TraitId, TypeAliasId,
+};
+use smallvec::SmallVec;
+
+use crate::{
+ consteval::unknown_const_as_generic, db::HirDatabase, infer::unify::InferenceTable, primitive,
- TyBuilder { data, param_kinds: self.param_kinds, vec: self.vec }
++ to_assoc_type_id, to_chalk_trait_id, utils::generics, Binders, BoundVar, CallableSig,
++ GenericArg, Interner, ProjectionTy, Substitution, TraitRef, Ty, TyDefId, TyExt, TyKind,
++ ValueTyDefId,
+};
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum ParamKind {
+ Type,
+ Const(Ty),
+}
+
+/// This is a builder for `Ty` or anything that needs a `Substitution`.
+pub struct TyBuilder<D> {
+ /// The `data` field is used to keep track of what we're building (e.g. an
+ /// ADT, a `TraitRef`, ...).
+ data: D,
+ vec: SmallVec<[GenericArg; 2]>,
+ param_kinds: SmallVec<[ParamKind; 2]>,
++ parent_subst: Substitution,
+}
+
+impl<A> TyBuilder<A> {
+ fn with_data<B>(self, data: B) -> TyBuilder<B> {
- fn new(data: D, param_kinds: SmallVec<[ParamKind; 2]>) -> TyBuilder<D> {
- TyBuilder { data, vec: SmallVec::with_capacity(param_kinds.len()), param_kinds }
++ TyBuilder {
++ data,
++ vec: self.vec,
++ param_kinds: self.param_kinds,
++ parent_subst: self.parent_subst,
++ }
+ }
+}
+
+impl<D> TyBuilder<D> {
- let subst = Substitution::from_iter(Interner, self.vec);
++ fn new(
++ data: D,
++ param_kinds: SmallVec<[ParamKind; 2]>,
++ parent_subst: Option<Substitution>,
++ ) -> Self {
++ let parent_subst = parent_subst.unwrap_or_else(|| Substitution::empty(Interner));
++ Self { data, vec: SmallVec::with_capacity(param_kinds.len()), param_kinds, parent_subst }
++ }
++
++ fn new_empty(data: D) -> Self {
++ TyBuilder::new(data, SmallVec::new(), None)
+ }
+
+ fn build_internal(self) -> (D, Substitution) {
+ assert_eq!(self.vec.len(), self.param_kinds.len());
+ for (a, e) in self.vec.iter().zip(self.param_kinds.iter()) {
+ self.assert_match_kind(a, e);
+ }
- let other = this.param_kinds.iter().skip(this.vec.len());
++ let subst = Substitution::from_iter(
++ Interner,
++ self.vec.into_iter().chain(self.parent_subst.iter(Interner).cloned()),
++ );
+ (self.data, subst)
+ }
+
+ pub fn push(mut self, arg: impl CastTo<GenericArg>) -> Self {
++ assert!(self.remaining() > 0);
+ let arg = arg.cast(Interner);
+ let expected_kind = &self.param_kinds[self.vec.len()];
++
+ let arg_kind = match arg.data(Interner) {
+ chalk_ir::GenericArgData::Ty(_) => ParamKind::Type,
+ chalk_ir::GenericArgData::Lifetime(_) => panic!("Got lifetime in TyBuilder::push"),
+ chalk_ir::GenericArgData::Const(c) => {
+ let c = c.data(Interner);
+ ParamKind::Const(c.ty.clone())
+ }
+ };
+ assert_eq!(*expected_kind, arg_kind);
++
+ self.vec.push(arg);
++
+ self
+ }
+
+ pub fn remaining(&self) -> usize {
+ self.param_kinds.len() - self.vec.len()
+ }
+
+ pub fn fill_with_bound_vars(self, debruijn: DebruijnIndex, starting_from: usize) -> Self {
+ // self.fill is inlined to make borrow checker happy
+ let mut this = self;
- ParamKind::Type => {
- GenericArgData::Ty(TyKind::BoundVar(BoundVar::new(debruijn, idx)).intern(Interner))
- .intern(Interner)
++ let other = &this.param_kinds[this.vec.len()..];
+ let filler = (starting_from..).zip(other).map(|(idx, kind)| match kind {
- ParamKind::Const(ty) => GenericArgData::Const(
- ConstData {
- value: ConstValue::BoundVar(BoundVar::new(debruijn, idx)),
- ty: ty.clone(),
- }
- .intern(Interner),
- )
- .intern(Interner),
++ ParamKind::Type => BoundVar::new(debruijn, idx).to_ty(Interner).cast(Interner),
++ ParamKind::Const(ty) => {
++ BoundVar::new(debruijn, idx).to_const(Interner, ty.clone()).cast(Interner)
+ }
- let filler = this.param_kinds.iter().skip(this.vec.len()).map(|x| match x {
- ParamKind::Type => GenericArgData::Ty(TyKind::Error.intern(Interner)).intern(Interner),
+ });
+ this.vec.extend(filler.take(this.remaining()).casted(Interner));
+ assert_eq!(this.remaining(), 0);
+ this
+ }
+
+ pub fn fill_with_unknown(self) -> Self {
+ // self.fill is inlined to make borrow checker happy
+ let mut this = self;
- ParamKind::Type => GenericArgData::Ty(table.new_type_var()).intern(Interner),
- ParamKind::Const(ty) => {
- GenericArgData::Const(table.new_const_var(ty.clone())).intern(Interner)
- }
++ let filler = this.param_kinds[this.vec.len()..].iter().map(|x| match x {
++ ParamKind::Type => TyKind::Error.intern(Interner).cast(Interner),
+ ParamKind::Const(ty) => unknown_const_as_generic(ty.clone()),
+ });
+ this.vec.extend(filler.casted(Interner));
+ assert_eq!(this.remaining(), 0);
+ this
+ }
+
+ pub(crate) fn fill_with_inference_vars(self, table: &mut InferenceTable<'_>) -> Self {
+ self.fill(|x| match x {
- self.vec.extend(self.param_kinds.iter().skip(self.vec.len()).map(filler));
++ ParamKind::Type => table.new_type_var().cast(Interner),
++ ParamKind::Const(ty) => table.new_const_var(ty.clone()).cast(Interner),
+ })
+ }
+
+ pub fn fill(mut self, filler: impl FnMut(&ParamKind) -> GenericArg) -> Self {
- pub fn use_parent_substs(mut self, parent_substs: &Substitution) -> Self {
- assert!(self.vec.is_empty());
- assert!(parent_substs.len(Interner) <= self.param_kinds.len());
- self.extend(parent_substs.iter(Interner).cloned());
- self
- }
-
- fn extend(&mut self, it: impl Iterator<Item = GenericArg> + Clone) {
- for x in it.clone().zip(self.param_kinds.iter().skip(self.vec.len())) {
- self.assert_match_kind(&x.0, &x.1);
- }
- self.vec.extend(it);
- }
-
++ self.vec.extend(self.param_kinds[self.vec.len()..].iter().map(filler));
+ assert_eq!(self.remaining(), 0);
+ self
+ }
+
- pub fn subst_for_def(db: &dyn HirDatabase, def: impl Into<GenericDefId>) -> TyBuilder<()> {
- let def = def.into();
- let params = generics(db.upcast(), def);
- TyBuilder::new(
- (),
- params
- .iter()
- .map(|(id, data)| match data {
- TypeOrConstParamData::TypeParamData(_) => ParamKind::Type,
- TypeOrConstParamData::ConstParamData(_) => {
- ParamKind::Const(db.const_param_ty(ConstParamId::from_unchecked(id)))
- }
- })
- .collect(),
- )
+ fn assert_match_kind(&self, a: &chalk_ir::GenericArg<Interner>, e: &ParamKind) {
+ match (a.data(Interner), e) {
+ (chalk_ir::GenericArgData::Ty(_), ParamKind::Type)
+ | (chalk_ir::GenericArgData::Const(_), ParamKind::Const(_)) => (),
+ _ => panic!("Mismatched kinds: {:?}, {:?}, {:?}", a, self.vec, self.param_kinds),
+ }
+ }
+}
+
+impl TyBuilder<()> {
+ pub fn unit() -> Ty {
+ TyKind::Tuple(0, Substitution::empty(Interner)).intern(Interner)
+ }
+
+ pub fn usize() -> Ty {
+ TyKind::Scalar(chalk_ir::Scalar::Uint(chalk_ir::UintTy::Usize)).intern(Interner)
+ }
+
+ pub fn fn_ptr(sig: CallableSig) -> Ty {
+ TyKind::Function(sig.to_fn_ptr()).intern(Interner)
+ }
+
+ pub fn builtin(builtin: BuiltinType) -> Ty {
+ match builtin {
+ BuiltinType::Char => TyKind::Scalar(Scalar::Char).intern(Interner),
+ BuiltinType::Bool => TyKind::Scalar(Scalar::Bool).intern(Interner),
+ BuiltinType::Str => TyKind::Str.intern(Interner),
+ BuiltinType::Int(t) => {
+ TyKind::Scalar(Scalar::Int(primitive::int_ty_from_builtin(t))).intern(Interner)
+ }
+ BuiltinType::Uint(t) => {
+ TyKind::Scalar(Scalar::Uint(primitive::uint_ty_from_builtin(t))).intern(Interner)
+ }
+ BuiltinType::Float(t) => {
+ TyKind::Scalar(Scalar::Float(primitive::float_ty_from_builtin(t))).intern(Interner)
+ }
+ }
+ }
+
+ pub fn slice(argument: Ty) -> Ty {
+ TyKind::Slice(argument).intern(Interner)
+ }
+
+ pub fn placeholder_subst(db: &dyn HirDatabase, def: impl Into<GenericDefId>) -> Substitution {
+ let params = generics(db.upcast(), def.into());
+ params.placeholder_subst(db)
+ }
+
- TyBuilder::subst_for_def(db, def).with_data(def)
++ pub fn subst_for_def(
++ db: &dyn HirDatabase,
++ def: impl Into<GenericDefId>,
++ parent_subst: Option<Substitution>,
++ ) -> TyBuilder<()> {
++ let generics = generics(db.upcast(), def.into());
++ assert!(generics.parent_generics().is_some() == parent_subst.is_some());
++ let params = generics
++ .iter_self()
++ .map(|(id, data)| match data {
++ TypeOrConstParamData::TypeParamData(_) => ParamKind::Type,
++ TypeOrConstParamData::ConstParamData(_) => {
++ ParamKind::Const(db.const_param_ty(ConstParamId::from_unchecked(id)))
++ }
++ })
++ .collect();
++ TyBuilder::new((), params, parent_subst)
++ }
++
++ /// Creates a `TyBuilder` to build `Substitution` for a generator defined in `parent`.
++ ///
++ /// A generator's substitution consists of:
++ /// - resume type of generator
++ /// - yield type of generator ([`Generator::Yield`](std::ops::Generator::Yield))
++ /// - return type of generator ([`Generator::Return`](std::ops::Generator::Return))
++ /// - generic parameters in scope on `parent`
++ /// in this order.
++ ///
++ /// This method prepopulates the builder with placeholder substitution of `parent`, so you
++ /// should only push exactly 3 `GenericArg`s before building.
++ pub fn subst_for_generator(db: &dyn HirDatabase, parent: DefWithBodyId) -> TyBuilder<()> {
++ let parent_subst =
++ parent.as_generic_def_id().map(|p| generics(db.upcast(), p).placeholder_subst(db));
++ // These represent resume type, yield type, and return type of generator.
++ let params = std::iter::repeat(ParamKind::Type).take(3).collect();
++ TyBuilder::new((), params, parent_subst)
+ }
+
+ pub fn build(self) -> Substitution {
+ let ((), subst) = self.build_internal();
+ subst
+ }
+}
+
+impl TyBuilder<hir_def::AdtId> {
+ pub fn adt(db: &dyn HirDatabase, def: hir_def::AdtId) -> TyBuilder<hir_def::AdtId> {
- if let GenericArgData::Ty(x) = default_ty.skip_binders().data(Interner) {
++ TyBuilder::subst_for_def(db, def, None).with_data(def)
+ }
+
+ pub fn fill_with_defaults(
+ mut self,
+ db: &dyn HirDatabase,
+ mut fallback: impl FnMut() -> Ty,
+ ) -> Self {
++ // Note that we're building ADT, so we never have parent generic parameters.
+ let defaults = db.generic_defaults(self.data.into());
++ let dummy_ty = TyKind::Error.intern(Interner).cast(Interner);
+ for default_ty in defaults.iter().skip(self.vec.len()) {
- };
- // each default can depend on the previous parameters
- let subst_so_far = Substitution::from_iter(Interner, self.vec.clone());
++ // NOTE(skip_binders): we only check if the arg type is error type.
++ if let Some(x) = default_ty.skip_binders().ty(Interner) {
+ if x.is_unknown() {
+ self.vec.push(fallback().cast(Interner));
+ continue;
+ }
- TyBuilder::new(Tuple(size), iter::repeat(ParamKind::Type).take(size).collect())
++ }
++ // Each default can only depend on the previous parameters.
++ // FIXME: we don't handle const generics here.
++ let subst_so_far = Substitution::from_iter(
++ Interner,
++ self.vec
++ .iter()
++ .cloned()
++ .chain(iter::repeat(dummy_ty.clone()))
++ .take(self.param_kinds.len()),
++ );
+ self.vec.push(default_ty.clone().substitute(Interner, &subst_so_far).cast(Interner));
+ }
+ self
+ }
+
+ pub fn build(self) -> Ty {
+ let (adt, subst) = self.build_internal();
+ TyKind::Adt(AdtId(adt), subst).intern(Interner)
+ }
+}
+
+pub struct Tuple(usize);
+impl TyBuilder<Tuple> {
+ pub fn tuple(size: usize) -> TyBuilder<Tuple> {
- TyBuilder::subst_for_def(db, def).with_data(def)
++ TyBuilder::new(Tuple(size), iter::repeat(ParamKind::Type).take(size).collect(), None)
+ }
+
+ pub fn build(self) -> Ty {
+ let (Tuple(size), subst) = self.build_internal();
+ TyKind::Tuple(size, subst).intern(Interner)
+ }
+}
+
+impl TyBuilder<TraitId> {
+ pub fn trait_ref(db: &dyn HirDatabase, def: TraitId) -> TyBuilder<TraitId> {
- pub fn assoc_type_projection(db: &dyn HirDatabase, def: TypeAliasId) -> TyBuilder<TypeAliasId> {
- TyBuilder::subst_for_def(db, def).with_data(def)
++ TyBuilder::subst_for_def(db, def, None).with_data(def)
+ }
+
+ pub fn build(self) -> TraitRef {
+ let (trait_id, substitution) = self.build_internal();
+ TraitRef { trait_id: to_chalk_trait_id(trait_id), substitution }
+ }
+}
+
+impl TyBuilder<TypeAliasId> {
- fn subst_binders(b: Binders<T>) -> Self {
- let param_kinds = b
- .binders
- .iter(Interner)
- .map(|x| match x {
- chalk_ir::VariableKind::Ty(_) => ParamKind::Type,
- chalk_ir::VariableKind::Lifetime => panic!("Got lifetime parameter"),
- chalk_ir::VariableKind::Const(ty) => ParamKind::Const(ty.clone()),
- })
- .collect();
- TyBuilder::new(b, param_kinds)
- }
-
++ pub fn assoc_type_projection(
++ db: &dyn HirDatabase,
++ def: TypeAliasId,
++ parent_subst: Option<Substitution>,
++ ) -> TyBuilder<TypeAliasId> {
++ TyBuilder::subst_for_def(db, def, parent_subst).with_data(def)
+ }
+
+ pub fn build(self) -> ProjectionTy {
+ let (type_alias, substitution) = self.build_internal();
+ ProjectionTy { associated_ty_id: to_assoc_type_id(type_alias), substitution }
+ }
+}
+
+impl<T: HasInterner<Interner = Interner> + TypeFoldable<Interner>> TyBuilder<Binders<T>> {
- pub fn def_ty(db: &dyn HirDatabase, def: TyDefId) -> TyBuilder<Binders<Ty>> {
- TyBuilder::subst_binders(db.ty(def))
+ pub fn build(self) -> T {
+ let (b, subst) = self.build_internal();
+ b.substitute(Interner, &subst)
+ }
+}
+
+impl TyBuilder<Binders<Ty>> {
- TyBuilder::subst_binders(db.impl_self_ty(def))
++ pub fn def_ty(
++ db: &dyn HirDatabase,
++ def: TyDefId,
++ parent_subst: Option<Substitution>,
++ ) -> TyBuilder<Binders<Ty>> {
++ let poly_ty = db.ty(def);
++ let id: GenericDefId = match def {
++ TyDefId::BuiltinType(_) => {
++ assert!(parent_subst.is_none());
++ return TyBuilder::new_empty(poly_ty);
++ }
++ TyDefId::AdtId(id) => id.into(),
++ TyDefId::TypeAliasId(id) => id.into(),
++ };
++ TyBuilder::subst_for_def(db, id, parent_subst).with_data(poly_ty)
+ }
+
+ pub fn impl_self_ty(db: &dyn HirDatabase, def: hir_def::ImplId) -> TyBuilder<Binders<Ty>> {
- pub fn value_ty(db: &dyn HirDatabase, def: ValueTyDefId) -> TyBuilder<Binders<Ty>> {
- TyBuilder::subst_binders(db.value_ty(def))
++ TyBuilder::subst_for_def(db, def, None).with_data(db.impl_self_ty(def))
+ }
+
++ pub fn value_ty(
++ db: &dyn HirDatabase,
++ def: ValueTyDefId,
++ parent_subst: Option<Substitution>,
++ ) -> TyBuilder<Binders<Ty>> {
++ let poly_value_ty = db.value_ty(def);
++ let id = match def.to_generic_def_id() {
++ Some(id) => id,
++ None => {
++ // static items
++ assert!(parent_subst.is_none());
++ return TyBuilder::new_empty(poly_value_ty);
++ }
++ };
++ TyBuilder::subst_for_def(db, id, parent_subst).with_data(poly_value_ty)
+ }
+}
--- /dev/null
- AliasEq, AliasTy, BoundVar, CallableDefId, DebruijnIndex, FnDefId, Interner, ProjectionTy,
- ProjectionTyExt, QuantifiedWhereClause, Substitution, TraitRef, TraitRefExt, Ty, TyBuilder,
- TyExt, TyKind, WhereClause,
+//! The implementation of `RustIrDatabase` for Chalk, which provides information
+//! about the code that Chalk needs.
+use std::sync::Arc;
+
+use cov_mark::hit;
+use syntax::SmolStr;
+use tracing::debug;
+
+use chalk_ir::{cast::Cast, fold::shift::Shift, CanonicalVarKinds};
+use chalk_solve::rust_ir::{self, OpaqueTyDatumBound, WellKnownTrait};
+
+use base_db::CrateId;
+use hir_def::{
++ expr::Movability,
+ lang_item::{lang_attr, LangItemTarget},
+ AssocItemId, GenericDefId, HasModule, ItemContainerId, Lookup, ModuleId, TypeAliasId,
+};
+use hir_expand::name::name;
+
+use crate::{
+ db::HirDatabase,
+ display::HirDisplay,
+ from_assoc_type_id, from_chalk_trait_id, from_foreign_def_id, make_binders,
+ make_single_type_binders,
+ mapping::{from_chalk, ToChalk, TypeAliasAsValue},
+ method_resolution::{TraitImpls, TyFingerprint, ALL_FLOAT_FPS, ALL_INT_FPS},
+ to_assoc_type_id, to_chalk_trait_id,
+ traits::ChalkContext,
+ utils::generics,
- _: chalk_ir::GeneratorId<Interner>,
++ wrap_empty_binders, AliasEq, AliasTy, BoundVar, CallableDefId, DebruijnIndex, FnDefId,
++ Interner, ProjectionTy, ProjectionTyExt, QuantifiedWhereClause, Substitution, TraitRef,
++ TraitRefExt, Ty, TyBuilder, TyExt, TyKind, WhereClause,
+};
+
+pub(crate) type AssociatedTyDatum = chalk_solve::rust_ir::AssociatedTyDatum<Interner>;
+pub(crate) type TraitDatum = chalk_solve::rust_ir::TraitDatum<Interner>;
+pub(crate) type StructDatum = chalk_solve::rust_ir::AdtDatum<Interner>;
+pub(crate) type ImplDatum = chalk_solve::rust_ir::ImplDatum<Interner>;
+pub(crate) type OpaqueTyDatum = chalk_solve::rust_ir::OpaqueTyDatum<Interner>;
+
+pub(crate) type AssocTypeId = chalk_ir::AssocTypeId<Interner>;
+pub(crate) type TraitId = chalk_ir::TraitId<Interner>;
+pub(crate) type AdtId = chalk_ir::AdtId<Interner>;
+pub(crate) type ImplId = chalk_ir::ImplId<Interner>;
+pub(crate) type AssociatedTyValueId = chalk_solve::rust_ir::AssociatedTyValueId<Interner>;
+pub(crate) type AssociatedTyValue = chalk_solve::rust_ir::AssociatedTyValue<Interner>;
+pub(crate) type FnDefDatum = chalk_solve::rust_ir::FnDefDatum<Interner>;
+pub(crate) type Variances = chalk_ir::Variances<Interner>;
+
+impl<'a> chalk_solve::RustIrDatabase<Interner> for ChalkContext<'a> {
+ fn associated_ty_data(&self, id: AssocTypeId) -> Arc<AssociatedTyDatum> {
+ self.db.associated_ty_data(id)
+ }
+ fn trait_datum(&self, trait_id: TraitId) -> Arc<TraitDatum> {
+ self.db.trait_datum(self.krate, trait_id)
+ }
+ fn adt_datum(&self, struct_id: AdtId) -> Arc<StructDatum> {
+ self.db.struct_datum(self.krate, struct_id)
+ }
+ fn adt_repr(&self, _struct_id: AdtId) -> Arc<rust_ir::AdtRepr<Interner>> {
+ // FIXME: keep track of these
+ Arc::new(rust_ir::AdtRepr { c: false, packed: false, int: None })
+ }
+ fn discriminant_type(&self, _ty: chalk_ir::Ty<Interner>) -> chalk_ir::Ty<Interner> {
+ // FIXME: keep track of this
+ chalk_ir::TyKind::Scalar(chalk_ir::Scalar::Uint(chalk_ir::UintTy::U32)).intern(Interner)
+ }
+ fn impl_datum(&self, impl_id: ImplId) -> Arc<ImplDatum> {
+ self.db.impl_datum(self.krate, impl_id)
+ }
+
+ fn fn_def_datum(
+ &self,
+ fn_def_id: chalk_ir::FnDefId<Interner>,
+ ) -> Arc<rust_ir::FnDefDatum<Interner>> {
+ self.db.fn_def_datum(self.krate, fn_def_id)
+ }
+
+ fn impls_for_trait(
+ &self,
+ trait_id: TraitId,
+ parameters: &[chalk_ir::GenericArg<Interner>],
+ binders: &CanonicalVarKinds<Interner>,
+ ) -> Vec<ImplId> {
+ debug!("impls_for_trait {:?}", trait_id);
+ let trait_: hir_def::TraitId = from_chalk_trait_id(trait_id);
+
+ let ty: Ty = parameters[0].assert_ty_ref(Interner).clone();
+
+ fn binder_kind(
+ ty: &Ty,
+ binders: &CanonicalVarKinds<Interner>,
+ ) -> Option<chalk_ir::TyVariableKind> {
+ if let TyKind::BoundVar(bv) = ty.kind(Interner) {
+ let binders = binders.as_slice(Interner);
+ if bv.debruijn == DebruijnIndex::INNERMOST {
+ if let chalk_ir::VariableKind::Ty(tk) = binders[bv.index].kind {
+ return Some(tk);
+ }
+ }
+ }
+ None
+ }
+
+ let self_ty_fp = TyFingerprint::for_trait_impl(&ty);
+ let fps: &[TyFingerprint] = match binder_kind(&ty, binders) {
+ Some(chalk_ir::TyVariableKind::Integer) => &ALL_INT_FPS,
+ Some(chalk_ir::TyVariableKind::Float) => &ALL_FLOAT_FPS,
+ _ => self_ty_fp.as_ref().map(std::slice::from_ref).unwrap_or(&[]),
+ };
+
+ fn local_impls(db: &dyn HirDatabase, module: ModuleId) -> Option<Arc<TraitImpls>> {
+ let block = module.containing_block()?;
+ hit!(block_local_impls);
+ db.trait_impls_in_block(block)
+ }
+
+ // Note: Since we're using impls_for_trait, only impls where the trait
+ // can be resolved should ever reach Chalk. impl_datum relies on that
+ // and will panic if the trait can't be resolved.
+ let in_deps = self.db.trait_impls_in_deps(self.krate);
+ let in_self = self.db.trait_impls_in_crate(self.krate);
+ let trait_module = trait_.module(self.db.upcast());
+ let type_module = match self_ty_fp {
+ Some(TyFingerprint::Adt(adt_id)) => Some(adt_id.module(self.db.upcast())),
+ Some(TyFingerprint::ForeignType(type_id)) => {
+ Some(from_foreign_def_id(type_id).module(self.db.upcast()))
+ }
+ Some(TyFingerprint::Dyn(trait_id)) => Some(trait_id.module(self.db.upcast())),
+ _ => None,
+ };
+ let impl_maps = [
+ Some(in_deps),
+ Some(in_self),
+ local_impls(self.db, trait_module),
+ type_module.and_then(|m| local_impls(self.db, m)),
+ ];
+
+ let id_to_chalk = |id: hir_def::ImplId| id.to_chalk(self.db);
+
+ let result: Vec<_> = if fps.is_empty() {
+ debug!("Unrestricted search for {:?} impls...", trait_);
+ impl_maps
+ .iter()
+ .filter_map(|o| o.as_ref())
+ .flat_map(|impls| impls.for_trait(trait_).map(id_to_chalk))
+ .collect()
+ } else {
+ impl_maps
+ .iter()
+ .filter_map(|o| o.as_ref())
+ .flat_map(|impls| {
+ fps.iter().flat_map(move |fp| {
+ impls.for_trait_and_self_ty(trait_, *fp).map(id_to_chalk)
+ })
+ })
+ .collect()
+ };
+
+ debug!("impls_for_trait returned {} impls", result.len());
+ result
+ }
+ fn impl_provided_for(&self, auto_trait_id: TraitId, kind: &chalk_ir::TyKind<Interner>) -> bool {
+ debug!("impl_provided_for {:?}, {:?}", auto_trait_id, kind);
+ false // FIXME
+ }
+ fn associated_ty_value(&self, id: AssociatedTyValueId) -> Arc<AssociatedTyValue> {
+ self.db.associated_ty_value(self.krate, id)
+ }
+
+ fn custom_clauses(&self) -> Vec<chalk_ir::ProgramClause<Interner>> {
+ vec![]
+ }
+ fn local_impls_to_coherence_check(&self, _trait_id: TraitId) -> Vec<ImplId> {
+ // We don't do coherence checking (yet)
+ unimplemented!()
+ }
+ fn interner(&self) -> Interner {
+ Interner
+ }
+ fn well_known_trait_id(
+ &self,
+ well_known_trait: rust_ir::WellKnownTrait,
+ ) -> Option<chalk_ir::TraitId<Interner>> {
+ let lang_attr = lang_attr_from_well_known_trait(well_known_trait);
+ let trait_ = match self.db.lang_item(self.krate, lang_attr.into()) {
+ Some(LangItemTarget::TraitId(trait_)) => trait_,
+ _ => return None,
+ };
+ Some(to_chalk_trait_id(trait_))
+ }
+
+ fn program_clauses_for_env(
+ &self,
+ environment: &chalk_ir::Environment<Interner>,
+ ) -> chalk_ir::ProgramClauses<Interner> {
+ self.db.program_clauses_for_chalk_env(self.krate, environment.clone())
+ }
+
+ fn opaque_ty_data(&self, id: chalk_ir::OpaqueTyId<Interner>) -> Arc<OpaqueTyDatum> {
+ let full_id = self.db.lookup_intern_impl_trait_id(id.into());
+ let bound = match full_id {
+ crate::ImplTraitId::ReturnTypeImplTrait(func, idx) => {
+ let datas = self
+ .db
+ .return_type_impl_traits(func)
+ .expect("impl trait id without impl traits");
+ let (datas, binders) = (*datas).as_ref().into_value_and_skipped_binders();
+ let data = &datas.impl_traits[idx as usize];
+ let bound = OpaqueTyDatumBound {
+ bounds: make_single_type_binders(data.bounds.skip_binders().to_vec()),
+ where_clauses: chalk_ir::Binders::empty(Interner, vec![]),
+ };
+ chalk_ir::Binders::new(binders, bound)
+ }
+ crate::ImplTraitId::AsyncBlockTypeImplTrait(..) => {
+ if let Some((future_trait, future_output)) = self
+ .db
+ .lang_item(self.krate, SmolStr::new_inline("future_trait"))
+ .and_then(|item| item.as_trait())
+ .and_then(|trait_| {
+ let alias =
+ self.db.trait_data(trait_).associated_type_by_name(&name![Output])?;
+ Some((trait_, alias))
+ })
+ {
+ // Making up Symbol’s value as variable is void: AsyncBlock<T>:
+ //
+ // |--------------------OpaqueTyDatum-------------------|
+ // |-------------OpaqueTyDatumBound--------------|
+ // for<T> <Self> [Future<Self>, Future::Output<Self> = T]
+ // ^1 ^0 ^0 ^0 ^1
+ let impl_bound = WhereClause::Implemented(TraitRef {
+ trait_id: to_chalk_trait_id(future_trait),
+ // Self type as the first parameter.
+ substitution: Substitution::from1(
+ Interner,
+ TyKind::BoundVar(BoundVar {
+ debruijn: DebruijnIndex::INNERMOST,
+ index: 0,
+ })
+ .intern(Interner),
+ ),
+ });
+ let mut binder = vec![];
+ binder.push(crate::wrap_empty_binders(impl_bound));
+ let sized_trait = self
+ .db
+ .lang_item(self.krate, SmolStr::new_inline("sized"))
+ .and_then(|item| item.as_trait());
+ if let Some(sized_trait_) = sized_trait {
+ let sized_bound = WhereClause::Implemented(TraitRef {
+ trait_id: to_chalk_trait_id(sized_trait_),
+ // Self type as the first parameter.
+ substitution: Substitution::from1(
+ Interner,
+ TyKind::BoundVar(BoundVar {
+ debruijn: DebruijnIndex::INNERMOST,
+ index: 0,
+ })
+ .intern(Interner),
+ ),
+ });
+ binder.push(crate::wrap_empty_binders(sized_bound));
+ }
+ let proj_bound = WhereClause::AliasEq(AliasEq {
+ alias: AliasTy::Projection(ProjectionTy {
+ associated_ty_id: to_assoc_type_id(future_output),
+ // Self type as the first parameter.
+ substitution: Substitution::from1(
+ Interner,
+ TyKind::BoundVar(BoundVar::new(DebruijnIndex::INNERMOST, 0))
+ .intern(Interner),
+ ),
+ }),
+ // The parameter of the opaque type.
+ ty: TyKind::BoundVar(BoundVar { debruijn: DebruijnIndex::ONE, index: 0 })
+ .intern(Interner),
+ });
+ binder.push(crate::wrap_empty_binders(proj_bound));
+ let bound = OpaqueTyDatumBound {
+ bounds: make_single_type_binders(binder),
+ where_clauses: chalk_ir::Binders::empty(Interner, vec![]),
+ };
+ // The opaque type has 1 parameter.
+ make_single_type_binders(bound)
+ } else {
+ // If failed to find Symbol’s value as variable is void: Future::Output, return empty bounds as fallback.
+ let bound = OpaqueTyDatumBound {
+ bounds: chalk_ir::Binders::empty(Interner, vec![]),
+ where_clauses: chalk_ir::Binders::empty(Interner, vec![]),
+ };
+ // The opaque type has 1 parameter.
+ make_single_type_binders(bound)
+ }
+ }
+ };
+
+ Arc::new(OpaqueTyDatum { opaque_ty_id: id, bound })
+ }
+
+ fn hidden_opaque_type(&self, _id: chalk_ir::OpaqueTyId<Interner>) -> chalk_ir::Ty<Interner> {
+ // FIXME: actually provide the hidden type; it is relevant for auto traits
+ TyKind::Error.intern(Interner)
+ }
+
+ fn is_object_safe(&self, _trait_id: chalk_ir::TraitId<Interner>) -> bool {
+ // FIXME: implement actual object safety
+ true
+ }
+
+ fn closure_kind(
+ &self,
+ _closure_id: chalk_ir::ClosureId<Interner>,
+ _substs: &chalk_ir::Substitution<Interner>,
+ ) -> rust_ir::ClosureKind {
+ // Fn is the closure kind that implements all three traits
+ rust_ir::ClosureKind::Fn
+ }
+ fn closure_inputs_and_output(
+ &self,
+ _closure_id: chalk_ir::ClosureId<Interner>,
+ substs: &chalk_ir::Substitution<Interner>,
+ ) -> chalk_ir::Binders<rust_ir::FnDefInputsAndOutputDatum<Interner>> {
+ let sig_ty = substs.at(Interner, 0).assert_ty_ref(Interner).clone();
+ let sig = &sig_ty.callable_sig(self.db).expect("first closure param should be fn ptr");
+ let io = rust_ir::FnDefInputsAndOutputDatum {
+ argument_types: sig.params().to_vec(),
+ return_type: sig.ret().clone(),
+ };
+ chalk_ir::Binders::empty(Interner, io.shifted_in(Interner))
+ }
+ fn closure_upvars(
+ &self,
+ _closure_id: chalk_ir::ClosureId<Interner>,
+ _substs: &chalk_ir::Substitution<Interner>,
+ ) -> chalk_ir::Binders<chalk_ir::Ty<Interner>> {
+ let ty = TyBuilder::unit();
+ chalk_ir::Binders::empty(Interner, ty)
+ }
+ fn closure_fn_substitution(
+ &self,
+ _closure_id: chalk_ir::ClosureId<Interner>,
+ _substs: &chalk_ir::Substitution<Interner>,
+ ) -> chalk_ir::Substitution<Interner> {
+ Substitution::empty(Interner)
+ }
+
+ fn trait_name(&self, trait_id: chalk_ir::TraitId<Interner>) -> String {
+ let id = from_chalk_trait_id(trait_id);
+ self.db.trait_data(id).name.to_string()
+ }
+ fn adt_name(&self, chalk_ir::AdtId(adt_id): AdtId) -> String {
+ match adt_id {
+ hir_def::AdtId::StructId(id) => self.db.struct_data(id).name.to_string(),
+ hir_def::AdtId::EnumId(id) => self.db.enum_data(id).name.to_string(),
+ hir_def::AdtId::UnionId(id) => self.db.union_data(id).name.to_string(),
+ }
+ }
+ fn adt_size_align(&self, _id: chalk_ir::AdtId<Interner>) -> Arc<rust_ir::AdtSizeAlign> {
+ // FIXME
+ Arc::new(rust_ir::AdtSizeAlign::from_one_zst(false))
+ }
+ fn assoc_type_name(&self, assoc_ty_id: chalk_ir::AssocTypeId<Interner>) -> String {
+ let id = self.db.associated_ty_data(assoc_ty_id).name;
+ self.db.type_alias_data(id).name.to_string()
+ }
+ fn opaque_type_name(&self, opaque_ty_id: chalk_ir::OpaqueTyId<Interner>) -> String {
+ format!("Opaque_{}", opaque_ty_id.0)
+ }
+ fn fn_def_name(&self, fn_def_id: chalk_ir::FnDefId<Interner>) -> String {
+ format!("fn_{}", fn_def_id.0)
+ }
+ fn generator_datum(
+ &self,
- // FIXME
- unimplemented!()
++ id: chalk_ir::GeneratorId<Interner>,
+ ) -> std::sync::Arc<chalk_solve::rust_ir::GeneratorDatum<Interner>> {
- _: chalk_ir::GeneratorId<Interner>,
++ let (parent, expr) = self.db.lookup_intern_generator(id.into());
++
++ // We fill substitution with unknown type, because we only need to know whether the generic
++ // params are types or consts to build `Binders` and those being filled up are for
++ // `resume_type`, `yield_type`, and `return_type` of the generator in question.
++ let subst = TyBuilder::subst_for_generator(self.db, parent).fill_with_unknown().build();
++
++ let input_output = rust_ir::GeneratorInputOutputDatum {
++ resume_type: TyKind::BoundVar(BoundVar::new(DebruijnIndex::INNERMOST, 0))
++ .intern(Interner),
++ yield_type: TyKind::BoundVar(BoundVar::new(DebruijnIndex::INNERMOST, 1))
++ .intern(Interner),
++ return_type: TyKind::BoundVar(BoundVar::new(DebruijnIndex::INNERMOST, 2))
++ .intern(Interner),
++ // FIXME: calculate upvars
++ upvars: vec![],
++ };
++
++ let it = subst
++ .iter(Interner)
++ .map(|it| it.constant(Interner).map(|c| c.data(Interner).ty.clone()));
++ let input_output = crate::make_type_and_const_binders(it, input_output);
++
++ let movability = match self.db.body(parent)[expr] {
++ hir_def::expr::Expr::Closure {
++ closure_kind: hir_def::expr::ClosureKind::Generator(movability),
++ ..
++ } => movability,
++ _ => unreachable!("non generator expression interned as generator"),
++ };
++ let movability = match movability {
++ Movability::Static => rust_ir::Movability::Static,
++ Movability::Movable => rust_ir::Movability::Movable,
++ };
++
++ Arc::new(rust_ir::GeneratorDatum { movability, input_output })
+ }
+ fn generator_witness_datum(
+ &self,
- // FIXME
- unimplemented!()
++ id: chalk_ir::GeneratorId<Interner>,
+ ) -> std::sync::Arc<chalk_solve::rust_ir::GeneratorWitnessDatum<Interner>> {
- let pro_ty = TyBuilder::assoc_type_projection(db, type_alias)
++ // FIXME: calculate inner types
++ let inner_types =
++ rust_ir::GeneratorWitnessExistential { types: wrap_empty_binders(vec![]) };
++
++ let (parent, _) = self.db.lookup_intern_generator(id.into());
++ // See the comment in `generator_datum()` for unknown types.
++ let subst = TyBuilder::subst_for_generator(self.db, parent).fill_with_unknown().build();
++ let it = subst
++ .iter(Interner)
++ .map(|it| it.constant(Interner).map(|c| c.data(Interner).ty.clone()));
++ let inner_types = crate::make_type_and_const_binders(it, inner_types);
++
++ Arc::new(rust_ir::GeneratorWitnessDatum { inner_types })
+ }
+
+ fn unification_database(&self) -> &dyn chalk_ir::UnificationDatabase<Interner> {
+ &self.db
+ }
+}
+
+impl<'a> chalk_ir::UnificationDatabase<Interner> for &'a dyn HirDatabase {
+ fn fn_def_variance(
+ &self,
+ fn_def_id: chalk_ir::FnDefId<Interner>,
+ ) -> chalk_ir::Variances<Interner> {
+ HirDatabase::fn_def_variance(*self, fn_def_id)
+ }
+
+ fn adt_variance(&self, adt_id: chalk_ir::AdtId<Interner>) -> chalk_ir::Variances<Interner> {
+ HirDatabase::adt_variance(*self, adt_id)
+ }
+}
+
+pub(crate) fn program_clauses_for_chalk_env_query(
+ db: &dyn HirDatabase,
+ krate: CrateId,
+ environment: chalk_ir::Environment<Interner>,
+) -> chalk_ir::ProgramClauses<Interner> {
+ chalk_solve::program_clauses_for_env(&ChalkContext { db, krate }, &environment)
+}
+
+pub(crate) fn associated_ty_data_query(
+ db: &dyn HirDatabase,
+ id: AssocTypeId,
+) -> Arc<AssociatedTyDatum> {
+ debug!("associated_ty_data {:?}", id);
+ let type_alias: TypeAliasId = from_assoc_type_id(id);
+ let trait_ = match type_alias.lookup(db.upcast()).container {
+ ItemContainerId::TraitId(t) => t,
+ _ => panic!("associated type not in trait"),
+ };
+
+ // Lower bounds -- we could/should maybe move this to a separate query in `lower`
+ let type_alias_data = db.type_alias_data(type_alias);
+ let generic_params = generics(db.upcast(), type_alias.into());
+ // let bound_vars = generic_params.bound_vars_subst(DebruijnIndex::INNERMOST);
+ let resolver = hir_def::resolver::HasResolver::resolver(type_alias, db.upcast());
+ let ctx = crate::TyLoweringContext::new(db, &resolver)
+ .with_type_param_mode(crate::lower::ParamLoweringMode::Variable);
++
++ let trait_subst = TyBuilder::subst_for_def(db, trait_, None)
++ .fill_with_bound_vars(crate::DebruijnIndex::INNERMOST, generic_params.len_self())
++ .build();
++ let pro_ty = TyBuilder::assoc_type_projection(db, type_alias, Some(trait_subst))
+ .fill_with_bound_vars(crate::DebruijnIndex::INNERMOST, 0)
+ .build();
+ let self_ty = TyKind::Alias(AliasTy::Projection(pro_ty)).intern(Interner);
++
+ let mut bounds: Vec<_> = type_alias_data
+ .bounds
+ .iter()
+ .flat_map(|bound| ctx.lower_type_bound(bound, self_ty.clone(), false))
+ .filter_map(|pred| generic_predicate_to_inline_bound(db, &pred, &self_ty))
+ .collect();
+
+ if !ctx.unsized_types.borrow().contains(&self_ty) {
+ let sized_trait = db
+ .lang_item(resolver.krate(), SmolStr::new_inline("sized"))
+ .and_then(|lang_item| lang_item.as_trait().map(to_chalk_trait_id));
+ let sized_bound = sized_trait.into_iter().map(|sized_trait| {
+ let trait_bound =
+ rust_ir::TraitBound { trait_id: sized_trait, args_no_self: Default::default() };
+ let inline_bound = rust_ir::InlineBound::TraitBound(trait_bound);
+ chalk_ir::Binders::empty(Interner, inline_bound)
+ });
+ bounds.extend(sized_bound);
+ bounds.shrink_to_fit();
+ }
+
+ // FIXME: Re-enable where clauses on associated types when an upstream chalk bug is fixed.
+ // (rust-analyzer#9052)
+ // let where_clauses = convert_where_clauses(db, type_alias.into(), &bound_vars);
+ let bound_data = rust_ir::AssociatedTyDatumBound { bounds, where_clauses: vec![] };
+ let datum = AssociatedTyDatum {
+ trait_id: to_chalk_trait_id(trait_),
+ id,
+ name: type_alias,
+ binders: make_binders(db, &generic_params, bound_data),
+ };
+ Arc::new(datum)
+}
+
+pub(crate) fn trait_datum_query(
+ db: &dyn HirDatabase,
+ krate: CrateId,
+ trait_id: TraitId,
+) -> Arc<TraitDatum> {
+ debug!("trait_datum {:?}", trait_id);
+ let trait_ = from_chalk_trait_id(trait_id);
+ let trait_data = db.trait_data(trait_);
+ debug!("trait {:?} = {:?}", trait_id, trait_data.name);
+ let generic_params = generics(db.upcast(), trait_.into());
+ let bound_vars = generic_params.bound_vars_subst(db, DebruijnIndex::INNERMOST);
+ let flags = rust_ir::TraitFlags {
+ auto: trait_data.is_auto,
+ upstream: trait_.lookup(db.upcast()).container.krate() != krate,
+ non_enumerable: true,
+ coinductive: false, // only relevant for Chalk testing
+ // FIXME: set these flags correctly
+ marker: false,
+ fundamental: false,
+ };
+ let where_clauses = convert_where_clauses(db, trait_.into(), &bound_vars);
+ let associated_ty_ids = trait_data.associated_types().map(to_assoc_type_id).collect();
+ let trait_datum_bound = rust_ir::TraitDatumBound { where_clauses };
+ let well_known =
+ lang_attr(db.upcast(), trait_).and_then(|name| well_known_trait_from_lang_attr(&name));
+ let trait_datum = TraitDatum {
+ id: trait_id,
+ binders: make_binders(db, &generic_params, trait_datum_bound),
+ flags,
+ associated_ty_ids,
+ well_known,
+ };
+ Arc::new(trait_datum)
+}
+
+fn well_known_trait_from_lang_attr(name: &str) -> Option<WellKnownTrait> {
+ Some(match name {
+ "clone" => WellKnownTrait::Clone,
+ "coerce_unsized" => WellKnownTrait::CoerceUnsized,
+ "copy" => WellKnownTrait::Copy,
+ "discriminant_kind" => WellKnownTrait::DiscriminantKind,
+ "dispatch_from_dyn" => WellKnownTrait::DispatchFromDyn,
+ "drop" => WellKnownTrait::Drop,
+ "fn" => WellKnownTrait::Fn,
+ "fn_mut" => WellKnownTrait::FnMut,
+ "fn_once" => WellKnownTrait::FnOnce,
+ "generator" => WellKnownTrait::Generator,
+ "sized" => WellKnownTrait::Sized,
+ "unpin" => WellKnownTrait::Unpin,
+ "unsize" => WellKnownTrait::Unsize,
+ _ => return None,
+ })
+}
+
+fn lang_attr_from_well_known_trait(attr: WellKnownTrait) -> &'static str {
+ match attr {
+ WellKnownTrait::Clone => "clone",
+ WellKnownTrait::CoerceUnsized => "coerce_unsized",
+ WellKnownTrait::Copy => "copy",
+ WellKnownTrait::DiscriminantKind => "discriminant_kind",
+ WellKnownTrait::DispatchFromDyn => "dispatch_from_dyn",
+ WellKnownTrait::Drop => "drop",
+ WellKnownTrait::Fn => "fn",
+ WellKnownTrait::FnMut => "fn_mut",
+ WellKnownTrait::FnOnce => "fn_once",
+ WellKnownTrait::Generator => "generator",
+ WellKnownTrait::Sized => "sized",
+ WellKnownTrait::Unpin => "unpin",
+ WellKnownTrait::Unsize => "unsize",
+ }
+}
+
+pub(crate) fn struct_datum_query(
+ db: &dyn HirDatabase,
+ krate: CrateId,
+ struct_id: AdtId,
+) -> Arc<StructDatum> {
+ debug!("struct_datum {:?}", struct_id);
+ let chalk_ir::AdtId(adt_id) = struct_id;
+ let generic_params = generics(db.upcast(), adt_id.into());
+ let upstream = adt_id.module(db.upcast()).krate() != krate;
+ let where_clauses = {
+ let generic_params = generics(db.upcast(), adt_id.into());
+ let bound_vars = generic_params.bound_vars_subst(db, DebruijnIndex::INNERMOST);
+ convert_where_clauses(db, adt_id.into(), &bound_vars)
+ };
+ let flags = rust_ir::AdtFlags {
+ upstream,
+ // FIXME set fundamental and phantom_data flags correctly
+ fundamental: false,
+ phantom_data: false,
+ };
+ // FIXME provide enum variants properly (for auto traits)
+ let variant = rust_ir::AdtVariantDatum {
+ fields: Vec::new(), // FIXME add fields (only relevant for auto traits),
+ };
+ let struct_datum_bound = rust_ir::AdtDatumBound { variants: vec![variant], where_clauses };
+ let struct_datum = StructDatum {
+ // FIXME set ADT kind
+ kind: rust_ir::AdtKind::Struct,
+ id: struct_id,
+ binders: make_binders(db, &generic_params, struct_datum_bound),
+ flags,
+ };
+ Arc::new(struct_datum)
+}
+
+pub(crate) fn impl_datum_query(
+ db: &dyn HirDatabase,
+ krate: CrateId,
+ impl_id: ImplId,
+) -> Arc<ImplDatum> {
+ let _p = profile::span("impl_datum");
+ debug!("impl_datum {:?}", impl_id);
+ let impl_: hir_def::ImplId = from_chalk(db, impl_id);
+ impl_def_datum(db, krate, impl_id, impl_)
+}
+
+fn impl_def_datum(
+ db: &dyn HirDatabase,
+ krate: CrateId,
+ chalk_id: ImplId,
+ impl_id: hir_def::ImplId,
+) -> Arc<ImplDatum> {
+ let trait_ref = db
+ .impl_trait(impl_id)
+ // ImplIds for impls where the trait ref can't be resolved should never reach Chalk
+ .expect("invalid impl passed to Chalk")
+ .into_value_and_skipped_binders()
+ .0;
+ let impl_data = db.impl_data(impl_id);
+
+ let generic_params = generics(db.upcast(), impl_id.into());
+ let bound_vars = generic_params.bound_vars_subst(db, DebruijnIndex::INNERMOST);
+ let trait_ = trait_ref.hir_trait_id();
+ let impl_type = if impl_id.lookup(db.upcast()).container.krate() == krate {
+ rust_ir::ImplType::Local
+ } else {
+ rust_ir::ImplType::External
+ };
+ let where_clauses = convert_where_clauses(db, impl_id.into(), &bound_vars);
+ let negative = impl_data.is_negative;
+ debug!(
+ "impl {:?}: {}{} where {:?}",
+ chalk_id,
+ if negative { "!" } else { "" },
+ trait_ref.display(db),
+ where_clauses
+ );
+
+ let polarity = if negative { rust_ir::Polarity::Negative } else { rust_ir::Polarity::Positive };
+
+ let impl_datum_bound = rust_ir::ImplDatumBound { trait_ref, where_clauses };
+ let trait_data = db.trait_data(trait_);
+ let associated_ty_value_ids = impl_data
+ .items
+ .iter()
+ .filter_map(|item| match item {
+ AssocItemId::TypeAliasId(type_alias) => Some(*type_alias),
+ _ => None,
+ })
+ .filter(|&type_alias| {
+ // don't include associated types that don't exist in the trait
+ let name = &db.type_alias_data(type_alias).name;
+ trait_data.associated_type_by_name(name).is_some()
+ })
+ .map(|type_alias| TypeAliasAsValue(type_alias).to_chalk(db))
+ .collect();
+ debug!("impl_datum: {:?}", impl_datum_bound);
+ let impl_datum = ImplDatum {
+ binders: make_binders(db, &generic_params, impl_datum_bound),
+ impl_type,
+ polarity,
+ associated_ty_value_ids,
+ };
+ Arc::new(impl_datum)
+}
+
+pub(crate) fn associated_ty_value_query(
+ db: &dyn HirDatabase,
+ krate: CrateId,
+ id: AssociatedTyValueId,
+) -> Arc<AssociatedTyValue> {
+ let type_alias: TypeAliasAsValue = from_chalk(db, id);
+ type_alias_associated_ty_value(db, krate, type_alias.0)
+}
+
+fn type_alias_associated_ty_value(
+ db: &dyn HirDatabase,
+ _krate: CrateId,
+ type_alias: TypeAliasId,
+) -> Arc<AssociatedTyValue> {
+ let type_alias_data = db.type_alias_data(type_alias);
+ let impl_id = match type_alias.lookup(db.upcast()).container {
+ ItemContainerId::ImplId(it) => it,
+ _ => panic!("assoc ty value should be in impl"),
+ };
+
+ let trait_ref = db
+ .impl_trait(impl_id)
+ .expect("assoc ty value should not exist")
+ .into_value_and_skipped_binders()
+ .0; // we don't return any assoc ty values if the impl'd trait can't be resolved
+
+ let assoc_ty = db
+ .trait_data(trait_ref.hir_trait_id())
+ .associated_type_by_name(&type_alias_data.name)
+ .expect("assoc ty value should not exist"); // validated when building the impl data as well
+ let (ty, binders) = db.ty(type_alias.into()).into_value_and_skipped_binders();
+ let value_bound = rust_ir::AssociatedTyValueBound { ty };
+ let value = rust_ir::AssociatedTyValue {
+ impl_id: impl_id.to_chalk(db),
+ associated_ty_id: to_assoc_type_id(assoc_ty),
+ value: chalk_ir::Binders::new(binders, value_bound),
+ };
+ Arc::new(value)
+}
+
+pub(crate) fn fn_def_datum_query(
+ db: &dyn HirDatabase,
+ _krate: CrateId,
+ fn_def_id: FnDefId,
+) -> Arc<FnDefDatum> {
+ let callable_def: CallableDefId = from_chalk(db, fn_def_id);
+ let generic_params = generics(db.upcast(), callable_def.into());
+ let (sig, binders) = db.callable_item_signature(callable_def).into_value_and_skipped_binders();
+ let bound_vars = generic_params.bound_vars_subst(db, DebruijnIndex::INNERMOST);
+ let where_clauses = convert_where_clauses(db, callable_def.into(), &bound_vars);
+ let bound = rust_ir::FnDefDatumBound {
+ // Note: Chalk doesn't actually use this information yet as far as I am aware, but we provide it anyway
+ inputs_and_output: chalk_ir::Binders::empty(
+ Interner,
+ rust_ir::FnDefInputsAndOutputDatum {
+ argument_types: sig.params().to_vec(),
+ return_type: sig.ret().clone(),
+ }
+ .shifted_in(Interner),
+ ),
+ where_clauses,
+ };
+ let datum = FnDefDatum {
+ id: fn_def_id,
+ sig: chalk_ir::FnSig { abi: (), safety: chalk_ir::Safety::Safe, variadic: sig.is_varargs },
+ binders: chalk_ir::Binders::new(binders, bound),
+ };
+ Arc::new(datum)
+}
+
+pub(crate) fn fn_def_variance_query(db: &dyn HirDatabase, fn_def_id: FnDefId) -> Variances {
+ let callable_def: CallableDefId = from_chalk(db, fn_def_id);
+ let generic_params = generics(db.upcast(), callable_def.into());
+ Variances::from_iter(
+ Interner,
+ std::iter::repeat(chalk_ir::Variance::Invariant).take(generic_params.len()),
+ )
+}
+
+pub(crate) fn adt_variance_query(
+ db: &dyn HirDatabase,
+ chalk_ir::AdtId(adt_id): AdtId,
+) -> Variances {
+ let generic_params = generics(db.upcast(), adt_id.into());
+ Variances::from_iter(
+ Interner,
+ std::iter::repeat(chalk_ir::Variance::Invariant).take(generic_params.len()),
+ )
+}
+
+pub(super) fn convert_where_clauses(
+ db: &dyn HirDatabase,
+ def: GenericDefId,
+ substs: &Substitution,
+) -> Vec<chalk_ir::QuantifiedWhereClause<Interner>> {
+ let generic_predicates = db.generic_predicates(def);
+ let mut result = Vec::with_capacity(generic_predicates.len());
+ for pred in generic_predicates.iter() {
+ result.push(pred.clone().substitute(Interner, substs));
+ }
+ result
+}
+
+pub(super) fn generic_predicate_to_inline_bound(
+ db: &dyn HirDatabase,
+ pred: &QuantifiedWhereClause,
+ self_ty: &Ty,
+) -> Option<chalk_ir::Binders<rust_ir::InlineBound<Interner>>> {
+ // An InlineBound is like a GenericPredicate, except the self type is left out.
+ // We don't have a special type for this, but Chalk does.
+ let self_ty_shifted_in = self_ty.clone().shifted_in_from(Interner, DebruijnIndex::ONE);
+ let (pred, binders) = pred.as_ref().into_value_and_skipped_binders();
+ match pred {
+ WhereClause::Implemented(trait_ref) => {
+ if trait_ref.self_type_parameter(Interner) != self_ty_shifted_in {
+ // we can only convert predicates back to type bounds if they
+ // have the expected self type
+ return None;
+ }
+ let args_no_self = trait_ref.substitution.as_slice(Interner)[1..]
+ .iter()
+ .map(|ty| ty.clone().cast(Interner))
+ .collect();
+ let trait_bound = rust_ir::TraitBound { trait_id: trait_ref.trait_id, args_no_self };
+ Some(chalk_ir::Binders::new(binders, rust_ir::InlineBound::TraitBound(trait_bound)))
+ }
+ WhereClause::AliasEq(AliasEq { alias: AliasTy::Projection(projection_ty), ty }) => {
+ if projection_ty.self_type_parameter(Interner) != self_ty_shifted_in {
+ return None;
+ }
+ let trait_ = projection_ty.trait_(db);
+ let args_no_self = projection_ty.substitution.as_slice(Interner)[1..]
+ .iter()
+ .map(|ty| ty.clone().cast(Interner))
+ .collect();
+ let alias_eq_bound = rust_ir::AliasEqBound {
+ value: ty.clone(),
+ trait_bound: rust_ir::TraitBound {
+ trait_id: to_chalk_trait_id(trait_),
+ args_no_self,
+ },
+ associated_ty_id: projection_ty.associated_ty_id,
+ parameters: Vec::new(), // FIXME we don't support generic associated types yet
+ };
+ Some(chalk_ir::Binders::new(
+ binders,
+ rust_ir::InlineBound::AliasEqBound(alias_eq_bound),
+ ))
+ }
+ _ => None,
+ }
+}
--- /dev/null
- Some(sig.substitute(Interner, ¶meters))
+//! Various extensions traits for Chalk types.
+
+use chalk_ir::{FloatTy, IntTy, Mutability, Scalar, UintTy};
+use hir_def::{
+ builtin_type::{BuiltinFloat, BuiltinInt, BuiltinType, BuiltinUint},
+ generics::TypeOrConstParamData,
+ type_ref::Rawness,
+ FunctionId, GenericDefId, HasModule, ItemContainerId, Lookup, TraitId,
+};
+use syntax::SmolStr;
+
+use crate::{
+ db::HirDatabase, from_assoc_type_id, from_chalk_trait_id, from_foreign_def_id,
+ from_placeholder_idx, to_chalk_trait_id, AdtId, AliasEq, AliasTy, Binders, CallableDefId,
+ CallableSig, FnPointer, ImplTraitId, Interner, Lifetime, ProjectionTy, QuantifiedWhereClause,
+ Substitution, TraitRef, Ty, TyBuilder, TyKind, WhereClause,
+};
+
+pub trait TyExt {
+ fn is_unit(&self) -> bool;
+ fn is_never(&self) -> bool;
+ fn is_unknown(&self) -> bool;
+ fn is_ty_var(&self) -> bool;
+
+ fn as_adt(&self) -> Option<(hir_def::AdtId, &Substitution)>;
+ fn as_builtin(&self) -> Option<BuiltinType>;
+ fn as_tuple(&self) -> Option<&Substitution>;
+ fn as_fn_def(&self, db: &dyn HirDatabase) -> Option<FunctionId>;
+ fn as_reference(&self) -> Option<(&Ty, Lifetime, Mutability)>;
+ fn as_reference_or_ptr(&self) -> Option<(&Ty, Rawness, Mutability)>;
+ fn as_generic_def(&self, db: &dyn HirDatabase) -> Option<GenericDefId>;
+
+ fn callable_def(&self, db: &dyn HirDatabase) -> Option<CallableDefId>;
+ fn callable_sig(&self, db: &dyn HirDatabase) -> Option<CallableSig>;
+
+ fn strip_references(&self) -> &Ty;
+ fn strip_reference(&self) -> &Ty;
+
+ /// If this is a `dyn Trait`, returns that trait.
+ fn dyn_trait(&self) -> Option<TraitId>;
+
+ fn impl_trait_bounds(&self, db: &dyn HirDatabase) -> Option<Vec<QuantifiedWhereClause>>;
+ fn associated_type_parent_trait(&self, db: &dyn HirDatabase) -> Option<TraitId>;
+
+ /// FIXME: Get rid of this, it's not a good abstraction
+ fn equals_ctor(&self, other: &Ty) -> bool;
+}
+
+impl TyExt for Ty {
+ fn is_unit(&self) -> bool {
+ matches!(self.kind(Interner), TyKind::Tuple(0, _))
+ }
+
+ fn is_never(&self) -> bool {
+ matches!(self.kind(Interner), TyKind::Never)
+ }
+
+ fn is_unknown(&self) -> bool {
+ matches!(self.kind(Interner), TyKind::Error)
+ }
+
+ fn is_ty_var(&self) -> bool {
+ matches!(self.kind(Interner), TyKind::InferenceVar(_, _))
+ }
+
+ fn as_adt(&self) -> Option<(hir_def::AdtId, &Substitution)> {
+ match self.kind(Interner) {
+ TyKind::Adt(AdtId(adt), parameters) => Some((*adt, parameters)),
+ _ => None,
+ }
+ }
+
+ fn as_builtin(&self) -> Option<BuiltinType> {
+ match self.kind(Interner) {
+ TyKind::Str => Some(BuiltinType::Str),
+ TyKind::Scalar(Scalar::Bool) => Some(BuiltinType::Bool),
+ TyKind::Scalar(Scalar::Char) => Some(BuiltinType::Char),
+ TyKind::Scalar(Scalar::Float(fty)) => Some(BuiltinType::Float(match fty {
+ FloatTy::F64 => BuiltinFloat::F64,
+ FloatTy::F32 => BuiltinFloat::F32,
+ })),
+ TyKind::Scalar(Scalar::Int(ity)) => Some(BuiltinType::Int(match ity {
+ IntTy::Isize => BuiltinInt::Isize,
+ IntTy::I8 => BuiltinInt::I8,
+ IntTy::I16 => BuiltinInt::I16,
+ IntTy::I32 => BuiltinInt::I32,
+ IntTy::I64 => BuiltinInt::I64,
+ IntTy::I128 => BuiltinInt::I128,
+ })),
+ TyKind::Scalar(Scalar::Uint(ity)) => Some(BuiltinType::Uint(match ity {
+ UintTy::Usize => BuiltinUint::Usize,
+ UintTy::U8 => BuiltinUint::U8,
+ UintTy::U16 => BuiltinUint::U16,
+ UintTy::U32 => BuiltinUint::U32,
+ UintTy::U64 => BuiltinUint::U64,
+ UintTy::U128 => BuiltinUint::U128,
+ })),
+ _ => None,
+ }
+ }
+
+ fn as_tuple(&self) -> Option<&Substitution> {
+ match self.kind(Interner) {
+ TyKind::Tuple(_, substs) => Some(substs),
+ _ => None,
+ }
+ }
+
+ fn as_fn_def(&self, db: &dyn HirDatabase) -> Option<FunctionId> {
+ match self.callable_def(db) {
+ Some(CallableDefId::FunctionId(func)) => Some(func),
+ Some(CallableDefId::StructId(_) | CallableDefId::EnumVariantId(_)) | None => None,
+ }
+ }
+ fn as_reference(&self) -> Option<(&Ty, Lifetime, Mutability)> {
+ match self.kind(Interner) {
+ TyKind::Ref(mutability, lifetime, ty) => Some((ty, lifetime.clone(), *mutability)),
+ _ => None,
+ }
+ }
+
+ fn as_reference_or_ptr(&self) -> Option<(&Ty, Rawness, Mutability)> {
+ match self.kind(Interner) {
+ TyKind::Ref(mutability, _, ty) => Some((ty, Rawness::Ref, *mutability)),
+ TyKind::Raw(mutability, ty) => Some((ty, Rawness::RawPtr, *mutability)),
+ _ => None,
+ }
+ }
+
+ fn as_generic_def(&self, db: &dyn HirDatabase) -> Option<GenericDefId> {
+ match *self.kind(Interner) {
+ TyKind::Adt(AdtId(adt), ..) => Some(adt.into()),
+ TyKind::FnDef(callable, ..) => {
+ Some(db.lookup_intern_callable_def(callable.into()).into())
+ }
+ TyKind::AssociatedType(type_alias, ..) => Some(from_assoc_type_id(type_alias).into()),
+ TyKind::Foreign(type_alias, ..) => Some(from_foreign_def_id(type_alias).into()),
+ _ => None,
+ }
+ }
+
+ fn callable_def(&self, db: &dyn HirDatabase) -> Option<CallableDefId> {
+ match self.kind(Interner) {
+ &TyKind::FnDef(def, ..) => Some(db.lookup_intern_callable_def(def.into())),
+ _ => None,
+ }
+ }
+
+ fn callable_sig(&self, db: &dyn HirDatabase) -> Option<CallableSig> {
+ match self.kind(Interner) {
+ TyKind::Function(fn_ptr) => Some(CallableSig::from_fn_ptr(fn_ptr)),
+ TyKind::FnDef(def, parameters) => {
+ let callable_def = db.lookup_intern_callable_def((*def).into());
+ let sig = db.callable_item_signature(callable_def);
++ Some(sig.substitute(Interner, parameters))
+ }
+ TyKind::Closure(.., substs) => {
+ let sig_param = substs.at(Interner, 0).assert_ty_ref(Interner);
+ sig_param.callable_sig(db)
+ }
+ _ => None,
+ }
+ }
+
+ fn dyn_trait(&self) -> Option<TraitId> {
+ let trait_ref = match self.kind(Interner) {
+ // The principal trait bound should be the first element of the bounds. This is an
+ // invariant ensured by `TyLoweringContext::lower_dyn_trait()`.
++ // FIXME: dyn types may not have principal trait and we don't want to return auto trait
++ // here.
+ TyKind::Dyn(dyn_ty) => dyn_ty.bounds.skip_binders().interned().get(0).and_then(|b| {
+ match b.skip_binders() {
+ WhereClause::Implemented(trait_ref) => Some(trait_ref),
+ _ => None,
+ }
+ }),
+ _ => None,
+ }?;
+ Some(from_chalk_trait_id(trait_ref.trait_id))
+ }
+
+ fn strip_references(&self) -> &Ty {
+ let mut t: &Ty = self;
+ while let TyKind::Ref(_mutability, _lifetime, ty) = t.kind(Interner) {
+ t = ty;
+ }
+ t
+ }
+
+ fn strip_reference(&self) -> &Ty {
+ self.as_reference().map_or(self, |(ty, _, _)| ty)
+ }
+
+ fn impl_trait_bounds(&self, db: &dyn HirDatabase) -> Option<Vec<QuantifiedWhereClause>> {
+ match self.kind(Interner) {
+ TyKind::OpaqueType(opaque_ty_id, subst) => {
+ match db.lookup_intern_impl_trait_id((*opaque_ty_id).into()) {
+ ImplTraitId::AsyncBlockTypeImplTrait(def, _expr) => {
+ let krate = def.module(db.upcast()).krate();
+ if let Some(future_trait) = db
+ .lang_item(krate, SmolStr::new_inline("future_trait"))
+ .and_then(|item| item.as_trait())
+ {
+ // This is only used by type walking.
+ // Parameters will be walked outside, and projection predicate is not used.
+ // So just provide the Future trait.
+ let impl_bound = Binders::empty(
+ Interner,
+ WhereClause::Implemented(TraitRef {
+ trait_id: to_chalk_trait_id(future_trait),
+ substitution: Substitution::empty(Interner),
+ }),
+ );
+ Some(vec![impl_bound])
+ } else {
+ None
+ }
+ }
+ ImplTraitId::ReturnTypeImplTrait(func, idx) => {
+ db.return_type_impl_traits(func).map(|it| {
+ let data = (*it)
+ .as_ref()
+ .map(|rpit| rpit.impl_traits[idx as usize].bounds.clone());
+ data.substitute(Interner, &subst).into_value_and_skipped_binders().0
+ })
+ }
+ }
+ }
+ TyKind::Alias(AliasTy::Opaque(opaque_ty)) => {
+ let predicates = match db.lookup_intern_impl_trait_id(opaque_ty.opaque_ty_id.into())
+ {
+ ImplTraitId::ReturnTypeImplTrait(func, idx) => {
+ db.return_type_impl_traits(func).map(|it| {
+ let data = (*it)
+ .as_ref()
+ .map(|rpit| rpit.impl_traits[idx as usize].bounds.clone());
+ data.substitute(Interner, &opaque_ty.substitution)
+ })
+ }
+ // It always has an parameter for Future::Output type.
+ ImplTraitId::AsyncBlockTypeImplTrait(..) => unreachable!(),
+ };
+
+ predicates.map(|it| it.into_value_and_skipped_binders().0)
+ }
+ TyKind::Placeholder(idx) => {
+ let id = from_placeholder_idx(db, *idx);
+ let generic_params = db.generic_params(id.parent);
+ let param_data = &generic_params.type_or_consts[id.local_id];
+ match param_data {
+ TypeOrConstParamData::TypeParamData(p) => match p.provenance {
+ hir_def::generics::TypeParamProvenance::ArgumentImplTrait => {
+ let substs = TyBuilder::placeholder_subst(db, id.parent);
+ let predicates = db
+ .generic_predicates(id.parent)
+ .iter()
+ .map(|pred| pred.clone().substitute(Interner, &substs))
+ .filter(|wc| match &wc.skip_binders() {
+ WhereClause::Implemented(tr) => {
+ &tr.self_type_parameter(Interner) == self
+ }
+ WhereClause::AliasEq(AliasEq {
+ alias: AliasTy::Projection(proj),
+ ty: _,
+ }) => &proj.self_type_parameter(Interner) == self,
+ _ => false,
+ })
+ .collect::<Vec<_>>();
+
+ Some(predicates)
+ }
+ _ => None,
+ },
+ _ => None,
+ }
+ }
+ _ => None,
+ }
+ }
+
+ fn associated_type_parent_trait(&self, db: &dyn HirDatabase) -> Option<TraitId> {
+ match self.kind(Interner) {
+ TyKind::AssociatedType(id, ..) => {
+ match from_assoc_type_id(*id).lookup(db.upcast()).container {
+ ItemContainerId::TraitId(trait_id) => Some(trait_id),
+ _ => None,
+ }
+ }
+ TyKind::Alias(AliasTy::Projection(projection_ty)) => {
+ match from_assoc_type_id(projection_ty.associated_ty_id)
+ .lookup(db.upcast())
+ .container
+ {
+ ItemContainerId::TraitId(trait_id) => Some(trait_id),
+ _ => None,
+ }
+ }
+ _ => None,
+ }
+ }
+
+ fn equals_ctor(&self, other: &Ty) -> bool {
+ match (self.kind(Interner), other.kind(Interner)) {
+ (TyKind::Adt(adt, ..), TyKind::Adt(adt2, ..)) => adt == adt2,
+ (TyKind::Slice(_), TyKind::Slice(_)) | (TyKind::Array(_, _), TyKind::Array(_, _)) => {
+ true
+ }
+ (TyKind::FnDef(def_id, ..), TyKind::FnDef(def_id2, ..)) => def_id == def_id2,
+ (TyKind::OpaqueType(ty_id, ..), TyKind::OpaqueType(ty_id2, ..)) => ty_id == ty_id2,
+ (TyKind::AssociatedType(ty_id, ..), TyKind::AssociatedType(ty_id2, ..)) => {
+ ty_id == ty_id2
+ }
+ (TyKind::Foreign(ty_id, ..), TyKind::Foreign(ty_id2, ..)) => ty_id == ty_id2,
+ (TyKind::Closure(id1, _), TyKind::Closure(id2, _)) => id1 == id2,
+ (TyKind::Ref(mutability, ..), TyKind::Ref(mutability2, ..))
+ | (TyKind::Raw(mutability, ..), TyKind::Raw(mutability2, ..)) => {
+ mutability == mutability2
+ }
+ (
+ TyKind::Function(FnPointer { num_binders, sig, .. }),
+ TyKind::Function(FnPointer { num_binders: num_binders2, sig: sig2, .. }),
+ ) => num_binders == num_binders2 && sig == sig2,
+ (TyKind::Tuple(cardinality, _), TyKind::Tuple(cardinality2, _)) => {
+ cardinality == cardinality2
+ }
+ (TyKind::Str, TyKind::Str) | (TyKind::Never, TyKind::Never) => true,
+ (TyKind::Scalar(scalar), TyKind::Scalar(scalar2)) => scalar == scalar2,
+ _ => false,
+ }
+ }
+}
+
+pub trait ProjectionTyExt {
+ fn trait_ref(&self, db: &dyn HirDatabase) -> TraitRef;
+ fn trait_(&self, db: &dyn HirDatabase) -> TraitId;
+}
+
+impl ProjectionTyExt for ProjectionTy {
+ fn trait_ref(&self, db: &dyn HirDatabase) -> TraitRef {
+ TraitRef {
+ trait_id: to_chalk_trait_id(self.trait_(db)),
+ substitution: self.substitution.clone(),
+ }
+ }
+
+ fn trait_(&self, db: &dyn HirDatabase) -> TraitId {
+ match from_assoc_type_id(self.associated_ty_id).lookup(db.upcast()).container {
+ ItemContainerId::TraitId(it) => it,
+ _ => panic!("projection ty without parent trait"),
+ }
+ }
+}
+
+pub trait TraitRefExt {
+ fn hir_trait_id(&self) -> TraitId;
+}
+
+impl TraitRefExt for TraitRef {
+ fn hir_trait_id(&self) -> TraitId {
+ from_chalk_trait_id(self.trait_id)
+ }
+}
--- /dev/null
- ConstId, DefWithBodyId,
+//! Constant evaluation details
+
+use std::{
+ collections::HashMap,
+ fmt::{Display, Write},
+};
+
+use chalk_ir::{BoundVar, DebruijnIndex, GenericArgData, IntTy, Scalar};
+use hir_def::{
++ builtin_type::BuiltinInt,
+ expr::{ArithOp, BinaryOp, Expr, ExprId, Literal, Pat, PatId},
+ path::ModPath,
+ resolver::{resolver_for_expr, ResolveValueResult, Resolver, ValueNs},
++ src::HasChildSource,
+ type_ref::ConstScalar,
- use la_arena::{Arena, Idx};
++ ConstId, DefWithBodyId, EnumVariantId, Lookup,
+};
- Expr::Missing => Err(ConstEvalError::IncompleteExpr),
++use la_arena::{Arena, Idx, RawIdx};
+use stdx::never;
++use syntax::ast::HasName;
+
+use crate::{
+ db::HirDatabase, infer::InferenceContext, lower::ParamLoweringMode, to_placeholder_idx,
+ utils::Generics, Const, ConstData, ConstValue, GenericArg, InferenceResult, Interner, Ty,
+ TyBuilder, TyKind,
+};
+
+/// Extension trait for [`Const`]
+pub trait ConstExt {
+ /// Is a [`Const`] unknown?
+ fn is_unknown(&self) -> bool;
+}
+
+impl ConstExt for Const {
+ fn is_unknown(&self) -> bool {
+ match self.data(Interner).value {
+ // interned Unknown
+ chalk_ir::ConstValue::Concrete(chalk_ir::ConcreteConst {
+ interned: ConstScalar::Unknown,
+ }) => true,
+
+ // interned concrete anything else
+ chalk_ir::ConstValue::Concrete(..) => false,
+
+ _ => {
+ tracing::error!(
+ "is_unknown was called on a non-concrete constant value! {:?}",
+ self
+ );
+ true
+ }
+ }
+ }
+}
+
+pub struct ConstEvalCtx<'a> {
+ pub db: &'a dyn HirDatabase,
+ pub owner: DefWithBodyId,
+ pub exprs: &'a Arena<Expr>,
+ pub pats: &'a Arena<Pat>,
+ pub local_data: HashMap<PatId, ComputedExpr>,
+ infer: &'a InferenceResult,
+}
+
+impl ConstEvalCtx<'_> {
+ fn expr_ty(&mut self, expr: ExprId) -> Ty {
+ self.infer[expr].clone()
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum ConstEvalError {
+ NotSupported(&'static str),
+ SemanticError(&'static str),
+ Loop,
+ IncompleteExpr,
+ Panic(String),
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum ComputedExpr {
+ Literal(Literal),
++ Enum(String, EnumVariantId, Literal),
+ Tuple(Box<[ComputedExpr]>),
+}
+
+impl Display for ComputedExpr {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ ComputedExpr::Literal(l) => match l {
+ Literal::Int(x, _) => {
+ if *x >= 10 {
+ write!(f, "{} ({:#X})", x, x)
+ } else {
+ x.fmt(f)
+ }
+ }
+ Literal::Uint(x, _) => {
+ if *x >= 10 {
+ write!(f, "{} ({:#X})", x, x)
+ } else {
+ x.fmt(f)
+ }
+ }
+ Literal::Float(x, _) => x.fmt(f),
+ Literal::Bool(x) => x.fmt(f),
+ Literal::Char(x) => std::fmt::Debug::fmt(x, f),
+ Literal::String(x) => std::fmt::Debug::fmt(x, f),
+ Literal::ByteString(x) => std::fmt::Debug::fmt(x, f),
+ },
++ ComputedExpr::Enum(name, _, _) => name.fmt(f),
+ ComputedExpr::Tuple(t) => {
+ f.write_char('(')?;
+ for x in &**t {
+ x.fmt(f)?;
+ f.write_str(", ")?;
+ }
+ f.write_char(')')
+ }
+ }
+ }
+}
+
+fn scalar_max(scalar: &Scalar) -> i128 {
+ match scalar {
+ Scalar::Bool => 1,
+ Scalar::Char => u32::MAX as i128,
+ Scalar::Int(x) => match x {
+ IntTy::Isize => isize::MAX as i128,
+ IntTy::I8 => i8::MAX as i128,
+ IntTy::I16 => i16::MAX as i128,
+ IntTy::I32 => i32::MAX as i128,
+ IntTy::I64 => i64::MAX as i128,
+ IntTy::I128 => i128::MAX as i128,
+ },
+ Scalar::Uint(x) => match x {
+ chalk_ir::UintTy::Usize => usize::MAX as i128,
+ chalk_ir::UintTy::U8 => u8::MAX as i128,
+ chalk_ir::UintTy::U16 => u16::MAX as i128,
+ chalk_ir::UintTy::U32 => u32::MAX as i128,
+ chalk_ir::UintTy::U64 => u64::MAX as i128,
+ chalk_ir::UintTy::U128 => i128::MAX as i128, // ignore too big u128 for now
+ },
+ Scalar::Float(_) => 0,
+ }
+}
+
+fn is_valid(scalar: &Scalar, value: i128) -> bool {
+ if value < 0 {
+ !matches!(scalar, Scalar::Uint(_)) && -scalar_max(scalar) - 1 <= value
+ } else {
+ value <= scalar_max(scalar)
+ }
+}
+
++fn get_name(ctx: &mut ConstEvalCtx<'_>, variant: EnumVariantId) -> String {
++ let loc = variant.parent.lookup(ctx.db.upcast());
++ let children = variant.parent.child_source(ctx.db.upcast());
++ let item_tree = loc.id.item_tree(ctx.db.upcast());
++
++ let variant_name = children.value[variant.local_id].name();
++ let enum_name = item_tree[loc.id.value].name.to_string();
++ enum_name + "::" + &variant_name.unwrap().to_string()
++}
++
+pub fn eval_const(
+ expr_id: ExprId,
+ ctx: &mut ConstEvalCtx<'_>,
+) -> Result<ComputedExpr, ConstEvalError> {
++ let u128_to_i128 = |it: u128| -> Result<i128, ConstEvalError> {
++ it.try_into().map_err(|_| ConstEvalError::NotSupported("u128 is too big"))
++ };
++
+ let expr = &ctx.exprs[expr_id];
+ match expr {
- ComputedExpr::Literal(Literal::Uint(v, _)) => v
- .try_into()
- .map_err(|_| ConstEvalError::NotSupported("too big u128"))?,
++ Expr::Missing => match ctx.owner {
++ // evaluate the implicit variant index of an enum variant without expression
++ // FIXME: This should return the type of the enum representation
++ DefWithBodyId::VariantId(variant) => {
++ let prev_idx: u32 = variant.local_id.into_raw().into();
++ let prev_idx = prev_idx.checked_sub(1).map(RawIdx::from).map(Idx::from_raw);
++ let value = match prev_idx {
++ Some(local_id) => {
++ let prev_variant = EnumVariantId { local_id, parent: variant.parent };
++ 1 + match ctx.db.const_eval_variant(prev_variant)? {
++ ComputedExpr::Literal(Literal::Int(v, _)) => v,
++ ComputedExpr::Literal(Literal::Uint(v, _)) => u128_to_i128(v)?,
++ _ => {
++ return Err(ConstEvalError::NotSupported(
++ "Enum can't contain this kind of value",
++ ))
++ }
++ }
++ }
++ _ => 0,
++ };
++ Ok(ComputedExpr::Literal(Literal::Int(value, Some(BuiltinInt::I128))))
++ }
++ _ => Err(ConstEvalError::IncompleteExpr),
++ },
+ Expr::Literal(l) => Ok(ComputedExpr::Literal(l.clone())),
+ &Expr::UnaryOp { expr, op } => {
+ let ty = &ctx.expr_ty(expr);
+ let ev = eval_const(expr, ctx)?;
+ match op {
+ hir_def::expr::UnaryOp::Deref => Err(ConstEvalError::NotSupported("deref")),
+ hir_def::expr::UnaryOp::Not => {
+ let v = match ev {
+ ComputedExpr::Literal(Literal::Bool(b)) => {
+ return Ok(ComputedExpr::Literal(Literal::Bool(!b)))
+ }
+ ComputedExpr::Literal(Literal::Int(v, _)) => v,
- ComputedExpr::Literal(Literal::Uint(v, _)) => v
- .try_into()
- .map_err(|_| ConstEvalError::NotSupported("too big u128"))?,
++ ComputedExpr::Literal(Literal::Uint(v, _)) => u128_to_i128(v)?,
+ _ => return Err(ConstEvalError::NotSupported("this kind of operator")),
+ };
+ let r = match ty.kind(Interner) {
+ TyKind::Scalar(Scalar::Uint(x)) => match x {
+ chalk_ir::UintTy::U8 => !(v as u8) as i128,
+ chalk_ir::UintTy::U16 => !(v as u16) as i128,
+ chalk_ir::UintTy::U32 => !(v as u32) as i128,
+ chalk_ir::UintTy::U64 => !(v as u64) as i128,
+ chalk_ir::UintTy::U128 => {
+ return Err(ConstEvalError::NotSupported("negation of u128"))
+ }
+ chalk_ir::UintTy::Usize => !(v as usize) as i128,
+ },
+ TyKind::Scalar(Scalar::Int(x)) => match x {
+ chalk_ir::IntTy::I8 => !(v as i8) as i128,
+ chalk_ir::IntTy::I16 => !(v as i16) as i128,
+ chalk_ir::IntTy::I32 => !(v as i32) as i128,
+ chalk_ir::IntTy::I64 => !(v as i64) as i128,
+ chalk_ir::IntTy::I128 => !v,
+ chalk_ir::IntTy::Isize => !(v as isize) as i128,
+ },
+ _ => return Err(ConstEvalError::NotSupported("unreachable?")),
+ };
+ Ok(ComputedExpr::Literal(Literal::Int(r, None)))
+ }
+ hir_def::expr::UnaryOp::Neg => {
+ let v = match ev {
+ ComputedExpr::Literal(Literal::Int(v, _)) => v,
- ComputedExpr::Literal(Literal::Uint(v, _)) => {
- v.try_into().map_err(|_| ConstEvalError::NotSupported("too big u128"))?
- }
++ ComputedExpr::Literal(Literal::Uint(v, _)) => u128_to_i128(v)?,
+ _ => return Err(ConstEvalError::NotSupported("this kind of operator")),
+ };
+ Ok(ComputedExpr::Literal(Literal::Int(
+ v.checked_neg().ok_or_else(|| {
+ ConstEvalError::Panic("overflow in negation".to_string())
+ })?,
+ None,
+ )))
+ }
+ }
+ }
+ &Expr::BinaryOp { lhs, rhs, op } => {
+ let ty = &ctx.expr_ty(lhs);
+ let lhs = eval_const(lhs, ctx)?;
+ let rhs = eval_const(rhs, ctx)?;
+ let op = op.ok_or(ConstEvalError::IncompleteExpr)?;
+ let v1 = match lhs {
+ ComputedExpr::Literal(Literal::Int(v, _)) => v,
- ComputedExpr::Literal(Literal::Uint(v, _)) => {
- v.try_into().map_err(|_| ConstEvalError::NotSupported("too big u128"))?
- }
++ ComputedExpr::Literal(Literal::Uint(v, _)) => u128_to_i128(v)?,
+ _ => return Err(ConstEvalError::NotSupported("this kind of operator")),
+ };
+ let v2 = match rhs {
+ ComputedExpr::Literal(Literal::Int(v, _)) => v,
- pub(crate) fn const_eval_query(
++ ComputedExpr::Literal(Literal::Uint(v, _)) => u128_to_i128(v)?,
+ _ => return Err(ConstEvalError::NotSupported("this kind of operator")),
+ };
+ match op {
+ BinaryOp::ArithOp(b) => {
+ let panic_arith = ConstEvalError::Panic(
+ "attempt to run invalid arithmetic operation".to_string(),
+ );
+ let r = match b {
+ ArithOp::Add => v1.checked_add(v2).ok_or_else(|| panic_arith.clone())?,
+ ArithOp::Mul => v1.checked_mul(v2).ok_or_else(|| panic_arith.clone())?,
+ ArithOp::Sub => v1.checked_sub(v2).ok_or_else(|| panic_arith.clone())?,
+ ArithOp::Div => v1.checked_div(v2).ok_or_else(|| panic_arith.clone())?,
+ ArithOp::Rem => v1.checked_rem(v2).ok_or_else(|| panic_arith.clone())?,
+ ArithOp::Shl => v1
+ .checked_shl(v2.try_into().map_err(|_| panic_arith.clone())?)
+ .ok_or_else(|| panic_arith.clone())?,
+ ArithOp::Shr => v1
+ .checked_shr(v2.try_into().map_err(|_| panic_arith.clone())?)
+ .ok_or_else(|| panic_arith.clone())?,
+ ArithOp::BitXor => v1 ^ v2,
+ ArithOp::BitOr => v1 | v2,
+ ArithOp::BitAnd => v1 & v2,
+ };
+ if let TyKind::Scalar(s) = ty.kind(Interner) {
+ if !is_valid(s, r) {
+ return Err(panic_arith);
+ }
+ }
+ Ok(ComputedExpr::Literal(Literal::Int(r, None)))
+ }
+ BinaryOp::LogicOp(_) => Err(ConstEvalError::SemanticError("logic op on numbers")),
+ _ => Err(ConstEvalError::NotSupported("bin op on this operators")),
+ }
+ }
+ Expr::Block { statements, tail, .. } => {
+ let mut prev_values = HashMap::<PatId, Option<ComputedExpr>>::default();
+ for statement in &**statements {
+ match *statement {
+ hir_def::expr::Statement::Let { pat: pat_id, initializer, .. } => {
+ let pat = &ctx.pats[pat_id];
+ match pat {
+ Pat::Bind { subpat, .. } if subpat.is_none() => (),
+ _ => {
+ return Err(ConstEvalError::NotSupported("complex patterns in let"))
+ }
+ };
+ let value = match initializer {
+ Some(x) => eval_const(x, ctx)?,
+ None => continue,
+ };
+ if !prev_values.contains_key(&pat_id) {
+ let prev = ctx.local_data.insert(pat_id, value);
+ prev_values.insert(pat_id, prev);
+ } else {
+ ctx.local_data.insert(pat_id, value);
+ }
+ }
+ hir_def::expr::Statement::Expr { .. } => {
+ return Err(ConstEvalError::NotSupported("this kind of statement"))
+ }
+ }
+ }
+ let r = match tail {
+ &Some(x) => eval_const(x, ctx),
+ None => Ok(ComputedExpr::Tuple(Box::new([]))),
+ };
+ // clean up local data, so caller will receive the exact map that passed to us
+ for (name, val) in prev_values {
+ match val {
+ Some(x) => ctx.local_data.insert(name, x),
+ None => ctx.local_data.remove(&name),
+ };
+ }
+ r
+ }
+ Expr::Path(p) => {
+ let resolver = resolver_for_expr(ctx.db.upcast(), ctx.owner, expr_id);
+ let pr = resolver
+ .resolve_path_in_value_ns(ctx.db.upcast(), p.mod_path())
+ .ok_or(ConstEvalError::SemanticError("unresolved path"))?;
+ let pr = match pr {
+ ResolveValueResult::ValueNs(v) => v,
+ ResolveValueResult::Partial(..) => {
+ return match ctx
+ .infer
+ .assoc_resolutions_for_expr(expr_id)
+ .ok_or(ConstEvalError::SemanticError("unresolved assoc item"))?
+ {
+ hir_def::AssocItemId::FunctionId(_) => {
+ Err(ConstEvalError::NotSupported("assoc function"))
+ }
+ hir_def::AssocItemId::ConstId(c) => ctx.db.const_eval(c),
+ hir_def::AssocItemId::TypeAliasId(_) => {
+ Err(ConstEvalError::NotSupported("assoc type alias"))
+ }
+ }
+ }
+ };
+ match pr {
+ ValueNs::LocalBinding(pat_id) => {
+ let r = ctx
+ .local_data
+ .get(&pat_id)
+ .ok_or(ConstEvalError::NotSupported("Unexpected missing local"))?;
+ Ok(r.clone())
+ }
+ ValueNs::ConstId(id) => ctx.db.const_eval(id),
+ ValueNs::GenericParam(_) => {
+ Err(ConstEvalError::NotSupported("const generic without substitution"))
+ }
++ ValueNs::EnumVariantId(id) => match ctx.db.const_eval_variant(id)? {
++ ComputedExpr::Literal(lit) => {
++ Ok(ComputedExpr::Enum(get_name(ctx, id), id, lit))
++ }
++ _ => Err(ConstEvalError::NotSupported(
++ "Enums can't evalute to anything but numbers",
++ )),
++ },
+ _ => Err(ConstEvalError::NotSupported("path that are not const or local")),
+ }
+ }
++ // FIXME: Handle the cast target
++ &Expr::Cast { expr, .. } => match eval_const(expr, ctx)? {
++ ComputedExpr::Enum(_, _, lit) => Ok(ComputedExpr::Literal(lit)),
++ _ => Err(ConstEvalError::NotSupported("Can't cast these types")),
++ },
+ _ => Err(ConstEvalError::NotSupported("This kind of expression")),
+ }
+}
+
+pub(crate) fn path_to_const(
+ db: &dyn HirDatabase,
+ resolver: &Resolver,
+ path: &ModPath,
+ mode: ParamLoweringMode,
+ args_lazy: impl FnOnce() -> Generics,
+ debruijn: DebruijnIndex,
+) -> Option<Const> {
+ match resolver.resolve_path_in_value_ns_fully(db.upcast(), &path) {
+ Some(ValueNs::GenericParam(p)) => {
+ let ty = db.const_param_ty(p);
+ let args = args_lazy();
+ let value = match mode {
+ ParamLoweringMode::Placeholder => {
+ ConstValue::Placeholder(to_placeholder_idx(db, p.into()))
+ }
+ ParamLoweringMode::Variable => match args.param_idx(p.into()) {
+ Some(x) => ConstValue::BoundVar(BoundVar::new(debruijn, x)),
+ None => {
+ never!(
+ "Generic list doesn't contain this param: {:?}, {}, {:?}",
+ args,
+ path,
+ p
+ );
+ return None;
+ }
+ },
+ };
+ Some(ConstData { ty, value }.intern(Interner))
+ }
+ _ => None,
+ }
+}
+
+pub fn unknown_const(ty: Ty) -> Const {
+ ConstData {
+ ty,
+ value: ConstValue::Concrete(chalk_ir::ConcreteConst { interned: ConstScalar::Unknown }),
+ }
+ .intern(Interner)
+}
+
+pub fn unknown_const_as_generic(ty: Ty) -> GenericArg {
+ GenericArgData::Const(unknown_const(ty)).intern(Interner)
+}
+
+/// Interns a constant scalar with the given type
+pub fn intern_const_scalar(value: ConstScalar, ty: Ty) -> Const {
+ ConstData { ty, value: ConstValue::Concrete(chalk_ir::ConcreteConst { interned: value }) }
+ .intern(Interner)
+}
+
+/// Interns a possibly-unknown target usize
+pub fn usize_const(value: Option<u128>) -> Const {
+ intern_const_scalar(value.map_or(ConstScalar::Unknown, ConstScalar::UInt), TyBuilder::usize())
+}
+
+pub(crate) fn const_eval_recover(
+ _: &dyn HirDatabase,
+ _: &[String],
+ _: &ConstId,
+) -> Result<ComputedExpr, ConstEvalError> {
+ Err(ConstEvalError::Loop)
+}
+
++pub(crate) fn const_eval_variant_recover(
++ _: &dyn HirDatabase,
++ _: &[String],
++ _: &EnumVariantId,
++) -> Result<ComputedExpr, ConstEvalError> {
++ Err(ConstEvalError::Loop)
++}
++
++pub(crate) fn const_eval_variant_query(
+ db: &dyn HirDatabase,
+ const_id: ConstId,
+) -> Result<ComputedExpr, ConstEvalError> {
+ let def = const_id.into();
+ let body = db.body(def);
+ let infer = &db.infer(def);
+ let result = eval_const(
+ body.body_expr,
+ &mut ConstEvalCtx {
+ db,
+ owner: const_id.into(),
+ exprs: &body.exprs,
+ pats: &body.pats,
+ local_data: HashMap::default(),
+ infer,
+ },
+ );
+ result
+}
+
++pub(crate) fn const_eval_query_variant(
++ db: &dyn HirDatabase,
++ variant_id: EnumVariantId,
++) -> Result<ComputedExpr, ConstEvalError> {
++ let def = variant_id.into();
++ let body = db.body(def);
++ let infer = &db.infer(def);
++ eval_const(
++ body.body_expr,
++ &mut ConstEvalCtx {
++ db,
++ owner: def,
++ exprs: &body.exprs,
++ pats: &body.pats,
++ local_data: HashMap::default(),
++ infer,
++ },
++ )
++}
++
+pub(crate) fn eval_to_const<'a>(
+ expr: Idx<Expr>,
+ mode: ParamLoweringMode,
+ ctx: &mut InferenceContext<'a>,
+ args: impl FnOnce() -> Generics,
+ debruijn: DebruijnIndex,
+) -> Const {
+ if let Expr::Path(p) = &ctx.body.exprs[expr] {
+ let db = ctx.db;
+ let resolver = &ctx.resolver;
+ if let Some(c) = path_to_const(db, resolver, p.mod_path(), mode, args, debruijn) {
+ return c;
+ }
+ }
+ let body = ctx.body.clone();
+ let mut ctx = ConstEvalCtx {
+ db: ctx.db,
+ owner: ctx.owner,
+ exprs: &body.exprs,
+ pats: &body.pats,
+ local_data: HashMap::default(),
+ infer: &ctx.result,
+ };
+ let computed_expr = eval_const(expr, &mut ctx);
+ let const_scalar = match computed_expr {
+ Ok(ComputedExpr::Literal(literal)) => literal.into(),
+ _ => ConstScalar::Unknown,
+ };
+ intern_const_scalar(const_scalar, TyBuilder::usize())
+}
+
+#[cfg(test)]
+mod tests;
--- /dev/null
+use base_db::fixture::WithFixture;
+use hir_def::{db::DefDatabase, expr::Literal};
+
+use crate::{consteval::ComputedExpr, db::HirDatabase, test_db::TestDB};
+
+use super::ConstEvalError;
+
+fn check_fail(ra_fixture: &str, error: ConstEvalError) {
+ assert_eq!(eval_goal(ra_fixture), Err(error));
+}
+
+fn check_number(ra_fixture: &str, answer: i128) {
+ let r = eval_goal(ra_fixture).unwrap();
+ match r {
+ ComputedExpr::Literal(Literal::Int(r, _)) => assert_eq!(r, answer),
+ ComputedExpr::Literal(Literal::Uint(r, _)) => assert_eq!(r, answer as u128),
+ x => panic!("Expected number but found {:?}", x),
+ }
+}
+
+fn eval_goal(ra_fixture: &str) -> Result<ComputedExpr, ConstEvalError> {
+ let (db, file_id) = TestDB::with_single_file(ra_fixture);
+ let module_id = db.module_for_file(file_id);
+ let def_map = module_id.def_map(&db);
+ let scope = &def_map[module_id.local_id].scope;
+ let const_id = scope
+ .declarations()
+ .into_iter()
+ .find_map(|x| match x {
+ hir_def::ModuleDefId::ConstId(x) => {
+ if db.const_data(x).name.as_ref()?.to_string() == "GOAL" {
+ Some(x)
+ } else {
+ None
+ }
+ }
+ _ => None,
+ })
+ .unwrap();
+ db.const_eval(const_id)
+}
+
+#[test]
+fn add() {
+ check_number(r#"const GOAL: usize = 2 + 2;"#, 4);
+}
+
+#[test]
+fn bit_op() {
+ check_number(r#"const GOAL: u8 = !0 & !(!0 >> 1)"#, 128);
+ check_number(r#"const GOAL: i8 = !0 & !(!0 >> 1)"#, 0);
+ // FIXME: rustc evaluate this to -128
+ check_fail(
+ r#"const GOAL: i8 = 1 << 7"#,
+ ConstEvalError::Panic("attempt to run invalid arithmetic operation".to_string()),
+ );
+ check_fail(
+ r#"const GOAL: i8 = 1 << 8"#,
+ ConstEvalError::Panic("attempt to run invalid arithmetic operation".to_string()),
+ );
+}
+
+#[test]
+fn locals() {
+ check_number(
+ r#"
+ const GOAL: usize = {
+ let a = 3 + 2;
+ let b = a * a;
+ b
+ };
+ "#,
+ 25,
+ );
+}
+
+#[test]
+fn consts() {
+ check_number(
+ r#"
+ const F1: i32 = 1;
+ const F3: i32 = 3 * F2;
+ const F2: i32 = 2 * F1;
+ const GOAL: i32 = F3;
+ "#,
+ 6,
+ );
+}
+
++#[test]
++fn enums() {
++ check_number(
++ r#"
++ enum E {
++ F1 = 1,
++ F2 = 2 * E::F1 as u8,
++ F3 = 3 * E::F2 as u8,
++ }
++ const GOAL: i32 = E::F3 as u8;
++ "#,
++ 6,
++ );
++ check_number(
++ r#"
++ enum E { F1 = 1, F2, }
++ const GOAL: i32 = E::F2 as u8;
++ "#,
++ 2,
++ );
++ check_number(
++ r#"
++ enum E { F1, }
++ const GOAL: i32 = E::F1 as u8;
++ "#,
++ 0,
++ );
++ let r = eval_goal(
++ r#"
++ enum E { A = 1, }
++ const GOAL: E = E::A;
++ "#,
++ )
++ .unwrap();
++ match r {
++ ComputedExpr::Enum(name, _, Literal::Uint(val, _)) => {
++ assert_eq!(name, "E::A");
++ assert_eq!(val, 1);
++ }
++ x => panic!("Expected enum but found {:?}", x),
++ }
++}
++
+#[test]
+fn const_loop() {
+ check_fail(
+ r#"
+ const F1: i32 = 1 * F3;
+ const F3: i32 = 3 * F2;
+ const F2: i32 = 2 * F1;
+ const GOAL: i32 = F3;
+ "#,
+ ConstEvalError::Loop,
+ );
+}
+
+#[test]
+fn const_impl_assoc() {
+ check_number(
+ r#"
+ struct U5;
+ impl U5 {
+ const VAL: usize = 5;
+ }
+ const GOAL: usize = U5::VAL;
+ "#,
+ 5,
+ );
+}
+
+#[test]
+fn const_generic_subst() {
+ // FIXME: this should evaluate to 5
+ check_fail(
+ r#"
+ struct Adder<const N: usize, const M: usize>;
+ impl<const N: usize, const M: usize> Adder<N, M> {
+ const VAL: usize = N + M;
+ }
+ const GOAL: usize = Adder::<2, 3>::VAL;
+ "#,
+ ConstEvalError::NotSupported("const generic without substitution"),
+ );
+}
+
+#[test]
+fn const_trait_assoc() {
+ // FIXME: this should evaluate to 0
+ check_fail(
+ r#"
+ struct U0;
+ trait ToConst {
+ const VAL: usize;
+ }
+ impl ToConst for U0 {
+ const VAL: usize = 0;
+ }
+ const GOAL: usize = U0::VAL;
+ "#,
+ ConstEvalError::IncompleteExpr,
+ );
+}
--- /dev/null
- db::DefDatabase, expr::ExprId, BlockId, ConstId, ConstParamId, DefWithBodyId, FunctionId,
- GenericDefId, ImplId, LifetimeParamId, LocalFieldId, TypeOrConstParamId, VariantId,
+//! The home of `HirDatabase`, which is the Salsa database containing all the
+//! type inference-related queries.
+
+use std::sync::Arc;
+
+use arrayvec::ArrayVec;
+use base_db::{impl_intern_key, salsa, CrateId, Upcast};
+use hir_def::{
- #[salsa::invoke(crate::consteval::const_eval_query)]
++ db::DefDatabase, expr::ExprId, BlockId, ConstId, ConstParamId, DefWithBodyId, EnumVariantId,
++ FunctionId, GenericDefId, ImplId, LifetimeParamId, LocalFieldId, TypeOrConstParamId, VariantId,
+};
+use la_arena::ArenaMap;
+
+use crate::{
+ chalk_db,
+ consteval::{ComputedExpr, ConstEvalError},
+ method_resolution::{InherentImpls, TraitImpls, TyFingerprint},
+ Binders, CallableDefId, FnDefId, GenericArg, ImplTraitId, InferenceResult, Interner, PolyFnSig,
+ QuantifiedWhereClause, ReturnTypeImplTraits, TraitRef, Ty, TyDefId, ValueTyDefId,
+};
+use hir_expand::name::Name;
+
+#[salsa::query_group(HirDatabaseStorage)]
+pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
+ #[salsa::invoke(infer_wait)]
+ #[salsa::transparent]
+ fn infer(&self, def: DefWithBodyId) -> Arc<InferenceResult>;
+
+ #[salsa::invoke(crate::infer::infer_query)]
+ fn infer_query(&self, def: DefWithBodyId) -> Arc<InferenceResult>;
+
+ #[salsa::invoke(crate::lower::ty_query)]
+ #[salsa::cycle(crate::lower::ty_recover)]
+ fn ty(&self, def: TyDefId) -> Binders<Ty>;
+
+ #[salsa::invoke(crate::lower::value_ty_query)]
+ fn value_ty(&self, def: ValueTyDefId) -> Binders<Ty>;
+
+ #[salsa::invoke(crate::lower::impl_self_ty_query)]
+ #[salsa::cycle(crate::lower::impl_self_ty_recover)]
+ fn impl_self_ty(&self, def: ImplId) -> Binders<Ty>;
+
+ #[salsa::invoke(crate::lower::const_param_ty_query)]
+ fn const_param_ty(&self, def: ConstParamId) -> Ty;
+
++ #[salsa::invoke(crate::consteval::const_eval_variant_query)]
+ #[salsa::cycle(crate::consteval::const_eval_recover)]
+ fn const_eval(&self, def: ConstId) -> Result<ComputedExpr, ConstEvalError>;
+
++ #[salsa::invoke(crate::consteval::const_eval_query_variant)]
++ #[salsa::cycle(crate::consteval::const_eval_variant_recover)]
++ fn const_eval_variant(&self, def: EnumVariantId) -> Result<ComputedExpr, ConstEvalError>;
++
+ #[salsa::invoke(crate::lower::impl_trait_query)]
+ fn impl_trait(&self, def: ImplId) -> Option<Binders<TraitRef>>;
+
+ #[salsa::invoke(crate::lower::field_types_query)]
+ fn field_types(&self, var: VariantId) -> Arc<ArenaMap<LocalFieldId, Binders<Ty>>>;
+
+ #[salsa::invoke(crate::lower::callable_item_sig)]
+ fn callable_item_signature(&self, def: CallableDefId) -> PolyFnSig;
+
+ #[salsa::invoke(crate::lower::return_type_impl_traits)]
+ fn return_type_impl_traits(
+ &self,
+ def: FunctionId,
+ ) -> Option<Arc<Binders<ReturnTypeImplTraits>>>;
+
+ #[salsa::invoke(crate::lower::generic_predicates_for_param_query)]
+ #[salsa::cycle(crate::lower::generic_predicates_for_param_recover)]
+ fn generic_predicates_for_param(
+ &self,
+ def: GenericDefId,
+ param_id: TypeOrConstParamId,
+ assoc_name: Option<Name>,
+ ) -> Arc<[Binders<QuantifiedWhereClause>]>;
+
+ #[salsa::invoke(crate::lower::generic_predicates_query)]
+ fn generic_predicates(&self, def: GenericDefId) -> Arc<[Binders<QuantifiedWhereClause>]>;
+
+ #[salsa::invoke(crate::lower::trait_environment_query)]
+ fn trait_environment(&self, def: GenericDefId) -> Arc<crate::TraitEnvironment>;
+
+ #[salsa::invoke(crate::lower::generic_defaults_query)]
+ #[salsa::cycle(crate::lower::generic_defaults_recover)]
+ fn generic_defaults(&self, def: GenericDefId) -> Arc<[Binders<GenericArg>]>;
+
+ #[salsa::invoke(InherentImpls::inherent_impls_in_crate_query)]
+ fn inherent_impls_in_crate(&self, krate: CrateId) -> Arc<InherentImpls>;
+
+ #[salsa::invoke(InherentImpls::inherent_impls_in_block_query)]
+ fn inherent_impls_in_block(&self, block: BlockId) -> Option<Arc<InherentImpls>>;
+
+ /// Collects all crates in the dependency graph that have impls for the
+ /// given fingerprint. This is only used for primitive types; for
+ /// user-defined types we just look at the crate where the type is defined.
+ #[salsa::invoke(crate::method_resolution::inherent_impl_crates_query)]
+ fn inherent_impl_crates(&self, krate: CrateId, fp: TyFingerprint) -> ArrayVec<CrateId, 2>;
+
+ #[salsa::invoke(TraitImpls::trait_impls_in_crate_query)]
+ fn trait_impls_in_crate(&self, krate: CrateId) -> Arc<TraitImpls>;
+
+ #[salsa::invoke(TraitImpls::trait_impls_in_block_query)]
+ fn trait_impls_in_block(&self, krate: BlockId) -> Option<Arc<TraitImpls>>;
+
+ #[salsa::invoke(TraitImpls::trait_impls_in_deps_query)]
+ fn trait_impls_in_deps(&self, krate: CrateId) -> Arc<TraitImpls>;
+
+ // Interned IDs for Chalk integration
+ #[salsa::interned]
+ fn intern_callable_def(&self, callable_def: CallableDefId) -> InternedCallableDefId;
+ #[salsa::interned]
+ fn intern_type_or_const_param_id(
+ &self,
+ param_id: TypeOrConstParamId,
+ ) -> InternedTypeOrConstParamId;
+ #[salsa::interned]
+ fn intern_lifetime_param_id(&self, param_id: LifetimeParamId) -> InternedLifetimeParamId;
+ #[salsa::interned]
+ fn intern_impl_trait_id(&self, id: ImplTraitId) -> InternedOpaqueTyId;
+ #[salsa::interned]
+ fn intern_closure(&self, id: (DefWithBodyId, ExprId)) -> InternedClosureId;
++ #[salsa::interned]
++ fn intern_generator(&self, id: (DefWithBodyId, ExprId)) -> InternedGeneratorId;
+
+ #[salsa::invoke(chalk_db::associated_ty_data_query)]
+ fn associated_ty_data(&self, id: chalk_db::AssocTypeId) -> Arc<chalk_db::AssociatedTyDatum>;
+
+ #[salsa::invoke(chalk_db::trait_datum_query)]
+ fn trait_datum(&self, krate: CrateId, trait_id: chalk_db::TraitId)
+ -> Arc<chalk_db::TraitDatum>;
+
+ #[salsa::invoke(chalk_db::struct_datum_query)]
+ fn struct_datum(
+ &self,
+ krate: CrateId,
+ struct_id: chalk_db::AdtId,
+ ) -> Arc<chalk_db::StructDatum>;
+
+ #[salsa::invoke(chalk_db::impl_datum_query)]
+ fn impl_datum(&self, krate: CrateId, impl_id: chalk_db::ImplId) -> Arc<chalk_db::ImplDatum>;
+
+ #[salsa::invoke(chalk_db::fn_def_datum_query)]
+ fn fn_def_datum(&self, krate: CrateId, fn_def_id: FnDefId) -> Arc<chalk_db::FnDefDatum>;
+
+ #[salsa::invoke(chalk_db::fn_def_variance_query)]
+ fn fn_def_variance(&self, fn_def_id: FnDefId) -> chalk_db::Variances;
+
+ #[salsa::invoke(chalk_db::adt_variance_query)]
+ fn adt_variance(&self, adt_id: chalk_db::AdtId) -> chalk_db::Variances;
+
+ #[salsa::invoke(chalk_db::associated_ty_value_query)]
+ fn associated_ty_value(
+ &self,
+ krate: CrateId,
+ id: chalk_db::AssociatedTyValueId,
+ ) -> Arc<chalk_db::AssociatedTyValue>;
+
+ #[salsa::invoke(crate::traits::normalize_projection_query)]
+ #[salsa::transparent]
+ fn normalize_projection(
+ &self,
+ projection: crate::ProjectionTy,
+ env: Arc<crate::TraitEnvironment>,
+ ) -> Ty;
+
+ #[salsa::invoke(trait_solve_wait)]
+ #[salsa::transparent]
+ fn trait_solve(
+ &self,
+ krate: CrateId,
+ goal: crate::Canonical<crate::InEnvironment<crate::Goal>>,
+ ) -> Option<crate::Solution>;
+
+ #[salsa::invoke(crate::traits::trait_solve_query)]
+ fn trait_solve_query(
+ &self,
+ krate: CrateId,
+ goal: crate::Canonical<crate::InEnvironment<crate::Goal>>,
+ ) -> Option<crate::Solution>;
+
+ #[salsa::invoke(chalk_db::program_clauses_for_chalk_env_query)]
+ fn program_clauses_for_chalk_env(
+ &self,
+ krate: CrateId,
+ env: chalk_ir::Environment<Interner>,
+ ) -> chalk_ir::ProgramClauses<Interner>;
+}
+
+fn infer_wait(db: &dyn HirDatabase, def: DefWithBodyId) -> Arc<InferenceResult> {
+ let _p = profile::span("infer:wait").detail(|| match def {
+ DefWithBodyId::FunctionId(it) => db.function_data(it).name.to_string(),
+ DefWithBodyId::StaticId(it) => db.static_data(it).name.clone().to_string(),
+ DefWithBodyId::ConstId(it) => {
+ db.const_data(it).name.clone().unwrap_or_else(Name::missing).to_string()
+ }
++ DefWithBodyId::VariantId(it) => {
++ db.enum_data(it.parent).variants[it.local_id].name.to_string()
++ }
+ });
+ db.infer_query(def)
+}
+
+fn trait_solve_wait(
+ db: &dyn HirDatabase,
+ krate: CrateId,
+ goal: crate::Canonical<crate::InEnvironment<crate::Goal>>,
+) -> Option<crate::Solution> {
+ let _p = profile::span("trait_solve::wait");
+ db.trait_solve_query(krate, goal)
+}
+
+#[test]
+fn hir_database_is_object_safe() {
+ fn _assert_object_safe(_: &dyn HirDatabase) {}
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct InternedTypeOrConstParamId(salsa::InternId);
+impl_intern_key!(InternedTypeOrConstParamId);
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct InternedLifetimeParamId(salsa::InternId);
+impl_intern_key!(InternedLifetimeParamId);
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct InternedConstParamId(salsa::InternId);
+impl_intern_key!(InternedConstParamId);
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct InternedOpaqueTyId(salsa::InternId);
+impl_intern_key!(InternedOpaqueTyId);
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct InternedClosureId(salsa::InternId);
+impl_intern_key!(InternedClosureId);
+
++#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
++pub struct InternedGeneratorId(salsa::InternId);
++impl_intern_key!(InternedGeneratorId);
++
+/// This exists just for Chalk, because Chalk just has a single `FnDefId` where
+/// we have different IDs for struct and enum variant constructors.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
+pub struct InternedCallableDefId(salsa::InternId);
+impl_intern_key!(InternedCallableDefId);
--- /dev/null
- DefWithBodyId::StaticId(_) | DefWithBodyId::ConstId(_) => false,
+//! Provides validations for unsafe code. Currently checks if unsafe functions are missing
+//! unsafe blocks.
+
+use hir_def::{
+ body::Body,
+ expr::{Expr, ExprId, UnaryOp},
+ resolver::{resolver_for_expr, ResolveValueResult, ValueNs},
+ DefWithBodyId,
+};
+
+use crate::{
+ db::HirDatabase, utils::is_fn_unsafe_to_call, InferenceResult, Interner, TyExt, TyKind,
+};
+
+pub fn missing_unsafe(db: &dyn HirDatabase, def: DefWithBodyId) -> Vec<ExprId> {
+ let infer = db.infer(def);
+ let mut res = Vec::new();
+
+ let is_unsafe = match def {
+ DefWithBodyId::FunctionId(it) => db.function_data(it).has_unsafe_kw(),
++ DefWithBodyId::StaticId(_) | DefWithBodyId::ConstId(_) | DefWithBodyId::VariantId(_) => {
++ false
++ }
+ };
+ if is_unsafe {
+ return res;
+ }
+
+ let body = db.body(def);
+ unsafe_expressions(db, &infer, def, &body, body.body_expr, &mut |expr| {
+ if !expr.inside_unsafe_block {
+ res.push(expr.expr);
+ }
+ });
+
+ res
+}
+
+pub struct UnsafeExpr {
+ pub expr: ExprId,
+ pub inside_unsafe_block: bool,
+}
+
+// FIXME: Move this out, its not a diagnostic only thing anymore, and handle unsafe pattern accesses as well
+pub fn unsafe_expressions(
+ db: &dyn HirDatabase,
+ infer: &InferenceResult,
+ def: DefWithBodyId,
+ body: &Body,
+ current: ExprId,
+ unsafe_expr_cb: &mut dyn FnMut(UnsafeExpr),
+) {
+ walk_unsafe(db, infer, def, body, current, false, unsafe_expr_cb)
+}
+
+fn walk_unsafe(
+ db: &dyn HirDatabase,
+ infer: &InferenceResult,
+ def: DefWithBodyId,
+ body: &Body,
+ current: ExprId,
+ inside_unsafe_block: bool,
+ unsafe_expr_cb: &mut dyn FnMut(UnsafeExpr),
+) {
+ let expr = &body.exprs[current];
+ match expr {
+ &Expr::Call { callee, .. } => {
+ if let Some(func) = infer[callee].as_fn_def(db) {
+ if is_fn_unsafe_to_call(db, func) {
+ unsafe_expr_cb(UnsafeExpr { expr: current, inside_unsafe_block });
+ }
+ }
+ }
+ Expr::Path(path) => {
+ let resolver = resolver_for_expr(db.upcast(), def, current);
+ let value_or_partial = resolver.resolve_path_in_value_ns(db.upcast(), path.mod_path());
+ if let Some(ResolveValueResult::ValueNs(ValueNs::StaticId(id))) = value_or_partial {
+ if db.static_data(id).mutable {
+ unsafe_expr_cb(UnsafeExpr { expr: current, inside_unsafe_block });
+ }
+ }
+ }
+ Expr::MethodCall { .. } => {
+ if infer
+ .method_resolution(current)
+ .map(|(func, _)| is_fn_unsafe_to_call(db, func))
+ .unwrap_or(false)
+ {
+ unsafe_expr_cb(UnsafeExpr { expr: current, inside_unsafe_block });
+ }
+ }
+ Expr::UnaryOp { expr, op: UnaryOp::Deref } => {
+ if let TyKind::Raw(..) = &infer[*expr].kind(Interner) {
+ unsafe_expr_cb(UnsafeExpr { expr: current, inside_unsafe_block });
+ }
+ }
+ Expr::Unsafe { body: child } => {
+ return walk_unsafe(db, infer, def, body, *child, true, unsafe_expr_cb);
+ }
+ _ => {}
+ }
+
+ expr.walk_child_exprs(|child| {
+ walk_unsafe(db, infer, def, body, child, inside_unsafe_block, unsafe_expr_cb);
+ });
+}
--- /dev/null
- primitive, subst_prefix, to_assoc_type_id,
+//! The `HirDisplay` trait, which serves two purposes: Turning various bits from
+//! HIR back into source code, and just displaying them for debugging/testing
+//! purposes.
+
+use std::fmt::{self, Debug};
+
+use base_db::CrateId;
+use chalk_ir::BoundVar;
+use hir_def::{
+ body,
+ db::DefDatabase,
+ find_path,
+ generics::{TypeOrConstParamData, TypeParamProvenance},
+ intern::{Internable, Interned},
+ item_scope::ItemInNs,
+ path::{Path, PathKind},
+ type_ref::{ConstScalar, TraitBoundModifier, TypeBound, TypeRef},
+ visibility::Visibility,
+ HasModule, ItemContainerId, Lookup, ModuleId, TraitId,
+};
+use hir_expand::{hygiene::Hygiene, name::Name};
+use itertools::Itertools;
++use smallvec::SmallVec;
+use syntax::SmolStr;
+
+use crate::{
+ db::HirDatabase,
+ from_assoc_type_id, from_foreign_def_id, from_placeholder_idx, lt_from_placeholder_idx,
+ mapping::from_chalk,
- f.write_joined(¶meters.as_slice(Interner)[..total_len], ", ")?;
++ primitive, to_assoc_type_id,
+ utils::{self, generics},
+ AdtId, AliasEq, AliasTy, Binders, CallableDefId, CallableSig, Const, ConstValue, DomainGoal,
+ GenericArg, ImplTraitId, Interner, Lifetime, LifetimeData, LifetimeOutlives, Mutability,
+ OpaqueTy, ProjectionTy, ProjectionTyExt, QuantifiedWhereClause, Scalar, Substitution, TraitRef,
+ TraitRefExt, Ty, TyExt, TyKind, WhereClause,
+};
+
+pub struct HirFormatter<'a> {
+ pub db: &'a dyn HirDatabase,
+ fmt: &'a mut dyn fmt::Write,
+ buf: String,
+ curr_size: usize,
+ pub(crate) max_size: Option<usize>,
+ omit_verbose_types: bool,
+ display_target: DisplayTarget,
+}
+
+pub trait HirDisplay {
+ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError>;
+
+ /// Returns a `Display`able type that is human-readable.
+ fn into_displayable<'a>(
+ &'a self,
+ db: &'a dyn HirDatabase,
+ max_size: Option<usize>,
+ omit_verbose_types: bool,
+ display_target: DisplayTarget,
+ ) -> HirDisplayWrapper<'a, Self>
+ where
+ Self: Sized,
+ {
+ assert!(
+ !matches!(display_target, DisplayTarget::SourceCode { .. }),
+ "HirDisplayWrapper cannot fail with DisplaySourceCodeError, use HirDisplay::hir_fmt directly instead"
+ );
+ HirDisplayWrapper { db, t: self, max_size, omit_verbose_types, display_target }
+ }
+
+ /// Returns a `Display`able type that is human-readable.
+ /// Use this for showing types to the user (e.g. diagnostics)
+ fn display<'a>(&'a self, db: &'a dyn HirDatabase) -> HirDisplayWrapper<'a, Self>
+ where
+ Self: Sized,
+ {
+ HirDisplayWrapper {
+ db,
+ t: self,
+ max_size: None,
+ omit_verbose_types: false,
+ display_target: DisplayTarget::Diagnostics,
+ }
+ }
+
+ /// Returns a `Display`able type that is human-readable and tries to be succinct.
+ /// Use this for showing types to the user where space is constrained (e.g. doc popups)
+ fn display_truncated<'a>(
+ &'a self,
+ db: &'a dyn HirDatabase,
+ max_size: Option<usize>,
+ ) -> HirDisplayWrapper<'a, Self>
+ where
+ Self: Sized,
+ {
+ HirDisplayWrapper {
+ db,
+ t: self,
+ max_size,
+ omit_verbose_types: true,
+ display_target: DisplayTarget::Diagnostics,
+ }
+ }
+
+ /// Returns a String representation of `self` that can be inserted into the given module.
+ /// Use this when generating code (e.g. assists)
+ fn display_source_code<'a>(
+ &'a self,
+ db: &'a dyn HirDatabase,
+ module_id: ModuleId,
+ ) -> Result<String, DisplaySourceCodeError> {
+ let mut result = String::new();
+ match self.hir_fmt(&mut HirFormatter {
+ db,
+ fmt: &mut result,
+ buf: String::with_capacity(20),
+ curr_size: 0,
+ max_size: None,
+ omit_verbose_types: false,
+ display_target: DisplayTarget::SourceCode { module_id },
+ }) {
+ Ok(()) => {}
+ Err(HirDisplayError::FmtError) => panic!("Writing to String can't fail!"),
+ Err(HirDisplayError::DisplaySourceCodeError(e)) => return Err(e),
+ };
+ Ok(result)
+ }
+
+ /// Returns a String representation of `self` for test purposes
+ fn display_test<'a>(&'a self, db: &'a dyn HirDatabase) -> HirDisplayWrapper<'a, Self>
+ where
+ Self: Sized,
+ {
+ HirDisplayWrapper {
+ db,
+ t: self,
+ max_size: None,
+ omit_verbose_types: false,
+ display_target: DisplayTarget::Test,
+ }
+ }
+}
+
+impl<'a> HirFormatter<'a> {
+ pub fn write_joined<T: HirDisplay>(
+ &mut self,
+ iter: impl IntoIterator<Item = T>,
+ sep: &str,
+ ) -> Result<(), HirDisplayError> {
+ let mut first = true;
+ for e in iter {
+ if !first {
+ write!(self, "{}", sep)?;
+ }
+ first = false;
+
+ // Abbreviate multiple omitted types with a single ellipsis.
+ if self.should_truncate() {
+ return write!(self, "{}", TYPE_HINT_TRUNCATION);
+ }
+
+ e.hir_fmt(self)?;
+ }
+ Ok(())
+ }
+
+ /// This allows using the `write!` macro directly with a `HirFormatter`.
+ pub fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> Result<(), HirDisplayError> {
+ // We write to a buffer first to track output size
+ self.buf.clear();
+ fmt::write(&mut self.buf, args)?;
+ self.curr_size += self.buf.len();
+
+ // Then we write to the internal formatter from the buffer
+ self.fmt.write_str(&self.buf).map_err(HirDisplayError::from)
+ }
+
+ pub fn write_str(&mut self, s: &str) -> Result<(), HirDisplayError> {
+ self.fmt.write_str(s)?;
+ Ok(())
+ }
+
+ pub fn write_char(&mut self, c: char) -> Result<(), HirDisplayError> {
+ self.fmt.write_char(c)?;
+ Ok(())
+ }
+
+ pub fn should_truncate(&self) -> bool {
+ match self.max_size {
+ Some(max_size) => self.curr_size >= max_size,
+ None => false,
+ }
+ }
+
+ pub fn omit_verbose_types(&self) -> bool {
+ self.omit_verbose_types
+ }
+}
+
+#[derive(Clone, Copy)]
+pub enum DisplayTarget {
+ /// Display types for inlays, doc popups, autocompletion, etc...
+ /// Showing `{unknown}` or not qualifying paths is fine here.
+ /// There's no reason for this to fail.
+ Diagnostics,
+ /// Display types for inserting them in source files.
+ /// The generated code should compile, so paths need to be qualified.
+ SourceCode { module_id: ModuleId },
+ /// Only for test purpose to keep real types
+ Test,
+}
+
+impl DisplayTarget {
+ fn is_source_code(&self) -> bool {
+ matches!(self, Self::SourceCode { .. })
+ }
+ fn is_test(&self) -> bool {
+ matches!(self, Self::Test)
+ }
+}
+
+#[derive(Debug)]
+pub enum DisplaySourceCodeError {
+ PathNotFound,
+ UnknownType,
+ Closure,
++ Generator,
+}
+
+pub enum HirDisplayError {
+ /// Errors that can occur when generating source code
+ DisplaySourceCodeError(DisplaySourceCodeError),
+ /// `FmtError` is required to be compatible with std::fmt::Display
+ FmtError,
+}
+impl From<fmt::Error> for HirDisplayError {
+ fn from(_: fmt::Error) -> Self {
+ Self::FmtError
+ }
+}
+
+pub struct HirDisplayWrapper<'a, T> {
+ db: &'a dyn HirDatabase,
+ t: &'a T,
+ max_size: Option<usize>,
+ omit_verbose_types: bool,
+ display_target: DisplayTarget,
+}
+
+impl<'a, T> fmt::Display for HirDisplayWrapper<'a, T>
+where
+ T: HirDisplay,
+{
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self.t.hir_fmt(&mut HirFormatter {
+ db: self.db,
+ fmt: f,
+ buf: String::with_capacity(20),
+ curr_size: 0,
+ max_size: self.max_size,
+ omit_verbose_types: self.omit_verbose_types,
+ display_target: self.display_target,
+ }) {
+ Ok(()) => Ok(()),
+ Err(HirDisplayError::FmtError) => Err(fmt::Error),
+ Err(HirDisplayError::DisplaySourceCodeError(_)) => {
+ // This should never happen
+ panic!("HirDisplay::hir_fmt failed with DisplaySourceCodeError when calling Display::fmt!")
+ }
+ }
+ }
+}
+
+const TYPE_HINT_TRUNCATION: &str = "…";
+
+impl<T: HirDisplay> HirDisplay for &'_ T {
+ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
+ HirDisplay::hir_fmt(*self, f)
+ }
+}
+
+impl<T: HirDisplay + Internable> HirDisplay for Interned<T> {
+ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
+ HirDisplay::hir_fmt(self.as_ref(), f)
+ }
+}
+
+impl HirDisplay for ProjectionTy {
+ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
+ if f.should_truncate() {
+ return write!(f, "{}", TYPE_HINT_TRUNCATION);
+ }
+
+ let trait_ = f.db.trait_data(self.trait_(f.db));
+ write!(f, "<")?;
+ self.self_type_parameter(Interner).hir_fmt(f)?;
+ write!(f, " as {}", trait_.name)?;
+ if self.substitution.len(Interner) > 1 {
+ write!(f, "<")?;
+ f.write_joined(&self.substitution.as_slice(Interner)[1..], ", ")?;
+ write!(f, ">")?;
+ }
+ write!(f, ">::{}", f.db.type_alias_data(from_assoc_type_id(self.associated_ty_id)).name)?;
+ Ok(())
+ }
+}
+
+impl HirDisplay for OpaqueTy {
+ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
+ if f.should_truncate() {
+ return write!(f, "{}", TYPE_HINT_TRUNCATION);
+ }
+
+ self.substitution.at(Interner, 0).hir_fmt(f)
+ }
+}
+
+impl HirDisplay for GenericArg {
+ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
+ match self.interned() {
+ crate::GenericArgData::Ty(ty) => ty.hir_fmt(f),
+ crate::GenericArgData::Lifetime(lt) => lt.hir_fmt(f),
+ crate::GenericArgData::Const(c) => c.hir_fmt(f),
+ }
+ }
+}
+
+impl HirDisplay for Const {
+ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
+ let data = self.interned();
+ match data.value {
+ ConstValue::BoundVar(idx) => idx.hir_fmt(f),
+ ConstValue::InferenceVar(..) => write!(f, "#c#"),
+ ConstValue::Placeholder(idx) => {
+ let id = from_placeholder_idx(f.db, idx);
+ let generics = generics(f.db.upcast(), id.parent);
+ let param_data = &generics.params.type_or_consts[id.local_id];
+ write!(f, "{}", param_data.name().unwrap())
+ }
+ ConstValue::Concrete(c) => write!(f, "{}", c.interned),
+ }
+ }
+}
+
+impl HirDisplay for BoundVar {
+ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
+ write!(f, "?{}.{}", self.debruijn.depth(), self.index)
+ }
+}
+
+impl HirDisplay for Ty {
+ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
+ if f.should_truncate() {
+ return write!(f, "{}", TYPE_HINT_TRUNCATION);
+ }
+
+ match self.kind(Interner) {
+ TyKind::Never => write!(f, "!")?,
+ TyKind::Str => write!(f, "str")?,
+ TyKind::Scalar(Scalar::Bool) => write!(f, "bool")?,
+ TyKind::Scalar(Scalar::Char) => write!(f, "char")?,
+ &TyKind::Scalar(Scalar::Float(t)) => write!(f, "{}", primitive::float_ty_to_string(t))?,
+ &TyKind::Scalar(Scalar::Int(t)) => write!(f, "{}", primitive::int_ty_to_string(t))?,
+ &TyKind::Scalar(Scalar::Uint(t)) => write!(f, "{}", primitive::uint_ty_to_string(t))?,
+ TyKind::Slice(t) => {
+ write!(f, "[")?;
+ t.hir_fmt(f)?;
+ write!(f, "]")?;
+ }
+ TyKind::Array(t, c) => {
+ write!(f, "[")?;
+ t.hir_fmt(f)?;
+ write!(f, "; ")?;
+ c.hir_fmt(f)?;
+ write!(f, "]")?;
+ }
+ TyKind::Raw(m, t) | TyKind::Ref(m, _, t) => {
+ if matches!(self.kind(Interner), TyKind::Raw(..)) {
+ write!(
+ f,
+ "*{}",
+ match m {
+ Mutability::Not => "const ",
+ Mutability::Mut => "mut ",
+ }
+ )?;
+ } else {
+ write!(
+ f,
+ "&{}",
+ match m {
+ Mutability::Not => "",
+ Mutability::Mut => "mut ",
+ }
+ )?;
+ }
+
+ // FIXME: all this just to decide whether to use parentheses...
+ let contains_impl_fn = |bounds: &[QuantifiedWhereClause]| {
+ bounds.iter().any(|bound| {
+ if let WhereClause::Implemented(trait_ref) = bound.skip_binders() {
+ let trait_ = trait_ref.hir_trait_id();
+ fn_traits(f.db.upcast(), trait_).any(|it| it == trait_)
+ } else {
+ false
+ }
+ })
+ };
+ let (preds_to_print, has_impl_fn_pred) = match t.kind(Interner) {
+ TyKind::Dyn(dyn_ty) if dyn_ty.bounds.skip_binders().interned().len() > 1 => {
+ let bounds = dyn_ty.bounds.skip_binders().interned();
+ (bounds.len(), contains_impl_fn(bounds))
+ }
+ TyKind::Alias(AliasTy::Opaque(OpaqueTy {
+ opaque_ty_id,
+ substitution: parameters,
+ }))
+ | TyKind::OpaqueType(opaque_ty_id, parameters) => {
+ let impl_trait_id =
+ f.db.lookup_intern_impl_trait_id((*opaque_ty_id).into());
+ if let ImplTraitId::ReturnTypeImplTrait(func, idx) = impl_trait_id {
+ let datas =
+ f.db.return_type_impl_traits(func)
+ .expect("impl trait id without data");
+ let data = (*datas)
+ .as_ref()
+ .map(|rpit| rpit.impl_traits[idx as usize].bounds.clone());
+ let bounds = data.substitute(Interner, parameters);
+ let mut len = bounds.skip_binders().len();
+
+ // Don't count Sized but count when it absent
+ // (i.e. when explicit ?Sized bound is set).
+ let default_sized = SizedByDefault::Sized {
+ anchor: func.lookup(f.db.upcast()).module(f.db.upcast()).krate(),
+ };
+ let sized_bounds = bounds
+ .skip_binders()
+ .iter()
+ .filter(|b| {
+ matches!(
+ b.skip_binders(),
+ WhereClause::Implemented(trait_ref)
+ if default_sized.is_sized_trait(
+ trait_ref.hir_trait_id(),
+ f.db.upcast(),
+ ),
+ )
+ })
+ .count();
+ match sized_bounds {
+ 0 => len += 1,
+ _ => {
+ len = len.saturating_sub(sized_bounds);
+ }
+ }
+
+ (len, contains_impl_fn(bounds.skip_binders()))
+ } else {
+ (0, false)
+ }
+ }
+ _ => (0, false),
+ };
+
+ if has_impl_fn_pred && preds_to_print <= 2 {
+ return t.hir_fmt(f);
+ }
+
+ if preds_to_print > 1 {
+ write!(f, "(")?;
+ t.hir_fmt(f)?;
+ write!(f, ")")?;
+ } else {
+ t.hir_fmt(f)?;
+ }
+ }
+ TyKind::Tuple(_, substs) => {
+ if substs.len(Interner) == 1 {
+ write!(f, "(")?;
+ substs.at(Interner, 0).hir_fmt(f)?;
+ write!(f, ",)")?;
+ } else {
+ write!(f, "(")?;
+ f.write_joined(&*substs.as_slice(Interner), ", ")?;
+ write!(f, ")")?;
+ }
+ }
+ TyKind::Function(fn_ptr) => {
+ let sig = CallableSig::from_fn_ptr(fn_ptr);
+ sig.hir_fmt(f)?;
+ }
+ TyKind::FnDef(def, parameters) => {
+ let def = from_chalk(f.db, *def);
+ let sig = f.db.callable_item_signature(def).substitute(Interner, parameters);
+ match def {
+ CallableDefId::FunctionId(ff) => {
+ write!(f, "fn {}", f.db.function_data(ff).name)?
+ }
+ CallableDefId::StructId(s) => write!(f, "{}", f.db.struct_data(s).name)?,
+ CallableDefId::EnumVariantId(e) => {
+ write!(f, "{}", f.db.enum_data(e.parent).variants[e.local_id].name)?
+ }
+ };
+ if parameters.len(Interner) > 0 {
+ let generics = generics(f.db.upcast(), def.into());
+ let (parent_params, self_param, type_params, const_params, _impl_trait_params) =
+ generics.provenance_split();
+ let total_len = parent_params + self_param + type_params + const_params;
+ // We print all params except implicit impl Trait params. Still a bit weird; should we leave out parent and self?
+ if total_len > 0 {
++ // `parameters` are in the order of fn's params (including impl traits),
++ // parent's params (those from enclosing impl or trait, if any).
++ let parameters = parameters.as_slice(Interner);
++ let fn_params_len = self_param + type_params + const_params;
++ let fn_params = parameters.get(..fn_params_len);
++ let parent_params = parameters.get(parameters.len() - parent_params..);
++ let params = parent_params.into_iter().chain(fn_params).flatten();
+ write!(f, "<")?;
- let actual_default = default_parameter
- .clone()
- .substitute(Interner, &subst_prefix(parameters, i));
++ f.write_joined(params, ", ")?;
+ write!(f, ">")?;
+ }
+ }
+ write!(f, "(")?;
+ f.write_joined(sig.params(), ", ")?;
+ write!(f, ")")?;
+ let ret = sig.ret();
+ if !ret.is_unit() {
+ write!(f, " -> ")?;
+ ret.hir_fmt(f)?;
+ }
+ }
+ TyKind::Adt(AdtId(def_id), parameters) => {
+ match f.display_target {
+ DisplayTarget::Diagnostics | DisplayTarget::Test => {
+ let name = match *def_id {
+ hir_def::AdtId::StructId(it) => f.db.struct_data(it).name.clone(),
+ hir_def::AdtId::UnionId(it) => f.db.union_data(it).name.clone(),
+ hir_def::AdtId::EnumId(it) => f.db.enum_data(it).name.clone(),
+ };
+ write!(f, "{}", name)?;
+ }
+ DisplayTarget::SourceCode { module_id } => {
+ if let Some(path) = find_path::find_path(
+ f.db.upcast(),
+ ItemInNs::Types((*def_id).into()),
+ module_id,
+ false,
+ ) {
+ write!(f, "{}", path)?;
+ } else {
+ return Err(HirDisplayError::DisplaySourceCodeError(
+ DisplaySourceCodeError::PathNotFound,
+ ));
+ }
+ }
+ }
+
+ if parameters.len(Interner) > 0 {
+ let parameters_to_write = if f.display_target.is_source_code()
+ || f.omit_verbose_types()
+ {
+ match self
+ .as_generic_def(f.db)
+ .map(|generic_def_id| f.db.generic_defaults(generic_def_id))
+ .filter(|defaults| !defaults.is_empty())
+ {
+ None => parameters.as_slice(Interner),
+ Some(default_parameters) => {
+ fn should_show(
+ parameter: &GenericArg,
+ default_parameters: &[Binders<GenericArg>],
+ i: usize,
+ parameters: &Substitution,
+ ) -> bool {
+ if parameter.ty(Interner).map(|x| x.kind(Interner))
+ == Some(&TyKind::Error)
+ {
+ return true;
+ }
+ if let Some(ConstValue::Concrete(c)) =
+ parameter.constant(Interner).map(|x| x.data(Interner).value)
+ {
+ if c.interned == ConstScalar::Unknown {
+ return true;
+ }
+ }
+ let default_parameter = match default_parameters.get(i) {
+ Some(x) => x,
+ None => return true,
+ };
- TyKind::Generator(..) => write!(f, "{{generator}}")?,
++ let actual_default =
++ default_parameter.clone().substitute(Interner, ¶meters);
+ parameter != &actual_default
+ }
+ let mut default_from = 0;
+ for (i, parameter) in parameters.iter(Interner).enumerate() {
+ if should_show(parameter, &default_parameters, i, parameters) {
+ default_from = i + 1;
+ }
+ }
+ ¶meters.as_slice(Interner)[0..default_from]
+ }
+ }
+ } else {
+ parameters.as_slice(Interner)
+ };
+ if !parameters_to_write.is_empty() {
+ write!(f, "<")?;
+
+ if f.display_target.is_source_code() {
+ let mut first = true;
+ for generic_arg in parameters_to_write {
+ if !first {
+ write!(f, ", ")?;
+ }
+ first = false;
+
+ if generic_arg.ty(Interner).map(|ty| ty.kind(Interner))
+ == Some(&TyKind::Error)
+ {
+ write!(f, "_")?;
+ } else {
+ generic_arg.hir_fmt(f)?;
+ }
+ }
+ } else {
+ f.write_joined(parameters_to_write, ", ")?;
+ }
+
+ write!(f, ">")?;
+ }
+ }
+ }
+ TyKind::AssociatedType(assoc_type_id, parameters) => {
+ let type_alias = from_assoc_type_id(*assoc_type_id);
+ let trait_ = match type_alias.lookup(f.db.upcast()).container {
+ ItemContainerId::TraitId(it) => it,
+ _ => panic!("not an associated type"),
+ };
+ let trait_ = f.db.trait_data(trait_);
+ let type_alias_data = f.db.type_alias_data(type_alias);
+
+ // Use placeholder associated types when the target is test (https://rust-lang.github.io/chalk/book/clauses/type_equality.html#placeholder-associated-types)
+ if f.display_target.is_test() {
+ write!(f, "{}::{}", trait_.name, type_alias_data.name)?;
+ if parameters.len(Interner) > 0 {
+ write!(f, "<")?;
+ f.write_joined(&*parameters.as_slice(Interner), ", ")?;
+ write!(f, ">")?;
+ }
+ } else {
+ let projection_ty = ProjectionTy {
+ associated_ty_id: to_assoc_type_id(type_alias),
+ substitution: parameters.clone(),
+ };
+
+ projection_ty.hir_fmt(f)?;
+ }
+ }
+ TyKind::Foreign(type_alias) => {
+ let type_alias = f.db.type_alias_data(from_foreign_def_id(*type_alias));
+ write!(f, "{}", type_alias.name)?;
+ }
+ TyKind::OpaqueType(opaque_ty_id, parameters) => {
+ let impl_trait_id = f.db.lookup_intern_impl_trait_id((*opaque_ty_id).into());
+ match impl_trait_id {
+ ImplTraitId::ReturnTypeImplTrait(func, idx) => {
+ let datas =
+ f.db.return_type_impl_traits(func).expect("impl trait id without data");
+ let data = (*datas)
+ .as_ref()
+ .map(|rpit| rpit.impl_traits[idx as usize].bounds.clone());
+ let bounds = data.substitute(Interner, ¶meters);
+ let krate = func.lookup(f.db.upcast()).module(f.db.upcast()).krate();
+ write_bounds_like_dyn_trait_with_prefix(
+ "impl",
+ bounds.skip_binders(),
+ SizedByDefault::Sized { anchor: krate },
+ f,
+ )?;
+ // FIXME: it would maybe be good to distinguish this from the alias type (when debug printing), and to show the substitution
+ }
+ ImplTraitId::AsyncBlockTypeImplTrait(..) => {
+ write!(f, "impl Future<Output = ")?;
+ parameters.at(Interner, 0).hir_fmt(f)?;
+ write!(f, ">")?;
+ }
+ }
+ }
+ TyKind::Closure(.., substs) => {
+ if f.display_target.is_source_code() {
+ return Err(HirDisplayError::DisplaySourceCodeError(
+ DisplaySourceCodeError::Closure,
+ ));
+ }
+ let sig = substs.at(Interner, 0).assert_ty_ref(Interner).callable_sig(f.db);
+ if let Some(sig) = sig {
+ if sig.params().is_empty() {
+ write!(f, "||")?;
+ } else if f.should_truncate() {
+ write!(f, "|{}|", TYPE_HINT_TRUNCATION)?;
+ } else {
+ write!(f, "|")?;
+ f.write_joined(sig.params(), ", ")?;
+ write!(f, "|")?;
+ };
+
+ write!(f, " -> ")?;
+ sig.ret().hir_fmt(f)?;
+ } else {
+ write!(f, "{{closure}}")?;
+ }
+ }
+ TyKind::Placeholder(idx) => {
+ let id = from_placeholder_idx(f.db, *idx);
+ let generics = generics(f.db.upcast(), id.parent);
+ let param_data = &generics.params.type_or_consts[id.local_id];
+ match param_data {
+ TypeOrConstParamData::TypeParamData(p) => match p.provenance {
+ TypeParamProvenance::TypeParamList | TypeParamProvenance::TraitSelf => {
+ write!(f, "{}", p.name.clone().unwrap_or_else(Name::missing))?
+ }
+ TypeParamProvenance::ArgumentImplTrait => {
+ let substs = generics.placeholder_subst(f.db);
+ let bounds =
+ f.db.generic_predicates(id.parent)
+ .iter()
+ .map(|pred| pred.clone().substitute(Interner, &substs))
+ .filter(|wc| match &wc.skip_binders() {
+ WhereClause::Implemented(tr) => {
+ &tr.self_type_parameter(Interner) == self
+ }
+ WhereClause::AliasEq(AliasEq {
+ alias: AliasTy::Projection(proj),
+ ty: _,
+ }) => &proj.self_type_parameter(Interner) == self,
+ _ => false,
+ })
+ .collect::<Vec<_>>();
+ let krate = id.parent.module(f.db.upcast()).krate();
+ write_bounds_like_dyn_trait_with_prefix(
+ "impl",
+ &bounds,
+ SizedByDefault::Sized { anchor: krate },
+ f,
+ )?;
+ }
+ },
+ TypeOrConstParamData::ConstParamData(p) => {
+ write!(f, "{}", p.name)?;
+ }
+ }
+ }
+ TyKind::BoundVar(idx) => idx.hir_fmt(f)?,
+ TyKind::Dyn(dyn_ty) => {
+ write_bounds_like_dyn_trait_with_prefix(
+ "dyn",
+ dyn_ty.bounds.skip_binders().interned(),
+ SizedByDefault::NotSized,
+ f,
+ )?;
+ }
+ TyKind::Alias(AliasTy::Projection(p_ty)) => p_ty.hir_fmt(f)?,
+ TyKind::Alias(AliasTy::Opaque(opaque_ty)) => {
+ let impl_trait_id = f.db.lookup_intern_impl_trait_id(opaque_ty.opaque_ty_id.into());
+ match impl_trait_id {
+ ImplTraitId::ReturnTypeImplTrait(func, idx) => {
+ let datas =
+ f.db.return_type_impl_traits(func).expect("impl trait id without data");
+ let data = (*datas)
+ .as_ref()
+ .map(|rpit| rpit.impl_traits[idx as usize].bounds.clone());
+ let bounds = data.substitute(Interner, &opaque_ty.substitution);
+ let krate = func.lookup(f.db.upcast()).module(f.db.upcast()).krate();
+ write_bounds_like_dyn_trait_with_prefix(
+ "impl",
+ bounds.skip_binders(),
+ SizedByDefault::Sized { anchor: krate },
+ f,
+ )?;
+ }
+ ImplTraitId::AsyncBlockTypeImplTrait(..) => {
+ write!(f, "{{async block}}")?;
+ }
+ };
+ }
+ TyKind::Error => {
+ if f.display_target.is_source_code() {
+ return Err(HirDisplayError::DisplaySourceCodeError(
+ DisplaySourceCodeError::UnknownType,
+ ));
+ }
+ write!(f, "{{unknown}}")?;
+ }
+ TyKind::InferenceVar(..) => write!(f, "_")?,
++ TyKind::Generator(_, subst) => {
++ if f.display_target.is_source_code() {
++ return Err(HirDisplayError::DisplaySourceCodeError(
++ DisplaySourceCodeError::Generator,
++ ));
++ }
++
++ let subst = subst.as_slice(Interner);
++ let a: Option<SmallVec<[&Ty; 3]>> = subst
++ .get(subst.len() - 3..)
++ .map(|args| args.iter().map(|arg| arg.ty(Interner)).collect())
++ .flatten();
++
++ if let Some([resume_ty, yield_ty, ret_ty]) = a.as_deref() {
++ write!(f, "|")?;
++ resume_ty.hir_fmt(f)?;
++ write!(f, "|")?;
++
++ write!(f, " yields ")?;
++ yield_ty.hir_fmt(f)?;
++
++ write!(f, " -> ")?;
++ ret_ty.hir_fmt(f)?;
++ } else {
++ // This *should* be unreachable, but fallback just in case.
++ write!(f, "{{generator}}")?;
++ }
++ }
+ TyKind::GeneratorWitness(..) => write!(f, "{{generator witness}}")?,
+ }
+ Ok(())
+ }
+}
+
+impl HirDisplay for CallableSig {
+ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
+ write!(f, "fn(")?;
+ f.write_joined(self.params(), ", ")?;
+ if self.is_varargs {
+ if self.params().is_empty() {
+ write!(f, "...")?;
+ } else {
+ write!(f, ", ...")?;
+ }
+ }
+ write!(f, ")")?;
+ let ret = self.ret();
+ if !ret.is_unit() {
+ write!(f, " -> ")?;
+ ret.hir_fmt(f)?;
+ }
+ Ok(())
+ }
+}
+
+fn fn_traits(db: &dyn DefDatabase, trait_: TraitId) -> impl Iterator<Item = TraitId> {
+ let krate = trait_.lookup(db).container.krate();
+ utils::fn_traits(db, krate)
+}
+
+#[derive(Clone, Copy, PartialEq, Eq)]
+pub enum SizedByDefault {
+ NotSized,
+ Sized { anchor: CrateId },
+}
+
+impl SizedByDefault {
+ fn is_sized_trait(self, trait_: TraitId, db: &dyn DefDatabase) -> bool {
+ match self {
+ Self::NotSized => false,
+ Self::Sized { anchor } => {
+ let sized_trait = db
+ .lang_item(anchor, SmolStr::new_inline("sized"))
+ .and_then(|lang_item| lang_item.as_trait());
+ Some(trait_) == sized_trait
+ }
+ }
+ }
+}
+
+pub fn write_bounds_like_dyn_trait_with_prefix(
+ prefix: &str,
+ predicates: &[QuantifiedWhereClause],
+ default_sized: SizedByDefault,
+ f: &mut HirFormatter<'_>,
+) -> Result<(), HirDisplayError> {
+ write!(f, "{}", prefix)?;
+ if !predicates.is_empty()
+ || predicates.is_empty() && matches!(default_sized, SizedByDefault::Sized { .. })
+ {
+ write!(f, " ")?;
+ write_bounds_like_dyn_trait(predicates, default_sized, f)
+ } else {
+ Ok(())
+ }
+}
+
+fn write_bounds_like_dyn_trait(
+ predicates: &[QuantifiedWhereClause],
+ default_sized: SizedByDefault,
+ f: &mut HirFormatter<'_>,
+) -> Result<(), HirDisplayError> {
+ // Note: This code is written to produce nice results (i.e.
+ // corresponding to surface Rust) for types that can occur in
+ // actual Rust. It will have weird results if the predicates
+ // aren't as expected (i.e. self types = $0, projection
+ // predicates for a certain trait come after the Implemented
+ // predicate for that trait).
+ let mut first = true;
+ let mut angle_open = false;
+ let mut is_fn_trait = false;
+ let mut is_sized = false;
+ for p in predicates.iter() {
+ match p.skip_binders() {
+ WhereClause::Implemented(trait_ref) => {
+ let trait_ = trait_ref.hir_trait_id();
+ if default_sized.is_sized_trait(trait_, f.db.upcast()) {
+ is_sized = true;
+ if matches!(default_sized, SizedByDefault::Sized { .. }) {
+ // Don't print +Sized, but rather +?Sized if absent.
+ continue;
+ }
+ }
+ if !is_fn_trait {
+ is_fn_trait = fn_traits(f.db.upcast(), trait_).any(|it| it == trait_);
+ }
+ if !is_fn_trait && angle_open {
+ write!(f, ">")?;
+ angle_open = false;
+ }
+ if !first {
+ write!(f, " + ")?;
+ }
+ // We assume that the self type is ^0.0 (i.e. the
+ // existential) here, which is the only thing that's
+ // possible in actual Rust, and hence don't print it
+ write!(f, "{}", f.db.trait_data(trait_).name)?;
+ if let [_, params @ ..] = &*trait_ref.substitution.as_slice(Interner) {
+ if is_fn_trait {
+ if let Some(args) =
+ params.first().and_then(|it| it.assert_ty_ref(Interner).as_tuple())
+ {
+ write!(f, "(")?;
+ f.write_joined(args.as_slice(Interner), ", ")?;
+ write!(f, ")")?;
+ }
+ } else if !params.is_empty() {
+ write!(f, "<")?;
+ f.write_joined(params, ", ")?;
+ // there might be assoc type bindings, so we leave the angle brackets open
+ angle_open = true;
+ }
+ }
+ }
+ WhereClause::AliasEq(alias_eq) if is_fn_trait => {
+ is_fn_trait = false;
+ if !alias_eq.ty.is_unit() {
+ write!(f, " -> ")?;
+ alias_eq.ty.hir_fmt(f)?;
+ }
+ }
+ WhereClause::AliasEq(AliasEq { ty, alias }) => {
+ // in types in actual Rust, these will always come
+ // after the corresponding Implemented predicate
+ if angle_open {
+ write!(f, ", ")?;
+ } else {
+ write!(f, "<")?;
+ angle_open = true;
+ }
+ if let AliasTy::Projection(proj) = alias {
+ let type_alias =
+ f.db.type_alias_data(from_assoc_type_id(proj.associated_ty_id));
+ write!(f, "{} = ", type_alias.name)?;
+ }
+ ty.hir_fmt(f)?;
+ }
+
+ // FIXME implement these
+ WhereClause::LifetimeOutlives(_) => {}
+ WhereClause::TypeOutlives(_) => {}
+ }
+ first = false;
+ }
+ if angle_open {
+ write!(f, ">")?;
+ }
+ if matches!(default_sized, SizedByDefault::Sized { .. }) {
+ if !is_sized {
+ write!(f, "{}?Sized", if first { "" } else { " + " })?;
+ } else if first {
+ write!(f, "Sized")?;
+ }
+ }
+ Ok(())
+}
+
+fn fmt_trait_ref(
+ tr: &TraitRef,
+ f: &mut HirFormatter<'_>,
+ use_as: bool,
+) -> Result<(), HirDisplayError> {
+ if f.should_truncate() {
+ return write!(f, "{}", TYPE_HINT_TRUNCATION);
+ }
+
+ tr.self_type_parameter(Interner).hir_fmt(f)?;
+ if use_as {
+ write!(f, " as ")?;
+ } else {
+ write!(f, ": ")?;
+ }
+ write!(f, "{}", f.db.trait_data(tr.hir_trait_id()).name)?;
+ if tr.substitution.len(Interner) > 1 {
+ write!(f, "<")?;
+ f.write_joined(&tr.substitution.as_slice(Interner)[1..], ", ")?;
+ write!(f, ">")?;
+ }
+ Ok(())
+}
+
+impl HirDisplay for TraitRef {
+ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
+ fmt_trait_ref(self, f, false)
+ }
+}
+
+impl HirDisplay for WhereClause {
+ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
+ if f.should_truncate() {
+ return write!(f, "{}", TYPE_HINT_TRUNCATION);
+ }
+
+ match self {
+ WhereClause::Implemented(trait_ref) => trait_ref.hir_fmt(f)?,
+ WhereClause::AliasEq(AliasEq { alias: AliasTy::Projection(projection_ty), ty }) => {
+ write!(f, "<")?;
+ fmt_trait_ref(&projection_ty.trait_ref(f.db), f, true)?;
+ write!(
+ f,
+ ">::{} = ",
+ f.db.type_alias_data(from_assoc_type_id(projection_ty.associated_ty_id)).name,
+ )?;
+ ty.hir_fmt(f)?;
+ }
+ WhereClause::AliasEq(_) => write!(f, "{{error}}")?,
+
+ // FIXME implement these
+ WhereClause::TypeOutlives(..) => {}
+ WhereClause::LifetimeOutlives(..) => {}
+ }
+ Ok(())
+ }
+}
+
+impl HirDisplay for LifetimeOutlives {
+ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
+ self.a.hir_fmt(f)?;
+ write!(f, ": ")?;
+ self.b.hir_fmt(f)
+ }
+}
+
+impl HirDisplay for Lifetime {
+ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
+ self.interned().hir_fmt(f)
+ }
+}
+
+impl HirDisplay for LifetimeData {
+ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
+ match self {
+ LifetimeData::BoundVar(idx) => idx.hir_fmt(f),
+ LifetimeData::InferenceVar(_) => write!(f, "_"),
+ LifetimeData::Placeholder(idx) => {
+ let id = lt_from_placeholder_idx(f.db, *idx);
+ let generics = generics(f.db.upcast(), id.parent);
+ let param_data = &generics.params.lifetimes[id.local_id];
+ write!(f, "{}", param_data.name)
+ }
+ LifetimeData::Static => write!(f, "'static"),
+ LifetimeData::Empty(_) => Ok(()),
+ LifetimeData::Erased => Ok(()),
+ LifetimeData::Phantom(_, _) => Ok(()),
+ }
+ }
+}
+
+impl HirDisplay for DomainGoal {
+ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
+ match self {
+ DomainGoal::Holds(wc) => {
+ write!(f, "Holds(")?;
+ wc.hir_fmt(f)?;
+ write!(f, ")")?;
+ }
+ _ => write!(f, "?")?,
+ }
+ Ok(())
+ }
+}
+
+pub fn write_visibility(
+ module_id: ModuleId,
+ vis: Visibility,
+ f: &mut HirFormatter<'_>,
+) -> Result<(), HirDisplayError> {
+ match vis {
+ Visibility::Public => write!(f, "pub "),
+ Visibility::Module(vis_id) => {
+ let def_map = module_id.def_map(f.db.upcast());
+ let root_module_id = def_map.module_id(def_map.root());
+ if vis_id == module_id {
+ // pub(self) or omitted
+ Ok(())
+ } else if root_module_id == vis_id {
+ write!(f, "pub(crate) ")
+ } else if module_id.containing_module(f.db.upcast()) == Some(vis_id) {
+ write!(f, "pub(super) ")
+ } else {
+ write!(f, "pub(in ...) ")
+ }
+ }
+ }
+}
+
+impl HirDisplay for TypeRef {
+ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
+ match self {
+ TypeRef::Never => write!(f, "!")?,
+ TypeRef::Placeholder => write!(f, "_")?,
+ TypeRef::Tuple(elems) => {
+ write!(f, "(")?;
+ f.write_joined(elems, ", ")?;
+ if elems.len() == 1 {
+ write!(f, ",")?;
+ }
+ write!(f, ")")?;
+ }
+ TypeRef::Path(path) => path.hir_fmt(f)?,
+ TypeRef::RawPtr(inner, mutability) => {
+ let mutability = match mutability {
+ hir_def::type_ref::Mutability::Shared => "*const ",
+ hir_def::type_ref::Mutability::Mut => "*mut ",
+ };
+ write!(f, "{}", mutability)?;
+ inner.hir_fmt(f)?;
+ }
+ TypeRef::Reference(inner, lifetime, mutability) => {
+ let mutability = match mutability {
+ hir_def::type_ref::Mutability::Shared => "",
+ hir_def::type_ref::Mutability::Mut => "mut ",
+ };
+ write!(f, "&")?;
+ if let Some(lifetime) = lifetime {
+ write!(f, "{} ", lifetime.name)?;
+ }
+ write!(f, "{}", mutability)?;
+ inner.hir_fmt(f)?;
+ }
+ TypeRef::Array(inner, len) => {
+ write!(f, "[")?;
+ inner.hir_fmt(f)?;
+ write!(f, "; {}]", len)?;
+ }
+ TypeRef::Slice(inner) => {
+ write!(f, "[")?;
+ inner.hir_fmt(f)?;
+ write!(f, "]")?;
+ }
+ TypeRef::Fn(parameters, is_varargs) => {
+ // FIXME: Function pointer qualifiers.
+ write!(f, "fn(")?;
+ if let Some(((_, return_type), function_parameters)) = parameters.split_last() {
+ for index in 0..function_parameters.len() {
+ let (param_name, param_type) = &function_parameters[index];
+ if let Some(name) = param_name {
+ write!(f, "{}: ", name)?;
+ }
+
+ param_type.hir_fmt(f)?;
+
+ if index != function_parameters.len() - 1 {
+ write!(f, ", ")?;
+ }
+ }
+ if *is_varargs {
+ write!(f, "{}...", if parameters.len() == 1 { "" } else { ", " })?;
+ }
+ write!(f, ")")?;
+ match &return_type {
+ TypeRef::Tuple(tup) if tup.is_empty() => {}
+ _ => {
+ write!(f, " -> ")?;
+ return_type.hir_fmt(f)?;
+ }
+ }
+ }
+ }
+ TypeRef::ImplTrait(bounds) => {
+ write!(f, "impl ")?;
+ f.write_joined(bounds, " + ")?;
+ }
+ TypeRef::DynTrait(bounds) => {
+ write!(f, "dyn ")?;
+ f.write_joined(bounds, " + ")?;
+ }
+ TypeRef::Macro(macro_call) => {
+ let macro_call = macro_call.to_node(f.db.upcast());
+ let ctx = body::LowerCtx::with_hygiene(f.db.upcast(), &Hygiene::new_unhygienic());
+ match macro_call.path() {
+ Some(path) => match Path::from_src(path, &ctx) {
+ Some(path) => path.hir_fmt(f)?,
+ None => write!(f, "{{macro}}")?,
+ },
+ None => write!(f, "{{macro}}")?,
+ }
+ write!(f, "!(..)")?;
+ }
+ TypeRef::Error => write!(f, "{{error}}")?,
+ }
+ Ok(())
+ }
+}
+
+impl HirDisplay for TypeBound {
+ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
+ match self {
+ TypeBound::Path(path, modifier) => {
+ match modifier {
+ TraitBoundModifier::None => (),
+ TraitBoundModifier::Maybe => write!(f, "?")?,
+ }
+ path.hir_fmt(f)
+ }
+ TypeBound::Lifetime(lifetime) => write!(f, "{}", lifetime.name),
+ TypeBound::ForLifetime(lifetimes, path) => {
+ write!(f, "for<{}> ", lifetimes.iter().format(", "))?;
+ path.hir_fmt(f)
+ }
+ TypeBound::Error => write!(f, "{{error}}"),
+ }
+ }
+}
+
+impl HirDisplay for Path {
+ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
+ match (self.type_anchor(), self.kind()) {
+ (Some(anchor), _) => {
+ write!(f, "<")?;
+ anchor.hir_fmt(f)?;
+ write!(f, ">")?;
+ }
+ (_, PathKind::Plain) => {}
+ (_, PathKind::Abs) => {}
+ (_, PathKind::Crate) => write!(f, "crate")?,
+ (_, PathKind::Super(0)) => write!(f, "self")?,
+ (_, PathKind::Super(n)) => {
+ for i in 0..*n {
+ if i > 0 {
+ write!(f, "::")?;
+ }
+ write!(f, "super")?;
+ }
+ }
+ (_, PathKind::DollarCrate(id)) => {
+ // Resolve `$crate` to the crate's display name.
+ // FIXME: should use the dependency name instead if available, but that depends on
+ // the crate invoking `HirDisplay`
+ let crate_graph = f.db.crate_graph();
+ let name = crate_graph[*id]
+ .display_name
+ .as_ref()
+ .map(|name| name.canonical_name())
+ .unwrap_or("$crate");
+ write!(f, "{name}")?
+ }
+ }
+
+ for (seg_idx, segment) in self.segments().iter().enumerate() {
+ if !matches!(self.kind(), PathKind::Plain) || seg_idx > 0 {
+ write!(f, "::")?;
+ }
+ write!(f, "{}", segment.name)?;
+ if let Some(generic_args) = segment.args_and_bindings {
+ // We should be in type context, so format as `Foo<Bar>` instead of `Foo::<Bar>`.
+ // Do we actually format expressions?
+ if generic_args.desugared_from_fn {
+ // First argument will be a tuple, which already includes the parentheses.
+ // If the tuple only contains 1 item, write it manually to avoid the trailing `,`.
+ if let hir_def::path::GenericArg::Type(TypeRef::Tuple(v)) =
+ &generic_args.args[0]
+ {
+ if v.len() == 1 {
+ write!(f, "(")?;
+ v[0].hir_fmt(f)?;
+ write!(f, ")")?;
+ } else {
+ generic_args.args[0].hir_fmt(f)?;
+ }
+ }
+ if let Some(ret) = &generic_args.bindings[0].type_ref {
+ if !matches!(ret, TypeRef::Tuple(v) if v.is_empty()) {
+ write!(f, " -> ")?;
+ ret.hir_fmt(f)?;
+ }
+ }
+ return Ok(());
+ }
+
+ write!(f, "<")?;
+ let mut first = true;
+ for arg in &generic_args.args {
+ if first {
+ first = false;
+ if generic_args.has_self_type {
+ // FIXME: Convert to `<Ty as Trait>` form.
+ write!(f, "Self = ")?;
+ }
+ } else {
+ write!(f, ", ")?;
+ }
+ arg.hir_fmt(f)?;
+ }
+ for binding in &generic_args.bindings {
+ if first {
+ first = false;
+ } else {
+ write!(f, ", ")?;
+ }
+ write!(f, "{}", binding.name)?;
+ match &binding.type_ref {
+ Some(ty) => {
+ write!(f, " = ")?;
+ ty.hir_fmt(f)?
+ }
+ None => {
+ write!(f, ": ")?;
+ f.write_joined(&binding.bounds, " + ")?;
+ }
+ }
+ }
+ write!(f, ">")?;
+ }
+ }
+ Ok(())
+ }
+}
+
+impl HirDisplay for hir_def::path::GenericArg {
+ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
+ match self {
+ hir_def::path::GenericArg::Type(ty) => ty.hir_fmt(f),
+ hir_def::path::GenericArg::Const(c) => write!(f, "{}", c),
+ hir_def::path::GenericArg::Lifetime(lifetime) => write!(f, "{}", lifetime.name),
+ }
+ }
+}
--- /dev/null
- AdtId, AssocItemId, DefWithBodyId, EnumVariantId, FieldId, FunctionId, HasModule, Lookup,
- TraitId, TypeAliasId, VariantId,
+//! Type inference, i.e. the process of walking through the code and determining
+//! the type of each expression and pattern.
+//!
+//! For type inference, compare the implementations in rustc (the various
+//! check_* methods in rustc_hir_analysis/check/mod.rs are a good entry point) and
+//! IntelliJ-Rust (org.rust.lang.core.types.infer). Our entry point for
+//! inference here is the `infer` function, which infers the types of all
+//! expressions in a given function.
+//!
+//! During inference, types (i.e. the `Ty` struct) can contain type 'variables'
+//! which represent currently unknown types; as we walk through the expressions,
+//! we might determine that certain variables need to be equal to each other, or
+//! to certain types. To record this, we use the union-find implementation from
+//! the `ena` crate, which is extracted from rustc.
+
+use std::ops::Index;
+use std::sync::Arc;
+
+use chalk_ir::{cast::Cast, ConstValue, DebruijnIndex, Mutability, Safety, Scalar, TypeFlags};
+use hir_def::{
+ body::Body,
++ builtin_type::BuiltinType,
+ data::{ConstData, StaticData},
+ expr::{BindingAnnotation, ExprId, PatId},
+ lang_item::LangItemTarget,
+ path::{path, Path},
+ resolver::{HasResolver, ResolveValueResult, Resolver, TypeNs, ValueNs},
+ type_ref::TypeRef,
- /// Interned Unknown to return references to.
++ AdtId, AssocItemId, DefWithBodyId, EnumVariantId, FieldId, FunctionId, HasModule,
++ ItemContainerId, Lookup, TraitId, TypeAliasId, VariantId,
+};
+use hir_expand::name::{name, Name};
+use itertools::Either;
+use la_arena::ArenaMap;
+use rustc_hash::FxHashMap;
+use stdx::{always, impl_from};
+
+use crate::{
+ db::HirDatabase, fold_tys, fold_tys_and_consts, infer::coerce::CoerceMany,
+ lower::ImplTraitLoweringMode, to_assoc_type_id, AliasEq, AliasTy, Const, DomainGoal,
+ GenericArg, Goal, ImplTraitId, InEnvironment, Interner, ProjectionTy, Substitution,
+ TraitEnvironment, TraitRef, Ty, TyBuilder, TyExt, TyKind,
+};
+
+// This lint has a false positive here. See the link below for details.
+//
+// https://github.com/rust-lang/rust/issues/57411
+#[allow(unreachable_pub)]
+pub use coerce::could_coerce;
+#[allow(unreachable_pub)]
+pub use unify::could_unify;
+
+pub(crate) mod unify;
+mod path;
+mod expr;
+mod pat;
+mod coerce;
+mod closure;
+
+/// The entry point of type inference.
+pub(crate) fn infer_query(db: &dyn HirDatabase, def: DefWithBodyId) -> Arc<InferenceResult> {
+ let _p = profile::span("infer_query");
+ let resolver = def.resolver(db.upcast());
+ let body = db.body(def);
+ let mut ctx = InferenceContext::new(db, def, &body, resolver);
+
+ match def {
+ DefWithBodyId::ConstId(c) => ctx.collect_const(&db.const_data(c)),
+ DefWithBodyId::FunctionId(f) => ctx.collect_fn(f),
+ DefWithBodyId::StaticId(s) => ctx.collect_static(&db.static_data(s)),
++ DefWithBodyId::VariantId(v) => {
++ ctx.return_ty = TyBuilder::builtin(match db.enum_data(v.parent).variant_body_type() {
++ Either::Left(builtin) => BuiltinType::Int(builtin),
++ Either::Right(builtin) => BuiltinType::Uint(builtin),
++ });
++ }
+ }
+
+ ctx.infer_body();
+
+ Arc::new(ctx.resolve_all())
+}
+
+/// Fully normalize all the types found within `ty` in context of `owner` body definition.
+///
+/// This is appropriate to use only after type-check: it assumes
+/// that normalization will succeed, for example.
+pub(crate) fn normalize(db: &dyn HirDatabase, owner: DefWithBodyId, ty: Ty) -> Ty {
+ if !ty.data(Interner).flags.intersects(TypeFlags::HAS_PROJECTION) {
+ return ty;
+ }
+ let krate = owner.module(db.upcast()).krate();
+ let trait_env = owner
+ .as_generic_def_id()
+ .map_or_else(|| Arc::new(TraitEnvironment::empty(krate)), |d| db.trait_environment(d));
+ let mut table = unify::InferenceTable::new(db, trait_env);
+
+ let ty_with_vars = table.normalize_associated_types_in(ty);
+ table.resolve_obligations_as_possible();
+ table.propagate_diverging_flag();
+ table.resolve_completely(ty_with_vars)
+}
+
+#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
+enum ExprOrPatId {
+ ExprId(ExprId),
+ PatId(PatId),
+}
+impl_from!(ExprId, PatId for ExprOrPatId);
+
+/// Binding modes inferred for patterns.
+/// <https://doc.rust-lang.org/reference/patterns.html#binding-modes>
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub enum BindingMode {
+ Move,
+ Ref(Mutability),
+}
+
+impl BindingMode {
+ fn convert(annotation: BindingAnnotation) -> BindingMode {
+ match annotation {
+ BindingAnnotation::Unannotated | BindingAnnotation::Mutable => BindingMode::Move,
+ BindingAnnotation::Ref => BindingMode::Ref(Mutability::Not),
+ BindingAnnotation::RefMut => BindingMode::Ref(Mutability::Mut),
+ }
+ }
+}
+
+impl Default for BindingMode {
+ fn default() -> Self {
+ BindingMode::Move
+ }
+}
+
+/// Used to generalize patterns and assignee expressions.
+trait PatLike: Into<ExprOrPatId> + Copy {
+ type BindingMode: Copy;
+
+ fn infer(
+ this: &mut InferenceContext<'_>,
+ id: Self,
+ expected_ty: &Ty,
+ default_bm: Self::BindingMode,
+ ) -> Ty;
+}
+
+impl PatLike for ExprId {
+ type BindingMode = ();
+
+ fn infer(
+ this: &mut InferenceContext<'_>,
+ id: Self,
+ expected_ty: &Ty,
+ _: Self::BindingMode,
+ ) -> Ty {
+ this.infer_assignee_expr(id, expected_ty)
+ }
+}
+
+impl PatLike for PatId {
+ type BindingMode = BindingMode;
+
+ fn infer(
+ this: &mut InferenceContext<'_>,
+ id: Self,
+ expected_ty: &Ty,
+ default_bm: Self::BindingMode,
+ ) -> Ty {
+ this.infer_pat(id, expected_ty, default_bm)
+ }
+}
+
+#[derive(Debug)]
+pub(crate) struct InferOk<T> {
+ value: T,
+ goals: Vec<InEnvironment<Goal>>,
+}
+
+impl<T> InferOk<T> {
+ fn map<U>(self, f: impl FnOnce(T) -> U) -> InferOk<U> {
+ InferOk { value: f(self.value), goals: self.goals }
+ }
+}
+
+#[derive(Debug)]
+pub(crate) struct TypeError;
+pub(crate) type InferResult<T> = Result<InferOk<T>, TypeError>;
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub enum InferenceDiagnostic {
+ NoSuchField { expr: ExprId },
+ BreakOutsideOfLoop { expr: ExprId, is_break: bool },
+ MismatchedArgCount { call_expr: ExprId, expected: usize, found: usize },
+}
+
+/// A mismatch between an expected and an inferred type.
+#[derive(Clone, PartialEq, Eq, Debug, Hash)]
+pub struct TypeMismatch {
+ pub expected: Ty,
+ pub actual: Ty,
+}
+
+#[derive(Clone, PartialEq, Eq, Debug)]
+struct InternedStandardTypes {
+ unknown: Ty,
+ bool_: Ty,
+ unit: Ty,
+}
+
+impl Default for InternedStandardTypes {
+ fn default() -> Self {
+ InternedStandardTypes {
+ unknown: TyKind::Error.intern(Interner),
+ bool_: TyKind::Scalar(Scalar::Bool).intern(Interner),
+ unit: TyKind::Tuple(0, Substitution::empty(Interner)).intern(Interner),
+ }
+ }
+}
+/// Represents coercing a value to a different type of value.
+///
+/// We transform values by following a number of `Adjust` steps in order.
+/// See the documentation on variants of `Adjust` for more details.
+///
+/// Here are some common scenarios:
+///
+/// 1. The simplest cases are where a pointer is not adjusted fat vs thin.
+/// Here the pointer will be dereferenced N times (where a dereference can
+/// happen to raw or borrowed pointers or any smart pointer which implements
+/// Deref, including Box<_>). The types of dereferences is given by
+/// `autoderefs`. It can then be auto-referenced zero or one times, indicated
+/// by `autoref`, to either a raw or borrowed pointer. In these cases unsize is
+/// `false`.
+///
+/// 2. A thin-to-fat coercion involves unsizing the underlying data. We start
+/// with a thin pointer, deref a number of times, unsize the underlying data,
+/// then autoref. The 'unsize' phase may change a fixed length array to a
+/// dynamically sized one, a concrete object to a trait object, or statically
+/// sized struct to a dynamically sized one. E.g., &[i32; 4] -> &[i32] is
+/// represented by:
+///
+/// ```
+/// Deref(None) -> [i32; 4],
+/// Borrow(AutoBorrow::Ref) -> &[i32; 4],
+/// Unsize -> &[i32],
+/// ```
+///
+/// Note that for a struct, the 'deep' unsizing of the struct is not recorded.
+/// E.g., `struct Foo<T> { x: T }` we can coerce &Foo<[i32; 4]> to &Foo<[i32]>
+/// The autoderef and -ref are the same as in the above example, but the type
+/// stored in `unsize` is `Foo<[i32]>`, we don't store any further detail about
+/// the underlying conversions from `[i32; 4]` to `[i32]`.
+///
+/// 3. Coercing a `Box<T>` to `Box<dyn Trait>` is an interesting special case. In
+/// that case, we have the pointer we need coming in, so there are no
+/// autoderefs, and no autoref. Instead we just do the `Unsize` transformation.
+/// At some point, of course, `Box` should move out of the compiler, in which
+/// case this is analogous to transforming a struct. E.g., Box<[i32; 4]> ->
+/// Box<[i32]> is an `Adjust::Unsize` with the target `Box<[i32]>`.
+#[derive(Clone, Debug, PartialEq, Eq, Hash)]
+pub struct Adjustment {
+ pub kind: Adjust,
+ pub target: Ty,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub enum Adjust {
+ /// Go from ! to any type.
+ NeverToAny,
+ /// Dereference once, producing a place.
+ Deref(Option<OverloadedDeref>),
+ /// Take the address and produce either a `&` or `*` pointer.
+ Borrow(AutoBorrow),
+ Pointer(PointerCast),
+}
+
+/// An overloaded autoderef step, representing a `Deref(Mut)::deref(_mut)`
+/// call, with the signature `&'a T -> &'a U` or `&'a mut T -> &'a mut U`.
+/// The target type is `U` in both cases, with the region and mutability
+/// being those shared by both the receiver and the returned reference.
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub struct OverloadedDeref(pub Mutability);
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub enum AutoBorrow {
+ /// Converts from T to &T.
+ Ref(Mutability),
+ /// Converts from T to *T.
+ RawPtr(Mutability),
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub enum PointerCast {
+ /// Go from a fn-item type to a fn-pointer type.
+ ReifyFnPointer,
+
+ /// Go from a safe fn pointer to an unsafe fn pointer.
+ UnsafeFnPointer,
+
+ /// Go from a non-capturing closure to an fn pointer or an unsafe fn pointer.
+ /// It cannot convert a closure that requires unsafe.
+ ClosureFnPointer(Safety),
+
+ /// Go from a mut raw pointer to a const raw pointer.
+ MutToConstPointer,
+
+ #[allow(dead_code)]
+ /// Go from `*const [T; N]` to `*const T`
+ ArrayToPointer,
+
+ /// Unsize a pointer/reference value, e.g., `&[T; n]` to
+ /// `&[T]`. Note that the source could be a thin or fat pointer.
+ /// This will do things like convert thin pointers to fat
+ /// pointers, or convert structs containing thin pointers to
+ /// structs containing fat pointers, or convert between fat
+ /// pointers. We don't store the details of how the transform is
+ /// done (in fact, we don't know that, because it might depend on
+ /// the precise type parameters). We just store the target
+ /// type. Codegen backends and miri figure out what has to be done
+ /// based on the precise source/target type at hand.
+ Unsize,
+}
+
+/// The result of type inference: A mapping from expressions and patterns to types.
+#[derive(Clone, PartialEq, Eq, Debug, Default)]
+pub struct InferenceResult {
+ /// For each method call expr, records the function it resolves to.
+ method_resolutions: FxHashMap<ExprId, (FunctionId, Substitution)>,
+ /// For each field access expr, records the field it resolves to.
+ field_resolutions: FxHashMap<ExprId, FieldId>,
+ /// For each struct literal or pattern, records the variant it resolves to.
+ variant_resolutions: FxHashMap<ExprOrPatId, VariantId>,
+ /// For each associated item record what it resolves to
+ assoc_resolutions: FxHashMap<ExprOrPatId, AssocItemId>,
+ pub diagnostics: Vec<InferenceDiagnostic>,
+ pub type_of_expr: ArenaMap<ExprId, Ty>,
+ /// For each pattern record the type it resolves to.
+ ///
+ /// **Note**: When a pattern type is resolved it may still contain
+ /// unresolved or missing subpatterns or subpatterns of mismatched types.
+ pub type_of_pat: ArenaMap<PatId, Ty>,
+ type_mismatches: FxHashMap<ExprOrPatId, TypeMismatch>,
- let ty = TyBuilder::def_ty(self.db, it.into())
++ /// Interned common types to return references to.
+ standard_types: InternedStandardTypes,
+ /// Stores the types which were implicitly dereferenced in pattern binding modes.
+ pub pat_adjustments: FxHashMap<PatId, Vec<Ty>>,
+ pub pat_binding_modes: FxHashMap<PatId, BindingMode>,
+ pub expr_adjustments: FxHashMap<ExprId, Vec<Adjustment>>,
+}
+
+impl InferenceResult {
+ pub fn method_resolution(&self, expr: ExprId) -> Option<(FunctionId, Substitution)> {
+ self.method_resolutions.get(&expr).cloned()
+ }
+ pub fn field_resolution(&self, expr: ExprId) -> Option<FieldId> {
+ self.field_resolutions.get(&expr).copied()
+ }
+ pub fn variant_resolution_for_expr(&self, id: ExprId) -> Option<VariantId> {
+ self.variant_resolutions.get(&id.into()).copied()
+ }
+ pub fn variant_resolution_for_pat(&self, id: PatId) -> Option<VariantId> {
+ self.variant_resolutions.get(&id.into()).copied()
+ }
+ pub fn assoc_resolutions_for_expr(&self, id: ExprId) -> Option<AssocItemId> {
+ self.assoc_resolutions.get(&id.into()).copied()
+ }
+ pub fn assoc_resolutions_for_pat(&self, id: PatId) -> Option<AssocItemId> {
+ self.assoc_resolutions.get(&id.into()).copied()
+ }
+ pub fn type_mismatch_for_expr(&self, expr: ExprId) -> Option<&TypeMismatch> {
+ self.type_mismatches.get(&expr.into())
+ }
+ pub fn type_mismatch_for_pat(&self, pat: PatId) -> Option<&TypeMismatch> {
+ self.type_mismatches.get(&pat.into())
+ }
+ pub fn expr_type_mismatches(&self) -> impl Iterator<Item = (ExprId, &TypeMismatch)> {
+ self.type_mismatches.iter().filter_map(|(expr_or_pat, mismatch)| match *expr_or_pat {
+ ExprOrPatId::ExprId(expr) => Some((expr, mismatch)),
+ _ => None,
+ })
+ }
+ pub fn pat_type_mismatches(&self) -> impl Iterator<Item = (PatId, &TypeMismatch)> {
+ self.type_mismatches.iter().filter_map(|(expr_or_pat, mismatch)| match *expr_or_pat {
+ ExprOrPatId::PatId(pat) => Some((pat, mismatch)),
+ _ => None,
+ })
+ }
+}
+
+impl Index<ExprId> for InferenceResult {
+ type Output = Ty;
+
+ fn index(&self, expr: ExprId) -> &Ty {
+ self.type_of_expr.get(expr).unwrap_or(&self.standard_types.unknown)
+ }
+}
+
+impl Index<PatId> for InferenceResult {
+ type Output = Ty;
+
+ fn index(&self, pat: PatId) -> &Ty {
+ self.type_of_pat.get(pat).unwrap_or(&self.standard_types.unknown)
+ }
+}
+
+/// The inference context contains all information needed during type inference.
+#[derive(Clone, Debug)]
+pub(crate) struct InferenceContext<'a> {
+ pub(crate) db: &'a dyn HirDatabase,
+ pub(crate) owner: DefWithBodyId,
+ pub(crate) body: &'a Body,
+ pub(crate) resolver: Resolver,
+ table: unify::InferenceTable<'a>,
+ trait_env: Arc<TraitEnvironment>,
+ pub(crate) result: InferenceResult,
+ /// The return type of the function being inferred, the closure or async block if we're
+ /// currently within one.
+ ///
+ /// We might consider using a nested inference context for checking
+ /// closures, but currently this is the only field that will change there,
+ /// so it doesn't make sense.
+ return_ty: Ty,
++ /// The resume type and the yield type, respectively, of the generator being inferred.
++ resume_yield_tys: Option<(Ty, Ty)>,
+ diverges: Diverges,
+ breakables: Vec<BreakableContext>,
+}
+
+#[derive(Clone, Debug)]
+struct BreakableContext {
+ /// Whether this context contains at least one break expression.
+ may_break: bool,
+ /// The coercion target of the context.
+ coerce: CoerceMany,
+ /// The optional label of the context.
+ label: Option<name::Name>,
+ kind: BreakableKind,
+}
+
+#[derive(Clone, Debug)]
+enum BreakableKind {
+ Block,
+ Loop,
+ /// A border is something like an async block, closure etc. Anything that prevents
+ /// breaking/continuing through
+ Border,
+}
+
+fn find_breakable<'c>(
+ ctxs: &'c mut [BreakableContext],
+ label: Option<&name::Name>,
+) -> Option<&'c mut BreakableContext> {
+ let mut ctxs = ctxs
+ .iter_mut()
+ .rev()
+ .take_while(|it| matches!(it.kind, BreakableKind::Block | BreakableKind::Loop));
+ match label {
+ Some(_) => ctxs.find(|ctx| ctx.label.as_ref() == label),
+ None => ctxs.find(|ctx| matches!(ctx.kind, BreakableKind::Loop)),
+ }
+}
+
+fn find_continuable<'c>(
+ ctxs: &'c mut [BreakableContext],
+ label: Option<&name::Name>,
+) -> Option<&'c mut BreakableContext> {
+ match label {
+ Some(_) => find_breakable(ctxs, label).filter(|it| matches!(it.kind, BreakableKind::Loop)),
+ None => find_breakable(ctxs, label),
+ }
+}
+
+impl<'a> InferenceContext<'a> {
+ fn new(
+ db: &'a dyn HirDatabase,
+ owner: DefWithBodyId,
+ body: &'a Body,
+ resolver: Resolver,
+ ) -> Self {
+ let krate = owner.module(db.upcast()).krate();
+ let trait_env = owner
+ .as_generic_def_id()
+ .map_or_else(|| Arc::new(TraitEnvironment::empty(krate)), |d| db.trait_environment(d));
+ InferenceContext {
+ result: InferenceResult::default(),
+ table: unify::InferenceTable::new(db, trait_env.clone()),
+ trait_env,
+ return_ty: TyKind::Error.intern(Interner), // set in collect_fn_signature
++ resume_yield_tys: None,
+ db,
+ owner,
+ body,
+ resolver,
+ diverges: Diverges::Maybe,
+ breakables: Vec::new(),
+ }
+ }
+
+ fn resolve_all(self) -> InferenceResult {
+ let InferenceContext { mut table, mut result, .. } = self;
+
+ // FIXME resolve obligations as well (use Guidance if necessary)
+ table.resolve_obligations_as_possible();
+
+ // make sure diverging type variables are marked as such
+ table.propagate_diverging_flag();
+ for ty in result.type_of_expr.values_mut() {
+ *ty = table.resolve_completely(ty.clone());
+ }
+ for ty in result.type_of_pat.values_mut() {
+ *ty = table.resolve_completely(ty.clone());
+ }
+ for mismatch in result.type_mismatches.values_mut() {
+ mismatch.expected = table.resolve_completely(mismatch.expected.clone());
+ mismatch.actual = table.resolve_completely(mismatch.actual.clone());
+ }
+ for (_, subst) in result.method_resolutions.values_mut() {
+ *subst = table.resolve_completely(subst.clone());
+ }
+ for adjustment in result.expr_adjustments.values_mut().flatten() {
+ adjustment.target = table.resolve_completely(adjustment.target.clone());
+ }
+ for adjustment in result.pat_adjustments.values_mut().flatten() {
+ *adjustment = table.resolve_completely(adjustment.clone());
+ }
+ result
+ }
+
+ fn collect_const(&mut self, data: &ConstData) {
+ self.return_ty = self.make_ty(&data.type_ref);
+ }
+
+ fn collect_static(&mut self, data: &StaticData) {
+ self.return_ty = self.make_ty(&data.type_ref);
+ }
+
+ fn collect_fn(&mut self, func: FunctionId) {
+ let data = self.db.function_data(func);
+ let ctx = crate::lower::TyLoweringContext::new(self.db, &self.resolver)
+ .with_impl_trait_mode(ImplTraitLoweringMode::Param);
+ let param_tys =
+ data.params.iter().map(|(_, type_ref)| ctx.lower_ty(type_ref)).collect::<Vec<_>>();
+ for (ty, pat) in param_tys.into_iter().zip(self.body.params.iter()) {
+ let ty = self.insert_type_vars(ty);
+ let ty = self.normalize_associated_types_in(ty);
+
+ self.infer_pat(*pat, &ty, BindingMode::default());
+ }
+ let error_ty = &TypeRef::Error;
+ let return_ty = if data.has_async_kw() {
+ data.async_ret_type.as_deref().unwrap_or(error_ty)
+ } else {
+ &*data.ret_type
+ };
+ let return_ty = self.make_ty_with_mode(return_ty, ImplTraitLoweringMode::Opaque);
+ self.return_ty = return_ty;
+
+ if let Some(rpits) = self.db.return_type_impl_traits(func) {
+ // RPIT opaque types use substitution of their parent function.
+ let fn_placeholders = TyBuilder::placeholder_subst(self.db, func);
+ self.return_ty = fold_tys(
+ self.return_ty.clone(),
+ |ty, _| {
+ let opaque_ty_id = match ty.kind(Interner) {
+ TyKind::OpaqueType(opaque_ty_id, _) => *opaque_ty_id,
+ _ => return ty,
+ };
+ let idx = match self.db.lookup_intern_impl_trait_id(opaque_ty_id.into()) {
+ ImplTraitId::ReturnTypeImplTrait(_, idx) => idx,
+ _ => unreachable!(),
+ };
+ let bounds = (*rpits).map_ref(|rpits| {
+ rpits.impl_traits[idx as usize].bounds.map_ref(|it| it.into_iter())
+ });
+ let var = self.table.new_type_var();
+ let var_subst = Substitution::from1(Interner, var.clone());
+ for bound in bounds {
+ let predicate =
+ bound.map(|it| it.cloned()).substitute(Interner, &fn_placeholders);
+ let (var_predicate, binders) = predicate
+ .substitute(Interner, &var_subst)
+ .into_value_and_skipped_binders();
+ always!(binders.len(Interner) == 0); // quantified where clauses not yet handled
+ self.push_obligation(var_predicate.cast(Interner));
+ }
+ var
+ },
+ DebruijnIndex::INNERMOST,
+ );
+ }
+ }
+
+ fn infer_body(&mut self) {
+ self.infer_expr_coerce(self.body.body_expr, &Expectation::has_type(self.return_ty.clone()));
+ }
+
+ fn write_expr_ty(&mut self, expr: ExprId, ty: Ty) {
+ self.result.type_of_expr.insert(expr, ty);
+ }
+
+ fn write_expr_adj(&mut self, expr: ExprId, adjustments: Vec<Adjustment>) {
+ self.result.expr_adjustments.insert(expr, adjustments);
+ }
+
+ fn write_method_resolution(&mut self, expr: ExprId, func: FunctionId, subst: Substitution) {
+ self.result.method_resolutions.insert(expr, (func, subst));
+ }
+
+ fn write_variant_resolution(&mut self, id: ExprOrPatId, variant: VariantId) {
+ self.result.variant_resolutions.insert(id, variant);
+ }
+
+ fn write_assoc_resolution(&mut self, id: ExprOrPatId, item: AssocItemId) {
+ self.result.assoc_resolutions.insert(id, item);
+ }
+
+ fn write_pat_ty(&mut self, pat: PatId, ty: Ty) {
+ self.result.type_of_pat.insert(pat, ty);
+ }
+
+ fn push_diagnostic(&mut self, diagnostic: InferenceDiagnostic) {
+ self.result.diagnostics.push(diagnostic);
+ }
+
+ fn make_ty_with_mode(
+ &mut self,
+ type_ref: &TypeRef,
+ impl_trait_mode: ImplTraitLoweringMode,
+ ) -> Ty {
+ // FIXME use right resolver for block
+ let ctx = crate::lower::TyLoweringContext::new(self.db, &self.resolver)
+ .with_impl_trait_mode(impl_trait_mode);
+ let ty = ctx.lower_ty(type_ref);
+ let ty = self.insert_type_vars(ty);
+ self.normalize_associated_types_in(ty)
+ }
+
+ fn make_ty(&mut self, type_ref: &TypeRef) -> Ty {
+ self.make_ty_with_mode(type_ref, ImplTraitLoweringMode::Disallowed)
+ }
+
+ fn err_ty(&self) -> Ty {
+ self.result.standard_types.unknown.clone()
+ }
+
+ /// Replaces ConstScalar::Unknown by a new type var, so we can maybe still infer it.
+ fn insert_const_vars_shallow(&mut self, c: Const) -> Const {
+ let data = c.data(Interner);
+ match data.value {
+ ConstValue::Concrete(cc) => match cc.interned {
+ hir_def::type_ref::ConstScalar::Unknown => {
+ self.table.new_const_var(data.ty.clone())
+ }
+ _ => c,
+ },
+ _ => c,
+ }
+ }
+
+ /// Replaces Ty::Unknown by a new type var, so we can maybe still infer it.
+ fn insert_type_vars_shallow(&mut self, ty: Ty) -> Ty {
+ match ty.kind(Interner) {
+ TyKind::Error => self.table.new_type_var(),
+ TyKind::InferenceVar(..) => {
+ let ty_resolved = self.resolve_ty_shallow(&ty);
+ if ty_resolved.is_unknown() {
+ self.table.new_type_var()
+ } else {
+ ty
+ }
+ }
+ _ => ty,
+ }
+ }
+
+ fn insert_type_vars(&mut self, ty: Ty) -> Ty {
+ fold_tys_and_consts(
+ ty,
+ |x, _| match x {
+ Either::Left(ty) => Either::Left(self.insert_type_vars_shallow(ty)),
+ Either::Right(c) => Either::Right(self.insert_const_vars_shallow(c)),
+ },
+ DebruijnIndex::INNERMOST,
+ )
+ }
+
+ fn push_obligation(&mut self, o: DomainGoal) {
+ self.table.register_obligation(o.cast(Interner));
+ }
+
+ fn unify(&mut self, ty1: &Ty, ty2: &Ty) -> bool {
+ self.table.unify(ty1, ty2)
+ }
+
+ /// Recurses through the given type, normalizing associated types mentioned
+ /// in it by replacing them by type variables and registering obligations to
+ /// resolve later. This should be done once for every type we get from some
+ /// type annotation (e.g. from a let type annotation, field type or function
+ /// call). `make_ty` handles this already, but e.g. for field types we need
+ /// to do it as well.
+ fn normalize_associated_types_in(&mut self, ty: Ty) -> Ty {
+ self.table.normalize_associated_types_in(ty)
+ }
+
+ fn resolve_ty_shallow(&mut self, ty: &Ty) -> Ty {
+ self.table.resolve_ty_shallow(ty)
+ }
+
+ fn resolve_associated_type(&mut self, inner_ty: Ty, assoc_ty: Option<TypeAliasId>) -> Ty {
+ self.resolve_associated_type_with_params(inner_ty, assoc_ty, &[])
+ }
+
+ fn resolve_associated_type_with_params(
+ &mut self,
+ inner_ty: Ty,
+ assoc_ty: Option<TypeAliasId>,
++ // FIXME(GATs): these are args for the trait ref, args for assoc type itself should be
++ // handled when we support them.
+ params: &[GenericArg],
+ ) -> Ty {
+ match assoc_ty {
+ Some(res_assoc_ty) => {
+ let trait_ = match res_assoc_ty.lookup(self.db.upcast()).container {
+ hir_def::ItemContainerId::TraitId(trait_) => trait_,
+ _ => panic!("resolve_associated_type called with non-associated type"),
+ };
+ let ty = self.table.new_type_var();
+ let mut param_iter = params.iter().cloned();
+ let trait_ref = TyBuilder::trait_ref(self.db, trait_)
+ .push(inner_ty)
+ .fill(|_| param_iter.next().unwrap())
+ .build();
+ let alias_eq = AliasEq {
+ alias: AliasTy::Projection(ProjectionTy {
+ associated_ty_id: to_assoc_type_id(res_assoc_ty),
+ substitution: trait_ref.substitution.clone(),
+ }),
+ ty: ty.clone(),
+ };
+ self.push_obligation(trait_ref.cast(Interner));
+ self.push_obligation(alias_eq.cast(Interner));
+ ty
+ }
+ None => self.err_ty(),
+ }
+ }
+
+ fn resolve_variant(&mut self, path: Option<&Path>, value_ns: bool) -> (Ty, Option<VariantId>) {
+ let path = match path {
+ Some(path) => path,
+ None => return (self.err_ty(), None),
+ };
+ let resolver = &self.resolver;
+ let ctx = crate::lower::TyLoweringContext::new(self.db, &self.resolver);
+ // FIXME: this should resolve assoc items as well, see this example:
+ // https://play.rust-lang.org/?gist=087992e9e22495446c01c0d4e2d69521
+ let (resolution, unresolved) = if value_ns {
+ match resolver.resolve_path_in_value_ns(self.db.upcast(), path.mod_path()) {
+ Some(ResolveValueResult::ValueNs(value)) => match value {
+ ValueNs::EnumVariantId(var) => {
+ let substs = ctx.substs_from_path(path, var.into(), true);
+ let ty = self.db.ty(var.parent.into());
+ let ty = self.insert_type_vars(ty.substitute(Interner, &substs));
+ return (ty, Some(var.into()));
+ }
+ ValueNs::StructId(strukt) => {
+ let substs = ctx.substs_from_path(path, strukt.into(), true);
+ let ty = self.db.ty(strukt.into());
+ let ty = self.insert_type_vars(ty.substitute(Interner, &substs));
+ return (ty, Some(strukt.into()));
+ }
+ ValueNs::ImplSelf(impl_id) => (TypeNs::SelfType(impl_id), None),
+ _ => return (self.err_ty(), None),
+ },
+ Some(ResolveValueResult::Partial(typens, unresolved)) => (typens, Some(unresolved)),
+ None => return (self.err_ty(), None),
+ }
+ } else {
+ match resolver.resolve_path_in_type_ns(self.db.upcast(), path.mod_path()) {
+ Some(it) => it,
+ None => return (self.err_ty(), None),
+ }
+ };
+ return match resolution {
+ TypeNs::AdtId(AdtId::StructId(strukt)) => {
+ let substs = ctx.substs_from_path(path, strukt.into(), true);
+ let ty = self.db.ty(strukt.into());
+ let ty = self.insert_type_vars(ty.substitute(Interner, &substs));
+ forbid_unresolved_segments((ty, Some(strukt.into())), unresolved)
+ }
+ TypeNs::AdtId(AdtId::UnionId(u)) => {
+ let substs = ctx.substs_from_path(path, u.into(), true);
+ let ty = self.db.ty(u.into());
+ let ty = self.insert_type_vars(ty.substitute(Interner, &substs));
+ forbid_unresolved_segments((ty, Some(u.into())), unresolved)
+ }
+ TypeNs::EnumVariantId(var) => {
+ let substs = ctx.substs_from_path(path, var.into(), true);
+ let ty = self.db.ty(var.parent.into());
+ let ty = self.insert_type_vars(ty.substitute(Interner, &substs));
+ forbid_unresolved_segments((ty, Some(var.into())), unresolved)
+ }
+ TypeNs::SelfType(impl_id) => {
+ let generics = crate::utils::generics(self.db.upcast(), impl_id.into());
+ let substs = generics.placeholder_subst(self.db);
+ let ty = self.db.impl_self_ty(impl_id).substitute(Interner, &substs);
+ self.resolve_variant_on_alias(ty, unresolved, path)
+ }
+ TypeNs::TypeAliasId(it) => {
++ let container = it.lookup(self.db.upcast()).container;
++ let parent_subst = match container {
++ ItemContainerId::TraitId(id) => {
++ let subst = TyBuilder::subst_for_def(self.db, id, None)
++ .fill_with_inference_vars(&mut self.table)
++ .build();
++ Some(subst)
++ }
++ // Type aliases do not exist in impls.
++ _ => None,
++ };
++ let ty = TyBuilder::def_ty(self.db, it.into(), parent_subst)
+ .fill_with_inference_vars(&mut self.table)
+ .build();
+ self.resolve_variant_on_alias(ty, unresolved, path)
+ }
+ TypeNs::AdtSelfType(_) => {
+ // FIXME this could happen in array size expressions, once we're checking them
+ (self.err_ty(), None)
+ }
+ TypeNs::GenericParam(_) => {
+ // FIXME potentially resolve assoc type
+ (self.err_ty(), None)
+ }
+ TypeNs::AdtId(AdtId::EnumId(_)) | TypeNs::BuiltinType(_) | TypeNs::TraitId(_) => {
+ // FIXME diagnostic
+ (self.err_ty(), None)
+ }
+ };
+
+ fn forbid_unresolved_segments(
+ result: (Ty, Option<VariantId>),
+ unresolved: Option<usize>,
+ ) -> (Ty, Option<VariantId>) {
+ if unresolved.is_none() {
+ result
+ } else {
+ // FIXME diagnostic
+ (TyKind::Error.intern(Interner), None)
+ }
+ }
+ }
+
+ fn resolve_variant_on_alias(
+ &mut self,
+ ty: Ty,
+ unresolved: Option<usize>,
+ path: &Path,
+ ) -> (Ty, Option<VariantId>) {
+ let remaining = unresolved.map(|x| path.segments().skip(x).len()).filter(|x| x > &0);
+ match remaining {
+ None => {
+ let variant = ty.as_adt().and_then(|(adt_id, _)| match adt_id {
+ AdtId::StructId(s) => Some(VariantId::StructId(s)),
+ AdtId::UnionId(u) => Some(VariantId::UnionId(u)),
+ AdtId::EnumId(_) => {
+ // FIXME Error E0071, expected struct, variant or union type, found enum `Foo`
+ None
+ }
+ });
+ (ty, variant)
+ }
+ Some(1) => {
+ let segment = path.mod_path().segments().last().unwrap();
+ // this could be an enum variant or associated type
+ if let Some((AdtId::EnumId(enum_id), _)) = ty.as_adt() {
+ let enum_data = self.db.enum_data(enum_id);
+ if let Some(local_id) = enum_data.variant(segment) {
+ let variant = EnumVariantId { parent: enum_id, local_id };
+ return (ty, Some(variant.into()));
+ }
+ }
+ // FIXME potentially resolve assoc type
+ (self.err_ty(), None)
+ }
+ Some(_) => {
+ // FIXME diagnostic
+ (self.err_ty(), None)
+ }
+ }
+ }
+
+ fn resolve_lang_item(&self, name: Name) -> Option<LangItemTarget> {
+ let krate = self.resolver.krate();
+ self.db.lang_item(krate, name.to_smol_str())
+ }
+
+ fn resolve_into_iter_item(&self) -> Option<TypeAliasId> {
+ let path = path![core::iter::IntoIterator];
+ let trait_ = self.resolver.resolve_known_trait(self.db.upcast(), &path)?;
++ self.db.trait_data(trait_).associated_type_by_name(&name![IntoIter])
++ }
++
++ fn resolve_iterator_item(&self) -> Option<TypeAliasId> {
++ let path = path![core::iter::Iterator];
++ let trait_ = self.resolver.resolve_known_trait(self.db.upcast(), &path)?;
+ self.db.trait_data(trait_).associated_type_by_name(&name![Item])
+ }
+
+ fn resolve_ops_try_ok(&self) -> Option<TypeAliasId> {
+ // FIXME resolve via lang_item once try v2 is stable
+ let path = path![core::ops::Try];
+ let trait_ = self.resolver.resolve_known_trait(self.db.upcast(), &path)?;
+ let trait_data = self.db.trait_data(trait_);
+ trait_data
+ // FIXME remove once try v2 is stable
+ .associated_type_by_name(&name![Ok])
+ .or_else(|| trait_data.associated_type_by_name(&name![Output]))
+ }
+
+ fn resolve_ops_neg_output(&self) -> Option<TypeAliasId> {
+ let trait_ = self.resolve_lang_item(name![neg])?.as_trait()?;
+ self.db.trait_data(trait_).associated_type_by_name(&name![Output])
+ }
+
+ fn resolve_ops_not_output(&self) -> Option<TypeAliasId> {
+ let trait_ = self.resolve_lang_item(name![not])?.as_trait()?;
+ self.db.trait_data(trait_).associated_type_by_name(&name![Output])
+ }
+
+ fn resolve_future_future_output(&self) -> Option<TypeAliasId> {
+ let trait_ = self
+ .resolver
+ .resolve_known_trait(self.db.upcast(), &path![core::future::IntoFuture])
+ .or_else(|| self.resolve_lang_item(name![future_trait])?.as_trait())?;
+ self.db.trait_data(trait_).associated_type_by_name(&name![Output])
+ }
+
+ fn resolve_boxed_box(&self) -> Option<AdtId> {
+ let struct_ = self.resolve_lang_item(name![owned_box])?.as_struct()?;
+ Some(struct_.into())
+ }
+
+ fn resolve_range_full(&self) -> Option<AdtId> {
+ let path = path![core::ops::RangeFull];
+ let struct_ = self.resolver.resolve_known_struct(self.db.upcast(), &path)?;
+ Some(struct_.into())
+ }
+
+ fn resolve_range(&self) -> Option<AdtId> {
+ let path = path![core::ops::Range];
+ let struct_ = self.resolver.resolve_known_struct(self.db.upcast(), &path)?;
+ Some(struct_.into())
+ }
+
+ fn resolve_range_inclusive(&self) -> Option<AdtId> {
+ let path = path![core::ops::RangeInclusive];
+ let struct_ = self.resolver.resolve_known_struct(self.db.upcast(), &path)?;
+ Some(struct_.into())
+ }
+
+ fn resolve_range_from(&self) -> Option<AdtId> {
+ let path = path![core::ops::RangeFrom];
+ let struct_ = self.resolver.resolve_known_struct(self.db.upcast(), &path)?;
+ Some(struct_.into())
+ }
+
+ fn resolve_range_to(&self) -> Option<AdtId> {
+ let path = path![core::ops::RangeTo];
+ let struct_ = self.resolver.resolve_known_struct(self.db.upcast(), &path)?;
+ Some(struct_.into())
+ }
+
+ fn resolve_range_to_inclusive(&self) -> Option<AdtId> {
+ let path = path![core::ops::RangeToInclusive];
+ let struct_ = self.resolver.resolve_known_struct(self.db.upcast(), &path)?;
+ Some(struct_.into())
+ }
+
+ fn resolve_ops_index(&self) -> Option<TraitId> {
+ self.resolve_lang_item(name![index])?.as_trait()
+ }
+
+ fn resolve_ops_index_output(&self) -> Option<TypeAliasId> {
+ let trait_ = self.resolve_ops_index()?;
+ self.db.trait_data(trait_).associated_type_by_name(&name![Output])
+ }
+}
+
+/// When inferring an expression, we propagate downward whatever type hint we
+/// are able in the form of an `Expectation`.
+#[derive(Clone, PartialEq, Eq, Debug)]
+pub(crate) enum Expectation {
+ None,
+ HasType(Ty),
+ // Castable(Ty), // rustc has this, we currently just don't propagate an expectation for casts
+ RValueLikeUnsized(Ty),
+}
+
+impl Expectation {
+ /// The expectation that the type of the expression needs to equal the given
+ /// type.
+ fn has_type(ty: Ty) -> Self {
+ if ty.is_unknown() {
+ // FIXME: get rid of this?
+ Expectation::None
+ } else {
+ Expectation::HasType(ty)
+ }
+ }
+
+ fn from_option(ty: Option<Ty>) -> Self {
+ ty.map_or(Expectation::None, Expectation::HasType)
+ }
+
+ /// The following explanation is copied straight from rustc:
+ /// Provides an expectation for an rvalue expression given an *optional*
+ /// hint, which is not required for type safety (the resulting type might
+ /// be checked higher up, as is the case with `&expr` and `box expr`), but
+ /// is useful in determining the concrete type.
+ ///
+ /// The primary use case is where the expected type is a fat pointer,
+ /// like `&[isize]`. For example, consider the following statement:
+ ///
+ /// let x: &[isize] = &[1, 2, 3];
+ ///
+ /// In this case, the expected type for the `&[1, 2, 3]` expression is
+ /// `&[isize]`. If however we were to say that `[1, 2, 3]` has the
+ /// expectation `ExpectHasType([isize])`, that would be too strong --
+ /// `[1, 2, 3]` does not have the type `[isize]` but rather `[isize; 3]`.
+ /// It is only the `&[1, 2, 3]` expression as a whole that can be coerced
+ /// to the type `&[isize]`. Therefore, we propagate this more limited hint,
+ /// which still is useful, because it informs integer literals and the like.
+ /// See the test case `test/ui/coerce-expect-unsized.rs` and #20169
+ /// for examples of where this comes up,.
+ fn rvalue_hint(table: &mut unify::InferenceTable<'_>, ty: Ty) -> Self {
+ // FIXME: do struct_tail_without_normalization
+ match table.resolve_ty_shallow(&ty).kind(Interner) {
+ TyKind::Slice(_) | TyKind::Str | TyKind::Dyn(_) => Expectation::RValueLikeUnsized(ty),
+ _ => Expectation::has_type(ty),
+ }
+ }
+
+ /// This expresses no expectation on the type.
+ fn none() -> Self {
+ Expectation::None
+ }
+
+ fn resolve(&self, table: &mut unify::InferenceTable<'_>) -> Expectation {
+ match self {
+ Expectation::None => Expectation::None,
+ Expectation::HasType(t) => Expectation::HasType(table.resolve_ty_shallow(t)),
+ Expectation::RValueLikeUnsized(t) => {
+ Expectation::RValueLikeUnsized(table.resolve_ty_shallow(t))
+ }
+ }
+ }
+
+ fn to_option(&self, table: &mut unify::InferenceTable<'_>) -> Option<Ty> {
+ match self.resolve(table) {
+ Expectation::None => None,
+ Expectation::HasType(t) |
+ // Expectation::Castable(t) |
+ Expectation::RValueLikeUnsized(t) => Some(t),
+ }
+ }
+
+ fn only_has_type(&self, table: &mut unify::InferenceTable<'_>) -> Option<Ty> {
+ match self {
+ Expectation::HasType(t) => Some(table.resolve_ty_shallow(t)),
+ // Expectation::Castable(_) |
+ Expectation::RValueLikeUnsized(_) | Expectation::None => None,
+ }
+ }
+
+ /// Comment copied from rustc:
+ /// Disregard "castable to" expectations because they
+ /// can lead us astray. Consider for example `if cond
+ /// {22} else {c} as u8` -- if we propagate the
+ /// "castable to u8" constraint to 22, it will pick the
+ /// type 22u8, which is overly constrained (c might not
+ /// be a u8). In effect, the problem is that the
+ /// "castable to" expectation is not the tightest thing
+ /// we can say, so we want to drop it in this case.
+ /// The tightest thing we can say is "must unify with
+ /// else branch". Note that in the case of a "has type"
+ /// constraint, this limitation does not hold.
+ ///
+ /// If the expected type is just a type variable, then don't use
+ /// an expected type. Otherwise, we might write parts of the type
+ /// when checking the 'then' block which are incompatible with the
+ /// 'else' branch.
+ fn adjust_for_branches(&self, table: &mut unify::InferenceTable<'_>) -> Expectation {
+ match self {
+ Expectation::HasType(ety) => {
+ let ety = table.resolve_ty_shallow(ety);
+ if !ety.is_ty_var() {
+ Expectation::HasType(ety)
+ } else {
+ Expectation::None
+ }
+ }
+ Expectation::RValueLikeUnsized(ety) => Expectation::RValueLikeUnsized(ety.clone()),
+ _ => Expectation::None,
+ }
+ }
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
+enum Diverges {
+ Maybe,
+ Always,
+}
+
+impl Diverges {
+ fn is_always(self) -> bool {
+ self == Diverges::Always
+ }
+}
+
+impl std::ops::BitAnd for Diverges {
+ type Output = Self;
+ fn bitand(self, other: Self) -> Self {
+ std::cmp::min(self, other)
+ }
+}
+
+impl std::ops::BitOr for Diverges {
+ type Output = Self;
+ fn bitor(self, other: Self) -> Self {
+ std::cmp::max(self, other)
+ }
+}
+
+impl std::ops::BitAndAssign for Diverges {
+ fn bitand_assign(&mut self, other: Self) {
+ *self = *self & other;
+ }
+}
+
+impl std::ops::BitOrAssign for Diverges {
+ fn bitor_assign(&mut self, other: Self) {
+ *self = *self | other;
+ }
+}
--- /dev/null
+//! Inference of closure parameter types based on the closure's expected type.
+
+use chalk_ir::{cast::Cast, AliasEq, AliasTy, FnSubst, WhereClause};
+use hir_def::{expr::ExprId, HasModule};
+use smallvec::SmallVec;
+
+use crate::{
+ to_chalk_trait_id, utils, ChalkTraitId, DynTy, FnPointer, FnSig, Interner, Substitution, Ty,
+ TyExt, TyKind,
+};
+
+use super::{Expectation, InferenceContext};
+
+impl InferenceContext<'_> {
++ // This function handles both closures and generators.
+ pub(super) fn deduce_closure_type_from_expectations(
+ &mut self,
+ closure_expr: ExprId,
+ closure_ty: &Ty,
+ sig_ty: &Ty,
+ expectation: &Expectation,
+ ) {
+ let expected_ty = match expectation.to_option(&mut self.table) {
+ Some(ty) => ty,
+ None => return,
+ };
+
+ // Deduction from where-clauses in scope, as well as fn-pointer coercion are handled here.
+ let _ = self.coerce(Some(closure_expr), closure_ty, &expected_ty);
+
++ // Generators are not Fn* so return early.
++ if matches!(closure_ty.kind(Interner), TyKind::Generator(..)) {
++ return;
++ }
++
+ // Deduction based on the expected `dyn Fn` is done separately.
+ if let TyKind::Dyn(dyn_ty) = expected_ty.kind(Interner) {
+ if let Some(sig) = self.deduce_sig_from_dyn_ty(dyn_ty) {
+ let expected_sig_ty = TyKind::Function(sig).intern(Interner);
+
+ self.unify(sig_ty, &expected_sig_ty);
+ }
+ }
+ }
+
+ fn deduce_sig_from_dyn_ty(&self, dyn_ty: &DynTy) -> Option<FnPointer> {
+ // Search for a predicate like `<$self as FnX<Args>>::Output == Ret`
+
+ let fn_traits: SmallVec<[ChalkTraitId; 3]> =
+ utils::fn_traits(self.db.upcast(), self.owner.module(self.db.upcast()).krate())
+ .map(to_chalk_trait_id)
+ .collect();
+
+ let self_ty = TyKind::Error.intern(Interner);
+ let bounds = dyn_ty.bounds.clone().substitute(Interner, &[self_ty.cast(Interner)]);
+ for bound in bounds.iter(Interner) {
+ // NOTE(skip_binders): the extracted types are rebound by the returned `FnPointer`
+ if let WhereClause::AliasEq(AliasEq { alias: AliasTy::Projection(projection), ty }) =
+ bound.skip_binders()
+ {
+ let assoc_data = self.db.associated_ty_data(projection.associated_ty_id);
+ if !fn_traits.contains(&assoc_data.trait_id) {
+ return None;
+ }
+
+ // Skip `Self`, get the type argument.
+ let arg = projection.substitution.as_slice(Interner).get(1)?;
+ if let Some(subst) = arg.ty(Interner)?.as_tuple() {
+ let generic_args = subst.as_slice(Interner);
+ let mut sig_tys = Vec::new();
+ for arg in generic_args {
+ sig_tys.push(arg.ty(Interner)?.clone());
+ }
+ sig_tys.push(ty.clone());
+
+ cov_mark::hit!(dyn_fn_param_informs_call_site_closure_signature);
+ return Some(FnPointer {
+ num_binders: bound.len(Interner),
+ sig: FnSig { abi: (), safety: chalk_ir::Safety::Safe, variadic: false },
+ substitution: FnSubst(Substitution::from_iter(Interner, sig_tys)),
+ });
+ }
+ }
+ }
+
+ None
+ }
+}
--- /dev/null
- expr::{ArithOp, Array, BinaryOp, CmpOp, Expr, ExprId, LabelId, Literal, Statement, UnaryOp},
+//! Type inference for expressions.
+
+use std::{
+ collections::hash_map::Entry,
+ iter::{repeat, repeat_with},
+ mem,
+};
+
+use chalk_ir::{
+ cast::Cast, fold::Shift, DebruijnIndex, GenericArgData, Mutability, TyVariableKind,
+};
+use hir_def::{
- let pat_ty =
++ expr::{
++ ArithOp, Array, BinaryOp, ClosureKind, CmpOp, Expr, ExprId, LabelId, Literal, Statement,
++ UnaryOp,
++ },
+ generics::TypeOrConstParamData,
+ path::{GenericArg, GenericArgs},
+ resolver::resolver_for_expr,
+ ConstParamId, FieldId, ItemContainerId, Lookup,
+};
+use hir_expand::name::Name;
+use stdx::always;
+use syntax::ast::RangeOp;
+
+use crate::{
+ autoderef::{self, Autoderef},
+ consteval,
+ infer::{coerce::CoerceMany, find_continuable, BreakableKind},
+ lower::{
+ const_or_path_to_chalk, generic_arg_to_chalk, lower_to_chalk_mutability, ParamLoweringMode,
+ },
+ mapping::{from_chalk, ToChalk},
+ method_resolution::{self, lang_names_for_bin_op, VisibleFromModule},
+ primitive::{self, UintTy},
+ static_lifetime, to_chalk_trait_id,
+ utils::{generics, Generics},
+ AdtId, Binders, CallableDefId, FnPointer, FnSig, FnSubst, Interner, Rawness, Scalar,
+ Substitution, TraitRef, Ty, TyBuilder, TyExt, TyKind,
+};
+
+use super::{
+ coerce::auto_deref_adjust_steps, find_breakable, BindingMode, BreakableContext, Diverges,
+ Expectation, InferenceContext, InferenceDiagnostic, TypeMismatch,
+};
+
+impl<'a> InferenceContext<'a> {
+ pub(crate) fn infer_expr(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty {
+ let ty = self.infer_expr_inner(tgt_expr, expected);
+ if let Some(expected_ty) = expected.only_has_type(&mut self.table) {
+ let could_unify = self.unify(&ty, &expected_ty);
+ if !could_unify {
+ self.result.type_mismatches.insert(
+ tgt_expr.into(),
+ TypeMismatch { expected: expected_ty, actual: ty.clone() },
+ );
+ }
+ }
+ ty
+ }
+
+ /// Infer type of expression with possibly implicit coerce to the expected type.
+ /// Return the type after possible coercion.
+ pub(super) fn infer_expr_coerce(&mut self, expr: ExprId, expected: &Expectation) -> Ty {
+ let ty = self.infer_expr_inner(expr, expected);
+ if let Some(target) = expected.only_has_type(&mut self.table) {
+ match self.coerce(Some(expr), &ty, &target) {
+ Ok(res) => res,
+ Err(_) => {
+ self.result.type_mismatches.insert(
+ expr.into(),
+ TypeMismatch { expected: target.clone(), actual: ty.clone() },
+ );
+ target
+ }
+ }
+ } else {
+ ty
+ }
+ }
+
+ fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty {
+ self.db.unwind_if_cancelled();
+
+ let ty = match &self.body[tgt_expr] {
+ Expr::Missing => self.err_ty(),
+ &Expr::If { condition, then_branch, else_branch } => {
+ self.infer_expr(
+ condition,
+ &Expectation::has_type(TyKind::Scalar(Scalar::Bool).intern(Interner)),
+ );
+
+ let condition_diverges = mem::replace(&mut self.diverges, Diverges::Maybe);
+ let mut both_arms_diverge = Diverges::Always;
+
+ let result_ty = self.table.new_type_var();
+ let then_ty = self.infer_expr_inner(then_branch, expected);
+ both_arms_diverge &= mem::replace(&mut self.diverges, Diverges::Maybe);
+ let mut coerce = CoerceMany::new(result_ty);
+ coerce.coerce(self, Some(then_branch), &then_ty);
+ let else_ty = match else_branch {
+ Some(else_branch) => self.infer_expr_inner(else_branch, expected),
+ None => TyBuilder::unit(),
+ };
+ both_arms_diverge &= self.diverges;
+ // FIXME: create a synthetic `else {}` so we have something to refer to here instead of None?
+ coerce.coerce(self, else_branch, &else_ty);
+
+ self.diverges = condition_diverges | both_arms_diverge;
+
+ coerce.complete()
+ }
+ &Expr::Let { pat, expr } => {
+ let input_ty = self.infer_expr(expr, &Expectation::none());
+ self.infer_pat(pat, &input_ty, BindingMode::default());
+ TyKind::Scalar(Scalar::Bool).intern(Interner)
+ }
+ Expr::Block { statements, tail, label, id: _ } => {
+ let old_resolver = mem::replace(
+ &mut self.resolver,
+ resolver_for_expr(self.db.upcast(), self.owner, tgt_expr),
+ );
+ let ty = match label {
+ Some(_) => {
+ let break_ty = self.table.new_type_var();
+ let (breaks, ty) = self.with_breakable_ctx(
+ BreakableKind::Block,
+ break_ty.clone(),
+ *label,
+ |this| {
+ this.infer_block(
+ tgt_expr,
+ statements,
+ *tail,
+ &Expectation::has_type(break_ty),
+ )
+ },
+ );
+ breaks.unwrap_or(ty)
+ }
+ None => self.infer_block(tgt_expr, statements, *tail, expected),
+ };
+ self.resolver = old_resolver;
+ ty
+ }
+ Expr::Unsafe { body } => self.infer_expr(*body, expected),
+ Expr::Const { body } => {
+ self.with_breakable_ctx(BreakableKind::Border, self.err_ty(), None, |this| {
+ this.infer_expr(*body, expected)
+ })
+ .1
+ }
+ Expr::TryBlock { body } => {
+ self.with_breakable_ctx(BreakableKind::Border, self.err_ty(), None, |this| {
+ let _inner = this.infer_expr(*body, expected);
+ });
+ // FIXME should be std::result::Result<{inner}, _>
+ self.err_ty()
+ }
+ Expr::Async { body } => {
+ let ret_ty = self.table.new_type_var();
+ let prev_diverges = mem::replace(&mut self.diverges, Diverges::Maybe);
+ let prev_ret_ty = mem::replace(&mut self.return_ty, ret_ty.clone());
+
+ let (_, inner_ty) =
+ self.with_breakable_ctx(BreakableKind::Border, self.err_ty(), None, |this| {
+ this.infer_expr_coerce(*body, &Expectation::has_type(ret_ty))
+ });
+
+ self.diverges = prev_diverges;
+ self.return_ty = prev_ret_ty;
+
+ // Use the first type parameter as the output type of future.
+ // existential type AsyncBlockImplTrait<InnerType>: Future<Output = InnerType>
+ let impl_trait_id = crate::ImplTraitId::AsyncBlockTypeImplTrait(self.owner, *body);
+ let opaque_ty_id = self.db.intern_impl_trait_id(impl_trait_id).into();
+ TyKind::OpaqueType(opaque_ty_id, Substitution::from1(Interner, inner_ty))
+ .intern(Interner)
+ }
+ &Expr::Loop { body, label } => {
+ let ty = self.table.new_type_var();
+ let (breaks, ()) =
+ self.with_breakable_ctx(BreakableKind::Loop, ty, label, |this| {
+ this.infer_expr(body, &Expectation::has_type(TyBuilder::unit()));
+ });
+
+ match breaks {
+ Some(breaks) => {
+ self.diverges = Diverges::Maybe;
+ breaks
+ }
+ None => TyKind::Never.intern(Interner),
+ }
+ }
+ &Expr::While { condition, body, label } => {
+ self.with_breakable_ctx(BreakableKind::Loop, self.err_ty(), label, |this| {
+ this.infer_expr(
+ condition,
+ &Expectation::has_type(TyKind::Scalar(Scalar::Bool).intern(Interner)),
+ );
+ this.infer_expr(body, &Expectation::has_type(TyBuilder::unit()));
+ });
+
+ // the body may not run, so it diverging doesn't mean we diverge
+ self.diverges = Diverges::Maybe;
+ TyBuilder::unit()
+ }
+ &Expr::For { iterable, body, pat, label } => {
+ let iterable_ty = self.infer_expr(iterable, &Expectation::none());
- Expr::Closure { body, args, ret_type, arg_types } => {
++ let into_iter_ty =
+ self.resolve_associated_type(iterable_ty, self.resolve_into_iter_item());
++ let pat_ty =
++ self.resolve_associated_type(into_iter_ty, self.resolve_iterator_item());
+
+ self.infer_pat(pat, &pat_ty, BindingMode::default());
+ self.with_breakable_ctx(BreakableKind::Loop, self.err_ty(), label, |this| {
+ this.infer_expr(body, &Expectation::has_type(TyBuilder::unit()));
+ });
+
+ // the body may not run, so it diverging doesn't mean we diverge
+ self.diverges = Diverges::Maybe;
+ TyBuilder::unit()
+ }
- let closure_id = self.db.intern_closure((self.owner, tgt_expr)).into();
- let closure_ty =
- TyKind::Closure(closure_id, Substitution::from1(Interner, sig_ty.clone()))
- .intern(Interner);
++ Expr::Closure { body, args, ret_type, arg_types, closure_kind } => {
+ assert_eq!(args.len(), arg_types.len());
+
+ let mut sig_tys = Vec::new();
+
+ // collect explicitly written argument types
+ for arg_type in arg_types.iter() {
+ let arg_ty = match arg_type {
+ Some(type_ref) => self.make_ty(type_ref),
+ None => self.table.new_type_var(),
+ };
+ sig_tys.push(arg_ty);
+ }
+
+ // add return type
+ let ret_ty = match ret_type {
+ Some(type_ref) => self.make_ty(type_ref),
+ None => self.table.new_type_var(),
+ };
+ sig_tys.push(ret_ty.clone());
+ let sig_ty = TyKind::Function(FnPointer {
+ num_binders: 0,
+ sig: FnSig { abi: (), safety: chalk_ir::Safety::Safe, variadic: false },
+ substitution: FnSubst(
+ Substitution::from_iter(Interner, sig_tys.clone()).shifted_in(Interner),
+ ),
+ })
+ .intern(Interner);
- self.deduce_closure_type_from_expectations(
- tgt_expr,
- &closure_ty,
- &sig_ty,
- expected,
- );
++
++ let (ty, resume_yield_tys) = if matches!(closure_kind, ClosureKind::Generator(_)) {
++ // FIXME: report error when there are more than 1 parameter.
++ let resume_ty = match sig_tys.first() {
++ // When `sig_tys.len() == 1` the first type is the return type, not the
++ // first parameter type.
++ Some(ty) if sig_tys.len() > 1 => ty.clone(),
++ _ => self.result.standard_types.unit.clone(),
++ };
++ let yield_ty = self.table.new_type_var();
++
++ let subst = TyBuilder::subst_for_generator(self.db, self.owner)
++ .push(resume_ty.clone())
++ .push(yield_ty.clone())
++ .push(ret_ty.clone())
++ .build();
++
++ let generator_id = self.db.intern_generator((self.owner, tgt_expr)).into();
++ let generator_ty = TyKind::Generator(generator_id, subst).intern(Interner);
++
++ (generator_ty, Some((resume_ty, yield_ty)))
++ } else {
++ let closure_id = self.db.intern_closure((self.owner, tgt_expr)).into();
++ let closure_ty =
++ TyKind::Closure(closure_id, Substitution::from1(Interner, sig_ty.clone()))
++ .intern(Interner);
++
++ (closure_ty, None)
++ };
+
+ // Eagerly try to relate the closure type with the expected
+ // type, otherwise we often won't have enough information to
+ // infer the body.
- closure_ty
++ self.deduce_closure_type_from_expectations(tgt_expr, &ty, &sig_ty, expected);
+
+ // Now go through the argument patterns
+ for (arg_pat, arg_ty) in args.iter().zip(sig_tys) {
+ self.infer_pat(*arg_pat, &arg_ty, BindingMode::default());
+ }
+
+ let prev_diverges = mem::replace(&mut self.diverges, Diverges::Maybe);
+ let prev_ret_ty = mem::replace(&mut self.return_ty, ret_ty.clone());
++ let prev_resume_yield_tys =
++ mem::replace(&mut self.resume_yield_tys, resume_yield_tys);
+
+ self.with_breakable_ctx(BreakableKind::Border, self.err_ty(), None, |this| {
+ this.infer_expr_coerce(*body, &Expectation::has_type(ret_ty));
+ });
+
+ self.diverges = prev_diverges;
+ self.return_ty = prev_ret_ty;
++ self.resume_yield_tys = prev_resume_yield_tys;
+
- // FIXME: track yield type for coercion
- if let Some(expr) = expr {
- self.infer_expr(*expr, &Expectation::none());
++ ty
+ }
+ Expr::Call { callee, args, .. } => {
+ let callee_ty = self.infer_expr(*callee, &Expectation::none());
+ let mut derefs = Autoderef::new(&mut self.table, callee_ty.clone());
+ let mut res = None;
+ let mut derefed_callee = callee_ty.clone();
+ // manual loop to be able to access `derefs.table`
+ while let Some((callee_deref_ty, _)) = derefs.next() {
+ res = derefs.table.callable_sig(&callee_deref_ty, args.len());
+ if res.is_some() {
+ derefed_callee = callee_deref_ty;
+ break;
+ }
+ }
+ // if the function is unresolved, we use is_varargs=true to
+ // suppress the arg count diagnostic here
+ let is_varargs =
+ derefed_callee.callable_sig(self.db).map_or(false, |sig| sig.is_varargs)
+ || res.is_none();
+ let (param_tys, ret_ty) = match res {
+ Some(res) => {
+ let adjustments = auto_deref_adjust_steps(&derefs);
+ self.write_expr_adj(*callee, adjustments);
+ res
+ }
+ None => (Vec::new(), self.err_ty()), // FIXME diagnostic
+ };
+ let indices_to_skip = self.check_legacy_const_generics(derefed_callee, args);
+ self.register_obligations_for_call(&callee_ty);
+
+ let expected_inputs = self.expected_inputs_for_expected_output(
+ expected,
+ ret_ty.clone(),
+ param_tys.clone(),
+ );
+
+ self.check_call_arguments(
+ tgt_expr,
+ args,
+ &expected_inputs,
+ ¶m_tys,
+ &indices_to_skip,
+ is_varargs,
+ );
+ self.normalize_associated_types_in(ret_ty)
+ }
+ Expr::MethodCall { receiver, args, method_name, generic_args } => self
+ .infer_method_call(
+ tgt_expr,
+ *receiver,
+ args,
+ method_name,
+ generic_args.as_deref(),
+ expected,
+ ),
+ Expr::Match { expr, arms } => {
+ let input_ty = self.infer_expr(*expr, &Expectation::none());
+
+ let expected = expected.adjust_for_branches(&mut self.table);
+
+ let result_ty = if arms.is_empty() {
+ TyKind::Never.intern(Interner)
+ } else {
+ match &expected {
+ Expectation::HasType(ty) => ty.clone(),
+ _ => self.table.new_type_var(),
+ }
+ };
+ let mut coerce = CoerceMany::new(result_ty);
+
+ let matchee_diverges = self.diverges;
+ let mut all_arms_diverge = Diverges::Always;
+
+ for arm in arms.iter() {
+ self.diverges = Diverges::Maybe;
+ let _pat_ty = self.infer_pat(arm.pat, &input_ty, BindingMode::default());
+ if let Some(guard_expr) = arm.guard {
+ self.infer_expr(
+ guard_expr,
+ &Expectation::has_type(TyKind::Scalar(Scalar::Bool).intern(Interner)),
+ );
+ }
+
+ let arm_ty = self.infer_expr_inner(arm.expr, &expected);
+ all_arms_diverge &= self.diverges;
+ coerce.coerce(self, Some(arm.expr), &arm_ty);
+ }
+
+ self.diverges = matchee_diverges | all_arms_diverge;
+
+ coerce.complete()
+ }
+ Expr::Path(p) => {
+ // FIXME this could be more efficient...
+ let resolver = resolver_for_expr(self.db.upcast(), self.owner, tgt_expr);
+ self.infer_path(&resolver, p, tgt_expr.into()).unwrap_or_else(|| self.err_ty())
+ }
+ Expr::Continue { label } => {
+ if let None = find_continuable(&mut self.breakables, label.as_ref()) {
+ self.push_diagnostic(InferenceDiagnostic::BreakOutsideOfLoop {
+ expr: tgt_expr,
+ is_break: false,
+ });
+ };
+ TyKind::Never.intern(Interner)
+ }
+ Expr::Break { expr, label } => {
+ let val_ty = if let Some(expr) = *expr {
+ self.infer_expr(expr, &Expectation::none())
+ } else {
+ TyBuilder::unit()
+ };
+
+ match find_breakable(&mut self.breakables, label.as_ref()) {
+ Some(ctxt) => {
+ // avoiding the borrowck
+ let mut coerce = mem::replace(
+ &mut ctxt.coerce,
+ CoerceMany::new(self.result.standard_types.unknown.clone()),
+ );
+
+ // FIXME: create a synthetic `()` during lowering so we have something to refer to here?
+ coerce.coerce(self, *expr, &val_ty);
+
+ let ctxt = find_breakable(&mut self.breakables, label.as_ref())
+ .expect("breakable stack changed during coercion");
+ ctxt.coerce = coerce;
+ ctxt.may_break = true;
+ }
+ None => {
+ self.push_diagnostic(InferenceDiagnostic::BreakOutsideOfLoop {
+ expr: tgt_expr,
+ is_break: true,
+ });
+ }
+ }
+ TyKind::Never.intern(Interner)
+ }
+ Expr::Return { expr } => {
+ if let Some(expr) = expr {
+ self.infer_expr_coerce(*expr, &Expectation::has_type(self.return_ty.clone()));
+ } else {
+ let unit = TyBuilder::unit();
+ let _ = self.coerce(Some(tgt_expr), &unit, &self.return_ty.clone());
+ }
+ TyKind::Never.intern(Interner)
+ }
+ Expr::Yield { expr } => {
- TyKind::Never.intern(Interner)
++ if let Some((resume_ty, yield_ty)) = self.resume_yield_tys.clone() {
++ if let Some(expr) = expr {
++ self.infer_expr_coerce(*expr, &Expectation::has_type(yield_ty));
++ } else {
++ let unit = self.result.standard_types.unit.clone();
++ let _ = self.coerce(Some(tgt_expr), &unit, &yield_ty);
++ }
++ resume_ty
++ } else {
++ // FIXME: report error (yield expr in non-generator)
++ TyKind::Error.intern(Interner)
+ }
- let func = lang_names_for_bin_op(op).and_then(|(name, lang_item)| {
- self.db.trait_data(self.resolve_lang_item(lang_item)?.as_trait()?).method_by_name(&name)
+ }
+ Expr::RecordLit { path, fields, spread, .. } => {
+ let (ty, def_id) = self.resolve_variant(path.as_deref(), false);
+ if let Some(variant) = def_id {
+ self.write_variant_resolution(tgt_expr.into(), variant);
+ }
+
+ if let Some(t) = expected.only_has_type(&mut self.table) {
+ self.unify(&ty, &t);
+ }
+
+ let substs = ty
+ .as_adt()
+ .map(|(_, s)| s.clone())
+ .unwrap_or_else(|| Substitution::empty(Interner));
+ let field_types = def_id.map(|it| self.db.field_types(it)).unwrap_or_default();
+ let variant_data = def_id.map(|it| it.variant_data(self.db.upcast()));
+ for field in fields.iter() {
+ let field_def =
+ variant_data.as_ref().and_then(|it| match it.field(&field.name) {
+ Some(local_id) => Some(FieldId { parent: def_id.unwrap(), local_id }),
+ None => {
+ self.push_diagnostic(InferenceDiagnostic::NoSuchField {
+ expr: field.expr,
+ });
+ None
+ }
+ });
+ let field_ty = field_def.map_or(self.err_ty(), |it| {
+ field_types[it.local_id].clone().substitute(Interner, &substs)
+ });
+ self.infer_expr_coerce(field.expr, &Expectation::has_type(field_ty));
+ }
+ if let Some(expr) = spread {
+ self.infer_expr(*expr, &Expectation::has_type(ty.clone()));
+ }
+ ty
+ }
+ Expr::Field { expr, name } => {
+ let receiver_ty = self.infer_expr_inner(*expr, &Expectation::none());
+
+ let mut autoderef = Autoderef::new(&mut self.table, receiver_ty);
+ let ty = autoderef.by_ref().find_map(|(derefed_ty, _)| {
+ let (field_id, parameters) = match derefed_ty.kind(Interner) {
+ TyKind::Tuple(_, substs) => {
+ return name.as_tuple_index().and_then(|idx| {
+ substs
+ .as_slice(Interner)
+ .get(idx)
+ .map(|a| a.assert_ty_ref(Interner))
+ .cloned()
+ });
+ }
+ TyKind::Adt(AdtId(hir_def::AdtId::StructId(s)), parameters) => {
+ let local_id = self.db.struct_data(*s).variant_data.field(name)?;
+ let field = FieldId { parent: (*s).into(), local_id };
+ (field, parameters.clone())
+ }
+ TyKind::Adt(AdtId(hir_def::AdtId::UnionId(u)), parameters) => {
+ let local_id = self.db.union_data(*u).variant_data.field(name)?;
+ let field = FieldId { parent: (*u).into(), local_id };
+ (field, parameters.clone())
+ }
+ _ => return None,
+ };
+ let is_visible = self.db.field_visibilities(field_id.parent)[field_id.local_id]
+ .is_visible_from(self.db.upcast(), self.resolver.module());
+ if !is_visible {
+ // Write down the first field resolution even if it is not visible
+ // This aids IDE features for private fields like goto def and in
+ // case of autoderef finding an applicable field, this will be
+ // overwritten in a following cycle
+ if let Entry::Vacant(entry) = self.result.field_resolutions.entry(tgt_expr)
+ {
+ entry.insert(field_id);
+ }
+ return None;
+ }
+ // can't have `write_field_resolution` here because `self.table` is borrowed :(
+ self.result.field_resolutions.insert(tgt_expr, field_id);
+ let ty = self.db.field_types(field_id.parent)[field_id.local_id]
+ .clone()
+ .substitute(Interner, ¶meters);
+ Some(ty)
+ });
+ let ty = match ty {
+ Some(ty) => {
+ let adjustments = auto_deref_adjust_steps(&autoderef);
+ self.write_expr_adj(*expr, adjustments);
+ let ty = self.insert_type_vars(ty);
+ let ty = self.normalize_associated_types_in(ty);
+ ty
+ }
+ _ => self.err_ty(),
+ };
+ ty
+ }
+ Expr::Await { expr } => {
+ let inner_ty = self.infer_expr_inner(*expr, &Expectation::none());
+ self.resolve_associated_type(inner_ty, self.resolve_future_future_output())
+ }
+ Expr::Try { expr } => {
+ let inner_ty = self.infer_expr_inner(*expr, &Expectation::none());
+ self.resolve_associated_type(inner_ty, self.resolve_ops_try_ok())
+ }
+ Expr::Cast { expr, type_ref } => {
+ // FIXME: propagate the "castable to" expectation (and find a test case that shows this is necessary)
+ let _inner_ty = self.infer_expr_inner(*expr, &Expectation::none());
+ let cast_ty = self.make_ty(type_ref);
+ // FIXME check the cast...
+ cast_ty
+ }
+ Expr::Ref { expr, rawness, mutability } => {
+ let mutability = lower_to_chalk_mutability(*mutability);
+ let expectation = if let Some((exp_inner, exp_rawness, exp_mutability)) = expected
+ .only_has_type(&mut self.table)
+ .as_ref()
+ .and_then(|t| t.as_reference_or_ptr())
+ {
+ if exp_mutability == Mutability::Mut && mutability == Mutability::Not {
+ // FIXME: record type error - expected mut reference but found shared ref,
+ // which cannot be coerced
+ }
+ if exp_rawness == Rawness::Ref && *rawness == Rawness::RawPtr {
+ // FIXME: record type error - expected reference but found ptr,
+ // which cannot be coerced
+ }
+ Expectation::rvalue_hint(&mut self.table, Ty::clone(exp_inner))
+ } else {
+ Expectation::none()
+ };
+ let inner_ty = self.infer_expr_inner(*expr, &expectation);
+ match rawness {
+ Rawness::RawPtr => TyKind::Raw(mutability, inner_ty),
+ Rawness::Ref => TyKind::Ref(mutability, static_lifetime(), inner_ty),
+ }
+ .intern(Interner)
+ }
+ &Expr::Box { expr } => self.infer_expr_box(expr, expected),
+ Expr::UnaryOp { expr, op } => {
+ let inner_ty = self.infer_expr_inner(*expr, &Expectation::none());
+ let inner_ty = self.resolve_ty_shallow(&inner_ty);
+ match op {
+ UnaryOp::Deref => {
+ autoderef::deref(&mut self.table, inner_ty).unwrap_or_else(|| self.err_ty())
+ }
+ UnaryOp::Neg => {
+ match inner_ty.kind(Interner) {
+ // Fast path for builtins
+ TyKind::Scalar(Scalar::Int(_) | Scalar::Uint(_) | Scalar::Float(_))
+ | TyKind::InferenceVar(
+ _,
+ TyVariableKind::Integer | TyVariableKind::Float,
+ ) => inner_ty,
+ // Otherwise we resolve via the std::ops::Neg trait
+ _ => self
+ .resolve_associated_type(inner_ty, self.resolve_ops_neg_output()),
+ }
+ }
+ UnaryOp::Not => {
+ match inner_ty.kind(Interner) {
+ // Fast path for builtins
+ TyKind::Scalar(Scalar::Bool | Scalar::Int(_) | Scalar::Uint(_))
+ | TyKind::InferenceVar(_, TyVariableKind::Integer) => inner_ty,
+ // Otherwise we resolve via the std::ops::Not trait
+ _ => self
+ .resolve_associated_type(inner_ty, self.resolve_ops_not_output()),
+ }
+ }
+ }
+ }
+ Expr::BinaryOp { lhs, rhs, op } => match op {
+ Some(BinaryOp::Assignment { op: None }) => {
+ let lhs = *lhs;
+ let is_ordinary = match &self.body[lhs] {
+ Expr::Array(_)
+ | Expr::RecordLit { .. }
+ | Expr::Tuple { .. }
+ | Expr::Underscore => false,
+ Expr::Call { callee, .. } => !matches!(&self.body[*callee], Expr::Path(_)),
+ _ => true,
+ };
+
+ // In ordinary (non-destructuring) assignments, the type of
+ // `lhs` must be inferred first so that the ADT fields
+ // instantiations in RHS can be coerced to it. Note that this
+ // cannot happen in destructuring assignments because of how
+ // they are desugared.
+ if is_ordinary {
+ let lhs_ty = self.infer_expr(lhs, &Expectation::none());
+ self.infer_expr_coerce(*rhs, &Expectation::has_type(lhs_ty));
+ } else {
+ let rhs_ty = self.infer_expr(*rhs, &Expectation::none());
+ self.infer_assignee_expr(lhs, &rhs_ty);
+ }
+ self.result.standard_types.unit.clone()
+ }
+ Some(BinaryOp::LogicOp(_)) => {
+ let bool_ty = self.result.standard_types.bool_.clone();
+ self.infer_expr_coerce(*lhs, &Expectation::HasType(bool_ty.clone()));
+ let lhs_diverges = self.diverges;
+ self.infer_expr_coerce(*rhs, &Expectation::HasType(bool_ty.clone()));
+ // Depending on the LHS' value, the RHS can never execute.
+ self.diverges = lhs_diverges;
+ bool_ty
+ }
+ Some(op) => self.infer_overloadable_binop(*lhs, *op, *rhs, tgt_expr),
+ _ => self.err_ty(),
+ },
+ Expr::Range { lhs, rhs, range_type } => {
+ let lhs_ty = lhs.map(|e| self.infer_expr_inner(e, &Expectation::none()));
+ let rhs_expect = lhs_ty
+ .as_ref()
+ .map_or_else(Expectation::none, |ty| Expectation::has_type(ty.clone()));
+ let rhs_ty = rhs.map(|e| self.infer_expr(e, &rhs_expect));
+ match (range_type, lhs_ty, rhs_ty) {
+ (RangeOp::Exclusive, None, None) => match self.resolve_range_full() {
+ Some(adt) => TyBuilder::adt(self.db, adt).build(),
+ None => self.err_ty(),
+ },
+ (RangeOp::Exclusive, None, Some(ty)) => match self.resolve_range_to() {
+ Some(adt) => TyBuilder::adt(self.db, adt).push(ty).build(),
+ None => self.err_ty(),
+ },
+ (RangeOp::Inclusive, None, Some(ty)) => {
+ match self.resolve_range_to_inclusive() {
+ Some(adt) => TyBuilder::adt(self.db, adt).push(ty).build(),
+ None => self.err_ty(),
+ }
+ }
+ (RangeOp::Exclusive, Some(_), Some(ty)) => match self.resolve_range() {
+ Some(adt) => TyBuilder::adt(self.db, adt).push(ty).build(),
+ None => self.err_ty(),
+ },
+ (RangeOp::Inclusive, Some(_), Some(ty)) => {
+ match self.resolve_range_inclusive() {
+ Some(adt) => TyBuilder::adt(self.db, adt).push(ty).build(),
+ None => self.err_ty(),
+ }
+ }
+ (RangeOp::Exclusive, Some(ty), None) => match self.resolve_range_from() {
+ Some(adt) => TyBuilder::adt(self.db, adt).push(ty).build(),
+ None => self.err_ty(),
+ },
+ (RangeOp::Inclusive, _, None) => self.err_ty(),
+ }
+ }
+ Expr::Index { base, index } => {
+ let base_ty = self.infer_expr_inner(*base, &Expectation::none());
+ let index_ty = self.infer_expr(*index, &Expectation::none());
+
+ if let Some(index_trait) = self.resolve_ops_index() {
+ let canonicalized = self.canonicalize(base_ty.clone());
+ let receiver_adjustments = method_resolution::resolve_indexing_op(
+ self.db,
+ self.trait_env.clone(),
+ canonicalized.value,
+ index_trait,
+ );
+ let (self_ty, adj) = receiver_adjustments
+ .map_or((self.err_ty(), Vec::new()), |adj| {
+ adj.apply(&mut self.table, base_ty)
+ });
+ self.write_expr_adj(*base, adj);
+ self.resolve_associated_type_with_params(
+ self_ty,
+ self.resolve_ops_index_output(),
+ &[GenericArgData::Ty(index_ty).intern(Interner)],
+ )
+ } else {
+ self.err_ty()
+ }
+ }
+ Expr::Tuple { exprs, .. } => {
+ let mut tys = match expected
+ .only_has_type(&mut self.table)
+ .as_ref()
+ .map(|t| t.kind(Interner))
+ {
+ Some(TyKind::Tuple(_, substs)) => substs
+ .iter(Interner)
+ .map(|a| a.assert_ty_ref(Interner).clone())
+ .chain(repeat_with(|| self.table.new_type_var()))
+ .take(exprs.len())
+ .collect::<Vec<_>>(),
+ _ => (0..exprs.len()).map(|_| self.table.new_type_var()).collect(),
+ };
+
+ for (expr, ty) in exprs.iter().zip(tys.iter_mut()) {
+ self.infer_expr_coerce(*expr, &Expectation::has_type(ty.clone()));
+ }
+
+ TyKind::Tuple(tys.len(), Substitution::from_iter(Interner, tys)).intern(Interner)
+ }
+ Expr::Array(array) => {
+ let elem_ty =
+ match expected.to_option(&mut self.table).as_ref().map(|t| t.kind(Interner)) {
+ Some(TyKind::Array(st, _) | TyKind::Slice(st)) => st.clone(),
+ _ => self.table.new_type_var(),
+ };
+ let mut coerce = CoerceMany::new(elem_ty.clone());
+
+ let expected = Expectation::has_type(elem_ty.clone());
+ let len = match array {
+ Array::ElementList { elements, .. } => {
+ for &expr in elements.iter() {
+ let cur_elem_ty = self.infer_expr_inner(expr, &expected);
+ coerce.coerce(self, Some(expr), &cur_elem_ty);
+ }
+ consteval::usize_const(Some(elements.len() as u128))
+ }
+ &Array::Repeat { initializer, repeat } => {
+ self.infer_expr_coerce(initializer, &Expectation::has_type(elem_ty));
+ self.infer_expr(
+ repeat,
+ &Expectation::has_type(
+ TyKind::Scalar(Scalar::Uint(UintTy::Usize)).intern(Interner),
+ ),
+ );
+
+ if let Some(g_def) = self.owner.as_generic_def_id() {
+ let generics = generics(self.db.upcast(), g_def);
+ consteval::eval_to_const(
+ repeat,
+ ParamLoweringMode::Placeholder,
+ self,
+ || generics,
+ DebruijnIndex::INNERMOST,
+ )
+ } else {
+ consteval::usize_const(None)
+ }
+ }
+ };
+
+ TyKind::Array(coerce.complete(), len).intern(Interner)
+ }
+ Expr::Literal(lit) => match lit {
+ Literal::Bool(..) => TyKind::Scalar(Scalar::Bool).intern(Interner),
+ Literal::String(..) => {
+ TyKind::Ref(Mutability::Not, static_lifetime(), TyKind::Str.intern(Interner))
+ .intern(Interner)
+ }
+ Literal::ByteString(bs) => {
+ let byte_type = TyKind::Scalar(Scalar::Uint(UintTy::U8)).intern(Interner);
+
+ let len = consteval::usize_const(Some(bs.len() as u128));
+
+ let array_type = TyKind::Array(byte_type, len).intern(Interner);
+ TyKind::Ref(Mutability::Not, static_lifetime(), array_type).intern(Interner)
+ }
+ Literal::Char(..) => TyKind::Scalar(Scalar::Char).intern(Interner),
+ Literal::Int(_v, ty) => match ty {
+ Some(int_ty) => {
+ TyKind::Scalar(Scalar::Int(primitive::int_ty_from_builtin(*int_ty)))
+ .intern(Interner)
+ }
+ None => self.table.new_integer_var(),
+ },
+ Literal::Uint(_v, ty) => match ty {
+ Some(int_ty) => {
+ TyKind::Scalar(Scalar::Uint(primitive::uint_ty_from_builtin(*int_ty)))
+ .intern(Interner)
+ }
+ None => self.table.new_integer_var(),
+ },
+ Literal::Float(_v, ty) => match ty {
+ Some(float_ty) => {
+ TyKind::Scalar(Scalar::Float(primitive::float_ty_from_builtin(*float_ty)))
+ .intern(Interner)
+ }
+ None => self.table.new_float_var(),
+ },
+ },
+ Expr::Underscore => {
+ // Underscore expressions may only appear in assignee expressions,
+ // which are handled by `infer_assignee_expr()`, so any underscore
+ // expression reaching this branch is an error.
+ self.err_ty()
+ }
+ };
+ // use a new type variable if we got unknown here
+ let ty = self.insert_type_vars_shallow(ty);
+ self.write_expr_ty(tgt_expr, ty.clone());
+ if self.resolve_ty_shallow(&ty).is_never() {
+ // Any expression that produces a value of type `!` must have diverged
+ self.diverges = Diverges::Always;
+ }
+ ty
+ }
+
+ fn infer_expr_box(&mut self, inner_expr: ExprId, expected: &Expectation) -> Ty {
+ if let Some(box_id) = self.resolve_boxed_box() {
+ let table = &mut self.table;
+ let inner_exp = expected
+ .to_option(table)
+ .as_ref()
+ .map(|e| e.as_adt())
+ .flatten()
+ .filter(|(e_adt, _)| e_adt == &box_id)
+ .map(|(_, subts)| {
+ let g = subts.at(Interner, 0);
+ Expectation::rvalue_hint(table, Ty::clone(g.assert_ty_ref(Interner)))
+ })
+ .unwrap_or_else(Expectation::none);
+
+ let inner_ty = self.infer_expr_inner(inner_expr, &inner_exp);
+ TyBuilder::adt(self.db, box_id)
+ .push(inner_ty)
+ .fill_with_defaults(self.db, || self.table.new_type_var())
+ .build()
+ } else {
+ self.err_ty()
+ }
+ }
+
+ pub(super) fn infer_assignee_expr(&mut self, lhs: ExprId, rhs_ty: &Ty) -> Ty {
+ let is_rest_expr = |expr| {
+ matches!(
+ &self.body[expr],
+ Expr::Range { lhs: None, rhs: None, range_type: RangeOp::Exclusive },
+ )
+ };
+
+ let rhs_ty = self.resolve_ty_shallow(rhs_ty);
+
+ let ty = match &self.body[lhs] {
+ Expr::Tuple { exprs, .. } => {
+ // We don't consider multiple ellipses. This is analogous to
+ // `hir_def::body::lower::ExprCollector::collect_tuple_pat()`.
+ let ellipsis = exprs.iter().position(|e| is_rest_expr(*e));
+ let exprs: Vec<_> = exprs.iter().filter(|e| !is_rest_expr(**e)).copied().collect();
+
+ self.infer_tuple_pat_like(&rhs_ty, (), ellipsis, &exprs)
+ }
+ Expr::Call { callee, args, .. } => {
+ // Tuple structs
+ let path = match &self.body[*callee] {
+ Expr::Path(path) => Some(path),
+ _ => None,
+ };
+
+ // We don't consider multiple ellipses. This is analogous to
+ // `hir_def::body::lower::ExprCollector::collect_tuple_pat()`.
+ let ellipsis = args.iter().position(|e| is_rest_expr(*e));
+ let args: Vec<_> = args.iter().filter(|e| !is_rest_expr(**e)).copied().collect();
+
+ self.infer_tuple_struct_pat_like(path, &rhs_ty, (), lhs, ellipsis, &args)
+ }
+ Expr::Array(Array::ElementList { elements, .. }) => {
+ let elem_ty = match rhs_ty.kind(Interner) {
+ TyKind::Array(st, _) => st.clone(),
+ _ => self.err_ty(),
+ };
+
+ // There's no need to handle `..` as it cannot be bound.
+ let sub_exprs = elements.iter().filter(|e| !is_rest_expr(**e));
+
+ for e in sub_exprs {
+ self.infer_assignee_expr(*e, &elem_ty);
+ }
+
+ match rhs_ty.kind(Interner) {
+ TyKind::Array(_, _) => rhs_ty.clone(),
+ // Even when `rhs_ty` is not an array type, this assignee
+ // expression is inferred to be an array (of unknown element
+ // type and length). This should not be just an error type,
+ // because we are to compute the unifiability of this type and
+ // `rhs_ty` in the end of this function to issue type mismatches.
+ _ => TyKind::Array(self.err_ty(), crate::consteval::usize_const(None))
+ .intern(Interner),
+ }
+ }
+ Expr::RecordLit { path, fields, .. } => {
+ let subs = fields.iter().map(|f| (f.name.clone(), f.expr));
+
+ self.infer_record_pat_like(path.as_deref(), &rhs_ty, (), lhs.into(), subs)
+ }
+ Expr::Underscore => rhs_ty.clone(),
+ _ => {
+ // `lhs` is a place expression, a unit struct, or an enum variant.
+ let lhs_ty = self.infer_expr(lhs, &Expectation::none());
+
+ // This is the only branch where this function may coerce any type.
+ // We are returning early to avoid the unifiability check below.
+ let lhs_ty = self.insert_type_vars_shallow(lhs_ty);
+ let ty = match self.coerce(None, &rhs_ty, &lhs_ty) {
+ Ok(ty) => ty,
+ Err(_) => {
+ self.result.type_mismatches.insert(
+ lhs.into(),
+ TypeMismatch { expected: rhs_ty.clone(), actual: lhs_ty.clone() },
+ );
+ // `rhs_ty` is returned so no further type mismatches are
+ // reported because of this mismatch.
+ rhs_ty
+ }
+ };
+ self.write_expr_ty(lhs, ty.clone());
+ return ty;
+ }
+ };
+
+ let ty = self.insert_type_vars_shallow(ty);
+ if !self.unify(&ty, &rhs_ty) {
+ self.result
+ .type_mismatches
+ .insert(lhs.into(), TypeMismatch { expected: rhs_ty.clone(), actual: ty.clone() });
+ }
+ self.write_expr_ty(lhs, ty.clone());
+ ty
+ }
+
+ fn infer_overloadable_binop(
+ &mut self,
+ lhs: ExprId,
+ op: BinaryOp,
+ rhs: ExprId,
+ tgt_expr: ExprId,
+ ) -> Ty {
+ let lhs_expectation = Expectation::none();
+ let lhs_ty = self.infer_expr(lhs, &lhs_expectation);
+ let rhs_ty = self.table.new_type_var();
+
- let func = match func {
- Some(func) => func,
++ let trait_func = lang_names_for_bin_op(op).and_then(|(name, lang_item)| {
++ let trait_id = self.resolve_lang_item(lang_item)?.as_trait()?;
++ let func = self.db.trait_data(trait_id).method_by_name(&name)?;
++ Some((trait_id, func))
+ });
- let subst = TyBuilder::subst_for_def(self.db, func)
++ let (trait_, func) = match trait_func {
++ Some(it) => it,
+ None => {
+ let rhs_ty = self.builtin_binary_op_rhs_expectation(op, lhs_ty.clone());
+ let rhs_ty = self.infer_expr_coerce(rhs, &Expectation::from_option(rhs_ty));
+ return self
+ .builtin_binary_op_return_ty(op, lhs_ty, rhs_ty)
+ .unwrap_or_else(|| self.err_ty());
+ }
+ };
+
- // Parent arguments are unknown
- for (id, param) in def_generics.iter_parent() {
- match param {
- TypeOrConstParamData::TypeParamData(_) => {
- substs.push(GenericArgData::Ty(self.table.new_type_var()).intern(Interner));
- }
- TypeOrConstParamData::ConstParamData(_) => {
- let ty = self.db.const_param_ty(ConstParamId::from_unchecked(id));
- substs
- .push(GenericArgData::Const(self.table.new_const_var(ty)).intern(Interner));
- }
- }
- }
++ // HACK: We can use this substitution for the function because the function itself doesn't
++ // have its own generic parameters.
++ let subst = TyBuilder::subst_for_def(self.db, trait_, None)
+ .push(lhs_ty.clone())
+ .push(rhs_ty.clone())
+ .build();
+ self.write_method_resolution(tgt_expr, func, subst.clone());
+
+ let method_ty = self.db.value_ty(func.into()).substitute(Interner, &subst);
+ self.register_obligations_for_call(&method_ty);
+
+ self.infer_expr_coerce(rhs, &Expectation::has_type(rhs_ty.clone()));
+
+ let ret_ty = match method_ty.callable_sig(self.db) {
+ Some(sig) => sig.ret().clone(),
+ None => self.err_ty(),
+ };
+
+ let ret_ty = self.normalize_associated_types_in(ret_ty);
+
+ // FIXME: record autoref adjustments
+
+ // use knowledge of built-in binary ops, which can sometimes help inference
+ if let Some(builtin_rhs) = self.builtin_binary_op_rhs_expectation(op, lhs_ty.clone()) {
+ self.unify(&builtin_rhs, &rhs_ty);
+ }
+ if let Some(builtin_ret) = self.builtin_binary_op_return_ty(op, lhs_ty, rhs_ty) {
+ self.unify(&builtin_ret, &ret_ty);
+ }
+
+ ret_ty
+ }
+
+ fn infer_block(
+ &mut self,
+ expr: ExprId,
+ statements: &[Statement],
+ tail: Option<ExprId>,
+ expected: &Expectation,
+ ) -> Ty {
+ for stmt in statements {
+ match stmt {
+ Statement::Let { pat, type_ref, initializer, else_branch } => {
+ let decl_ty = type_ref
+ .as_ref()
+ .map(|tr| self.make_ty(tr))
+ .unwrap_or_else(|| self.err_ty());
+
+ // Always use the declared type when specified
+ let mut ty = decl_ty.clone();
+
+ if let Some(expr) = initializer {
+ let actual_ty =
+ self.infer_expr_coerce(*expr, &Expectation::has_type(decl_ty.clone()));
+ if decl_ty.is_unknown() {
+ ty = actual_ty;
+ }
+ }
+
+ if let Some(expr) = else_branch {
+ self.infer_expr_coerce(
+ *expr,
+ &Expectation::has_type(Ty::new(Interner, TyKind::Never)),
+ );
+ }
+
+ self.infer_pat(*pat, &ty, BindingMode::default());
+ }
+ Statement::Expr { expr, .. } => {
+ self.infer_expr(*expr, &Expectation::none());
+ }
+ }
+ }
+
+ if let Some(expr) = tail {
+ self.infer_expr_coerce(expr, expected)
+ } else {
+ // Citing rustc: if there is no explicit tail expression,
+ // that is typically equivalent to a tail expression
+ // of `()` -- except if the block diverges. In that
+ // case, there is no value supplied from the tail
+ // expression (assuming there are no other breaks,
+ // this implies that the type of the block will be
+ // `!`).
+ if self.diverges.is_always() {
+ // we don't even make an attempt at coercion
+ self.table.new_maybe_never_var()
+ } else {
+ if let Some(t) = expected.only_has_type(&mut self.table) {
+ if self.coerce(Some(expr), &TyBuilder::unit(), &t).is_err() {
+ self.result.type_mismatches.insert(
+ expr.into(),
+ TypeMismatch { expected: t.clone(), actual: TyBuilder::unit() },
+ );
+ }
+ t
+ } else {
+ TyBuilder::unit()
+ }
+ }
+ }
+ }
+
+ fn infer_method_call(
+ &mut self,
+ tgt_expr: ExprId,
+ receiver: ExprId,
+ args: &[ExprId],
+ method_name: &Name,
+ generic_args: Option<&GenericArgs>,
+ expected: &Expectation,
+ ) -> Ty {
+ let receiver_ty = self.infer_expr(receiver, &Expectation::none());
+ let canonicalized_receiver = self.canonicalize(receiver_ty.clone());
+
+ let traits_in_scope = self.resolver.traits_in_scope(self.db.upcast());
+
+ let resolved = method_resolution::lookup_method(
+ &canonicalized_receiver.value,
+ self.db,
+ self.trait_env.clone(),
+ &traits_in_scope,
+ VisibleFromModule::Filter(self.resolver.module()),
+ method_name,
+ );
+ let (receiver_ty, method_ty, substs) = match resolved {
+ Some((adjust, func)) => {
+ let (ty, adjustments) = adjust.apply(&mut self.table, receiver_ty);
+ let generics = generics(self.db.upcast(), func.into());
+ let substs = self.substs_for_method_call(generics, generic_args);
+ self.write_expr_adj(receiver, adjustments);
+ self.write_method_resolution(tgt_expr, func, substs.clone());
+ (ty, self.db.value_ty(func.into()), substs)
+ }
+ None => (
+ receiver_ty,
+ Binders::empty(Interner, self.err_ty()),
+ Substitution::empty(Interner),
+ ),
+ };
+ let method_ty = method_ty.substitute(Interner, &substs);
+ self.register_obligations_for_call(&method_ty);
+ let (formal_receiver_ty, param_tys, ret_ty, is_varargs) =
+ match method_ty.callable_sig(self.db) {
+ Some(sig) => {
+ if !sig.params().is_empty() {
+ (
+ sig.params()[0].clone(),
+ sig.params()[1..].to_vec(),
+ sig.ret().clone(),
+ sig.is_varargs,
+ )
+ } else {
+ (self.err_ty(), Vec::new(), sig.ret().clone(), sig.is_varargs)
+ }
+ }
+ None => (self.err_ty(), Vec::new(), self.err_ty(), true),
+ };
+ self.unify(&formal_receiver_ty, &receiver_ty);
+
+ let expected_inputs =
+ self.expected_inputs_for_expected_output(expected, ret_ty.clone(), param_tys.clone());
+
+ self.check_call_arguments(tgt_expr, args, &expected_inputs, ¶m_tys, &[], is_varargs);
+ self.normalize_associated_types_in(ret_ty)
+ }
+
+ fn expected_inputs_for_expected_output(
+ &mut self,
+ expected_output: &Expectation,
+ output: Ty,
+ inputs: Vec<Ty>,
+ ) -> Vec<Ty> {
+ if let Some(expected_ty) = expected_output.to_option(&mut self.table) {
+ self.table.fudge_inference(|table| {
+ if table.try_unify(&expected_ty, &output).is_ok() {
+ table.resolve_with_fallback(inputs, &|var, kind, _, _| match kind {
+ chalk_ir::VariableKind::Ty(tk) => var.to_ty(Interner, tk).cast(Interner),
+ chalk_ir::VariableKind::Lifetime => {
+ var.to_lifetime(Interner).cast(Interner)
+ }
+ chalk_ir::VariableKind::Const(ty) => {
+ var.to_const(Interner, ty).cast(Interner)
+ }
+ })
+ } else {
+ Vec::new()
+ }
+ })
+ } else {
+ Vec::new()
+ }
+ }
+
+ fn check_call_arguments(
+ &mut self,
+ expr: ExprId,
+ args: &[ExprId],
+ expected_inputs: &[Ty],
+ param_tys: &[Ty],
+ skip_indices: &[u32],
+ is_varargs: bool,
+ ) {
+ if args.len() != param_tys.len() + skip_indices.len() && !is_varargs {
+ self.push_diagnostic(InferenceDiagnostic::MismatchedArgCount {
+ call_expr: expr,
+ expected: param_tys.len() + skip_indices.len(),
+ found: args.len(),
+ });
+ }
+
+ // Quoting https://github.com/rust-lang/rust/blob/6ef275e6c3cb1384ec78128eceeb4963ff788dca/src/librustc_typeck/check/mod.rs#L3325 --
+ // We do this in a pretty awful way: first we type-check any arguments
+ // that are not closures, then we type-check the closures. This is so
+ // that we have more information about the types of arguments when we
+ // type-check the functions. This isn't really the right way to do this.
+ for &check_closures in &[false, true] {
+ let mut skip_indices = skip_indices.into_iter().copied().fuse().peekable();
+ let param_iter = param_tys.iter().cloned().chain(repeat(self.err_ty()));
+ let expected_iter = expected_inputs
+ .iter()
+ .cloned()
+ .chain(param_iter.clone().skip(expected_inputs.len()));
+ for (idx, ((&arg, param_ty), expected_ty)) in
+ args.iter().zip(param_iter).zip(expected_iter).enumerate()
+ {
+ let is_closure = matches!(&self.body[arg], Expr::Closure { .. });
+ if is_closure != check_closures {
+ continue;
+ }
+
+ while skip_indices.peek().map_or(false, |i| *i < idx as u32) {
+ skip_indices.next();
+ }
+ if skip_indices.peek().copied() == Some(idx as u32) {
+ continue;
+ }
+
+ // the difference between param_ty and expected here is that
+ // expected is the parameter when the expected *return* type is
+ // taken into account. So in `let _: &[i32] = identity(&[1, 2])`
+ // the expected type is already `&[i32]`, whereas param_ty is
+ // still an unbound type variable. We don't always want to force
+ // the parameter to coerce to the expected type (for example in
+ // `coerce_unsize_expected_type_4`).
+ let param_ty = self.normalize_associated_types_in(param_ty);
+ let expected = Expectation::rvalue_hint(&mut self.table, expected_ty);
+ // infer with the expected type we have...
+ let ty = self.infer_expr_inner(arg, &expected);
+
+ // then coerce to either the expected type or just the formal parameter type
+ let coercion_target = if let Some(ty) = expected.only_has_type(&mut self.table) {
+ // if we are coercing to the expectation, unify with the
+ // formal parameter type to connect everything
+ self.unify(&ty, ¶m_ty);
+ ty
+ } else {
+ param_ty
+ };
+ if !coercion_target.is_unknown() {
+ if self.coerce(Some(arg), &ty, &coercion_target).is_err() {
+ self.result.type_mismatches.insert(
+ arg.into(),
+ TypeMismatch { expected: coercion_target, actual: ty.clone() },
+ );
+ }
+ }
+ }
+ }
+ }
+
+ fn substs_for_method_call(
+ &mut self,
+ def_generics: Generics,
+ generic_args: Option<&GenericArgs>,
+ ) -> Substitution {
+ let (parent_params, self_params, type_params, const_params, impl_trait_params) =
+ def_generics.provenance_split();
+ assert_eq!(self_params, 0); // method shouldn't have another Self param
+ let total_len = parent_params + type_params + const_params + impl_trait_params;
+ let mut substs = Vec::with_capacity(total_len);
- .zip(def_generics.iter_id().skip(parent_params))
++
+ // handle provided arguments
+ if let Some(generic_args) = generic_args {
+ // if args are provided, it should be all of them, but we can't rely on that
+ for (arg, kind_id) in generic_args
+ .args
+ .iter()
+ .filter(|arg| !matches!(arg, GenericArg::Lifetime(_)))
+ .take(type_params + const_params)
- let substs = crate::subst_prefix(
- &*parameters,
- generics(self.db.upcast(), trait_.into()).len(),
++ .zip(def_generics.iter_id())
+ {
+ if let Some(g) = generic_arg_to_chalk(
+ self.db,
+ kind_id,
+ arg,
+ self,
+ |this, type_ref| this.make_ty(type_ref),
+ |this, c, ty| {
+ const_or_path_to_chalk(
+ this.db,
+ &this.resolver,
+ ty,
+ c,
+ ParamLoweringMode::Placeholder,
+ || generics(this.db.upcast(), (&this.resolver).generic_def().unwrap()),
+ DebruijnIndex::INNERMOST,
+ )
+ },
+ ) {
+ substs.push(g);
+ }
+ }
+ };
++
++ // Handle everything else as unknown. This also handles generic arguments for the method's
++ // parent (impl or trait), which should come after those for the method.
+ for (id, data) in def_generics.iter().skip(substs.len()) {
+ match data {
+ TypeOrConstParamData::TypeParamData(_) => {
+ substs.push(GenericArgData::Ty(self.table.new_type_var()).intern(Interner))
+ }
+ TypeOrConstParamData::ConstParamData(_) => {
+ substs.push(
+ GenericArgData::Const(self.table.new_const_var(
+ self.db.const_param_ty(ConstParamId::from_unchecked(id)),
+ ))
+ .intern(Interner),
+ )
+ }
+ }
+ }
+ assert_eq!(substs.len(), total_len);
+ Substitution::from_iter(Interner, substs)
+ }
+
+ fn register_obligations_for_call(&mut self, callable_ty: &Ty) {
+ let callable_ty = self.resolve_ty_shallow(callable_ty);
+ if let TyKind::FnDef(fn_def, parameters) = callable_ty.kind(Interner) {
+ let def: CallableDefId = from_chalk(self.db, *fn_def);
+ let generic_predicates = self.db.generic_predicates(def.into());
+ for predicate in generic_predicates.iter() {
+ let (predicate, binders) = predicate
+ .clone()
+ .substitute(Interner, parameters)
+ .into_value_and_skipped_binders();
+ always!(binders.len(Interner) == 0); // quantified where clauses not yet handled
+ self.push_obligation(predicate.cast(Interner));
+ }
+ // add obligation for trait implementation, if this is a trait method
+ match def {
+ CallableDefId::FunctionId(f) => {
+ if let ItemContainerId::TraitId(trait_) = f.lookup(self.db.upcast()).container {
+ // construct a TraitRef
++ let params_len = parameters.len(Interner);
++ let trait_params_len = generics(self.db.upcast(), trait_.into()).len();
++ let substs = Substitution::from_iter(
++ Interner,
++ // The generic parameters for the trait come after those for the
++ // function.
++ ¶meters.as_slice(Interner)[params_len - trait_params_len..],
+ );
+ self.push_obligation(
+ TraitRef { trait_id: to_chalk_trait_id(trait_), substitution: substs }
+ .cast(Interner),
+ );
+ }
+ }
+ CallableDefId::StructId(_) | CallableDefId::EnumVariantId(_) => {}
+ }
+ }
+ }
+
+ /// Returns the argument indices to skip.
+ fn check_legacy_const_generics(&mut self, callee: Ty, args: &[ExprId]) -> Box<[u32]> {
+ let (func, subst) = match callee.kind(Interner) {
+ TyKind::FnDef(fn_id, subst) => {
+ let callable = CallableDefId::from_chalk(self.db, *fn_id);
+ let func = match callable {
+ CallableDefId::FunctionId(f) => f,
+ _ => return Default::default(),
+ };
+ (func, subst)
+ }
+ _ => return Default::default(),
+ };
+
+ let data = self.db.function_data(func);
+ if data.legacy_const_generics_indices.is_empty() {
+ return Default::default();
+ }
+
+ // only use legacy const generics if the param count matches with them
+ if data.params.len() + data.legacy_const_generics_indices.len() != args.len() {
+ if args.len() <= data.params.len() {
+ return Default::default();
+ } else {
+ // there are more parameters than there should be without legacy
+ // const params; use them
+ let mut indices = data.legacy_const_generics_indices.clone();
+ indices.sort();
+ return indices;
+ }
+ }
+
+ // check legacy const parameters
+ for (subst_idx, arg_idx) in data.legacy_const_generics_indices.iter().copied().enumerate() {
+ let arg = match subst.at(Interner, subst_idx).constant(Interner) {
+ Some(c) => c,
+ None => continue, // not a const parameter?
+ };
+ if arg_idx >= args.len() as u32 {
+ continue;
+ }
+ let _ty = arg.data(Interner).ty.clone();
+ let expected = Expectation::none(); // FIXME use actual const ty, when that is lowered correctly
+ self.infer_expr(args[arg_idx as usize], &expected);
+ // FIXME: evaluate and unify with the const
+ }
+ let mut indices = data.legacy_const_generics_indices.clone();
+ indices.sort();
+ indices
+ }
+
+ fn builtin_binary_op_return_ty(&mut self, op: BinaryOp, lhs_ty: Ty, rhs_ty: Ty) -> Option<Ty> {
+ let lhs_ty = self.resolve_ty_shallow(&lhs_ty);
+ let rhs_ty = self.resolve_ty_shallow(&rhs_ty);
+ match op {
+ BinaryOp::LogicOp(_) | BinaryOp::CmpOp(_) => {
+ Some(TyKind::Scalar(Scalar::Bool).intern(Interner))
+ }
+ BinaryOp::Assignment { .. } => Some(TyBuilder::unit()),
+ BinaryOp::ArithOp(ArithOp::Shl | ArithOp::Shr) => {
+ // all integer combinations are valid here
+ if matches!(
+ lhs_ty.kind(Interner),
+ TyKind::Scalar(Scalar::Int(_) | Scalar::Uint(_))
+ | TyKind::InferenceVar(_, TyVariableKind::Integer)
+ ) && matches!(
+ rhs_ty.kind(Interner),
+ TyKind::Scalar(Scalar::Int(_) | Scalar::Uint(_))
+ | TyKind::InferenceVar(_, TyVariableKind::Integer)
+ ) {
+ Some(lhs_ty)
+ } else {
+ None
+ }
+ }
+ BinaryOp::ArithOp(_) => match (lhs_ty.kind(Interner), rhs_ty.kind(Interner)) {
+ // (int, int) | (uint, uint) | (float, float)
+ (TyKind::Scalar(Scalar::Int(_)), TyKind::Scalar(Scalar::Int(_)))
+ | (TyKind::Scalar(Scalar::Uint(_)), TyKind::Scalar(Scalar::Uint(_)))
+ | (TyKind::Scalar(Scalar::Float(_)), TyKind::Scalar(Scalar::Float(_))) => {
+ Some(rhs_ty)
+ }
+ // ({int}, int) | ({int}, uint)
+ (
+ TyKind::InferenceVar(_, TyVariableKind::Integer),
+ TyKind::Scalar(Scalar::Int(_) | Scalar::Uint(_)),
+ ) => Some(rhs_ty),
+ // (int, {int}) | (uint, {int})
+ (
+ TyKind::Scalar(Scalar::Int(_) | Scalar::Uint(_)),
+ TyKind::InferenceVar(_, TyVariableKind::Integer),
+ ) => Some(lhs_ty),
+ // ({float} | float)
+ (
+ TyKind::InferenceVar(_, TyVariableKind::Float),
+ TyKind::Scalar(Scalar::Float(_)),
+ ) => Some(rhs_ty),
+ // (float, {float})
+ (
+ TyKind::Scalar(Scalar::Float(_)),
+ TyKind::InferenceVar(_, TyVariableKind::Float),
+ ) => Some(lhs_ty),
+ // ({int}, {int}) | ({float}, {float})
+ (
+ TyKind::InferenceVar(_, TyVariableKind::Integer),
+ TyKind::InferenceVar(_, TyVariableKind::Integer),
+ )
+ | (
+ TyKind::InferenceVar(_, TyVariableKind::Float),
+ TyKind::InferenceVar(_, TyVariableKind::Float),
+ ) => Some(rhs_ty),
+ _ => None,
+ },
+ }
+ }
+
+ fn builtin_binary_op_rhs_expectation(&mut self, op: BinaryOp, lhs_ty: Ty) -> Option<Ty> {
+ Some(match op {
+ BinaryOp::LogicOp(..) => TyKind::Scalar(Scalar::Bool).intern(Interner),
+ BinaryOp::Assignment { op: None } => lhs_ty,
+ BinaryOp::CmpOp(CmpOp::Eq { .. }) => match self
+ .resolve_ty_shallow(&lhs_ty)
+ .kind(Interner)
+ {
+ TyKind::Scalar(_) | TyKind::Str => lhs_ty,
+ TyKind::InferenceVar(_, TyVariableKind::Integer | TyVariableKind::Float) => lhs_ty,
+ _ => return None,
+ },
+ BinaryOp::ArithOp(ArithOp::Shl | ArithOp::Shr) => return None,
+ BinaryOp::CmpOp(CmpOp::Ord { .. })
+ | BinaryOp::Assignment { op: Some(_) }
+ | BinaryOp::ArithOp(_) => match self.resolve_ty_shallow(&lhs_ty).kind(Interner) {
+ TyKind::Scalar(Scalar::Int(_) | Scalar::Uint(_) | Scalar::Float(_)) => lhs_ty,
+ TyKind::InferenceVar(_, TyVariableKind::Integer | TyVariableKind::Float) => lhs_ty,
+ _ => return None,
+ },
+ })
+ }
+
+ fn with_breakable_ctx<T>(
+ &mut self,
+ kind: BreakableKind,
+ ty: Ty,
+ label: Option<LabelId>,
+ cb: impl FnOnce(&mut Self) -> T,
+ ) -> (Option<Ty>, T) {
+ self.breakables.push({
+ let label = label.map(|label| self.body[label].name.clone());
+ BreakableContext { kind, may_break: false, coerce: CoerceMany::new(ty), label }
+ });
+ let res = cb(self);
+ let ctx = self.breakables.pop().expect("breakable stack broken");
+ (ctx.may_break.then(|| ctx.coerce.complete()), res)
+ }
+}
--- /dev/null
- GenericArgData, Interner, Substitution, TraitRefExt, Ty, TyBuilder, TyExt, TyKind,
- ValueTyDefId,
+//! Path expression resolution.
+
+use chalk_ir::cast::Cast;
+use hir_def::{
+ path::{Path, PathSegment},
+ resolver::{ResolveValueResult, Resolver, TypeNs, ValueNs},
+ AdtId, AssocItemId, EnumVariantId, ItemContainerId, Lookup,
+};
+use hir_expand::name::Name;
+
+use crate::{
+ builder::ParamKind,
+ consteval,
+ method_resolution::{self, VisibleFromModule},
- let parent_substs = self_subst.unwrap_or_else(|| Substitution::empty(Interner));
++ utils::generics,
++ Interner, Substitution, TraitRefExt, Ty, TyBuilder, TyExt, TyKind, ValueTyDefId,
+};
+
+use super::{ExprOrPatId, InferenceContext, TraitRef};
+
+impl<'a> InferenceContext<'a> {
+ pub(super) fn infer_path(
+ &mut self,
+ resolver: &Resolver,
+ path: &Path,
+ id: ExprOrPatId,
+ ) -> Option<Ty> {
+ let ty = self.resolve_value_path(resolver, path, id)?;
+ let ty = self.insert_type_vars(ty);
+ let ty = self.normalize_associated_types_in(ty);
+ Some(ty)
+ }
+
+ fn resolve_value_path(
+ &mut self,
+ resolver: &Resolver,
+ path: &Path,
+ id: ExprOrPatId,
+ ) -> Option<Ty> {
+ let (value, self_subst) = if let Some(type_ref) = path.type_anchor() {
+ if path.segments().is_empty() {
+ // This can't actually happen syntax-wise
+ return None;
+ }
+ let ty = self.make_ty(type_ref);
+ let remaining_segments_for_ty = path.segments().take(path.segments().len() - 1);
+ let ctx = crate::lower::TyLoweringContext::new(self.db, resolver);
+ let (ty, _) = ctx.lower_ty_relative_path(ty, None, remaining_segments_for_ty);
+ self.resolve_ty_assoc_item(
+ ty,
+ path.segments().last().expect("path had at least one segment").name,
+ id,
+ )?
+ } else {
+ let value_or_partial =
+ resolver.resolve_path_in_value_ns(self.db.upcast(), path.mod_path())?;
+
+ match value_or_partial {
+ ResolveValueResult::ValueNs(it) => (it, None),
+ ResolveValueResult::Partial(def, remaining_index) => {
+ self.resolve_assoc_item(def, path, remaining_index, id)?
+ }
+ }
+ };
+
+ let typable: ValueTyDefId = match value {
+ ValueNs::LocalBinding(pat) => {
+ let ty = self.result.type_of_pat.get(pat)?.clone();
+ return Some(ty);
+ }
+ ValueNs::FunctionId(it) => it.into(),
+ ValueNs::ConstId(it) => it.into(),
+ ValueNs::StaticId(it) => it.into(),
+ ValueNs::StructId(it) => {
+ self.write_variant_resolution(id, it.into());
+
+ it.into()
+ }
+ ValueNs::EnumVariantId(it) => {
+ self.write_variant_resolution(id, it.into());
+
+ it.into()
+ }
+ ValueNs::ImplSelf(impl_id) => {
+ let generics = crate::utils::generics(self.db.upcast(), impl_id.into());
+ let substs = generics.placeholder_subst(self.db);
+ let ty = self.db.impl_self_ty(impl_id).substitute(Interner, &substs);
+ if let Some((AdtId::StructId(struct_id), substs)) = ty.as_adt() {
+ let ty = self.db.value_ty(struct_id.into()).substitute(Interner, &substs);
+ return Some(ty);
+ } else {
+ // FIXME: diagnostic, invalid Self reference
+ return None;
+ }
+ }
+ ValueNs::GenericParam(it) => return Some(self.db.const_param_ty(it)),
+ };
+
- let mut it = substs.as_slice(Interner)[parent_substs.len(Interner)..].iter().cloned();
- let ty = TyBuilder::value_ty(self.db, typable)
- .use_parent_substs(&parent_substs)
+ let ctx = crate::lower::TyLoweringContext::new(self.db, &self.resolver);
+ let substs = ctx.substs_from_path(path, typable, true);
- ParamKind::Type => {
- GenericArgData::Ty(TyKind::Error.intern(Interner)).intern(Interner)
- }
++ let substs = substs.as_slice(Interner);
++ let parent_substs = self_subst.or_else(|| {
++ let generics = generics(self.db.upcast(), typable.to_generic_def_id()?);
++ let parent_params_len = generics.parent_generics()?.len();
++ let parent_args = &substs[substs.len() - parent_params_len..];
++ Some(Substitution::from_iter(Interner, parent_args))
++ });
++ let parent_substs_len = parent_substs.as_ref().map_or(0, |s| s.len(Interner));
++ let mut it = substs.iter().take(substs.len() - parent_substs_len).cloned();
++ let ty = TyBuilder::value_ty(self.db, typable, parent_substs)
+ .fill(|x| {
+ it.next().unwrap_or_else(|| match x {
- let impl_substs = TyBuilder::subst_for_def(self.db, impl_id)
++ ParamKind::Type => TyKind::Error.intern(Interner).cast(Interner),
+ ParamKind::Const(ty) => consteval::unknown_const_as_generic(ty.clone()),
+ })
+ })
+ .build();
+ Some(ty)
+ }
+
+ fn resolve_assoc_item(
+ &mut self,
+ def: TypeNs,
+ path: &Path,
+ remaining_index: usize,
+ id: ExprOrPatId,
+ ) -> Option<(ValueNs, Option<Substitution>)> {
+ assert!(remaining_index < path.segments().len());
+ // there may be more intermediate segments between the resolved one and
+ // the end. Only the last segment needs to be resolved to a value; from
+ // the segments before that, we need to get either a type or a trait ref.
+
+ let resolved_segment = path.segments().get(remaining_index - 1).unwrap();
+ let remaining_segments = path.segments().skip(remaining_index);
+ let is_before_last = remaining_segments.len() == 1;
+
+ match (def, is_before_last) {
+ (TypeNs::TraitId(trait_), true) => {
+ let segment =
+ remaining_segments.last().expect("there should be at least one segment here");
+ let ctx = crate::lower::TyLoweringContext::new(self.db, &self.resolver);
+ let trait_ref =
+ ctx.lower_trait_ref_from_resolved_path(trait_, resolved_segment, None);
+ self.resolve_trait_assoc_item(trait_ref, segment, id)
+ }
+ (def, _) => {
+ // Either we already have a type (e.g. `Vec::new`), or we have a
+ // trait but it's not the last segment, so the next segment
+ // should resolve to an associated type of that trait (e.g. `<T
+ // as Iterator>::Item::default`)
+ let remaining_segments_for_ty =
+ remaining_segments.take(remaining_segments.len() - 1);
+ let ctx = crate::lower::TyLoweringContext::new(self.db, &self.resolver);
+ let (ty, _) = ctx.lower_partly_resolved_path(
+ def,
+ resolved_segment,
+ remaining_segments_for_ty,
+ true,
+ );
+ if let TyKind::Error = ty.kind(Interner) {
+ return None;
+ }
+
+ let ty = self.insert_type_vars(ty);
+ let ty = self.normalize_associated_types_in(ty);
+
+ let segment =
+ remaining_segments.last().expect("there should be at least one segment here");
+
+ self.resolve_ty_assoc_item(ty, segment.name, id)
+ }
+ }
+ }
+
+ fn resolve_trait_assoc_item(
+ &mut self,
+ trait_ref: TraitRef,
+ segment: PathSegment<'_>,
+ id: ExprOrPatId,
+ ) -> Option<(ValueNs, Option<Substitution>)> {
+ let trait_ = trait_ref.hir_trait_id();
+ let item =
+ self.db.trait_data(trait_).items.iter().map(|(_name, id)| (*id)).find_map(|item| {
+ match item {
+ AssocItemId::FunctionId(func) => {
+ if segment.name == &self.db.function_data(func).name {
+ Some(AssocItemId::FunctionId(func))
+ } else {
+ None
+ }
+ }
+
+ AssocItemId::ConstId(konst) => {
+ if self
+ .db
+ .const_data(konst)
+ .name
+ .as_ref()
+ .map_or(false, |n| n == segment.name)
+ {
+ Some(AssocItemId::ConstId(konst))
+ } else {
+ None
+ }
+ }
+ AssocItemId::TypeAliasId(_) => None,
+ }
+ })?;
+ let def = match item {
+ AssocItemId::FunctionId(f) => ValueNs::FunctionId(f),
+ AssocItemId::ConstId(c) => ValueNs::ConstId(c),
+ AssocItemId::TypeAliasId(_) => unreachable!(),
+ };
+
+ self.write_assoc_resolution(id, item);
+ Some((def, Some(trait_ref.substitution)))
+ }
+
+ fn resolve_ty_assoc_item(
+ &mut self,
+ ty: Ty,
+ name: &Name,
+ id: ExprOrPatId,
+ ) -> Option<(ValueNs, Option<Substitution>)> {
+ if let TyKind::Error = ty.kind(Interner) {
+ return None;
+ }
+
+ if let Some(result) = self.resolve_enum_variant_on_ty(&ty, name, id) {
+ return Some(result);
+ }
+
+ let canonical_ty = self.canonicalize(ty.clone());
+ let traits_in_scope = self.resolver.traits_in_scope(self.db.upcast());
+
+ method_resolution::iterate_method_candidates(
+ &canonical_ty.value,
+ self.db,
+ self.table.trait_env.clone(),
+ &traits_in_scope,
+ VisibleFromModule::Filter(self.resolver.module()),
+ Some(name),
+ method_resolution::LookupMode::Path,
+ move |_ty, item| {
+ let (def, container) = match item {
+ AssocItemId::FunctionId(f) => {
+ (ValueNs::FunctionId(f), f.lookup(self.db.upcast()).container)
+ }
+ AssocItemId::ConstId(c) => {
+ (ValueNs::ConstId(c), c.lookup(self.db.upcast()).container)
+ }
+ AssocItemId::TypeAliasId(_) => unreachable!(),
+ };
+ let substs = match container {
+ ItemContainerId::ImplId(impl_id) => {
++ let impl_substs = TyBuilder::subst_for_def(self.db, impl_id, None)
+ .fill_with_inference_vars(&mut self.table)
+ .build();
+ let impl_self_ty =
+ self.db.impl_self_ty(impl_id).substitute(Interner, &impl_substs);
+ self.unify(&impl_self_ty, &ty);
+ Some(impl_substs)
+ }
+ ItemContainerId::TraitId(trait_) => {
+ // we're picking this method
+ let trait_ref = TyBuilder::trait_ref(self.db, trait_)
+ .push(ty.clone())
+ .fill_with_inference_vars(&mut self.table)
+ .build();
+ self.push_obligation(trait_ref.clone().cast(Interner));
+ Some(trait_ref.substitution)
+ }
+ ItemContainerId::ModuleId(_) | ItemContainerId::ExternBlockId(_) => None,
+ };
+
+ self.write_assoc_resolution(id, item);
+ Some((def, substs))
+ },
+ )
+ }
+
+ fn resolve_enum_variant_on_ty(
+ &mut self,
+ ty: &Ty,
+ name: &Name,
+ id: ExprOrPatId,
+ ) -> Option<(ValueNs, Option<Substitution>)> {
+ let ty = self.resolve_ty_shallow(ty);
+ let (enum_id, subst) = match ty.as_adt() {
+ Some((AdtId::EnumId(e), subst)) => (e, subst),
+ _ => return None,
+ };
+ let enum_data = self.db.enum_data(enum_id);
+ let local_id = enum_data.variant(name)?;
+ let variant = EnumVariantId { parent: enum_id, local_id };
+ self.write_variant_resolution(id, variant.into());
+ Some((ValueNs::EnumVariantId(variant), Some(subst.clone())))
+ }
+}
--- /dev/null
- let b = TyBuilder::assoc_type_projection(self.db, output_assoc_type);
+//! Unification and canonicalization logic.
+
+use std::{fmt, mem, sync::Arc};
+
+use chalk_ir::{
+ cast::Cast, fold::TypeFoldable, interner::HasInterner, zip::Zip, CanonicalVarKind, FloatTy,
+ IntTy, NoSolution, TyVariableKind, UniverseIndex,
+};
+use chalk_solve::infer::ParameterEnaVariableExt;
+use ena::unify::UnifyKey;
+use hir_expand::name;
+use stdx::never;
+
+use super::{InferOk, InferResult, InferenceContext, TypeError};
+use crate::{
+ db::HirDatabase, fold_tys, static_lifetime, traits::FnTrait, AliasEq, AliasTy, BoundVar,
+ Canonical, Const, DebruijnIndex, GenericArg, GenericArgData, Goal, Guidance, InEnvironment,
+ InferenceVar, Interner, Lifetime, ParamKind, ProjectionTy, ProjectionTyExt, Scalar, Solution,
+ Substitution, TraitEnvironment, Ty, TyBuilder, TyExt, TyKind, VariableKind,
+};
+
+impl<'a> InferenceContext<'a> {
+ pub(super) fn canonicalize<T: TypeFoldable<Interner> + HasInterner<Interner = Interner>>(
+ &mut self,
+ t: T,
+ ) -> Canonicalized<T>
+ where
+ T: HasInterner<Interner = Interner>,
+ {
+ self.table.canonicalize(t)
+ }
+}
+
+#[derive(Debug, Clone)]
+pub(crate) struct Canonicalized<T>
+where
+ T: HasInterner<Interner = Interner>,
+{
+ pub(crate) value: Canonical<T>,
+ free_vars: Vec<GenericArg>,
+}
+
+impl<T: HasInterner<Interner = Interner>> Canonicalized<T> {
+ pub(super) fn apply_solution(
+ &self,
+ ctx: &mut InferenceTable<'_>,
+ solution: Canonical<Substitution>,
+ ) {
+ // the solution may contain new variables, which we need to convert to new inference vars
+ let new_vars = Substitution::from_iter(
+ Interner,
+ solution.binders.iter(Interner).map(|k| match &k.kind {
+ VariableKind::Ty(TyVariableKind::General) => ctx.new_type_var().cast(Interner),
+ VariableKind::Ty(TyVariableKind::Integer) => ctx.new_integer_var().cast(Interner),
+ VariableKind::Ty(TyVariableKind::Float) => ctx.new_float_var().cast(Interner),
+ // Chalk can sometimes return new lifetime variables. We just use the static lifetime everywhere
+ VariableKind::Lifetime => static_lifetime().cast(Interner),
+ VariableKind::Const(ty) => ctx.new_const_var(ty.clone()).cast(Interner),
+ }),
+ );
+ for (i, v) in solution.value.iter(Interner).enumerate() {
+ let var = self.free_vars[i].clone();
+ if let Some(ty) = v.ty(Interner) {
+ // eagerly replace projections in the type; we may be getting types
+ // e.g. from where clauses where this hasn't happened yet
+ let ty = ctx.normalize_associated_types_in(new_vars.apply(ty.clone(), Interner));
+ ctx.unify(var.assert_ty_ref(Interner), &ty);
+ } else {
+ let _ = ctx.try_unify(&var, &new_vars.apply(v.clone(), Interner));
+ }
+ }
+ }
+}
+
+pub fn could_unify(
+ db: &dyn HirDatabase,
+ env: Arc<TraitEnvironment>,
+ tys: &Canonical<(Ty, Ty)>,
+) -> bool {
+ unify(db, env, tys).is_some()
+}
+
+pub(crate) fn unify(
+ db: &dyn HirDatabase,
+ env: Arc<TraitEnvironment>,
+ tys: &Canonical<(Ty, Ty)>,
+) -> Option<Substitution> {
+ let mut table = InferenceTable::new(db, env);
+ let vars = Substitution::from_iter(
+ Interner,
+ tys.binders.iter(Interner).map(|x| match &x.kind {
+ chalk_ir::VariableKind::Ty(_) => {
+ GenericArgData::Ty(table.new_type_var()).intern(Interner)
+ }
+ chalk_ir::VariableKind::Lifetime => {
+ GenericArgData::Ty(table.new_type_var()).intern(Interner)
+ } // FIXME: maybe wrong?
+ chalk_ir::VariableKind::Const(ty) => {
+ GenericArgData::Const(table.new_const_var(ty.clone())).intern(Interner)
+ }
+ }),
+ );
+ let ty1_with_vars = vars.apply(tys.value.0.clone(), Interner);
+ let ty2_with_vars = vars.apply(tys.value.1.clone(), Interner);
+ if !table.unify(&ty1_with_vars, &ty2_with_vars) {
+ return None;
+ }
+ // default any type vars that weren't unified back to their original bound vars
+ // (kind of hacky)
+ let find_var = |iv| {
+ vars.iter(Interner).position(|v| match v.interned() {
+ chalk_ir::GenericArgData::Ty(ty) => ty.inference_var(Interner),
+ chalk_ir::GenericArgData::Lifetime(lt) => lt.inference_var(Interner),
+ chalk_ir::GenericArgData::Const(c) => c.inference_var(Interner),
+ } == Some(iv))
+ };
+ let fallback = |iv, kind, default, binder| match kind {
+ chalk_ir::VariableKind::Ty(_ty_kind) => find_var(iv)
+ .map_or(default, |i| BoundVar::new(binder, i).to_ty(Interner).cast(Interner)),
+ chalk_ir::VariableKind::Lifetime => find_var(iv)
+ .map_or(default, |i| BoundVar::new(binder, i).to_lifetime(Interner).cast(Interner)),
+ chalk_ir::VariableKind::Const(ty) => find_var(iv)
+ .map_or(default, |i| BoundVar::new(binder, i).to_const(Interner, ty).cast(Interner)),
+ };
+ Some(Substitution::from_iter(
+ Interner,
+ vars.iter(Interner).map(|v| table.resolve_with_fallback(v.clone(), &fallback)),
+ ))
+}
+
+#[derive(Copy, Clone, Debug)]
+pub(crate) struct TypeVariableData {
+ diverging: bool,
+}
+
+type ChalkInferenceTable = chalk_solve::infer::InferenceTable<Interner>;
+
+#[derive(Clone)]
+pub(crate) struct InferenceTable<'a> {
+ pub(crate) db: &'a dyn HirDatabase,
+ pub(crate) trait_env: Arc<TraitEnvironment>,
+ var_unification_table: ChalkInferenceTable,
+ type_variable_table: Vec<TypeVariableData>,
+ pending_obligations: Vec<Canonicalized<InEnvironment<Goal>>>,
+}
+
+pub(crate) struct InferenceTableSnapshot {
+ var_table_snapshot: chalk_solve::infer::InferenceSnapshot<Interner>,
+ pending_obligations: Vec<Canonicalized<InEnvironment<Goal>>>,
+ type_variable_table_snapshot: Vec<TypeVariableData>,
+}
+
+impl<'a> InferenceTable<'a> {
+ pub(crate) fn new(db: &'a dyn HirDatabase, trait_env: Arc<TraitEnvironment>) -> Self {
+ InferenceTable {
+ db,
+ trait_env,
+ var_unification_table: ChalkInferenceTable::new(),
+ type_variable_table: Vec::new(),
+ pending_obligations: Vec::new(),
+ }
+ }
+
+ /// Chalk doesn't know about the `diverging` flag, so when it unifies two
+ /// type variables of which one is diverging, the chosen root might not be
+ /// diverging and we have no way of marking it as such at that time. This
+ /// function goes through all type variables and make sure their root is
+ /// marked as diverging if necessary, so that resolving them gives the right
+ /// result.
+ pub(super) fn propagate_diverging_flag(&mut self) {
+ for i in 0..self.type_variable_table.len() {
+ if !self.type_variable_table[i].diverging {
+ continue;
+ }
+ let v = InferenceVar::from(i as u32);
+ let root = self.var_unification_table.inference_var_root(v);
+ if let Some(data) = self.type_variable_table.get_mut(root.index() as usize) {
+ data.diverging = true;
+ }
+ }
+ }
+
+ pub(super) fn set_diverging(&mut self, iv: InferenceVar, diverging: bool) {
+ self.type_variable_table[iv.index() as usize].diverging = diverging;
+ }
+
+ fn fallback_value(&self, iv: InferenceVar, kind: TyVariableKind) -> Ty {
+ match kind {
+ _ if self
+ .type_variable_table
+ .get(iv.index() as usize)
+ .map_or(false, |data| data.diverging) =>
+ {
+ TyKind::Never
+ }
+ TyVariableKind::General => TyKind::Error,
+ TyVariableKind::Integer => TyKind::Scalar(Scalar::Int(IntTy::I32)),
+ TyVariableKind::Float => TyKind::Scalar(Scalar::Float(FloatTy::F64)),
+ }
+ .intern(Interner)
+ }
+
+ pub(crate) fn canonicalize<T: TypeFoldable<Interner> + HasInterner<Interner = Interner>>(
+ &mut self,
+ t: T,
+ ) -> Canonicalized<T>
+ where
+ T: HasInterner<Interner = Interner>,
+ {
+ // try to resolve obligations before canonicalizing, since this might
+ // result in new knowledge about variables
+ self.resolve_obligations_as_possible();
+ let result = self.var_unification_table.canonicalize(Interner, t);
+ let free_vars = result
+ .free_vars
+ .into_iter()
+ .map(|free_var| free_var.to_generic_arg(Interner))
+ .collect();
+ Canonicalized { value: result.quantified, free_vars }
+ }
+
+ /// Recurses through the given type, normalizing associated types mentioned
+ /// in it by replacing them by type variables and registering obligations to
+ /// resolve later. This should be done once for every type we get from some
+ /// type annotation (e.g. from a let type annotation, field type or function
+ /// call). `make_ty` handles this already, but e.g. for field types we need
+ /// to do it as well.
+ pub(crate) fn normalize_associated_types_in(&mut self, ty: Ty) -> Ty {
+ fold_tys(
+ ty,
+ |ty, _| match ty.kind(Interner) {
+ TyKind::Alias(AliasTy::Projection(proj_ty)) => {
+ self.normalize_projection_ty(proj_ty.clone())
+ }
+ _ => ty,
+ },
+ DebruijnIndex::INNERMOST,
+ )
+ }
+
+ pub(crate) fn normalize_projection_ty(&mut self, proj_ty: ProjectionTy) -> Ty {
+ let var = self.new_type_var();
+ let alias_eq = AliasEq { alias: AliasTy::Projection(proj_ty), ty: var.clone() };
+ let obligation = alias_eq.cast(Interner);
+ self.register_obligation(obligation);
+ var
+ }
+
+ fn extend_type_variable_table(&mut self, to_index: usize) {
+ self.type_variable_table.extend(
+ (0..1 + to_index - self.type_variable_table.len())
+ .map(|_| TypeVariableData { diverging: false }),
+ );
+ }
+
+ fn new_var(&mut self, kind: TyVariableKind, diverging: bool) -> Ty {
+ let var = self.var_unification_table.new_variable(UniverseIndex::ROOT);
+ // Chalk might have created some type variables for its own purposes that we don't know about...
+ self.extend_type_variable_table(var.index() as usize);
+ assert_eq!(var.index() as usize, self.type_variable_table.len() - 1);
+ self.type_variable_table[var.index() as usize].diverging = diverging;
+ var.to_ty_with_kind(Interner, kind)
+ }
+
+ pub(crate) fn new_type_var(&mut self) -> Ty {
+ self.new_var(TyVariableKind::General, false)
+ }
+
+ pub(crate) fn new_integer_var(&mut self) -> Ty {
+ self.new_var(TyVariableKind::Integer, false)
+ }
+
+ pub(crate) fn new_float_var(&mut self) -> Ty {
+ self.new_var(TyVariableKind::Float, false)
+ }
+
+ pub(crate) fn new_maybe_never_var(&mut self) -> Ty {
+ self.new_var(TyVariableKind::General, true)
+ }
+
+ pub(crate) fn new_const_var(&mut self, ty: Ty) -> Const {
+ let var = self.var_unification_table.new_variable(UniverseIndex::ROOT);
+ var.to_const(Interner, ty)
+ }
+
+ pub(crate) fn new_lifetime_var(&mut self) -> Lifetime {
+ let var = self.var_unification_table.new_variable(UniverseIndex::ROOT);
+ var.to_lifetime(Interner)
+ }
+
+ pub(crate) fn resolve_with_fallback<T>(
+ &mut self,
+ t: T,
+ fallback: &dyn Fn(InferenceVar, VariableKind, GenericArg, DebruijnIndex) -> GenericArg,
+ ) -> T
+ where
+ T: HasInterner<Interner = Interner> + TypeFoldable<Interner>,
+ {
+ self.resolve_with_fallback_inner(&mut Vec::new(), t, &fallback)
+ }
+
+ pub(crate) fn fresh_subst(&mut self, binders: &[CanonicalVarKind<Interner>]) -> Substitution {
+ Substitution::from_iter(
+ Interner,
+ binders.iter().map(|kind| {
+ let param_infer_var =
+ kind.map_ref(|&ui| self.var_unification_table.new_variable(ui));
+ param_infer_var.to_generic_arg(Interner)
+ }),
+ )
+ }
+
+ pub(crate) fn instantiate_canonical<T>(&mut self, canonical: Canonical<T>) -> T
+ where
+ T: HasInterner<Interner = Interner> + TypeFoldable<Interner> + std::fmt::Debug,
+ {
+ let subst = self.fresh_subst(canonical.binders.as_slice(Interner));
+ subst.apply(canonical.value, Interner)
+ }
+
+ fn resolve_with_fallback_inner<T>(
+ &mut self,
+ var_stack: &mut Vec<InferenceVar>,
+ t: T,
+ fallback: &dyn Fn(InferenceVar, VariableKind, GenericArg, DebruijnIndex) -> GenericArg,
+ ) -> T
+ where
+ T: HasInterner<Interner = Interner> + TypeFoldable<Interner>,
+ {
+ t.fold_with(
+ &mut resolve::Resolver { table: self, var_stack, fallback },
+ DebruijnIndex::INNERMOST,
+ )
+ .expect("fold failed unexpectedly")
+ }
+
+ pub(crate) fn resolve_completely<T>(&mut self, t: T) -> T
+ where
+ T: HasInterner<Interner = Interner> + TypeFoldable<Interner>,
+ {
+ self.resolve_with_fallback(t, &|_, _, d, _| d)
+ }
+
+ /// Unify two types and register new trait goals that arise from that.
+ pub(crate) fn unify(&mut self, ty1: &Ty, ty2: &Ty) -> bool {
+ let result = match self.try_unify(ty1, ty2) {
+ Ok(r) => r,
+ Err(_) => return false,
+ };
+ self.register_infer_ok(result);
+ true
+ }
+
+ /// Unify two types and return new trait goals arising from it, so the
+ /// caller needs to deal with them.
+ pub(crate) fn try_unify<T: Zip<Interner>>(&mut self, t1: &T, t2: &T) -> InferResult<()> {
+ match self.var_unification_table.relate(
+ Interner,
+ &self.db,
+ &self.trait_env.env,
+ chalk_ir::Variance::Invariant,
+ t1,
+ t2,
+ ) {
+ Ok(result) => Ok(InferOk { goals: result.goals, value: () }),
+ Err(chalk_ir::NoSolution) => Err(TypeError),
+ }
+ }
+
+ /// If `ty` is a type variable with known type, returns that type;
+ /// otherwise, return ty.
+ pub(crate) fn resolve_ty_shallow(&mut self, ty: &Ty) -> Ty {
+ self.resolve_obligations_as_possible();
+ self.var_unification_table.normalize_ty_shallow(Interner, ty).unwrap_or_else(|| ty.clone())
+ }
+
+ pub(crate) fn snapshot(&mut self) -> InferenceTableSnapshot {
+ let var_table_snapshot = self.var_unification_table.snapshot();
+ let type_variable_table_snapshot = self.type_variable_table.clone();
+ let pending_obligations = self.pending_obligations.clone();
+ InferenceTableSnapshot {
+ var_table_snapshot,
+ pending_obligations,
+ type_variable_table_snapshot,
+ }
+ }
+
+ pub(crate) fn rollback_to(&mut self, snapshot: InferenceTableSnapshot) {
+ self.var_unification_table.rollback_to(snapshot.var_table_snapshot);
+ self.type_variable_table = snapshot.type_variable_table_snapshot;
+ self.pending_obligations = snapshot.pending_obligations;
+ }
+
+ pub(crate) fn run_in_snapshot<T>(&mut self, f: impl FnOnce(&mut InferenceTable<'_>) -> T) -> T {
+ let snapshot = self.snapshot();
+ let result = f(self);
+ self.rollback_to(snapshot);
+ result
+ }
+
+ /// Checks an obligation without registering it. Useful mostly to check
+ /// whether a trait *might* be implemented before deciding to 'lock in' the
+ /// choice (during e.g. method resolution or deref).
+ pub(crate) fn try_obligation(&mut self, goal: Goal) -> Option<Solution> {
+ let in_env = InEnvironment::new(&self.trait_env.env, goal);
+ let canonicalized = self.canonicalize(in_env);
+ let solution = self.db.trait_solve(self.trait_env.krate, canonicalized.value);
+ solution
+ }
+
+ pub(crate) fn register_obligation(&mut self, goal: Goal) {
+ let in_env = InEnvironment::new(&self.trait_env.env, goal);
+ self.register_obligation_in_env(in_env)
+ }
+
+ fn register_obligation_in_env(&mut self, goal: InEnvironment<Goal>) {
+ let canonicalized = self.canonicalize(goal);
+ if !self.try_resolve_obligation(&canonicalized) {
+ self.pending_obligations.push(canonicalized);
+ }
+ }
+
+ pub(crate) fn register_infer_ok<T>(&mut self, infer_ok: InferOk<T>) {
+ infer_ok.goals.into_iter().for_each(|goal| self.register_obligation_in_env(goal));
+ }
+
+ pub(crate) fn resolve_obligations_as_possible(&mut self) {
+ let _span = profile::span("resolve_obligations_as_possible");
+ let mut changed = true;
+ let mut obligations = Vec::new();
+ while changed {
+ changed = false;
+ mem::swap(&mut self.pending_obligations, &mut obligations);
+ for canonicalized in obligations.drain(..) {
+ if !self.check_changed(&canonicalized) {
+ self.pending_obligations.push(canonicalized);
+ continue;
+ }
+ changed = true;
+ let uncanonical = chalk_ir::Substitute::apply(
+ &canonicalized.free_vars,
+ canonicalized.value.value,
+ Interner,
+ );
+ self.register_obligation_in_env(uncanonical);
+ }
+ }
+ }
+
+ pub(crate) fn fudge_inference<T: TypeFoldable<Interner>>(
+ &mut self,
+ f: impl FnOnce(&mut Self) -> T,
+ ) -> T {
+ use chalk_ir::fold::TypeFolder;
+ struct VarFudger<'a, 'b> {
+ table: &'a mut InferenceTable<'b>,
+ highest_known_var: InferenceVar,
+ }
+ impl<'a, 'b> TypeFolder<Interner> for VarFudger<'a, 'b> {
+ type Error = NoSolution;
+
+ fn as_dyn(&mut self) -> &mut dyn TypeFolder<Interner, Error = Self::Error> {
+ self
+ }
+
+ fn interner(&self) -> Interner {
+ Interner
+ }
+
+ fn fold_inference_ty(
+ &mut self,
+ var: chalk_ir::InferenceVar,
+ kind: TyVariableKind,
+ _outer_binder: chalk_ir::DebruijnIndex,
+ ) -> chalk_ir::Fallible<chalk_ir::Ty<Interner>> {
+ Ok(if var < self.highest_known_var {
+ var.to_ty(Interner, kind)
+ } else {
+ self.table.new_type_var()
+ })
+ }
+
+ fn fold_inference_lifetime(
+ &mut self,
+ var: chalk_ir::InferenceVar,
+ _outer_binder: chalk_ir::DebruijnIndex,
+ ) -> chalk_ir::Fallible<chalk_ir::Lifetime<Interner>> {
+ Ok(if var < self.highest_known_var {
+ var.to_lifetime(Interner)
+ } else {
+ self.table.new_lifetime_var()
+ })
+ }
+
+ fn fold_inference_const(
+ &mut self,
+ ty: chalk_ir::Ty<Interner>,
+ var: chalk_ir::InferenceVar,
+ _outer_binder: chalk_ir::DebruijnIndex,
+ ) -> chalk_ir::Fallible<chalk_ir::Const<Interner>> {
+ Ok(if var < self.highest_known_var {
+ var.to_const(Interner, ty)
+ } else {
+ self.table.new_const_var(ty)
+ })
+ }
+ }
+
+ let snapshot = self.snapshot();
+ let highest_known_var = self.new_type_var().inference_var(Interner).expect("inference_var");
+ let result = f(self);
+ self.rollback_to(snapshot);
+ result
+ .fold_with(&mut VarFudger { table: self, highest_known_var }, DebruijnIndex::INNERMOST)
+ .expect("fold_with with VarFudger")
+ }
+
+ /// This checks whether any of the free variables in the `canonicalized`
+ /// have changed (either been unified with another variable, or with a
+ /// value). If this is not the case, we don't need to try to solve the goal
+ /// again -- it'll give the same result as last time.
+ fn check_changed(&mut self, canonicalized: &Canonicalized<InEnvironment<Goal>>) -> bool {
+ canonicalized.free_vars.iter().any(|var| {
+ let iv = match var.data(Interner) {
+ chalk_ir::GenericArgData::Ty(ty) => ty.inference_var(Interner),
+ chalk_ir::GenericArgData::Lifetime(lt) => lt.inference_var(Interner),
+ chalk_ir::GenericArgData::Const(c) => c.inference_var(Interner),
+ }
+ .expect("free var is not inference var");
+ if self.var_unification_table.probe_var(iv).is_some() {
+ return true;
+ }
+ let root = self.var_unification_table.inference_var_root(iv);
+ iv != root
+ })
+ }
+
+ fn try_resolve_obligation(
+ &mut self,
+ canonicalized: &Canonicalized<InEnvironment<Goal>>,
+ ) -> bool {
+ let solution = self.db.trait_solve(self.trait_env.krate, canonicalized.value.clone());
+
+ match solution {
+ Some(Solution::Unique(canonical_subst)) => {
+ canonicalized.apply_solution(
+ self,
+ Canonical {
+ binders: canonical_subst.binders,
+ // FIXME: handle constraints
+ value: canonical_subst.value.subst,
+ },
+ );
+ true
+ }
+ Some(Solution::Ambig(Guidance::Definite(substs))) => {
+ canonicalized.apply_solution(self, substs);
+ false
+ }
+ Some(_) => {
+ // FIXME use this when trying to resolve everything at the end
+ false
+ }
+ None => {
+ // FIXME obligation cannot be fulfilled => diagnostic
+ true
+ }
+ }
+ }
+
+ pub(crate) fn callable_sig(&mut self, ty: &Ty, num_args: usize) -> Option<(Vec<Ty>, Ty)> {
+ match ty.callable_sig(self.db) {
+ Some(sig) => Some((sig.params().to_vec(), sig.ret().clone())),
+ None => self.callable_sig_from_fn_trait(ty, num_args),
+ }
+ }
+
+ fn callable_sig_from_fn_trait(&mut self, ty: &Ty, num_args: usize) -> Option<(Vec<Ty>, Ty)> {
+ let krate = self.trait_env.krate;
+ let fn_once_trait = FnTrait::FnOnce.get_id(self.db, krate)?;
+ let output_assoc_type =
+ self.db.trait_data(fn_once_trait).associated_type_by_name(&name![Output])?;
+
+ let mut arg_tys = vec![];
+ let arg_ty = TyBuilder::tuple(num_args)
+ .fill(|x| {
+ let arg = match x {
+ ParamKind::Type => self.new_type_var(),
+ ParamKind::Const(ty) => {
+ never!("Tuple with const parameter");
+ return GenericArgData::Const(self.new_const_var(ty.clone()))
+ .intern(Interner);
+ }
+ };
+ arg_tys.push(arg.clone());
+ GenericArgData::Ty(arg).intern(Interner)
+ })
+ .build();
+
+ let projection = {
- b.push(ty.clone()).push(arg_ty).build()
++ let b = TyBuilder::subst_for_def(self.db, fn_once_trait, None);
+ if b.remaining() != 2 {
+ return None;
+ }
++ let fn_once_subst = b.push(ty.clone()).push(arg_ty).build();
++
++ TyBuilder::assoc_type_projection(self.db, output_assoc_type, Some(fn_once_subst))
++ .build()
+ };
+
+ let trait_env = self.trait_env.env.clone();
+ let obligation = InEnvironment {
+ goal: projection.trait_ref(self.db).cast(Interner),
+ environment: trait_env,
+ };
+ let canonical = self.canonicalize(obligation.clone());
+ if self.db.trait_solve(krate, canonical.value.cast(Interner)).is_some() {
+ self.register_obligation(obligation.goal);
+ let return_ty = self.normalize_projection_ty(projection);
+ Some((arg_tys, return_ty))
+ } else {
+ None
+ }
+ }
+}
+
+impl<'a> fmt::Debug for InferenceTable<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_struct("InferenceTable").field("num_vars", &self.type_variable_table.len()).finish()
+ }
+}
+
+mod resolve {
+ use super::InferenceTable;
+ use crate::{
+ ConcreteConst, Const, ConstData, ConstValue, DebruijnIndex, GenericArg, InferenceVar,
+ Interner, Lifetime, Ty, TyVariableKind, VariableKind,
+ };
+ use chalk_ir::{
+ cast::Cast,
+ fold::{TypeFoldable, TypeFolder},
+ Fallible, NoSolution,
+ };
+ use hir_def::type_ref::ConstScalar;
+
+ pub(super) struct Resolver<'a, 'b, F> {
+ pub(super) table: &'a mut InferenceTable<'b>,
+ pub(super) var_stack: &'a mut Vec<InferenceVar>,
+ pub(super) fallback: F,
+ }
+ impl<'a, 'b, 'i, F> TypeFolder<Interner> for Resolver<'a, 'b, F>
+ where
+ F: Fn(InferenceVar, VariableKind, GenericArg, DebruijnIndex) -> GenericArg + 'i,
+ {
+ type Error = NoSolution;
+
+ fn as_dyn(&mut self) -> &mut dyn TypeFolder<Interner, Error = Self::Error> {
+ self
+ }
+
+ fn interner(&self) -> Interner {
+ Interner
+ }
+
+ fn fold_inference_ty(
+ &mut self,
+ var: InferenceVar,
+ kind: TyVariableKind,
+ outer_binder: DebruijnIndex,
+ ) -> Fallible<Ty> {
+ let var = self.table.var_unification_table.inference_var_root(var);
+ if self.var_stack.contains(&var) {
+ // recursive type
+ let default = self.table.fallback_value(var, kind).cast(Interner);
+ return Ok((self.fallback)(var, VariableKind::Ty(kind), default, outer_binder)
+ .assert_ty_ref(Interner)
+ .clone());
+ }
+ let result = if let Some(known_ty) = self.table.var_unification_table.probe_var(var) {
+ // known_ty may contain other variables that are known by now
+ self.var_stack.push(var);
+ let result =
+ known_ty.fold_with(self, outer_binder).expect("fold failed unexpectedly");
+ self.var_stack.pop();
+ result.assert_ty_ref(Interner).clone()
+ } else {
+ let default = self.table.fallback_value(var, kind).cast(Interner);
+ (self.fallback)(var, VariableKind::Ty(kind), default, outer_binder)
+ .assert_ty_ref(Interner)
+ .clone()
+ };
+ Ok(result)
+ }
+
+ fn fold_inference_const(
+ &mut self,
+ ty: Ty,
+ var: InferenceVar,
+ outer_binder: DebruijnIndex,
+ ) -> Fallible<Const> {
+ let var = self.table.var_unification_table.inference_var_root(var);
+ let default = ConstData {
+ ty: ty.clone(),
+ value: ConstValue::Concrete(ConcreteConst { interned: ConstScalar::Unknown }),
+ }
+ .intern(Interner)
+ .cast(Interner);
+ if self.var_stack.contains(&var) {
+ // recursive
+ return Ok((self.fallback)(var, VariableKind::Const(ty), default, outer_binder)
+ .assert_const_ref(Interner)
+ .clone());
+ }
+ let result = if let Some(known_ty) = self.table.var_unification_table.probe_var(var) {
+ // known_ty may contain other variables that are known by now
+ self.var_stack.push(var);
+ let result =
+ known_ty.fold_with(self, outer_binder).expect("fold failed unexpectedly");
+ self.var_stack.pop();
+ result.assert_const_ref(Interner).clone()
+ } else {
+ (self.fallback)(var, VariableKind::Const(ty), default, outer_binder)
+ .assert_const_ref(Interner)
+ .clone()
+ };
+ Ok(result)
+ }
+
+ fn fold_inference_lifetime(
+ &mut self,
+ _var: InferenceVar,
+ _outer_binder: DebruijnIndex,
+ ) -> Fallible<Lifetime> {
+ // fall back all lifetimes to 'static -- currently we don't deal
+ // with any lifetimes, but we can sometimes get some lifetime
+ // variables through Chalk's unification, and this at least makes
+ // sure we don't leak them outside of inference
+ Ok(crate::static_lifetime())
+ }
+ }
+}
--- /dev/null
- parent_params,
+//! Methods for lowering the HIR to types. There are two main cases here:
+//!
+//! - Lowering a type reference like `&usize` or `Option<foo::bar::Baz>` to a
+//! type: The entry point for this is `TyLoweringContext::lower_ty`.
+//! - Building the type for an item: This happens through the `ty` query.
+//!
+//! This usually involves resolving names, collecting generic arguments etc.
+use std::{
+ cell::{Cell, RefCell, RefMut},
+ iter,
+ sync::Arc,
+};
+
+use base_db::CrateId;
+use chalk_ir::{
+ cast::Cast, fold::Shift, fold::TypeFoldable, interner::HasInterner, Mutability, Safety,
+};
+
+use hir_def::{
+ adt::StructKind,
+ body::{Expander, LowerCtx},
+ builtin_type::BuiltinType,
+ generics::{
+ TypeOrConstParamData, TypeParamProvenance, WherePredicate, WherePredicateTypeTarget,
+ },
+ intern::Interned,
+ lang_item::lang_attr,
+ path::{GenericArg, ModPath, Path, PathKind, PathSegment, PathSegments},
+ resolver::{HasResolver, Resolver, TypeNs},
+ type_ref::{
+ ConstScalarOrPath, TraitBoundModifier, TraitRef as HirTraitRef, TypeBound, TypeRef,
+ },
+ AdtId, AssocItemId, ConstId, ConstParamId, EnumId, EnumVariantId, FunctionId, GenericDefId,
+ HasModule, ImplId, ItemContainerId, LocalFieldId, Lookup, StaticId, StructId, TraitId,
+ TypeAliasId, TypeOrConstParamId, TypeParamId, UnionId, VariantId,
+};
+use hir_expand::{name::Name, ExpandResult};
+use itertools::Either;
+use la_arena::ArenaMap;
+use rustc_hash::FxHashSet;
+use smallvec::SmallVec;
+use stdx::{impl_from, never};
+use syntax::{ast, SmolStr};
+
+use crate::{
+ all_super_traits,
+ consteval::{intern_const_scalar, path_to_const, unknown_const, unknown_const_as_generic},
+ db::HirDatabase,
+ make_binders,
+ mapping::{from_chalk_trait_id, ToChalk},
+ static_lifetime, to_assoc_type_id, to_chalk_trait_id, to_placeholder_idx,
+ utils::Generics,
+ utils::{all_super_trait_refs, associated_type_by_name_including_super_traits, generics},
+ AliasEq, AliasTy, Binders, BoundVar, CallableSig, Const, DebruijnIndex, DynTy, FnPointer,
+ FnSig, FnSubst, GenericArgData, ImplTraitId, Interner, ParamKind, PolyFnSig, ProjectionTy,
+ QuantifiedWhereClause, QuantifiedWhereClauses, ReturnTypeImplTrait, ReturnTypeImplTraits,
+ Substitution, TraitEnvironment, TraitRef, TraitRefExt, Ty, TyBuilder, TyKind, WhereClause,
+};
+
+#[derive(Debug)]
+pub struct TyLoweringContext<'a> {
+ pub db: &'a dyn HirDatabase,
+ pub resolver: &'a Resolver,
+ in_binders: DebruijnIndex,
+ /// Note: Conceptually, it's thinkable that we could be in a location where
+ /// some type params should be represented as placeholders, and others
+ /// should be converted to variables. I think in practice, this isn't
+ /// possible currently, so this should be fine for now.
+ pub type_param_mode: ParamLoweringMode,
+ pub impl_trait_mode: ImplTraitLoweringMode,
+ impl_trait_counter: Cell<u16>,
+ /// When turning `impl Trait` into opaque types, we have to collect the
+ /// bounds at the same time to get the IDs correct (without becoming too
+ /// complicated). I don't like using interior mutability (as for the
+ /// counter), but I've tried and failed to make the lifetimes work for
+ /// passing around a `&mut TyLoweringContext`. The core problem is that
+ /// we're grouping the mutable data (the counter and this field) together
+ /// with the immutable context (the references to the DB and resolver).
+ /// Splitting this up would be a possible fix.
+ opaque_type_data: RefCell<Vec<ReturnTypeImplTrait>>,
+ expander: RefCell<Option<Expander>>,
+ /// Tracks types with explicit `?Sized` bounds.
+ pub(crate) unsized_types: RefCell<FxHashSet<Ty>>,
+}
+
+impl<'a> TyLoweringContext<'a> {
+ pub fn new(db: &'a dyn HirDatabase, resolver: &'a Resolver) -> Self {
+ let impl_trait_counter = Cell::new(0);
+ let impl_trait_mode = ImplTraitLoweringMode::Disallowed;
+ let type_param_mode = ParamLoweringMode::Placeholder;
+ let in_binders = DebruijnIndex::INNERMOST;
+ let opaque_type_data = RefCell::new(Vec::new());
+ Self {
+ db,
+ resolver,
+ in_binders,
+ impl_trait_mode,
+ impl_trait_counter,
+ type_param_mode,
+ opaque_type_data,
+ expander: RefCell::new(None),
+ unsized_types: RefCell::default(),
+ }
+ }
+
+ pub fn with_debruijn<T>(
+ &self,
+ debruijn: DebruijnIndex,
+ f: impl FnOnce(&TyLoweringContext<'_>) -> T,
+ ) -> T {
+ let opaque_ty_data_vec = self.opaque_type_data.take();
+ let expander = self.expander.take();
+ let unsized_types = self.unsized_types.take();
+ let new_ctx = Self {
+ in_binders: debruijn,
+ impl_trait_counter: Cell::new(self.impl_trait_counter.get()),
+ opaque_type_data: RefCell::new(opaque_ty_data_vec),
+ expander: RefCell::new(expander),
+ unsized_types: RefCell::new(unsized_types),
+ ..*self
+ };
+ let result = f(&new_ctx);
+ self.impl_trait_counter.set(new_ctx.impl_trait_counter.get());
+ self.opaque_type_data.replace(new_ctx.opaque_type_data.into_inner());
+ self.expander.replace(new_ctx.expander.into_inner());
+ self.unsized_types.replace(new_ctx.unsized_types.into_inner());
+ result
+ }
+
+ pub fn with_shifted_in<T>(
+ &self,
+ debruijn: DebruijnIndex,
+ f: impl FnOnce(&TyLoweringContext<'_>) -> T,
+ ) -> T {
+ self.with_debruijn(self.in_binders.shifted_in_from(debruijn), f)
+ }
+
+ pub fn with_impl_trait_mode(self, impl_trait_mode: ImplTraitLoweringMode) -> Self {
+ Self { impl_trait_mode, ..self }
+ }
+
+ pub fn with_type_param_mode(self, type_param_mode: ParamLoweringMode) -> Self {
+ Self { type_param_mode, ..self }
+ }
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum ImplTraitLoweringMode {
+ /// `impl Trait` gets lowered into an opaque type that doesn't unify with
+ /// anything except itself. This is used in places where values flow 'out',
+ /// i.e. for arguments of the function we're currently checking, and return
+ /// types of functions we're calling.
+ Opaque,
+ /// `impl Trait` gets lowered into a type variable. Used for argument
+ /// position impl Trait when inside the respective function, since it allows
+ /// us to support that without Chalk.
+ Param,
+ /// `impl Trait` gets lowered into a variable that can unify with some
+ /// type. This is used in places where values flow 'in', i.e. for arguments
+ /// of functions we're calling, and the return type of the function we're
+ /// currently checking.
+ Variable,
+ /// `impl Trait` is disallowed and will be an error.
+ Disallowed,
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum ParamLoweringMode {
+ Placeholder,
+ Variable,
+}
+
+impl<'a> TyLoweringContext<'a> {
+ pub fn lower_ty(&self, type_ref: &TypeRef) -> Ty {
+ self.lower_ty_ext(type_ref).0
+ }
+
+ fn generics(&self) -> Generics {
+ generics(
+ self.db.upcast(),
+ self.resolver
+ .generic_def()
+ .expect("there should be generics if there's a generic param"),
+ )
+ }
+
+ pub fn lower_ty_ext(&self, type_ref: &TypeRef) -> (Ty, Option<TypeNs>) {
+ let mut res = None;
+ let ty = match type_ref {
+ TypeRef::Never => TyKind::Never.intern(Interner),
+ TypeRef::Tuple(inner) => {
+ let inner_tys = inner.iter().map(|tr| self.lower_ty(tr));
+ TyKind::Tuple(inner_tys.len(), Substitution::from_iter(Interner, inner_tys))
+ .intern(Interner)
+ }
+ TypeRef::Path(path) => {
+ let (ty, res_) = self.lower_path(path);
+ res = res_;
+ ty
+ }
+ TypeRef::RawPtr(inner, mutability) => {
+ let inner_ty = self.lower_ty(inner);
+ TyKind::Raw(lower_to_chalk_mutability(*mutability), inner_ty).intern(Interner)
+ }
+ TypeRef::Array(inner, len) => {
+ let inner_ty = self.lower_ty(inner);
+ let const_len = const_or_path_to_chalk(
+ self.db,
+ self.resolver,
+ TyBuilder::usize(),
+ len,
+ self.type_param_mode,
+ || self.generics(),
+ self.in_binders,
+ );
+
+ TyKind::Array(inner_ty, const_len).intern(Interner)
+ }
+ TypeRef::Slice(inner) => {
+ let inner_ty = self.lower_ty(inner);
+ TyKind::Slice(inner_ty).intern(Interner)
+ }
+ TypeRef::Reference(inner, _, mutability) => {
+ let inner_ty = self.lower_ty(inner);
+ let lifetime = static_lifetime();
+ TyKind::Ref(lower_to_chalk_mutability(*mutability), lifetime, inner_ty)
+ .intern(Interner)
+ }
+ TypeRef::Placeholder => TyKind::Error.intern(Interner),
+ TypeRef::Fn(params, is_varargs) => {
+ let substs = self.with_shifted_in(DebruijnIndex::ONE, |ctx| {
+ Substitution::from_iter(Interner, params.iter().map(|(_, tr)| ctx.lower_ty(tr)))
+ });
+ TyKind::Function(FnPointer {
+ num_binders: 0, // FIXME lower `for<'a> fn()` correctly
+ sig: FnSig { abi: (), safety: Safety::Safe, variadic: *is_varargs },
+ substitution: FnSubst(substs),
+ })
+ .intern(Interner)
+ }
+ TypeRef::DynTrait(bounds) => self.lower_dyn_trait(bounds),
+ TypeRef::ImplTrait(bounds) => {
+ match self.impl_trait_mode {
+ ImplTraitLoweringMode::Opaque => {
+ let idx = self.impl_trait_counter.get();
+ self.impl_trait_counter.set(idx + 1);
+ let func = match self.resolver.generic_def() {
+ Some(GenericDefId::FunctionId(f)) => f,
+ _ => panic!("opaque impl trait lowering in non-function"),
+ };
+
+ assert!(idx as usize == self.opaque_type_data.borrow().len());
+ // this dance is to make sure the data is in the right
+ // place even if we encounter more opaque types while
+ // lowering the bounds
+ self.opaque_type_data.borrow_mut().push(ReturnTypeImplTrait {
+ bounds: crate::make_single_type_binders(Vec::new()),
+ });
+ // We don't want to lower the bounds inside the binders
+ // we're currently in, because they don't end up inside
+ // those binders. E.g. when we have `impl Trait<impl
+ // OtherTrait<T>>`, the `impl OtherTrait<T>` can't refer
+ // to the self parameter from `impl Trait`, and the
+ // bounds aren't actually stored nested within each
+ // other, but separately. So if the `T` refers to a type
+ // parameter of the outer function, it's just one binder
+ // away instead of two.
+ let actual_opaque_type_data = self
+ .with_debruijn(DebruijnIndex::INNERMOST, |ctx| {
+ ctx.lower_impl_trait(bounds, func)
+ });
+ self.opaque_type_data.borrow_mut()[idx as usize] = actual_opaque_type_data;
+
+ let impl_trait_id = ImplTraitId::ReturnTypeImplTrait(func, idx);
+ let opaque_ty_id = self.db.intern_impl_trait_id(impl_trait_id).into();
+ let generics = generics(self.db.upcast(), func.into());
+ let parameters = generics.bound_vars_subst(self.db, self.in_binders);
+ TyKind::OpaqueType(opaque_ty_id, parameters).intern(Interner)
+ }
+ ImplTraitLoweringMode::Param => {
+ let idx = self.impl_trait_counter.get();
+ // FIXME we're probably doing something wrong here
+ self.impl_trait_counter.set(idx + count_impl_traits(type_ref) as u16);
+ if let Some(def) = self.resolver.generic_def() {
+ let generics = generics(self.db.upcast(), def);
+ let param = generics
+ .iter()
+ .filter(|(_, data)| {
+ matches!(
+ data,
+ TypeOrConstParamData::TypeParamData(data)
+ if data.provenance == TypeParamProvenance::ArgumentImplTrait
+ )
+ })
+ .nth(idx as usize)
+ .map_or(TyKind::Error, |(id, _)| {
+ TyKind::Placeholder(to_placeholder_idx(self.db, id))
+ });
+ param.intern(Interner)
+ } else {
+ TyKind::Error.intern(Interner)
+ }
+ }
+ ImplTraitLoweringMode::Variable => {
+ let idx = self.impl_trait_counter.get();
+ // FIXME we're probably doing something wrong here
+ self.impl_trait_counter.set(idx + count_impl_traits(type_ref) as u16);
+ let (
- idx as usize + parent_params + self_params + list_params + const_params,
++ _parent_params,
+ self_params,
+ list_params,
+ const_params,
+ _impl_trait_params,
+ ) = if let Some(def) = self.resolver.generic_def() {
+ let generics = generics(self.db.upcast(), def);
+ generics.provenance_split()
+ } else {
+ (0, 0, 0, 0, 0)
+ };
+ TyKind::BoundVar(BoundVar::new(
+ self.in_binders,
- let generics = generics(self.db.upcast(), impl_id.into());
- let substs = match self.type_param_mode {
- ParamLoweringMode::Placeholder => generics.placeholder_subst(self.db),
++ idx as usize + self_params + list_params + const_params,
+ ))
+ .intern(Interner)
+ }
+ ImplTraitLoweringMode::Disallowed => {
+ // FIXME: report error
+ TyKind::Error.intern(Interner)
+ }
+ }
+ }
+ TypeRef::Macro(macro_call) => {
+ let (mut expander, recursion_start) = {
+ match RefMut::filter_map(self.expander.borrow_mut(), Option::as_mut) {
+ // There already is an expander here, this means we are already recursing
+ Ok(expander) => (expander, false),
+ // No expander was created yet, so we are at the start of the expansion recursion
+ // and therefore have to create an expander.
+ Err(expander) => (
+ RefMut::map(expander, |it| {
+ it.insert(Expander::new(
+ self.db.upcast(),
+ macro_call.file_id,
+ self.resolver.module(),
+ ))
+ }),
+ true,
+ ),
+ }
+ };
+ let ty = {
+ let macro_call = macro_call.to_node(self.db.upcast());
+ match expander.enter_expand::<ast::Type>(self.db.upcast(), macro_call) {
+ Ok(ExpandResult { value: Some((mark, expanded)), .. }) => {
+ let ctx = LowerCtx::new(self.db.upcast(), expander.current_file_id());
+ let type_ref = TypeRef::from_ast(&ctx, expanded);
+
+ drop(expander);
+ let ty = self.lower_ty(&type_ref);
+
+ self.expander
+ .borrow_mut()
+ .as_mut()
+ .unwrap()
+ .exit(self.db.upcast(), mark);
+ Some(ty)
+ }
+ _ => {
+ drop(expander);
+ None
+ }
+ }
+ };
+
+ // drop the expander, resetting it to pre-recursion state
+ if recursion_start {
+ *self.expander.borrow_mut() = None;
+ }
+ ty.unwrap_or_else(|| TyKind::Error.intern(Interner))
+ }
+ TypeRef::Error => TyKind::Error.intern(Interner),
+ };
+ (ty, res)
+ }
+
+ /// This is only for `generic_predicates_for_param`, where we can't just
+ /// lower the self types of the predicates since that could lead to cycles.
+ /// So we just check here if the `type_ref` resolves to a generic param, and which.
+ fn lower_ty_only_param(&self, type_ref: &TypeRef) -> Option<TypeOrConstParamId> {
+ let path = match type_ref {
+ TypeRef::Path(path) => path,
+ _ => return None,
+ };
+ if path.type_anchor().is_some() {
+ return None;
+ }
+ if path.segments().len() > 1 {
+ return None;
+ }
+ let resolution =
+ match self.resolver.resolve_path_in_type_ns(self.db.upcast(), path.mod_path()) {
+ Some((it, None)) => it,
+ _ => return None,
+ };
+ match resolution {
+ TypeNs::GenericParam(param_id) => Some(param_id.into()),
+ _ => None,
+ }
+ }
+
+ pub(crate) fn lower_ty_relative_path(
+ &self,
+ ty: Ty,
+ // We need the original resolution to lower `Self::AssocTy` correctly
+ res: Option<TypeNs>,
+ remaining_segments: PathSegments<'_>,
+ ) -> (Ty, Option<TypeNs>) {
+ match remaining_segments.len() {
+ 0 => (ty, res),
+ 1 => {
+ // resolve unselected assoc types
+ let segment = remaining_segments.first().unwrap();
+ (self.select_associated_type(res, segment), None)
+ }
+ _ => {
+ // FIXME report error (ambiguous associated type)
+ (TyKind::Error.intern(Interner), None)
+ }
+ }
+ }
+
+ pub(crate) fn lower_partly_resolved_path(
+ &self,
+ resolution: TypeNs,
+ resolved_segment: PathSegment<'_>,
+ remaining_segments: PathSegments<'_>,
+ infer_args: bool,
+ ) -> (Ty, Option<TypeNs>) {
+ let ty = match resolution {
+ TypeNs::TraitId(trait_) => {
+ let ty = match remaining_segments.len() {
+ 1 => {
+ let trait_ref =
+ self.lower_trait_ref_from_resolved_path(trait_, resolved_segment, None);
+ let segment = remaining_segments.first().unwrap();
+ let found = self
+ .db
+ .trait_data(trait_ref.hir_trait_id())
+ .associated_type_by_name(segment.name);
+ match found {
+ Some(associated_ty) => {
+ // FIXME handle type parameters on the segment
+ TyKind::Alias(AliasTy::Projection(ProjectionTy {
+ associated_ty_id: to_assoc_type_id(associated_ty),
+ substitution: trait_ref.substitution,
+ }))
+ .intern(Interner)
+ }
+ None => {
+ // FIXME: report error (associated type not found)
+ TyKind::Error.intern(Interner)
+ }
+ }
+ }
+ 0 => {
+ // Trait object type without dyn; this should be handled in upstream. See
+ // `lower_path()`.
+ stdx::never!("unexpected fully resolved trait path");
+ TyKind::Error.intern(Interner)
+ }
+ _ => {
+ // FIXME report error (ambiguous associated type)
+ TyKind::Error.intern(Interner)
+ }
+ };
+ return (ty, None);
+ }
+ TypeNs::GenericParam(param_id) => {
+ let generics = generics(
+ self.db.upcast(),
+ self.resolver.generic_def().expect("generics in scope"),
+ );
+ match self.type_param_mode {
+ ParamLoweringMode::Placeholder => {
+ TyKind::Placeholder(to_placeholder_idx(self.db, param_id.into()))
+ }
+ ParamLoweringMode::Variable => {
+ let idx = match generics.param_idx(param_id.into()) {
+ None => {
+ never!("no matching generics");
+ return (TyKind::Error.intern(Interner), None);
+ }
+ Some(idx) => idx,
+ };
+
+ TyKind::BoundVar(BoundVar::new(self.in_binders, idx))
+ }
+ }
+ .intern(Interner)
+ }
+ TypeNs::SelfType(impl_id) => {
- generics.bound_vars_subst(self.db, self.in_binders)
++ let def =
++ self.resolver.generic_def().expect("impl should have generic param scope");
++ let generics = generics(self.db.upcast(), def);
++
++ match self.type_param_mode {
++ ParamLoweringMode::Placeholder => {
++ // `def` can be either impl itself or item within, and we need impl itself
++ // now.
++ let generics = generics.parent_generics().unwrap_or(&generics);
++ let subst = generics.placeholder_subst(self.db);
++ self.db.impl_self_ty(impl_id).substitute(Interner, &subst)
++ }
+ ParamLoweringMode::Variable => {
- };
- self.db.impl_self_ty(impl_id).substitute(Interner, &substs)
++ let starting_from = match def {
++ GenericDefId::ImplId(_) => 0,
++ // `def` is an item within impl. We need to substitute `BoundVar`s but
++ // remember that they are for parent (i.e. impl) generic params so they
++ // come after our own params.
++ _ => generics.len_self(),
++ };
++ TyBuilder::impl_self_ty(self.db, impl_id)
++ .fill_with_bound_vars(self.in_binders, starting_from)
++ .build()
+ }
- def_generic: Option<GenericDefId>,
++ }
+ }
+ TypeNs::AdtSelfType(adt) => {
+ let generics = generics(self.db.upcast(), adt.into());
+ let substs = match self.type_param_mode {
+ ParamLoweringMode::Placeholder => generics.placeholder_subst(self.db),
+ ParamLoweringMode::Variable => {
+ generics.bound_vars_subst(self.db, self.in_binders)
+ }
+ };
+ self.db.ty(adt.into()).substitute(Interner, &substs)
+ }
+
+ TypeNs::AdtId(it) => self.lower_path_inner(resolved_segment, it.into(), infer_args),
+ TypeNs::BuiltinType(it) => {
+ self.lower_path_inner(resolved_segment, it.into(), infer_args)
+ }
+ TypeNs::TypeAliasId(it) => {
+ self.lower_path_inner(resolved_segment, it.into(), infer_args)
+ }
+ // FIXME: report error
+ TypeNs::EnumVariantId(_) => return (TyKind::Error.intern(Interner), None),
+ };
+ self.lower_ty_relative_path(ty, Some(resolution), remaining_segments)
+ }
+
+ pub(crate) fn lower_path(&self, path: &Path) -> (Ty, Option<TypeNs>) {
+ // Resolve the path (in type namespace)
+ if let Some(type_ref) = path.type_anchor() {
+ let (ty, res) = self.lower_ty_ext(type_ref);
+ return self.lower_ty_relative_path(ty, res, path.segments());
+ }
+
+ let (resolution, remaining_index) =
+ match self.resolver.resolve_path_in_type_ns(self.db.upcast(), path.mod_path()) {
+ Some(it) => it,
+ None => return (TyKind::Error.intern(Interner), None),
+ };
+
+ if matches!(resolution, TypeNs::TraitId(_)) && remaining_index.is_none() {
+ // trait object type without dyn
+ let bound = TypeBound::Path(path.clone(), TraitBoundModifier::None);
+ let ty = self.lower_dyn_trait(&[Interned::new(bound)]);
+ return (ty, None);
+ }
+
+ let (resolved_segment, remaining_segments) = match remaining_index {
+ None => (
+ path.segments().last().expect("resolved path has at least one element"),
+ PathSegments::EMPTY,
+ ),
+ Some(i) => (path.segments().get(i - 1).unwrap(), path.segments().skip(i)),
+ };
+ self.lower_partly_resolved_path(resolution, resolved_segment, remaining_segments, false)
+ }
+
+ fn select_associated_type(&self, res: Option<TypeNs>, segment: PathSegment<'_>) -> Ty {
+ let (def, res) = match (self.resolver.generic_def(), res) {
+ (Some(def), Some(res)) => (def, res),
+ _ => return TyKind::Error.intern(Interner),
+ };
+ let ty = named_associated_type_shorthand_candidates(
+ self.db,
+ def,
+ res,
+ Some(segment.name.clone()),
+ move |name, t, associated_ty| {
+ if name == segment.name {
+ let substs = match self.type_param_mode {
+ ParamLoweringMode::Placeholder => {
+ // if we're lowering to placeholders, we have to put
+ // them in now
+ let generics = generics(
+ self.db.upcast(),
+ self.resolver
+ .generic_def()
+ .expect("there should be generics if there's a generic param"),
+ );
+ let s = generics.placeholder_subst(self.db);
+ s.apply(t.substitution.clone(), Interner)
+ }
+ ParamLoweringMode::Variable => t.substitution.clone(),
+ };
+ // We need to shift in the bound vars, since
+ // associated_type_shorthand_candidates does not do that
+ let substs = substs.shifted_in_from(Interner, self.in_binders);
+ // FIXME handle type parameters on the segment
+ Some(
+ TyKind::Alias(AliasTy::Projection(ProjectionTy {
+ associated_ty_id: to_assoc_type_id(associated_ty),
+ substitution: substs,
+ }))
+ .intern(Interner),
+ )
+ } else {
+ None
+ }
+ },
+ );
+
+ ty.unwrap_or_else(|| TyKind::Error.intern(Interner))
+ }
+
+ fn lower_path_inner(
+ &self,
+ segment: PathSegment<'_>,
+ typeable: TyDefId,
+ infer_args: bool,
+ ) -> Ty {
+ let generic_def = match typeable {
+ TyDefId::BuiltinType(_) => None,
+ TyDefId::AdtId(it) => Some(it.into()),
+ TyDefId::TypeAliasId(it) => Some(it.into()),
+ };
+ let substs = self.substs_from_path_segment(segment, generic_def, infer_args, None);
+ self.db.ty(typeable).substitute(Interner, &substs)
+ }
+
+ /// Collect generic arguments from a path into a `Substs`. See also
+ /// `create_substs_for_ast_path` and `def_to_ty` in rustc.
+ pub(super) fn substs_from_path(
+ &self,
+ path: &Path,
+ // Note that we don't call `db.value_type(resolved)` here,
+ // `ValueTyDefId` is just a convenient way to pass generics and
+ // special-case enum variants
+ resolved: ValueTyDefId,
+ infer_args: bool,
+ ) -> Substitution {
+ let last = path.segments().last().expect("path should have at least one segment");
+ let (segment, generic_def) = match resolved {
+ ValueTyDefId::FunctionId(it) => (last, Some(it.into())),
+ ValueTyDefId::StructId(it) => (last, Some(it.into())),
+ ValueTyDefId::UnionId(it) => (last, Some(it.into())),
+ ValueTyDefId::ConstId(it) => (last, Some(it.into())),
+ ValueTyDefId::StaticId(_) => (last, None),
+ ValueTyDefId::EnumVariantId(var) => {
+ // the generic args for an enum variant may be either specified
+ // on the segment referring to the enum, or on the segment
+ // referring to the variant. So `Option::<T>::None` and
+ // `Option::None::<T>` are both allowed (though the former is
+ // preferred). See also `def_ids_for_path_segments` in rustc.
+ let len = path.segments().len();
+ let penultimate = len.checked_sub(2).and_then(|idx| path.segments().get(idx));
+ let segment = match penultimate {
+ Some(segment) if segment.args_and_bindings.is_some() => segment,
+ _ => last,
+ };
+ (segment, Some(var.parent.into()))
+ }
+ };
+ self.substs_from_path_segment(segment, generic_def, infer_args, None)
+ }
+
+ fn substs_from_path_segment(
+ &self,
+ segment: PathSegment<'_>,
- let def_generics = if let Some(def) = def_generic {
- generics(self.db.upcast(), def)
++ def: Option<GenericDefId>,
+ infer_args: bool,
+ explicit_self_ty: Option<Ty>,
+ ) -> Substitution {
++ // Remember that the item's own generic args come before its parent's.
+ let mut substs = Vec::new();
- let total_len =
- parent_params + self_params + type_params + const_params + impl_trait_params;
++ let def = if let Some(d) = def {
++ d
+ } else {
+ return Substitution::empty(Interner);
+ };
++ let def_generics = generics(self.db.upcast(), def);
+ let (parent_params, self_params, type_params, const_params, impl_trait_params) =
+ def_generics.provenance_split();
- let ty_error = GenericArgData::Ty(TyKind::Error.intern(Interner)).intern(Interner);
++ let item_len = self_params + type_params + const_params + impl_trait_params;
++ let total_len = parent_params + item_len;
+
- for _ in 0..parent_params {
- if let Some(eid) = def_generic_iter.next() {
- match eid {
- Either::Left(_) => substs.push(ty_error.clone()),
- Either::Right(x) => {
- substs.push(unknown_const_as_generic(self.db.const_param_ty(x)))
- }
- }
- }
- }
-
++ let ty_error = TyKind::Error.intern(Interner).cast(Interner);
+
+ let mut def_generic_iter = def_generics.iter_id();
+
- .map(|x| GenericArgData::Ty(x).intern(Interner))
+ let fill_self_params = || {
+ for x in explicit_self_ty
+ .into_iter()
- if let Some(def_generic) = def_generic {
- let defaults = self.db.generic_defaults(def_generic);
- assert_eq!(total_len, defaults.len());
-
- for default_ty in defaults.iter().skip(substs.len()) {
- // each default can depend on the previous parameters
- let substs_so_far = Substitution::from_iter(Interner, substs.clone());
- if let Some(_id) = def_generic_iter.next() {
- substs.push(default_ty.clone().substitute(Interner, &substs_so_far));
- }
- }
++ .map(|x| x.cast(Interner))
+ .chain(iter::repeat(ty_error.clone()))
+ .take(self_params)
+ {
+ if let Some(id) = def_generic_iter.next() {
+ assert!(id.is_left());
+ substs.push(x);
+ }
+ }
+ };
+ let mut had_explicit_args = false;
+
+ if let Some(generic_args) = &segment.args_and_bindings {
+ if !generic_args.has_self_type {
+ fill_self_params();
+ }
+ let expected_num = if generic_args.has_self_type {
+ self_params + type_params + const_params
+ } else {
+ type_params + const_params
+ };
+ let skip = if generic_args.has_self_type && self_params == 0 { 1 } else { 0 };
+ // if args are provided, it should be all of them, but we can't rely on that
+ for arg in generic_args
+ .args
+ .iter()
+ .filter(|arg| !matches!(arg, GenericArg::Lifetime(_)))
+ .skip(skip)
+ .take(expected_num)
+ {
+ if let Some(id) = def_generic_iter.next() {
+ if let Some(x) = generic_arg_to_chalk(
+ self.db,
+ id,
+ arg,
+ &mut (),
+ |_, type_ref| self.lower_ty(type_ref),
+ |_, c, ty| {
+ const_or_path_to_chalk(
+ self.db,
+ &self.resolver,
+ ty,
+ c,
+ self.type_param_mode,
+ || self.generics(),
+ self.in_binders,
+ )
+ },
+ ) {
+ had_explicit_args = true;
+ substs.push(x);
+ } else {
+ // we just filtered them out
+ never!("Unexpected lifetime argument");
+ }
+ }
+ }
+ } else {
+ fill_self_params();
+ }
+
++ // These params include those of parent.
++ let remaining_params: SmallVec<[_; 2]> = def_generic_iter
++ .map(|eid| match eid {
++ Either::Left(_) => ty_error.clone(),
++ Either::Right(x) => unknown_const_as_generic(self.db.const_param_ty(x)),
++ })
++ .collect();
++ assert_eq!(remaining_params.len() + substs.len(), total_len);
++
+ // handle defaults. In expression or pattern path segments without
+ // explicitly specified type arguments, missing type arguments are inferred
+ // (i.e. defaults aren't used).
+ if !infer_args || had_explicit_args {
- }
++ let defaults = self.db.generic_defaults(def);
++ assert_eq!(total_len, defaults.len());
++ let parent_from = item_len - substs.len();
++
++ for (idx, default_ty) in defaults[substs.len()..item_len].iter().enumerate() {
++ // each default can depend on the previous parameters
++ let substs_so_far = Substitution::from_iter(
++ Interner,
++ substs.iter().cloned().chain(remaining_params[idx..].iter().cloned()),
++ );
++ substs.push(default_ty.clone().substitute(Interner, &substs_so_far));
+ }
- // add placeholders for args that were not provided
- // FIXME: emit diagnostics in contexts where this is not allowed
- for eid in def_generic_iter {
- match eid {
- Either::Left(_) => substs.push(ty_error.clone()),
- Either::Right(x) => {
- substs.push(unknown_const_as_generic(self.db.const_param_ty(x)))
- }
- }
+
- // If this assert fails, it means you pushed into subst but didn't call .next() of def_generic_iter
- assert_eq!(substs.len(), total_len);
++ // Keep parent's params as unknown.
++ let mut remaining_params = remaining_params;
++ substs.extend(remaining_params.drain(parent_from..));
++ } else {
++ substs.extend(remaining_params);
+ }
- // INVARIANT: The principal trait bound must come first. Others may be in any order but
- // should be in the same order for the same set but possibly different order of bounds in
- // the input.
- // This invariant is used by `TyExt::dyn_trait()` and chalk.
+
++ assert_eq!(substs.len(), total_len);
+ Substitution::from_iter(Interner, substs)
+ }
+
+ fn lower_trait_ref_from_path(
+ &self,
+ path: &Path,
+ explicit_self_ty: Option<Ty>,
+ ) -> Option<TraitRef> {
+ let resolved =
+ match self.resolver.resolve_path_in_type_ns_fully(self.db.upcast(), path.mod_path())? {
+ TypeNs::TraitId(tr) => tr,
+ _ => return None,
+ };
+ let segment = path.segments().last().expect("path should have at least one segment");
+ Some(self.lower_trait_ref_from_resolved_path(resolved, segment, explicit_self_ty))
+ }
+
+ pub(crate) fn lower_trait_ref_from_resolved_path(
+ &self,
+ resolved: TraitId,
+ segment: PathSegment<'_>,
+ explicit_self_ty: Option<Ty>,
+ ) -> TraitRef {
+ let substs = self.trait_ref_substs_from_path(segment, resolved, explicit_self_ty);
+ TraitRef { trait_id: to_chalk_trait_id(resolved), substitution: substs }
+ }
+
+ fn lower_trait_ref(
+ &self,
+ trait_ref: &HirTraitRef,
+ explicit_self_ty: Option<Ty>,
+ ) -> Option<TraitRef> {
+ self.lower_trait_ref_from_path(&trait_ref.path, explicit_self_ty)
+ }
+
+ fn trait_ref_substs_from_path(
+ &self,
+ segment: PathSegment<'_>,
+ resolved: TraitId,
+ explicit_self_ty: Option<Ty>,
+ ) -> Substitution {
+ self.substs_from_path_segment(segment, Some(resolved.into()), false, explicit_self_ty)
+ }
+
+ pub(crate) fn lower_where_predicate(
+ &'a self,
+ where_predicate: &'a WherePredicate,
+ ignore_bindings: bool,
+ ) -> impl Iterator<Item = QuantifiedWhereClause> + 'a {
+ match where_predicate {
+ WherePredicate::ForLifetime { target, bound, .. }
+ | WherePredicate::TypeBound { target, bound } => {
+ let self_ty = match target {
+ WherePredicateTypeTarget::TypeRef(type_ref) => self.lower_ty(type_ref),
+ WherePredicateTypeTarget::TypeOrConstParam(param_id) => {
+ let generic_def = self.resolver.generic_def().expect("generics in scope");
+ let generics = generics(self.db.upcast(), generic_def);
+ let param_id = hir_def::TypeOrConstParamId {
+ parent: generic_def,
+ local_id: *param_id,
+ };
+ let placeholder = to_placeholder_idx(self.db, param_id);
+ match self.type_param_mode {
+ ParamLoweringMode::Placeholder => TyKind::Placeholder(placeholder),
+ ParamLoweringMode::Variable => {
+ let idx = generics.param_idx(param_id).expect("matching generics");
+ TyKind::BoundVar(BoundVar::new(DebruijnIndex::INNERMOST, idx))
+ }
+ }
+ .intern(Interner)
+ }
+ };
+ self.lower_type_bound(bound, self_ty, ignore_bindings)
+ .collect::<Vec<_>>()
+ .into_iter()
+ }
+ WherePredicate::Lifetime { .. } => vec![].into_iter(),
+ }
+ }
+
+ pub(crate) fn lower_type_bound(
+ &'a self,
+ bound: &'a TypeBound,
+ self_ty: Ty,
+ ignore_bindings: bool,
+ ) -> impl Iterator<Item = QuantifiedWhereClause> + 'a {
+ let mut bindings = None;
+ let trait_ref = match bound {
+ TypeBound::Path(path, TraitBoundModifier::None) => {
+ bindings = self.lower_trait_ref_from_path(path, Some(self_ty));
+ bindings
+ .clone()
+ .filter(|tr| {
+ // ignore `T: Drop` or `T: Destruct` bounds.
+ // - `T: ~const Drop` has a special meaning in Rust 1.61 that we don't implement.
+ // (So ideally, we'd only ignore `~const Drop` here)
+ // - `Destruct` impls are built-in in 1.62 (current nightlies as of 08-04-2022), so until
+ // the builtin impls are supported by Chalk, we ignore them here.
+ if let Some(lang) = lang_attr(self.db.upcast(), tr.hir_trait_id()) {
+ if lang == "drop" || lang == "destruct" {
+ return false;
+ }
+ }
+ true
+ })
+ .map(WhereClause::Implemented)
+ .map(crate::wrap_empty_binders)
+ }
+ TypeBound::Path(path, TraitBoundModifier::Maybe) => {
+ let sized_trait = self
+ .db
+ .lang_item(self.resolver.krate(), SmolStr::new_inline("sized"))
+ .and_then(|lang_item| lang_item.as_trait());
+ // Don't lower associated type bindings as the only possible relaxed trait bound
+ // `?Sized` has no of them.
+ // If we got another trait here ignore the bound completely.
+ let trait_id = self
+ .lower_trait_ref_from_path(path, Some(self_ty.clone()))
+ .map(|trait_ref| trait_ref.hir_trait_id());
+ if trait_id == sized_trait {
+ self.unsized_types.borrow_mut().insert(self_ty);
+ }
+ None
+ }
+ TypeBound::ForLifetime(_, path) => {
+ // FIXME Don't silently drop the hrtb lifetimes here
+ bindings = self.lower_trait_ref_from_path(path, Some(self_ty));
+ bindings.clone().map(WhereClause::Implemented).map(crate::wrap_empty_binders)
+ }
+ TypeBound::Lifetime(_) => None,
+ TypeBound::Error => None,
+ };
+ trait_ref.into_iter().chain(
+ bindings
+ .into_iter()
+ .filter(move |_| !ignore_bindings)
+ .flat_map(move |tr| self.assoc_type_bindings_from_type_bound(bound, tr)),
+ )
+ }
+
+ fn assoc_type_bindings_from_type_bound(
+ &'a self,
+ bound: &'a TypeBound,
+ trait_ref: TraitRef,
+ ) -> impl Iterator<Item = QuantifiedWhereClause> + 'a {
+ let last_segment = match bound {
+ TypeBound::Path(path, TraitBoundModifier::None) | TypeBound::ForLifetime(_, path) => {
+ path.segments().last()
+ }
+ TypeBound::Path(_, TraitBoundModifier::Maybe)
+ | TypeBound::Error
+ | TypeBound::Lifetime(_) => None,
+ };
+ last_segment
+ .into_iter()
+ .filter_map(|segment| segment.args_and_bindings)
+ .flat_map(|args_and_bindings| &args_and_bindings.bindings)
+ .flat_map(move |binding| {
+ let found = associated_type_by_name_including_super_traits(
+ self.db,
+ trait_ref.clone(),
+ &binding.name,
+ );
+ let (super_trait_ref, associated_ty) = match found {
+ None => return SmallVec::new(),
+ Some(t) => t,
+ };
+ let projection_ty = ProjectionTy {
+ associated_ty_id: to_assoc_type_id(associated_ty),
+ substitution: super_trait_ref.substitution,
+ };
+ let mut preds: SmallVec<[_; 1]> = SmallVec::with_capacity(
+ binding.type_ref.as_ref().map_or(0, |_| 1) + binding.bounds.len(),
+ );
+ if let Some(type_ref) = &binding.type_ref {
+ let ty = self.lower_ty(type_ref);
+ let alias_eq =
+ AliasEq { alias: AliasTy::Projection(projection_ty.clone()), ty };
+ preds.push(crate::wrap_empty_binders(WhereClause::AliasEq(alias_eq)));
+ }
+ for bound in &binding.bounds {
+ preds.extend(self.lower_type_bound(
+ bound,
+ TyKind::Alias(AliasTy::Projection(projection_ty.clone())).intern(Interner),
+ false,
+ ));
+ }
+ preds
+ })
+ }
+
+ fn lower_dyn_trait(&self, bounds: &[Interned<TypeBound>]) -> Ty {
+ let self_ty = TyKind::BoundVar(BoundVar::new(DebruijnIndex::INNERMOST, 0)).intern(Interner);
- // FIXME: report error (additional non-auto traits or associated type rebound)
++ // INVARIANT: The principal trait bound, if present, must come first. Others may be in any
++ // order but should be in the same order for the same set but possibly different order of
++ // bounds in the input.
++ // INVARIANT: If this function returns `DynTy`, there should be at least one trait bound.
++ // These invariants are utilized by `TyExt::dyn_trait()` and chalk.
+ let bounds = self.with_shifted_in(DebruijnIndex::ONE, |ctx| {
+ let mut bounds: Vec<_> = bounds
+ .iter()
+ .flat_map(|b| ctx.lower_type_bound(b, self_ty.clone(), false))
+ .collect();
+
+ let mut multiple_regular_traits = false;
+ let mut multiple_same_projection = false;
+ bounds.sort_unstable_by(|lhs, rhs| {
+ use std::cmp::Ordering;
+ match (lhs.skip_binders(), rhs.skip_binders()) {
+ (WhereClause::Implemented(lhs), WhereClause::Implemented(rhs)) => {
+ let lhs_id = lhs.trait_id;
+ let lhs_is_auto = ctx.db.trait_data(from_chalk_trait_id(lhs_id)).is_auto;
+ let rhs_id = rhs.trait_id;
+ let rhs_is_auto = ctx.db.trait_data(from_chalk_trait_id(rhs_id)).is_auto;
+
+ if !lhs_is_auto && !rhs_is_auto {
+ multiple_regular_traits = true;
+ }
+ // Note that the ordering here is important; this ensures the invariant
+ // mentioned above.
+ (lhs_is_auto, lhs_id).cmp(&(rhs_is_auto, rhs_id))
+ }
+ (WhereClause::Implemented(_), _) => Ordering::Less,
+ (_, WhereClause::Implemented(_)) => Ordering::Greater,
+ (WhereClause::AliasEq(lhs), WhereClause::AliasEq(rhs)) => {
+ match (&lhs.alias, &rhs.alias) {
+ (AliasTy::Projection(lhs_proj), AliasTy::Projection(rhs_proj)) => {
+ // We only compare the `associated_ty_id`s. We shouldn't have
+ // multiple bounds for an associated type in the correct Rust code,
+ // and if we do, we error out.
+ if lhs_proj.associated_ty_id == rhs_proj.associated_ty_id {
+ multiple_same_projection = true;
+ }
+ lhs_proj.associated_ty_id.cmp(&rhs_proj.associated_ty_id)
+ }
+ // We don't produce `AliasTy::Opaque`s yet.
+ _ => unreachable!(),
+ }
+ }
+ // We don't produce `WhereClause::{TypeOutlives, LifetimeOutlives}` yet.
+ _ => unreachable!(),
+ }
+ });
+
+ if multiple_regular_traits || multiple_same_projection {
+ return None;
+ }
+
++ if bounds.first().and_then(|b| b.trait_id()).is_none() {
++ // When there's no trait bound, that's an error. This happens when the trait refs
++ // are unresolved.
++ return None;
++ }
++
+ // As multiple occurrences of the same auto traits *are* permitted, we dedulicate the
+ // bounds. We shouldn't have repeated elements besides auto traits at this point.
+ bounds.dedup();
+
+ Some(QuantifiedWhereClauses::from_iter(Interner, bounds))
+ });
+
+ if let Some(bounds) = bounds {
+ let bounds = crate::make_single_type_binders(bounds);
+ TyKind::Dyn(DynTy { bounds, lifetime: static_lifetime() }).intern(Interner)
+ } else {
- TypeNs::SelfType(impl_id) => search(
++ // FIXME: report error
++ // (additional non-auto traits, associated type rebound, or no resolved trait)
+ TyKind::Error.intern(Interner)
+ }
+ }
+
+ fn lower_impl_trait(
+ &self,
+ bounds: &[Interned<TypeBound>],
+ func: FunctionId,
+ ) -> ReturnTypeImplTrait {
+ cov_mark::hit!(lower_rpit);
+ let self_ty = TyKind::BoundVar(BoundVar::new(DebruijnIndex::INNERMOST, 0)).intern(Interner);
+ let predicates = self.with_shifted_in(DebruijnIndex::ONE, |ctx| {
+ let mut predicates: Vec<_> = bounds
+ .iter()
+ .flat_map(|b| ctx.lower_type_bound(b, self_ty.clone(), false))
+ .collect();
+
+ if !ctx.unsized_types.borrow().contains(&self_ty) {
+ let krate = func.lookup(ctx.db.upcast()).module(ctx.db.upcast()).krate();
+ let sized_trait = ctx
+ .db
+ .lang_item(krate, SmolStr::new_inline("sized"))
+ .and_then(|lang_item| lang_item.as_trait().map(to_chalk_trait_id));
+ let sized_clause = sized_trait.map(|trait_id| {
+ let clause = WhereClause::Implemented(TraitRef {
+ trait_id,
+ substitution: Substitution::from1(Interner, self_ty.clone()),
+ });
+ crate::wrap_empty_binders(clause)
+ });
+ predicates.extend(sized_clause.into_iter());
+ predicates.shrink_to_fit();
+ }
+ predicates
+ });
+ ReturnTypeImplTrait { bounds: crate::make_single_type_binders(predicates) }
+ }
+}
+
+fn count_impl_traits(type_ref: &TypeRef) -> usize {
+ let mut count = 0;
+ type_ref.walk(&mut |type_ref| {
+ if matches!(type_ref, TypeRef::ImplTrait(_)) {
+ count += 1;
+ }
+ });
+ count
+}
+
+/// Build the signature of a callable item (function, struct or enum variant).
+pub(crate) fn callable_item_sig(db: &dyn HirDatabase, def: CallableDefId) -> PolyFnSig {
+ match def {
+ CallableDefId::FunctionId(f) => fn_sig_for_fn(db, f),
+ CallableDefId::StructId(s) => fn_sig_for_struct_constructor(db, s),
+ CallableDefId::EnumVariantId(e) => fn_sig_for_enum_variant_constructor(db, e),
+ }
+}
+
+pub fn associated_type_shorthand_candidates<R>(
+ db: &dyn HirDatabase,
+ def: GenericDefId,
+ res: TypeNs,
+ cb: impl FnMut(&Name, &TraitRef, TypeAliasId) -> Option<R>,
+) -> Option<R> {
+ named_associated_type_shorthand_candidates(db, def, res, None, cb)
+}
+
+fn named_associated_type_shorthand_candidates<R>(
+ db: &dyn HirDatabase,
+ // If the type parameter is defined in an impl and we're in a method, there
+ // might be additional where clauses to consider
+ def: GenericDefId,
+ res: TypeNs,
+ assoc_name: Option<Name>,
+ mut cb: impl FnMut(&Name, &TraitRef, TypeAliasId) -> Option<R>,
+) -> Option<R> {
+ let mut search = |t| {
+ for t in all_super_trait_refs(db, t) {
+ let data = db.trait_data(t.hir_trait_id());
+
+ for (name, assoc_id) in &data.items {
+ if let AssocItemId::TypeAliasId(alias) = assoc_id {
+ if let Some(result) = cb(name, &t, *alias) {
+ return Some(result);
+ }
+ }
+ }
+ }
+ None
+ };
+
+ match res {
- db.impl_trait(impl_id)?.into_value_and_skipped_binders().0,
- ),
++ TypeNs::SelfType(impl_id) => {
+ // we're _in_ the impl -- the binders get added back later. Correct,
+ // but it would be nice to make this more explicit
- let generics = generics(db.upcast(), trait_id.into());
- if generics.params.type_or_consts[param_id.local_id()].is_trait_self() {
++ let trait_ref = db.impl_trait(impl_id)?.into_value_and_skipped_binders().0;
++
++ let impl_id_as_generic_def: GenericDefId = impl_id.into();
++ if impl_id_as_generic_def != def {
++ // `trait_ref` contains `BoundVar`s bound by impl's `Binders`, but here we need
++ // `BoundVar`s from `def`'s point of view.
++ // FIXME: A `HirDatabase` query may be handy if this process is needed in more
++ // places. It'd be almost identical as `impl_trait_query` where `resolver` would be
++ // of `def` instead of `impl_id`.
++ let starting_idx = generics(db.upcast(), def).len_self();
++ let subst = TyBuilder::subst_for_def(db, impl_id, None)
++ .fill_with_bound_vars(DebruijnIndex::INNERMOST, starting_idx)
++ .build();
++ let trait_ref = subst.apply(trait_ref, Interner);
++ search(trait_ref)
++ } else {
++ search(trait_ref)
++ }
++ }
+ TypeNs::GenericParam(param_id) => {
+ let predicates = db.generic_predicates_for_param(def, param_id.into(), assoc_name);
+ let res = predicates.iter().find_map(|pred| match pred.skip_binders().skip_binders() {
+ // FIXME: how to correctly handle higher-ranked bounds here?
+ WhereClause::Implemented(tr) => search(
+ tr.clone()
+ .shifted_out_to(Interner, DebruijnIndex::ONE)
+ .expect("FIXME unexpected higher-ranked trait bound"),
+ ),
+ _ => None,
+ });
+ if let Some(_) = res {
+ return res;
+ }
+ // Handle `Self::Type` referring to own associated type in trait definitions
+ if let GenericDefId::TraitId(trait_id) = param_id.parent() {
- .fill_with_bound_vars(DebruijnIndex::INNERMOST, 0)
++ let trait_generics = generics(db.upcast(), trait_id.into());
++ if trait_generics.params.type_or_consts[param_id.local_id()].is_trait_self() {
++ let def_generics = generics(db.upcast(), def);
++ let starting_idx = match def {
++ GenericDefId::TraitId(_) => 0,
++ // `def` is an item within trait. We need to substitute `BoundVar`s but
++ // remember that they are for parent (i.e. trait) generic params so they
++ // come after our own params.
++ _ => def_generics.len_self(),
++ };
+ let trait_ref = TyBuilder::trait_ref(db, trait_id)
- return crate::make_binders_with_count(db, idx, &generic_params, val);
++ .fill_with_bound_vars(DebruijnIndex::INNERMOST, starting_idx)
+ .build();
+ return search(trait_ref);
+ }
+ }
+ None
+ }
+ _ => None,
+ }
+}
+
+/// Build the type of all specific fields of a struct or enum variant.
+pub(crate) fn field_types_query(
+ db: &dyn HirDatabase,
+ variant_id: VariantId,
+) -> Arc<ArenaMap<LocalFieldId, Binders<Ty>>> {
+ let var_data = variant_id.variant_data(db.upcast());
+ let (resolver, def): (_, GenericDefId) = match variant_id {
+ VariantId::StructId(it) => (it.resolver(db.upcast()), it.into()),
+ VariantId::UnionId(it) => (it.resolver(db.upcast()), it.into()),
+ VariantId::EnumVariantId(it) => (it.parent.resolver(db.upcast()), it.parent.into()),
+ };
+ let generics = generics(db.upcast(), def);
+ let mut res = ArenaMap::default();
+ let ctx =
+ TyLoweringContext::new(db, &resolver).with_type_param_mode(ParamLoweringMode::Variable);
+ for (field_id, field_data) in var_data.fields().iter() {
+ res.insert(field_id, make_binders(db, &generics, ctx.lower_ty(&field_data.type_ref)));
+ }
+ Arc::new(res)
+}
+
+/// This query exists only to be used when resolving short-hand associated types
+/// like `T::Item`.
+///
+/// See the analogous query in rustc and its comment:
+/// <https://github.com/rust-lang/rust/blob/9150f844e2624eb013ec78ca08c1d416e6644026/src/librustc_typeck/astconv.rs#L46>
+/// This is a query mostly to handle cycles somewhat gracefully; e.g. the
+/// following bounds are disallowed: `T: Foo<U::Item>, U: Foo<T::Item>`, but
+/// these are fine: `T: Foo<U::Item>, U: Foo<()>`.
+pub(crate) fn generic_predicates_for_param_query(
+ db: &dyn HirDatabase,
+ def: GenericDefId,
+ param_id: TypeOrConstParamId,
+ assoc_name: Option<Name>,
+) -> Arc<[Binders<QuantifiedWhereClause>]> {
+ let resolver = def.resolver(db.upcast());
+ let ctx =
+ TyLoweringContext::new(db, &resolver).with_type_param_mode(ParamLoweringMode::Variable);
+ let generics = generics(db.upcast(), def);
+ let mut predicates: Vec<_> = resolver
+ .where_predicates_in_scope()
+ // we have to filter out all other predicates *first*, before attempting to lower them
+ .filter(|pred| match pred {
+ WherePredicate::ForLifetime { target, bound, .. }
+ | WherePredicate::TypeBound { target, bound, .. } => {
+ match target {
+ WherePredicateTypeTarget::TypeRef(type_ref) => {
+ if ctx.lower_ty_only_param(type_ref) != Some(param_id) {
+ return false;
+ }
+ }
+ &WherePredicateTypeTarget::TypeOrConstParam(local_id) => {
+ let target_id = TypeOrConstParamId { parent: def, local_id };
+ if target_id != param_id {
+ return false;
+ }
+ }
+ };
+
+ match &**bound {
+ TypeBound::ForLifetime(_, path) | TypeBound::Path(path, _) => {
+ // Only lower the bound if the trait could possibly define the associated
+ // type we're looking for.
+
+ let assoc_name = match &assoc_name {
+ Some(it) => it,
+ None => return true,
+ };
+ let tr = match resolver
+ .resolve_path_in_type_ns_fully(db.upcast(), path.mod_path())
+ {
+ Some(TypeNs::TraitId(tr)) => tr,
+ _ => return false,
+ };
+
+ all_super_traits(db.upcast(), tr).iter().any(|tr| {
+ db.trait_data(*tr).items.iter().any(|(name, item)| {
+ matches!(item, AssocItemId::TypeAliasId(_)) && name == assoc_name
+ })
+ })
+ }
+ TypeBound::Lifetime(_) | TypeBound::Error => false,
+ }
+ }
+ WherePredicate::Lifetime { .. } => false,
+ })
+ .flat_map(|pred| {
+ ctx.lower_where_predicate(pred, true).map(|p| make_binders(db, &generics, p))
+ })
+ .collect();
+
+ let subst = generics.bound_vars_subst(db, DebruijnIndex::INNERMOST);
+ let explicitly_unsized_tys = ctx.unsized_types.into_inner();
+ let implicitly_sized_predicates =
+ implicitly_sized_clauses(db, param_id.parent, &explicitly_unsized_tys, &subst, &resolver)
+ .map(|p| make_binders(db, &generics, crate::wrap_empty_binders(p)));
+ predicates.extend(implicitly_sized_predicates);
+ predicates.into()
+}
+
+pub(crate) fn generic_predicates_for_param_recover(
+ _db: &dyn HirDatabase,
+ _cycle: &[String],
+ _def: &GenericDefId,
+ _param_id: &TypeOrConstParamId,
+ _assoc_name: &Option<Name>,
+) -> Arc<[Binders<QuantifiedWhereClause>]> {
+ Arc::new([])
+}
+
+pub(crate) fn trait_environment_query(
+ db: &dyn HirDatabase,
+ def: GenericDefId,
+) -> Arc<TraitEnvironment> {
+ let resolver = def.resolver(db.upcast());
+ let ctx =
+ TyLoweringContext::new(db, &resolver).with_type_param_mode(ParamLoweringMode::Placeholder);
+ let mut traits_in_scope = Vec::new();
+ let mut clauses = Vec::new();
+ for pred in resolver.where_predicates_in_scope() {
+ for pred in ctx.lower_where_predicate(pred, false) {
+ if let WhereClause::Implemented(tr) = &pred.skip_binders() {
+ traits_in_scope.push((tr.self_type_parameter(Interner).clone(), tr.hir_trait_id()));
+ }
+ let program_clause: chalk_ir::ProgramClause<Interner> = pred.cast(Interner);
+ clauses.push(program_clause.into_from_env_clause(Interner));
+ }
+ }
+
+ let container: Option<ItemContainerId> = match def {
+ // FIXME: is there a function for this?
+ GenericDefId::FunctionId(f) => Some(f.lookup(db.upcast()).container),
+ GenericDefId::AdtId(_) => None,
+ GenericDefId::TraitId(_) => None,
+ GenericDefId::TypeAliasId(t) => Some(t.lookup(db.upcast()).container),
+ GenericDefId::ImplId(_) => None,
+ GenericDefId::EnumVariantId(_) => None,
+ GenericDefId::ConstId(c) => Some(c.lookup(db.upcast()).container),
+ };
+ if let Some(ItemContainerId::TraitId(trait_id)) = container {
+ // add `Self: Trait<T1, T2, ...>` to the environment in trait
+ // function default implementations (and speculative code
+ // inside consts or type aliases)
+ cov_mark::hit!(trait_self_implements_self);
+ let substs = TyBuilder::placeholder_subst(db, trait_id);
+ let trait_ref = TraitRef { trait_id: to_chalk_trait_id(trait_id), substitution: substs };
+ let pred = WhereClause::Implemented(trait_ref);
+ let program_clause: chalk_ir::ProgramClause<Interner> = pred.cast(Interner);
+ clauses.push(program_clause.into_from_env_clause(Interner));
+ }
+
+ let subst = generics(db.upcast(), def).placeholder_subst(db);
+ let explicitly_unsized_tys = ctx.unsized_types.into_inner();
+ let implicitly_sized_clauses =
+ implicitly_sized_clauses(db, def, &explicitly_unsized_tys, &subst, &resolver).map(|pred| {
+ let program_clause: chalk_ir::ProgramClause<Interner> = pred.cast(Interner);
+ program_clause.into_from_env_clause(Interner)
+ });
+ clauses.extend(implicitly_sized_clauses);
+
+ let krate = def.module(db.upcast()).krate();
+
+ let env = chalk_ir::Environment::new(Interner).add_clauses(Interner, clauses);
+
+ Arc::new(TraitEnvironment { krate, traits_from_clauses: traits_in_scope, env })
+}
+
+/// Resolve the where clause(s) of an item with generics.
+pub(crate) fn generic_predicates_query(
+ db: &dyn HirDatabase,
+ def: GenericDefId,
+) -> Arc<[Binders<QuantifiedWhereClause>]> {
+ let resolver = def.resolver(db.upcast());
+ let ctx =
+ TyLoweringContext::new(db, &resolver).with_type_param_mode(ParamLoweringMode::Variable);
+ let generics = generics(db.upcast(), def);
+
+ let mut predicates = resolver
+ .where_predicates_in_scope()
+ .flat_map(|pred| {
+ ctx.lower_where_predicate(pred, false).map(|p| make_binders(db, &generics, p))
+ })
+ .collect::<Vec<_>>();
+
+ let subst = generics.bound_vars_subst(db, DebruijnIndex::INNERMOST);
+ let explicitly_unsized_tys = ctx.unsized_types.into_inner();
+ let implicitly_sized_predicates =
+ implicitly_sized_clauses(db, def, &explicitly_unsized_tys, &subst, &resolver)
+ .map(|p| make_binders(db, &generics, crate::wrap_empty_binders(p)));
+ predicates.extend(implicitly_sized_predicates);
+ predicates.into()
+}
+
+/// Generate implicit `: Sized` predicates for all generics that has no `?Sized` bound.
+/// Exception is Self of a trait def.
+fn implicitly_sized_clauses<'a>(
+ db: &dyn HirDatabase,
+ def: GenericDefId,
+ explicitly_unsized_tys: &'a FxHashSet<Ty>,
+ substitution: &'a Substitution,
+ resolver: &Resolver,
+) -> impl Iterator<Item = WhereClause> + 'a {
+ let is_trait_def = matches!(def, GenericDefId::TraitId(..));
+ let generic_args = &substitution.as_slice(Interner)[is_trait_def as usize..];
+ let sized_trait = db
+ .lang_item(resolver.krate(), SmolStr::new_inline("sized"))
+ .and_then(|lang_item| lang_item.as_trait().map(to_chalk_trait_id));
+
+ sized_trait.into_iter().flat_map(move |sized_trait| {
+ let implicitly_sized_tys = generic_args
+ .iter()
+ .filter_map(|generic_arg| generic_arg.ty(Interner))
+ .filter(move |&self_ty| !explicitly_unsized_tys.contains(self_ty));
+ implicitly_sized_tys.map(move |self_ty| {
+ WhereClause::Implemented(TraitRef {
+ trait_id: sized_trait,
+ substitution: Substitution::from1(Interner, self_ty.clone()),
+ })
+ })
+ })
+}
+
+/// Resolve the default type params from generics
+pub(crate) fn generic_defaults_query(
+ db: &dyn HirDatabase,
+ def: GenericDefId,
+) -> Arc<[Binders<chalk_ir::GenericArg<Interner>>]> {
+ let resolver = def.resolver(db.upcast());
+ let ctx =
+ TyLoweringContext::new(db, &resolver).with_type_param_mode(ParamLoweringMode::Variable);
+ let generic_params = generics(db.upcast(), def);
++ let parent_start_idx = generic_params.len_self();
+
+ let defaults = generic_params
+ .iter()
+ .enumerate()
+ .map(|(idx, (id, p))| {
+ let p = match p {
+ TypeOrConstParamData::TypeParamData(p) => p,
+ TypeOrConstParamData::ConstParamData(_) => {
+ // FIXME: implement const generic defaults
+ let val = unknown_const_as_generic(
+ db.const_param_ty(ConstParamId::from_unchecked(id)),
+ );
- // type variable default referring to parameter coming
- // after it. This is forbidden (FIXME: report
- // diagnostic)
- ty = fallback_bound_vars(ty, idx);
- let val = GenericArgData::Ty(ty).intern(Interner);
- crate::make_binders_with_count(db, idx, &generic_params, val)
++ return make_binders(db, &generic_params, val);
+ }
+ };
+ let mut ty =
+ p.default.as_ref().map_or(TyKind::Error.intern(Interner), |t| ctx.lower_ty(t));
+
+ // Each default can only refer to previous parameters.
- .enumerate()
- .map(|(count, id)| {
++ // Type variable default referring to parameter coming
++ // after it is forbidden (FIXME: report diagnostic)
++ ty = fallback_bound_vars(ty, idx, parent_start_idx);
++ crate::make_binders(db, &generic_params, ty.cast(Interner))
+ })
+ .collect();
+
+ defaults
+}
+
+pub(crate) fn generic_defaults_recover(
+ db: &dyn HirDatabase,
+ _cycle: &[String],
+ def: &GenericDefId,
+) -> Arc<[Binders<crate::GenericArg>]> {
+ let generic_params = generics(db.upcast(), *def);
+ // FIXME: this code is not covered in tests.
+ // we still need one default per parameter
+ let defaults = generic_params
+ .iter_id()
- crate::make_binders_with_count(db, count, &generic_params, val)
++ .map(|id| {
+ let val = match id {
+ itertools::Either::Left(_) => {
+ GenericArgData::Ty(TyKind::Error.intern(Interner)).intern(Interner)
+ }
+ itertools::Either::Right(id) => unknown_const_as_generic(db.const_param_ty(id)),
+ };
- /// This replaces any 'free' Bound vars in `s` (i.e. those with indices past
- /// num_vars_to_keep) by `TyKind::Unknown`.
++ crate::make_binders(db, &generic_params, val)
+ })
+ .collect();
+
+ defaults
+}
+
+fn fn_sig_for_fn(db: &dyn HirDatabase, def: FunctionId) -> PolyFnSig {
+ let data = db.function_data(def);
+ let resolver = def.resolver(db.upcast());
+ let ctx_params = TyLoweringContext::new(db, &resolver)
+ .with_impl_trait_mode(ImplTraitLoweringMode::Variable)
+ .with_type_param_mode(ParamLoweringMode::Variable);
+ let params = data.params.iter().map(|(_, tr)| ctx_params.lower_ty(tr)).collect::<Vec<_>>();
+ let ctx_ret = TyLoweringContext::new(db, &resolver)
+ .with_impl_trait_mode(ImplTraitLoweringMode::Opaque)
+ .with_type_param_mode(ParamLoweringMode::Variable);
+ let ret = ctx_ret.lower_ty(&data.ret_type);
+ let generics = generics(db.upcast(), def.into());
+ let sig = CallableSig::from_params_and_return(params, ret, data.is_varargs());
+ make_binders(db, &generics, sig)
+}
+
+/// Build the declared type of a function. This should not need to look at the
+/// function body.
+fn type_for_fn(db: &dyn HirDatabase, def: FunctionId) -> Binders<Ty> {
+ let generics = generics(db.upcast(), def.into());
+ let substs = generics.bound_vars_subst(db, DebruijnIndex::INNERMOST);
+ make_binders(
+ db,
+ &generics,
+ TyKind::FnDef(CallableDefId::FunctionId(def).to_chalk(db), substs).intern(Interner),
+ )
+}
+
+/// Build the declared type of a const.
+fn type_for_const(db: &dyn HirDatabase, def: ConstId) -> Binders<Ty> {
+ let data = db.const_data(def);
+ let generics = generics(db.upcast(), def.into());
+ let resolver = def.resolver(db.upcast());
+ let ctx =
+ TyLoweringContext::new(db, &resolver).with_type_param_mode(ParamLoweringMode::Variable);
+
+ make_binders(db, &generics, ctx.lower_ty(&data.type_ref))
+}
+
+/// Build the declared type of a static.
+fn type_for_static(db: &dyn HirDatabase, def: StaticId) -> Binders<Ty> {
+ let data = db.static_data(def);
+ let resolver = def.resolver(db.upcast());
+ let ctx = TyLoweringContext::new(db, &resolver);
+
+ Binders::empty(Interner, ctx.lower_ty(&data.type_ref))
+}
+
+fn fn_sig_for_struct_constructor(db: &dyn HirDatabase, def: StructId) -> PolyFnSig {
+ let struct_data = db.struct_data(def);
+ let fields = struct_data.variant_data.fields();
+ let resolver = def.resolver(db.upcast());
+ let ctx =
+ TyLoweringContext::new(db, &resolver).with_type_param_mode(ParamLoweringMode::Variable);
+ let params = fields.iter().map(|(_, field)| ctx.lower_ty(&field.type_ref)).collect::<Vec<_>>();
+ let (ret, binders) = type_for_adt(db, def.into()).into_value_and_skipped_binders();
+ Binders::new(binders, CallableSig::from_params_and_return(params, ret, false))
+}
+
+/// Build the type of a tuple struct constructor.
+fn type_for_struct_constructor(db: &dyn HirDatabase, def: StructId) -> Binders<Ty> {
+ let struct_data = db.struct_data(def);
+ if let StructKind::Unit = struct_data.variant_data.kind() {
+ return type_for_adt(db, def.into());
+ }
+ let generics = generics(db.upcast(), def.into());
+ let substs = generics.bound_vars_subst(db, DebruijnIndex::INNERMOST);
+ make_binders(
+ db,
+ &generics,
+ TyKind::FnDef(CallableDefId::StructId(def).to_chalk(db), substs).intern(Interner),
+ )
+}
+
+fn fn_sig_for_enum_variant_constructor(db: &dyn HirDatabase, def: EnumVariantId) -> PolyFnSig {
+ let enum_data = db.enum_data(def.parent);
+ let var_data = &enum_data.variants[def.local_id];
+ let fields = var_data.variant_data.fields();
+ let resolver = def.parent.resolver(db.upcast());
+ let ctx =
+ TyLoweringContext::new(db, &resolver).with_type_param_mode(ParamLoweringMode::Variable);
+ let params = fields.iter().map(|(_, field)| ctx.lower_ty(&field.type_ref)).collect::<Vec<_>>();
+ let (ret, binders) = type_for_adt(db, def.parent.into()).into_value_and_skipped_binders();
+ Binders::new(binders, CallableSig::from_params_and_return(params, ret, false))
+}
+
+/// Build the type of a tuple enum variant constructor.
+fn type_for_enum_variant_constructor(db: &dyn HirDatabase, def: EnumVariantId) -> Binders<Ty> {
+ let enum_data = db.enum_data(def.parent);
+ let var_data = &enum_data.variants[def.local_id].variant_data;
+ if let StructKind::Unit = var_data.kind() {
+ return type_for_adt(db, def.parent.into());
+ }
+ let generics = generics(db.upcast(), def.parent.into());
+ let substs = generics.bound_vars_subst(db, DebruijnIndex::INNERMOST);
+ make_binders(
+ db,
+ &generics,
+ TyKind::FnDef(CallableDefId::EnumVariantId(def).to_chalk(db), substs).intern(Interner),
+ )
+}
+
+fn type_for_adt(db: &dyn HirDatabase, adt: AdtId) -> Binders<Ty> {
+ let generics = generics(db.upcast(), adt.into());
+ let subst = generics.bound_vars_subst(db, DebruijnIndex::INNERMOST);
+ let ty = TyKind::Adt(crate::AdtId(adt), subst).intern(Interner);
+ make_binders(db, &generics, ty)
+}
+
+fn type_for_type_alias(db: &dyn HirDatabase, t: TypeAliasId) -> Binders<Ty> {
+ let generics = generics(db.upcast(), t.into());
+ let resolver = t.resolver(db.upcast());
+ let ctx =
+ TyLoweringContext::new(db, &resolver).with_type_param_mode(ParamLoweringMode::Variable);
+ if db.type_alias_data(t).is_extern {
+ Binders::empty(Interner, TyKind::Foreign(crate::to_foreign_def_id(t)).intern(Interner))
+ } else {
+ let type_ref = &db.type_alias_data(t).type_ref;
+ let inner = ctx.lower_ty(type_ref.as_deref().unwrap_or(&TypeRef::Error));
+ make_binders(db, &generics, inner)
+ }
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub enum CallableDefId {
+ FunctionId(FunctionId),
+ StructId(StructId),
+ EnumVariantId(EnumVariantId),
+}
+impl_from!(FunctionId, StructId, EnumVariantId for CallableDefId);
+
+impl CallableDefId {
+ pub fn krate(self, db: &dyn HirDatabase) -> CrateId {
+ let db = db.upcast();
+ match self {
+ CallableDefId::FunctionId(f) => f.lookup(db).module(db),
+ CallableDefId::StructId(s) => s.lookup(db).container,
+ CallableDefId::EnumVariantId(e) => e.parent.lookup(db).container,
+ }
+ .krate()
+ }
+}
+
+impl From<CallableDefId> for GenericDefId {
+ fn from(def: CallableDefId) -> GenericDefId {
+ match def {
+ CallableDefId::FunctionId(f) => f.into(),
+ CallableDefId::StructId(s) => s.into(),
+ CallableDefId::EnumVariantId(e) => e.into(),
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum TyDefId {
+ BuiltinType(BuiltinType),
+ AdtId(AdtId),
+ TypeAliasId(TypeAliasId),
+}
+impl_from!(BuiltinType, AdtId(StructId, EnumId, UnionId), TypeAliasId for TyDefId);
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum ValueTyDefId {
+ FunctionId(FunctionId),
+ StructId(StructId),
+ UnionId(UnionId),
+ EnumVariantId(EnumVariantId),
+ ConstId(ConstId),
+ StaticId(StaticId),
+}
+impl_from!(FunctionId, StructId, UnionId, EnumVariantId, ConstId, StaticId for ValueTyDefId);
+
++impl ValueTyDefId {
++ pub(crate) fn to_generic_def_id(self) -> Option<GenericDefId> {
++ match self {
++ Self::FunctionId(id) => Some(id.into()),
++ Self::StructId(id) => Some(id.into()),
++ Self::UnionId(id) => Some(id.into()),
++ Self::EnumVariantId(var) => Some(var.into()),
++ Self::ConstId(id) => Some(id.into()),
++ Self::StaticId(_) => None,
++ }
++ }
++}
++
+/// Build the declared type of an item. This depends on the namespace; e.g. for
+/// `struct Foo(usize)`, we have two types: The type of the struct itself, and
+/// the constructor function `(usize) -> Foo` which lives in the values
+/// namespace.
+pub(crate) fn ty_query(db: &dyn HirDatabase, def: TyDefId) -> Binders<Ty> {
+ match def {
+ TyDefId::BuiltinType(it) => Binders::empty(Interner, TyBuilder::builtin(it)),
+ TyDefId::AdtId(it) => type_for_adt(db, it),
+ TyDefId::TypeAliasId(it) => type_for_type_alias(db, it),
+ }
+}
+
+pub(crate) fn ty_recover(db: &dyn HirDatabase, _cycle: &[String], def: &TyDefId) -> Binders<Ty> {
+ let generics = match *def {
+ TyDefId::BuiltinType(_) => return Binders::empty(Interner, TyKind::Error.intern(Interner)),
+ TyDefId::AdtId(it) => generics(db.upcast(), it.into()),
+ TyDefId::TypeAliasId(it) => generics(db.upcast(), it.into()),
+ };
+ make_binders(db, &generics, TyKind::Error.intern(Interner))
+}
+
+pub(crate) fn value_ty_query(db: &dyn HirDatabase, def: ValueTyDefId) -> Binders<Ty> {
+ match def {
+ ValueTyDefId::FunctionId(it) => type_for_fn(db, it),
+ ValueTyDefId::StructId(it) => type_for_struct_constructor(db, it),
+ ValueTyDefId::UnionId(it) => type_for_adt(db, it.into()),
+ ValueTyDefId::EnumVariantId(it) => type_for_enum_variant_constructor(db, it),
+ ValueTyDefId::ConstId(it) => type_for_const(db, it),
+ ValueTyDefId::StaticId(it) => type_for_static(db, it),
+ }
+}
+
+pub(crate) fn impl_self_ty_query(db: &dyn HirDatabase, impl_id: ImplId) -> Binders<Ty> {
+ let impl_loc = impl_id.lookup(db.upcast());
+ let impl_data = db.impl_data(impl_id);
+ let resolver = impl_id.resolver(db.upcast());
+ let _cx = stdx::panic_context::enter(format!(
+ "impl_self_ty_query({:?} -> {:?} -> {:?})",
+ impl_id, impl_loc, impl_data
+ ));
+ let generics = generics(db.upcast(), impl_id.into());
+ let ctx =
+ TyLoweringContext::new(db, &resolver).with_type_param_mode(ParamLoweringMode::Variable);
+ make_binders(db, &generics, ctx.lower_ty(&impl_data.self_ty))
+}
+
+// returns None if def is a type arg
+pub(crate) fn const_param_ty_query(db: &dyn HirDatabase, def: ConstParamId) -> Ty {
+ let parent_data = db.generic_params(def.parent());
+ let data = &parent_data.type_or_consts[def.local_id()];
+ let resolver = def.parent().resolver(db.upcast());
+ let ctx = TyLoweringContext::new(db, &resolver);
+ match data {
+ TypeOrConstParamData::TypeParamData(_) => {
+ never!();
+ Ty::new(Interner, TyKind::Error)
+ }
+ TypeOrConstParamData::ConstParamData(d) => ctx.lower_ty(&d.ty),
+ }
+}
+
+pub(crate) fn impl_self_ty_recover(
+ db: &dyn HirDatabase,
+ _cycle: &[String],
+ impl_id: &ImplId,
+) -> Binders<Ty> {
+ let generics = generics(db.upcast(), (*impl_id).into());
+ make_binders(db, &generics, TyKind::Error.intern(Interner))
+}
+
+pub(crate) fn impl_trait_query(db: &dyn HirDatabase, impl_id: ImplId) -> Option<Binders<TraitRef>> {
+ let impl_loc = impl_id.lookup(db.upcast());
+ let impl_data = db.impl_data(impl_id);
+ let resolver = impl_id.resolver(db.upcast());
+ let _cx = stdx::panic_context::enter(format!(
+ "impl_trait_query({:?} -> {:?} -> {:?})",
+ impl_id, impl_loc, impl_data
+ ));
+ let ctx =
+ TyLoweringContext::new(db, &resolver).with_type_param_mode(ParamLoweringMode::Variable);
+ let (self_ty, binders) = db.impl_self_ty(impl_id).into_value_and_skipped_binders();
+ let target_trait = impl_data.target_trait.as_ref()?;
+ Some(Binders::new(binders, ctx.lower_trait_ref(target_trait, Some(self_ty))?))
+}
+
+pub(crate) fn return_type_impl_traits(
+ db: &dyn HirDatabase,
+ def: hir_def::FunctionId,
+) -> Option<Arc<Binders<ReturnTypeImplTraits>>> {
+ // FIXME unify with fn_sig_for_fn instead of doing lowering twice, maybe
+ let data = db.function_data(def);
+ let resolver = def.resolver(db.upcast());
+ let ctx_ret = TyLoweringContext::new(db, &resolver)
+ .with_impl_trait_mode(ImplTraitLoweringMode::Opaque)
+ .with_type_param_mode(ParamLoweringMode::Variable);
+ let _ret = (&ctx_ret).lower_ty(&data.ret_type);
+ let generics = generics(db.upcast(), def.into());
+ let return_type_impl_traits =
+ ReturnTypeImplTraits { impl_traits: ctx_ret.opaque_type_data.into_inner() };
+ if return_type_impl_traits.impl_traits.is_empty() {
+ None
+ } else {
+ Some(Arc::new(make_binders(db, &generics, return_type_impl_traits)))
+ }
+}
+
+pub(crate) fn lower_to_chalk_mutability(m: hir_def::type_ref::Mutability) -> Mutability {
+ match m {
+ hir_def::type_ref::Mutability::Shared => Mutability::Not,
+ hir_def::type_ref::Mutability::Mut => Mutability::Mut,
+ }
+}
+
+/// Checks if the provided generic arg matches its expected kind, then lower them via
+/// provided closures. Use unknown if there was kind mismatch.
+///
+/// Returns `Some` of the lowered generic arg. `None` if the provided arg is a lifetime.
+pub(crate) fn generic_arg_to_chalk<'a, T>(
+ db: &dyn HirDatabase,
+ kind_id: Either<TypeParamId, ConstParamId>,
+ arg: &'a GenericArg,
+ this: &mut T,
+ for_type: impl FnOnce(&mut T, &TypeRef) -> Ty + 'a,
+ for_const: impl FnOnce(&mut T, &ConstScalarOrPath, Ty) -> Const + 'a,
+) -> Option<crate::GenericArg> {
+ let kind = match kind_id {
+ Either::Left(_) => ParamKind::Type,
+ Either::Right(id) => {
+ let ty = db.const_param_ty(id);
+ ParamKind::Const(ty)
+ }
+ };
+ Some(match (arg, kind) {
+ (GenericArg::Type(type_ref), ParamKind::Type) => {
+ let ty = for_type(this, type_ref);
+ GenericArgData::Ty(ty).intern(Interner)
+ }
+ (GenericArg::Const(c), ParamKind::Const(c_ty)) => {
+ GenericArgData::Const(for_const(this, c, c_ty)).intern(Interner)
+ }
+ (GenericArg::Const(_), ParamKind::Type) => {
+ GenericArgData::Ty(TyKind::Error.intern(Interner)).intern(Interner)
+ }
+ (GenericArg::Type(t), ParamKind::Const(c_ty)) => {
+ // We want to recover simple idents, which parser detects them
+ // as types. Maybe here is not the best place to do it, but
+ // it works.
+ if let TypeRef::Path(p) = t {
+ let p = p.mod_path();
+ if p.kind == PathKind::Plain {
+ if let [n] = p.segments() {
+ let c = ConstScalarOrPath::Path(n.clone());
+ return Some(
+ GenericArgData::Const(for_const(this, &c, c_ty)).intern(Interner),
+ );
+ }
+ }
+ }
+ unknown_const_as_generic(c_ty)
+ }
+ (GenericArg::Lifetime(_), _) => return None,
+ })
+}
+
+pub(crate) fn const_or_path_to_chalk(
+ db: &dyn HirDatabase,
+ resolver: &Resolver,
+ expected_ty: Ty,
+ value: &ConstScalarOrPath,
+ mode: ParamLoweringMode,
+ args: impl FnOnce() -> Generics,
+ debruijn: DebruijnIndex,
+) -> Const {
+ match value {
+ ConstScalarOrPath::Scalar(s) => intern_const_scalar(s.clone(), expected_ty),
+ ConstScalarOrPath::Path(n) => {
+ let path = ModPath::from_segments(PathKind::Plain, Some(n.clone()));
+ path_to_const(db, resolver, &path, mode, args, debruijn)
+ .unwrap_or_else(|| unknown_const(expected_ty))
+ }
+ }
+}
+
- num_vars_to_keep: usize,
++/// Replaces any 'free' `BoundVar`s in `s` by `TyKind::Error` from the perspective of generic
++/// parameter whose index is `param_index`. A `BoundVar` is free when it is or (syntactically)
++/// appears after the generic parameter of `param_index`.
+fn fallback_bound_vars<T: TypeFoldable<Interner> + HasInterner<Interner = Interner>>(
+ s: T,
- if bound.index >= num_vars_to_keep && bound.debruijn == DebruijnIndex::INNERMOST {
- TyKind::Error.intern(Interner)
- } else {
++ param_index: usize,
++ parent_start: usize,
+) -> T {
++ // Keep in mind that parent generic parameters, if any, come *after* those of the item in
++ // question. In the diagrams below, `c*` and `p*` represent generic parameters of the item and
++ // its parent respectively.
++ let is_allowed = |index| {
++ if param_index < parent_start {
++ // The parameter of `param_index` is one from the item in question. Any parent generic
++ // parameters or the item's generic parameters that come before `param_index` is
++ // allowed.
++ // [c1, .., cj, .., ck, p1, .., pl] where cj is `param_index`
++ // ^^^^^^ ^^^^^^^^^^ these are allowed
++ !(param_index..parent_start).contains(&index)
++ } else {
++ // The parameter of `param_index` is one from the parent generics. Only parent generic
++ // parameters that come before `param_index` are allowed.
++ // [c1, .., ck, p1, .., pj, .., pl] where pj is `param_index`
++ // ^^^^^^ these are allowed
++ (parent_start..param_index).contains(&index)
++ }
++ };
++
+ crate::fold_free_vars(
+ s,
+ |bound, binders| {
- if bound.index >= num_vars_to_keep && bound.debruijn == DebruijnIndex::INNERMOST {
- unknown_const(ty.clone())
- } else {
++ if bound.index_if_innermost().map_or(true, is_allowed) {
+ bound.shifted_in_from(binders).to_ty(Interner)
++ } else {
++ TyKind::Error.intern(Interner)
+ }
+ },
+ |ty, bound, binders| {
++ if bound.index_if_innermost().map_or(true, is_allowed) {
+ bound.shifted_in_from(binders).to_const(Interner, ty)
++ } else {
++ unknown_const(ty.clone())
+ }
+ },
+ )
+}
--- /dev/null
+//! This module contains the implementations of the `ToChalk` trait, which
+//! handles conversion between our data types and their corresponding types in
+//! Chalk (in both directions); plus some helper functions for more specialized
+//! conversions.
+
+use chalk_solve::rust_ir;
+
+use base_db::salsa::{self, InternKey};
+use hir_def::{LifetimeParamId, TraitId, TypeAliasId, TypeOrConstParamId};
+
+use crate::{
+ chalk_db, db::HirDatabase, AssocTypeId, CallableDefId, ChalkTraitId, FnDefId, ForeignDefId,
+ Interner, OpaqueTyId, PlaceholderIndex,
+};
+
+pub(crate) trait ToChalk {
+ type Chalk;
+ fn to_chalk(self, db: &dyn HirDatabase) -> Self::Chalk;
+ fn from_chalk(db: &dyn HirDatabase, chalk: Self::Chalk) -> Self;
+}
+
+pub(crate) fn from_chalk<T, ChalkT>(db: &dyn HirDatabase, chalk: ChalkT) -> T
+where
+ T: ToChalk<Chalk = ChalkT>,
+{
+ T::from_chalk(db, chalk)
+}
+
+impl ToChalk for hir_def::ImplId {
+ type Chalk = chalk_db::ImplId;
+
+ fn to_chalk(self, _db: &dyn HirDatabase) -> chalk_db::ImplId {
+ chalk_ir::ImplId(self.as_intern_id())
+ }
+
+ fn from_chalk(_db: &dyn HirDatabase, impl_id: chalk_db::ImplId) -> hir_def::ImplId {
+ InternKey::from_intern_id(impl_id.0)
+ }
+}
+
+impl ToChalk for CallableDefId {
+ type Chalk = FnDefId;
+
+ fn to_chalk(self, db: &dyn HirDatabase) -> FnDefId {
+ db.intern_callable_def(self).into()
+ }
+
+ fn from_chalk(db: &dyn HirDatabase, fn_def_id: FnDefId) -> CallableDefId {
+ db.lookup_intern_callable_def(fn_def_id.into())
+ }
+}
+
+pub(crate) struct TypeAliasAsValue(pub(crate) TypeAliasId);
+
+impl ToChalk for TypeAliasAsValue {
+ type Chalk = chalk_db::AssociatedTyValueId;
+
+ fn to_chalk(self, _db: &dyn HirDatabase) -> chalk_db::AssociatedTyValueId {
+ rust_ir::AssociatedTyValueId(self.0.as_intern_id())
+ }
+
+ fn from_chalk(
+ _db: &dyn HirDatabase,
+ assoc_ty_value_id: chalk_db::AssociatedTyValueId,
+ ) -> TypeAliasAsValue {
+ TypeAliasAsValue(TypeAliasId::from_intern_id(assoc_ty_value_id.0))
+ }
+}
+
+impl From<FnDefId> for crate::db::InternedCallableDefId {
+ fn from(fn_def_id: FnDefId) -> Self {
+ InternKey::from_intern_id(fn_def_id.0)
+ }
+}
+
+impl From<crate::db::InternedCallableDefId> for FnDefId {
+ fn from(callable_def_id: crate::db::InternedCallableDefId) -> Self {
+ chalk_ir::FnDefId(callable_def_id.as_intern_id())
+ }
+}
+
+impl From<OpaqueTyId> for crate::db::InternedOpaqueTyId {
+ fn from(id: OpaqueTyId) -> Self {
+ InternKey::from_intern_id(id.0)
+ }
+}
+
+impl From<crate::db::InternedOpaqueTyId> for OpaqueTyId {
+ fn from(id: crate::db::InternedOpaqueTyId) -> Self {
+ chalk_ir::OpaqueTyId(id.as_intern_id())
+ }
+}
+
+impl From<chalk_ir::ClosureId<Interner>> for crate::db::InternedClosureId {
+ fn from(id: chalk_ir::ClosureId<Interner>) -> Self {
+ Self::from_intern_id(id.0)
+ }
+}
+
+impl From<crate::db::InternedClosureId> for chalk_ir::ClosureId<Interner> {
+ fn from(id: crate::db::InternedClosureId) -> Self {
+ chalk_ir::ClosureId(id.as_intern_id())
+ }
+}
+
++impl From<chalk_ir::GeneratorId<Interner>> for crate::db::InternedGeneratorId {
++ fn from(id: chalk_ir::GeneratorId<Interner>) -> Self {
++ Self::from_intern_id(id.0)
++ }
++}
++
++impl From<crate::db::InternedGeneratorId> for chalk_ir::GeneratorId<Interner> {
++ fn from(id: crate::db::InternedGeneratorId) -> Self {
++ chalk_ir::GeneratorId(id.as_intern_id())
++ }
++}
++
+pub fn to_foreign_def_id(id: TypeAliasId) -> ForeignDefId {
+ chalk_ir::ForeignDefId(salsa::InternKey::as_intern_id(&id))
+}
+
+pub fn from_foreign_def_id(id: ForeignDefId) -> TypeAliasId {
+ salsa::InternKey::from_intern_id(id.0)
+}
+
+pub fn to_assoc_type_id(id: TypeAliasId) -> AssocTypeId {
+ chalk_ir::AssocTypeId(salsa::InternKey::as_intern_id(&id))
+}
+
+pub fn from_assoc_type_id(id: AssocTypeId) -> TypeAliasId {
+ salsa::InternKey::from_intern_id(id.0)
+}
+
+pub fn from_placeholder_idx(db: &dyn HirDatabase, idx: PlaceholderIndex) -> TypeOrConstParamId {
+ assert_eq!(idx.ui, chalk_ir::UniverseIndex::ROOT);
+ let interned_id = salsa::InternKey::from_intern_id(salsa::InternId::from(idx.idx));
+ db.lookup_intern_type_or_const_param_id(interned_id)
+}
+
+pub fn to_placeholder_idx(db: &dyn HirDatabase, id: TypeOrConstParamId) -> PlaceholderIndex {
+ let interned_id = db.intern_type_or_const_param_id(id);
+ PlaceholderIndex {
+ ui: chalk_ir::UniverseIndex::ROOT,
+ idx: salsa::InternKey::as_intern_id(&interned_id).as_usize(),
+ }
+}
+
+pub fn lt_from_placeholder_idx(db: &dyn HirDatabase, idx: PlaceholderIndex) -> LifetimeParamId {
+ assert_eq!(idx.ui, chalk_ir::UniverseIndex::ROOT);
+ let interned_id = salsa::InternKey::from_intern_id(salsa::InternId::from(idx.idx));
+ db.lookup_intern_lifetime_param_id(interned_id)
+}
+
+pub fn to_chalk_trait_id(id: TraitId) -> ChalkTraitId {
+ chalk_ir::TraitId(salsa::InternKey::as_intern_id(&id))
+}
+
+pub fn from_chalk_trait_id(id: ChalkTraitId) -> TraitId {
+ salsa::InternKey::from_intern_id(id.0)
+}
--- /dev/null
- TyBuilder::subst_for_def(db, impl_).fill_with_inference_vars(table).build();
+//! This module is concerned with finding methods that a given type provides.
+//! For details about how this works in rustc, see the method lookup page in the
+//! [rustc guide](https://rust-lang.github.io/rustc-guide/method-lookup.html)
+//! and the corresponding code mostly in rustc_hir_analysis/check/method/probe.rs.
+use std::{iter, ops::ControlFlow, sync::Arc};
+
+use arrayvec::ArrayVec;
+use base_db::{CrateId, Edition};
+use chalk_ir::{cast::Cast, Mutability, UniverseIndex};
+use hir_def::{
+ data::ImplData, item_scope::ItemScope, nameres::DefMap, AssocItemId, BlockId, ConstId,
+ FunctionId, GenericDefId, HasModule, ImplId, ItemContainerId, Lookup, ModuleDefId, ModuleId,
+ TraitId,
+};
+use hir_expand::name::Name;
+use rustc_hash::{FxHashMap, FxHashSet};
+use stdx::never;
+
+use crate::{
+ autoderef::{self, AutoderefKind},
+ db::HirDatabase,
+ from_foreign_def_id,
+ infer::{unify::InferenceTable, Adjust, Adjustment, AutoBorrow, OverloadedDeref, PointerCast},
+ primitive::{FloatTy, IntTy, UintTy},
+ static_lifetime,
+ utils::all_super_traits,
+ AdtId, Canonical, CanonicalVarKinds, DebruijnIndex, ForeignDefId, InEnvironment, Interner,
+ Scalar, TraitEnvironment, TraitRefExt, Ty, TyBuilder, TyExt, TyKind,
+};
+
+/// This is used as a key for indexing impls.
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+pub enum TyFingerprint {
+ // These are lang item impls:
+ Str,
+ Slice,
+ Array,
+ Never,
+ RawPtr(Mutability),
+ Scalar(Scalar),
+ // These can have user-defined impls:
+ Adt(hir_def::AdtId),
+ Dyn(TraitId),
+ ForeignType(ForeignDefId),
+ // These only exist for trait impls
+ Unit,
+ Unnameable,
+ Function(u32),
+}
+
+impl TyFingerprint {
+ /// Creates a TyFingerprint for looking up an inherent impl. Only certain
+ /// types can have inherent impls: if we have some `struct S`, we can have
+ /// an `impl S`, but not `impl &S`. Hence, this will return `None` for
+ /// reference types and such.
+ pub fn for_inherent_impl(ty: &Ty) -> Option<TyFingerprint> {
+ let fp = match ty.kind(Interner) {
+ TyKind::Str => TyFingerprint::Str,
+ TyKind::Never => TyFingerprint::Never,
+ TyKind::Slice(..) => TyFingerprint::Slice,
+ TyKind::Array(..) => TyFingerprint::Array,
+ TyKind::Scalar(scalar) => TyFingerprint::Scalar(*scalar),
+ TyKind::Adt(AdtId(adt), _) => TyFingerprint::Adt(*adt),
+ TyKind::Raw(mutability, ..) => TyFingerprint::RawPtr(*mutability),
+ TyKind::Foreign(alias_id, ..) => TyFingerprint::ForeignType(*alias_id),
+ TyKind::Dyn(_) => ty.dyn_trait().map(TyFingerprint::Dyn)?,
+ _ => return None,
+ };
+ Some(fp)
+ }
+
+ /// Creates a TyFingerprint for looking up a trait impl.
+ pub fn for_trait_impl(ty: &Ty) -> Option<TyFingerprint> {
+ let fp = match ty.kind(Interner) {
+ TyKind::Str => TyFingerprint::Str,
+ TyKind::Never => TyFingerprint::Never,
+ TyKind::Slice(..) => TyFingerprint::Slice,
+ TyKind::Array(..) => TyFingerprint::Array,
+ TyKind::Scalar(scalar) => TyFingerprint::Scalar(*scalar),
+ TyKind::Adt(AdtId(adt), _) => TyFingerprint::Adt(*adt),
+ TyKind::Raw(mutability, ..) => TyFingerprint::RawPtr(*mutability),
+ TyKind::Foreign(alias_id, ..) => TyFingerprint::ForeignType(*alias_id),
+ TyKind::Dyn(_) => ty.dyn_trait().map(TyFingerprint::Dyn)?,
+ TyKind::Ref(_, _, ty) => return TyFingerprint::for_trait_impl(ty),
+ TyKind::Tuple(_, subst) => {
+ let first_ty = subst.interned().get(0).map(|arg| arg.assert_ty_ref(Interner));
+ match first_ty {
+ Some(ty) => return TyFingerprint::for_trait_impl(ty),
+ None => TyFingerprint::Unit,
+ }
+ }
+ TyKind::AssociatedType(_, _)
+ | TyKind::OpaqueType(_, _)
+ | TyKind::FnDef(_, _)
+ | TyKind::Closure(_, _)
+ | TyKind::Generator(..)
+ | TyKind::GeneratorWitness(..) => TyFingerprint::Unnameable,
+ TyKind::Function(fn_ptr) => {
+ TyFingerprint::Function(fn_ptr.substitution.0.len(Interner) as u32)
+ }
+ TyKind::Alias(_)
+ | TyKind::Placeholder(_)
+ | TyKind::BoundVar(_)
+ | TyKind::InferenceVar(_, _)
+ | TyKind::Error => return None,
+ };
+ Some(fp)
+ }
+}
+
+pub(crate) const ALL_INT_FPS: [TyFingerprint; 12] = [
+ TyFingerprint::Scalar(Scalar::Int(IntTy::I8)),
+ TyFingerprint::Scalar(Scalar::Int(IntTy::I16)),
+ TyFingerprint::Scalar(Scalar::Int(IntTy::I32)),
+ TyFingerprint::Scalar(Scalar::Int(IntTy::I64)),
+ TyFingerprint::Scalar(Scalar::Int(IntTy::I128)),
+ TyFingerprint::Scalar(Scalar::Int(IntTy::Isize)),
+ TyFingerprint::Scalar(Scalar::Uint(UintTy::U8)),
+ TyFingerprint::Scalar(Scalar::Uint(UintTy::U16)),
+ TyFingerprint::Scalar(Scalar::Uint(UintTy::U32)),
+ TyFingerprint::Scalar(Scalar::Uint(UintTy::U64)),
+ TyFingerprint::Scalar(Scalar::Uint(UintTy::U128)),
+ TyFingerprint::Scalar(Scalar::Uint(UintTy::Usize)),
+];
+
+pub(crate) const ALL_FLOAT_FPS: [TyFingerprint; 2] = [
+ TyFingerprint::Scalar(Scalar::Float(FloatTy::F32)),
+ TyFingerprint::Scalar(Scalar::Float(FloatTy::F64)),
+];
+
+/// Trait impls defined or available in some crate.
+#[derive(Debug, Eq, PartialEq)]
+pub struct TraitImpls {
+ // If the `Option<TyFingerprint>` is `None`, the impl may apply to any self type.
+ map: FxHashMap<TraitId, FxHashMap<Option<TyFingerprint>, Vec<ImplId>>>,
+}
+
+impl TraitImpls {
+ pub(crate) fn trait_impls_in_crate_query(db: &dyn HirDatabase, krate: CrateId) -> Arc<Self> {
+ let _p = profile::span("trait_impls_in_crate_query").detail(|| format!("{krate:?}"));
+ let mut impls = Self { map: FxHashMap::default() };
+
+ let crate_def_map = db.crate_def_map(krate);
+ impls.collect_def_map(db, &crate_def_map);
+ impls.shrink_to_fit();
+
+ Arc::new(impls)
+ }
+
+ pub(crate) fn trait_impls_in_block_query(
+ db: &dyn HirDatabase,
+ block: BlockId,
+ ) -> Option<Arc<Self>> {
+ let _p = profile::span("trait_impls_in_block_query");
+ let mut impls = Self { map: FxHashMap::default() };
+
+ let block_def_map = db.block_def_map(block)?;
+ impls.collect_def_map(db, &block_def_map);
+ impls.shrink_to_fit();
+
+ Some(Arc::new(impls))
+ }
+
+ pub(crate) fn trait_impls_in_deps_query(db: &dyn HirDatabase, krate: CrateId) -> Arc<Self> {
+ let _p = profile::span("trait_impls_in_deps_query").detail(|| format!("{krate:?}"));
+ let crate_graph = db.crate_graph();
+ let mut res = Self { map: FxHashMap::default() };
+
+ for krate in crate_graph.transitive_deps(krate) {
+ res.merge(&db.trait_impls_in_crate(krate));
+ }
+ res.shrink_to_fit();
+
+ Arc::new(res)
+ }
+
+ fn shrink_to_fit(&mut self) {
+ self.map.shrink_to_fit();
+ self.map.values_mut().for_each(|map| {
+ map.shrink_to_fit();
+ map.values_mut().for_each(Vec::shrink_to_fit);
+ });
+ }
+
+ fn collect_def_map(&mut self, db: &dyn HirDatabase, def_map: &DefMap) {
+ for (_module_id, module_data) in def_map.modules() {
+ for impl_id in module_data.scope.impls() {
+ let target_trait = match db.impl_trait(impl_id) {
+ Some(tr) => tr.skip_binders().hir_trait_id(),
+ None => continue,
+ };
+ let self_ty = db.impl_self_ty(impl_id);
+ let self_ty_fp = TyFingerprint::for_trait_impl(self_ty.skip_binders());
+ self.map
+ .entry(target_trait)
+ .or_default()
+ .entry(self_ty_fp)
+ .or_default()
+ .push(impl_id);
+ }
+
+ // To better support custom derives, collect impls in all unnamed const items.
+ // const _: () = { ... };
+ for konst in collect_unnamed_consts(db, &module_data.scope) {
+ let body = db.body(konst.into());
+ for (_, block_def_map) in body.blocks(db.upcast()) {
+ self.collect_def_map(db, &block_def_map);
+ }
+ }
+ }
+ }
+
+ fn merge(&mut self, other: &Self) {
+ for (trait_, other_map) in &other.map {
+ let map = self.map.entry(*trait_).or_default();
+ for (fp, impls) in other_map {
+ map.entry(*fp).or_default().extend(impls);
+ }
+ }
+ }
+
+ /// Queries all trait impls for the given type.
+ pub fn for_self_ty_without_blanket_impls(
+ &self,
+ fp: TyFingerprint,
+ ) -> impl Iterator<Item = ImplId> + '_ {
+ self.map
+ .values()
+ .flat_map(move |impls| impls.get(&Some(fp)).into_iter())
+ .flat_map(|it| it.iter().copied())
+ }
+
+ /// Queries all impls of the given trait.
+ pub fn for_trait(&self, trait_: TraitId) -> impl Iterator<Item = ImplId> + '_ {
+ self.map
+ .get(&trait_)
+ .into_iter()
+ .flat_map(|map| map.values().flat_map(|v| v.iter().copied()))
+ }
+
+ /// Queries all impls of `trait_` that may apply to `self_ty`.
+ pub fn for_trait_and_self_ty(
+ &self,
+ trait_: TraitId,
+ self_ty: TyFingerprint,
+ ) -> impl Iterator<Item = ImplId> + '_ {
+ self.map
+ .get(&trait_)
+ .into_iter()
+ .flat_map(move |map| map.get(&Some(self_ty)).into_iter().chain(map.get(&None)))
+ .flat_map(|v| v.iter().copied())
+ }
+
+ pub fn all_impls(&self) -> impl Iterator<Item = ImplId> + '_ {
+ self.map.values().flat_map(|map| map.values().flat_map(|v| v.iter().copied()))
+ }
+}
+
+/// Inherent impls defined in some crate.
+///
+/// Inherent impls can only be defined in the crate that also defines the self type of the impl
+/// (note that some primitives are considered to be defined by both libcore and liballoc).
+///
+/// This makes inherent impl lookup easier than trait impl lookup since we only have to consider a
+/// single crate.
+#[derive(Debug, Eq, PartialEq)]
+pub struct InherentImpls {
+ map: FxHashMap<TyFingerprint, Vec<ImplId>>,
+}
+
+impl InherentImpls {
+ pub(crate) fn inherent_impls_in_crate_query(db: &dyn HirDatabase, krate: CrateId) -> Arc<Self> {
+ let mut impls = Self { map: FxHashMap::default() };
+
+ let crate_def_map = db.crate_def_map(krate);
+ impls.collect_def_map(db, &crate_def_map);
+ impls.shrink_to_fit();
+
+ Arc::new(impls)
+ }
+
+ pub(crate) fn inherent_impls_in_block_query(
+ db: &dyn HirDatabase,
+ block: BlockId,
+ ) -> Option<Arc<Self>> {
+ let mut impls = Self { map: FxHashMap::default() };
+ if let Some(block_def_map) = db.block_def_map(block) {
+ impls.collect_def_map(db, &block_def_map);
+ impls.shrink_to_fit();
+ return Some(Arc::new(impls));
+ }
+ None
+ }
+
+ fn shrink_to_fit(&mut self) {
+ self.map.values_mut().for_each(Vec::shrink_to_fit);
+ self.map.shrink_to_fit();
+ }
+
+ fn collect_def_map(&mut self, db: &dyn HirDatabase, def_map: &DefMap) {
+ for (_module_id, module_data) in def_map.modules() {
+ for impl_id in module_data.scope.impls() {
+ let data = db.impl_data(impl_id);
+ if data.target_trait.is_some() {
+ continue;
+ }
+
+ let self_ty = db.impl_self_ty(impl_id);
+ let fp = TyFingerprint::for_inherent_impl(self_ty.skip_binders());
+ if let Some(fp) = fp {
+ self.map.entry(fp).or_default().push(impl_id);
+ }
+ // `fp` should only be `None` in error cases (either erroneous code or incomplete name resolution)
+ }
+
+ // To better support custom derives, collect impls in all unnamed const items.
+ // const _: () = { ... };
+ for konst in collect_unnamed_consts(db, &module_data.scope) {
+ let body = db.body(konst.into());
+ for (_, block_def_map) in body.blocks(db.upcast()) {
+ self.collect_def_map(db, &block_def_map);
+ }
+ }
+ }
+ }
+
+ pub fn for_self_ty(&self, self_ty: &Ty) -> &[ImplId] {
+ match TyFingerprint::for_inherent_impl(self_ty) {
+ Some(fp) => self.map.get(&fp).map(|vec| vec.as_ref()).unwrap_or(&[]),
+ None => &[],
+ }
+ }
+
+ pub fn all_impls(&self) -> impl Iterator<Item = ImplId> + '_ {
+ self.map.values().flat_map(|v| v.iter().copied())
+ }
+}
+
+pub(crate) fn inherent_impl_crates_query(
+ db: &dyn HirDatabase,
+ krate: CrateId,
+ fp: TyFingerprint,
+) -> ArrayVec<CrateId, 2> {
+ let _p = profile::span("inherent_impl_crates_query");
+ let mut res = ArrayVec::new();
+ let crate_graph = db.crate_graph();
+
+ for krate in crate_graph.transitive_deps(krate) {
+ if res.is_full() {
+ // we don't currently look for or store more than two crates here,
+ // so don't needlessly look at more crates than necessary.
+ break;
+ }
+ let impls = db.inherent_impls_in_crate(krate);
+ if impls.map.get(&fp).map_or(false, |v| !v.is_empty()) {
+ res.push(krate);
+ }
+ }
+
+ res
+}
+
+fn collect_unnamed_consts<'a>(
+ db: &'a dyn HirDatabase,
+ scope: &'a ItemScope,
+) -> impl Iterator<Item = ConstId> + 'a {
+ let unnamed_consts = scope.unnamed_consts();
+
+ // FIXME: Also treat consts named `_DERIVE_*` as unnamed, since synstructure generates those.
+ // Should be removed once synstructure stops doing that.
+ let synstructure_hack_consts = scope.values().filter_map(|(item, _)| match item {
+ ModuleDefId::ConstId(id) => {
+ let loc = id.lookup(db.upcast());
+ let item_tree = loc.id.item_tree(db.upcast());
+ if item_tree[loc.id.value]
+ .name
+ .as_ref()
+ .map_or(false, |n| n.to_smol_str().starts_with("_DERIVE_"))
+ {
+ Some(id)
+ } else {
+ None
+ }
+ }
+ _ => None,
+ });
+
+ unnamed_consts.chain(synstructure_hack_consts)
+}
+
+pub fn def_crates(
+ db: &dyn HirDatabase,
+ ty: &Ty,
+ cur_crate: CrateId,
+) -> Option<ArrayVec<CrateId, 2>> {
+ let mod_to_crate_ids = |module: ModuleId| Some(iter::once(module.krate()).collect());
+
+ let fp = TyFingerprint::for_inherent_impl(ty);
+
+ match ty.kind(Interner) {
+ TyKind::Adt(AdtId(def_id), _) => mod_to_crate_ids(def_id.module(db.upcast())),
+ TyKind::Foreign(id) => {
+ mod_to_crate_ids(from_foreign_def_id(*id).lookup(db.upcast()).module(db.upcast()))
+ }
+ TyKind::Dyn(_) => ty
+ .dyn_trait()
+ .and_then(|trait_| mod_to_crate_ids(GenericDefId::TraitId(trait_).module(db.upcast()))),
+ // for primitives, there may be impls in various places (core and alloc
+ // mostly). We just check the whole crate graph for crates with impls
+ // (cached behind a query).
+ TyKind::Scalar(_)
+ | TyKind::Str
+ | TyKind::Slice(_)
+ | TyKind::Array(..)
+ | TyKind::Raw(..) => {
+ Some(db.inherent_impl_crates(cur_crate, fp.expect("fingerprint for primitive")))
+ }
+ _ => return None,
+ }
+}
+
+pub fn lang_names_for_bin_op(op: syntax::ast::BinaryOp) -> Option<(Name, Name)> {
+ use hir_expand::name;
+ use syntax::ast::{ArithOp, BinaryOp, CmpOp, Ordering};
+ Some(match op {
+ BinaryOp::LogicOp(_) => return None,
+ BinaryOp::ArithOp(aop) => match aop {
+ ArithOp::Add => (name!(add), name!(add)),
+ ArithOp::Mul => (name!(mul), name!(mul)),
+ ArithOp::Sub => (name!(sub), name!(sub)),
+ ArithOp::Div => (name!(div), name!(div)),
+ ArithOp::Rem => (name!(rem), name!(rem)),
+ ArithOp::Shl => (name!(shl), name!(shl)),
+ ArithOp::Shr => (name!(shr), name!(shr)),
+ ArithOp::BitXor => (name!(bitxor), name!(bitxor)),
+ ArithOp::BitOr => (name!(bitor), name!(bitor)),
+ ArithOp::BitAnd => (name!(bitand), name!(bitand)),
+ },
+ BinaryOp::Assignment { op: Some(aop) } => match aop {
+ ArithOp::Add => (name!(add_assign), name!(add_assign)),
+ ArithOp::Mul => (name!(mul_assign), name!(mul_assign)),
+ ArithOp::Sub => (name!(sub_assign), name!(sub_assign)),
+ ArithOp::Div => (name!(div_assign), name!(div_assign)),
+ ArithOp::Rem => (name!(rem_assign), name!(rem_assign)),
+ ArithOp::Shl => (name!(shl_assign), name!(shl_assign)),
+ ArithOp::Shr => (name!(shr_assign), name!(shr_assign)),
+ ArithOp::BitXor => (name!(bitxor_assign), name!(bitxor_assign)),
+ ArithOp::BitOr => (name!(bitor_assign), name!(bitor_assign)),
+ ArithOp::BitAnd => (name!(bitand_assign), name!(bitand_assign)),
+ },
+ BinaryOp::CmpOp(cop) => match cop {
+ CmpOp::Eq { negated: false } => (name!(eq), name!(eq)),
+ CmpOp::Eq { negated: true } => (name!(ne), name!(eq)),
+ CmpOp::Ord { ordering: Ordering::Less, strict: false } => {
+ (name!(le), name!(partial_ord))
+ }
+ CmpOp::Ord { ordering: Ordering::Less, strict: true } => {
+ (name!(lt), name!(partial_ord))
+ }
+ CmpOp::Ord { ordering: Ordering::Greater, strict: false } => {
+ (name!(ge), name!(partial_ord))
+ }
+ CmpOp::Ord { ordering: Ordering::Greater, strict: true } => {
+ (name!(gt), name!(partial_ord))
+ }
+ },
+ BinaryOp::Assignment { op: None } => return None,
+ })
+}
+
+/// Look up the method with the given name.
+pub(crate) fn lookup_method(
+ ty: &Canonical<Ty>,
+ db: &dyn HirDatabase,
+ env: Arc<TraitEnvironment>,
+ traits_in_scope: &FxHashSet<TraitId>,
+ visible_from_module: VisibleFromModule,
+ name: &Name,
+) -> Option<(ReceiverAdjustments, FunctionId)> {
+ iterate_method_candidates(
+ ty,
+ db,
+ env,
+ traits_in_scope,
+ visible_from_module,
+ Some(name),
+ LookupMode::MethodCall,
+ |adjustments, f| match f {
+ AssocItemId::FunctionId(f) => Some((adjustments, f)),
+ _ => None,
+ },
+ )
+}
+
+/// Whether we're looking up a dotted method call (like `v.len()`) or a path
+/// (like `Vec::new`).
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum LookupMode {
+ /// Looking up a method call like `v.len()`: We only consider candidates
+ /// that have a `self` parameter, and do autoderef.
+ MethodCall,
+ /// Looking up a path like `Vec::new` or `Vec::default`: We consider all
+ /// candidates including associated constants, but don't do autoderef.
+ Path,
+}
+
+#[derive(Clone, Copy)]
+pub enum VisibleFromModule {
+ /// Filter for results that are visible from the given module
+ Filter(ModuleId),
+ /// Include impls from the given block.
+ IncludeBlock(BlockId),
+ /// Do nothing special in regards visibility
+ None,
+}
+
+impl From<Option<ModuleId>> for VisibleFromModule {
+ fn from(module: Option<ModuleId>) -> Self {
+ match module {
+ Some(module) => Self::Filter(module),
+ None => Self::None,
+ }
+ }
+}
+
+impl From<Option<BlockId>> for VisibleFromModule {
+ fn from(block: Option<BlockId>) -> Self {
+ match block {
+ Some(block) => Self::IncludeBlock(block),
+ None => Self::None,
+ }
+ }
+}
+
+#[derive(Debug, Clone, Default)]
+pub struct ReceiverAdjustments {
+ autoref: Option<Mutability>,
+ autoderefs: usize,
+ unsize_array: bool,
+}
+
+impl ReceiverAdjustments {
+ pub(crate) fn apply(&self, table: &mut InferenceTable<'_>, ty: Ty) -> (Ty, Vec<Adjustment>) {
+ let mut ty = ty;
+ let mut adjust = Vec::new();
+ for _ in 0..self.autoderefs {
+ match autoderef::autoderef_step(table, ty.clone()) {
+ None => {
+ never!("autoderef not possible for {:?}", ty);
+ ty = TyKind::Error.intern(Interner);
+ break;
+ }
+ Some((kind, new_ty)) => {
+ ty = new_ty.clone();
+ adjust.push(Adjustment {
+ kind: Adjust::Deref(match kind {
+ // FIXME should we know the mutability here?
+ AutoderefKind::Overloaded => Some(OverloadedDeref(Mutability::Not)),
+ AutoderefKind::Builtin => None,
+ }),
+ target: new_ty,
+ });
+ }
+ }
+ }
+ if self.unsize_array {
+ ty = match ty.kind(Interner) {
+ TyKind::Array(inner, _) => TyKind::Slice(inner.clone()).intern(Interner),
+ _ => {
+ never!("unsize_array with non-array {:?}", ty);
+ ty
+ }
+ };
+ // FIXME this is kind of wrong since the unsize needs to happen to a pointer/reference
+ adjust.push(Adjustment {
+ kind: Adjust::Pointer(PointerCast::Unsize),
+ target: ty.clone(),
+ });
+ }
+ if let Some(m) = self.autoref {
+ ty = TyKind::Ref(m, static_lifetime(), ty).intern(Interner);
+ adjust
+ .push(Adjustment { kind: Adjust::Borrow(AutoBorrow::Ref(m)), target: ty.clone() });
+ }
+ (ty, adjust)
+ }
+
+ fn with_autoref(&self, m: Mutability) -> ReceiverAdjustments {
+ Self { autoref: Some(m), ..*self }
+ }
+}
+
+// This would be nicer if it just returned an iterator, but that runs into
+// lifetime problems, because we need to borrow temp `CrateImplDefs`.
+// FIXME add a context type here?
+pub(crate) fn iterate_method_candidates<T>(
+ ty: &Canonical<Ty>,
+ db: &dyn HirDatabase,
+ env: Arc<TraitEnvironment>,
+ traits_in_scope: &FxHashSet<TraitId>,
+ visible_from_module: VisibleFromModule,
+ name: Option<&Name>,
+ mode: LookupMode,
+ mut callback: impl FnMut(ReceiverAdjustments, AssocItemId) -> Option<T>,
+) -> Option<T> {
+ let mut slot = None;
+ iterate_method_candidates_dyn(
+ ty,
+ db,
+ env,
+ traits_in_scope,
+ visible_from_module,
+ name,
+ mode,
+ &mut |adj, item| {
+ assert!(slot.is_none());
+ if let Some(it) = callback(adj, item) {
+ slot = Some(it);
+ return ControlFlow::Break(());
+ }
+ ControlFlow::Continue(())
+ },
+ );
+ slot
+}
+
+pub fn lookup_impl_method(
+ self_ty: &Ty,
+ db: &dyn HirDatabase,
+ env: Arc<TraitEnvironment>,
+ trait_: TraitId,
+ name: &Name,
+) -> Option<FunctionId> {
+ let self_ty_fp = TyFingerprint::for_trait_impl(self_ty)?;
+ let trait_impls = db.trait_impls_in_deps(env.krate);
+ let impls = trait_impls.for_trait_and_self_ty(trait_, self_ty_fp);
+ let mut table = InferenceTable::new(db, env.clone());
+ find_matching_impl(impls, &mut table, &self_ty).and_then(|data| {
+ data.items.iter().find_map(|it| match it {
+ AssocItemId::FunctionId(f) => (db.function_data(*f).name == *name).then(|| *f),
+ _ => None,
+ })
+ })
+}
+
+fn find_matching_impl(
+ mut impls: impl Iterator<Item = ImplId>,
+ table: &mut InferenceTable<'_>,
+ self_ty: &Ty,
+) -> Option<Arc<ImplData>> {
+ let db = table.db;
+ loop {
+ let impl_ = impls.next()?;
+ let r = table.run_in_snapshot(|table| {
+ let impl_data = db.impl_data(impl_);
+ let substs =
- let subst =
- TyBuilder::subst_for_def(db, c).fill_with_inference_vars(table).build();
- let expected_self_ty =
- subst.apply(db.impl_self_ty(impl_id).skip_binders().clone(), Interner);
++ TyBuilder::subst_for_def(db, impl_, None).fill_with_inference_vars(table).build();
+ let impl_ty = db.impl_self_ty(impl_).substitute(Interner, &substs);
+
+ table
+ .unify(self_ty, &impl_ty)
+ .then(|| {
+ let wh_goals =
+ crate::chalk_db::convert_where_clauses(db, impl_.into(), &substs)
+ .into_iter()
+ .map(|b| b.cast(Interner));
+
+ let goal = crate::Goal::all(Interner, wh_goals);
+
+ table.try_obligation(goal).map(|_| impl_data)
+ })
+ .flatten()
+ });
+ if r.is_some() {
+ break r;
+ }
+ }
+}
+
+pub fn iterate_path_candidates(
+ ty: &Canonical<Ty>,
+ db: &dyn HirDatabase,
+ env: Arc<TraitEnvironment>,
+ traits_in_scope: &FxHashSet<TraitId>,
+ visible_from_module: VisibleFromModule,
+ name: Option<&Name>,
+ callback: &mut dyn FnMut(AssocItemId) -> ControlFlow<()>,
+) -> ControlFlow<()> {
+ iterate_method_candidates_dyn(
+ ty,
+ db,
+ env,
+ traits_in_scope,
+ visible_from_module,
+ name,
+ LookupMode::Path,
+ // the adjustments are not relevant for path lookup
+ &mut |_, id| callback(id),
+ )
+}
+
+pub fn iterate_method_candidates_dyn(
+ ty: &Canonical<Ty>,
+ db: &dyn HirDatabase,
+ env: Arc<TraitEnvironment>,
+ traits_in_scope: &FxHashSet<TraitId>,
+ visible_from_module: VisibleFromModule,
+ name: Option<&Name>,
+ mode: LookupMode,
+ callback: &mut dyn FnMut(ReceiverAdjustments, AssocItemId) -> ControlFlow<()>,
+) -> ControlFlow<()> {
+ match mode {
+ LookupMode::MethodCall => {
+ // For method calls, rust first does any number of autoderef, and
+ // then one autoref (i.e. when the method takes &self or &mut self).
+ // Note that when we've got a receiver like &S, even if the method
+ // we find in the end takes &self, we still do the autoderef step
+ // (just as rustc does an autoderef and then autoref again).
+
+ // We have to be careful about the order we're looking at candidates
+ // in here. Consider the case where we're resolving `x.clone()`
+ // where `x: &Vec<_>`. This resolves to the clone method with self
+ // type `Vec<_>`, *not* `&_`. I.e. we need to consider methods where
+ // the receiver type exactly matches before cases where we have to
+ // do autoref. But in the autoderef steps, the `&_` self type comes
+ // up *before* the `Vec<_>` self type.
+ //
+ // On the other hand, we don't want to just pick any by-value method
+ // before any by-autoref method; it's just that we need to consider
+ // the methods by autoderef order of *receiver types*, not *self
+ // types*.
+
+ let mut table = InferenceTable::new(db, env.clone());
+ let ty = table.instantiate_canonical(ty.clone());
+ let (deref_chain, adj) = autoderef_method_receiver(&mut table, ty);
+
+ let result = deref_chain.into_iter().zip(adj).try_for_each(|(receiver_ty, adj)| {
+ iterate_method_candidates_with_autoref(
+ &receiver_ty,
+ adj,
+ db,
+ env.clone(),
+ traits_in_scope,
+ visible_from_module,
+ name,
+ callback,
+ )
+ });
+ result
+ }
+ LookupMode::Path => {
+ // No autoderef for path lookups
+ iterate_method_candidates_for_self_ty(
+ ty,
+ db,
+ env,
+ traits_in_scope,
+ visible_from_module,
+ name,
+ callback,
+ )
+ }
+ }
+}
+
+fn iterate_method_candidates_with_autoref(
+ receiver_ty: &Canonical<Ty>,
+ first_adjustment: ReceiverAdjustments,
+ db: &dyn HirDatabase,
+ env: Arc<TraitEnvironment>,
+ traits_in_scope: &FxHashSet<TraitId>,
+ visible_from_module: VisibleFromModule,
+ name: Option<&Name>,
+ mut callback: &mut dyn FnMut(ReceiverAdjustments, AssocItemId) -> ControlFlow<()>,
+) -> ControlFlow<()> {
+ if receiver_ty.value.is_general_var(Interner, &receiver_ty.binders) {
+ // don't try to resolve methods on unknown types
+ return ControlFlow::Continue(());
+ }
+
+ iterate_method_candidates_by_receiver(
+ receiver_ty,
+ first_adjustment.clone(),
+ db,
+ env.clone(),
+ traits_in_scope,
+ visible_from_module,
+ name,
+ &mut callback,
+ )?;
+
+ let refed = Canonical {
+ value: TyKind::Ref(Mutability::Not, static_lifetime(), receiver_ty.value.clone())
+ .intern(Interner),
+ binders: receiver_ty.binders.clone(),
+ };
+
+ iterate_method_candidates_by_receiver(
+ &refed,
+ first_adjustment.with_autoref(Mutability::Not),
+ db,
+ env.clone(),
+ traits_in_scope,
+ visible_from_module,
+ name,
+ &mut callback,
+ )?;
+
+ let ref_muted = Canonical {
+ value: TyKind::Ref(Mutability::Mut, static_lifetime(), receiver_ty.value.clone())
+ .intern(Interner),
+ binders: receiver_ty.binders.clone(),
+ };
+
+ iterate_method_candidates_by_receiver(
+ &ref_muted,
+ first_adjustment.with_autoref(Mutability::Mut),
+ db,
+ env,
+ traits_in_scope,
+ visible_from_module,
+ name,
+ &mut callback,
+ )
+}
+
+fn iterate_method_candidates_by_receiver(
+ receiver_ty: &Canonical<Ty>,
+ receiver_adjustments: ReceiverAdjustments,
+ db: &dyn HirDatabase,
+ env: Arc<TraitEnvironment>,
+ traits_in_scope: &FxHashSet<TraitId>,
+ visible_from_module: VisibleFromModule,
+ name: Option<&Name>,
+ mut callback: &mut dyn FnMut(ReceiverAdjustments, AssocItemId) -> ControlFlow<()>,
+) -> ControlFlow<()> {
+ let mut table = InferenceTable::new(db, env);
+ let receiver_ty = table.instantiate_canonical(receiver_ty.clone());
+ let snapshot = table.snapshot();
+ // We're looking for methods with *receiver* type receiver_ty. These could
+ // be found in any of the derefs of receiver_ty, so we have to go through
+ // that.
+ let mut autoderef = autoderef::Autoderef::new(&mut table, receiver_ty.clone());
+ while let Some((self_ty, _)) = autoderef.next() {
+ iterate_inherent_methods(
+ &self_ty,
+ &mut autoderef.table,
+ name,
+ Some(&receiver_ty),
+ Some(receiver_adjustments.clone()),
+ visible_from_module,
+ &mut callback,
+ )?
+ }
+
+ table.rollback_to(snapshot);
+
+ let mut autoderef = autoderef::Autoderef::new(&mut table, receiver_ty.clone());
+ while let Some((self_ty, _)) = autoderef.next() {
+ iterate_trait_method_candidates(
+ &self_ty,
+ &mut autoderef.table,
+ traits_in_scope,
+ name,
+ Some(&receiver_ty),
+ Some(receiver_adjustments.clone()),
+ &mut callback,
+ )?
+ }
+
+ ControlFlow::Continue(())
+}
+
+fn iterate_method_candidates_for_self_ty(
+ self_ty: &Canonical<Ty>,
+ db: &dyn HirDatabase,
+ env: Arc<TraitEnvironment>,
+ traits_in_scope: &FxHashSet<TraitId>,
+ visible_from_module: VisibleFromModule,
+ name: Option<&Name>,
+ mut callback: &mut dyn FnMut(ReceiverAdjustments, AssocItemId) -> ControlFlow<()>,
+) -> ControlFlow<()> {
+ let mut table = InferenceTable::new(db, env);
+ let self_ty = table.instantiate_canonical(self_ty.clone());
+ iterate_inherent_methods(
+ &self_ty,
+ &mut table,
+ name,
+ None,
+ None,
+ visible_from_module,
+ &mut callback,
+ )?;
+ iterate_trait_method_candidates(
+ &self_ty,
+ &mut table,
+ traits_in_scope,
+ name,
+ None,
+ None,
+ callback,
+ )
+}
+
+fn iterate_trait_method_candidates(
+ self_ty: &Ty,
+ table: &mut InferenceTable<'_>,
+ traits_in_scope: &FxHashSet<TraitId>,
+ name: Option<&Name>,
+ receiver_ty: Option<&Ty>,
+ receiver_adjustments: Option<ReceiverAdjustments>,
+ callback: &mut dyn FnMut(ReceiverAdjustments, AssocItemId) -> ControlFlow<()>,
+) -> ControlFlow<()> {
+ let db = table.db;
+ let env = table.trait_env.clone();
+ let self_is_array = matches!(self_ty.kind(Interner), chalk_ir::TyKind::Array(..));
+
+ let canonical_self_ty = table.canonicalize(self_ty.clone()).value;
+
+ 'traits: for &t in traits_in_scope {
+ let data = db.trait_data(t);
+
+ // Traits annotated with `#[rustc_skip_array_during_method_dispatch]` are skipped during
+ // method resolution, if the receiver is an array, and we're compiling for editions before
+ // 2021.
+ // This is to make `[a].into_iter()` not break code with the new `IntoIterator` impl for
+ // arrays.
+ if data.skip_array_during_method_dispatch && self_is_array {
+ // FIXME: this should really be using the edition of the method name's span, in case it
+ // comes from a macro
+ if db.crate_graph()[env.krate].edition < Edition::Edition2021 {
+ continue;
+ }
+ }
+
+ // we'll be lazy about checking whether the type implements the
+ // trait, but if we find out it doesn't, we'll skip the rest of the
+ // iteration
+ let mut known_implemented = false;
+ for &(_, item) in data.items.iter() {
+ // Don't pass a `visible_from_module` down to `is_valid_candidate`,
+ // since only inherent methods should be included into visibility checking.
+ if !is_valid_candidate(table, name, receiver_ty, item, self_ty, None) {
+ continue;
+ }
+ if !known_implemented {
+ let goal = generic_implements_goal(db, env.clone(), t, &canonical_self_ty);
+ if db.trait_solve(env.krate, goal.cast(Interner)).is_none() {
+ continue 'traits;
+ }
+ }
+ known_implemented = true;
+ callback(receiver_adjustments.clone().unwrap_or_default(), item)?;
+ }
+ }
+ ControlFlow::Continue(())
+}
+
+fn iterate_inherent_methods(
+ self_ty: &Ty,
+ table: &mut InferenceTable<'_>,
+ name: Option<&Name>,
+ receiver_ty: Option<&Ty>,
+ receiver_adjustments: Option<ReceiverAdjustments>,
+ visible_from_module: VisibleFromModule,
+ callback: &mut dyn FnMut(ReceiverAdjustments, AssocItemId) -> ControlFlow<()>,
+) -> ControlFlow<()> {
+ let db = table.db;
+ let env = table.trait_env.clone();
+
+ // For trait object types and placeholder types with trait bounds, the methods of the trait and
+ // its super traits are considered inherent methods. This matters because these methods have
+ // higher priority than the other traits' methods, which would be considered in
+ // `iterate_trait_method_candidates()` only after this function.
+ match self_ty.kind(Interner) {
+ TyKind::Placeholder(_) => {
+ let env = table.trait_env.clone();
+ let traits = env
+ .traits_in_scope_from_clauses(self_ty.clone())
+ .flat_map(|t| all_super_traits(db.upcast(), t));
+ iterate_inherent_trait_methods(
+ self_ty,
+ table,
+ name,
+ receiver_ty,
+ receiver_adjustments.clone(),
+ callback,
+ traits,
+ )?;
+ }
+ TyKind::Dyn(_) => {
+ if let Some(principal_trait) = self_ty.dyn_trait() {
+ let traits = all_super_traits(db.upcast(), principal_trait);
+ iterate_inherent_trait_methods(
+ self_ty,
+ table,
+ name,
+ receiver_ty,
+ receiver_adjustments.clone(),
+ callback,
+ traits.into_iter(),
+ )?;
+ }
+ }
+ _ => {}
+ }
+
+ let def_crates = match def_crates(db, self_ty, env.krate) {
+ Some(k) => k,
+ None => return ControlFlow::Continue(()),
+ };
+
+ let (module, block) = match visible_from_module {
+ VisibleFromModule::Filter(module) => (Some(module), module.containing_block()),
+ VisibleFromModule::IncludeBlock(block) => (None, Some(block)),
+ VisibleFromModule::None => (None, None),
+ };
+
+ if let Some(block_id) = block {
+ if let Some(impls) = db.inherent_impls_in_block(block_id) {
+ impls_for_self_ty(
+ &impls,
+ self_ty,
+ table,
+ name,
+ receiver_ty,
+ receiver_adjustments.clone(),
+ module,
+ callback,
+ )?;
+ }
+ }
+
+ for krate in def_crates {
+ let impls = db.inherent_impls_in_crate(krate);
+ impls_for_self_ty(
+ &impls,
+ self_ty,
+ table,
+ name,
+ receiver_ty,
+ receiver_adjustments.clone(),
+ module,
+ callback,
+ )?;
+ }
+ return ControlFlow::Continue(());
+
+ fn iterate_inherent_trait_methods(
+ self_ty: &Ty,
+ table: &mut InferenceTable<'_>,
+ name: Option<&Name>,
+ receiver_ty: Option<&Ty>,
+ receiver_adjustments: Option<ReceiverAdjustments>,
+ callback: &mut dyn FnMut(ReceiverAdjustments, AssocItemId) -> ControlFlow<()>,
+ traits: impl Iterator<Item = TraitId>,
+ ) -> ControlFlow<()> {
+ let db = table.db;
+ for t in traits {
+ let data = db.trait_data(t);
+ for &(_, item) in data.items.iter() {
+ // We don't pass `visible_from_module` as all trait items should be visible.
+ if is_valid_candidate(table, name, receiver_ty, item, self_ty, None) {
+ callback(receiver_adjustments.clone().unwrap_or_default(), item)?;
+ }
+ }
+ }
+ ControlFlow::Continue(())
+ }
+
+ fn impls_for_self_ty(
+ impls: &InherentImpls,
+ self_ty: &Ty,
+ table: &mut InferenceTable<'_>,
+ name: Option<&Name>,
+ receiver_ty: Option<&Ty>,
+ receiver_adjustments: Option<ReceiverAdjustments>,
+ visible_from_module: Option<ModuleId>,
+ callback: &mut dyn FnMut(ReceiverAdjustments, AssocItemId) -> ControlFlow<()>,
+ ) -> ControlFlow<()> {
+ let db = table.db;
+ let impls_for_self_ty = impls.for_self_ty(self_ty);
+ for &impl_def in impls_for_self_ty {
+ for &item in &db.impl_data(impl_def).items {
+ if !is_valid_candidate(table, name, receiver_ty, item, self_ty, visible_from_module)
+ {
+ continue;
+ }
+ callback(receiver_adjustments.clone().unwrap_or_default(), item)?;
+ }
+ }
+ ControlFlow::Continue(())
+ }
+}
+
+/// Returns the receiver type for the index trait call.
+pub fn resolve_indexing_op(
+ db: &dyn HirDatabase,
+ env: Arc<TraitEnvironment>,
+ ty: Canonical<Ty>,
+ index_trait: TraitId,
+) -> Option<ReceiverAdjustments> {
+ let mut table = InferenceTable::new(db, env.clone());
+ let ty = table.instantiate_canonical(ty);
+ let (deref_chain, adj) = autoderef_method_receiver(&mut table, ty);
+ for (ty, adj) in deref_chain.into_iter().zip(adj) {
+ let goal = generic_implements_goal(db, env.clone(), index_trait, &ty);
+ if db.trait_solve(env.krate, goal.cast(Interner)).is_some() {
+ return Some(adj);
+ }
+ }
+ None
+}
+
+macro_rules! check_that {
+ ($cond:expr) => {
+ if !$cond {
+ return false;
+ }
+ };
+}
+
+fn is_valid_candidate(
+ table: &mut InferenceTable<'_>,
+ name: Option<&Name>,
+ receiver_ty: Option<&Ty>,
+ item: AssocItemId,
+ self_ty: &Ty,
+ visible_from_module: Option<ModuleId>,
+) -> bool {
+ let db = table.db;
+ match item {
+ AssocItemId::FunctionId(m) => {
+ is_valid_fn_candidate(table, m, name, receiver_ty, self_ty, visible_from_module)
+ }
+ AssocItemId::ConstId(c) => {
+ let data = db.const_data(c);
+ check_that!(receiver_ty.is_none());
+
+ check_that!(name.map_or(true, |n| data.name.as_ref() == Some(n)));
+ check_that!(visible_from_module.map_or(true, |from_module| {
+ let v = db.const_visibility(c).is_visible_from(db.upcast(), from_module);
+ if !v {
+ cov_mark::hit!(const_candidate_not_visible);
+ }
+ v
+ }));
+ if let ItemContainerId::ImplId(impl_id) = c.lookup(db.upcast()).container {
+ let self_ty_matches = table.run_in_snapshot(|table| {
- let impl_subst = match container {
++ let expected_self_ty = TyBuilder::impl_self_ty(db, impl_id)
++ .fill_with_inference_vars(table)
++ .build();
+ table.unify(&expected_self_ty, &self_ty)
+ });
+ if !self_ty_matches {
+ cov_mark::hit!(const_candidate_self_type_mismatch);
+ return false;
+ }
+ }
+ true
+ }
+ _ => false,
+ }
+}
+
+fn is_valid_fn_candidate(
+ table: &mut InferenceTable<'_>,
+ fn_id: FunctionId,
+ name: Option<&Name>,
+ receiver_ty: Option<&Ty>,
+ self_ty: &Ty,
+ visible_from_module: Option<ModuleId>,
+) -> bool {
+ let db = table.db;
+ let data = db.function_data(fn_id);
+
+ check_that!(name.map_or(true, |n| n == &data.name));
+ check_that!(visible_from_module.map_or(true, |from_module| {
+ let v = db.function_visibility(fn_id).is_visible_from(db.upcast(), from_module);
+ if !v {
+ cov_mark::hit!(autoderef_candidate_not_visible);
+ }
+ v
+ }));
+
+ table.run_in_snapshot(|table| {
+ let container = fn_id.lookup(db.upcast()).container;
- TyBuilder::subst_for_def(db, it).fill_with_inference_vars(table).build()
++ let (impl_subst, expect_self_ty) = match container {
+ ItemContainerId::ImplId(it) => {
- TyBuilder::subst_for_def(db, it).fill_with_inference_vars(table).build()
++ let subst =
++ TyBuilder::subst_for_def(db, it, None).fill_with_inference_vars(table).build();
++ let self_ty = db.impl_self_ty(it).substitute(Interner, &subst);
++ (subst, self_ty)
+ }
+ ItemContainerId::TraitId(it) => {
- let fn_subst = TyBuilder::subst_for_def(db, fn_id)
- .use_parent_substs(&impl_subst)
++ let subst =
++ TyBuilder::subst_for_def(db, it, None).fill_with_inference_vars(table).build();
++ let self_ty = subst.at(Interner, 0).assert_ty_ref(Interner).clone();
++ (subst, self_ty)
+ }
+ _ => unreachable!(),
+ };
+
- let expect_self_ty = match container {
- ItemContainerId::TraitId(_) => fn_subst.at(Interner, 0).assert_ty_ref(Interner).clone(),
- ItemContainerId::ImplId(impl_id) => {
- fn_subst.apply(db.impl_self_ty(impl_id).skip_binders().clone(), Interner)
- }
- // We should only get called for associated items (impl/trait)
- ItemContainerId::ModuleId(_) | ItemContainerId::ExternBlockId(_) => {
- unreachable!()
- }
- };
++ let fn_subst = TyBuilder::subst_for_def(db, fn_id, Some(impl_subst.clone()))
+ .fill_with_inference_vars(table)
+ .build();
+
+ check_that!(table.unify(&expect_self_ty, self_ty));
+
+ if let Some(receiver_ty) = receiver_ty {
+ check_that!(data.has_self_param());
+
+ let sig = db.callable_item_signature(fn_id.into());
+ let expected_receiver =
+ sig.map(|s| s.params()[0].clone()).substitute(Interner, &fn_subst);
+
+ check_that!(table.unify(&receiver_ty, &expected_receiver));
+ }
+
+ if let ItemContainerId::ImplId(impl_id) = container {
+ // We need to consider the bounds on the impl to distinguish functions of the same name
+ // for a type.
+ let predicates = db.generic_predicates(impl_id.into());
+ predicates
+ .iter()
+ .map(|predicate| {
+ let (p, b) = predicate
+ .clone()
+ .substitute(Interner, &impl_subst)
+ // Skipping the inner binders is ok, as we don't handle quantified where
+ // clauses yet.
+ .into_value_and_skipped_binders();
+ stdx::always!(b.len(Interner) == 0);
+ p
+ })
+ // It's ok to get ambiguity here, as we may not have enough information to prove
+ // obligations. We'll check if the user is calling the selected method properly
+ // later anyway.
+ .all(|p| table.try_obligation(p.cast(Interner)).is_some())
+ } else {
+ // For `ItemContainerId::TraitId`, we check if `self_ty` implements the trait in
+ // `iterate_trait_method_candidates()`.
+ // For others, this function shouldn't be called.
+ true
+ }
+ })
+}
+
+pub fn implements_trait(
+ ty: &Canonical<Ty>,
+ db: &dyn HirDatabase,
+ env: Arc<TraitEnvironment>,
+ trait_: TraitId,
+) -> bool {
+ let goal = generic_implements_goal(db, env.clone(), trait_, ty);
+ let solution = db.trait_solve(env.krate, goal.cast(Interner));
+
+ solution.is_some()
+}
+
+pub fn implements_trait_unique(
+ ty: &Canonical<Ty>,
+ db: &dyn HirDatabase,
+ env: Arc<TraitEnvironment>,
+ trait_: TraitId,
+) -> bool {
+ let goal = generic_implements_goal(db, env.clone(), trait_, ty);
+ let solution = db.trait_solve(env.krate, goal.cast(Interner));
+
+ matches!(solution, Some(crate::Solution::Unique(_)))
+}
+
+/// This creates Substs for a trait with the given Self type and type variables
+/// for all other parameters, to query Chalk with it.
+fn generic_implements_goal(
+ db: &dyn HirDatabase,
+ env: Arc<TraitEnvironment>,
+ trait_: TraitId,
+ self_ty: &Canonical<Ty>,
+) -> Canonical<InEnvironment<super::DomainGoal>> {
+ let mut kinds = self_ty.binders.interned().to_vec();
+ let trait_ref = TyBuilder::trait_ref(db, trait_)
+ .push(self_ty.value.clone())
+ .fill_with_bound_vars(DebruijnIndex::INNERMOST, kinds.len())
+ .build();
+ kinds.extend(trait_ref.substitution.iter(Interner).skip(1).map(|x| {
+ let vk = match x.data(Interner) {
+ chalk_ir::GenericArgData::Ty(_) => {
+ chalk_ir::VariableKind::Ty(chalk_ir::TyVariableKind::General)
+ }
+ chalk_ir::GenericArgData::Lifetime(_) => chalk_ir::VariableKind::Lifetime,
+ chalk_ir::GenericArgData::Const(c) => {
+ chalk_ir::VariableKind::Const(c.data(Interner).ty.clone())
+ }
+ };
+ chalk_ir::WithKind::new(vk, UniverseIndex::ROOT)
+ }));
+ let obligation = trait_ref.cast(Interner);
+ Canonical {
+ binders: CanonicalVarKinds::from_iter(Interner, kinds),
+ value: InEnvironment::new(&env.env, obligation),
+ }
+}
+
+fn autoderef_method_receiver(
+ table: &mut InferenceTable<'_>,
+ ty: Ty,
+) -> (Vec<Canonical<Ty>>, Vec<ReceiverAdjustments>) {
+ let (mut deref_chain, mut adjustments): (Vec<_>, Vec<_>) = (Vec::new(), Vec::new());
+ let mut autoderef = autoderef::Autoderef::new(table, ty);
+ while let Some((ty, derefs)) = autoderef.next() {
+ deref_chain.push(autoderef.table.canonicalize(ty).value);
+ adjustments.push(ReceiverAdjustments {
+ autoref: None,
+ autoderefs: derefs,
+ unsize_array: false,
+ });
+ }
+ // As a last step, we can do array unsizing (that's the only unsizing that rustc does for method receivers!)
+ if let (Some((TyKind::Array(parameters, _), binders)), Some(adj)) = (
+ deref_chain.last().map(|ty| (ty.value.kind(Interner), ty.binders.clone())),
+ adjustments.last().cloned(),
+ ) {
+ let unsized_ty = TyKind::Slice(parameters.clone()).intern(Interner);
+ deref_chain.push(Canonical { value: unsized_ty, binders });
+ adjustments.push(ReceiverAdjustments { unsize_array: true, ..adj });
+ }
+ (deref_chain, adjustments)
+}
--- /dev/null
- db::DefDatabase,
+mod never_type;
+mod coercion;
+mod regression;
+mod simple;
+mod patterns;
+mod traits;
+mod method_resolution;
+mod macros;
+mod display_source_code;
+mod incremental;
+mod diagnostics;
+
+use std::{collections::HashMap, env, sync::Arc};
+
+use base_db::{fixture::WithFixture, FileRange, SourceDatabaseExt};
+use expect_test::Expect;
+use hir_def::{
+ body::{Body, BodySourceMap, SyntheticSyntax},
++ db::{DefDatabase, InternDatabase},
+ expr::{ExprId, PatId},
+ item_scope::ItemScope,
+ nameres::DefMap,
+ src::HasSource,
+ AssocItemId, DefWithBodyId, HasModule, LocalModuleId, Lookup, ModuleDefId,
+};
+use hir_expand::{db::AstDatabase, InFile};
+use once_cell::race::OnceBool;
+use stdx::format_to;
+use syntax::{
+ ast::{self, AstNode, HasName},
+ SyntaxNode,
+};
+use tracing_subscriber::{layer::SubscriberExt, EnvFilter, Registry};
+use tracing_tree::HierarchicalLayer;
+
+use crate::{
+ db::HirDatabase,
+ display::HirDisplay,
+ infer::{Adjustment, TypeMismatch},
+ test_db::TestDB,
+ InferenceResult, Ty,
+};
+
+// These tests compare the inference results for all expressions in a file
+// against snapshots of the expected results using expect. Use
+// `env UPDATE_EXPECT=1 cargo test -p hir_ty` to update the snapshots.
+
+fn setup_tracing() -> Option<tracing::subscriber::DefaultGuard> {
+ static ENABLE: OnceBool = OnceBool::new();
+ if !ENABLE.get_or_init(|| env::var("CHALK_DEBUG").is_ok()) {
+ return None;
+ }
+
+ let filter = EnvFilter::from_env("CHALK_DEBUG");
+ let layer = HierarchicalLayer::default()
+ .with_indent_lines(true)
+ .with_ansi(false)
+ .with_indent_amount(2)
+ .with_writer(std::io::stderr);
+ let subscriber = Registry::default().with(filter).with(layer);
+ Some(tracing::subscriber::set_default(subscriber))
+}
+
+fn check_types(ra_fixture: &str) {
+ check_impl(ra_fixture, false, true, false)
+}
+
+fn check_types_source_code(ra_fixture: &str) {
+ check_impl(ra_fixture, false, true, true)
+}
+
+fn check_no_mismatches(ra_fixture: &str) {
+ check_impl(ra_fixture, true, false, false)
+}
+
+fn check(ra_fixture: &str) {
+ check_impl(ra_fixture, false, false, false)
+}
+
+fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_source: bool) {
+ let _tracing = setup_tracing();
+ let (db, files) = TestDB::with_many_files(ra_fixture);
+
+ let mut had_annotations = false;
+ let mut mismatches = HashMap::new();
+ let mut types = HashMap::new();
+ let mut adjustments = HashMap::<_, Vec<_>>::new();
+ for (file_id, annotations) in db.extract_annotations() {
+ for (range, expected) in annotations {
+ let file_range = FileRange { file_id, range };
+ if only_types {
+ types.insert(file_range, expected);
+ } else if expected.starts_with("type: ") {
+ types.insert(file_range, expected.trim_start_matches("type: ").to_string());
+ } else if expected.starts_with("expected") {
+ mismatches.insert(file_range, expected);
+ } else if expected.starts_with("adjustments: ") {
+ adjustments.insert(
+ file_range,
+ expected
+ .trim_start_matches("adjustments: ")
+ .split(',')
+ .map(|it| it.trim().to_string())
+ .filter(|it| !it.is_empty())
+ .collect(),
+ );
+ } else {
+ panic!("unexpected annotation: {}", expected);
+ }
+ had_annotations = true;
+ }
+ }
+ assert!(had_annotations || allow_none, "no `//^` annotations found");
+
+ let mut defs: Vec<DefWithBodyId> = Vec::new();
+ for file_id in files {
+ let module = db.module_for_file_opt(file_id);
+ let module = match module {
+ Some(m) => m,
+ None => continue,
+ };
+ let def_map = module.def_map(&db);
+ visit_module(&db, &def_map, module.local_id, &mut |it| defs.push(it));
+ }
+ defs.sort_by_key(|def| match def {
+ DefWithBodyId::FunctionId(it) => {
+ let loc = it.lookup(&db);
+ loc.source(&db).value.syntax().text_range().start()
+ }
+ DefWithBodyId::ConstId(it) => {
+ let loc = it.lookup(&db);
+ loc.source(&db).value.syntax().text_range().start()
+ }
+ DefWithBodyId::StaticId(it) => {
+ let loc = it.lookup(&db);
+ loc.source(&db).value.syntax().text_range().start()
+ }
++ DefWithBodyId::VariantId(it) => {
++ let loc = db.lookup_intern_enum(it.parent);
++ loc.source(&db).value.syntax().text_range().start()
++ }
+ });
+ let mut unexpected_type_mismatches = String::new();
+ for def in defs {
+ let (_body, body_source_map) = db.body_with_source_map(def);
+ let inference_result = db.infer(def);
+
+ for (pat, ty) in inference_result.type_of_pat.iter() {
+ let node = match pat_node(&body_source_map, pat, &db) {
+ Some(value) => value,
+ None => continue,
+ };
+ let range = node.as_ref().original_file_range(&db);
+ if let Some(expected) = types.remove(&range) {
+ let actual = if display_source {
+ ty.display_source_code(&db, def.module(&db)).unwrap()
+ } else {
+ ty.display_test(&db).to_string()
+ };
+ assert_eq!(actual, expected);
+ }
+ }
+
+ for (expr, ty) in inference_result.type_of_expr.iter() {
+ let node = match expr_node(&body_source_map, expr, &db) {
+ Some(value) => value,
+ None => continue,
+ };
+ let range = node.as_ref().original_file_range(&db);
+ if let Some(expected) = types.remove(&range) {
+ let actual = if display_source {
+ ty.display_source_code(&db, def.module(&db)).unwrap()
+ } else {
+ ty.display_test(&db).to_string()
+ };
+ assert_eq!(actual, expected);
+ }
+ if let Some(expected) = adjustments.remove(&range) {
+ if let Some(adjustments) = inference_result.expr_adjustments.get(&expr) {
+ assert_eq!(
+ expected,
+ adjustments
+ .iter()
+ .map(|Adjustment { kind, .. }| format!("{:?}", kind))
+ .collect::<Vec<_>>()
+ );
+ } else {
+ panic!("expected {:?} adjustments, found none", expected);
+ }
+ }
+ }
+
+ for (pat, mismatch) in inference_result.pat_type_mismatches() {
+ let node = match pat_node(&body_source_map, pat, &db) {
+ Some(value) => value,
+ None => continue,
+ };
+ let range = node.as_ref().original_file_range(&db);
+ let actual = format!(
+ "expected {}, got {}",
+ mismatch.expected.display_test(&db),
+ mismatch.actual.display_test(&db)
+ );
+ match mismatches.remove(&range) {
+ Some(annotation) => assert_eq!(actual, annotation),
+ None => format_to!(unexpected_type_mismatches, "{:?}: {}\n", range.range, actual),
+ }
+ }
+ for (expr, mismatch) in inference_result.expr_type_mismatches() {
+ let node = match body_source_map.expr_syntax(expr) {
+ Ok(sp) => {
+ let root = db.parse_or_expand(sp.file_id).unwrap();
+ sp.map(|ptr| ptr.to_node(&root).syntax().clone())
+ }
+ Err(SyntheticSyntax) => continue,
+ };
+ let range = node.as_ref().original_file_range(&db);
+ let actual = format!(
+ "expected {}, got {}",
+ mismatch.expected.display_test(&db),
+ mismatch.actual.display_test(&db)
+ );
+ match mismatches.remove(&range) {
+ Some(annotation) => assert_eq!(actual, annotation),
+ None => format_to!(unexpected_type_mismatches, "{:?}: {}\n", range.range, actual),
+ }
+ }
+ }
+
+ let mut buf = String::new();
+ if !unexpected_type_mismatches.is_empty() {
+ format_to!(buf, "Unexpected type mismatches:\n{}", unexpected_type_mismatches);
+ }
+ if !mismatches.is_empty() {
+ format_to!(buf, "Unchecked mismatch annotations:\n");
+ for m in mismatches {
+ format_to!(buf, "{:?}: {}\n", m.0.range, m.1);
+ }
+ }
+ if !types.is_empty() {
+ format_to!(buf, "Unchecked type annotations:\n");
+ for t in types {
+ format_to!(buf, "{:?}: type {}\n", t.0.range, t.1);
+ }
+ }
+ if !adjustments.is_empty() {
+ format_to!(buf, "Unchecked adjustments annotations:\n");
+ for t in adjustments {
+ format_to!(buf, "{:?}: type {:?}\n", t.0.range, t.1);
+ }
+ }
+ assert!(buf.is_empty(), "{}", buf);
+}
+
+fn expr_node(
+ body_source_map: &BodySourceMap,
+ expr: ExprId,
+ db: &TestDB,
+) -> Option<InFile<SyntaxNode>> {
+ Some(match body_source_map.expr_syntax(expr) {
+ Ok(sp) => {
+ let root = db.parse_or_expand(sp.file_id).unwrap();
+ sp.map(|ptr| ptr.to_node(&root).syntax().clone())
+ }
+ Err(SyntheticSyntax) => return None,
+ })
+}
+
+fn pat_node(
+ body_source_map: &BodySourceMap,
+ pat: PatId,
+ db: &TestDB,
+) -> Option<InFile<SyntaxNode>> {
+ Some(match body_source_map.pat_syntax(pat) {
+ Ok(sp) => {
+ let root = db.parse_or_expand(sp.file_id).unwrap();
+ sp.map(|ptr| {
+ ptr.either(
+ |it| it.to_node(&root).syntax().clone(),
+ |it| it.to_node(&root).syntax().clone(),
+ )
+ })
+ }
+ Err(SyntheticSyntax) => return None,
+ })
+}
+
+fn infer(ra_fixture: &str) -> String {
+ infer_with_mismatches(ra_fixture, false)
+}
+
+fn infer_with_mismatches(content: &str, include_mismatches: bool) -> String {
+ let _tracing = setup_tracing();
+ let (db, file_id) = TestDB::with_single_file(content);
+
+ let mut buf = String::new();
+
+ let mut infer_def = |inference_result: Arc<InferenceResult>,
+ body_source_map: Arc<BodySourceMap>| {
+ let mut types: Vec<(InFile<SyntaxNode>, &Ty)> = Vec::new();
+ let mut mismatches: Vec<(InFile<SyntaxNode>, &TypeMismatch)> = Vec::new();
+
+ for (pat, ty) in inference_result.type_of_pat.iter() {
+ let syntax_ptr = match body_source_map.pat_syntax(pat) {
+ Ok(sp) => {
+ let root = db.parse_or_expand(sp.file_id).unwrap();
+ sp.map(|ptr| {
+ ptr.either(
+ |it| it.to_node(&root).syntax().clone(),
+ |it| it.to_node(&root).syntax().clone(),
+ )
+ })
+ }
+ Err(SyntheticSyntax) => continue,
+ };
+ types.push((syntax_ptr.clone(), ty));
+ if let Some(mismatch) = inference_result.type_mismatch_for_pat(pat) {
+ mismatches.push((syntax_ptr, mismatch));
+ }
+ }
+
+ for (expr, ty) in inference_result.type_of_expr.iter() {
+ let node = match body_source_map.expr_syntax(expr) {
+ Ok(sp) => {
+ let root = db.parse_or_expand(sp.file_id).unwrap();
+ sp.map(|ptr| ptr.to_node(&root).syntax().clone())
+ }
+ Err(SyntheticSyntax) => continue,
+ };
+ types.push((node.clone(), ty));
+ if let Some(mismatch) = inference_result.type_mismatch_for_expr(expr) {
+ mismatches.push((node, mismatch));
+ }
+ }
+
+ // sort ranges for consistency
+ types.sort_by_key(|(node, _)| {
+ let range = node.value.text_range();
+ (range.start(), range.end())
+ });
+ for (node, ty) in &types {
+ let (range, text) = if let Some(self_param) = ast::SelfParam::cast(node.value.clone()) {
+ (self_param.name().unwrap().syntax().text_range(), "self".to_string())
+ } else {
+ (node.value.text_range(), node.value.text().to_string().replace('\n', " "))
+ };
+ let macro_prefix = if node.file_id != file_id.into() { "!" } else { "" };
+ format_to!(
+ buf,
+ "{}{:?} '{}': {}\n",
+ macro_prefix,
+ range,
+ ellipsize(text, 15),
+ ty.display_test(&db)
+ );
+ }
+ if include_mismatches {
+ mismatches.sort_by_key(|(node, _)| {
+ let range = node.value.text_range();
+ (range.start(), range.end())
+ });
+ for (src_ptr, mismatch) in &mismatches {
+ let range = src_ptr.value.text_range();
+ let macro_prefix = if src_ptr.file_id != file_id.into() { "!" } else { "" };
+ format_to!(
+ buf,
+ "{}{:?}: expected {}, got {}\n",
+ macro_prefix,
+ range,
+ mismatch.expected.display_test(&db),
+ mismatch.actual.display_test(&db),
+ );
+ }
+ }
+ };
+
+ let module = db.module_for_file(file_id);
+ let def_map = module.def_map(&db);
+
+ let mut defs: Vec<DefWithBodyId> = Vec::new();
+ visit_module(&db, &def_map, module.local_id, &mut |it| defs.push(it));
+ defs.sort_by_key(|def| match def {
+ DefWithBodyId::FunctionId(it) => {
+ let loc = it.lookup(&db);
+ loc.source(&db).value.syntax().text_range().start()
+ }
+ DefWithBodyId::ConstId(it) => {
+ let loc = it.lookup(&db);
+ loc.source(&db).value.syntax().text_range().start()
+ }
+ DefWithBodyId::StaticId(it) => {
+ let loc = it.lookup(&db);
+ loc.source(&db).value.syntax().text_range().start()
+ }
++ DefWithBodyId::VariantId(it) => {
++ let loc = db.lookup_intern_enum(it.parent);
++ loc.source(&db).value.syntax().text_range().start()
++ }
+ });
+ for def in defs {
+ let (_body, source_map) = db.body_with_source_map(def);
+ let infer = db.infer(def);
+ infer_def(infer, source_map);
+ }
+
+ buf.truncate(buf.trim_end().len());
+ buf
+}
+
+fn visit_module(
+ db: &TestDB,
+ crate_def_map: &DefMap,
+ module_id: LocalModuleId,
+ cb: &mut dyn FnMut(DefWithBodyId),
+) {
+ visit_scope(db, crate_def_map, &crate_def_map[module_id].scope, cb);
+ for impl_id in crate_def_map[module_id].scope.impls() {
+ let impl_data = db.impl_data(impl_id);
+ for &item in impl_data.items.iter() {
+ match item {
+ AssocItemId::FunctionId(it) => {
+ let def = it.into();
+ cb(def);
+ let body = db.body(def);
+ visit_body(db, &body, cb);
+ }
+ AssocItemId::ConstId(it) => {
+ let def = it.into();
+ cb(def);
+ let body = db.body(def);
+ visit_body(db, &body, cb);
+ }
+ AssocItemId::TypeAliasId(_) => (),
+ }
+ }
+ }
+
+ fn visit_scope(
+ db: &TestDB,
+ crate_def_map: &DefMap,
+ scope: &ItemScope,
+ cb: &mut dyn FnMut(DefWithBodyId),
+ ) {
+ for decl in scope.declarations() {
+ match decl {
+ ModuleDefId::FunctionId(it) => {
+ let def = it.into();
+ cb(def);
+ let body = db.body(def);
+ visit_body(db, &body, cb);
+ }
+ ModuleDefId::ConstId(it) => {
+ let def = it.into();
+ cb(def);
+ let body = db.body(def);
+ visit_body(db, &body, cb);
+ }
+ ModuleDefId::StaticId(it) => {
+ let def = it.into();
+ cb(def);
+ let body = db.body(def);
+ visit_body(db, &body, cb);
+ }
++ ModuleDefId::AdtId(hir_def::AdtId::EnumId(it)) => {
++ db.enum_data(it)
++ .variants
++ .iter()
++ .map(|(id, _)| hir_def::EnumVariantId { parent: it, local_id: id })
++ .for_each(|it| {
++ let def = it.into();
++ cb(def);
++ let body = db.body(def);
++ visit_body(db, &body, cb);
++ });
++ }
+ ModuleDefId::TraitId(it) => {
+ let trait_data = db.trait_data(it);
+ for &(_, item) in trait_data.items.iter() {
+ match item {
+ AssocItemId::FunctionId(it) => cb(it.into()),
+ AssocItemId::ConstId(it) => cb(it.into()),
+ AssocItemId::TypeAliasId(_) => (),
+ }
+ }
+ }
+ ModuleDefId::ModuleId(it) => visit_module(db, crate_def_map, it.local_id, cb),
+ _ => (),
+ }
+ }
+ }
+
+ fn visit_body(db: &TestDB, body: &Body, cb: &mut dyn FnMut(DefWithBodyId)) {
+ for (_, def_map) in body.blocks(db) {
+ for (mod_id, _) in def_map.modules() {
+ visit_module(db, &def_map, mod_id, cb);
+ }
+ }
+ }
+}
+
+fn ellipsize(mut text: String, max_len: usize) -> String {
+ if text.len() <= max_len {
+ return text;
+ }
+ let ellipsis = "...";
+ let e_len = ellipsis.len();
+ let mut prefix_len = (max_len - e_len) / 2;
+ while !text.is_char_boundary(prefix_len) {
+ prefix_len += 1;
+ }
+ let mut suffix_len = max_len - e_len - prefix_len;
+ while !text.is_char_boundary(text.len() - suffix_len) {
+ suffix_len += 1;
+ }
+ text.replace_range(prefix_len..text.len() - suffix_len, ellipsis);
+ text
+}
+
+fn check_infer(ra_fixture: &str, expect: Expect) {
+ let mut actual = infer(ra_fixture);
+ actual.push('\n');
+ expect.assert_eq(&actual);
+}
+
+fn check_infer_with_mismatches(ra_fixture: &str, expect: Expect) {
+ let mut actual = infer_with_mismatches(ra_fixture, true);
+ actual.push('\n');
+ expect.assert_eq(&actual);
+}
+
+#[test]
+fn salsa_bug() {
+ let (mut db, pos) = TestDB::with_position(
+ "
+ //- /lib.rs
+ trait Index {
+ type Output;
+ }
+
+ type Key<S: UnificationStoreBase> = <S as UnificationStoreBase>::Key;
+
+ pub trait UnificationStoreBase: Index<Output = Key<Self>> {
+ type Key;
+
+ fn len(&self) -> usize;
+ }
+
+ pub trait UnificationStoreMut: UnificationStoreBase {
+ fn push(&mut self, value: Self::Key);
+ }
+
+ fn main() {
+ let x = 1;
+ x.push(1);$0
+ }
+ ",
+ );
+
+ let module = db.module_for_file(pos.file_id);
+ let crate_def_map = module.def_map(&db);
+ visit_module(&db, &crate_def_map, module.local_id, &mut |def| {
+ db.infer(def);
+ });
+
+ let new_text = "
+ //- /lib.rs
+ trait Index {
+ type Output;
+ }
+
+ type Key<S: UnificationStoreBase> = <S as UnificationStoreBase>::Key;
+
+ pub trait UnificationStoreBase: Index<Output = Key<Self>> {
+ type Key;
+
+ fn len(&self) -> usize;
+ }
+
+ pub trait UnificationStoreMut: UnificationStoreBase {
+ fn push(&mut self, value: Self::Key);
+ }
+
+ fn main() {
+
+ let x = 1;
+ x.push(1);
+ }
+ "
+ .to_string();
+
+ db.set_file_text(pos.file_id, Arc::new(new_text));
+
+ let module = db.module_for_file(pos.file_id);
+ let crate_def_map = module.def_map(&db);
+ visit_module(&db, &crate_def_map, module.local_id, &mut |def| {
+ db.infer(def);
+ });
+}
--- /dev/null
+use super::{check, check_no_mismatches, check_types};
+
+#[test]
+fn block_expr_type_mismatch() {
+ check(
+ r"
+fn test() {
+ let a: i32 = { 1i64 };
+ // ^^^^ expected i32, got i64
+}
+ ",
+ );
+}
+
+#[test]
+fn coerce_places() {
+ check_no_mismatches(
+ r#"
+//- minicore: coerce_unsized
+struct S<T> { a: T }
+
+fn f<T>(_: &[T]) -> T { loop {} }
+fn g<T>(_: S<&[T]>) -> T { loop {} }
+
+fn gen<T>() -> *mut [T; 2] { loop {} }
+fn test1<U>() -> *mut [U] {
+ gen()
+}
+
+fn test2() {
+ let arr: &[u8; 1] = &[1];
+
+ let a: &[_] = arr;
+ let b = f(arr);
+ let c: &[_] = { arr };
+ let d = g(S { a: arr });
+ let e: [&[_]; 1] = [arr];
+ let f: [&[_]; 2] = [arr; 2];
+ let g: (&[_], &[_]) = (arr, arr);
+}
+"#,
+ );
+}
+
+#[test]
+fn let_stmt_coerce() {
+ check(
+ r"
+//- minicore: coerce_unsized
+fn test() {
+ let x: &[isize] = &[1];
+ // ^^^^ adjustments: Deref(None), Borrow(Ref(Not)), Pointer(Unsize)
+ let x: *const [isize] = &[1];
+ // ^^^^ adjustments: Deref(None), Borrow(RawPtr(Not)), Pointer(Unsize)
+}
+",
+ );
+}
+
+#[test]
+fn custom_coerce_unsized() {
+ check(
+ r#"
+//- minicore: coerce_unsized
+use core::{marker::Unsize, ops::CoerceUnsized};
+
+struct A<T: ?Sized>(*const T);
+struct B<T: ?Sized>(*const T);
+struct C<T: ?Sized> { inner: *const T }
+
+impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<B<U>> for B<T> {}
+impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<C<U>> for C<T> {}
+
+fn foo1<T>(x: A<[T]>) -> A<[T]> { x }
+fn foo2<T>(x: B<[T]>) -> B<[T]> { x }
+fn foo3<T>(x: C<[T]>) -> C<[T]> { x }
+
+fn test(a: A<[u8; 2]>, b: B<[u8; 2]>, c: C<[u8; 2]>) {
+ let d = foo1(a);
+ // ^ expected A<[{unknown}]>, got A<[u8; 2]>
+ let e = foo2(b);
+ // ^ type: B<[u8]>
+ let f = foo3(c);
+ // ^ type: C<[u8]>
+}
+"#,
+ );
+}
+
+#[test]
+fn if_coerce() {
+ check_no_mismatches(
+ r#"
+//- minicore: coerce_unsized
+fn foo<T>(x: &[T]) -> &[T] { x }
+fn test() {
+ let x = if true {
+ foo(&[1])
+ // ^^^^ adjustments: Deref(None), Borrow(Ref(Not)), Pointer(Unsize)
+ } else {
+ &[1]
+ };
+}
+"#,
+ );
+}
+
+#[test]
+fn if_else_coerce() {
+ check_no_mismatches(
+ r#"
+//- minicore: coerce_unsized
+fn foo<T>(x: &[T]) -> &[T] { x }
+fn test() {
+ let x = if true {
+ &[1]
+ } else {
+ foo(&[1])
+ };
+}
+"#,
+ )
+}
+
+#[test]
+fn match_first_coerce() {
+ check_no_mismatches(
+ r#"
+//- minicore: coerce_unsized
+fn foo<T>(x: &[T]) -> &[T] { x }
+fn test(i: i32) {
+ let x = match i {
+ 2 => foo(&[2]),
+ // ^^^^ adjustments: Deref(None), Borrow(Ref(Not)), Pointer(Unsize)
+ 1 => &[1],
+ _ => &[3],
+ };
+}
+"#,
+ );
+}
+
+#[test]
+fn match_second_coerce() {
+ check_no_mismatches(
+ r#"
+//- minicore: coerce_unsized
+fn foo<T>(x: &[T]) -> &[T] { loop {} }
+ // ^^^^^^^ adjustments: NeverToAny
+fn test(i: i32) {
+ let x = match i {
+ 1 => &[1],
+ 2 => foo(&[2]),
+ _ => &[3],
+ };
+}
+"#,
+ );
+}
+
+#[test]
+fn coerce_merge_one_by_one1() {
+ cov_mark::check!(coerce_merge_fail_fallback);
+
+ check(
+ r"
+fn test() {
+ let t = &mut 1;
+ let x = match 1 {
+ 1 => t as *mut i32,
+ 2 => t as &i32,
+ //^^^^^^^^^ expected *mut i32, got &i32
+ _ => t as *const i32,
+ // ^^^^^^^^^^^^^^^ adjustments: Pointer(MutToConstPointer)
+
+ };
+ x;
+ //^ type: *const i32
+
+}
+ ",
+ );
+}
+
+#[test]
+fn return_coerce_unknown() {
+ check_types(
+ r"
+fn foo() -> u32 {
+ return unknown;
+ //^^^^^^^ u32
+}
+ ",
+ );
+}
+
+#[test]
+fn coerce_autoderef() {
+ check_no_mismatches(
+ r"
+struct Foo;
+fn takes_ref_foo(x: &Foo) {}
+fn test() {
+ takes_ref_foo(&Foo);
+ takes_ref_foo(&&Foo);
+ takes_ref_foo(&&&Foo);
+}",
+ );
+}
+
+#[test]
+fn coerce_autoderef_generic() {
+ check_no_mismatches(
+ r#"
+struct Foo;
+fn takes_ref<T>(x: &T) -> T { *x }
+fn test() {
+ takes_ref(&Foo);
+ takes_ref(&&Foo);
+ takes_ref(&&&Foo);
+}
+"#,
+ );
+}
+
+#[test]
+fn coerce_autoderef_block() {
+ check_no_mismatches(
+ r#"
+//- minicore: deref
+struct String {}
+impl core::ops::Deref for String { type Target = str; }
+fn takes_ref_str(x: &str) {}
+fn returns_string() -> String { loop {} }
+fn test() {
+ takes_ref_str(&{ returns_string() });
+ // ^^^^^^^^^^^^^^^^^^^^^ adjustments: Deref(None), Deref(Some(OverloadedDeref(Not))), Borrow(Ref(Not))
+}
+"#,
+ );
+}
+
+#[test]
+fn coerce_autoderef_implication_1() {
+ check_no_mismatches(
+ r"
+//- minicore: deref
+struct Foo<T>;
+impl core::ops::Deref for Foo<u32> { type Target = (); }
+
+fn takes_ref_foo<T>(x: &Foo<T>) {}
+fn test() {
+ let foo = Foo;
+ //^^^ type: Foo<{unknown}>
+ takes_ref_foo(&foo);
+
+ let foo = Foo;
+ //^^^ type: Foo<u32>
+ let _: &() = &foo;
+}",
+ );
+}
+
+#[test]
+fn coerce_autoderef_implication_2() {
+ check(
+ r"
+//- minicore: deref
+struct Foo<T>;
+impl core::ops::Deref for Foo<u32> { type Target = (); }
+
+fn takes_ref_foo<T>(x: &Foo<T>) {}
+fn test() {
+ let foo = Foo;
+ //^^^ type: Foo<{unknown}>
+ let _: &u32 = &Foo;
+ //^^^^ expected &u32, got &Foo<{unknown}>
+}",
+ );
+}
+
+#[test]
+fn closure_return_coerce() {
+ check_no_mismatches(
+ r"
+fn foo() {
+ let x = || {
+ if true {
+ return &1u32;
+ }
+ &&1u32
+ };
+}",
+ );
+}
+
++#[test]
++fn generator_yield_return_coerce() {
++ check_no_mismatches(
++ r#"
++fn test() {
++ let g = || {
++ yield &1u32;
++ yield &&1u32;
++ if true {
++ return &1u32;
++ }
++ &&1u32
++ };
++}
++ "#,
++ );
++}
++
+#[test]
+fn assign_coerce() {
+ check_no_mismatches(
+ r"
+//- minicore: deref
+struct String;
+impl core::ops::Deref for String { type Target = str; }
+fn g(_text: &str) {}
+fn f(text: &str) {
+ let mut text = text;
+ let tmp = String;
+ text = &tmp;
+ g(text);
+}
+",
+ );
+}
+
+#[test]
+fn destructuring_assign_coerce() {
+ check_no_mismatches(
+ r"
+//- minicore: deref
+struct String;
+impl core::ops::Deref for String { type Target = str; }
+fn g(_text: &str) {}
+fn f(text: &str) {
+ let mut text = text;
+ let tmp = String;
+ [text, _] = [&tmp, &tmp];
+ g(text);
+}
+",
+ );
+}
+
+#[test]
+fn coerce_fn_item_to_fn_ptr() {
+ check_no_mismatches(
+ r"
+fn foo(x: u32) -> isize { 1 }
+fn test() {
+ let f: fn(u32) -> isize = foo;
+ // ^^^ adjustments: Pointer(ReifyFnPointer)
+ let f: unsafe fn(u32) -> isize = foo;
+ // ^^^ adjustments: Pointer(ReifyFnPointer)
+}",
+ );
+}
+
+#[test]
+fn coerce_fn_items_in_match_arms() {
+ cov_mark::check!(coerce_fn_reification);
+
+ check_types(
+ r"
+fn foo1(x: u32) -> isize { 1 }
+fn foo2(x: u32) -> isize { 2 }
+fn foo3(x: u32) -> isize { 3 }
+fn test() {
+ let x = match 1 {
+ 1 => foo1,
+ 2 => foo2,
+ _ => foo3,
+ };
+ x;
+ //^ fn(u32) -> isize
+}",
+ );
+}
+
+#[test]
+fn coerce_closure_to_fn_ptr() {
+ check_no_mismatches(
+ r"
+fn test() {
+ let f: fn(u32) -> isize = |x| { 1 };
+}",
+ );
+}
+
+#[test]
+fn coerce_placeholder_ref() {
+ // placeholders should unify, even behind references
+ check_no_mismatches(
+ r"
+struct S<T> { t: T }
+impl<TT> S<TT> {
+ fn get(&self) -> &TT {
+ &self.t
+ }
+}",
+ );
+}
+
+#[test]
+fn coerce_unsize_array() {
+ check_types(
+ r#"
+//- minicore: coerce_unsized
+fn test() {
+ let f: &[usize] = &[1, 2, 3];
+ //^ usize
+}"#,
+ );
+}
+
+#[test]
+fn coerce_unsize_trait_object_simple() {
+ check_types(
+ r#"
+//- minicore: coerce_unsized
+trait Foo<T, U> {}
+trait Bar<U, T, X>: Foo<T, U> {}
+trait Baz<T, X>: Bar<usize, T, X> {}
+
+struct S<T, X>;
+impl<T, X> Foo<T, usize> for S<T, X> {}
+impl<T, X> Bar<usize, T, X> for S<T, X> {}
+impl<T, X> Baz<T, X> for S<T, X> {}
+
+fn test() {
+ let obj: &dyn Baz<i8, i16> = &S;
+ //^ S<i8, i16>
+ let obj: &dyn Bar<_, i8, i16> = &S;
+ //^ S<i8, i16>
+ let obj: &dyn Foo<i8, _> = &S;
+ //^ S<i8, {unknown}>
+}"#,
+ );
+}
+
+#[test]
+fn coerce_unsize_super_trait_cycle() {
+ check_no_mismatches(
+ r#"
+//- minicore: coerce_unsized
+trait A {}
+trait B: C + A {}
+trait C: B {}
+trait D: C
+
+struct S;
+impl A for S {}
+impl B for S {}
+impl C for S {}
+impl D for S {}
+
+fn test() {
+ let obj: &dyn D = &S;
+ let obj: &dyn A = &S;
+}
+"#,
+ );
+}
+
+#[test]
+fn coerce_unsize_generic() {
+ // FIXME: fix the type mismatches here
+ check(
+ r#"
+//- minicore: coerce_unsized
+struct Foo<T> { t: T };
+struct Bar<T>(Foo<T>);
+
+fn test() {
+ let _: &Foo<[usize]> = &Foo { t: [1, 2, 3] };
+ //^^^^^^^^^ expected [usize], got [usize; 3]
+ let _: &Bar<[usize]> = &Bar(Foo { t: [1, 2, 3] });
+ //^^^^^^^^^ expected [usize], got [usize; 3]
+}
+"#,
+ );
+}
+
+#[test]
+fn coerce_unsize_apit() {
+ check(
+ r#"
+//- minicore: coerce_unsized
+trait Foo {}
+
+fn test(f: impl Foo, g: &(impl Foo + ?Sized)) {
+ let _: &dyn Foo = &f;
+ let _: &dyn Foo = g;
+ //^ expected &dyn Foo, got &impl Foo + ?Sized
+}
+ "#,
+ );
+}
+
+#[test]
+fn two_closures_lub() {
+ check_types(
+ r#"
+fn foo(c: i32) {
+ let add = |a: i32, b: i32| a + b;
+ let sub = |a, b| a - b;
+ //^^^^^^^^^^^^ |i32, i32| -> i32
+ if c > 42 { add } else { sub };
+ //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ fn(i32, i32) -> i32
+}
+ "#,
+ )
+}
+
+#[test]
+fn match_diverging_branch_1() {
+ check_types(
+ r#"
+enum Result<T> { Ok(T), Err }
+fn parse<T>() -> T { loop {} }
+
+fn test() -> i32 {
+ let a = match parse() {
+ Ok(val) => val,
+ Err => return 0,
+ };
+ a
+ //^ i32
+}
+ "#,
+ )
+}
+
+#[test]
+fn match_diverging_branch_2() {
+ // same as 1 except for order of branches
+ check_types(
+ r#"
+enum Result<T> { Ok(T), Err }
+fn parse<T>() -> T { loop {} }
+
+fn test() -> i32 {
+ let a = match parse() {
+ Err => return 0,
+ Ok(val) => val,
+ };
+ a
+ //^ i32
+}
+ "#,
+ )
+}
+
+#[test]
+fn panic_macro() {
+ check_no_mismatches(
+ r#"
+mod panic {
+ #[macro_export]
+ pub macro panic_2015 {
+ () => (
+ $crate::panicking::panic()
+ ),
+ }
+}
+
+mod panicking {
+ pub fn panic() -> ! { loop {} }
+}
+
+#[rustc_builtin_macro = "core_panic"]
+macro_rules! panic {
+ // Expands to either `$crate::panic::panic_2015` or `$crate::panic::panic_2021`
+ // depending on the edition of the caller.
+ ($($arg:tt)*) => {
+ /* compiler built-in */
+ };
+}
+
+fn main() {
+ panic!()
+}
+ "#,
+ );
+}
+
+#[test]
+fn coerce_unsize_expected_type_1() {
+ check_no_mismatches(
+ r#"
+//- minicore: coerce_unsized
+fn main() {
+ let foo: &[u32] = &[1, 2];
+ let foo: &[u32] = match true {
+ true => &[1, 2],
+ false => &[1, 2, 3],
+ };
+ let foo: &[u32] = if true {
+ &[1, 2]
+ } else {
+ &[1, 2, 3]
+ };
+}
+ "#,
+ );
+}
+
+#[test]
+fn coerce_unsize_expected_type_2() {
+ check_no_mismatches(
+ r#"
+//- minicore: coerce_unsized
+struct InFile<T>;
+impl<T> InFile<T> {
+ fn with_value<U>(self, value: U) -> InFile<U> { InFile }
+}
+struct RecordField;
+trait AstNode {}
+impl AstNode for RecordField {}
+
+fn takes_dyn(it: InFile<&dyn AstNode>) {}
+
+fn test() {
+ let x: InFile<()> = InFile;
+ let n = &RecordField;
+ takes_dyn(x.with_value(n));
+}
+ "#,
+ );
+}
+
+#[test]
+fn coerce_unsize_expected_type_3() {
+ check_no_mismatches(
+ r#"
+//- minicore: coerce_unsized
+enum Option<T> { Some(T), None }
+struct RecordField;
+trait AstNode {}
+impl AstNode for RecordField {}
+
+fn takes_dyn(it: Option<&dyn AstNode>) {}
+
+fn test() {
+ let x: InFile<()> = InFile;
+ let n = &RecordField;
+ takes_dyn(Option::Some(n));
+}
+ "#,
+ );
+}
+
+#[test]
+fn coerce_unsize_expected_type_4() {
+ check_no_mismatches(
+ r#"
+//- minicore: coerce_unsized
+use core::{marker::Unsize, ops::CoerceUnsized};
+
+struct B<T: ?Sized>(*const T);
+impl<T: ?Sized> B<T> {
+ fn new(t: T) -> Self { B(&t) }
+}
+
+impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<B<U>> for B<T> {}
+
+fn test() {
+ let _: B<[isize]> = B::new({ [1, 2, 3] });
+}
+ "#,
+ );
+}
+
+#[test]
+fn coerce_array_elems_lub() {
+ check_no_mismatches(
+ r#"
+fn f() {}
+fn g() {}
+
+fn test() {
+ [f, g];
+}
+ "#,
+ );
+}
+
+#[test]
+fn coerce_type_var() {
+ check_types(
+ r#"
+//- minicore: from, coerce_unsized
+fn test() {
+ let x = ();
+ let _: &() = &x.into();
+} //^^^^^^^^ ()
+"#,
+ )
+}
+
+#[test]
+fn coerce_overloaded_binary_op_rhs() {
+ check_types(
+ r#"
+//- minicore: deref, add
+
+struct String {}
+impl core::ops::Deref for String { type Target = str; }
+
+impl core::ops::Add<&str> for String {
+ type Output = String;
+}
+
+fn test() {
+ let s1 = String {};
+ let s2 = String {};
+ s1 + &s2;
+ //^^^^^^^^ String
+}
+
+ "#,
+ );
+}
+
+#[test]
+fn assign_coerce_struct_fields() {
+ check_no_mismatches(
+ r#"
+//- minicore: coerce_unsized
+struct S;
+trait Tr {}
+impl Tr for S {}
+struct V<T> { t: T }
+
+fn main() {
+ let a: V<&dyn Tr>;
+ a = V { t: &S };
+
+ let mut a: V<&dyn Tr> = V { t: &S };
+ a = V { t: &S };
+}
+ "#,
+ );
+}
+
+#[test]
+fn destructuring_assign_coerce_struct_fields() {
+ check(
+ r#"
+//- minicore: coerce_unsized
+struct S;
+trait Tr {}
+impl Tr for S {}
+struct V<T> { t: T }
+
+fn main() {
+ let a: V<&dyn Tr>;
+ (a,) = V { t: &S };
+ //^^^^expected V<&S>, got (V<&dyn Tr>,)
+
+ let mut a: V<&dyn Tr> = V { t: &S };
+ (a,) = V { t: &S };
+ //^^^^expected V<&S>, got (V<&dyn Tr>,)
+}
+ "#,
+ );
+}
--- /dev/null
- cov_mark::check!(ignore_gats);
+use expect_test::expect;
+
+use super::{check_infer, check_no_mismatches, check_types};
+
+#[test]
+fn bug_484() {
+ check_infer(
+ r#"
+ fn test() {
+ let x = if true {};
+ }
+ "#,
+ expect![[r#"
+ 10..37 '{ ... {}; }': ()
+ 20..21 'x': ()
+ 24..34 'if true {}': ()
+ 27..31 'true': bool
+ 32..34 '{}': ()
+ "#]],
+ );
+}
+
+#[test]
+fn no_panic_on_field_of_enum() {
+ check_infer(
+ r#"
+ enum X {}
+
+ fn test(x: X) {
+ x.some_field;
+ }
+ "#,
+ expect![[r#"
+ 19..20 'x': X
+ 25..46 '{ ...eld; }': ()
+ 31..32 'x': X
+ 31..43 'x.some_field': {unknown}
+ "#]],
+ );
+}
+
+#[test]
+fn bug_585() {
+ check_infer(
+ r#"
+ fn test() {
+ X {};
+ match x {
+ A::B {} => (),
+ A::Y() => (),
+ }
+ }
+ "#,
+ expect![[r#"
+ 10..88 '{ ... } }': ()
+ 16..20 'X {}': {unknown}
+ 26..86 'match ... }': ()
+ 32..33 'x': {unknown}
+ 44..51 'A::B {}': {unknown}
+ 55..57 '()': ()
+ 67..73 'A::Y()': {unknown}
+ 77..79 '()': ()
+ "#]],
+ );
+}
+
+#[test]
+fn bug_651() {
+ check_infer(
+ r#"
+ fn quux() {
+ let y = 92;
+ 1 + y;
+ }
+ "#,
+ expect![[r#"
+ 10..40 '{ ...+ y; }': ()
+ 20..21 'y': i32
+ 24..26 '92': i32
+ 32..33 '1': i32
+ 32..37 '1 + y': i32
+ 36..37 'y': i32
+ "#]],
+ );
+}
+
+#[test]
+fn recursive_vars() {
+ check_infer(
+ r#"
+ fn test() {
+ let y = unknown;
+ [y, &y];
+ }
+ "#,
+ expect![[r#"
+ 10..47 '{ ...&y]; }': ()
+ 20..21 'y': {unknown}
+ 24..31 'unknown': {unknown}
+ 37..44 '[y, &y]': [{unknown}; 2]
+ 38..39 'y': {unknown}
+ 41..43 '&y': &{unknown}
+ 42..43 'y': {unknown}
+ "#]],
+ );
+}
+
+#[test]
+fn recursive_vars_2() {
+ check_infer(
+ r#"
+ fn test() {
+ let x = unknown;
+ let y = unknown;
+ [(x, y), (&y, &x)];
+ }
+ "#,
+ expect![[r#"
+ 10..79 '{ ...x)]; }': ()
+ 20..21 'x': &{unknown}
+ 24..31 'unknown': &{unknown}
+ 41..42 'y': {unknown}
+ 45..52 'unknown': {unknown}
+ 58..76 '[(x, y..., &x)]': [(&{unknown}, {unknown}); 2]
+ 59..65 '(x, y)': (&{unknown}, {unknown})
+ 60..61 'x': &{unknown}
+ 63..64 'y': {unknown}
+ 67..75 '(&y, &x)': (&{unknown}, {unknown})
+ 68..70 '&y': &{unknown}
+ 69..70 'y': {unknown}
+ 72..74 '&x': &&{unknown}
+ 73..74 'x': &{unknown}
+ "#]],
+ );
+}
+
+#[test]
+fn array_elements_expected_type() {
+ check_no_mismatches(
+ r#"
+ fn test() {
+ let x: [[u32; 2]; 2] = [[1, 2], [3, 4]];
+ }
+ "#,
+ );
+}
+
+#[test]
+fn infer_std_crash_1() {
+ // caused stack overflow, taken from std
+ check_infer(
+ r#"
+ enum Maybe<T> {
+ Real(T),
+ Fake,
+ }
+
+ fn write() {
+ match something_unknown {
+ Maybe::Real(ref mut something) => (),
+ }
+ }
+ "#,
+ expect![[r#"
+ 53..138 '{ ... } }': ()
+ 59..136 'match ... }': ()
+ 65..82 'someth...nknown': Maybe<{unknown}>
+ 93..123 'Maybe:...thing)': Maybe<{unknown}>
+ 105..122 'ref mu...ething': &mut {unknown}
+ 127..129 '()': ()
+ "#]],
+ );
+}
+
+#[test]
+fn infer_std_crash_2() {
+ // caused "equating two type variables, ...", taken from std
+ check_infer(
+ r#"
+ fn test_line_buffer() {
+ &[0, b'\n', 1, b'\n'];
+ }
+ "#,
+ expect![[r#"
+ 22..52 '{ ...n']; }': ()
+ 28..49 '&[0, b...b'\n']': &[u8; 4]
+ 29..49 '[0, b'...b'\n']': [u8; 4]
+ 30..31 '0': u8
+ 33..38 'b'\n'': u8
+ 40..41 '1': u8
+ 43..48 'b'\n'': u8
+ "#]],
+ );
+}
+
+#[test]
+fn infer_std_crash_3() {
+ // taken from rustc
+ check_infer(
+ r#"
+ pub fn compute() {
+ match nope!() {
+ SizeSkeleton::Pointer { non_zero: true, tail } => {}
+ }
+ }
+ "#,
+ expect![[r#"
+ 17..107 '{ ... } }': ()
+ 23..105 'match ... }': ()
+ 29..36 'nope!()': {unknown}
+ 47..93 'SizeSk...tail }': {unknown}
+ 81..85 'true': bool
+ 81..85 'true': bool
+ 87..91 'tail': {unknown}
+ 97..99 '{}': ()
+ "#]],
+ );
+}
+
+#[test]
+fn infer_std_crash_4() {
+ // taken from rustc
+ check_infer(
+ r#"
+ pub fn primitive_type() {
+ match *self {
+ BorrowedRef { type_: Primitive(p), ..} => {},
+ }
+ }
+ "#,
+ expect![[r#"
+ 24..105 '{ ... } }': ()
+ 30..103 'match ... }': ()
+ 36..41 '*self': {unknown}
+ 37..41 'self': {unknown}
+ 52..90 'Borrow...), ..}': {unknown}
+ 73..85 'Primitive(p)': {unknown}
+ 83..84 'p': {unknown}
+ 94..96 '{}': ()
+ "#]],
+ );
+}
+
+#[test]
+fn infer_std_crash_5() {
+ // taken from rustc
+ check_infer(
+ r#"
+ fn extra_compiler_flags() {
+ for content in doesnt_matter {
+ let name = if doesnt_matter {
+ first
+ } else {
+ &content
+ };
+
+ let content = if ICE_REPORT_COMPILER_FLAGS_STRIP_VALUE.contains(&name) {
+ name
+ } else {
+ content
+ };
+ }
+ }
+ "#,
+ expect![[r#"
+ 26..322 '{ ... } }': ()
+ 32..320 'for co... }': ()
+ 36..43 'content': {unknown}
+ 47..60 'doesnt_matter': {unknown}
+ 61..320 '{ ... }': ()
+ 75..79 'name': &{unknown}
+ 82..166 'if doe... }': &{unknown}
+ 85..98 'doesnt_matter': bool
+ 99..128 '{ ... }': &{unknown}
+ 113..118 'first': &{unknown}
+ 134..166 '{ ... }': &{unknown}
+ 148..156 '&content': &{unknown}
+ 149..156 'content': {unknown}
+ 181..188 'content': &{unknown}
+ 191..313 'if ICE... }': &{unknown}
+ 194..231 'ICE_RE..._VALUE': {unknown}
+ 194..247 'ICE_RE...&name)': bool
+ 241..246 '&name': &&{unknown}
+ 242..246 'name': &{unknown}
+ 248..276 '{ ... }': &{unknown}
+ 262..266 'name': &{unknown}
+ 282..313 '{ ... }': {unknown}
+ 296..303 'content': {unknown}
+ "#]],
+ );
+}
+
+#[test]
+fn infer_nested_generics_crash() {
+ // another crash found typechecking rustc
+ check_infer(
+ r#"
+ struct Canonical<V> {
+ value: V,
+ }
+ struct QueryResponse<V> {
+ value: V,
+ }
+ fn test<R>(query_response: Canonical<QueryResponse<R>>) {
+ &query_response.value;
+ }
+ "#,
+ expect![[r#"
+ 91..105 'query_response': Canonical<QueryResponse<R>>
+ 136..166 '{ ...lue; }': ()
+ 142..163 '&query....value': &QueryResponse<R>
+ 143..157 'query_response': Canonical<QueryResponse<R>>
+ 143..163 'query_....value': QueryResponse<R>
+ "#]],
+ );
+}
+
+#[test]
+fn infer_paren_macro_call() {
+ check_infer(
+ r#"
+ macro_rules! bar { () => {0u32} }
+ fn test() {
+ let a = (bar!());
+ }
+ "#,
+ expect![[r#"
+ !0..4 '0u32': u32
+ 44..69 '{ ...()); }': ()
+ 54..55 'a': u32
+ "#]],
+ );
+}
+
+#[test]
+fn infer_array_macro_call() {
+ check_infer(
+ r#"
+ macro_rules! bar { () => {0u32} }
+ fn test() {
+ let a = [bar!()];
+ }
+ "#,
+ expect![[r#"
+ !0..4 '0u32': u32
+ 44..69 '{ ...()]; }': ()
+ 54..55 'a': [u32; 1]
+ 58..66 '[bar!()]': [u32; 1]
+ "#]],
+ );
+}
+
+#[test]
+fn bug_1030() {
+ check_infer(
+ r#"
+ struct HashSet<T, H>;
+ struct FxHasher;
+ type FxHashSet<T> = HashSet<T, FxHasher>;
+
+ impl<T, H> HashSet<T, H> {
+ fn default() -> HashSet<T, H> {}
+ }
+
+ pub fn main_loop() {
+ FxHashSet::default();
+ }
+ "#,
+ expect![[r#"
+ 143..145 '{}': HashSet<T, H>
+ 168..197 '{ ...t(); }': ()
+ 174..192 'FxHash...efault': fn default<{unknown}, FxHasher>() -> HashSet<{unknown}, FxHasher>
+ 174..194 'FxHash...ault()': HashSet<{unknown}, FxHasher>
+ "#]],
+ );
+}
+
+#[test]
+fn issue_2669() {
+ check_infer(
+ r#"
+ trait A {}
+ trait Write {}
+ struct Response<T> {}
+
+ trait D {
+ fn foo();
+ }
+
+ impl<T:A> D for Response<T> {
+ fn foo() {
+ end();
+ fn end<W: Write>() {
+ let _x: T = loop {};
+ }
+ }
+ }
+ "#,
+ expect![[r#"
+ 119..214 '{ ... }': ()
+ 129..132 'end': fn end<{unknown}>()
+ 129..134 'end()': ()
+ 163..208 '{ ... }': ()
+ 181..183 '_x': !
+ 190..197 'loop {}': !
+ 195..197 '{}': ()
+ "#]],
+ )
+}
+
+#[test]
+fn issue_2705() {
+ check_infer(
+ r#"
+ trait Trait {}
+ fn test() {
+ <Trait<u32>>::foo()
+ }
+ "#,
+ expect![[r#"
+ 25..52 '{ ...oo() }': ()
+ 31..48 '<Trait...>::foo': {unknown}
+ 31..50 '<Trait...:foo()': ()
+ "#]],
+ );
+}
+
+#[test]
+fn issue_2683_chars_impl() {
+ check_types(
+ r#"
+//- minicore: iterator
+pub struct Chars<'a> {}
+impl<'a> Iterator for Chars<'a> {
+ type Item = char;
+ fn next(&mut self) -> Option<char> { loop {} }
+}
+
+fn test() {
+ let chars: Chars<'_>;
+ (chars.next(), chars.nth(1));
+} //^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (Option<char>, Option<char>)
+"#,
+ );
+}
+
+#[test]
+fn issue_3999_slice() {
+ check_infer(
+ r#"
+ fn foo(params: &[usize]) {
+ match params {
+ [ps @ .., _] => {}
+ }
+ }
+ "#,
+ expect![[r#"
+ 7..13 'params': &[usize]
+ 25..80 '{ ... } }': ()
+ 31..78 'match ... }': ()
+ 37..43 'params': &[usize]
+ 54..66 '[ps @ .., _]': [usize]
+ 55..62 'ps @ ..': &[usize]
+ 60..62 '..': [usize]
+ 64..65 '_': usize
+ 70..72 '{}': ()
+ "#]],
+ );
+}
+
+#[test]
+fn issue_3999_struct() {
+ // rust-analyzer should not panic on seeing this malformed
+ // record pattern.
+ check_infer(
+ r#"
+ struct Bar {
+ a: bool,
+ }
+ fn foo(b: Bar) {
+ match b {
+ Bar { a: .. } => {},
+ }
+ }
+ "#,
+ expect![[r#"
+ 35..36 'b': Bar
+ 43..95 '{ ... } }': ()
+ 49..93 'match ... }': ()
+ 55..56 'b': Bar
+ 67..80 'Bar { a: .. }': Bar
+ 76..78 '..': bool
+ 84..86 '{}': ()
+ "#]],
+ );
+}
+
+#[test]
+fn issue_4235_name_conflicts() {
+ check_infer(
+ r#"
+ struct FOO {}
+ static FOO:FOO = FOO {};
+
+ impl FOO {
+ fn foo(&self) {}
+ }
+
+ fn main() {
+ let a = &FOO;
+ a.foo();
+ }
+ "#,
+ expect![[r#"
+ 31..37 'FOO {}': FOO
+ 63..67 'self': &FOO
+ 69..71 '{}': ()
+ 85..119 '{ ...o(); }': ()
+ 95..96 'a': &FOO
+ 99..103 '&FOO': &FOO
+ 100..103 'FOO': FOO
+ 109..110 'a': &FOO
+ 109..116 'a.foo()': ()
+ "#]],
+ );
+}
+
+#[test]
+fn issue_4465_dollar_crate_at_type() {
+ check_infer(
+ r#"
+ pub struct Foo {}
+ pub fn anything<T>() -> T {
+ loop {}
+ }
+ macro_rules! foo {
+ () => {{
+ let r: $crate::Foo = anything();
+ r
+ }};
+ }
+ fn main() {
+ let _a = foo!();
+ }
+ "#,
+ expect![[r#"
+ 44..59 '{ loop {} }': T
+ 50..57 'loop {}': !
+ 55..57 '{}': ()
+ !0..31 '{letr:...g();r}': Foo
+ !4..5 'r': Foo
+ !18..26 'anything': fn anything<Foo>() -> Foo
+ !18..28 'anything()': Foo
+ !29..30 'r': Foo
+ 163..187 '{ ...!(); }': ()
+ 173..175 '_a': Foo
+ "#]],
+ );
+}
+
+#[test]
+fn issue_6811() {
+ check_infer(
+ r#"
+ macro_rules! profile_function {
+ () => {
+ let _a = 1;
+ let _b = 1;
+ };
+ }
+ fn main() {
+ profile_function!();
+ }
+ "#,
+ expect![[r#"
+ !3..5 '_a': i32
+ !6..7 '1': i32
+ !11..13 '_b': i32
+ !14..15 '1': i32
+ 103..131 '{ ...!(); }': ()
+ "#]],
+ );
+}
+
+#[test]
+fn issue_4053_diesel_where_clauses() {
+ check_infer(
+ r#"
+ trait BoxedDsl<DB> {
+ type Output;
+ fn internal_into_boxed(self) -> Self::Output;
+ }
+
+ struct SelectStatement<From, Select, Distinct, Where, Order, LimitOffset, GroupBy, Locking> {
+ order: Order,
+ }
+
+ trait QueryFragment<DB: Backend> {}
+
+ trait Into<T> { fn into(self) -> T; }
+
+ impl<F, S, D, W, O, LOf, DB> BoxedDsl<DB>
+ for SelectStatement<F, S, D, W, O, LOf, G>
+ where
+ O: Into<dyn QueryFragment<DB>>,
+ {
+ type Output = XXX;
+
+ fn internal_into_boxed(self) -> Self::Output {
+ self.order.into();
+ }
+ }
+ "#,
+ expect![[r#"
+ 65..69 'self': Self
+ 267..271 'self': Self
+ 466..470 'self': SelectStatement<F, S, D, W, O, LOf, {unknown}, {unknown}>
+ 488..522 '{ ... }': ()
+ 498..502 'self': SelectStatement<F, S, D, W, O, LOf, {unknown}, {unknown}>
+ 498..508 'self.order': O
+ 498..515 'self.o...into()': dyn QueryFragment<DB>
+ "#]],
+ );
+}
+
+#[test]
+fn issue_4953() {
+ check_infer(
+ r#"
+ pub struct Foo(pub i64);
+ impl Foo {
+ fn test() -> Self { Self(0i64) }
+ }
+ "#,
+ expect![[r#"
+ 58..72 '{ Self(0i64) }': Foo
+ 60..64 'Self': Foo(i64) -> Foo
+ 60..70 'Self(0i64)': Foo
+ 65..69 '0i64': i64
+ "#]],
+ );
+ check_infer(
+ r#"
+ pub struct Foo<T>(pub T);
+ impl Foo<i64> {
+ fn test() -> Self { Self(0i64) }
+ }
+ "#,
+ expect![[r#"
+ 64..78 '{ Self(0i64) }': Foo<i64>
+ 66..70 'Self': Foo<i64>(i64) -> Foo<i64>
+ 66..76 'Self(0i64)': Foo<i64>
+ 71..75 '0i64': i64
+ "#]],
+ );
+}
+
+#[test]
+fn issue_4931() {
+ check_infer(
+ r#"
+ trait Div<T> {
+ type Output;
+ }
+
+ trait CheckedDiv: Div<()> {}
+
+ trait PrimInt: CheckedDiv<Output = ()> {
+ fn pow(self);
+ }
+
+ fn check<T: PrimInt>(i: T) {
+ i.pow();
+ }
+ "#,
+ expect![[r#"
+ 117..121 'self': Self
+ 148..149 'i': T
+ 154..170 '{ ...w(); }': ()
+ 160..161 'i': T
+ 160..167 'i.pow()': ()
+ "#]],
+ );
+}
+
+#[test]
+fn issue_4885() {
+ check_infer(
+ r#"
+ //- minicore: coerce_unsized, future
+ use core::future::Future;
+ trait Foo<R> {
+ type Bar;
+ }
+ fn foo<R, K>(key: &K) -> impl Future<Output = K::Bar>
+ where
+ K: Foo<R>,
+ {
+ bar(key)
+ }
+ fn bar<R, K>(key: &K) -> impl Future<Output = K::Bar>
+ where
+ K: Foo<R>,
+ {
+ }
+ "#,
+ expect![[r#"
+ 70..73 'key': &K
+ 132..148 '{ ...key) }': impl Future<Output = <K as Foo<R>>::Bar>
+ 138..141 'bar': fn bar<R, K>(&K) -> impl Future<Output = <K as Foo<R>>::Bar>
+ 138..146 'bar(key)': impl Future<Output = <K as Foo<R>>::Bar>
+ 142..145 'key': &K
+ 162..165 'key': &K
+ 224..227 '{ }': ()
+ "#]],
+ );
+}
+
+#[test]
+fn issue_4800() {
+ check_infer(
+ r#"
+ trait Debug {}
+
+ struct Foo<T>;
+
+ type E1<T> = (T, T, T);
+ type E2<T> = E1<E1<E1<(T, T, T)>>>;
+
+ impl Debug for Foo<E2<()>> {}
+
+ struct Request;
+
+ pub trait Future {
+ type Output;
+ }
+
+ pub struct PeerSet<D>;
+
+ impl<D> Service<Request> for PeerSet<D>
+ where
+ D: Discover,
+ D::Key: Debug,
+ {
+ type Error = ();
+ type Future = dyn Future<Output = Self::Error>;
+
+ fn call(&mut self) -> Self::Future {
+ loop {}
+ }
+ }
+
+ pub trait Discover {
+ type Key;
+ }
+
+ pub trait Service<Request> {
+ type Error;
+ type Future: Future<Output = Self::Error>;
+ fn call(&mut self) -> Self::Future;
+ }
+ "#,
+ expect![[r#"
+ 379..383 'self': &mut PeerSet<D>
+ 401..424 '{ ... }': dyn Future<Output = ()>
+ 411..418 'loop {}': !
+ 416..418 '{}': ()
+ 575..579 'self': &mut Self
+ "#]],
+ );
+}
+
+#[test]
+fn issue_4966() {
+ check_infer(
+ r#"
+ //- minicore: deref
+ pub trait IntoIterator {
+ type Item;
+ }
+
+ struct Repeat<A> { element: A }
+
+ struct Map<F> { f: F }
+
+ struct Vec<T> {}
+
+ impl<T> core::ops::Deref for Vec<T> {
+ type Target = [T];
+ }
+
+ fn from_iter<A, T: IntoIterator<Item = A>>(iter: T) -> Vec<A> {}
+
+ fn main() {
+ let inner = Map { f: |_: &f64| 0.0 };
+
+ let repeat = Repeat { element: inner };
+
+ let vec = from_iter(repeat);
+
+ vec.foo_bar();
+ }
+ "#,
+ expect![[r#"
+ 225..229 'iter': T
+ 244..246 '{}': Vec<A>
+ 258..402 '{ ...r(); }': ()
+ 268..273 'inner': Map<|&f64| -> f64>
+ 276..300 'Map { ... 0.0 }': Map<|&f64| -> f64>
+ 285..298 '|_: &f64| 0.0': |&f64| -> f64
+ 286..287 '_': &f64
+ 295..298 '0.0': f64
+ 311..317 'repeat': Repeat<Map<|&f64| -> f64>>
+ 320..345 'Repeat...nner }': Repeat<Map<|&f64| -> f64>>
+ 338..343 'inner': Map<|&f64| -> f64>
+ 356..359 'vec': Vec<IntoIterator::Item<Repeat<Map<|&f64| -> f64>>>>
+ 362..371 'from_iter': fn from_iter<IntoIterator::Item<Repeat<Map<|&f64| -> f64>>>, Repeat<Map<|&f64| -> f64>>>(Repeat<Map<|&f64| -> f64>>) -> Vec<IntoIterator::Item<Repeat<Map<|&f64| -> f64>>>>
+ 362..379 'from_i...epeat)': Vec<IntoIterator::Item<Repeat<Map<|&f64| -> f64>>>>
+ 372..378 'repeat': Repeat<Map<|&f64| -> f64>>
+ 386..389 'vec': Vec<IntoIterator::Item<Repeat<Map<|&f64| -> f64>>>>
+ 386..399 'vec.foo_bar()': {unknown}
+ "#]],
+ );
+}
+
+#[test]
+fn issue_6628() {
+ check_infer(
+ r#"
+//- minicore: fn
+struct S<T>();
+impl<T> S<T> {
+ fn f(&self, _t: T) {}
+ fn g<F: FnOnce(&T)>(&self, _f: F) {}
+}
+fn main() {
+ let s = S();
+ s.g(|_x| {});
+ s.f(10);
+}
+"#,
+ expect![[r#"
+ 40..44 'self': &S<T>
+ 46..48 '_t': T
+ 53..55 '{}': ()
+ 81..85 'self': &S<T>
+ 87..89 '_f': F
+ 94..96 '{}': ()
+ 109..160 '{ ...10); }': ()
+ 119..120 's': S<i32>
+ 123..124 'S': S<i32>() -> S<i32>
+ 123..126 'S()': S<i32>
+ 132..133 's': S<i32>
+ 132..144 's.g(|_x| {})': ()
+ 136..143 '|_x| {}': |&i32| -> ()
+ 137..139 '_x': &i32
+ 141..143 '{}': ()
+ 150..151 's': S<i32>
+ 150..157 's.f(10)': ()
+ 154..156 '10': i32
+ "#]],
+ );
+}
+
+#[test]
+fn issue_6852() {
+ check_infer(
+ r#"
+//- minicore: deref
+use core::ops::Deref;
+
+struct BufWriter {}
+
+struct Mutex<T> {}
+struct MutexGuard<'a, T> {}
+impl<T> Mutex<T> {
+ fn lock(&self) -> MutexGuard<'_, T> {}
+}
+impl<'a, T: 'a> Deref for MutexGuard<'a, T> {
+ type Target = T;
+}
+fn flush(&self) {
+ let w: &Mutex<BufWriter>;
+ *(w.lock());
+}
+"#,
+ expect![[r#"
+ 123..127 'self': &Mutex<T>
+ 150..152 '{}': MutexGuard<T>
+ 234..238 'self': &{unknown}
+ 240..290 '{ ...()); }': ()
+ 250..251 'w': &Mutex<BufWriter>
+ 276..287 '*(w.lock())': BufWriter
+ 278..279 'w': &Mutex<BufWriter>
+ 278..286 'w.lock()': MutexGuard<BufWriter>
+ "#]],
+ );
+}
+
+#[test]
+fn param_overrides_fn() {
+ check_types(
+ r#"
+ fn example(example: i32) {
+ fn f() {}
+ example;
+ //^^^^^^^ i32
+ }
+ "#,
+ )
+}
+
+#[test]
+fn lifetime_from_chalk_during_deref() {
+ check_types(
+ r#"
+//- minicore: deref
+struct Box<T: ?Sized> {}
+impl<T: ?Sized> core::ops::Deref for Box<T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ loop {}
+ }
+}
+
+trait Iterator {
+ type Item;
+}
+
+pub struct Iter<'a, T: 'a> {
+ inner: Box<dyn IterTrait<'a, T, Item = &'a T> + 'a>,
+}
+
+trait IterTrait<'a, T: 'a>: Iterator<Item = &'a T> {
+ fn clone_box(&self);
+}
+
+fn clone_iter<T>(s: Iter<T>) {
+ s.inner.clone_box();
+ //^^^^^^^^^^^^^^^^^^^ ()
+}
+"#,
+ )
+}
+
+#[test]
+fn issue_8686() {
+ check_infer(
+ r#"
+pub trait Try: FromResidual {
+ type Output;
+ type Residual;
+}
+pub trait FromResidual<R = <Self as Try>::Residual> {
+ fn from_residual(residual: R) -> Self;
+}
+
+struct ControlFlow<B, C>;
+impl<B, C> Try for ControlFlow<B, C> {
+ type Output = C;
+ type Residual = ControlFlow<B, !>;
+}
+impl<B, C> FromResidual for ControlFlow<B, C> {
+ fn from_residual(r: ControlFlow<B, !>) -> Self { ControlFlow }
+}
+
+fn test() {
+ ControlFlow::from_residual(ControlFlow::<u32, !>);
+}
+ "#,
+ expect![[r#"
+ 144..152 'residual': R
+ 365..366 'r': ControlFlow<B, !>
+ 395..410 '{ ControlFlow }': ControlFlow<B, C>
+ 397..408 'ControlFlow': ControlFlow<B, C>
+ 424..482 '{ ...!>); }': ()
+ 430..456 'Contro...sidual': fn from_residual<ControlFlow<u32, {unknown}>, ControlFlow<u32, !>>(ControlFlow<u32, !>) -> ControlFlow<u32, {unknown}>
+ 430..479 'Contro...2, !>)': ControlFlow<u32, {unknown}>
+ 457..478 'Contro...32, !>': ControlFlow<u32, !>
+ "#]],
+ );
+}
+
+#[test]
+fn cfg_tail() {
+ // https://github.com/rust-lang/rust-analyzer/issues/8378
+ check_infer(
+ r#"
+ fn fake_tail(){
+ { "first" }
+ #[cfg(never)] 9
+ }
+ fn multiple_fake(){
+ { "fake" }
+ { "fake" }
+ { "second" }
+ #[cfg(never)] { 11 }
+ #[cfg(never)] 12;
+ #[cfg(never)] 13
+ }
+ fn no_normal_tail(){
+ { "third" }
+ #[cfg(never)] 14;
+ #[cfg(never)] 15;
+ }
+ fn no_actual_tail(){
+ { "fourth" };
+ #[cfg(never)] 14;
+ #[cfg(never)] 15
+ }
+ "#,
+ expect![[r#"
+ 14..53 '{ ...)] 9 }': ()
+ 20..31 '{ "first" }': ()
+ 22..29 '"first"': &str
+ 72..190 '{ ...] 13 }': ()
+ 78..88 '{ "fake" }': &str
+ 80..86 '"fake"': &str
+ 93..103 '{ "fake" }': &str
+ 95..101 '"fake"': &str
+ 108..120 '{ "second" }': ()
+ 110..118 '"second"': &str
+ 210..273 '{ ... 15; }': ()
+ 216..227 '{ "third" }': ()
+ 218..225 '"third"': &str
+ 293..357 '{ ...] 15 }': ()
+ 299..311 '{ "fourth" }': &str
+ 301..309 '"fourth"': &str
+ "#]],
+ )
+}
+
+#[test]
+fn impl_trait_in_option_9530() {
+ check_types(
+ r#"
+//- minicore: sized
+struct Option<T>;
+impl<T> Option<T> {
+ fn unwrap(self) -> T { loop {} }
+}
+fn make() -> Option<impl Copy> { Option }
+trait Copy {}
+fn test() {
+ let o = make();
+ o.unwrap();
+ //^^^^^^^^^^ impl Copy
+}
+ "#,
+ )
+}
+
+#[test]
+fn bare_dyn_trait_binders_9639() {
+ check_no_mismatches(
+ r#"
+//- minicore: fn, coerce_unsized
+fn infix_parse<T, S>(_state: S, _level_code: &Fn(S)) -> T {
+ loop {}
+}
+
+fn parse_arule() {
+ infix_parse((), &(|_recurse| ()))
+}
+ "#,
+ )
+}
+
+#[test]
+fn call_expected_type_closure() {
+ check_types(
+ r#"
+//- minicore: fn, option
+
+fn map<T, U>(o: Option<T>, f: impl FnOnce(T) -> U) -> Option<U> { loop {} }
+struct S {
+ field: u32
+}
+
+fn test() {
+ let o = Some(S { field: 2 });
+ let _: Option<()> = map(o, |s| { s.field; });
+ // ^^^^^^^ u32
+}
+ "#,
+ );
+}
+
+#[test]
+fn coerce_diesel_panic() {
+ check_no_mismatches(
+ r#"
+//- minicore: option
+
+trait TypeMetadata {
+ type MetadataLookup;
+}
+
+pub struct Output<'a, T, DB>
+where
+ DB: TypeMetadata,
+ DB::MetadataLookup: 'a,
+{
+ out: T,
+ metadata_lookup: Option<&'a DB::MetadataLookup>,
+}
+
+impl<'a, T, DB: TypeMetadata> Output<'a, T, DB> {
+ pub fn new(out: T, metadata_lookup: &'a DB::MetadataLookup) -> Self {
+ Output {
+ out,
+ metadata_lookup: Some(metadata_lookup),
+ }
+ }
+}
+ "#,
+ );
+}
+
+#[test]
+fn bitslice_panic() {
+ check_no_mismatches(
+ r#"
+//- minicore: option, deref
+
+pub trait BitView {
+ type Store;
+}
+
+pub struct Lsb0;
+
+pub struct BitArray<V: BitView> { }
+
+pub struct BitSlice<T> { }
+
+impl<V: BitView> core::ops::Deref for BitArray<V> {
+ type Target = BitSlice<V::Store>;
+}
+
+impl<T> BitSlice<T> {
+ pub fn split_first(&self) -> Option<(T, &Self)> { loop {} }
+}
+
+fn multiexp_inner() {
+ let exp: &BitArray<Foo>;
+ exp.split_first();
+}
+ "#,
+ );
+}
+
+#[test]
+fn macro_expands_to_impl_trait() {
+ check_no_mismatches(
+ r#"
+trait Foo {}
+
+macro_rules! ty {
+ () => {
+ impl Foo
+ }
+}
+
+fn foo(_: ty!()) {}
+
+fn bar() {
+ foo(());
+}
+ "#,
+ )
+}
+
+#[test]
+fn nested_macro_in_fn_params() {
+ check_no_mismatches(
+ r#"
+macro_rules! U32Inner {
+ () => {
+ u32
+ };
+}
+
+macro_rules! U32 {
+ () => {
+ U32Inner!()
+ };
+}
+
+fn mamba(a: U32!(), p: u32) -> u32 {
+ a
+}
+ "#,
+ )
+}
+
+#[test]
+fn for_loop_block_expr_iterable() {
+ check_infer(
+ r#"
+fn test() {
+ for _ in { let x = 0; } {
+ let y = 0;
+ }
+}
+ "#,
+ expect![[r#"
+ 10..68 '{ ... } }': ()
+ 16..66 'for _ ... }': ()
+ 20..21 '_': {unknown}
+ 25..39 '{ let x = 0; }': ()
+ 31..32 'x': i32
+ 35..36 '0': i32
+ 40..66 '{ ... }': ()
+ 54..55 'y': i32
+ 58..59 '0': i32
+ "#]],
+ );
+}
+
+#[test]
+fn while_loop_block_expr_iterable() {
+ check_infer(
+ r#"
+fn test() {
+ while { true } {
+ let y = 0;
+ }
+}
+ "#,
+ expect![[r#"
+ 10..59 '{ ... } }': ()
+ 16..57 'while ... }': ()
+ 22..30 '{ true }': bool
+ 24..28 'true': bool
+ 31..57 '{ ... }': ()
+ 45..46 'y': i32
+ 49..50 '0': i32
+ "#]],
+ );
+}
+
+#[test]
+fn bug_11242() {
+ // FIXME: wrong, should be u32
+ check_types(
+ r#"
+fn foo<A, B>()
+where
+ A: IntoIterator<Item = u32>,
+ B: IntoIterator<Item = usize>,
+{
+ let _x: <A as IntoIterator>::Item;
+ // ^^ {unknown}
+}
+
+pub trait Iterator {
+ type Item;
+}
+
+pub trait IntoIterator {
+ type Item;
+ type IntoIter: Iterator<Item = Self::Item>;
+}
+
+impl<I: Iterator> IntoIterator for I {
+ type Item = I::Item;
+ type IntoIter = I;
+}
+"#,
+ );
+}
+
+#[test]
+fn bug_11659() {
+ check_no_mismatches(
+ r#"
+struct LinkArray<const N: usize, LD>(LD);
+fn f<const N: usize, LD>(x: LD) -> LinkArray<N, LD> {
+ let r = LinkArray::<N, LD>(x);
+ r
+}
+
+fn test() {
+ let x = f::<2, i32>(5);
+ let y = LinkArray::<52, LinkArray<2, i32>>(x);
+}
+ "#,
+ );
+ check_no_mismatches(
+ r#"
+struct LinkArray<LD, const N: usize>(LD);
+fn f<const N: usize, LD>(x: LD) -> LinkArray<LD, N> {
+ let r = LinkArray::<LD, N>(x);
+ r
+}
+
+fn test() {
+ let x = f::<i32, 2>(5);
+ let y = LinkArray::<LinkArray<i32, 2>, 52>(x);
+}
+ "#,
+ );
+}
+
+#[test]
+fn const_generic_error_tolerance() {
+ check_no_mismatches(
+ r#"
+#[lang = "sized"]
+pub trait Sized {}
+
+struct CT<const N: usize, T>(T);
+struct TC<T, const N: usize>(T);
+fn f<const N: usize, T>(x: T) -> (CT<N, T>, TC<T, N>) {
+ let l = CT::<N, T>(x);
+ let r = TC::<N, T>(x);
+ (l, r)
+}
+
+trait TR1<const N: usize>;
+trait TR2<const N: usize>;
+
+impl<const N: usize, T> TR1<N> for CT<N, T>;
+impl<const N: usize, T> TR1<5> for TC<T, N>;
+impl<const N: usize, T> TR2<N> for CT<T, N>;
+
+trait TR3<const N: usize> {
+ fn tr3(&self) -> &Self;
+}
+
+impl<const N: usize, T> TR3<5> for TC<T, N> {
+ fn tr3(&self) -> &Self {
+ self
+ }
+}
+
+impl<const N: usize, T> TR3<Item = 5> for TC<T, N> {}
+impl<const N: usize, T> TR3<T> for TC<T, N> {}
+
+fn impl_trait<const N: usize>(inp: impl TR1<N>) {}
+fn dyn_trait<const N: usize>(inp: &dyn TR2<N>) {}
+fn impl_trait_bad<'a, const N: usize>(inp: impl TR1<i32>) -> impl TR1<'a, i32> {}
+fn impl_trait_very_bad<const N: usize>(inp: impl TR1<Item = i32>) -> impl TR1<'a, Item = i32, 5, Foo = N> {}
+
+fn test() {
+ f::<2, i32>(5);
+ f::<2, 2>(5);
+ f(5);
+ f::<i32>(5);
+ CT::<52, CT<2, i32>>(x);
+ CT::<CT<2, i32>>(x);
+ impl_trait_bad(5);
+ impl_trait_bad(12);
+ TR3<5>::tr3();
+ TR3<{ 2+3 }>::tr3();
+ TC::<i32, 10>(5).tr3();
+ TC::<i32, 20>(5).tr3();
+ TC::<i32, i32>(5).tr3();
+ TC::<i32, { 7 + 3 }>(5).tr3();
+}
+ "#,
+ );
+}
+
+#[test]
+fn const_generic_impl_trait() {
+ check_no_mismatches(
+ r#"
+ //- minicore: from
+
+ struct Foo<T, const M: usize>;
+
+ trait Tr<T> {
+ fn f(T) -> Self;
+ }
+
+ impl<T, const M: usize> Tr<[T; M]> for Foo<T, M> {
+ fn f(_: [T; M]) -> Self {
+ Self
+ }
+ }
+
+ fn test() {
+ Foo::f([1, 2, 7, 10]);
+ }
+ "#,
+ );
+}
+
+#[test]
+fn nalgebra_factorial() {
+ check_no_mismatches(
+ r#"
+ const FACTORIAL: [u128; 4] = [1, 1, 2, 6];
+
+ fn factorial(n: usize) -> u128 {
+ match FACTORIAL.get(n) {
+ Some(f) => *f,
+ None => panic!("{}! is greater than u128::MAX", n),
+ }
+ }
+ "#,
+ )
+}
+
+#[test]
+fn regression_11688_1() {
+ check_no_mismatches(
+ r#"
+ pub struct Buffer<T>(T);
+ type Writer = Buffer<u8>;
+ impl<T> Buffer<T> {
+ fn extend_from_array<const N: usize>(&mut self, xs: &[T; N]) {
+ loop {}
+ }
+ }
+ trait Encode<S> {
+ fn encode(self, w: &mut Writer, s: &mut S);
+ }
+ impl<S> Encode<S> for u8 {
+ fn encode(self, w: &mut Writer, _: &mut S) {
+ w.extend_from_array(&self.to_le_bytes());
+ }
+ }
+ "#,
+ );
+}
+
+#[test]
+fn regression_11688_2() {
+ check_types(
+ r#"
+ union MaybeUninit<T> {
+ uninit: (),
+ value: T,
+ }
+
+ impl<T> MaybeUninit<T> {
+ fn uninit_array<const LEN: usize>() -> [Self; LEN] {
+ loop {}
+ }
+ }
+
+ fn main() {
+ let x = MaybeUninit::<i32>::uninit_array::<1>();
+ //^ [MaybeUninit<i32>; 1]
+ }
+ "#,
+ );
+}
+
+#[test]
+fn regression_11688_3() {
+ check_types(
+ r#"
+ //- minicore: iterator
+ struct Ar<T, const N: u8>(T);
+ fn f<const LEN: usize, T, const BASE: u8>(
+ num_zeros: usize,
+ ) -> dyn Iterator<Item = [Ar<T, BASE>; LEN]> {
+ loop {}
+ }
+ fn dynamic_programming() {
+ for board in f::<9, u8, 7>(1) {
+ //^^^^^ [Ar<u8, 7>; 9]
+ }
+ }
+ "#,
+ );
+}
+
+#[test]
+fn regression_11688_4() {
+ check_types(
+ r#"
+ trait Bar<const C: usize> {
+ fn baz(&self) -> [i32; C];
+ }
+
+ fn foo(x: &dyn Bar<2>) {
+ x.baz();
+ //^^^^^^^ [i32; 2]
+ }
+ "#,
+ )
+}
+
+#[test]
+fn gat_crash_1() {
- // FIXME: This test currently crashes rust analyzer in a debug build but not in a
- // release build (i.e. for the user). With the assumption that tests will always be run
- // in debug mode, we catch the unwind and expect that it panicked. See the
- // [`crate::utils::generics`] function for more information.
- cov_mark::check!(ignore_gats);
- std::panic::catch_unwind(|| {
- check_no_mismatches(
- r#"
+ check_no_mismatches(
+ r#"
+trait ATrait {}
+
+trait Crash {
+ type Member<const N: usize>: ATrait;
+ fn new<const N: usize>() -> Self::Member<N>;
+}
+
+fn test<T: Crash>() {
+ T::new();
+}
+"#,
+ );
+}
+
+#[test]
+fn gat_crash_2() {
+ check_no_mismatches(
+ r#"
+pub struct InlineStorage {}
+
+pub struct InlineStorageHandle<T: ?Sized> {}
+
+pub unsafe trait Storage {
+ type Handle<T: ?Sized>;
+ fn create<T: ?Sized>() -> Self::Handle<T>;
+}
+
+unsafe impl Storage for InlineStorage {
+ type Handle<T: ?Sized> = InlineStorageHandle<T>;
+}
+"#,
+ );
+}
+
+#[test]
+fn gat_crash_3() {
- type Item;
- type Member<T>: Collection<Item = T>;
- fn add(&mut self, value: Self::Item) -> Result<(), Self::Error>;
++ check_no_mismatches(
++ r#"
+trait Collection {
- data: [T; N],
++type Item;
++type Member<T>: Collection<Item = T>;
++fn add(&mut self, value: Self::Item) -> Result<(), Self::Error>;
+}
+struct ConstGen<T, const N: usize> {
- type Item = T;
- type Member<U> = ConstGen<U, N>;
++data: [T; N],
+}
+impl<T, const N: usize> Collection for ConstGen<T, N> {
- "#,
- );
- })
- .expect_err("must panic");
++type Item = T;
++type Member<U> = ConstGen<U, N>;
+}
++ "#,
++ );
+}
+
+#[test]
+fn cfgd_out_self_param() {
+ cov_mark::check!(cfgd_out_self_param);
+ check_no_mismatches(
+ r#"
+struct S;
+impl S {
+ fn f(#[cfg(never)] &self) {}
+}
+
+fn f(s: S) {
+ s.f();
+}
+"#,
+ );
+}
+
+#[test]
+fn rust_161_option_clone() {
+ check_types(
+ r#"
+//- minicore: option, drop
+
+fn test(o: &Option<i32>) {
+ o.my_clone();
+ //^^^^^^^^^^^^ Option<i32>
+}
+
+pub trait MyClone: Sized {
+ fn my_clone(&self) -> Self;
+}
+
+impl<T> const MyClone for Option<T>
+where
+ T: ~const MyClone + ~const Drop + ~const Destruct,
+{
+ fn my_clone(&self) -> Self {
+ match self {
+ Some(x) => Some(x.my_clone()),
+ None => None,
+ }
+ }
+}
+
+impl const MyClone for i32 {
+ fn my_clone(&self) -> Self {
+ *self
+ }
+}
+
+pub trait Destruct {}
+
+impl<T: ?Sized> const Destruct for T {}
+"#,
+ );
+}
+
+#[test]
+fn rust_162_option_clone() {
+ check_types(
+ r#"
+//- minicore: option, drop
+
+fn test(o: &Option<i32>) {
+ o.my_clone();
+ //^^^^^^^^^^^^ Option<i32>
+}
+
+pub trait MyClone: Sized {
+ fn my_clone(&self) -> Self;
+}
+
+impl<T> const MyClone for Option<T>
+where
+ T: ~const MyClone + ~const Destruct,
+{
+ fn my_clone(&self) -> Self {
+ match self {
+ Some(x) => Some(x.my_clone()),
+ None => None,
+ }
+ }
+}
+
+impl const MyClone for i32 {
+ fn my_clone(&self) -> Self {
+ *self
+ }
+}
+
+#[lang = "destruct"]
+pub trait Destruct {}
+"#,
+ );
+}
+
+#[test]
+fn tuple_struct_pattern_with_unmatched_args_crash() {
+ check_infer(
+ r#"
+struct S(usize);
+fn main() {
+ let S(.., a, b) = S(1);
+ let (.., a, b) = (1,);
+}
+ "#,
+ expect![[r#"
+ 27..85 '{ ...1,); }': ()
+ 37..48 'S(.., a, b)': S
+ 43..44 'a': usize
+ 46..47 'b': {unknown}
+ 51..52 'S': S(usize) -> S
+ 51..55 'S(1)': S
+ 53..54 '1': usize
+ 65..75 '(.., a, b)': (i32, {unknown})
+ 70..71 'a': i32
+ 73..74 'b': {unknown}
+ 78..82 '(1,)': (i32,)
+ 79..80 '1': i32
+ "#]],
+ );
+}
+
+#[test]
+fn trailing_empty_macro() {
+ check_no_mismatches(
+ r#"
+macro_rules! m2 {
+ ($($t:tt)*) => {$($t)*};
+}
+
+fn macrostmts() -> u8 {
+ m2! { 0 }
+ m2! {}
+}
+ "#,
+ );
+}
++
++#[test]
++fn dyn_with_unresolved_trait() {
++ check_types(
++ r#"
++fn foo(a: &dyn DoesNotExist) {
++ a.bar();
++ //^&{unknown}
++}
++ "#,
++ );
++}
++
++#[test]
++fn self_assoc_with_const_generics_crash() {
++ check_no_mismatches(
++ r#"
++trait Trait { type Item; }
++impl<T, const N: usize> Trait for [T; N] {
++ type Item = ();
++ fn f<U>(_: Self::Item) {}
++}
++ "#,
++ );
++}
--- /dev/null
- struct Foo;
- impl Foo { const ASSOC_CONST: u32 = 0; }
- const GLOBAL_CONST: u32 = 101;
- fn test() {
- const LOCAL_CONST: u32 = 99;
- let x = LOCAL_CONST;
- let z = GLOBAL_CONST;
- let id = Foo::ASSOC_CONST;
- }
- "#,
+use expect_test::expect;
+
+use super::{check, check_infer, check_no_mismatches, check_types};
+
+#[test]
+fn infer_box() {
+ check_types(
+ r#"
+//- /main.rs crate:main deps:std
+fn test() {
+ let x = box 1;
+ let t = (x, box x, box &1, box [1]);
+ t;
+} //^ (Box<i32>, Box<Box<i32>>, Box<&i32>, Box<[i32; 1]>)
+
+//- /std.rs crate:std
+#[prelude_import] use prelude::*;
+mod prelude {}
+
+mod boxed {
+ #[lang = "owned_box"]
+ pub struct Box<T: ?Sized> {
+ inner: *mut T,
+ }
+}
+"#,
+ );
+}
+
+#[test]
+fn infer_box_with_allocator() {
+ check_types(
+ r#"
+//- /main.rs crate:main deps:std
+fn test() {
+ let x = box 1;
+ let t = (x, box x, box &1, box [1]);
+ t;
+} //^ (Box<i32, {unknown}>, Box<Box<i32, {unknown}>, {unknown}>, Box<&i32, {unknown}>, Box<[i32; 1], {unknown}>)
+
+//- /std.rs crate:std
+#[prelude_import] use prelude::*;
+mod boxed {
+ #[lang = "owned_box"]
+ pub struct Box<T: ?Sized, A: Allocator> {
+ inner: *mut T,
+ allocator: A,
+ }
+}
+"#,
+ );
+}
+
+#[test]
+fn infer_adt_self() {
+ check_types(
+ r#"
+enum Nat { Succ(Self), Demo(Nat), Zero }
+
+fn test() {
+ let foo: Nat = Nat::Zero;
+ if let Nat::Succ(x) = foo {
+ x;
+ } //^ Nat
+}
+"#,
+ );
+}
+
+#[test]
+fn self_in_struct_lit() {
+ check_infer(
+ r#"
+ //- /main.rs
+ struct S<T> { x: T }
+
+ impl S<u32> {
+ fn foo() {
+ Self { x: 1 };
+ }
+ }
+ "#,
+ expect![[r#"
+ 49..79 '{ ... }': ()
+ 59..72 'Self { x: 1 }': S<u32>
+ 69..70 '1': u32
+ "#]],
+ );
+}
+
+#[test]
+fn type_alias_in_struct_lit() {
+ check_infer(
+ r#"
+ //- /main.rs
+ struct S<T> { x: T }
+
+ type SS = S<u32>;
+
+ fn foo() {
+ SS { x: 1 };
+ }
+ "#,
+ expect![[r#"
+ 50..70 '{ ...1 }; }': ()
+ 56..67 'SS { x: 1 }': S<u32>
+ 64..65 '1': u32
+ "#]],
+ );
+}
+
+#[test]
+fn infer_ranges() {
+ check_types(
+ r#"
+//- minicore: range
+fn test() {
+ let a = ..;
+ let b = 1..;
+ let c = ..2u32;
+ let d = 1..2usize;
+ let e = ..=10;
+ let f = 'a'..='z';
+
+ let t = (a, b, c, d, e, f);
+ t;
+} //^ (RangeFull, RangeFrom<i32>, RangeTo<u32>, Range<usize>, RangeToInclusive<i32>, RangeInclusive<char>)
+"#,
+ );
+}
+
+#[test]
+fn infer_while_let() {
+ check_types(
+ r#"
+enum Option<T> { Some(T), None }
+
+fn test() {
+ let foo: Option<f32> = None;
+ while let Option::Some(x) = foo {
+ x;
+ } //^ f32
+}
+"#,
+ );
+}
+
+#[test]
+fn infer_basics() {
+ check_infer(
+ r#"
+fn test(a: u32, b: isize, c: !, d: &str) {
+ a;
+ b;
+ c;
+ d;
+ 1usize;
+ 1isize;
+ "test";
+ 1.0f32;
+}
+"#,
+ expect![[r#"
+ 8..9 'a': u32
+ 16..17 'b': isize
+ 26..27 'c': !
+ 32..33 'd': &str
+ 41..120 '{ ...f32; }': ()
+ 47..48 'a': u32
+ 54..55 'b': isize
+ 61..62 'c': !
+ 68..69 'd': &str
+ 75..81 '1usize': usize
+ 87..93 '1isize': isize
+ 99..105 '"test"': &str
+ 111..117 '1.0f32': f32
+ "#]],
+ );
+}
+
+#[test]
+fn infer_let() {
+ check_infer(
+ r#"
+fn test() {
+ let a = 1isize;
+ let b: usize = 1;
+ let c = b;
+ let d: u32;
+ let e;
+ let f: i32 = e;
+}
+"#,
+ expect![[r#"
+ 10..117 '{ ...= e; }': ()
+ 20..21 'a': isize
+ 24..30 '1isize': isize
+ 40..41 'b': usize
+ 51..52 '1': usize
+ 62..63 'c': usize
+ 66..67 'b': usize
+ 77..78 'd': u32
+ 93..94 'e': i32
+ 104..105 'f': i32
+ 113..114 'e': i32
+ "#]],
+ );
+}
+
+#[test]
+fn infer_paths() {
+ check_infer(
+ r#"
+fn a() -> u32 { 1 }
+
+mod b {
+ fn c() -> u32 { 1 }
+}
+
+fn test() {
+ a();
+ b::c();
+}
+"#,
+ expect![[r#"
+ 14..19 '{ 1 }': u32
+ 16..17 '1': u32
+ 47..52 '{ 1 }': u32
+ 49..50 '1': u32
+ 66..90 '{ ...c(); }': ()
+ 72..73 'a': fn a() -> u32
+ 72..75 'a()': u32
+ 81..85 'b::c': fn c() -> u32
+ 81..87 'b::c()': u32
+ "#]],
+ );
+}
+
+#[test]
+fn infer_path_type() {
+ check_infer(
+ r#"
+struct S;
+
+impl S {
+ fn foo() -> i32 { 1 }
+}
+
+fn test() {
+ S::foo();
+ <S>::foo();
+}
+"#,
+ expect![[r#"
+ 40..45 '{ 1 }': i32
+ 42..43 '1': i32
+ 59..92 '{ ...o(); }': ()
+ 65..71 'S::foo': fn foo() -> i32
+ 65..73 'S::foo()': i32
+ 79..87 '<S>::foo': fn foo() -> i32
+ 79..89 '<S>::foo()': i32
+ "#]],
+ );
+}
+
+#[test]
+fn infer_struct() {
+ check_infer(
+ r#"
+struct A {
+ b: B,
+ c: C,
+}
+struct B;
+struct C(usize);
+
+fn test() {
+ let c = C(1);
+ B;
+ let a: A = A { b: B, c: C(1) };
+ a.b;
+ a.c;
+}
+"#,
+ expect![[r#"
+ 71..153 '{ ...a.c; }': ()
+ 81..82 'c': C
+ 85..86 'C': C(usize) -> C
+ 85..89 'C(1)': C
+ 87..88 '1': usize
+ 95..96 'B': B
+ 106..107 'a': A
+ 113..132 'A { b:...C(1) }': A
+ 120..121 'B': B
+ 126..127 'C': C(usize) -> C
+ 126..130 'C(1)': C
+ 128..129 '1': usize
+ 138..139 'a': A
+ 138..141 'a.b': B
+ 147..148 'a': A
+ 147..150 'a.c': C
+ "#]],
+ );
+}
+
+#[test]
+fn infer_enum() {
+ check_infer(
+ r#"
+enum E {
+ V1 { field: u32 },
+ V2
+}
+fn test() {
+ E::V1 { field: 1 };
+ E::V2;
+}
+"#,
+ expect![[r#"
+ 51..89 '{ ...:V2; }': ()
+ 57..75 'E::V1 ...d: 1 }': E
+ 72..73 '1': u32
+ 81..86 'E::V2': E
+ "#]],
+ );
+}
+
+#[test]
+fn infer_union() {
+ check_infer(
+ r#"
+union MyUnion {
+ foo: u32,
+ bar: f32,
+}
+
+fn test() {
+ let u = MyUnion { foo: 0 };
+ unsafe { baz(u); }
+ let u = MyUnion { bar: 0.0 };
+ unsafe { baz(u); }
+}
+
+unsafe fn baz(u: MyUnion) {
+ let inner = u.foo;
+ let inner = u.bar;
+}
+"#,
+ expect![[r#"
+ 57..172 '{ ...); } }': ()
+ 67..68 'u': MyUnion
+ 71..89 'MyUnio...o: 0 }': MyUnion
+ 86..87 '0': u32
+ 95..113 'unsafe...(u); }': ()
+ 95..113 'unsafe...(u); }': ()
+ 104..107 'baz': fn baz(MyUnion)
+ 104..110 'baz(u)': ()
+ 108..109 'u': MyUnion
+ 122..123 'u': MyUnion
+ 126..146 'MyUnio... 0.0 }': MyUnion
+ 141..144 '0.0': f32
+ 152..170 'unsafe...(u); }': ()
+ 152..170 'unsafe...(u); }': ()
+ 161..164 'baz': fn baz(MyUnion)
+ 161..167 'baz(u)': ()
+ 165..166 'u': MyUnion
+ 188..189 'u': MyUnion
+ 200..249 '{ ...bar; }': ()
+ 210..215 'inner': u32
+ 218..219 'u': MyUnion
+ 218..223 'u.foo': u32
+ 233..238 'inner': f32
+ 241..242 'u': MyUnion
+ 241..246 'u.bar': f32
+ "#]],
+ );
+}
+
+#[test]
+fn infer_refs() {
+ check_infer(
+ r#"
+fn test(a: &u32, b: &mut u32, c: *const u32, d: *mut u32) {
+ a;
+ *a;
+ &a;
+ &mut a;
+ b;
+ *b;
+ &b;
+ c;
+ *c;
+ d;
+ *d;
+}
+ "#,
+ expect![[r#"
+ 8..9 'a': &u32
+ 17..18 'b': &mut u32
+ 30..31 'c': *const u32
+ 45..46 'd': *mut u32
+ 58..149 '{ ... *d; }': ()
+ 64..65 'a': &u32
+ 71..73 '*a': u32
+ 72..73 'a': &u32
+ 79..81 '&a': &&u32
+ 80..81 'a': &u32
+ 87..93 '&mut a': &mut &u32
+ 92..93 'a': &u32
+ 99..100 'b': &mut u32
+ 106..108 '*b': u32
+ 107..108 'b': &mut u32
+ 114..116 '&b': &&mut u32
+ 115..116 'b': &mut u32
+ 122..123 'c': *const u32
+ 129..131 '*c': u32
+ 130..131 'c': *const u32
+ 137..138 'd': *mut u32
+ 144..146 '*d': u32
+ 145..146 'd': *mut u32
+ "#]],
+ );
+}
+
+#[test]
+fn infer_raw_ref() {
+ check_infer(
+ r#"
+fn test(a: i32) {
+ &raw mut a;
+ &raw const a;
+}
+"#,
+ expect![[r#"
+ 8..9 'a': i32
+ 16..53 '{ ...t a; }': ()
+ 22..32 '&raw mut a': *mut i32
+ 31..32 'a': i32
+ 38..50 '&raw const a': *const i32
+ 49..50 'a': i32
+ "#]],
+ );
+}
+
+#[test]
+fn infer_literals() {
+ check_infer(
+ r##"
+ fn test() {
+ 5i32;
+ 5f32;
+ 5f64;
+ "hello";
+ b"bytes";
+ 'c';
+ b'b';
+ 3.14;
+ 5000;
+ false;
+ true;
+ r#"
+ //! doc
+ // non-doc
+ mod foo {}
+ "#;
+ br#"yolo"#;
+ let a = b"a\x20b\
+ c";
+ let b = br"g\
+h";
+ let c = br#"x"\"yb"#;
+ }
+ "##,
+ expect![[r##"
+ 18..478 '{ ... }': ()
+ 32..36 '5i32': i32
+ 50..54 '5f32': f32
+ 68..72 '5f64': f64
+ 86..93 '"hello"': &str
+ 107..115 'b"bytes"': &[u8; 5]
+ 129..132 ''c'': char
+ 146..150 'b'b'': u8
+ 164..168 '3.14': f64
+ 182..186 '5000': i32
+ 200..205 'false': bool
+ 219..223 'true': bool
+ 237..333 'r#" ... "#': &str
+ 347..357 'br#"yolo"#': &[u8; 4]
+ 375..376 'a': &[u8; 4]
+ 379..403 'b"a\x2... c"': &[u8; 4]
+ 421..422 'b': &[u8; 4]
+ 425..433 'br"g\ h"': &[u8; 4]
+ 451..452 'c': &[u8; 6]
+ 455..467 'br#"x"\"yb"#': &[u8; 6]
+ "##]],
+ );
+}
+
+#[test]
+fn infer_unary_op() {
+ check_infer(
+ r#"
+enum SomeType {}
+
+fn test(x: SomeType) {
+ let b = false;
+ let c = !b;
+ let a = 100;
+ let d: i128 = -a;
+ let e = -100;
+ let f = !!!true;
+ let g = !42;
+ let h = !10u32;
+ let j = !a;
+ -3.14;
+ !3;
+ -x;
+ !x;
+ -"hello";
+ !"hello";
+}
+"#,
+ expect![[r#"
+ 26..27 'x': SomeType
+ 39..271 '{ ...lo"; }': ()
+ 49..50 'b': bool
+ 53..58 'false': bool
+ 68..69 'c': bool
+ 72..74 '!b': bool
+ 73..74 'b': bool
+ 84..85 'a': i128
+ 88..91 '100': i128
+ 101..102 'd': i128
+ 111..113 '-a': i128
+ 112..113 'a': i128
+ 123..124 'e': i32
+ 127..131 '-100': i32
+ 128..131 '100': i32
+ 141..142 'f': bool
+ 145..152 '!!!true': bool
+ 146..152 '!!true': bool
+ 147..152 '!true': bool
+ 148..152 'true': bool
+ 162..163 'g': i32
+ 166..169 '!42': i32
+ 167..169 '42': i32
+ 179..180 'h': u32
+ 183..189 '!10u32': u32
+ 184..189 '10u32': u32
+ 199..200 'j': i128
+ 203..205 '!a': i128
+ 204..205 'a': i128
+ 211..216 '-3.14': f64
+ 212..216 '3.14': f64
+ 222..224 '!3': i32
+ 223..224 '3': i32
+ 230..232 '-x': {unknown}
+ 231..232 'x': SomeType
+ 238..240 '!x': {unknown}
+ 239..240 'x': SomeType
+ 246..254 '-"hello"': {unknown}
+ 247..254 '"hello"': &str
+ 260..268 '!"hello"': {unknown}
+ 261..268 '"hello"': &str
+ "#]],
+ );
+}
+
+#[test]
+fn infer_backwards() {
+ check_infer(
+ r#"
+fn takes_u32(x: u32) {}
+
+struct S { i32_field: i32 }
+
+fn test() -> &mut &f64 {
+ let a = unknown_function();
+ takes_u32(a);
+ let b = unknown_function();
+ S { i32_field: b };
+ let c = unknown_function();
+ &mut &c
+}
+"#,
+ expect![[r#"
+ 13..14 'x': u32
+ 21..23 '{}': ()
+ 77..230 '{ ...t &c }': &mut &f64
+ 87..88 'a': u32
+ 91..107 'unknow...nction': {unknown}
+ 91..109 'unknow...tion()': u32
+ 115..124 'takes_u32': fn takes_u32(u32)
+ 115..127 'takes_u32(a)': ()
+ 125..126 'a': u32
+ 137..138 'b': i32
+ 141..157 'unknow...nction': {unknown}
+ 141..159 'unknow...tion()': i32
+ 165..183 'S { i3...d: b }': S
+ 180..181 'b': i32
+ 193..194 'c': f64
+ 197..213 'unknow...nction': {unknown}
+ 197..215 'unknow...tion()': f64
+ 221..228 '&mut &c': &mut &f64
+ 226..228 '&c': &f64
+ 227..228 'c': f64
+ "#]],
+ );
+}
+
+#[test]
+fn infer_self() {
+ check_infer(
+ r#"
+struct S;
+
+impl S {
+ fn test(&self) {
+ self;
+ }
+ fn test2(self: &Self) {
+ self;
+ }
+ fn test3() -> Self {
+ S {}
+ }
+ fn test4() -> Self {
+ Self {}
+ }
+}
+"#,
+ expect![[r#"
+ 33..37 'self': &S
+ 39..60 '{ ... }': ()
+ 49..53 'self': &S
+ 74..78 'self': &S
+ 87..108 '{ ... }': ()
+ 97..101 'self': &S
+ 132..152 '{ ... }': S
+ 142..146 'S {}': S
+ 176..199 '{ ... }': S
+ 186..193 'Self {}': S
+ "#]],
+ );
+}
+
+#[test]
+fn infer_self_as_path() {
+ check_infer(
+ r#"
+struct S1;
+struct S2(isize);
+enum E {
+ V1,
+ V2(u32),
+}
+
+impl S1 {
+ fn test() {
+ Self;
+ }
+}
+impl S2 {
+ fn test() {
+ Self(1);
+ }
+}
+impl E {
+ fn test() {
+ Self::V1;
+ Self::V2(1);
+ }
+}
+"#,
+ expect![[r#"
+ 86..107 '{ ... }': ()
+ 96..100 'Self': S1
+ 134..158 '{ ... }': ()
+ 144..148 'Self': S2(isize) -> S2
+ 144..151 'Self(1)': S2
+ 149..150 '1': isize
+ 184..230 '{ ... }': ()
+ 194..202 'Self::V1': E
+ 212..220 'Self::V2': V2(u32) -> E
+ 212..223 'Self::V2(1)': E
+ 221..222 '1': u32
+ "#]],
+ );
+}
+
+#[test]
+fn infer_binary_op() {
+ check_infer(
+ r#"
+fn f(x: bool) -> i32 {
+ 0i32
+}
+
+fn test() -> bool {
+ let x = a && b;
+ let y = true || false;
+ let z = x == y;
+ let t = x != y;
+ let minus_forty: isize = -40isize;
+ let h = minus_forty <= CONST_2;
+ let c = f(z || y) + 5;
+ let d = b;
+ let g = minus_forty ^= i;
+ let ten: usize = 10;
+ let ten_is_eleven = ten == some_num;
+
+ ten < 3
+}
+"#,
+ expect![[r#"
+ 5..6 'x': bool
+ 21..33 '{ 0i32 }': i32
+ 27..31 '0i32': i32
+ 53..369 '{ ... < 3 }': bool
+ 63..64 'x': bool
+ 67..68 'a': bool
+ 67..73 'a && b': bool
+ 72..73 'b': bool
+ 83..84 'y': bool
+ 87..91 'true': bool
+ 87..100 'true || false': bool
+ 95..100 'false': bool
+ 110..111 'z': bool
+ 114..115 'x': bool
+ 114..120 'x == y': bool
+ 119..120 'y': bool
+ 130..131 't': bool
+ 134..135 'x': bool
+ 134..140 'x != y': bool
+ 139..140 'y': bool
+ 150..161 'minus_forty': isize
+ 171..179 '-40isize': isize
+ 172..179 '40isize': isize
+ 189..190 'h': bool
+ 193..204 'minus_forty': isize
+ 193..215 'minus_...ONST_2': bool
+ 208..215 'CONST_2': isize
+ 225..226 'c': i32
+ 229..230 'f': fn f(bool) -> i32
+ 229..238 'f(z || y)': i32
+ 229..242 'f(z || y) + 5': i32
+ 231..232 'z': bool
+ 231..237 'z || y': bool
+ 236..237 'y': bool
+ 241..242 '5': i32
+ 252..253 'd': {unknown}
+ 256..257 'b': {unknown}
+ 267..268 'g': ()
+ 271..282 'minus_forty': isize
+ 271..287 'minus_...y ^= i': ()
+ 286..287 'i': isize
+ 297..300 'ten': usize
+ 310..312 '10': usize
+ 322..335 'ten_is_eleven': bool
+ 338..341 'ten': usize
+ 338..353 'ten == some_num': bool
+ 345..353 'some_num': usize
+ 360..363 'ten': usize
+ 360..367 'ten < 3': bool
+ 366..367 '3': usize
+ "#]],
+ );
+}
+
+#[test]
+fn infer_shift_op() {
+ check_infer(
+ r#"
+fn test() {
+ 1u32 << 5u8;
+ 1u32 >> 5u8;
+}
+"#,
+ expect![[r#"
+ 10..47 '{ ...5u8; }': ()
+ 16..20 '1u32': u32
+ 16..27 '1u32 << 5u8': u32
+ 24..27 '5u8': u8
+ 33..37 '1u32': u32
+ 33..44 '1u32 >> 5u8': u32
+ 41..44 '5u8': u8
+ "#]],
+ );
+}
+
+#[test]
+fn infer_field_autoderef() {
+ check_infer(
+ r#"
+struct A {
+ b: B,
+}
+struct B;
+
+fn test1(a: A) {
+ let a1 = a;
+ a1.b;
+ let a2 = &a;
+ a2.b;
+ let a3 = &mut a;
+ a3.b;
+ let a4 = &&&&&&&a;
+ a4.b;
+ let a5 = &mut &&mut &&mut a;
+ a5.b;
+}
+
+fn test2(a1: *const A, a2: *mut A) {
+ a1.b;
+ a2.b;
+}
+"#,
+ expect![[r#"
+ 43..44 'a': A
+ 49..212 '{ ...5.b; }': ()
+ 59..61 'a1': A
+ 64..65 'a': A
+ 71..73 'a1': A
+ 71..75 'a1.b': B
+ 85..87 'a2': &A
+ 90..92 '&a': &A
+ 91..92 'a': A
+ 98..100 'a2': &A
+ 98..102 'a2.b': B
+ 112..114 'a3': &mut A
+ 117..123 '&mut a': &mut A
+ 122..123 'a': A
+ 129..131 'a3': &mut A
+ 129..133 'a3.b': B
+ 143..145 'a4': &&&&&&&A
+ 148..156 '&&&&&&&a': &&&&&&&A
+ 149..156 '&&&&&&a': &&&&&&A
+ 150..156 '&&&&&a': &&&&&A
+ 151..156 '&&&&a': &&&&A
+ 152..156 '&&&a': &&&A
+ 153..156 '&&a': &&A
+ 154..156 '&a': &A
+ 155..156 'a': A
+ 162..164 'a4': &&&&&&&A
+ 162..166 'a4.b': B
+ 176..178 'a5': &mut &&mut &&mut A
+ 181..199 '&mut &...&mut a': &mut &&mut &&mut A
+ 186..199 '&&mut &&mut a': &&mut &&mut A
+ 187..199 '&mut &&mut a': &mut &&mut A
+ 192..199 '&&mut a': &&mut A
+ 193..199 '&mut a': &mut A
+ 198..199 'a': A
+ 205..207 'a5': &mut &&mut &&mut A
+ 205..209 'a5.b': B
+ 223..225 'a1': *const A
+ 237..239 'a2': *mut A
+ 249..272 '{ ...2.b; }': ()
+ 255..257 'a1': *const A
+ 255..259 'a1.b': B
+ 265..267 'a2': *mut A
+ 265..269 'a2.b': B
+ "#]],
+ );
+}
+
+#[test]
+fn infer_argument_autoderef() {
+ check_infer(
+ r#"
+//- minicore: deref
+use core::ops::Deref;
+struct A<T>(T);
+
+impl<T> A<T> {
+ fn foo(&self) -> &T {
+ &self.0
+ }
+}
+
+struct B<T>(T);
+
+impl<T> Deref for B<T> {
+ type Target = T;
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+fn test() {
+ let t = A::foo(&&B(B(A(42))));
+}
+"#,
+ expect![[r#"
+ 66..70 'self': &A<T>
+ 78..101 '{ ... }': &T
+ 88..95 '&self.0': &T
+ 89..93 'self': &A<T>
+ 89..95 'self.0': T
+ 182..186 'self': &B<T>
+ 205..228 '{ ... }': &T
+ 215..222 '&self.0': &T
+ 216..220 'self': &B<T>
+ 216..222 'self.0': T
+ 242..280 '{ ...))); }': ()
+ 252..253 't': &i32
+ 256..262 'A::foo': fn foo<i32>(&A<i32>) -> &i32
+ 256..277 'A::foo...42))))': &i32
+ 263..276 '&&B(B(A(42)))': &&B<B<A<i32>>>
+ 264..276 '&B(B(A(42)))': &B<B<A<i32>>>
+ 265..266 'B': B<B<A<i32>>>(B<A<i32>>) -> B<B<A<i32>>>
+ 265..276 'B(B(A(42)))': B<B<A<i32>>>
+ 267..268 'B': B<A<i32>>(A<i32>) -> B<A<i32>>
+ 267..275 'B(A(42))': B<A<i32>>
+ 269..270 'A': A<i32>(i32) -> A<i32>
+ 269..274 'A(42)': A<i32>
+ 271..273 '42': i32
+ "#]],
+ );
+}
+
+#[test]
+fn infer_method_argument_autoderef() {
+ check_infer(
+ r#"
+//- minicore: deref
+use core::ops::Deref;
+struct A<T>(*mut T);
+
+impl<T> A<T> {
+ fn foo(&self, x: &A<T>) -> &T {
+ &*x.0
+ }
+}
+
+struct B<T>(T);
+
+impl<T> Deref for B<T> {
+ type Target = T;
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+fn test(a: A<i32>) {
+ let t = A(0 as *mut _).foo(&&B(B(a)));
+}
+"#,
+ expect![[r#"
+ 71..75 'self': &A<T>
+ 77..78 'x': &A<T>
+ 93..114 '{ ... }': &T
+ 103..108 '&*x.0': &T
+ 104..108 '*x.0': T
+ 105..106 'x': &A<T>
+ 105..108 'x.0': *mut T
+ 195..199 'self': &B<T>
+ 218..241 '{ ... }': &T
+ 228..235 '&self.0': &T
+ 229..233 'self': &B<T>
+ 229..235 'self.0': T
+ 253..254 'a': A<i32>
+ 264..310 '{ ...))); }': ()
+ 274..275 't': &i32
+ 278..279 'A': A<i32>(*mut i32) -> A<i32>
+ 278..292 'A(0 as *mut _)': A<i32>
+ 278..307 'A(0 as...B(a)))': &i32
+ 280..281 '0': i32
+ 280..291 '0 as *mut _': *mut i32
+ 297..306 '&&B(B(a))': &&B<B<A<i32>>>
+ 298..306 '&B(B(a))': &B<B<A<i32>>>
+ 299..300 'B': B<B<A<i32>>>(B<A<i32>>) -> B<B<A<i32>>>
+ 299..306 'B(B(a))': B<B<A<i32>>>
+ 301..302 'B': B<A<i32>>(A<i32>) -> B<A<i32>>
+ 301..305 'B(a)': B<A<i32>>
+ 303..304 'a': A<i32>
+ "#]],
+ );
+}
+
+#[test]
+fn infer_in_elseif() {
+ check_infer(
+ r#"
+struct Foo { field: i32 }
+fn main(foo: Foo) {
+ if true {
+
+ } else if false {
+ foo.field
+ }
+}
+"#,
+ expect![[r#"
+ 34..37 'foo': Foo
+ 44..108 '{ ... } }': ()
+ 50..106 'if tru... }': ()
+ 53..57 'true': bool
+ 58..66 '{ }': ()
+ 72..106 'if fal... }': ()
+ 75..80 'false': bool
+ 81..106 '{ ... }': ()
+ 91..94 'foo': Foo
+ 91..100 'foo.field': i32
+ "#]],
+ )
+}
+
+#[test]
+fn infer_if_match_with_return() {
+ check_infer(
+ r#"
+fn foo() {
+ let _x1 = if true {
+ 1
+ } else {
+ return;
+ };
+ let _x2 = if true {
+ 2
+ } else {
+ return
+ };
+ let _x3 = match true {
+ true => 3,
+ _ => {
+ return;
+ }
+ };
+ let _x4 = match true {
+ true => 4,
+ _ => return
+ };
+}
+"#,
+ expect![[r#"
+ 9..322 '{ ... }; }': ()
+ 19..22 '_x1': i32
+ 25..79 'if tru... }': i32
+ 28..32 'true': bool
+ 33..50 '{ ... }': i32
+ 43..44 '1': i32
+ 56..79 '{ ... }': i32
+ 66..72 'return': !
+ 89..92 '_x2': i32
+ 95..148 'if tru... }': i32
+ 98..102 'true': bool
+ 103..120 '{ ... }': i32
+ 113..114 '2': i32
+ 126..148 '{ ... }': !
+ 136..142 'return': !
+ 158..161 '_x3': i32
+ 164..246 'match ... }': i32
+ 170..174 'true': bool
+ 185..189 'true': bool
+ 185..189 'true': bool
+ 193..194 '3': i32
+ 204..205 '_': bool
+ 209..240 '{ ... }': i32
+ 223..229 'return': !
+ 256..259 '_x4': i32
+ 262..319 'match ... }': i32
+ 268..272 'true': bool
+ 283..287 'true': bool
+ 283..287 'true': bool
+ 291..292 '4': i32
+ 302..303 '_': bool
+ 307..313 'return': !
+ "#]],
+ )
+}
+
+#[test]
+fn infer_inherent_method() {
+ check_infer(
+ r#"
+ struct A;
+
+ impl A {
+ fn foo(self, x: u32) -> i32 {}
+ }
+
+ mod b {
+ impl super::A {
+ pub fn bar(&self, x: u64) -> i64 {}
+ }
+ }
+
+ fn test(a: A) {
+ a.foo(1);
+ (&a).bar(1);
+ a.bar(1);
+ }
+ "#,
+ expect![[r#"
+ 31..35 'self': A
+ 37..38 'x': u32
+ 52..54 '{}': i32
+ 106..110 'self': &A
+ 112..113 'x': u64
+ 127..129 '{}': i64
+ 147..148 'a': A
+ 153..201 '{ ...(1); }': ()
+ 159..160 'a': A
+ 159..167 'a.foo(1)': i32
+ 165..166 '1': u32
+ 173..184 '(&a).bar(1)': i64
+ 174..176 '&a': &A
+ 175..176 'a': A
+ 182..183 '1': u64
+ 190..191 'a': A
+ 190..198 'a.bar(1)': i64
+ 196..197 '1': u64
+ "#]],
+ );
+}
+
+#[test]
+fn infer_inherent_method_str() {
+ check_infer(
+ r#"
+ #[lang = "str"]
+ impl str {
+ fn foo(&self) -> i32 {}
+ }
+
+ fn test() {
+ "foo".foo();
+ }
+ "#,
+ expect![[r#"
+ 39..43 'self': &str
+ 52..54 '{}': i32
+ 68..88 '{ ...o(); }': ()
+ 74..79 '"foo"': &str
+ 74..85 '"foo".foo()': i32
+ "#]],
+ );
+}
+
+#[test]
+fn infer_tuple() {
+ check_infer(
+ r#"
+ fn test(x: &str, y: isize) {
+ let a: (u32, &str) = (1, "a");
+ let b = (a, x);
+ let c = (y, x);
+ let d = (c, x);
+ let e = (1, "e");
+ let f = (e, "d");
+ }
+ "#,
+ expect![[r#"
+ 8..9 'x': &str
+ 17..18 'y': isize
+ 27..169 '{ ...d"); }': ()
+ 37..38 'a': (u32, &str)
+ 54..62 '(1, "a")': (u32, &str)
+ 55..56 '1': u32
+ 58..61 '"a"': &str
+ 72..73 'b': ((u32, &str), &str)
+ 76..82 '(a, x)': ((u32, &str), &str)
+ 77..78 'a': (u32, &str)
+ 80..81 'x': &str
+ 92..93 'c': (isize, &str)
+ 96..102 '(y, x)': (isize, &str)
+ 97..98 'y': isize
+ 100..101 'x': &str
+ 112..113 'd': ((isize, &str), &str)
+ 116..122 '(c, x)': ((isize, &str), &str)
+ 117..118 'c': (isize, &str)
+ 120..121 'x': &str
+ 132..133 'e': (i32, &str)
+ 136..144 '(1, "e")': (i32, &str)
+ 137..138 '1': i32
+ 140..143 '"e"': &str
+ 154..155 'f': ((i32, &str), &str)
+ 158..166 '(e, "d")': ((i32, &str), &str)
+ 159..160 'e': (i32, &str)
+ 162..165 '"d"': &str
+ "#]],
+ );
+}
+
+#[test]
+fn infer_array() {
+ check_infer(
+ r#"
+ fn test(x: &str, y: isize) {
+ let a = [x];
+ let b = [a, a];
+ let c = [b, b];
+
+ let d = [y, 1, 2, 3];
+ let d = [1, y, 2, 3];
+ let e = [y];
+ let f = [d, d];
+ let g = [e, e];
+
+ let h = [1, 2];
+ let i = ["a", "b"];
+
+ let b = [a, ["b"]];
+ let x: [u8; 0] = [];
+ let y: [u8; 2+2] = [1,2,3,4];
+ }
+ "#,
+ expect![[r#"
+ 8..9 'x': &str
+ 17..18 'y': isize
+ 27..326 '{ ...,4]; }': ()
+ 37..38 'a': [&str; 1]
+ 41..44 '[x]': [&str; 1]
+ 42..43 'x': &str
+ 54..55 'b': [[&str; 1]; 2]
+ 58..64 '[a, a]': [[&str; 1]; 2]
+ 59..60 'a': [&str; 1]
+ 62..63 'a': [&str; 1]
+ 74..75 'c': [[[&str; 1]; 2]; 2]
+ 78..84 '[b, b]': [[[&str; 1]; 2]; 2]
+ 79..80 'b': [[&str; 1]; 2]
+ 82..83 'b': [[&str; 1]; 2]
+ 95..96 'd': [isize; 4]
+ 99..111 '[y, 1, 2, 3]': [isize; 4]
+ 100..101 'y': isize
+ 103..104 '1': isize
+ 106..107 '2': isize
+ 109..110 '3': isize
+ 121..122 'd': [isize; 4]
+ 125..137 '[1, y, 2, 3]': [isize; 4]
+ 126..127 '1': isize
+ 129..130 'y': isize
+ 132..133 '2': isize
+ 135..136 '3': isize
+ 147..148 'e': [isize; 1]
+ 151..154 '[y]': [isize; 1]
+ 152..153 'y': isize
+ 164..165 'f': [[isize; 4]; 2]
+ 168..174 '[d, d]': [[isize; 4]; 2]
+ 169..170 'd': [isize; 4]
+ 172..173 'd': [isize; 4]
+ 184..185 'g': [[isize; 1]; 2]
+ 188..194 '[e, e]': [[isize; 1]; 2]
+ 189..190 'e': [isize; 1]
+ 192..193 'e': [isize; 1]
+ 205..206 'h': [i32; 2]
+ 209..215 '[1, 2]': [i32; 2]
+ 210..211 '1': i32
+ 213..214 '2': i32
+ 225..226 'i': [&str; 2]
+ 229..239 '["a", "b"]': [&str; 2]
+ 230..233 '"a"': &str
+ 235..238 '"b"': &str
+ 250..251 'b': [[&str; 1]; 2]
+ 254..264 '[a, ["b"]]': [[&str; 1]; 2]
+ 255..256 'a': [&str; 1]
+ 258..263 '["b"]': [&str; 1]
+ 259..262 '"b"': &str
+ 274..275 'x': [u8; 0]
+ 287..289 '[]': [u8; 0]
+ 299..300 'y': [u8; 4]
+ 314..323 '[1,2,3,4]': [u8; 4]
+ 315..316 '1': u8
+ 317..318 '2': u8
+ 319..320 '3': u8
+ 321..322 '4': u8
+ "#]],
+ );
+}
+
+#[test]
+fn infer_struct_generics() {
+ check_infer(
+ r#"
+ struct A<T> {
+ x: T,
+ }
+
+ fn test(a1: A<u32>, i: i32) {
+ a1.x;
+ let a2 = A { x: i };
+ a2.x;
+ let a3 = A::<i128> { x: 1 };
+ a3.x;
+ }
+ "#,
+ expect![[r#"
+ 35..37 'a1': A<u32>
+ 47..48 'i': i32
+ 55..146 '{ ...3.x; }': ()
+ 61..63 'a1': A<u32>
+ 61..65 'a1.x': u32
+ 75..77 'a2': A<i32>
+ 80..90 'A { x: i }': A<i32>
+ 87..88 'i': i32
+ 96..98 'a2': A<i32>
+ 96..100 'a2.x': i32
+ 110..112 'a3': A<i128>
+ 115..133 'A::<i1...x: 1 }': A<i128>
+ 130..131 '1': i128
+ 139..141 'a3': A<i128>
+ 139..143 'a3.x': i128
+ "#]],
+ );
+}
+
+#[test]
+fn infer_tuple_struct_generics() {
+ check_infer(
+ r#"
+ struct A<T>(T);
+ enum Option<T> { Some(T), None }
+ use Option::*;
+
+ fn test() {
+ A(42);
+ A(42u128);
+ Some("x");
+ Option::Some("x");
+ None;
+ let x: Option<i64> = None;
+ }
+ "#,
+ expect![[r#"
+ 75..183 '{ ...one; }': ()
+ 81..82 'A': A<i32>(i32) -> A<i32>
+ 81..86 'A(42)': A<i32>
+ 83..85 '42': i32
+ 92..93 'A': A<u128>(u128) -> A<u128>
+ 92..101 'A(42u128)': A<u128>
+ 94..100 '42u128': u128
+ 107..111 'Some': Some<&str>(&str) -> Option<&str>
+ 107..116 'Some("x")': Option<&str>
+ 112..115 '"x"': &str
+ 122..134 'Option::Some': Some<&str>(&str) -> Option<&str>
+ 122..139 'Option...e("x")': Option<&str>
+ 135..138 '"x"': &str
+ 145..149 'None': Option<{unknown}>
+ 159..160 'x': Option<i64>
+ 176..180 'None': Option<i64>
+ "#]],
+ );
+}
+
+#[test]
+fn infer_function_generics() {
+ check_infer(
+ r#"
+ fn id<T>(t: T) -> T { t }
+
+ fn test() {
+ id(1u32);
+ id::<i128>(1);
+ let x: u64 = id(1);
+ }
+ "#,
+ expect![[r#"
+ 9..10 't': T
+ 20..25 '{ t }': T
+ 22..23 't': T
+ 37..97 '{ ...(1); }': ()
+ 43..45 'id': fn id<u32>(u32) -> u32
+ 43..51 'id(1u32)': u32
+ 46..50 '1u32': u32
+ 57..67 'id::<i128>': fn id<i128>(i128) -> i128
+ 57..70 'id::<i128>(1)': i128
+ 68..69 '1': i128
+ 80..81 'x': u64
+ 89..91 'id': fn id<u64>(u64) -> u64
+ 89..94 'id(1)': u64
+ 92..93 '1': u64
+ "#]],
+ );
+}
+
+#[test]
+fn infer_impl_generics_basic() {
+ check_infer(
+ r#"
+ struct A<T1, T2> {
+ x: T1,
+ y: T2,
+ }
+ impl<Y, X> A<X, Y> {
+ fn x(self) -> X {
+ self.x
+ }
+ fn y(self) -> Y {
+ self.y
+ }
+ fn z<T>(self, t: T) -> (X, Y, T) {
+ (self.x, self.y, t)
+ }
+ }
+
+ fn test() -> i128 {
+ let a = A { x: 1u64, y: 1i64 };
+ a.x();
+ a.y();
+ a.z(1i128);
+ a.z::<u128>(1);
+ }
+ "#,
+ expect![[r#"
+ 73..77 'self': A<X, Y>
+ 84..106 '{ ... }': X
+ 94..98 'self': A<X, Y>
+ 94..100 'self.x': X
+ 116..120 'self': A<X, Y>
+ 127..149 '{ ... }': Y
+ 137..141 'self': A<X, Y>
+ 137..143 'self.y': Y
+ 162..166 'self': A<X, Y>
+ 168..169 't': T
+ 187..222 '{ ... }': (X, Y, T)
+ 197..216 '(self.....y, t)': (X, Y, T)
+ 198..202 'self': A<X, Y>
+ 198..204 'self.x': X
+ 206..210 'self': A<X, Y>
+ 206..212 'self.y': Y
+ 214..215 't': T
+ 244..341 '{ ...(1); }': i128
+ 254..255 'a': A<u64, i64>
+ 258..280 'A { x:...1i64 }': A<u64, i64>
+ 265..269 '1u64': u64
+ 274..278 '1i64': i64
+ 286..287 'a': A<u64, i64>
+ 286..291 'a.x()': u64
+ 297..298 'a': A<u64, i64>
+ 297..302 'a.y()': i64
+ 308..309 'a': A<u64, i64>
+ 308..318 'a.z(1i128)': (u64, i64, i128)
+ 312..317 '1i128': i128
+ 324..325 'a': A<u64, i64>
+ 324..338 'a.z::<u128>(1)': (u64, i64, u128)
+ 336..337 '1': u128
+ "#]],
+ );
+}
+
+#[test]
+fn infer_impl_generics_with_autoderef() {
+ check_infer(
+ r#"
+ enum Option<T> {
+ Some(T),
+ None,
+ }
+ impl<T> Option<T> {
+ fn as_ref(&self) -> Option<&T> {}
+ }
+ fn test(o: Option<u32>) {
+ (&o).as_ref();
+ o.as_ref();
+ }
+ "#,
+ expect![[r#"
+ 77..81 'self': &Option<T>
+ 97..99 '{}': Option<&T>
+ 110..111 'o': Option<u32>
+ 126..164 '{ ...f(); }': ()
+ 132..145 '(&o).as_ref()': Option<&u32>
+ 133..135 '&o': &Option<u32>
+ 134..135 'o': Option<u32>
+ 151..152 'o': Option<u32>
+ 151..161 'o.as_ref()': Option<&u32>
+ "#]],
+ );
+}
+
+#[test]
+fn infer_generic_chain() {
+ check_infer(
+ r#"
+ struct A<T> {
+ x: T,
+ }
+ impl<T2> A<T2> {
+ fn x(self) -> T2 {
+ self.x
+ }
+ }
+ fn id<T>(t: T) -> T { t }
+
+ fn test() -> i128 {
+ let x = 1;
+ let y = id(x);
+ let a = A { x: id(y) };
+ let z = id(a.x);
+ let b = A { x: z };
+ b.x()
+ }
+ "#,
+ expect![[r#"
+ 52..56 'self': A<T2>
+ 64..86 '{ ... }': T2
+ 74..78 'self': A<T2>
+ 74..80 'self.x': T2
+ 98..99 't': T
+ 109..114 '{ t }': T
+ 111..112 't': T
+ 134..254 '{ ....x() }': i128
+ 144..145 'x': i128
+ 148..149 '1': i128
+ 159..160 'y': i128
+ 163..165 'id': fn id<i128>(i128) -> i128
+ 163..168 'id(x)': i128
+ 166..167 'x': i128
+ 178..179 'a': A<i128>
+ 182..196 'A { x: id(y) }': A<i128>
+ 189..191 'id': fn id<i128>(i128) -> i128
+ 189..194 'id(y)': i128
+ 192..193 'y': i128
+ 206..207 'z': i128
+ 210..212 'id': fn id<i128>(i128) -> i128
+ 210..217 'id(a.x)': i128
+ 213..214 'a': A<i128>
+ 213..216 'a.x': i128
+ 227..228 'b': A<i128>
+ 231..241 'A { x: z }': A<i128>
+ 238..239 'z': i128
+ 247..248 'b': A<i128>
+ 247..252 'b.x()': i128
+ "#]],
+ );
+}
+
+#[test]
+fn infer_associated_const() {
+ check_infer(
+ r#"
+ struct Struct;
+
+ impl Struct {
+ const FOO: u32 = 1;
+ }
+
+ enum Enum {}
+
+ impl Enum {
+ const BAR: u32 = 2;
+ }
+
+ trait Trait {
+ const ID: u32;
+ }
+
+ struct TraitTest;
+
+ impl Trait for TraitTest {
+ const ID: u32 = 5;
+ }
+
+ fn test() {
+ let x = Struct::FOO;
+ let y = Enum::BAR;
+ let z = TraitTest::ID;
+ }
+ "#,
+ expect![[r#"
+ 51..52 '1': u32
+ 104..105 '2': u32
+ 212..213 '5': u32
+ 228..306 '{ ...:ID; }': ()
+ 238..239 'x': u32
+ 242..253 'Struct::FOO': u32
+ 263..264 'y': u32
+ 267..276 'Enum::BAR': u32
+ 286..287 'z': u32
+ 290..303 'TraitTest::ID': u32
+ "#]],
+ );
+}
+
+#[test]
+fn infer_type_alias() {
+ check_infer(
+ r#"
+ struct A<X, Y> { x: X, y: Y }
+ type Foo = A<u32, i128>;
+ type Bar<T> = A<T, u128>;
+ type Baz<U, V> = A<V, U>;
+ fn test(x: Foo, y: Bar<&str>, z: Baz<i8, u8>) {
+ x.x;
+ x.y;
+ y.x;
+ y.y;
+ z.x;
+ z.y;
+ }
+ mod m {
+ pub enum Enum {
+ Foo(u8),
+ }
+ pub type Alias = Enum;
+ }
+ fn f() {
+ let e = m::Alias::Foo(0);
+ let m::Alias::Foo(x) = &e;
+ }
+ "#,
+ expect![[r#"
+ 115..116 'x': A<u32, i128>
+ 123..124 'y': A<&str, u128>
+ 137..138 'z': A<u8, i8>
+ 153..210 '{ ...z.y; }': ()
+ 159..160 'x': A<u32, i128>
+ 159..162 'x.x': u32
+ 168..169 'x': A<u32, i128>
+ 168..171 'x.y': i128
+ 177..178 'y': A<&str, u128>
+ 177..180 'y.x': &str
+ 186..187 'y': A<&str, u128>
+ 186..189 'y.y': u128
+ 195..196 'z': A<u8, i8>
+ 195..198 'z.x': u8
+ 204..205 'z': A<u8, i8>
+ 204..207 'z.y': i8
+ 298..362 '{ ... &e; }': ()
+ 308..309 'e': Enum
+ 312..325 'm::Alias::Foo': Foo(u8) -> Enum
+ 312..328 'm::Ali...Foo(0)': Enum
+ 326..327 '0': u8
+ 338..354 'm::Ali...Foo(x)': Enum
+ 352..353 'x': &u8
+ 357..359 '&e': &Enum
+ 358..359 'e': Enum
+ "#]],
+ )
+}
+
+#[test]
+fn recursive_type_alias() {
+ check_infer(
+ r#"
+ struct A<X> {}
+ type Foo = Foo;
+ type Bar = A<Bar>;
+ fn test(x: Foo) {}
+ "#,
+ expect![[r#"
+ 58..59 'x': {unknown}
+ 66..68 '{}': ()
+ "#]],
+ )
+}
+
+#[test]
+fn infer_type_param() {
+ check_infer(
+ r#"
+ fn id<T>(x: T) -> T {
+ x
+ }
+
+ fn clone<T>(x: &T) -> T {
+ *x
+ }
+
+ fn test() {
+ let y = 10u32;
+ id(y);
+ let x: bool = clone(z);
+ id::<i128>(1);
+ }
+ "#,
+ expect![[r#"
+ 9..10 'x': T
+ 20..29 '{ x }': T
+ 26..27 'x': T
+ 43..44 'x': &T
+ 55..65 '{ *x }': T
+ 61..63 '*x': T
+ 62..63 'x': &T
+ 77..157 '{ ...(1); }': ()
+ 87..88 'y': u32
+ 91..96 '10u32': u32
+ 102..104 'id': fn id<u32>(u32) -> u32
+ 102..107 'id(y)': u32
+ 105..106 'y': u32
+ 117..118 'x': bool
+ 127..132 'clone': fn clone<bool>(&bool) -> bool
+ 127..135 'clone(z)': bool
+ 133..134 'z': &bool
+ 141..151 'id::<i128>': fn id<i128>(i128) -> i128
+ 141..154 'id::<i128>(1)': i128
+ 152..153 '1': i128
+ "#]],
+ );
+}
+
+#[test]
+fn infer_const() {
+ check_infer(
+ r#"
- static GLOBAL_STATIC: u32 = 101;
- static mut GLOBAL_STATIC_MUT: u32 = 101;
- fn test() {
- static LOCAL_STATIC: u32 = 99;
- static mut LOCAL_STATIC_MUT: u32 = 99;
- let x = LOCAL_STATIC;
- let y = LOCAL_STATIC_MUT;
- let z = GLOBAL_STATIC;
- let w = GLOBAL_STATIC_MUT;
- }
- "#,
++struct Foo;
++impl Foo { const ASSOC_CONST: u32 = 0; }
++const GLOBAL_CONST: u32 = 101;
++fn test() {
++ const LOCAL_CONST: u32 = 99;
++ let x = LOCAL_CONST;
++ let z = GLOBAL_CONST;
++ let id = Foo::ASSOC_CONST;
++}
++"#,
+ expect![[r#"
+ 48..49 '0': u32
+ 79..82 '101': u32
+ 94..212 '{ ...NST; }': ()
+ 137..138 'x': u32
+ 141..152 'LOCAL_CONST': u32
+ 162..163 'z': u32
+ 166..178 'GLOBAL_CONST': u32
+ 188..190 'id': u32
+ 193..209 'Foo::A..._CONST': u32
+ 125..127 '99': u32
+ "#]],
+ );
+}
+
+#[test]
+fn infer_static() {
+ check_infer(
+ r#"
++static GLOBAL_STATIC: u32 = 101;
++static mut GLOBAL_STATIC_MUT: u32 = 101;
++fn test() {
++ static LOCAL_STATIC: u32 = 99;
++ static mut LOCAL_STATIC_MUT: u32 = 99;
++ let x = LOCAL_STATIC;
++ let y = LOCAL_STATIC_MUT;
++ let z = GLOBAL_STATIC;
++ let w = GLOBAL_STATIC_MUT;
++}
++"#,
+ expect![[r#"
+ 28..31 '101': u32
+ 69..72 '101': u32
+ 84..279 '{ ...MUT; }': ()
+ 172..173 'x': u32
+ 176..188 'LOCAL_STATIC': u32
+ 198..199 'y': u32
+ 202..218 'LOCAL_...IC_MUT': u32
+ 228..229 'z': u32
+ 232..245 'GLOBAL_STATIC': u32
+ 255..256 'w': u32
+ 259..276 'GLOBAL...IC_MUT': u32
+ 117..119 '99': u32
+ 160..162 '99': u32
+ "#]],
+ );
+}
+
++#[test]
++fn infer_enum_variant() {
++ check_infer(
++ r#"
++enum Foo {
++ A = 15,
++ B = Foo::A as isize + 1
++}
++"#,
++ expect![[r#"
++ 19..21 '15': isize
++ 31..37 'Foo::A': Foo
++ 31..46 'Foo::A as isize': isize
++ 31..50 'Foo::A...ze + 1': isize
++ 49..50 '1': isize
++ "#]],
++ );
++ check_infer(
++ r#"
++#[repr(u32)]
++enum Foo {
++ A = 15,
++ B = Foo::A as u32 + 1
++}
++"#,
++ expect![[r#"
++ 32..34 '15': u32
++ 44..50 'Foo::A': Foo
++ 44..57 'Foo::A as u32': u32
++ 44..61 'Foo::A...32 + 1': u32
++ 60..61 '1': u32
++ "#]],
++ );
++}
++
+#[test]
+fn shadowing_primitive() {
+ check_types(
+ r#"
+struct i32;
+struct Foo;
+
+impl i32 { fn foo(&self) -> Foo { Foo } }
+
+fn main() {
+ let x: i32 = i32;
+ x.foo();
+ //^^^^^^^ Foo
+}"#,
+ );
+}
+
+#[test]
+fn const_eval_array_repeat_expr() {
+ check_types(
+ r#"
+fn main() {
+ const X: usize = 6 - 1;
+ let t = [(); X + 2];
+ //^ [(); 7]
+}"#,
+ );
+}
+
+#[test]
+fn shadowing_primitive_with_inner_items() {
+ check_types(
+ r#"
+struct i32;
+struct Foo;
+
+impl i32 { fn foo(&self) -> Foo { Foo } }
+
+fn main() {
+ fn inner() {}
+ let x: i32 = i32;
+ x.foo();
+ //^^^^^^^ Foo
+}"#,
+ );
+}
+
+#[test]
+fn not_shadowing_primitive_by_module() {
+ check_types(
+ r#"
+//- /str.rs
+fn foo() {}
+
+//- /main.rs
+mod str;
+fn foo() -> &'static str { "" }
+
+fn main() {
+ foo();
+ //^^^^^ &str
+}"#,
+ );
+}
+
+#[test]
+fn not_shadowing_module_by_primitive() {
+ check_types(
+ r#"
+//- /str.rs
+fn foo() -> u32 {0}
+
+//- /main.rs
+mod str;
+fn foo() -> &'static str { "" }
+
+fn main() {
+ str::foo();
+ //^^^^^^^^^^ u32
+}"#,
+ );
+}
+
+// This test is actually testing the shadowing behavior within hir_def. It
+// lives here because the testing infrastructure in hir_def isn't currently
+// capable of asserting the necessary conditions.
+#[test]
+fn should_be_shadowing_imports() {
+ check_types(
+ r#"
+mod a {
+ pub fn foo() -> i8 {0}
+ pub struct foo { a: i8 }
+}
+mod b { pub fn foo () -> u8 {0} }
+mod c { pub struct foo { a: u8 } }
+mod d {
+ pub use super::a::*;
+ pub use super::c::foo;
+ pub use super::b::foo;
+}
+
+fn main() {
+ d::foo();
+ //^^^^^^^^ u8
+ d::foo{a:0};
+ //^^^^^^^^^^^ foo
+}"#,
+ );
+}
+
+#[test]
+fn closure_return() {
+ check_infer(
+ r#"
+ fn foo() -> u32 {
+ let x = || -> usize { return 1; };
+ }
+ "#,
+ expect![[r#"
+ 16..58 '{ ...; }; }': u32
+ 26..27 'x': || -> usize
+ 30..55 '|| -> ...n 1; }': || -> usize
+ 42..55 '{ return 1; }': usize
+ 44..52 'return 1': !
+ 51..52 '1': usize
+ "#]],
+ );
+}
+
+#[test]
+fn closure_return_unit() {
+ check_infer(
+ r#"
+ fn foo() -> u32 {
+ let x = || { return; };
+ }
+ "#,
+ expect![[r#"
+ 16..47 '{ ...; }; }': u32
+ 26..27 'x': || -> ()
+ 30..44 '|| { return; }': || -> ()
+ 33..44 '{ return; }': ()
+ 35..41 'return': !
+ "#]],
+ );
+}
+
+#[test]
+fn closure_return_inferred() {
+ check_infer(
+ r#"
+ fn foo() -> u32 {
+ let x = || { "test" };
+ }
+ "#,
+ expect![[r#"
+ 16..46 '{ ..." }; }': u32
+ 26..27 'x': || -> &str
+ 30..43 '|| { "test" }': || -> &str
+ 33..43 '{ "test" }': &str
+ 35..41 '"test"': &str
+ "#]],
+ );
+}
+
++#[test]
++fn generator_types_inferred() {
++ check_infer(
++ r#"
++//- minicore: generator, deref
++use core::ops::{Generator, GeneratorState};
++use core::pin::Pin;
++
++fn f(v: i64) {}
++fn test() {
++ let mut g = |r| {
++ let a = yield 0;
++ let a = yield 1;
++ let a = yield 2;
++ "return value"
++ };
++
++ match Pin::new(&mut g).resume(0usize) {
++ GeneratorState::Yielded(y) => { f(y); }
++ GeneratorState::Complete(r) => {}
++ }
++}
++ "#,
++ expect![[r#"
++ 70..71 'v': i64
++ 78..80 '{}': ()
++ 91..362 '{ ... } }': ()
++ 101..106 'mut g': |usize| yields i64 -> &str
++ 109..218 '|r| { ... }': |usize| yields i64 -> &str
++ 110..111 'r': usize
++ 113..218 '{ ... }': &str
++ 127..128 'a': usize
++ 131..138 'yield 0': usize
++ 137..138 '0': i64
++ 152..153 'a': usize
++ 156..163 'yield 1': usize
++ 162..163 '1': i64
++ 177..178 'a': usize
++ 181..188 'yield 2': usize
++ 187..188 '2': i64
++ 198..212 '"return value"': &str
++ 225..360 'match ... }': ()
++ 231..239 'Pin::new': fn new<&mut |usize| yields i64 -> &str>(&mut |usize| yields i64 -> &str) -> Pin<&mut |usize| yields i64 -> &str>
++ 231..247 'Pin::n...mut g)': Pin<&mut |usize| yields i64 -> &str>
++ 231..262 'Pin::n...usize)': GeneratorState<i64, &str>
++ 240..246 '&mut g': &mut |usize| yields i64 -> &str
++ 245..246 'g': |usize| yields i64 -> &str
++ 255..261 '0usize': usize
++ 273..299 'Genera...ded(y)': GeneratorState<i64, &str>
++ 297..298 'y': i64
++ 303..312 '{ f(y); }': ()
++ 305..306 'f': fn f(i64)
++ 305..309 'f(y)': ()
++ 307..308 'y': i64
++ 321..348 'Genera...ete(r)': GeneratorState<i64, &str>
++ 346..347 'r': &str
++ 352..354 '{}': ()
++ "#]],
++ );
++}
++
++#[test]
++fn generator_resume_yield_return_unit() {
++ check_no_mismatches(
++ r#"
++//- minicore: generator, deref
++use core::ops::{Generator, GeneratorState};
++use core::pin::Pin;
++fn test() {
++ let mut g = || {
++ let () = yield;
++ };
++
++ match Pin::new(&mut g).resume(()) {
++ GeneratorState::Yielded(()) => {}
++ GeneratorState::Complete(()) => {}
++ }
++}
++ "#,
++ );
++}
++
+#[test]
+fn fn_pointer_return() {
+ check_infer(
+ r#"
+ struct Vtable {
+ method: fn(),
+ }
+
+ fn main() {
+ let vtable = Vtable { method: || {} };
+ let m = vtable.method;
+ }
+ "#,
+ expect![[r#"
+ 47..120 '{ ...hod; }': ()
+ 57..63 'vtable': Vtable
+ 66..90 'Vtable...| {} }': Vtable
+ 83..88 '|| {}': || -> ()
+ 86..88 '{}': ()
+ 100..101 'm': fn()
+ 104..110 'vtable': Vtable
+ 104..117 'vtable.method': fn()
+ "#]],
+ );
+}
+
+#[test]
+fn block_modifiers_smoke_test() {
+ check_infer(
+ r#"
+//- minicore: future
+async fn main() {
+ let x = unsafe { 92 };
+ let y = async { async { () }.await };
+ let z = try { () };
+ let w = const { 92 };
+ let t = 'a: { 92 };
+}
+ "#,
+ expect![[r#"
+ 16..162 '{ ...2 }; }': ()
+ 26..27 'x': i32
+ 30..43 'unsafe { 92 }': i32
+ 30..43 'unsafe { 92 }': i32
+ 39..41 '92': i32
+ 53..54 'y': impl Future<Output = ()>
+ 57..85 'async ...wait }': ()
+ 57..85 'async ...wait }': impl Future<Output = ()>
+ 65..77 'async { () }': ()
+ 65..77 'async { () }': impl Future<Output = ()>
+ 65..83 'async ....await': ()
+ 73..75 '()': ()
+ 95..96 'z': {unknown}
+ 99..109 'try { () }': ()
+ 99..109 'try { () }': {unknown}
+ 105..107 '()': ()
+ 119..120 'w': i32
+ 123..135 'const { 92 }': i32
+ 123..135 'const { 92 }': i32
+ 131..133 '92': i32
+ 145..146 't': i32
+ 149..159 ''a: { 92 }': i32
+ 155..157 '92': i32
+ "#]],
+ )
+}
+#[test]
+fn async_block_early_return() {
+ check_infer(
+ r#"
+//- minicore: future, result, fn
+fn test<I, E, F: FnMut() -> Fut, Fut: core::future::Future<Output = Result<I, E>>>(f: F) {}
+
+fn main() {
+ async {
+ return Err(());
+ Ok(())
+ };
+ test(|| async {
+ return Err(());
+ Ok(())
+ });
+}
+ "#,
+ expect![[r#"
+ 83..84 'f': F
+ 89..91 '{}': ()
+ 103..231 '{ ... }); }': ()
+ 109..161 'async ... }': Result<(), ()>
+ 109..161 'async ... }': impl Future<Output = Result<(), ()>>
+ 125..139 'return Err(())': !
+ 132..135 'Err': Err<(), ()>(()) -> Result<(), ()>
+ 132..139 'Err(())': Result<(), ()>
+ 136..138 '()': ()
+ 149..151 'Ok': Ok<(), ()>(()) -> Result<(), ()>
+ 149..155 'Ok(())': Result<(), ()>
+ 152..154 '()': ()
+ 167..171 'test': fn test<(), (), || -> impl Future<Output = Result<(), ()>>, impl Future<Output = Result<(), ()>>>(|| -> impl Future<Output = Result<(), ()>>)
+ 167..228 'test(|... })': ()
+ 172..227 '|| asy... }': || -> impl Future<Output = Result<(), ()>>
+ 175..227 'async ... }': Result<(), ()>
+ 175..227 'async ... }': impl Future<Output = Result<(), ()>>
+ 191..205 'return Err(())': !
+ 198..201 'Err': Err<(), ()>(()) -> Result<(), ()>
+ 198..205 'Err(())': Result<(), ()>
+ 202..204 '()': ()
+ 215..217 'Ok': Ok<(), ()>(()) -> Result<(), ()>
+ 215..221 'Ok(())': Result<(), ()>
+ 218..220 '()': ()
+ "#]],
+ )
+}
+
+#[test]
+fn infer_generic_from_later_assignment() {
+ check_infer(
+ r#"
+ enum Option<T> { Some(T), None }
+ use Option::*;
+
+ fn test() {
+ let mut end = None;
+ loop {
+ end = Some(true);
+ }
+ }
+ "#,
+ expect![[r#"
+ 59..129 '{ ... } }': ()
+ 69..76 'mut end': Option<bool>
+ 79..83 'None': Option<bool>
+ 89..127 'loop {... }': !
+ 94..127 '{ ... }': ()
+ 104..107 'end': Option<bool>
+ 104..120 'end = ...(true)': ()
+ 110..114 'Some': Some<bool>(bool) -> Option<bool>
+ 110..120 'Some(true)': Option<bool>
+ 115..119 'true': bool
+ "#]],
+ );
+}
+
+#[test]
+fn infer_loop_break_with_val() {
+ check_infer(
+ r#"
+ enum Option<T> { Some(T), None }
+ use Option::*;
+
+ fn test() {
+ let x = loop {
+ if false {
+ break None;
+ }
+
+ break Some(true);
+ };
+ }
+ "#,
+ expect![[r#"
+ 59..168 '{ ... }; }': ()
+ 69..70 'x': Option<bool>
+ 73..165 'loop {... }': Option<bool>
+ 78..165 '{ ... }': ()
+ 88..132 'if fal... }': ()
+ 91..96 'false': bool
+ 97..132 '{ ... }': ()
+ 111..121 'break None': !
+ 117..121 'None': Option<bool>
+ 142..158 'break ...(true)': !
+ 148..152 'Some': Some<bool>(bool) -> Option<bool>
+ 148..158 'Some(true)': Option<bool>
+ 153..157 'true': bool
+ "#]],
+ );
+}
+
+#[test]
+fn infer_loop_break_without_val() {
+ check_infer(
+ r#"
+ enum Option<T> { Some(T), None }
+ use Option::*;
+
+ fn test() {
+ let x = loop {
+ if false {
+ break;
+ }
+ };
+ }
+ "#,
+ expect![[r#"
+ 59..136 '{ ... }; }': ()
+ 69..70 'x': ()
+ 73..133 'loop {... }': ()
+ 78..133 '{ ... }': ()
+ 88..127 'if fal... }': ()
+ 91..96 'false': bool
+ 97..127 '{ ... }': ()
+ 111..116 'break': !
+ "#]],
+ );
+}
+
+#[test]
+fn infer_labelled_break_with_val() {
+ check_infer(
+ r#"
+ fn foo() {
+ let _x = || 'outer: loop {
+ let inner = 'inner: loop {
+ let i = Default::default();
+ if (break 'outer i) {
+ loop { break 'inner 5i8; };
+ } else if true {
+ break 'inner 6;
+ }
+ break 7;
+ };
+ break inner < 8;
+ };
+ }
+ "#,
+ expect![[r#"
+ 9..335 '{ ... }; }': ()
+ 19..21 '_x': || -> bool
+ 24..332 '|| 'ou... }': || -> bool
+ 27..332 ''outer... }': bool
+ 40..332 '{ ... }': ()
+ 54..59 'inner': i8
+ 62..300 ''inner... }': i8
+ 75..300 '{ ... }': ()
+ 93..94 'i': bool
+ 97..113 'Defaul...efault': {unknown}
+ 97..115 'Defaul...ault()': bool
+ 129..269 'if (br... }': ()
+ 133..147 'break 'outer i': !
+ 146..147 'i': bool
+ 149..208 '{ ... }': ()
+ 167..193 'loop {...5i8; }': !
+ 172..193 '{ brea...5i8; }': ()
+ 174..190 'break ...er 5i8': !
+ 187..190 '5i8': i8
+ 214..269 'if tru... }': ()
+ 217..221 'true': bool
+ 222..269 '{ ... }': ()
+ 240..254 'break 'inner 6': !
+ 253..254 '6': i8
+ 282..289 'break 7': !
+ 288..289 '7': i8
+ 310..325 'break inner < 8': !
+ 316..321 'inner': i8
+ 316..325 'inner < 8': bool
+ 324..325 '8': i8
+ "#]],
+ );
+}
+
+#[test]
+fn infer_labelled_block_break_with_val() {
+ check_infer(
+ r#"
+fn default<T>() -> T { loop {} }
+fn foo() {
+ let _x = 'outer: {
+ let inner = 'inner: {
+ let i = default();
+ if (break 'outer i) {
+ break 'inner 5i8;
+ } else if true {
+ break 'inner 6;
+ }
+ break 'inner 'innermost: { 0 };
+ 42
+ };
+ break 'outer inner < 8;
+ };
+}
+"#,
+ expect![[r#"
+ 21..32 '{ loop {} }': T
+ 23..30 'loop {}': !
+ 28..30 '{}': ()
+ 42..381 '{ ... }; }': ()
+ 52..54 '_x': bool
+ 57..378 ''outer... }': bool
+ 79..84 'inner': i8
+ 87..339 ''inner... }': i8
+ 113..114 'i': bool
+ 117..124 'default': fn default<bool>() -> bool
+ 117..126 'default()': bool
+ 140..270 'if (br... }': ()
+ 144..158 'break 'outer i': !
+ 157..158 'i': bool
+ 160..209 '{ ... }': ()
+ 178..194 'break ...er 5i8': !
+ 191..194 '5i8': i8
+ 215..270 'if tru... }': ()
+ 218..222 'true': bool
+ 223..270 '{ ... }': ()
+ 241..255 'break 'inner 6': !
+ 254..255 '6': i8
+ 283..313 'break ... { 0 }': !
+ 296..313 ''inner... { 0 }': i8
+ 310..311 '0': i8
+ 327..329 '42': i8
+ 349..371 'break ...er < 8': !
+ 362..367 'inner': i8
+ 362..371 'inner < 8': bool
+ 370..371 '8': i8
+ "#]],
+ );
+}
+
+#[test]
+fn generic_default() {
+ check_infer(
+ r#"
+ struct Thing<T = ()> { t: T }
+ enum OtherThing<T = ()> {
+ One { t: T },
+ Two(T),
+ }
+
+ fn test(t1: Thing, t2: OtherThing, t3: Thing<i32>, t4: OtherThing<i32>) {
+ t1.t;
+ t3.t;
+ match t2 {
+ OtherThing::One { t } => { t; },
+ OtherThing::Two(t) => { t; },
+ }
+ match t4 {
+ OtherThing::One { t } => { t; },
+ OtherThing::Two(t) => { t; },
+ }
+ }
+ "#,
+ expect![[r#"
+ 97..99 't1': Thing<()>
+ 108..110 't2': OtherThing<()>
+ 124..126 't3': Thing<i32>
+ 140..142 't4': OtherThing<i32>
+ 161..384 '{ ... } }': ()
+ 167..169 't1': Thing<()>
+ 167..171 't1.t': ()
+ 177..179 't3': Thing<i32>
+ 177..181 't3.t': i32
+ 187..282 'match ... }': ()
+ 193..195 't2': OtherThing<()>
+ 206..227 'OtherT... { t }': OtherThing<()>
+ 224..225 't': ()
+ 231..237 '{ t; }': ()
+ 233..234 't': ()
+ 247..265 'OtherT...Two(t)': OtherThing<()>
+ 263..264 't': ()
+ 269..275 '{ t; }': ()
+ 271..272 't': ()
+ 287..382 'match ... }': ()
+ 293..295 't4': OtherThing<i32>
+ 306..327 'OtherT... { t }': OtherThing<i32>
+ 324..325 't': i32
+ 331..337 '{ t; }': ()
+ 333..334 't': i32
+ 347..365 'OtherT...Two(t)': OtherThing<i32>
+ 363..364 't': i32
+ 369..375 '{ t; }': ()
+ 371..372 't': i32
+ "#]],
+ );
+}
+
+#[test]
+fn generic_default_in_struct_literal() {
+ check_infer(
+ r#"
+ struct Thing<T = ()> { t: T }
+ enum OtherThing<T = ()> {
+ One { t: T },
+ Two(T),
+ }
+
+ fn test() {
+ let x = Thing { t: loop {} };
+ let y = Thing { t: () };
+ let z = Thing { t: 1i32 };
+ if let Thing { t } = z {
+ t;
+ }
+
+ let a = OtherThing::One { t: 1i32 };
+ let b = OtherThing::Two(1i32);
+ }
+ "#,
+ expect![[r#"
+ 99..319 '{ ...32); }': ()
+ 109..110 'x': Thing<!>
+ 113..133 'Thing ...p {} }': Thing<!>
+ 124..131 'loop {}': !
+ 129..131 '{}': ()
+ 143..144 'y': Thing<()>
+ 147..162 'Thing { t: () }': Thing<()>
+ 158..160 '()': ()
+ 172..173 'z': Thing<i32>
+ 176..193 'Thing ...1i32 }': Thing<i32>
+ 187..191 '1i32': i32
+ 199..240 'if let... }': ()
+ 202..221 'let Th... } = z': bool
+ 206..217 'Thing { t }': Thing<i32>
+ 214..215 't': i32
+ 220..221 'z': Thing<i32>
+ 222..240 '{ ... }': ()
+ 232..233 't': i32
+ 250..251 'a': OtherThing<i32>
+ 254..281 'OtherT...1i32 }': OtherThing<i32>
+ 275..279 '1i32': i32
+ 291..292 'b': OtherThing<i32>
+ 295..310 'OtherThing::Two': Two<i32>(i32) -> OtherThing<i32>
+ 295..316 'OtherT...(1i32)': OtherThing<i32>
+ 311..315 '1i32': i32
+ "#]],
+ );
+}
+
+#[test]
+fn generic_default_depending_on_other_type_arg() {
+ // FIXME: the {unknown} is a bug
+ check_infer(
+ r#"
+ struct Thing<T = u128, F = fn() -> T> { t: T }
+
+ fn test(t1: Thing<u32>, t2: Thing) {
+ t1;
+ t2;
+ Thing::<_> { t: 1u32 };
+ }
+ "#,
+ expect![[r#"
+ 56..58 't1': Thing<u32, fn() -> u32>
+ 72..74 't2': Thing<u128, fn() -> u128>
+ 83..130 '{ ...2 }; }': ()
+ 89..91 't1': Thing<u32, fn() -> u32>
+ 97..99 't2': Thing<u128, fn() -> u128>
+ 105..127 'Thing:...1u32 }': Thing<u32, fn() -> {unknown}>
+ 121..125 '1u32': u32
+ "#]],
+ );
+}
+
+#[test]
+fn generic_default_depending_on_other_type_arg_forward() {
+ // the {unknown} here is intentional, as defaults are not allowed to
+ // refer to type parameters coming later
+ check_infer(
+ r#"
+ struct Thing<F = fn() -> T, T = u128> { t: T }
+
+ fn test(t1: Thing) {
+ t1;
+ }
+ "#,
+ expect![[r#"
+ 56..58 't1': Thing<fn() -> {unknown}, u128>
+ 67..78 '{ t1; }': ()
+ 73..75 't1': Thing<fn() -> {unknown}, u128>
+ "#]],
+ );
+}
+
+#[test]
+fn infer_operator_overload() {
+ check_types(
+ r#"
+//- minicore: add
+struct V2([f32; 2]);
+
+impl core::ops::Add<V2> for V2 {
+ type Output = V2;
+}
+
+fn test() {
+ let va = V2([0.0, 1.0]);
+ let vb = V2([0.0, 1.0]);
+
+ let r = va + vb;
+ // ^^^^^^^ V2
+}
+
+ "#,
+ );
+}
+
+#[test]
+fn infer_const_params() {
+ check_infer(
+ r#"
+ fn foo<const FOO: usize>() {
+ let bar = FOO;
+ }
+ "#,
+ expect![[r#"
+ 27..49 '{ ...FOO; }': ()
+ 37..40 'bar': usize
+ 43..46 'FOO': usize
+ "#]],
+ );
+}
+
+#[test]
+fn infer_inner_type() {
+ check_infer(
+ r#"
+ fn foo() {
+ struct S { field: u32 }
+ let s = S { field: 0 };
+ let f = s.field;
+ }
+ "#,
+ expect![[r#"
+ 9..89 '{ ...eld; }': ()
+ 47..48 's': S
+ 51..65 'S { field: 0 }': S
+ 62..63 '0': u32
+ 75..76 'f': u32
+ 79..80 's': S
+ 79..86 's.field': u32
+ "#]],
+ );
+}
+
+#[test]
+fn infer_nested_inner_type() {
+ check_infer(
+ r#"
+ fn foo() {
+ {
+ let s = S { field: 0 };
+ let f = s.field;
+ }
+ struct S { field: u32 }
+ }
+ "#,
+ expect![[r#"
+ 9..109 '{ ...32 } }': ()
+ 15..79 '{ ... }': ()
+ 29..30 's': S
+ 33..47 'S { field: 0 }': S
+ 44..45 '0': u32
+ 61..62 'f': u32
+ 65..66 's': S
+ 65..72 's.field': u32
+ "#]],
+ );
+}
+
+#[test]
+fn inner_use_enum_rename() {
+ check_infer(
+ r#"
+ enum Request {
+ Info
+ }
+
+ fn f() {
+ use Request as R;
+
+ let r = R::Info;
+ match r {
+ R::Info => {}
+ }
+ }
+ "#,
+ expect![[r#"
+ 34..123 '{ ... } }': ()
+ 67..68 'r': Request
+ 71..78 'R::Info': Request
+ 84..121 'match ... }': ()
+ 90..91 'r': Request
+ 102..109 'R::Info': Request
+ 113..115 '{}': ()
+ "#]],
+ )
+}
+
+#[test]
+fn box_into_vec() {
+ check_infer(
+ r#"
+#[lang = "sized"]
+pub trait Sized {}
+
+#[lang = "unsize"]
+pub trait Unsize<T: ?Sized> {}
+
+#[lang = "coerce_unsized"]
+pub trait CoerceUnsized<T> {}
+
+pub unsafe trait Allocator {}
+
+pub struct Global;
+unsafe impl Allocator for Global {}
+
+#[lang = "owned_box"]
+#[fundamental]
+pub struct Box<T: ?Sized, A: Allocator = Global>;
+
+impl<T: ?Sized + Unsize<U>, U: ?Sized, A: Allocator> CoerceUnsized<Box<U, A>> for Box<T, A> {}
+
+pub struct Vec<T, A: Allocator = Global> {}
+
+#[lang = "slice"]
+impl<T> [T] {}
+
+#[lang = "slice_alloc"]
+impl<T> [T] {
+ pub fn into_vec<A: Allocator>(self: Box<Self, A>) -> Vec<T, A> {
+ unimplemented!()
+ }
+}
+
+fn test() {
+ let vec = <[_]>::into_vec(box [1i32]);
+ let v: Vec<Box<dyn B>> = <[_]> :: into_vec(box [box Astruct]);
+}
+
+trait B{}
+struct Astruct;
+impl B for Astruct {}
+"#,
+ expect![[r#"
+ 569..573 'self': Box<[T], A>
+ 602..634 '{ ... }': Vec<T, A>
+ 648..761 '{ ...t]); }': ()
+ 658..661 'vec': Vec<i32, Global>
+ 664..679 '<[_]>::into_vec': fn into_vec<i32, Global>(Box<[i32], Global>) -> Vec<i32, Global>
+ 664..691 '<[_]>:...1i32])': Vec<i32, Global>
+ 680..690 'box [1i32]': Box<[i32; 1], Global>
+ 684..690 '[1i32]': [i32; 1]
+ 685..689 '1i32': i32
+ 701..702 'v': Vec<Box<dyn B, Global>, Global>
+ 722..739 '<[_]> ...to_vec': fn into_vec<Box<dyn B, Global>, Global>(Box<[Box<dyn B, Global>], Global>) -> Vec<Box<dyn B, Global>, Global>
+ 722..758 '<[_]> ...ruct])': Vec<Box<dyn B, Global>, Global>
+ 740..757 'box [b...truct]': Box<[Box<dyn B, Global>; 1], Global>
+ 744..757 '[box Astruct]': [Box<dyn B, Global>; 1]
+ 745..756 'box Astruct': Box<Astruct, Global>
+ 749..756 'Astruct': Astruct
+ "#]],
+ )
+}
+
+#[test]
+fn cfgd_out_assoc_items() {
+ check_types(
+ r#"
+struct S;
+
+impl S {
+ #[cfg(FALSE)]
+ const C: S = S;
+}
+
+fn f() {
+ S::C;
+ //^^^^ {unknown}
+}
+ "#,
+ )
+}
+
+#[test]
+fn infer_missing_type() {
+ check_types(
+ r#"
+struct S;
+
+fn f() {
+ let s: = S;
+ //^ S
+}
+ "#,
+ );
+}
+
+#[test]
+fn infer_type_alias_variant() {
+ check_infer(
+ r#"
+type Qux = Foo;
+enum Foo {
+ Bar(i32),
+ Baz { baz: f32 }
+}
+
+fn f() {
+ match Foo::Bar(3) {
+ Qux::Bar(bar) => (),
+ Qux::Baz { baz } => (),
+ }
+}
+ "#,
+ expect![[r#"
+ 72..166 '{ ... } }': ()
+ 78..164 'match ... }': ()
+ 84..92 'Foo::Bar': Bar(i32) -> Foo
+ 84..95 'Foo::Bar(3)': Foo
+ 93..94 '3': i32
+ 106..119 'Qux::Bar(bar)': Foo
+ 115..118 'bar': i32
+ 123..125 '()': ()
+ 135..151 'Qux::B... baz }': Foo
+ 146..149 'baz': f32
+ 155..157 '()': ()
+ "#]],
+ )
+}
+
+#[test]
+fn infer_boxed_self_receiver() {
+ check_infer(
+ r#"
+//- minicore: deref
+use core::ops::Deref;
+
+struct Box<T>(T);
+
+impl<T> Deref for Box<T> {
+ type Target = T;
+ fn deref(&self) -> &Self::Target;
+}
+
+struct Foo<T>(T);
+
+impl<T> Foo<T> {
+ fn get_inner<'a>(self: &'a Box<Self>) -> &'a T {}
+
+ fn get_self<'a>(self: &'a Box<Self>) -> &'a Self {}
+
+ fn into_inner(self: Box<Self>) -> Self {}
+}
+
+fn main() {
+ let boxed = Box(Foo(0_i32));
+
+ let bad1 = boxed.get_inner();
+ let good1 = Foo::get_inner(&boxed);
+
+ let bad2 = boxed.get_self();
+ let good2 = Foo::get_self(&boxed);
+
+ let inner = boxed.into_inner();
+}
+ "#,
+ expect![[r#"
+ 104..108 'self': &Box<T>
+ 188..192 'self': &Box<Foo<T>>
+ 218..220 '{}': &T
+ 242..246 'self': &Box<Foo<T>>
+ 275..277 '{}': &Foo<T>
+ 297..301 'self': Box<Foo<T>>
+ 322..324 '{}': Foo<T>
+ 338..559 '{ ...r(); }': ()
+ 348..353 'boxed': Box<Foo<i32>>
+ 356..359 'Box': Box<Foo<i32>>(Foo<i32>) -> Box<Foo<i32>>
+ 356..371 'Box(Foo(0_i32))': Box<Foo<i32>>
+ 360..363 'Foo': Foo<i32>(i32) -> Foo<i32>
+ 360..370 'Foo(0_i32)': Foo<i32>
+ 364..369 '0_i32': i32
+ 382..386 'bad1': &i32
+ 389..394 'boxed': Box<Foo<i32>>
+ 389..406 'boxed....nner()': &i32
+ 416..421 'good1': &i32
+ 424..438 'Foo::get_inner': fn get_inner<i32>(&Box<Foo<i32>>) -> &i32
+ 424..446 'Foo::g...boxed)': &i32
+ 439..445 '&boxed': &Box<Foo<i32>>
+ 440..445 'boxed': Box<Foo<i32>>
+ 457..461 'bad2': &Foo<i32>
+ 464..469 'boxed': Box<Foo<i32>>
+ 464..480 'boxed....self()': &Foo<i32>
+ 490..495 'good2': &Foo<i32>
+ 498..511 'Foo::get_self': fn get_self<i32>(&Box<Foo<i32>>) -> &Foo<i32>
+ 498..519 'Foo::g...boxed)': &Foo<i32>
+ 512..518 '&boxed': &Box<Foo<i32>>
+ 513..518 'boxed': Box<Foo<i32>>
+ 530..535 'inner': Foo<i32>
+ 538..543 'boxed': Box<Foo<i32>>
+ 538..556 'boxed....nner()': Foo<i32>
+ "#]],
+ );
+}
+
+#[test]
+fn prelude_2015() {
+ check_types(
+ r#"
+//- /main.rs edition:2015 crate:main deps:core
+fn f() {
+ Rust;
+ //^^^^ Rust
+}
+
+//- /core.rs crate:core
+pub mod prelude {
+ pub mod rust_2015 {
+ pub struct Rust;
+ }
+}
+ "#,
+ );
+}
+
+#[test]
+fn legacy_const_generics() {
+ check_no_mismatches(
+ r#"
+#[rustc_legacy_const_generics(1, 3)]
+fn mixed<const N1: &'static str, const N2: bool>(
+ a: u8,
+ b: i8,
+) {}
+
+fn f() {
+ mixed(0, "", -1, true);
+ mixed::<"", true>(0, -1);
+}
+ "#,
+ );
+}
+
+#[test]
+fn destructuring_assignment_slice() {
+ check_types(
+ r#"
+fn main() {
+ let a;
+ //^usize
+ [a,] = [0usize];
+
+ let a;
+ //^usize
+ [a, ..] = [0usize; 5];
+
+ let a;
+ //^usize
+ [.., a] = [0usize; 5];
+
+ let a;
+ //^usize
+ [.., a, _] = [0usize; 5];
+
+ let a;
+ //^usize
+ [_, a, ..] = [0usize; 5];
+
+ let a: &mut i64 = &mut 0;
+ [*a, ..] = [1, 2, 3];
+
+ let a: usize;
+ let b;
+ //^usize
+ [a, _, b] = [3, 4, 5];
+ //^usize
+
+ let a;
+ //^i64
+ let b;
+ //^i64
+ [[a, ..], .., [.., b]] = [[1, 2], [3i64, 4], [5, 6], [7, 8]];
+}
+ "#,
+ );
+}
+
+#[test]
+fn destructuring_assignment_tuple() {
+ check_types(
+ r#"
+fn main() {
+ let a;
+ //^char
+ let b;
+ //^i64
+ (a, b) = ('c', 0i64);
+
+ let a;
+ //^char
+ (a, ..) = ('c', 0i64);
+
+ let a;
+ //^i64
+ (.., a) = ('c', 0i64);
+
+ let a;
+ //^char
+ let b;
+ //^i64
+ (a, .., b) = ('c', 0i64);
+
+ let a;
+ //^char
+ let b;
+ //^bool
+ (a, .., b) = ('c', 0i64, true);
+
+ let a;
+ //^i64
+ let b;
+ //^bool
+ (_, a, .., b) = ('c', 0i64, true);
+
+ let a;
+ //^i64
+ let b;
+ //^usize
+ (_, a, .., b) = ('c', 0i64, true, 0usize);
+
+ let mut a = 1;
+ //^^^^^i64
+ let mut b: i64 = 0;
+ (a, b) = (b, a);
+}
+ "#,
+ );
+}
+
+#[test]
+fn destructuring_assignment_tuple_struct() {
+ check_types(
+ r#"
+struct S2(char, i64);
+struct S3(char, i64, bool);
+struct S4(char, i64, bool usize);
+fn main() {
+ let a;
+ //^char
+ let b;
+ //^i64
+ S2(a, b) = S2('c', 0i64);
+
+ let a;
+ //^char
+ let b;
+ //^i64
+ S2(a, .., b) = S2('c', 0i64);
+
+ let a;
+ //^char
+ let b;
+ //^bool
+ S3(a, .., b) = S3('c', 0i64, true);
+
+ let a;
+ //^i64
+ let b;
+ //^bool
+ S3(_, a, .., b) = S3('c', 0i64, true);
+
+ let a;
+ //^i64
+ let b;
+ //^usize
+ S4(_, a, .., b) = S4('c', 0i64, true, 0usize);
+
+ struct Swap(i64, i64);
+
+ let mut a = 1;
+ //^^^^^i64
+ let mut b = 0;
+ //^^^^^i64
+ Swap(a, b) = Swap(b, a);
+}
+ "#,
+ );
+}
+
+#[test]
+fn destructuring_assignment_struct() {
+ check_types(
+ r#"
+struct S {
+ a: usize,
+ b: char,
+}
+struct T {
+ s: S,
+ t: i64,
+}
+
+fn main() {
+ let a;
+ //^usize
+ let c;
+ //^char
+ S { a, b: c } = S { a: 3, b: 'b' };
+
+ let a;
+ //^char
+ S { b: a, .. } = S { a: 3, b: 'b' };
+
+ let a;
+ //^char
+ S { b: a, _ } = S { a: 3, b: 'b' };
+
+ let a;
+ //^usize
+ let c;
+ //^char
+ let t;
+ //^i64
+ T { s: S { a, b: c }, t } = T { s: S { a: 3, b: 'b' }, t: 0 };
+}
+ "#,
+ );
+}
+
+#[test]
+fn destructuring_assignment_nested() {
+ check_types(
+ r#"
+struct S {
+ a: TS,
+ b: [char; 3],
+}
+struct TS(usize, i64);
+
+fn main() {
+ let a;
+ //^i32
+ let b;
+ //^bool
+ ([.., a], .., b, _) = ([0, 1, 2], true, 'c');
+
+ let a;
+ //^i32
+ let b;
+ //^i32
+ [(.., a, _), .., (b, ..)] = [(1, 2); 5];
+
+ let a;
+ //^usize
+ let b;
+ //^char
+ S { a: TS(a, ..), b: [_, b, ..] } = S { a: TS(0, 0), b: ['a'; 3] };
+}
+ "#,
+ );
+}
+
+#[test]
+fn destructuring_assignment_unit_struct() {
+ // taken from rustc; see https://github.com/rust-lang/rust/pull/95380
+ check_no_mismatches(
+ r#"
+struct S;
+enum E { V, }
+type A = E;
+
+fn main() {
+ let mut a;
+
+ (S, a) = (S, ());
+
+ (E::V, a) = (E::V, ());
+
+ (<E>::V, a) = (E::V, ());
+ (A::V, a) = (E::V, ());
+}
+
+impl S {
+ fn check() {
+ let a;
+ (Self, a) = (S, ());
+ }
+}
+
+impl E {
+ fn check() {
+ let a;
+ (Self::V, a) = (E::V, ());
+ }
+}
+ "#,
+ );
+}
+
+#[test]
+fn destructuring_assignment_no_default_binding_mode() {
+ check(
+ r#"
+struct S { a: usize }
+struct TS(usize);
+fn main() {
+ let x;
+ [x,] = &[1,];
+ //^^^^expected &[i32; 1], got [{unknown}; _]
+
+ // FIXME we only want the outermost error, but this matches the current
+ // behavior of slice patterns
+ let x;
+ [(x,),] = &[(1,),];
+ // ^^^^expected {unknown}, got ({unknown},)
+ //^^^^^^^expected &[(i32,); 1], got [{unknown}; _]
+
+ let x;
+ ((x,),) = &((1,),);
+ //^^^^^^^expected &((i32,),), got (({unknown},),)
+
+ let x;
+ (x,) = &(1,);
+ //^^^^expected &(i32,), got ({unknown},)
+
+ let x;
+ (S { a: x },) = &(S { a: 42 },);
+ //^^^^^^^^^^^^^expected &(S,), got (S,)
+
+ let x;
+ S { a: x } = &S { a: 42 };
+ //^^^^^^^^^^expected &S, got S
+
+ let x;
+ TS(x) = &TS(42);
+ //^^^^^expected &TS, got TS
+}
+ "#,
+ );
+}
+
+#[test]
+fn destructuring_assignment_type_mismatch_on_identifier() {
+ check(
+ r#"
+struct S { v: i64 }
+struct TS(i64);
+fn main() {
+ let mut a: usize = 0;
+ (a,) = (0i64,);
+ //^expected i64, got usize
+
+ let mut a: usize = 0;
+ [a,] = [0i64,];
+ //^expected i64, got usize
+
+ let mut a: usize = 0;
+ S { v: a } = S { v: 0 };
+ //^expected i64, got usize
+
+ let mut a: usize = 0;
+ TS(a) = TS(0);
+ //^expected i64, got usize
+}
+ "#,
+ );
+}
+
+#[test]
+fn nested_break() {
+ check_no_mismatches(
+ r#"
+fn func() {
+ let int = loop {
+ break 0;
+ break (break 0);
+ };
+}
+ "#,
+ );
+}
--- /dev/null
- type Item=T;
+use cov_mark::check;
+use expect_test::expect;
+
+use super::{check, check_infer, check_infer_with_mismatches, check_no_mismatches, check_types};
+
+#[test]
+fn infer_await() {
+ check_types(
+ r#"
+//- minicore: future
+struct IntFuture;
+
+impl core::future::Future for IntFuture {
+ type Output = u64;
+}
+
+fn test() {
+ let r = IntFuture;
+ let v = r.await;
+ v;
+} //^ u64
+"#,
+ );
+}
+
+#[test]
+fn infer_async() {
+ check_types(
+ r#"
+//- minicore: future
+async fn foo() -> u64 { 128 }
+
+fn test() {
+ let r = foo();
+ let v = r.await;
+ v;
+} //^ u64
+"#,
+ );
+}
+
+#[test]
+fn infer_desugar_async() {
+ check_types(
+ r#"
+//- minicore: future, sized
+async fn foo() -> u64 { 128 }
+
+fn test() {
+ let r = foo();
+ r;
+} //^ impl Future<Output = u64>
+"#,
+ );
+}
+
+#[test]
+fn infer_async_block() {
+ check_types(
+ r#"
+//- minicore: future, option
+async fn test() {
+ let a = async { 42 };
+ a;
+// ^ impl Future<Output = i32>
+ let x = a.await;
+ x;
+// ^ i32
+ let b = async {}.await;
+ b;
+// ^ ()
+ let c = async {
+ let y = None;
+ y
+ // ^ Option<u64>
+ };
+ let _: Option<u64> = c.await;
+ c;
+// ^ impl Future<Output = Option<u64>>
+}
+"#,
+ );
+}
+
+#[test]
+fn auto_sized_async_block() {
+ check_no_mismatches(
+ r#"
+//- minicore: future, sized
+
+use core::future::Future;
+struct MyFut<Fut>(Fut);
+
+impl<Fut> Future for MyFut<Fut>
+where Fut: Future
+{
+ type Output = Fut::Output;
+}
+async fn reproduction() -> usize {
+ let f = async {999usize};
+ MyFut(f).await
+}
+ "#,
+ );
+ check_no_mismatches(
+ r#"
+//- minicore: future
+//#11815
+#[lang = "sized"]
+pub trait Sized {}
+
+#[lang = "unsize"]
+pub trait Unsize<T: ?Sized> {}
+
+#[lang = "coerce_unsized"]
+pub trait CoerceUnsized<T> {}
+
+pub unsafe trait Allocator {}
+
+pub struct Global;
+unsafe impl Allocator for Global {}
+
+#[lang = "owned_box"]
+#[fundamental]
+pub struct Box<T: ?Sized, A: Allocator = Global>;
+
+impl<T: ?Sized + Unsize<U>, U: ?Sized, A: Allocator> CoerceUnsized<Box<U, A>> for Box<T, A> {}
+
+fn send() -> Box<dyn Future<Output = ()> + Send + 'static>{
+ box async move {}
+}
+
+fn not_send() -> Box<dyn Future<Output = ()> + 'static> {
+ box async move {}
+}
+ "#,
+ );
+}
+
+#[test]
+fn into_future_trait() {
+ check_types(
+ r#"
+//- minicore: future
+struct Futurable;
+impl core::future::IntoFuture for Futurable {
+ type Output = u64;
+ type IntoFuture = IntFuture;
+}
+
+struct IntFuture;
+impl core::future::Future for IntFuture {
+ type Output = u64;
+}
+
+fn test() {
+ let r = Futurable;
+ let v = r.await;
+ v;
+} //^ u64
+"#,
+ );
+}
+
+#[test]
+fn infer_try() {
+ check_types(
+ r#"
+//- /main.rs crate:main deps:core
+fn test() {
+ let r: Result<i32, u64> = Result::Ok(1);
+ let v = r?;
+ v;
+} //^ i32
+
+//- /core.rs crate:core
+pub mod ops {
+ pub trait Try {
+ type Ok;
+ type Error;
+ }
+}
+
+pub mod result {
+ pub enum Result<O, E> {
+ Ok(O),
+ Err(E)
+ }
+
+ impl<O, E> crate::ops::Try for Result<O, E> {
+ type Ok = O;
+ type Error = E;
+ }
+}
+
+pub mod prelude {
+ pub mod rust_2018 {
+ pub use crate::{result::*, ops::*};
+ }
+}
+"#,
+ );
+}
+
+#[test]
+fn infer_try_trait_v2() {
+ check_types(
+ r#"
+//- /main.rs crate:main deps:core
+fn test() {
+ let r: Result<i32, u64> = Result::Ok(1);
+ let v = r?;
+ v;
+} //^ i32
+
+//- /core.rs crate:core
+mod ops {
+ mod try_trait {
+ pub trait Try: FromResidual {
+ type Output;
+ type Residual;
+ }
+ pub trait FromResidual<R = <Self as Try>::Residual> {}
+ }
+
+ pub use self::try_trait::FromResidual;
+ pub use self::try_trait::Try;
+}
+
+mod convert {
+ pub trait From<T> {}
+ impl<T> From<T> for T {}
+}
+
+pub mod result {
+ use crate::convert::From;
+ use crate::ops::{Try, FromResidual};
+
+ pub enum Infallible {}
+ pub enum Result<O, E> {
+ Ok(O),
+ Err(E)
+ }
+
+ impl<O, E> Try for Result<O, E> {
+ type Output = O;
+ type Error = Result<Infallible, E>;
+ }
+
+ impl<T, E, F: From<E>> FromResidual<Result<Infallible, E>> for Result<T, F> {}
+}
+
+pub mod prelude {
+ pub mod rust_2018 {
+ pub use crate::result::*;
+ }
+}
+"#,
+ );
+}
+
+#[test]
+fn infer_for_loop() {
+ check_types(
+ r#"
+//- /main.rs crate:main deps:core,alloc
+#![no_std]
+use alloc::collections::Vec;
+
+fn test() {
+ let v = Vec::new();
+ v.push("foo");
+ for x in v {
+ x;
+ } //^ &str
+}
+
+//- /core.rs crate:core
+pub mod iter {
+ pub trait IntoIterator {
+ type Item;
++ type IntoIter: Iterator<Item = Self::Item>;
++ }
++ pub trait Iterator {
++ type Item;
+ }
+}
+pub mod prelude {
+ pub mod rust_2018 {
+ pub use crate::iter::*;
+ }
+}
+
+//- /alloc.rs crate:alloc deps:core
+#![no_std]
+pub mod collections {
+ pub struct Vec<T> {}
+ impl<T> Vec<T> {
+ pub fn new() -> Self { Vec {} }
+ pub fn push(&mut self, t: T) { }
+ }
+
+ impl<T> IntoIterator for Vec<T> {
++ type Item = T;
++ type IntoIter = IntoIter<T>;
++ }
++
++ struct IntoIter<T> {}
++ impl<T> Iterator for IntoIter<T> {
++ type Item = T;
+ }
+}
+"#,
+ );
+}
+
+#[test]
+fn infer_ops_neg() {
+ check_types(
+ r#"
+//- /main.rs crate:main deps:std
+struct Bar;
+struct Foo;
+
+impl std::ops::Neg for Bar {
+ type Output = Foo;
+}
+
+fn test() {
+ let a = Bar;
+ let b = -a;
+ b;
+} //^ Foo
+
+//- /std.rs crate:std
+#[prelude_import] use ops::*;
+mod ops {
+ #[lang = "neg"]
+ pub trait Neg {
+ type Output;
+ }
+}
+"#,
+ );
+}
+
+#[test]
+fn infer_ops_not() {
+ check_types(
+ r#"
+//- /main.rs crate:main deps:std
+struct Bar;
+struct Foo;
+
+impl std::ops::Not for Bar {
+ type Output = Foo;
+}
+
+fn test() {
+ let a = Bar;
+ let b = !a;
+ b;
+} //^ Foo
+
+//- /std.rs crate:std
+#[prelude_import] use ops::*;
+mod ops {
+ #[lang = "not"]
+ pub trait Not {
+ type Output;
+ }
+}
+"#,
+ );
+}
+
+#[test]
+fn infer_from_bound_1() {
+ check_types(
+ r#"
+trait Trait<T> {}
+struct S<T>(T);
+impl<U> Trait<U> for S<U> {}
+fn foo<T: Trait<u32>>(t: T) {}
+fn test() {
+ let s = S(unknown);
+ // ^^^^^^^ u32
+ foo(s);
+}"#,
+ );
+}
+
+#[test]
+fn infer_from_bound_2() {
+ check_types(
+ r#"
+trait Trait<T> {}
+struct S<T>(T);
+impl<U> Trait<U> for S<U> {}
+fn foo<U, T: Trait<U>>(t: T) -> U { loop {} }
+fn test() {
+ let s = S(unknown);
+ // ^^^^^^^ u32
+ let x: u32 = foo(s);
+}"#,
+ );
+}
+
+#[test]
+fn trait_default_method_self_bound_implements_trait() {
+ cov_mark::check!(trait_self_implements_self);
+ check(
+ r#"
+trait Trait {
+ fn foo(&self) -> i64;
+ fn bar(&self) -> () {
+ self.foo();
+ // ^^^^^^^^^^ type: i64
+ }
+}"#,
+ );
+}
+
+#[test]
+fn trait_default_method_self_bound_implements_super_trait() {
+ check(
+ r#"
+trait SuperTrait {
+ fn foo(&self) -> i64;
+}
+trait Trait: SuperTrait {
+ fn bar(&self) -> () {
+ self.foo();
+ // ^^^^^^^^^^ type: i64
+ }
+}"#,
+ );
+}
+
+#[test]
+fn infer_project_associated_type() {
+ check_types(
+ r#"
+trait Iterable {
+ type Item;
+}
+struct S;
+impl Iterable for S { type Item = u32; }
+fn test<T: Iterable>() {
+ let x: <S as Iterable>::Item = 1;
+ // ^ u32
+ let y: <T as Iterable>::Item = u;
+ // ^ Iterable::Item<T>
+ let z: T::Item = u;
+ // ^ Iterable::Item<T>
+ let a: <T>::Item = u;
+ // ^ Iterable::Item<T>
+}"#,
+ );
+}
+
+#[test]
+fn infer_return_associated_type() {
+ check_types(
+ r#"
+trait Iterable {
+ type Item;
+}
+struct S;
+impl Iterable for S { type Item = u32; }
+fn foo1<T: Iterable>(t: T) -> T::Item { loop {} }
+fn foo2<T: Iterable>(t: T) -> <T as Iterable>::Item { loop {} }
+fn foo3<T: Iterable>(t: T) -> <T>::Item { loop {} }
+fn test() {
+ foo1(S);
+ // ^^^^^^^ u32
+ foo2(S);
+ // ^^^^^^^ u32
+ foo3(S);
+ // ^^^^^^^ u32
+}"#,
+ );
+}
+
+#[test]
+fn associated_type_shorthand_from_method_bound() {
+ check_types(
+ r#"
+trait Iterable {
+ type Item;
+}
+struct S<T>;
+impl<T> S<T> {
+ fn foo(self) -> T::Item where T: Iterable { loop {} }
+}
+fn test<T: Iterable>() {
+ let s: S<T>;
+ s.foo();
+ // ^^^^^^^ Iterable::Item<T>
+}"#,
+ );
+}
+
+#[test]
+fn associated_type_shorthand_from_self_issue_12484() {
+ check_types(
+ r#"
+trait Bar {
+ type A;
+}
+trait Foo {
+ type A;
+ fn test(a: Self::A, _: impl Bar) {
+ a;
+ //^ Foo::A<Self>
+ }
+}"#,
+ );
+}
+
+#[test]
+fn infer_associated_type_bound() {
+ check_types(
+ r#"
+trait Iterable {
+ type Item;
+}
+fn test<T: Iterable<Item=u32>>() {
+ let y: T::Item = unknown;
+ // ^^^^^^^ u32
+}"#,
+ );
+}
+
+#[test]
+fn infer_const_body() {
+ // FIXME make check_types work with other bodies
+ check_infer(
+ r#"
+const A: u32 = 1 + 1;
+static B: u64 = { let x = 1; x };
+"#,
+ expect![[r#"
+ 15..16 '1': u32
+ 15..20 '1 + 1': u32
+ 19..20 '1': u32
+ 38..54 '{ let ...1; x }': u64
+ 44..45 'x': u64
+ 48..49 '1': u64
+ 51..52 'x': u64
+ "#]],
+ );
+}
+
+#[test]
+fn tuple_struct_fields() {
+ check_infer(
+ r#"
+struct S(i32, u64);
+fn test() -> u64 {
+ let a = S(4, 6);
+ let b = a.0;
+ a.1
+}"#,
+ expect![[r#"
+ 37..86 '{ ... a.1 }': u64
+ 47..48 'a': S
+ 51..52 'S': S(i32, u64) -> S
+ 51..58 'S(4, 6)': S
+ 53..54 '4': i32
+ 56..57 '6': u64
+ 68..69 'b': i32
+ 72..73 'a': S
+ 72..75 'a.0': i32
+ 81..82 'a': S
+ 81..84 'a.1': u64
+ "#]],
+ );
+}
+
+#[test]
+fn tuple_struct_with_fn() {
+ check_infer(
+ r#"
+struct S(fn(u32) -> u64);
+fn test() -> u64 {
+ let a = S(|i| 2*i);
+ let b = a.0(4);
+ a.0(2)
+}"#,
+ expect![[r#"
+ 43..101 '{ ...0(2) }': u64
+ 53..54 'a': S
+ 57..58 'S': S(fn(u32) -> u64) -> S
+ 57..67 'S(|i| 2*i)': S
+ 59..66 '|i| 2*i': |u32| -> u64
+ 60..61 'i': u32
+ 63..64 '2': u32
+ 63..66 '2*i': u32
+ 65..66 'i': u32
+ 77..78 'b': u64
+ 81..82 'a': S
+ 81..84 'a.0': fn(u32) -> u64
+ 81..87 'a.0(4)': u64
+ 85..86 '4': u32
+ 93..94 'a': S
+ 93..96 'a.0': fn(u32) -> u64
+ 93..99 'a.0(2)': u64
+ 97..98 '2': u32
+ "#]],
+ );
+}
+
+#[test]
+fn indexing_arrays() {
+ check_infer(
+ "fn main() { &mut [9][2]; }",
+ expect![[r#"
+ 10..26 '{ &mut...[2]; }': ()
+ 12..23 '&mut [9][2]': &mut {unknown}
+ 17..20 '[9]': [i32; 1]
+ 17..23 '[9][2]': {unknown}
+ 18..19 '9': i32
+ 21..22 '2': i32
+ "#]],
+ )
+}
+
+#[test]
+fn infer_ops_index() {
+ check_types(
+ r#"
+//- minicore: index
+struct Bar;
+struct Foo;
+
+impl core::ops::Index<u32> for Bar {
+ type Output = Foo;
+}
+
+fn test() {
+ let a = Bar;
+ let b = a[1u32];
+ b;
+} //^ Foo
+"#,
+ );
+}
+
+#[test]
+fn infer_ops_index_field() {
+ check_types(
+ r#"
+//- minicore: index
+struct Bar;
+struct Foo {
+ field: u32;
+}
+
+impl core::ops::Index<u32> for Bar {
+ type Output = Foo;
+}
+
+fn test() {
+ let a = Bar;
+ let b = a[1u32].field;
+ b;
+} //^ u32
+"#,
+ );
+}
+
+#[test]
+fn infer_ops_index_field_autoderef() {
+ check_types(
+ r#"
+//- minicore: index
+struct Bar;
+struct Foo {
+ field: u32;
+}
+
+impl core::ops::Index<u32> for Bar {
+ type Output = Foo;
+}
+
+fn test() {
+ let a = Bar;
+ let b = (&a[1u32]).field;
+ b;
+} //^ u32
+"#,
+ );
+}
+
+#[test]
+fn infer_ops_index_int() {
+ check_types(
+ r#"
+//- minicore: index
+struct Bar;
+struct Foo;
+
+impl core::ops::Index<u32> for Bar {
+ type Output = Foo;
+}
+
+struct Range;
+impl core::ops::Index<Range> for Bar {
+ type Output = Bar;
+}
+
+fn test() {
+ let a = Bar;
+ let b = a[1];
+ b;
+ //^ Foo
+}
+"#,
+ );
+}
+
+#[test]
+fn infer_ops_index_autoderef() {
+ check_types(
+ r#"
+//- minicore: index, slice
+fn test() {
+ let a = &[1u32, 2, 3];
+ let b = a[1];
+ b;
+} //^ u32
+"#,
+ );
+}
+
+#[test]
+fn deref_trait() {
+ check_types(
+ r#"
+//- minicore: deref
+struct Arc<T: ?Sized>;
+impl<T: ?Sized> core::ops::Deref for Arc<T> {
+ type Target = T;
+}
+
+struct S;
+impl S {
+ fn foo(&self) -> u128 { 0 }
+}
+
+fn test(s: Arc<S>) {
+ (*s, s.foo());
+} //^^^^^^^^^^^^^ (S, u128)
+"#,
+ );
+}
+
+#[test]
+fn deref_trait_with_inference_var() {
+ check_types(
+ r#"
+//- minicore: deref
+struct Arc<T: ?Sized>;
+fn new_arc<T: ?Sized>() -> Arc<T> { Arc }
+impl<T: ?Sized> core::ops::Deref for Arc<T> {
+ type Target = T;
+}
+
+struct S;
+fn foo(a: Arc<S>) {}
+
+fn test() {
+ let a = new_arc();
+ let b = *a;
+ //^^ S
+ foo(a);
+}
+"#,
+ );
+}
+
+#[test]
+fn deref_trait_infinite_recursion() {
+ check_types(
+ r#"
+//- minicore: deref
+struct S;
+
+impl core::ops::Deref for S {
+ type Target = S;
+}
+
+fn test(s: S) {
+ s.foo();
+} //^^^^^^^ {unknown}
+"#,
+ );
+}
+
+#[test]
+fn deref_trait_with_question_mark_size() {
+ check_types(
+ r#"
+//- minicore: deref
+struct Arc<T: ?Sized>;
+impl<T: ?Sized> core::ops::Deref for Arc<T> {
+ type Target = T;
+}
+
+struct S;
+impl S {
+ fn foo(&self) -> u128 { 0 }
+}
+
+fn test(s: Arc<S>) {
+ (*s, s.foo());
+} //^^^^^^^^^^^^^ (S, u128)
+"#,
+ );
+}
+
+#[test]
+fn deref_trait_with_implicit_sized_requirement_on_inference_var() {
+ check_types(
+ r#"
+//- minicore: deref
+struct Foo<T>;
+impl<T> core::ops::Deref for Foo<T> {
+ type Target = ();
+}
+fn test() {
+ let foo = Foo;
+ *foo;
+ //^^^^ ()
+ let _: Foo<u8> = foo;
+}
+"#,
+ )
+}
+
+#[test]
+fn obligation_from_function_clause() {
+ check_types(
+ r#"
+struct S;
+
+trait Trait<T> {}
+impl Trait<u32> for S {}
+
+fn foo<T: Trait<U>, U>(t: T) -> U { loop {} }
+
+fn test(s: S) {
+ foo(s);
+} //^^^^^^ u32
+"#,
+ );
+}
+
+#[test]
+fn obligation_from_method_clause() {
+ check_types(
+ r#"
+//- /main.rs
+struct S;
+
+trait Trait<T> {}
+impl Trait<isize> for S {}
+
+struct O;
+impl O {
+ fn foo<T: Trait<U>, U>(&self, t: T) -> U { loop {} }
+}
+
+fn test() {
+ O.foo(S);
+} //^^^^^^^^ isize
+"#,
+ );
+}
+
+#[test]
+fn obligation_from_self_method_clause() {
+ check_types(
+ r#"
+struct S;
+
+trait Trait<T> {}
+impl Trait<i64> for S {}
+
+impl S {
+ fn foo<U>(&self) -> U where Self: Trait<U> { loop {} }
+}
+
+fn test() {
+ S.foo();
+} //^^^^^^^ i64
+"#,
+ );
+}
+
+#[test]
+fn obligation_from_impl_clause() {
+ check_types(
+ r#"
+struct S;
+
+trait Trait<T> {}
+impl Trait<&str> for S {}
+
+struct O<T>;
+impl<U, T: Trait<U>> O<T> {
+ fn foo(&self) -> U { loop {} }
+}
+
+fn test(o: O<S>) {
+ o.foo();
+} //^^^^^^^ &str
+"#,
+ );
+}
+
+#[test]
+fn generic_param_env_1() {
+ check_types(
+ r#"
+trait Clone {}
+trait Trait { fn foo(self) -> u128; }
+struct S;
+impl Clone for S {}
+impl<T> Trait for T where T: Clone {}
+fn test<T: Clone>(t: T) { t.foo(); }
+ //^^^^^^^ u128
+"#,
+ );
+}
+
+#[test]
+fn generic_param_env_1_not_met() {
+ check_types(
+ r#"
+//- /main.rs
+trait Clone {}
+trait Trait { fn foo(self) -> u128; }
+struct S;
+impl Clone for S {}
+impl<T> Trait for T where T: Clone {}
+fn test<T>(t: T) { t.foo(); }
+ //^^^^^^^ {unknown}
+"#,
+ );
+}
+
+#[test]
+fn generic_param_env_2() {
+ check_types(
+ r#"
+trait Trait { fn foo(self) -> u128; }
+struct S;
+impl Trait for S {}
+fn test<T: Trait>(t: T) { t.foo(); }
+ //^^^^^^^ u128
+"#,
+ );
+}
+
+#[test]
+fn generic_param_env_2_not_met() {
+ check_types(
+ r#"
+trait Trait { fn foo(self) -> u128; }
+struct S;
+impl Trait for S {}
+fn test<T>(t: T) { t.foo(); }
+ //^^^^^^^ {unknown}
+"#,
+ );
+}
+
+#[test]
+fn generic_param_env_deref() {
+ check_types(
+ r#"
+//- minicore: deref
+trait Trait {}
+impl<T> core::ops::Deref for T where T: Trait {
+ type Target = i128;
+}
+fn test<T: Trait>(t: T) { *t; }
+ //^^ i128
+"#,
+ );
+}
+
+#[test]
+fn associated_type_placeholder() {
+ // inside the generic function, the associated type gets normalized to a placeholder `ApplL::Out<T>` [https://rust-lang.github.io/rustc-guide/traits/associated-types.html#placeholder-associated-types].
+ check_types(
+ r#"
+pub trait ApplyL {
+ type Out;
+}
+
+pub struct RefMutL<T>;
+
+impl<T> ApplyL for RefMutL<T> {
+ type Out = <T as ApplyL>::Out;
+}
+
+fn test<T: ApplyL>() {
+ let y: <RefMutL<T> as ApplyL>::Out = no_matter;
+ y;
+} //^ ApplyL::Out<T>
+"#,
+ );
+}
+
+#[test]
+fn associated_type_placeholder_2() {
+ check_types(
+ r#"
+pub trait ApplyL {
+ type Out;
+}
+fn foo<T: ApplyL>(t: T) -> <T as ApplyL>::Out;
+
+fn test<T: ApplyL>(t: T) {
+ let y = foo(t);
+ y;
+} //^ ApplyL::Out<T>
+"#,
+ );
+}
+
+#[test]
+fn argument_impl_trait() {
+ check_infer_with_mismatches(
+ r#"
+//- minicore: sized
+trait Trait<T> {
+ fn foo(&self) -> T;
+ fn foo2(&self) -> i64;
+}
+fn bar(x: impl Trait<u16>) {}
+struct S<T>(T);
+impl<T> Trait<T> for S<T> {}
+
+fn test(x: impl Trait<u64>, y: &impl Trait<u32>) {
+ x;
+ y;
+ let z = S(1);
+ bar(z);
+ x.foo();
+ y.foo();
+ z.foo();
+ x.foo2();
+ y.foo2();
+ z.foo2();
+}"#,
+ expect![[r#"
+ 29..33 'self': &Self
+ 54..58 'self': &Self
+ 77..78 'x': impl Trait<u16>
+ 97..99 '{}': ()
+ 154..155 'x': impl Trait<u64>
+ 174..175 'y': &impl Trait<u32>
+ 195..323 '{ ...2(); }': ()
+ 201..202 'x': impl Trait<u64>
+ 208..209 'y': &impl Trait<u32>
+ 219..220 'z': S<u16>
+ 223..224 'S': S<u16>(u16) -> S<u16>
+ 223..227 'S(1)': S<u16>
+ 225..226 '1': u16
+ 233..236 'bar': fn bar(S<u16>)
+ 233..239 'bar(z)': ()
+ 237..238 'z': S<u16>
+ 245..246 'x': impl Trait<u64>
+ 245..252 'x.foo()': u64
+ 258..259 'y': &impl Trait<u32>
+ 258..265 'y.foo()': u32
+ 271..272 'z': S<u16>
+ 271..278 'z.foo()': u16
+ 284..285 'x': impl Trait<u64>
+ 284..292 'x.foo2()': i64
+ 298..299 'y': &impl Trait<u32>
+ 298..306 'y.foo2()': i64
+ 312..313 'z': S<u16>
+ 312..320 'z.foo2()': i64
+ "#]],
+ );
+}
+
+#[test]
+fn argument_impl_trait_type_args_1() {
+ check_infer_with_mismatches(
+ r#"
+//- minicore: sized
+trait Trait {}
+trait Foo {
+ // this function has an implicit Self param, an explicit type param,
+ // and an implicit impl Trait param!
+ fn bar<T>(x: impl Trait) -> T { loop {} }
+}
+fn foo<T>(x: impl Trait) -> T { loop {} }
+struct S;
+impl Trait for S {}
+struct F;
+impl Foo for F {}
+
+fn test() {
+ Foo::bar(S);
+ <F as Foo>::bar(S);
+ F::bar(S);
+ Foo::bar::<u32>(S);
+ <F as Foo>::bar::<u32>(S);
+
+ foo(S);
+ foo::<u32>(S);
+ foo::<u32, i32>(S); // we should ignore the extraneous i32
+}"#,
+ expect![[r#"
+ 155..156 'x': impl Trait
+ 175..186 '{ loop {} }': T
+ 177..184 'loop {}': !
+ 182..184 '{}': ()
+ 199..200 'x': impl Trait
+ 219..230 '{ loop {} }': T
+ 221..228 'loop {}': !
+ 226..228 '{}': ()
+ 300..509 '{ ... i32 }': ()
+ 306..314 'Foo::bar': fn bar<{unknown}, {unknown}>(S) -> {unknown}
+ 306..317 'Foo::bar(S)': {unknown}
+ 315..316 'S': S
+ 323..338 '<F as Foo>::bar': fn bar<F, {unknown}>(S) -> {unknown}
+ 323..341 '<F as ...bar(S)': {unknown}
+ 339..340 'S': S
+ 347..353 'F::bar': fn bar<F, {unknown}>(S) -> {unknown}
+ 347..356 'F::bar(S)': {unknown}
+ 354..355 'S': S
+ 362..377 'Foo::bar::<u32>': fn bar<{unknown}, u32>(S) -> u32
+ 362..380 'Foo::b...32>(S)': u32
+ 378..379 'S': S
+ 386..408 '<F as ...:<u32>': fn bar<F, u32>(S) -> u32
+ 386..411 '<F as ...32>(S)': u32
+ 409..410 'S': S
+ 418..421 'foo': fn foo<{unknown}>(S) -> {unknown}
+ 418..424 'foo(S)': {unknown}
+ 422..423 'S': S
+ 430..440 'foo::<u32>': fn foo<u32>(S) -> u32
+ 430..443 'foo::<u32>(S)': u32
+ 441..442 'S': S
+ 449..464 'foo::<u32, i32>': fn foo<u32>(S) -> u32
+ 449..467 'foo::<...32>(S)': u32
+ 465..466 'S': S
+ "#]],
+ );
+}
+
+#[test]
+fn argument_impl_trait_type_args_2() {
+ check_infer_with_mismatches(
+ r#"
+//- minicore: sized
+trait Trait {}
+struct S;
+impl Trait for S {}
+struct F<T>;
+impl<T> F<T> {
+ fn foo<U>(self, x: impl Trait) -> (T, U) { loop {} }
+}
+
+fn test() {
+ F.foo(S);
+ F::<u32>.foo(S);
+ F::<u32>.foo::<i32>(S);
+ F::<u32>.foo::<i32, u32>(S); // extraneous argument should be ignored
+}"#,
+ expect![[r#"
+ 87..91 'self': F<T>
+ 93..94 'x': impl Trait
+ 118..129 '{ loop {} }': (T, U)
+ 120..127 'loop {}': !
+ 125..127 '{}': ()
+ 143..283 '{ ...ored }': ()
+ 149..150 'F': F<{unknown}>
+ 149..157 'F.foo(S)': ({unknown}, {unknown})
+ 155..156 'S': S
+ 163..171 'F::<u32>': F<u32>
+ 163..178 'F::<u32>.foo(S)': (u32, {unknown})
+ 176..177 'S': S
+ 184..192 'F::<u32>': F<u32>
+ 184..206 'F::<u3...32>(S)': (u32, i32)
+ 204..205 'S': S
+ 212..220 'F::<u32>': F<u32>
+ 212..239 'F::<u3...32>(S)': (u32, i32)
+ 237..238 'S': S
+ "#]],
+ );
+}
+
+#[test]
+fn argument_impl_trait_to_fn_pointer() {
+ check_infer_with_mismatches(
+ r#"
+//- minicore: sized
+trait Trait {}
+fn foo(x: impl Trait) { loop {} }
+struct S;
+impl Trait for S {}
+
+fn test() {
+ let f: fn(S) -> () = foo;
+}"#,
+ expect![[r#"
+ 22..23 'x': impl Trait
+ 37..48 '{ loop {} }': ()
+ 39..46 'loop {}': !
+ 44..46 '{}': ()
+ 90..123 '{ ...foo; }': ()
+ 100..101 'f': fn(S)
+ 117..120 'foo': fn foo(S)
+ "#]],
+ );
+}
+
+#[test]
+fn impl_trait() {
+ check_infer(
+ r#"
+//- minicore: sized
+trait Trait<T> {
+ fn foo(&self) -> T;
+ fn foo2(&self) -> i64;
+}
+fn bar() -> impl Trait<u64> {}
+
+fn test(x: impl Trait<u64>, y: &impl Trait<u64>) {
+ x;
+ y;
+ let z = bar();
+ x.foo();
+ y.foo();
+ z.foo();
+ x.foo2();
+ y.foo2();
+ z.foo2();
+}"#,
+ expect![[r#"
+ 29..33 'self': &Self
+ 54..58 'self': &Self
+ 98..100 '{}': ()
+ 110..111 'x': impl Trait<u64>
+ 130..131 'y': &impl Trait<u64>
+ 151..268 '{ ...2(); }': ()
+ 157..158 'x': impl Trait<u64>
+ 164..165 'y': &impl Trait<u64>
+ 175..176 'z': impl Trait<u64>
+ 179..182 'bar': fn bar() -> impl Trait<u64>
+ 179..184 'bar()': impl Trait<u64>
+ 190..191 'x': impl Trait<u64>
+ 190..197 'x.foo()': u64
+ 203..204 'y': &impl Trait<u64>
+ 203..210 'y.foo()': u64
+ 216..217 'z': impl Trait<u64>
+ 216..223 'z.foo()': u64
+ 229..230 'x': impl Trait<u64>
+ 229..237 'x.foo2()': i64
+ 243..244 'y': &impl Trait<u64>
+ 243..251 'y.foo2()': i64
+ 257..258 'z': impl Trait<u64>
+ 257..265 'z.foo2()': i64
+ "#]],
+ );
+}
+
+#[test]
+fn simple_return_pos_impl_trait() {
+ cov_mark::check!(lower_rpit);
+ check_infer(
+ r#"
+//- minicore: sized
+trait Trait<T> {
+ fn foo(&self) -> T;
+}
+fn bar() -> impl Trait<u64> { loop {} }
+
+fn test() {
+ let a = bar();
+ a.foo();
+}"#,
+ expect![[r#"
+ 29..33 'self': &Self
+ 71..82 '{ loop {} }': !
+ 73..80 'loop {}': !
+ 78..80 '{}': ()
+ 94..129 '{ ...o(); }': ()
+ 104..105 'a': impl Trait<u64>
+ 108..111 'bar': fn bar() -> impl Trait<u64>
+ 108..113 'bar()': impl Trait<u64>
+ 119..120 'a': impl Trait<u64>
+ 119..126 'a.foo()': u64
+ "#]],
+ );
+}
+
+#[test]
+fn more_return_pos_impl_trait() {
+ check_infer(
+ r#"
+//- minicore: sized
+trait Iterator {
+ type Item;
+ fn next(&mut self) -> Self::Item;
+}
+trait Trait<T> {
+ fn foo(&self) -> T;
+}
+fn bar() -> (impl Iterator<Item = impl Trait<u32>>, impl Trait<u64>) { loop {} }
+fn baz<T>(t: T) -> (impl Iterator<Item = impl Trait<T>>, impl Trait<T>) { loop {} }
+
+fn test() {
+ let (a, b) = bar();
+ a.next().foo();
+ b.foo();
+ let (c, d) = baz(1u128);
+ c.next().foo();
+ d.foo();
+}"#,
+ expect![[r#"
+ 49..53 'self': &mut Self
+ 101..105 'self': &Self
+ 184..195 '{ loop {} }': ({unknown}, {unknown})
+ 186..193 'loop {}': !
+ 191..193 '{}': ()
+ 206..207 't': T
+ 268..279 '{ loop {} }': ({unknown}, {unknown})
+ 270..277 'loop {}': !
+ 275..277 '{}': ()
+ 291..413 '{ ...o(); }': ()
+ 301..307 '(a, b)': (impl Iterator<Item = impl Trait<u32>>, impl Trait<u64>)
+ 302..303 'a': impl Iterator<Item = impl Trait<u32>>
+ 305..306 'b': impl Trait<u64>
+ 310..313 'bar': fn bar() -> (impl Iterator<Item = impl Trait<u32>>, impl Trait<u64>)
+ 310..315 'bar()': (impl Iterator<Item = impl Trait<u32>>, impl Trait<u64>)
+ 321..322 'a': impl Iterator<Item = impl Trait<u32>>
+ 321..329 'a.next()': impl Trait<u32>
+ 321..335 'a.next().foo()': u32
+ 341..342 'b': impl Trait<u64>
+ 341..348 'b.foo()': u64
+ 358..364 '(c, d)': (impl Iterator<Item = impl Trait<u128>>, impl Trait<u128>)
+ 359..360 'c': impl Iterator<Item = impl Trait<u128>>
+ 362..363 'd': impl Trait<u128>
+ 367..370 'baz': fn baz<u128>(u128) -> (impl Iterator<Item = impl Trait<u128>>, impl Trait<u128>)
+ 367..377 'baz(1u128)': (impl Iterator<Item = impl Trait<u128>>, impl Trait<u128>)
+ 371..376 '1u128': u128
+ 383..384 'c': impl Iterator<Item = impl Trait<u128>>
+ 383..391 'c.next()': impl Trait<u128>
+ 383..397 'c.next().foo()': u128
+ 403..404 'd': impl Trait<u128>
+ 403..410 'd.foo()': u128
+ "#]],
+ );
+}
+
+#[test]
+fn infer_from_return_pos_impl_trait() {
+ check_infer_with_mismatches(
+ r#"
+//- minicore: fn, sized
+trait Trait<T> {}
+struct Bar<T>(T);
+impl<T> Trait<T> for Bar<T> {}
+fn foo<const C: u8, T>() -> (impl FnOnce(&str, T), impl Trait<u8>) {
+ (|input, t| {}, Bar(C))
+}
+"#,
+ expect![[r#"
+ 134..165 '{ ...(C)) }': (|&str, T| -> (), Bar<u8>)
+ 140..163 '(|inpu...ar(C))': (|&str, T| -> (), Bar<u8>)
+ 141..154 '|input, t| {}': |&str, T| -> ()
+ 142..147 'input': &str
+ 149..150 't': T
+ 152..154 '{}': ()
+ 156..159 'Bar': Bar<u8>(u8) -> Bar<u8>
+ 156..162 'Bar(C)': Bar<u8>
+ 160..161 'C': u8
+ "#]],
+ );
+}
+
+#[test]
+fn dyn_trait() {
+ check_infer(
+ r#"
+//- minicore: sized
+trait Trait<T> {
+ fn foo(&self) -> T;
+ fn foo2(&self) -> i64;
+}
+fn bar() -> dyn Trait<u64> {}
+
+fn test(x: dyn Trait<u64>, y: &dyn Trait<u64>) {
+ x;
+ y;
+ let z = bar();
+ x.foo();
+ y.foo();
+ z.foo();
+ x.foo2();
+ y.foo2();
+ z.foo2();
+}"#,
+ expect![[r#"
+ 29..33 'self': &Self
+ 54..58 'self': &Self
+ 97..99 '{}': dyn Trait<u64>
+ 109..110 'x': dyn Trait<u64>
+ 128..129 'y': &dyn Trait<u64>
+ 148..265 '{ ...2(); }': ()
+ 154..155 'x': dyn Trait<u64>
+ 161..162 'y': &dyn Trait<u64>
+ 172..173 'z': dyn Trait<u64>
+ 176..179 'bar': fn bar() -> dyn Trait<u64>
+ 176..181 'bar()': dyn Trait<u64>
+ 187..188 'x': dyn Trait<u64>
+ 187..194 'x.foo()': u64
+ 200..201 'y': &dyn Trait<u64>
+ 200..207 'y.foo()': u64
+ 213..214 'z': dyn Trait<u64>
+ 213..220 'z.foo()': u64
+ 226..227 'x': dyn Trait<u64>
+ 226..234 'x.foo2()': i64
+ 240..241 'y': &dyn Trait<u64>
+ 240..248 'y.foo2()': i64
+ 254..255 'z': dyn Trait<u64>
+ 254..262 'z.foo2()': i64
+ "#]],
+ );
+}
+
+#[test]
+fn dyn_trait_in_impl() {
+ check_infer(
+ r#"
+//- minicore: sized
+trait Trait<T, U> {
+ fn foo(&self) -> (T, U);
+}
+struct S<T, U> {}
+impl<T, U> S<T, U> {
+ fn bar(&self) -> &dyn Trait<T, U> { loop {} }
+}
+trait Trait2<T, U> {
+ fn baz(&self) -> (T, U);
+}
+impl<T, U> Trait2<T, U> for dyn Trait<T, U> { }
+
+fn test(s: S<u32, i32>) {
+ s.bar().baz();
+}"#,
+ expect![[r#"
+ 32..36 'self': &Self
+ 102..106 'self': &S<T, U>
+ 128..139 '{ loop {} }': &dyn Trait<T, U>
+ 130..137 'loop {}': !
+ 135..137 '{}': ()
+ 175..179 'self': &Self
+ 251..252 's': S<u32, i32>
+ 267..289 '{ ...z(); }': ()
+ 273..274 's': S<u32, i32>
+ 273..280 's.bar()': &dyn Trait<u32, i32>
+ 273..286 's.bar().baz()': (u32, i32)
+ "#]],
+ );
+}
+
+#[test]
+fn dyn_trait_bare() {
+ check_infer(
+ r#"
+//- minicore: sized
+trait Trait {
+ fn foo(&self) -> u64;
+}
+fn bar() -> Trait {}
+
+fn test(x: Trait, y: &Trait) -> u64 {
+ x;
+ y;
+ let z = bar();
+ x.foo();
+ y.foo();
+ z.foo();
+}"#,
+ expect![[r#"
+ 26..30 'self': &Self
+ 60..62 '{}': dyn Trait
+ 72..73 'x': dyn Trait
+ 82..83 'y': &dyn Trait
+ 100..175 '{ ...o(); }': u64
+ 106..107 'x': dyn Trait
+ 113..114 'y': &dyn Trait
+ 124..125 'z': dyn Trait
+ 128..131 'bar': fn bar() -> dyn Trait
+ 128..133 'bar()': dyn Trait
+ 139..140 'x': dyn Trait
+ 139..146 'x.foo()': u64
+ 152..153 'y': &dyn Trait
+ 152..159 'y.foo()': u64
+ 165..166 'z': dyn Trait
+ 165..172 'z.foo()': u64
+ "#]],
+ );
+
+ check_infer_with_mismatches(
+ r#"
+//- minicore: fn, coerce_unsized
+struct S;
+impl S {
+ fn foo(&self) {}
+}
+fn f(_: &Fn(S)) {}
+fn main() {
+ f(&|number| number.foo());
+}
+ "#,
+ expect![[r#"
+ 31..35 'self': &S
+ 37..39 '{}': ()
+ 47..48 '_': &dyn Fn(S)
+ 58..60 '{}': ()
+ 71..105 '{ ...()); }': ()
+ 77..78 'f': fn f(&dyn Fn(S))
+ 77..102 'f(&|nu...foo())': ()
+ 79..101 '&|numb....foo()': &|S| -> ()
+ 80..101 '|numbe....foo()': |S| -> ()
+ 81..87 'number': S
+ 89..95 'number': S
+ 89..101 'number.foo()': ()
+ "#]],
+ )
+}
+
+#[test]
+fn weird_bounds() {
+ check_infer(
+ r#"
+//- minicore: sized
+trait Trait {}
+fn test(
+ a: impl Trait + 'lifetime,
+ b: impl 'lifetime,
+ c: impl (Trait),
+ d: impl ('lifetime),
+ e: impl ?Sized,
+ f: impl Trait + ?Sized
+) {}
+"#,
+ expect![[r#"
+ 28..29 'a': impl Trait
+ 59..60 'b': impl Sized
+ 82..83 'c': impl Trait
+ 103..104 'd': impl Sized
+ 128..129 'e': impl ?Sized
+ 148..149 'f': impl Trait + ?Sized
+ 173..175 '{}': ()
+ "#]],
+ );
+}
+
+#[test]
+fn error_bound_chalk() {
+ check_types(
+ r#"
+trait Trait {
+ fn foo(&self) -> u32 { 0 }
+}
+
+fn test(x: (impl Trait + UnknownTrait)) {
+ x.foo();
+} //^^^^^^^ u32
+"#,
+ );
+}
+
+#[test]
+fn assoc_type_bindings() {
+ check_infer(
+ r#"
+//- minicore: sized
+trait Trait {
+ type Type;
+}
+
+fn get<T: Trait>(t: T) -> <T as Trait>::Type {}
+fn get2<U, T: Trait<Type = U>>(t: T) -> U {}
+fn set<T: Trait<Type = u64>>(t: T) -> T {t}
+
+struct S<T>;
+impl<T> Trait for S<T> { type Type = T; }
+
+fn test<T: Trait<Type = u32>>(x: T, y: impl Trait<Type = i64>) {
+ get(x);
+ get2(x);
+ get(y);
+ get2(y);
+ get(set(S));
+ get2(set(S));
+ get2(S::<str>);
+}"#,
+ expect![[r#"
+ 49..50 't': T
+ 77..79 '{}': Trait::Type<T>
+ 111..112 't': T
+ 122..124 '{}': U
+ 154..155 't': T
+ 165..168 '{t}': T
+ 166..167 't': T
+ 256..257 'x': T
+ 262..263 'y': impl Trait<Type = i64>
+ 289..397 '{ ...r>); }': ()
+ 295..298 'get': fn get<T>(T) -> <T as Trait>::Type
+ 295..301 'get(x)': u32
+ 299..300 'x': T
+ 307..311 'get2': fn get2<u32, T>(T) -> u32
+ 307..314 'get2(x)': u32
+ 312..313 'x': T
+ 320..323 'get': fn get<impl Trait<Type = i64>>(impl Trait<Type = i64>) -> <impl Trait<Type = i64> as Trait>::Type
+ 320..326 'get(y)': i64
+ 324..325 'y': impl Trait<Type = i64>
+ 332..336 'get2': fn get2<i64, impl Trait<Type = i64>>(impl Trait<Type = i64>) -> i64
+ 332..339 'get2(y)': i64
+ 337..338 'y': impl Trait<Type = i64>
+ 345..348 'get': fn get<S<u64>>(S<u64>) -> <S<u64> as Trait>::Type
+ 345..356 'get(set(S))': u64
+ 349..352 'set': fn set<S<u64>>(S<u64>) -> S<u64>
+ 349..355 'set(S)': S<u64>
+ 353..354 'S': S<u64>
+ 362..366 'get2': fn get2<u64, S<u64>>(S<u64>) -> u64
+ 362..374 'get2(set(S))': u64
+ 367..370 'set': fn set<S<u64>>(S<u64>) -> S<u64>
+ 367..373 'set(S)': S<u64>
+ 371..372 'S': S<u64>
+ 380..384 'get2': fn get2<str, S<str>>(S<str>) -> str
+ 380..394 'get2(S::<str>)': str
+ 385..393 'S::<str>': S<str>
+ "#]],
+ );
+}
+
+#[test]
+fn impl_trait_assoc_binding_projection_bug() {
+ check_types(
+ r#"
+//- minicore: iterator
+pub trait Language {
+ type Kind;
+}
+pub enum RustLanguage {}
+impl Language for RustLanguage {
+ type Kind = SyntaxKind;
+}
+struct SyntaxNode<L> {}
+fn foo() -> impl Iterator<Item = SyntaxNode<RustLanguage>> {}
+
+trait Clone {
+ fn clone(&self) -> Self;
+}
+
+fn api_walkthrough() {
+ for node in foo() {
+ node.clone();
+ } //^^^^^^^^^^^^ {unknown}
+}
+"#,
+ );
+}
+
+#[test]
+fn projection_eq_within_chalk() {
+ check_infer(
+ r#"
+trait Trait1 {
+ type Type;
+}
+trait Trait2<T> {
+ fn foo(self) -> T;
+}
+impl<T, U> Trait2<T> for U where U: Trait1<Type = T> {}
+
+fn test<T: Trait1<Type = u32>>(x: T) {
+ x.foo();
+}"#,
+ expect![[r#"
+ 61..65 'self': Self
+ 163..164 'x': T
+ 169..185 '{ ...o(); }': ()
+ 175..176 'x': T
+ 175..182 'x.foo()': u32
+ "#]],
+ );
+}
+
+#[test]
+fn where_clause_trait_in_scope_for_method_resolution() {
+ check_types(
+ r#"
+mod foo {
+ trait Trait {
+ fn foo(&self) -> u32 { 0 }
+ }
+}
+
+fn test<T: foo::Trait>(x: T) {
+ x.foo();
+} //^^^^^^^ u32
+"#,
+ );
+}
+
+#[test]
+fn super_trait_method_resolution() {
+ check_infer(
+ r#"
+mod foo {
+ trait SuperTrait {
+ fn foo(&self) -> u32 {}
+ }
+}
+trait Trait1: foo::SuperTrait {}
+trait Trait2 where Self: foo::SuperTrait {}
+
+fn test<T: Trait1, U: Trait2>(x: T, y: U) {
+ x.foo();
+ y.foo();
+}"#,
+ expect![[r#"
+ 49..53 'self': &Self
+ 62..64 '{}': u32
+ 181..182 'x': T
+ 187..188 'y': U
+ 193..222 '{ ...o(); }': ()
+ 199..200 'x': T
+ 199..206 'x.foo()': u32
+ 212..213 'y': U
+ 212..219 'y.foo()': u32
+ "#]],
+ );
+}
+
+#[test]
+fn super_trait_impl_trait_method_resolution() {
+ check_infer(
+ r#"
+//- minicore: sized
+mod foo {
+ trait SuperTrait {
+ fn foo(&self) -> u32 {}
+ }
+}
+trait Trait1: foo::SuperTrait {}
+
+fn test(x: &impl Trait1) {
+ x.foo();
+}"#,
+ expect![[r#"
+ 49..53 'self': &Self
+ 62..64 '{}': u32
+ 115..116 'x': &impl Trait1
+ 132..148 '{ ...o(); }': ()
+ 138..139 'x': &impl Trait1
+ 138..145 'x.foo()': u32
+ "#]],
+ );
+}
+
+#[test]
+fn super_trait_cycle() {
+ // This just needs to not crash
+ check_infer(
+ r#"
+ trait A: B {}
+ trait B: A {}
+
+ fn test<T: A>(x: T) {
+ x.foo();
+ }
+ "#,
+ expect![[r#"
+ 43..44 'x': T
+ 49..65 '{ ...o(); }': ()
+ 55..56 'x': T
+ 55..62 'x.foo()': {unknown}
+ "#]],
+ );
+}
+
+#[test]
+fn super_trait_assoc_type_bounds() {
+ check_infer(
+ r#"
+trait SuperTrait { type Type; }
+trait Trait where Self: SuperTrait {}
+
+fn get2<U, T: Trait<Type = U>>(t: T) -> U {}
+fn set<T: Trait<Type = u64>>(t: T) -> T {t}
+
+struct S<T>;
+impl<T> SuperTrait for S<T> { type Type = T; }
+impl<T> Trait for S<T> {}
+
+fn test() {
+ get2(set(S));
+}"#,
+ expect![[r#"
+ 102..103 't': T
+ 113..115 '{}': U
+ 145..146 't': T
+ 156..159 '{t}': T
+ 157..158 't': T
+ 258..279 '{ ...S)); }': ()
+ 264..268 'get2': fn get2<u64, S<u64>>(S<u64>) -> u64
+ 264..276 'get2(set(S))': u64
+ 269..272 'set': fn set<S<u64>>(S<u64>) -> S<u64>
+ 269..275 'set(S)': S<u64>
+ 273..274 'S': S<u64>
+ "#]],
+ );
+}
+
+#[test]
+fn fn_trait() {
+ check_infer_with_mismatches(
+ r#"
+trait FnOnce<Args> {
+ type Output;
+
+ fn call_once(self, args: Args) -> <Self as FnOnce<Args>>::Output;
+}
+
+fn test<F: FnOnce(u32, u64) -> u128>(f: F) {
+ f.call_once((1, 2));
+}"#,
+ expect![[r#"
+ 56..60 'self': Self
+ 62..66 'args': Args
+ 149..150 'f': F
+ 155..183 '{ ...2)); }': ()
+ 161..162 'f': F
+ 161..180 'f.call...1, 2))': u128
+ 173..179 '(1, 2)': (u32, u64)
+ 174..175 '1': u32
+ 177..178 '2': u64
+ "#]],
+ );
+}
+
+#[test]
+fn fn_ptr_and_item() {
+ check_infer_with_mismatches(
+ r#"
+#[lang="fn_once"]
+trait FnOnce<Args> {
+ type Output;
+
+ fn call_once(self, args: Args) -> Self::Output;
+}
+
+trait Foo<T> {
+ fn foo(&self) -> T;
+}
+
+struct Bar<T>(T);
+
+impl<A1, R, F: FnOnce(A1) -> R> Foo<(A1, R)> for Bar<F> {
+ fn foo(&self) -> (A1, R) { loop {} }
+}
+
+enum Opt<T> { None, Some(T) }
+impl<T> Opt<T> {
+ fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Opt<U> { loop {} }
+}
+
+fn test() {
+ let bar: Bar<fn(u8) -> u32>;
+ bar.foo();
+
+ let opt: Opt<u8>;
+ let f: fn(u8) -> u32;
+ opt.map(f);
+}"#,
+ expect![[r#"
+ 74..78 'self': Self
+ 80..84 'args': Args
+ 139..143 'self': &Self
+ 243..247 'self': &Bar<F>
+ 260..271 '{ loop {} }': (A1, R)
+ 262..269 'loop {}': !
+ 267..269 '{}': ()
+ 355..359 'self': Opt<T>
+ 361..362 'f': F
+ 377..388 '{ loop {} }': Opt<U>
+ 379..386 'loop {}': !
+ 384..386 '{}': ()
+ 402..518 '{ ...(f); }': ()
+ 412..415 'bar': Bar<fn(u8) -> u32>
+ 441..444 'bar': Bar<fn(u8) -> u32>
+ 441..450 'bar.foo()': (u8, u32)
+ 461..464 'opt': Opt<u8>
+ 483..484 'f': fn(u8) -> u32
+ 505..508 'opt': Opt<u8>
+ 505..515 'opt.map(f)': Opt<u32>
+ 513..514 'f': fn(u8) -> u32
+ "#]],
+ );
+}
+
+#[test]
+fn fn_trait_deref_with_ty_default() {
+ check_infer(
+ r#"
+//- minicore: deref, fn
+struct Foo;
+
+impl Foo {
+ fn foo(&self) -> usize {}
+}
+
+struct Lazy<T, F = fn() -> T>(F);
+
+impl<T, F> Lazy<T, F> {
+ pub fn new(f: F) -> Lazy<T, F> {}
+}
+
+impl<T, F: FnOnce() -> T> core::ops::Deref for Lazy<T, F> {
+ type Target = T;
+}
+
+fn test() {
+ let lazy1: Lazy<Foo, _> = Lazy::new(|| Foo);
+ let r1 = lazy1.foo();
+
+ fn make_foo_fn() -> Foo {}
+ let make_foo_fn_ptr: fn() -> Foo = make_foo_fn;
+ let lazy2: Lazy<Foo, _> = Lazy::new(make_foo_fn_ptr);
+ let r2 = lazy2.foo();
+}"#,
+ expect![[r#"
+ 36..40 'self': &Foo
+ 51..53 '{}': usize
+ 131..132 'f': F
+ 151..153 '{}': Lazy<T, F>
+ 251..497 '{ ...o(); }': ()
+ 261..266 'lazy1': Lazy<Foo, || -> Foo>
+ 283..292 'Lazy::new': fn new<Foo, || -> Foo>(|| -> Foo) -> Lazy<Foo, || -> Foo>
+ 283..300 'Lazy::...| Foo)': Lazy<Foo, || -> Foo>
+ 293..299 '|| Foo': || -> Foo
+ 296..299 'Foo': Foo
+ 310..312 'r1': usize
+ 315..320 'lazy1': Lazy<Foo, || -> Foo>
+ 315..326 'lazy1.foo()': usize
+ 368..383 'make_foo_fn_ptr': fn() -> Foo
+ 399..410 'make_foo_fn': fn make_foo_fn() -> Foo
+ 420..425 'lazy2': Lazy<Foo, fn() -> Foo>
+ 442..451 'Lazy::new': fn new<Foo, fn() -> Foo>(fn() -> Foo) -> Lazy<Foo, fn() -> Foo>
+ 442..468 'Lazy::...n_ptr)': Lazy<Foo, fn() -> Foo>
+ 452..467 'make_foo_fn_ptr': fn() -> Foo
+ 478..480 'r2': usize
+ 483..488 'lazy2': Lazy<Foo, fn() -> Foo>
+ 483..494 'lazy2.foo()': usize
+ 357..359 '{}': Foo
+ "#]],
+ );
+}
+
+#[test]
+fn closure_1() {
+ check_infer_with_mismatches(
+ r#"
+//- minicore: fn
+enum Option<T> { Some(T), None }
+impl<T> Option<T> {
+ fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Option<U> { loop {} }
+}
+
+fn test() {
+ let x = Option::Some(1u32);
+ x.map(|v| v + 1);
+ x.map(|_v| 1u64);
+ let y: Option<i64> = x.map(|_v| 1);
+}"#,
+ expect![[r#"
+ 86..90 'self': Option<T>
+ 92..93 'f': F
+ 111..122 '{ loop {} }': Option<U>
+ 113..120 'loop {}': !
+ 118..120 '{}': ()
+ 136..255 '{ ... 1); }': ()
+ 146..147 'x': Option<u32>
+ 150..162 'Option::Some': Some<u32>(u32) -> Option<u32>
+ 150..168 'Option...(1u32)': Option<u32>
+ 163..167 '1u32': u32
+ 174..175 'x': Option<u32>
+ 174..190 'x.map(...v + 1)': Option<u32>
+ 180..189 '|v| v + 1': |u32| -> u32
+ 181..182 'v': u32
+ 184..185 'v': u32
+ 184..189 'v + 1': u32
+ 188..189 '1': u32
+ 196..197 'x': Option<u32>
+ 196..212 'x.map(... 1u64)': Option<u64>
+ 202..211 '|_v| 1u64': |u32| -> u64
+ 203..205 '_v': u32
+ 207..211 '1u64': u64
+ 222..223 'y': Option<i64>
+ 239..240 'x': Option<u32>
+ 239..252 'x.map(|_v| 1)': Option<i64>
+ 245..251 '|_v| 1': |u32| -> i64
+ 246..248 '_v': u32
+ 250..251 '1': i64
+ "#]],
+ );
+}
+
+#[test]
+fn closure_2() {
+ check_types(
+ r#"
+//- minicore: add, fn
+
+impl core::ops::Add for u64 {
+ type Output = Self;
+ fn add(self, rhs: u64) -> Self::Output {0}
+}
+
+impl core::ops::Add for u128 {
+ type Output = Self;
+ fn add(self, rhs: u128) -> Self::Output {0}
+}
+
+fn test<F: FnOnce(u32) -> u64>(f: F) {
+ f(1);
+ // ^ u32
+ //^^^^ u64
+ let g = |v| v + 1;
+ //^^^^^ u64
+ //^^^^^^^^^ |u64| -> u64
+ g(1u64);
+ //^^^^^^^ u64
+ let h = |v| 1u128 + v;
+ //^^^^^^^^^^^^^ |u128| -> u128
+}"#,
+ );
+}
+
+#[test]
+fn closure_as_argument_inference_order() {
+ check_infer_with_mismatches(
+ r#"
+//- minicore: fn
+fn foo1<T, U, F: FnOnce(T) -> U>(x: T, f: F) -> U { loop {} }
+fn foo2<T, U, F: FnOnce(T) -> U>(f: F, x: T) -> U { loop {} }
+
+struct S;
+impl S {
+ fn method(self) -> u64;
+
+ fn foo1<T, U, F: FnOnce(T) -> U>(self, x: T, f: F) -> U { loop {} }
+ fn foo2<T, U, F: FnOnce(T) -> U>(self, f: F, x: T) -> U { loop {} }
+}
+
+fn test() {
+ let x1 = foo1(S, |s| s.method());
+ let x2 = foo2(|s| s.method(), S);
+ let x3 = S.foo1(S, |s| s.method());
+ let x4 = S.foo2(|s| s.method(), S);
+}"#,
+ expect![[r#"
+ 33..34 'x': T
+ 39..40 'f': F
+ 50..61 '{ loop {} }': U
+ 52..59 'loop {}': !
+ 57..59 '{}': ()
+ 95..96 'f': F
+ 101..102 'x': T
+ 112..123 '{ loop {} }': U
+ 114..121 'loop {}': !
+ 119..121 '{}': ()
+ 158..162 'self': S
+ 210..214 'self': S
+ 216..217 'x': T
+ 222..223 'f': F
+ 233..244 '{ loop {} }': U
+ 235..242 'loop {}': !
+ 240..242 '{}': ()
+ 282..286 'self': S
+ 288..289 'f': F
+ 294..295 'x': T
+ 305..316 '{ loop {} }': U
+ 307..314 'loop {}': !
+ 312..314 '{}': ()
+ 330..489 '{ ... S); }': ()
+ 340..342 'x1': u64
+ 345..349 'foo1': fn foo1<S, u64, |S| -> u64>(S, |S| -> u64) -> u64
+ 345..368 'foo1(S...hod())': u64
+ 350..351 'S': S
+ 353..367 '|s| s.method()': |S| -> u64
+ 354..355 's': S
+ 357..358 's': S
+ 357..367 's.method()': u64
+ 378..380 'x2': u64
+ 383..387 'foo2': fn foo2<S, u64, |S| -> u64>(|S| -> u64, S) -> u64
+ 383..406 'foo2(|...(), S)': u64
+ 388..402 '|s| s.method()': |S| -> u64
+ 389..390 's': S
+ 392..393 's': S
+ 392..402 's.method()': u64
+ 404..405 'S': S
+ 416..418 'x3': u64
+ 421..422 'S': S
+ 421..446 'S.foo1...hod())': u64
+ 428..429 'S': S
+ 431..445 '|s| s.method()': |S| -> u64
+ 432..433 's': S
+ 435..436 's': S
+ 435..445 's.method()': u64
+ 456..458 'x4': u64
+ 461..462 'S': S
+ 461..486 'S.foo2...(), S)': u64
+ 468..482 '|s| s.method()': |S| -> u64
+ 469..470 's': S
+ 472..473 's': S
+ 472..482 's.method()': u64
+ 484..485 'S': S
+ "#]],
+ );
+}
+
+#[test]
+fn fn_item_fn_trait() {
+ check_types(
+ r#"
+//- minicore: fn
+struct S;
+
+fn foo() -> S { S }
+
+fn takes_closure<U, F: FnOnce() -> U>(f: F) -> U { f() }
+
+fn test() {
+ takes_closure(foo);
+} //^^^^^^^^^^^^^^^^^^ S
+"#,
+ );
+}
+
+#[test]
+fn unselected_projection_in_trait_env_1() {
+ check_types(
+ r#"
+//- /main.rs
+trait Trait {
+ type Item;
+}
+
+trait Trait2 {
+ fn foo(&self) -> u32;
+}
+
+fn test<T: Trait>() where T::Item: Trait2 {
+ let x: T::Item = no_matter;
+ x.foo();
+} //^^^^^^^ u32
+"#,
+ );
+}
+
+#[test]
+fn unselected_projection_in_trait_env_2() {
+ check_types(
+ r#"
+trait Trait<T> {
+ type Item;
+}
+
+trait Trait2 {
+ fn foo(&self) -> u32;
+}
+
+fn test<T, U>() where T::Item: Trait2, T: Trait<U::Item>, U: Trait<()> {
+ let x: T::Item = no_matter;
+ x.foo();
+} //^^^^^^^ u32
+"#,
+ );
+}
+
+#[test]
+fn unselected_projection_on_impl_self() {
+ check_infer(
+ r#"
+//- /main.rs
+trait Trait {
+ type Item;
+
+ fn f(&self, x: Self::Item);
+}
+
+struct S;
+
+impl Trait for S {
+ type Item = u32;
+ fn f(&self, x: Self::Item) { let y = x; }
+}
+
+struct S2;
+
+impl Trait for S2 {
+ type Item = i32;
+ fn f(&self, x: <Self>::Item) { let y = x; }
+}"#,
+ expect![[r#"
+ 40..44 'self': &Self
+ 46..47 'x': Trait::Item<Self>
+ 126..130 'self': &S
+ 132..133 'x': u32
+ 147..161 '{ let y = x; }': ()
+ 153..154 'y': u32
+ 157..158 'x': u32
+ 228..232 'self': &S2
+ 234..235 'x': i32
+ 251..265 '{ let y = x; }': ()
+ 257..258 'y': i32
+ 261..262 'x': i32
+ "#]],
+ );
+}
+
+#[test]
+fn unselected_projection_on_trait_self() {
+ check_types(
+ r#"
+trait Trait {
+ type Item;
+
+ fn f(&self) -> Self::Item { loop {} }
+}
+
+struct S;
+impl Trait for S {
+ type Item = u32;
+}
+
+fn test() {
+ S.f();
+} //^^^^^ u32
+"#,
+ );
+}
+
+#[test]
+fn unselected_projection_chalk_fold() {
+ check_types(
+ r#"
+trait Interner {}
+trait Fold<I: Interner, TI = I> {
+ type Result;
+}
+
+struct Ty<I: Interner> {}
+impl<I: Interner, TI: Interner> Fold<I, TI> for Ty<I> {
+ type Result = Ty<TI>;
+}
+
+fn fold<I: Interner, T>(interner: &I, t: T) -> T::Result
+where
+ T: Fold<I, I>,
+{
+ loop {}
+}
+
+fn foo<I: Interner>(interner: &I, t: Ty<I>) {
+ fold(interner, t);
+} //^^^^^^^^^^^^^^^^^ Ty<I>
+"#,
+ );
+}
+
+#[test]
+fn trait_impl_self_ty() {
+ check_types(
+ r#"
+trait Trait<T> {
+ fn foo(&self);
+}
+
+struct S;
+
+impl Trait<Self> for S {}
+
+fn test() {
+ S.foo();
+} //^^^^^^^ ()
+"#,
+ );
+}
+
+#[test]
+fn trait_impl_self_ty_cycle() {
+ check_types(
+ r#"
+trait Trait {
+ fn foo(&self);
+}
+
+struct S<T>;
+
+impl Trait for S<Self> {}
+
+fn test() {
+ S.foo();
+} //^^^^^^^ {unknown}
+"#,
+ );
+}
+
+#[test]
+fn unselected_projection_in_trait_env_cycle_1() {
+ // This is not a cycle, because the `T: Trait2<T::Item>` bound depends only on the `T: Trait`
+ // bound, not on itself (since only `Trait` can define `Item`).
+ check_types(
+ r#"
+trait Trait {
+ type Item;
+}
+
+trait Trait2<T> {}
+
+fn test<T: Trait>() where T: Trait2<T::Item> {
+ let x: T::Item = no_matter;
+} //^^^^^^^^^ Trait::Item<T>
+"#,
+ );
+}
+
+#[test]
+fn unselected_projection_in_trait_env_cycle_2() {
+ // this is a legitimate cycle
+ check_types(
+ r#"
+//- /main.rs
+trait Trait<T> {
+ type Item;
+}
+
+fn test<T, U>() where T: Trait<U::Item>, U: Trait<T::Item> {
+ let x: T::Item = no_matter;
+} //^^^^^^^^^ {unknown}
+"#,
+ );
+}
+
+#[test]
+fn unselected_projection_in_trait_env_cycle_3() {
+ // this is a cycle for rustc; we currently accept it
+ check_types(
+ r#"
+//- /main.rs
+trait Trait {
+ type Item;
+ type OtherItem;
+}
+
+fn test<T>() where T: Trait<OtherItem = T::Item> {
+ let x: T::Item = no_matter;
+} //^^^^^^^^^ Trait::Item<T>
+"#,
+ );
+}
+
+#[test]
+fn unselected_projection_in_trait_env_no_cycle() {
+ // this is not a cycle
+ check_types(
+ r#"
+//- /main.rs
+trait Index {
+ type Output;
+}
+
+type Key<S: UnificationStoreBase> = <S as UnificationStoreBase>::Key;
+
+pub trait UnificationStoreBase: Index<Output = Key<Self>> {
+ type Key;
+
+ fn len(&self) -> usize;
+}
+
+pub trait UnificationStoreMut: UnificationStoreBase {
+ fn push(&mut self, value: Self::Key);
+}
+
+fn test<T>(t: T) where T: UnificationStoreMut {
+ let x;
+ t.push(x);
+ let y: Key<T>;
+ (x, y);
+} //^^^^^^ (UnificationStoreBase::Key<T>, UnificationStoreBase::Key<T>)
+"#,
+ );
+}
+
+#[test]
+fn inline_assoc_type_bounds_1() {
+ check_types(
+ r#"
+trait Iterator {
+ type Item;
+}
+trait OtherTrait<T> {
+ fn foo(&self) -> T;
+}
+
+// workaround for Chalk assoc type normalization problems
+pub struct S<T>;
+impl<T: Iterator> Iterator for S<T> {
+ type Item = <T as Iterator>::Item;
+}
+
+fn test<I: Iterator<Item: OtherTrait<u32>>>() {
+ let x: <S<I> as Iterator>::Item;
+ x.foo();
+} //^^^^^^^ u32
+"#,
+ );
+}
+
+#[test]
+fn inline_assoc_type_bounds_2() {
+ check_types(
+ r#"
+trait Iterator {
+ type Item;
+}
+
+fn test<I: Iterator<Item: Iterator<Item = u32>>>() {
+ let x: <<I as Iterator>::Item as Iterator>::Item;
+ x;
+} //^ u32
+"#,
+ );
+}
+
+#[test]
+fn proc_macro_server_types() {
+ check_infer(
+ r#"
+macro_rules! with_api {
+ ($S:ident, $self:ident, $m:ident) => {
+ $m! {
+ TokenStream {
+ fn new() -> $S::TokenStream;
+ },
+ Group {
+ },
+ }
+ };
+}
+macro_rules! associated_item {
+ (type TokenStream) =>
+ (type TokenStream: 'static;);
+ (type Group) =>
+ (type Group: 'static;);
+ ($($item:tt)*) => ($($item)*;)
+}
+macro_rules! declare_server_traits {
+ ($($name:ident {
+ $(fn $method:ident($($arg:ident: $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)?;)*
+ }),* $(,)?) => {
+ pub trait Types {
+ $(associated_item!(type $name);)*
+ }
+
+ $(pub trait $name: Types {
+ $(associated_item!(fn $method($($arg: $arg_ty),*) $(-> $ret_ty)?);)*
+ })*
+
+ pub trait Server: Types $(+ $name)* {}
+ impl<S: Types $(+ $name)*> Server for S {}
+ }
+}
+
+with_api!(Self, self_, declare_server_traits);
+struct G {}
+struct T {}
+struct RustAnalyzer;
+impl Types for RustAnalyzer {
+ type TokenStream = T;
+ type Group = G;
+}
+
+fn make<T>() -> T { loop {} }
+impl TokenStream for RustAnalyzer {
+ fn new() -> Self::TokenStream {
+ let group: Self::Group = make();
+ make()
+ }
+}"#,
+ expect![[r#"
+ 1075..1086 '{ loop {} }': T
+ 1077..1084 'loop {}': !
+ 1082..1084 '{}': ()
+ 1157..1220 '{ ... }': T
+ 1171..1176 'group': G
+ 1192..1196 'make': fn make<G>() -> G
+ 1192..1198 'make()': G
+ 1208..1212 'make': fn make<T>() -> T
+ 1208..1214 'make()': T
+ "#]],
+ );
+}
+
+#[test]
+fn unify_impl_trait() {
+ check_infer_with_mismatches(
+ r#"
+//- minicore: sized
+trait Trait<T> {}
+
+fn foo(x: impl Trait<u32>) { loop {} }
+fn bar<T>(x: impl Trait<T>) -> T { loop {} }
+
+struct S<T>(T);
+impl<T> Trait<T> for S<T> {}
+
+fn default<T>() -> T { loop {} }
+
+fn test() -> impl Trait<i32> {
+ let s1 = S(default());
+ foo(s1);
+ let x: i32 = bar(S(default()));
+ S(default())
+}"#,
+ expect![[r#"
+ 26..27 'x': impl Trait<u32>
+ 46..57 '{ loop {} }': ()
+ 48..55 'loop {}': !
+ 53..55 '{}': ()
+ 68..69 'x': impl Trait<T>
+ 91..102 '{ loop {} }': T
+ 93..100 'loop {}': !
+ 98..100 '{}': ()
+ 171..182 '{ loop {} }': T
+ 173..180 'loop {}': !
+ 178..180 '{}': ()
+ 213..309 '{ ...t()) }': S<i32>
+ 223..225 's1': S<u32>
+ 228..229 'S': S<u32>(u32) -> S<u32>
+ 228..240 'S(default())': S<u32>
+ 230..237 'default': fn default<u32>() -> u32
+ 230..239 'default()': u32
+ 246..249 'foo': fn foo(S<u32>)
+ 246..253 'foo(s1)': ()
+ 250..252 's1': S<u32>
+ 263..264 'x': i32
+ 272..275 'bar': fn bar<i32>(S<i32>) -> i32
+ 272..289 'bar(S(...lt()))': i32
+ 276..277 'S': S<i32>(i32) -> S<i32>
+ 276..288 'S(default())': S<i32>
+ 278..285 'default': fn default<i32>() -> i32
+ 278..287 'default()': i32
+ 295..296 'S': S<i32>(i32) -> S<i32>
+ 295..307 'S(default())': S<i32>
+ 297..304 'default': fn default<i32>() -> i32
+ 297..306 'default()': i32
+ "#]],
+ );
+}
+
+#[test]
+fn assoc_types_from_bounds() {
+ check_infer(
+ r#"
+//- minicore: fn
+trait T {
+ type O;
+}
+
+impl T for () {
+ type O = ();
+}
+
+fn f<X, F>(_v: F)
+where
+ X: T,
+ F: FnOnce(&X::O),
+{ }
+
+fn main() {
+ f::<(), _>(|z| { z; });
+}"#,
+ expect![[r#"
+ 72..74 '_v': F
+ 117..120 '{ }': ()
+ 132..163 '{ ... }); }': ()
+ 138..148 'f::<(), _>': fn f<(), |&()| -> ()>(|&()| -> ())
+ 138..160 'f::<()... z; })': ()
+ 149..159 '|z| { z; }': |&()| -> ()
+ 150..151 'z': &()
+ 153..159 '{ z; }': ()
+ 155..156 'z': &()
+ "#]],
+ );
+}
+
+#[test]
+fn associated_type_bound() {
+ check_types(
+ r#"
+pub trait Trait {
+ type Item: OtherTrait<u32>;
+}
+pub trait OtherTrait<T> {
+ fn foo(&self) -> T;
+}
+
+// this is just a workaround for chalk#234
+pub struct S<T>;
+impl<T: Trait> Trait for S<T> {
+ type Item = <T as Trait>::Item;
+}
+
+fn test<T: Trait>() {
+ let y: <S<T> as Trait>::Item = no_matter;
+ y.foo();
+} //^^^^^^^ u32
+"#,
+ );
+}
+
+#[test]
+fn dyn_trait_through_chalk() {
+ check_types(
+ r#"
+//- minicore: deref
+struct Box<T: ?Sized> {}
+impl<T: ?Sized> core::ops::Deref for Box<T> {
+ type Target = T;
+}
+trait Trait {
+ fn foo(&self);
+}
+
+fn test(x: Box<dyn Trait>) {
+ x.foo();
+} //^^^^^^^ ()
+"#,
+ );
+}
+
+#[test]
+fn string_to_owned() {
+ check_types(
+ r#"
+struct String {}
+pub trait ToOwned {
+ type Owned;
+ fn to_owned(&self) -> Self::Owned;
+}
+impl ToOwned for str {
+ type Owned = String;
+}
+fn test() {
+ "foo".to_owned();
+} //^^^^^^^^^^^^^^^^ String
+"#,
+ );
+}
+
+#[test]
+fn iterator_chain() {
+ check_infer_with_mismatches(
+ r#"
+//- minicore: fn, option
+pub trait Iterator {
+ type Item;
+
+ fn filter_map<B, F>(self, f: F) -> FilterMap<Self, F>
+ where
+ F: FnMut(Self::Item) -> Option<B>,
+ { loop {} }
+
+ fn for_each<F>(self, f: F)
+ where
+ F: FnMut(Self::Item),
+ { loop {} }
+}
+
+pub trait IntoIterator {
+ type Item;
+ type IntoIter: Iterator<Item = Self::Item>;
+ fn into_iter(self) -> Self::IntoIter;
+}
+
+pub struct FilterMap<I, F> { }
+impl<B, I: Iterator, F> Iterator for FilterMap<I, F>
+where
+ F: FnMut(I::Item) -> Option<B>,
+{
+ type Item = B;
+}
+
+#[stable(feature = "rust1", since = "1.0.0")]
+impl<I: Iterator> IntoIterator for I {
+ type Item = I::Item;
+ type IntoIter = I;
+
+ fn into_iter(self) -> I {
+ self
+ }
+}
+
+struct Vec<T> {}
+impl<T> Vec<T> {
+ fn new() -> Self { loop {} }
+}
+
+impl<T> IntoIterator for Vec<T> {
+ type Item = T;
+ type IntoIter = IntoIter<T>;
+}
+
+pub struct IntoIter<T> { }
+impl<T> Iterator for IntoIter<T> {
+ type Item = T;
+}
+
+fn main() {
+ Vec::<i32>::new().into_iter()
+ .filter_map(|x| if x > 0 { Some(x as u32) } else { None })
+ .for_each(|y| { y; });
+}"#,
+ expect![[r#"
+ 61..65 'self': Self
+ 67..68 'f': F
+ 152..163 '{ loop {} }': FilterMap<Self, F>
+ 154..161 'loop {}': !
+ 159..161 '{}': ()
+ 184..188 'self': Self
+ 190..191 'f': F
+ 240..251 '{ loop {} }': ()
+ 242..249 'loop {}': !
+ 247..249 '{}': ()
+ 360..364 'self': Self
+ 689..693 'self': I
+ 700..720 '{ ... }': I
+ 710..714 'self': I
+ 779..790 '{ loop {} }': Vec<T>
+ 781..788 'loop {}': !
+ 786..788 '{}': ()
+ 977..1104 '{ ... }); }': ()
+ 983..998 'Vec::<i32>::new': fn new<i32>() -> Vec<i32>
+ 983..1000 'Vec::<...:new()': Vec<i32>
+ 983..1012 'Vec::<...iter()': IntoIter<i32>
+ 983..1075 'Vec::<...one })': FilterMap<IntoIter<i32>, |i32| -> Option<u32>>
+ 983..1101 'Vec::<... y; })': ()
+ 1029..1074 '|x| if...None }': |i32| -> Option<u32>
+ 1030..1031 'x': i32
+ 1033..1074 'if x >...None }': Option<u32>
+ 1036..1037 'x': i32
+ 1036..1041 'x > 0': bool
+ 1040..1041 '0': i32
+ 1042..1060 '{ Some...u32) }': Option<u32>
+ 1044..1048 'Some': Some<u32>(u32) -> Option<u32>
+ 1044..1058 'Some(x as u32)': Option<u32>
+ 1049..1050 'x': i32
+ 1049..1057 'x as u32': u32
+ 1066..1074 '{ None }': Option<u32>
+ 1068..1072 'None': Option<u32>
+ 1090..1100 '|y| { y; }': |u32| -> ()
+ 1091..1092 'y': u32
+ 1094..1100 '{ y; }': ()
+ 1096..1097 'y': u32
+ "#]],
+ );
+}
+
+#[test]
+fn nested_assoc() {
+ check_types(
+ r#"
+struct Bar;
+struct Foo;
+
+trait A {
+ type OutputA;
+}
+
+impl A for Bar {
+ type OutputA = Foo;
+}
+
+trait B {
+ type Output;
+ fn foo() -> Self::Output;
+}
+
+impl<T:A> B for T {
+ type Output = T::OutputA;
+ fn foo() -> Self::Output { loop {} }
+}
+
+fn main() {
+ Bar::foo();
+} //^^^^^^^^^^ Foo
+"#,
+ );
+}
+
+#[test]
+fn trait_object_no_coercion() {
+ check_infer_with_mismatches(
+ r#"
+trait Foo {}
+
+fn foo(x: &dyn Foo) {}
+
+fn test(x: &dyn Foo) {
+ foo(x);
+}"#,
+ expect![[r#"
+ 21..22 'x': &dyn Foo
+ 34..36 '{}': ()
+ 46..47 'x': &dyn Foo
+ 59..74 '{ foo(x); }': ()
+ 65..68 'foo': fn foo(&dyn Foo)
+ 65..71 'foo(x)': ()
+ 69..70 'x': &dyn Foo
+ "#]],
+ );
+}
+
+#[test]
+fn builtin_copy() {
+ check_infer_with_mismatches(
+ r#"
+//- minicore: copy
+struct IsCopy;
+impl Copy for IsCopy {}
+struct NotCopy;
+
+trait Test { fn test(&self) -> bool; }
+impl<T: Copy> Test for T {}
+
+fn test() {
+ IsCopy.test();
+ NotCopy.test();
+ (IsCopy, IsCopy).test();
+ (IsCopy, NotCopy).test();
+}"#,
+ expect![[r#"
+ 78..82 'self': &Self
+ 134..235 '{ ...t(); }': ()
+ 140..146 'IsCopy': IsCopy
+ 140..153 'IsCopy.test()': bool
+ 159..166 'NotCopy': NotCopy
+ 159..173 'NotCopy.test()': {unknown}
+ 179..195 '(IsCop...sCopy)': (IsCopy, IsCopy)
+ 179..202 '(IsCop...test()': bool
+ 180..186 'IsCopy': IsCopy
+ 188..194 'IsCopy': IsCopy
+ 208..225 '(IsCop...tCopy)': (IsCopy, NotCopy)
+ 208..232 '(IsCop...test()': {unknown}
+ 209..215 'IsCopy': IsCopy
+ 217..224 'NotCopy': NotCopy
+ "#]],
+ );
+}
+
+#[test]
+fn builtin_fn_def_copy() {
+ check_infer_with_mismatches(
+ r#"
+//- minicore: copy
+fn foo() {}
+fn bar<T: Copy>(T) -> T {}
+struct Struct(usize);
+enum Enum { Variant(usize) }
+
+trait Test { fn test(&self) -> bool; }
+impl<T: Copy> Test for T {}
+
+fn test() {
+ foo.test();
+ bar.test();
+ Struct.test();
+ Enum::Variant.test();
+}"#,
+ expect![[r#"
+ 9..11 '{}': ()
+ 28..29 'T': {unknown}
+ 36..38 '{}': T
+ 36..38: expected T, got ()
+ 113..117 'self': &Self
+ 169..249 '{ ...t(); }': ()
+ 175..178 'foo': fn foo()
+ 175..185 'foo.test()': bool
+ 191..194 'bar': fn bar<{unknown}>({unknown}) -> {unknown}
+ 191..201 'bar.test()': bool
+ 207..213 'Struct': Struct(usize) -> Struct
+ 207..220 'Struct.test()': bool
+ 226..239 'Enum::Variant': Variant(usize) -> Enum
+ 226..246 'Enum::...test()': bool
+ "#]],
+ );
+}
+
+#[test]
+fn builtin_fn_ptr_copy() {
+ check_infer_with_mismatches(
+ r#"
+//- minicore: copy
+trait Test { fn test(&self) -> bool; }
+impl<T: Copy> Test for T {}
+
+fn test(f1: fn(), f2: fn(usize) -> u8, f3: fn(u8, u8) -> &u8) {
+ f1.test();
+ f2.test();
+ f3.test();
+}"#,
+ expect![[r#"
+ 22..26 'self': &Self
+ 76..78 'f1': fn()
+ 86..88 'f2': fn(usize) -> u8
+ 107..109 'f3': fn(u8, u8) -> &u8
+ 130..178 '{ ...t(); }': ()
+ 136..138 'f1': fn()
+ 136..145 'f1.test()': bool
+ 151..153 'f2': fn(usize) -> u8
+ 151..160 'f2.test()': bool
+ 166..168 'f3': fn(u8, u8) -> &u8
+ 166..175 'f3.test()': bool
+ "#]],
+ );
+}
+
+#[test]
+fn builtin_sized() {
+ check_infer_with_mismatches(
+ r#"
+//- minicore: sized
+trait Test { fn test(&self) -> bool; }
+impl<T: Sized> Test for T {}
+
+fn test() {
+ 1u8.test();
+ (*"foo").test(); // not Sized
+ (1u8, 1u8).test();
+ (1u8, *"foo").test(); // not Sized
+}"#,
+ expect![[r#"
+ 22..26 'self': &Self
+ 79..194 '{ ...ized }': ()
+ 85..88 '1u8': u8
+ 85..95 '1u8.test()': bool
+ 101..116 '(*"foo").test()': {unknown}
+ 102..108 '*"foo"': str
+ 103..108 '"foo"': &str
+ 135..145 '(1u8, 1u8)': (u8, u8)
+ 135..152 '(1u8, ...test()': bool
+ 136..139 '1u8': u8
+ 141..144 '1u8': u8
+ 158..171 '(1u8, *"foo")': (u8, str)
+ 158..178 '(1u8, ...test()': {unknown}
+ 159..162 '1u8': u8
+ 164..170 '*"foo"': str
+ 165..170 '"foo"': &str
+ "#]],
+ );
+}
+
+#[test]
+fn integer_range_iterate() {
+ check_types(
+ r#"
+//- /main.rs crate:main deps:core
+fn test() {
+ for x in 0..100 { x; }
+} //^ i32
+
+//- /core.rs crate:core
+pub mod ops {
+ pub struct Range<Idx> {
+ pub start: Idx,
+ pub end: Idx,
+ }
+}
+
+pub mod iter {
+ pub trait Iterator {
+ type Item;
+ }
+
+ pub trait IntoIterator {
+ type Item;
+ type IntoIter: Iterator<Item = Self::Item>;
+ }
+
+ impl<T> IntoIterator for T where T: Iterator {
+ type Item = <T as Iterator>::Item;
+ type IntoIter = Self;
+ }
+}
+
+trait Step {}
+impl Step for i32 {}
+impl Step for i64 {}
+
+impl<A: Step> iter::Iterator for ops::Range<A> {
+ type Item = A;
+}
+"#,
+ );
+}
+
+#[test]
+fn infer_closure_arg() {
+ check_infer(
+ r#"
+//- /lib.rs
+
+enum Option<T> {
+ None,
+ Some(T)
+}
+
+fn foo() {
+ let s = Option::None;
+ let f = |x: Option<i32>| {};
+ (&f)(s)
+}"#,
+ expect![[r#"
+ 52..126 '{ ...)(s) }': ()
+ 62..63 's': Option<i32>
+ 66..78 'Option::None': Option<i32>
+ 88..89 'f': |Option<i32>| -> ()
+ 92..111 '|x: Op...2>| {}': |Option<i32>| -> ()
+ 93..94 'x': Option<i32>
+ 109..111 '{}': ()
+ 117..124 '(&f)(s)': ()
+ 118..120 '&f': &|Option<i32>| -> ()
+ 119..120 'f': |Option<i32>| -> ()
+ 122..123 's': Option<i32>
+ "#]],
+ );
+}
+
+#[test]
+fn dyn_fn_param_informs_call_site_closure_signature() {
+ cov_mark::check!(dyn_fn_param_informs_call_site_closure_signature);
+ check_types(
+ r#"
+//- minicore: fn, coerce_unsized
+struct S;
+impl S {
+ fn inherent(&self) -> u8 { 0 }
+}
+fn take_dyn_fn(f: &dyn Fn(S)) {}
+
+fn f() {
+ take_dyn_fn(&|x| { x.inherent(); });
+ //^^^^^^^^^^^^ u8
+}
+ "#,
+ );
+}
+
+#[test]
+fn infer_fn_trait_arg() {
+ check_infer_with_mismatches(
+ r#"
+//- minicore: fn, option
+fn foo<F, T>(f: F) -> T
+where
+ F: Fn(Option<i32>) -> T,
+{
+ let s = None;
+ f(s)
+}
+"#,
+ expect![[r#"
+ 13..14 'f': F
+ 59..89 '{ ...f(s) }': T
+ 69..70 's': Option<i32>
+ 73..77 'None': Option<i32>
+ 83..84 'f': F
+ 83..87 'f(s)': T
+ 85..86 's': Option<i32>
+ "#]],
+ );
+}
+
+#[test]
+fn infer_box_fn_arg() {
+ // The type mismatch is because we don't define Unsize and CoerceUnsized
+ check_infer_with_mismatches(
+ r#"
+//- minicore: fn, deref, option
+#[lang = "owned_box"]
+pub struct Box<T: ?Sized> {
+ inner: *mut T,
+}
+
+impl<T: ?Sized> core::ops::Deref for Box<T> {
+ type Target = T;
+
+ fn deref(&self) -> &T {
+ &self.inner
+ }
+}
+
+fn foo() {
+ let s = None;
+ let f: Box<dyn FnOnce(&Option<i32>)> = box (|ps| {});
+ f(&s);
+}"#,
+ expect![[r#"
+ 154..158 'self': &Box<T>
+ 166..193 '{ ... }': &T
+ 176..187 '&self.inner': &*mut T
+ 177..181 'self': &Box<T>
+ 177..187 'self.inner': *mut T
+ 206..296 '{ ...&s); }': ()
+ 216..217 's': Option<i32>
+ 220..224 'None': Option<i32>
+ 234..235 'f': Box<dyn FnOnce(&Option<i32>)>
+ 269..282 'box (|ps| {})': Box<|&Option<i32>| -> ()>
+ 274..281 '|ps| {}': |&Option<i32>| -> ()
+ 275..277 'ps': &Option<i32>
+ 279..281 '{}': ()
+ 288..289 'f': Box<dyn FnOnce(&Option<i32>)>
+ 288..293 'f(&s)': ()
+ 290..292 '&s': &Option<i32>
+ 291..292 's': Option<i32>
+ 269..282: expected Box<dyn FnOnce(&Option<i32>)>, got Box<|&Option<i32>| -> ()>
+ "#]],
+ );
+}
+
+#[test]
+fn infer_dyn_fn_output() {
+ check_types(
+ r#"
+//- minicore: fn
+fn foo() {
+ let f: &dyn Fn() -> i32;
+ f();
+ //^^^ i32
+}"#,
+ );
+}
+
+#[test]
+fn infer_dyn_fn_once_output() {
+ check_types(
+ r#"
+//- minicore: fn
+fn foo() {
+ let f: dyn FnOnce() -> i32;
+ f();
+ //^^^ i32
+}"#,
+ );
+}
+
+#[test]
+fn variable_kinds_1() {
+ check_types(
+ r#"
+trait Trait<T> { fn get(self, t: T) -> T; }
+struct S;
+impl Trait<u128> for S {}
+impl Trait<f32> for S {}
+fn test() {
+ S.get(1);
+ //^^^^^^^^ u128
+ S.get(1.);
+ //^^^^^^^^^ f32
+}
+ "#,
+ );
+}
+
+#[test]
+fn variable_kinds_2() {
+ check_types(
+ r#"
+trait Trait { fn get(self) -> Self; }
+impl Trait for u128 {}
+impl Trait for f32 {}
+fn test() {
+ 1.get();
+ //^^^^^^^ u128
+ (1.).get();
+ //^^^^^^^^^^ f32
+}
+ "#,
+ );
+}
+
+#[test]
+fn underscore_import() {
+ check_types(
+ r#"
+mod tr {
+ pub trait Tr {
+ fn method(&self) -> u8 { 0 }
+ }
+}
+
+struct Tr;
+impl crate::tr::Tr for Tr {}
+
+use crate::tr::Tr as _;
+fn test() {
+ Tr.method();
+ //^^^^^^^^^^^ u8
+}
+ "#,
+ );
+}
+
+#[test]
+fn inner_use() {
+ check_types(
+ r#"
+mod m {
+ pub trait Tr {
+ fn method(&self) -> u8 { 0 }
+ }
+
+ impl Tr for () {}
+}
+
+fn f() {
+ use m::Tr;
+
+ ().method();
+ //^^^^^^^^^^^ u8
+}
+ "#,
+ );
+}
+
+#[test]
+fn trait_in_scope_with_inner_item() {
+ check_infer(
+ r#"
+mod m {
+ pub trait Tr {
+ fn method(&self) -> u8 { 0 }
+ }
+
+ impl Tr for () {}
+}
+
+use m::Tr;
+
+fn f() {
+ fn inner() {
+ ().method();
+ //^^^^^^^^^^^ u8
+ }
+}"#,
+ expect![[r#"
+ 46..50 'self': &Self
+ 58..63 '{ 0 }': u8
+ 60..61 '0': u8
+ 115..185 '{ ... } }': ()
+ 132..183 '{ ... }': ()
+ 142..144 '()': ()
+ 142..153 '().method()': u8
+ "#]],
+ );
+}
+
+#[test]
+fn inner_use_in_block() {
+ check_types(
+ r#"
+mod m {
+ pub trait Tr {
+ fn method(&self) -> u8 { 0 }
+ }
+
+ impl Tr for () {}
+}
+
+fn f() {
+ {
+ use m::Tr;
+
+ ().method();
+ //^^^^^^^^^^^ u8
+ }
+
+ {
+ ().method();
+ //^^^^^^^^^^^ {unknown}
+ }
+
+ ().method();
+ //^^^^^^^^^^^ {unknown}
+}
+ "#,
+ );
+}
+
+#[test]
+fn nested_inner_function_calling_self() {
+ check_infer(
+ r#"
+struct S;
+fn f() {
+ fn inner() -> S {
+ let s = inner();
+ }
+}"#,
+ expect![[r#"
+ 17..73 '{ ... } }': ()
+ 39..71 '{ ... }': S
+ 53..54 's': S
+ 57..62 'inner': fn inner() -> S
+ 57..64 'inner()': S
+ "#]],
+ )
+}
+
+#[test]
+fn infer_default_trait_type_parameter() {
+ check_infer(
+ r#"
+struct A;
+
+trait Op<RHS=Self> {
+ type Output;
+
+ fn do_op(self, rhs: RHS) -> Self::Output;
+}
+
+impl Op for A {
+ type Output = bool;
+
+ fn do_op(self, rhs: Self) -> Self::Output {
+ true
+ }
+}
+
+fn test() {
+ let x = A;
+ let y = A;
+ let r = x.do_op(y);
+}"#,
+ expect![[r#"
+ 63..67 'self': Self
+ 69..72 'rhs': RHS
+ 153..157 'self': A
+ 159..162 'rhs': A
+ 186..206 '{ ... }': bool
+ 196..200 'true': bool
+ 220..277 '{ ...(y); }': ()
+ 230..231 'x': A
+ 234..235 'A': A
+ 245..246 'y': A
+ 249..250 'A': A
+ 260..261 'r': bool
+ 264..265 'x': A
+ 264..274 'x.do_op(y)': bool
+ 272..273 'y': A
+ "#]],
+ )
+}
+
+#[test]
+fn qualified_path_as_qualified_trait() {
+ check_infer(
+ r#"
+mod foo {
+
+ pub trait Foo {
+ type Target;
+ }
+ pub trait Bar {
+ type Output;
+ fn boo() -> Self::Output {
+ loop {}
+ }
+ }
+}
+
+struct F;
+impl foo::Foo for F {
+ type Target = ();
+}
+impl foo::Bar for F {
+ type Output = <F as foo::Foo>::Target;
+}
+
+fn foo() {
+ use foo::Bar;
+ let x = <F as Bar>::boo();
+}"#,
+ expect![[r#"
+ 132..163 '{ ... }': Bar::Output<Self>
+ 146..153 'loop {}': !
+ 151..153 '{}': ()
+ 306..358 '{ ...o(); }': ()
+ 334..335 'x': ()
+ 338..353 '<F as Bar>::boo': fn boo<F>() -> <F as Bar>::Output
+ 338..355 '<F as ...:boo()': ()
+ "#]],
+ );
+}
+
+#[test]
+fn renamed_extern_crate_in_block() {
+ check_types(
+ r#"
+//- /lib.rs crate:lib deps:serde
+use serde::Deserialize;
+
+struct Foo {}
+
+const _ : () = {
+ extern crate serde as _serde;
+ impl _serde::Deserialize for Foo {
+ fn deserialize() -> u8 { 0 }
+ }
+};
+
+fn foo() {
+ Foo::deserialize();
+ //^^^^^^^^^^^^^^^^^^ u8
+}
+
+//- /serde.rs crate:serde
+
+pub trait Deserialize {
+ fn deserialize() -> u8;
+}"#,
+ );
+}
+
+#[test]
+fn bin_op_with_rhs_is_self_for_assoc_bound() {
+ check_no_mismatches(
+ r#"//- minicore: eq
+ fn repro<T>(t: T) -> bool
+where
+ T: Request,
+ T::Output: Convertable,
+{
+ let a = execute(&t).convert();
+ let b = execute(&t).convert();
+ a.eq(&b);
+ let a = execute(&t).convert2();
+ let b = execute(&t).convert2();
+ a.eq(&b)
+}
+fn execute<T>(t: &T) -> T::Output
+where
+ T: Request,
+{
+ <T as Request>::output()
+}
+trait Convertable {
+ type TraitSelf: PartialEq<Self::TraitSelf>;
+ type AssocAsDefaultSelf: PartialEq;
+ fn convert(self) -> Self::AssocAsDefaultSelf;
+ fn convert2(self) -> Self::TraitSelf;
+}
+trait Request {
+ type Output;
+ fn output() -> Self::Output;
+}
+ "#,
+ );
+}
+
+#[test]
+fn bin_op_adt_with_rhs_primitive() {
+ check_infer_with_mismatches(
+ r#"
+#[lang = "add"]
+pub trait Add<Rhs = Self> {
+ type Output;
+ fn add(self, rhs: Rhs) -> Self::Output;
+}
+
+struct Wrapper(u32);
+impl Add<u32> for Wrapper {
+ type Output = Self;
+ fn add(self, rhs: u32) -> Wrapper {
+ Wrapper(rhs)
+ }
+}
+fn main(){
+ let wrapped = Wrapper(10);
+ let num: u32 = 2;
+ let res = wrapped + num;
+
+}"#,
+ expect![[r#"
+ 72..76 'self': Self
+ 78..81 'rhs': Rhs
+ 192..196 'self': Wrapper
+ 198..201 'rhs': u32
+ 219..247 '{ ... }': Wrapper
+ 229..236 'Wrapper': Wrapper(u32) -> Wrapper
+ 229..241 'Wrapper(rhs)': Wrapper
+ 237..240 'rhs': u32
+ 259..345 '{ ...um; }': ()
+ 269..276 'wrapped': Wrapper
+ 279..286 'Wrapper': Wrapper(u32) -> Wrapper
+ 279..290 'Wrapper(10)': Wrapper
+ 287..289 '10': u32
+ 300..303 'num': u32
+ 311..312 '2': u32
+ 322..325 'res': Wrapper
+ 328..335 'wrapped': Wrapper
+ 328..341 'wrapped + num': Wrapper
+ 338..341 'num': u32
+ "#]],
+ )
+}
+
+#[test]
+fn array_length() {
+ check_infer(
+ r#"
+trait T {
+ type Output;
+ fn do_thing(&self) -> Self::Output;
+}
+
+impl T for [u8; 4] {
+ type Output = usize;
+ fn do_thing(&self) -> Self::Output {
+ 2
+ }
+}
+
+impl T for [u8; 2] {
+ type Output = u8;
+ fn do_thing(&self) -> Self::Output {
+ 2
+ }
+}
+
+fn main() {
+ let v = [0u8; 2];
+ let v2 = v.do_thing();
+ let v3 = [0u8; 4];
+ let v4 = v3.do_thing();
+}
+"#,
+ expect![[r#"
+ 44..48 'self': &Self
+ 133..137 'self': &[u8; 4]
+ 155..172 '{ ... }': usize
+ 165..166 '2': usize
+ 236..240 'self': &[u8; 2]
+ 258..275 '{ ... }': u8
+ 268..269 '2': u8
+ 289..392 '{ ...g(); }': ()
+ 299..300 'v': [u8; 2]
+ 303..311 '[0u8; 2]': [u8; 2]
+ 304..307 '0u8': u8
+ 309..310 '2': usize
+ 321..323 'v2': u8
+ 326..327 'v': [u8; 2]
+ 326..338 'v.do_thing()': u8
+ 348..350 'v3': [u8; 4]
+ 353..361 '[0u8; 4]': [u8; 4]
+ 354..357 '0u8': u8
+ 359..360 '4': usize
+ 371..373 'v4': usize
+ 376..378 'v3': [u8; 4]
+ 376..389 'v3.do_thing()': usize
+ "#]],
+ )
+}
+
+#[test]
+fn const_generics() {
+ check_infer(
+ r#"
+trait T {
+ type Output;
+ fn do_thing(&self) -> Self::Output;
+}
+
+impl<const L: usize> T for [u8; L] {
+ type Output = [u8; L];
+ fn do_thing(&self) -> Self::Output {
+ *self
+ }
+}
+
+fn main() {
+ let v = [0u8; 2];
+ let v2 = v.do_thing();
+}
+"#,
+ expect![[r#"
+ 44..48 'self': &Self
+ 151..155 'self': &[u8; L]
+ 173..194 '{ ... }': [u8; L]
+ 183..188 '*self': [u8; L]
+ 184..188 'self': &[u8; L]
+ 208..260 '{ ...g(); }': ()
+ 218..219 'v': [u8; 2]
+ 222..230 '[0u8; 2]': [u8; 2]
+ 223..226 '0u8': u8
+ 228..229 '2': usize
+ 240..242 'v2': [u8; 2]
+ 245..246 'v': [u8; 2]
+ 245..257 'v.do_thing()': [u8; 2]
+ "#]],
+ )
+}
+
+#[test]
+fn fn_returning_unit() {
+ check_infer_with_mismatches(
+ r#"
+//- minicore: fn
+fn test<F: FnOnce()>(f: F) {
+ let _: () = f();
+}"#,
+ expect![[r#"
+ 21..22 'f': F
+ 27..51 '{ ...f(); }': ()
+ 37..38 '_': ()
+ 45..46 'f': F
+ 45..48 'f()': ()
+ "#]],
+ );
+}
+
+#[test]
+fn trait_in_scope_of_trait_impl() {
+ check_infer(
+ r#"
+mod foo {
+ pub trait Foo {
+ fn foo(self);
+ fn bar(self) -> usize { 0 }
+ }
+}
+impl foo::Foo for u32 {
+ fn foo(self) {
+ let _x = self.bar();
+ }
+}
+ "#,
+ expect![[r#"
+ 45..49 'self': Self
+ 67..71 'self': Self
+ 82..87 '{ 0 }': usize
+ 84..85 '0': usize
+ 131..135 'self': u32
+ 137..173 '{ ... }': ()
+ 151..153 '_x': usize
+ 156..160 'self': u32
+ 156..166 'self.bar()': usize
+ "#]],
+ );
+}
+
+#[test]
+fn infer_async_ret_type() {
+ check_types(
+ r#"
+//- minicore: future, result
+struct Fooey;
+
+impl Fooey {
+ fn collect<B: Convert>(self) -> B {
+ B::new()
+ }
+}
+
+trait Convert {
+ fn new() -> Self;
+}
+impl Convert for u32 {
+ fn new() -> Self { 0 }
+}
+
+async fn get_accounts() -> Result<u32, ()> {
+ let ret = Fooey.collect();
+ // ^^^^^^^^^^^^^^^ u32
+ Ok(ret)
+}
+"#,
+ );
+}
+
+#[test]
+fn local_impl_1() {
+ check!(block_local_impls);
+ check_types(
+ r#"
+trait Trait<T> {
+ fn foo(&self) -> T;
+}
+
+fn test() {
+ struct S;
+ impl Trait<u32> for S {
+ fn foo(&self) -> u32 { 0 }
+ }
+
+ S.foo();
+ // ^^^^^^^ u32
+}
+"#,
+ );
+}
+
+#[test]
+fn local_impl_2() {
+ check!(block_local_impls);
+ check_types(
+ r#"
+struct S;
+
+fn test() {
+ trait Trait<T> {
+ fn foo(&self) -> T;
+ }
+ impl Trait<u32> for S {
+ fn foo(&self) -> u32 { 0 }
+ }
+
+ S.foo();
+ // ^^^^^^^ u32
+}
+"#,
+ );
+}
+
+#[test]
+fn local_impl_3() {
+ check!(block_local_impls);
+ check_types(
+ r#"
+trait Trait<T> {
+ fn foo(&self) -> T;
+}
+
+fn test() {
+ struct S1;
+ {
+ struct S2;
+
+ impl Trait<S1> for S2 {
+ fn foo(&self) -> S1 { S1 }
+ }
+
+ S2.foo();
+ // ^^^^^^^^ S1
+ }
+}
+"#,
+ );
+}
+
+#[test]
+fn associated_type_sized_bounds() {
+ check_infer(
+ r#"
+//- minicore: sized
+struct Yes;
+trait IsSized { const IS_SIZED: Yes; }
+impl<T: Sized> IsSized for T { const IS_SIZED: Yes = Yes; }
+
+trait Foo {
+ type Explicit: Sized;
+ type Implicit;
+ type Relaxed: ?Sized;
+}
+fn f<F: Foo>() {
+ F::Explicit::IS_SIZED;
+ F::Implicit::IS_SIZED;
+ F::Relaxed::IS_SIZED;
+}
+"#,
+ expect![[r#"
+ 104..107 'Yes': Yes
+ 212..295 '{ ...ZED; }': ()
+ 218..239 'F::Exp..._SIZED': Yes
+ 245..266 'F::Imp..._SIZED': Yes
+ 272..292 'F::Rel..._SIZED': {unknown}
+ "#]],
+ );
+}
+
+#[test]
+fn dyn_map() {
+ check_types(
+ r#"
+pub struct Key<K, V, P = (K, V)> {}
+
+pub trait Policy {
+ type K;
+ type V;
+}
+
+impl<K, V> Policy for (K, V) {
+ type K = K;
+ type V = V;
+}
+
+pub struct KeyMap<KEY> {}
+
+impl<P: Policy> KeyMap<Key<P::K, P::V, P>> {
+ pub fn get(&self, key: &P::K) -> P::V {
+ loop {}
+ }
+}
+
+struct Fn {}
+struct FunctionId {}
+
+fn test() {
+ let key_map: &KeyMap<Key<Fn, FunctionId>> = loop {};
+ let key;
+ let result = key_map.get(key);
+ //^^^^^^ FunctionId
+}
+"#,
+ )
+}
+
+#[test]
+fn dyn_multiple_auto_traits_in_different_order() {
+ check_no_mismatches(
+ r#"
+auto trait Send {}
+auto trait Sync {}
+
+fn f(t: &(dyn Sync + Send)) {}
+fn g(t: &(dyn Send + Sync)) {
+ f(t);
+}
+ "#,
+ );
+
+ check_no_mismatches(
+ r#"
+auto trait Send {}
+auto trait Sync {}
+trait T {}
+
+fn f(t: &(dyn T + Send + Sync)) {}
+fn g(t: &(dyn Sync + T + Send)) {
+ f(t);
+}
+ "#,
+ );
+
+ check_infer_with_mismatches(
+ r#"
+auto trait Send {}
+auto trait Sync {}
+trait T1 {}
+trait T2 {}
+
+fn f(t: &(dyn T1 + T2 + Send + Sync)) {}
+fn g(t: &(dyn Sync + T2 + T1 + Send)) {
+ f(t);
+}
+ "#,
+ expect![[r#"
+ 68..69 't': &{unknown}
+ 101..103 '{}': ()
+ 109..110 't': &{unknown}
+ 142..155 '{ f(t); }': ()
+ 148..149 'f': fn f(&{unknown})
+ 148..152 'f(t)': ()
+ 150..151 't': &{unknown}
+ "#]],
+ );
+
+ check_no_mismatches(
+ r#"
+auto trait Send {}
+auto trait Sync {}
+trait T {
+ type Proj: Send + Sync;
+}
+
+fn f(t: &(dyn T<Proj = ()> + Send + Sync)) {}
+fn g(t: &(dyn Sync + T<Proj = ()> + Send)) {
+ f(t);
+}
+ "#,
+ );
+}
+
+#[test]
+fn dyn_multiple_projection_bounds() {
+ check_no_mismatches(
+ r#"
+trait Trait {
+ type T;
+ type U;
+}
+
+fn f(t: &dyn Trait<T = (), U = ()>) {}
+fn g(t: &dyn Trait<U = (), T = ()>) {
+ f(t);
+}
+ "#,
+ );
+
+ check_types(
+ r#"
+trait Trait {
+ type T;
+}
+
+fn f(t: &dyn Trait<T = (), T = ()>) {}
+ //^&{unknown}
+ "#,
+ );
+}
+
+#[test]
+fn dyn_duplicate_auto_trait() {
+ check_no_mismatches(
+ r#"
+auto trait Send {}
+
+fn f(t: &(dyn Send + Send)) {}
+fn g(t: &(dyn Send)) {
+ f(t);
+}
+ "#,
+ );
+
+ check_no_mismatches(
+ r#"
+auto trait Send {}
+trait T {}
+
+fn f(t: &(dyn T + Send + Send)) {}
+fn g(t: &(dyn T + Send)) {
+ f(t);
+}
+ "#,
+ );
+}
--- /dev/null
- use chalk_ir::{fold::Shift, BoundVar, DebruijnIndex};
+//! Helper functions for working with def, which don't need to be a separate
+//! query, but can't be computed directly from `*Data` (ie, which need a `db`).
+
+use std::iter;
+
+use base_db::CrateId;
- db::HirDatabase, ChalkTraitId, ConstData, ConstValue, GenericArgData, Interner, Substitution,
- TraitRef, TraitRefExt, TyKind, WhereClause,
++use chalk_ir::{cast::Cast, fold::Shift, BoundVar, DebruijnIndex};
+use hir_def::{
+ db::DefDatabase,
+ generics::{
+ GenericParams, TypeOrConstParamData, TypeParamProvenance, WherePredicate,
+ WherePredicateTypeTarget,
+ },
+ intern::Interned,
+ resolver::{HasResolver, TypeNs},
+ type_ref::{TraitBoundModifier, TypeRef},
+ ConstParamId, FunctionId, GenericDefId, ItemContainerId, Lookup, TraitId, TypeAliasId,
+ TypeOrConstParamId, TypeParamId,
+};
+use hir_expand::name::{known, Name};
+use itertools::Either;
+use rustc_hash::FxHashSet;
+use smallvec::{smallvec, SmallVec};
+use syntax::SmolStr;
+
+use crate::{
- if parent_generics.is_some() && matches!(def, GenericDefId::TypeAliasId(_)) {
- let params = db.generic_params(def);
- let parent_params = &parent_generics.as_ref().unwrap().params;
- let has_consts =
- params.iter().any(|(_, x)| matches!(x, TypeOrConstParamData::ConstParamData(_)));
- let parent_has_consts =
- parent_params.iter().any(|(_, x)| matches!(x, TypeOrConstParamData::ConstParamData(_)));
- return if has_consts || parent_has_consts {
- // XXX: treat const generic associated types as not existing to avoid crashes
- // (#11769)
- //
- // Note: Also crashes when the parent has const generics (also even if the GAT
- // doesn't use them), see `tests::regression::gat_crash_3` for an example.
- // Avoids that by disabling GATs when the parent (i.e. `impl` block) has
- // const generics (#12193).
- //
- // Chalk expects the inner associated type's parameters to come
- // *before*, not after the trait's generics as we've always done it.
- // Adapting to this requires a larger refactoring
- cov_mark::hit!(ignore_gats);
- Generics { def, params: Interned::new(Default::default()), parent_generics }
- } else {
- Generics { def, params, parent_generics }
- };
- }
++ db::HirDatabase, ChalkTraitId, Interner, Substitution, TraitRef, TraitRefExt, WhereClause,
+};
+
+pub(crate) fn fn_traits(db: &dyn DefDatabase, krate: CrateId) -> impl Iterator<Item = TraitId> {
+ [
+ db.lang_item(krate, SmolStr::new_inline("fn")),
+ db.lang_item(krate, SmolStr::new_inline("fn_mut")),
+ db.lang_item(krate, SmolStr::new_inline("fn_once")),
+ ]
+ .into_iter()
+ .flatten()
+ .flat_map(|it| it.as_trait())
+}
+
+fn direct_super_traits(db: &dyn DefDatabase, trait_: TraitId) -> SmallVec<[TraitId; 4]> {
+ let resolver = trait_.resolver(db);
+ // returning the iterator directly doesn't easily work because of
+ // lifetime problems, but since there usually shouldn't be more than a
+ // few direct traits this should be fine (we could even use some kind of
+ // SmallVec if performance is a concern)
+ let generic_params = db.generic_params(trait_.into());
+ let trait_self = generic_params.find_trait_self_param();
+ generic_params
+ .where_predicates
+ .iter()
+ .filter_map(|pred| match pred {
+ WherePredicate::ForLifetime { target, bound, .. }
+ | WherePredicate::TypeBound { target, bound } => {
+ let is_trait = match target {
+ WherePredicateTypeTarget::TypeRef(type_ref) => match &**type_ref {
+ TypeRef::Path(p) => p.is_self_type(),
+ _ => false,
+ },
+ WherePredicateTypeTarget::TypeOrConstParam(local_id) => {
+ Some(*local_id) == trait_self
+ }
+ };
+ match is_trait {
+ true => bound.as_path(),
+ false => None,
+ }
+ }
+ WherePredicate::Lifetime { .. } => None,
+ })
+ .filter(|(_, bound_modifier)| matches!(bound_modifier, TraitBoundModifier::None))
+ .filter_map(|(path, _)| match resolver.resolve_path_in_type_ns_fully(db, path.mod_path()) {
+ Some(TypeNs::TraitId(t)) => Some(t),
+ _ => None,
+ })
+ .collect()
+}
+
+fn direct_super_trait_refs(db: &dyn HirDatabase, trait_ref: &TraitRef) -> Vec<TraitRef> {
+ // returning the iterator directly doesn't easily work because of
+ // lifetime problems, but since there usually shouldn't be more than a
+ // few direct traits this should be fine (we could even use some kind of
+ // SmallVec if performance is a concern)
+ let generic_params = db.generic_params(trait_ref.hir_trait_id().into());
+ let trait_self = match generic_params.find_trait_self_param() {
+ Some(p) => TypeOrConstParamId { parent: trait_ref.hir_trait_id().into(), local_id: p },
+ None => return Vec::new(),
+ };
+ db.generic_predicates_for_param(trait_self.parent, trait_self, None)
+ .iter()
+ .filter_map(|pred| {
+ pred.as_ref().filter_map(|pred| match pred.skip_binders() {
+ // FIXME: how to correctly handle higher-ranked bounds here?
+ WhereClause::Implemented(tr) => Some(
+ tr.clone()
+ .shifted_out_to(Interner, DebruijnIndex::ONE)
+ .expect("FIXME unexpected higher-ranked trait bound"),
+ ),
+ _ => None,
+ })
+ })
+ .map(|pred| pred.substitute(Interner, &trait_ref.substitution))
+ .collect()
+}
+
+/// Returns an iterator over the whole super trait hierarchy (including the
+/// trait itself).
+pub fn all_super_traits(db: &dyn DefDatabase, trait_: TraitId) -> SmallVec<[TraitId; 4]> {
+ // we need to take care a bit here to avoid infinite loops in case of cycles
+ // (i.e. if we have `trait A: B; trait B: A;`)
+
+ let mut result = smallvec![trait_];
+ let mut i = 0;
+ while let Some(&t) = result.get(i) {
+ // yeah this is quadratic, but trait hierarchies should be flat
+ // enough that this doesn't matter
+ for tt in direct_super_traits(db, t) {
+ if !result.contains(&tt) {
+ result.push(tt);
+ }
+ }
+ i += 1;
+ }
+ result
+}
+
+/// Given a trait ref (`Self: Trait`), builds all the implied trait refs for
+/// super traits. The original trait ref will be included. So the difference to
+/// `all_super_traits` is that we keep track of type parameters; for example if
+/// we have `Self: Trait<u32, i32>` and `Trait<T, U>: OtherTrait<U>` we'll get
+/// `Self: OtherTrait<i32>`.
+pub(super) fn all_super_trait_refs(db: &dyn HirDatabase, trait_ref: TraitRef) -> SuperTraits<'_> {
+ SuperTraits { db, seen: iter::once(trait_ref.trait_id).collect(), stack: vec![trait_ref] }
+}
+
+pub(super) struct SuperTraits<'a> {
+ db: &'a dyn HirDatabase,
+ stack: Vec<TraitRef>,
+ seen: FxHashSet<ChalkTraitId>,
+}
+
+impl<'a> SuperTraits<'a> {
+ fn elaborate(&mut self, trait_ref: &TraitRef) {
+ let mut trait_refs = direct_super_trait_refs(self.db, trait_ref);
+ trait_refs.retain(|tr| !self.seen.contains(&tr.trait_id));
+ self.stack.extend(trait_refs);
+ }
+}
+
+impl<'a> Iterator for SuperTraits<'a> {
+ type Item = TraitRef;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if let Some(next) = self.stack.pop() {
+ self.elaborate(&next);
+ Some(next)
+ } else {
+ None
+ }
+ }
+}
+
+pub(super) fn associated_type_by_name_including_super_traits(
+ db: &dyn HirDatabase,
+ trait_ref: TraitRef,
+ name: &Name,
+) -> Option<(TraitRef, TypeAliasId)> {
+ all_super_trait_refs(db, trait_ref).find_map(|t| {
+ let assoc_type = db.trait_data(t.hir_trait_id()).associated_type_by_name(name)?;
+ Some((t, assoc_type))
+ })
+}
+
+pub(crate) fn generics(db: &dyn DefDatabase, def: GenericDefId) -> Generics {
+ let parent_generics = parent_generic_def(db, def).map(|def| Box::new(generics(db, def)));
- /// Iterator over types and const params of parent, then self.
+ Generics { def, params: db.generic_params(def), parent_generics }
+}
+
+#[derive(Debug)]
+pub(crate) struct Generics {
+ def: GenericDefId,
+ pub(crate) params: Interned<GenericParams>,
+ parent_generics: Option<Box<Generics>>,
+}
+
+impl Generics {
+ pub(crate) fn iter_id<'a>(
+ &'a self,
+ ) -> impl Iterator<Item = Either<TypeParamId, ConstParamId>> + 'a {
+ self.iter().map(|(id, data)| match data {
+ TypeOrConstParamData::TypeParamData(_) => Either::Left(TypeParamId::from_unchecked(id)),
+ TypeOrConstParamData::ConstParamData(_) => {
+ Either::Right(ConstParamId::from_unchecked(id))
+ }
+ })
+ }
+
- self.parent_generics()
- .into_iter()
- .flat_map(move |it| it.params.iter().map(to_toc_id(it)))
- .chain(self.params.iter().map(to_toc_id(self)))
++ /// Iterator over types and const params of self, then parent.
+ pub(crate) fn iter<'a>(
+ &'a self,
+ ) -> impl DoubleEndedIterator<Item = (TypeOrConstParamId, &'a TypeOrConstParamData)> + 'a {
+ let to_toc_id = |it: &'a Generics| {
+ move |(local_id, p)| (TypeOrConstParamId { parent: it.def, local_id }, p)
+ };
- ) -> impl Iterator<Item = (TypeOrConstParamId, &'a TypeOrConstParamData)> + 'a {
++ self.params.iter().map(to_toc_id(self)).chain(self.iter_parent())
++ }
++
++ /// Iterate over types and const params without parent params.
++ pub(crate) fn iter_self<'a>(
++ &'a self,
++ ) -> impl DoubleEndedIterator<Item = (TypeOrConstParamId, &'a TypeOrConstParamData)> + 'a {
++ let to_toc_id = |it: &'a Generics| {
++ move |(local_id, p)| (TypeOrConstParamId { parent: it.def, local_id }, p)
++ };
++ self.params.iter().map(to_toc_id(self))
+ }
+
+ /// Iterator over types and const params of parent.
+ pub(crate) fn iter_parent<'a>(
+ &'a self,
- let parent_len = self.parent_generics().map_or(0, Generics::len);
- Some((parent_len + idx, data))
++ ) -> impl DoubleEndedIterator<Item = (TypeOrConstParamId, &'a TypeOrConstParamData)> + 'a {
+ self.parent_generics().into_iter().flat_map(|it| {
+ let to_toc_id =
+ move |(local_id, p)| (TypeOrConstParamId { parent: it.def, local_id }, p);
+ it.params.iter().map(to_toc_id)
+ })
+ }
+
++ /// Returns total number of generic parameters in scope, including those from parent.
+ pub(crate) fn len(&self) -> usize {
+ let parent = self.parent_generics().map_or(0, Generics::len);
+ let child = self.params.type_or_consts.len();
+ parent + child
+ }
+
++ /// Returns numbers of generic parameters excluding those from parent.
++ pub(crate) fn len_self(&self) -> usize {
++ self.params.type_or_consts.len()
++ }
++
+ /// (parent total, self param, type param list, const param list, impl trait)
+ pub(crate) fn provenance_split(&self) -> (usize, usize, usize, usize, usize) {
+ let ty_iter = || self.params.iter().filter_map(|x| x.1.type_param());
+
+ let self_params =
+ ty_iter().filter(|p| p.provenance == TypeParamProvenance::TraitSelf).count();
+ let type_params =
+ ty_iter().filter(|p| p.provenance == TypeParamProvenance::TypeParamList).count();
+ let impl_trait_params =
+ ty_iter().filter(|p| p.provenance == TypeParamProvenance::ArgumentImplTrait).count();
+ let const_params = self.params.iter().filter_map(|x| x.1.const_param()).count();
+
+ let parent_len = self.parent_generics().map_or(0, Generics::len);
+ (parent_len, self_params, type_params, const_params, impl_trait_params)
+ }
+
+ pub(crate) fn param_idx(&self, param: TypeOrConstParamId) -> Option<usize> {
+ Some(self.find_param(param)?.0)
+ }
+
+ fn find_param(&self, param: TypeOrConstParamId) -> Option<(usize, &TypeOrConstParamData)> {
+ if param.parent == self.def {
+ let (idx, (_local_id, data)) =
+ self.params.iter().enumerate().find(|(_, (idx, _))| *idx == param.local_id)?;
- self.parent_generics().and_then(|g| g.find_param(param))
++ Some((idx, data))
+ } else {
- fn parent_generics(&self) -> Option<&Generics> {
- self.parent_generics.as_ref().map(|it| &**it)
++ self.parent_generics()
++ .and_then(|g| g.find_param(param))
++ // Remember that parent parameters come after parameters for self.
++ .map(|(idx, data)| (self.len_self() + idx, data))
+ }
+ }
+
- Either::Left(_) => GenericArgData::Ty(
- TyKind::BoundVar(BoundVar::new(debruijn, idx)).intern(Interner),
- )
- .intern(Interner),
- Either::Right(id) => GenericArgData::Const(
- ConstData {
- value: ConstValue::BoundVar(BoundVar::new(debruijn, idx)),
- ty: db.const_param_ty(id),
- }
- .intern(Interner),
- )
- .intern(Interner),
++ pub(crate) fn parent_generics(&self) -> Option<&Generics> {
++ self.parent_generics.as_deref()
+ }
+
+ /// Returns a Substitution that replaces each parameter by a bound variable.
+ pub(crate) fn bound_vars_subst(
+ &self,
+ db: &dyn HirDatabase,
+ debruijn: DebruijnIndex,
+ ) -> Substitution {
+ Substitution::from_iter(
+ Interner,
+ self.iter_id().enumerate().map(|(idx, id)| match id {
- Either::Left(id) => GenericArgData::Ty(
- TyKind::Placeholder(crate::to_placeholder_idx(db, id.into())).intern(Interner),
- )
- .intern(Interner),
- Either::Right(id) => GenericArgData::Const(
- ConstData {
- value: ConstValue::Placeholder(crate::to_placeholder_idx(db, id.into())),
- ty: db.const_param_ty(id),
- }
- .intern(Interner),
- )
- .intern(Interner),
++ Either::Left(_) => BoundVar::new(debruijn, idx).to_ty(Interner).cast(Interner),
++ Either::Right(id) => BoundVar::new(debruijn, idx)
++ .to_const(Interner, db.const_param_ty(id))
++ .cast(Interner),
+ }),
+ )
+ }
+
+ /// Returns a Substitution that replaces each parameter by itself (i.e. `Ty::Param`).
+ pub(crate) fn placeholder_subst(&self, db: &dyn HirDatabase) -> Substitution {
+ Substitution::from_iter(
+ Interner,
+ self.iter_id().map(|id| match id {
++ Either::Left(id) => {
++ crate::to_placeholder_idx(db, id.into()).to_ty(Interner).cast(Interner)
++ }
++ Either::Right(id) => crate::to_placeholder_idx(db, id.into())
++ .to_const(Interner, db.const_param_ty(id))
++ .cast(Interner),
+ }),
+ )
+ }
+}
+
+fn parent_generic_def(db: &dyn DefDatabase, def: GenericDefId) -> Option<GenericDefId> {
+ let container = match def {
+ GenericDefId::FunctionId(it) => it.lookup(db).container,
+ GenericDefId::TypeAliasId(it) => it.lookup(db).container,
+ GenericDefId::ConstId(it) => it.lookup(db).container,
+ GenericDefId::EnumVariantId(it) => return Some(it.parent.into()),
+ GenericDefId::AdtId(_) | GenericDefId::TraitId(_) | GenericDefId::ImplId(_) => return None,
+ };
+
+ match container {
+ ItemContainerId::ImplId(it) => Some(it.into()),
+ ItemContainerId::TraitId(it) => Some(it.into()),
+ ItemContainerId::ModuleId(_) | ItemContainerId::ExternBlockId(_) => None,
+ }
+}
+
+pub fn is_fn_unsafe_to_call(db: &dyn HirDatabase, func: FunctionId) -> bool {
+ let data = db.function_data(func);
+ if data.has_unsafe_kw() {
+ return true;
+ }
+
+ match func.lookup(db.upcast()).container {
+ hir_def::ItemContainerId::ExternBlockId(block) => {
+ // Function in an `extern` block are always unsafe to call, except when it has
+ // `"rust-intrinsic"` ABI there are a few exceptions.
+ let id = block.lookup(db.upcast()).id;
+ !matches!(
+ id.item_tree(db.upcast())[id.value].abi.as_deref(),
+ Some("rust-intrinsic") if !is_intrinsic_fn_unsafe(&data.name)
+ )
+ }
+ _ => false,
+ }
+}
+
+/// Returns `true` if the given intrinsic is unsafe to call, or false otherwise.
+fn is_intrinsic_fn_unsafe(name: &Name) -> bool {
+ // Should be kept in sync with https://github.com/rust-lang/rust/blob/532d2b14c05f9bc20b2d27cbb5f4550d28343a36/compiler/rustc_typeck/src/check/intrinsic.rs#L72-L106
+ ![
+ known::abort,
+ known::add_with_overflow,
+ known::bitreverse,
+ known::black_box,
+ known::bswap,
+ known::caller_location,
+ known::ctlz,
+ known::ctpop,
+ known::cttz,
+ known::discriminant_value,
+ known::forget,
+ known::likely,
+ known::maxnumf32,
+ known::maxnumf64,
+ known::min_align_of,
+ known::minnumf32,
+ known::minnumf64,
+ known::mul_with_overflow,
+ known::needs_drop,
+ known::ptr_guaranteed_eq,
+ known::ptr_guaranteed_ne,
+ known::rotate_left,
+ known::rotate_right,
+ known::rustc_peek,
+ known::saturating_add,
+ known::saturating_sub,
+ known::size_of,
+ known::sub_with_overflow,
+ known::type_id,
+ known::type_name,
+ known::unlikely,
+ known::variant_count,
+ known::wrapping_add,
+ known::wrapping_mul,
+ known::wrapping_sub,
+ ]
+ .contains(name)
+}
--- /dev/null
- //! This probably isn't the best way to do this -- ideally, diagnistics should
+//! Re-export diagnostics such that clients of `hir` don't have to depend on
+//! low-level crates.
+//!
++//! This probably isn't the best way to do this -- ideally, diagnostics should
+//! be expressed in terms of hir types themselves.
+use base_db::CrateId;
+use cfg::{CfgExpr, CfgOptions};
+use either::Either;
+use hir_def::path::ModPath;
+use hir_expand::{name::Name, HirFileId, InFile};
+use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange};
+
+use crate::{MacroKind, Type};
+
+macro_rules! diagnostics {
+ ($($diag:ident,)*) => {
+ #[derive(Debug)]
+ pub enum AnyDiagnostic {$(
+ $diag(Box<$diag>),
+ )*}
+
+ $(
+ impl From<$diag> for AnyDiagnostic {
+ fn from(d: $diag) -> AnyDiagnostic {
+ AnyDiagnostic::$diag(Box::new(d))
+ }
+ }
+ )*
+ };
+}
+
+diagnostics![
+ BreakOutsideOfLoop,
+ InactiveCode,
+ IncorrectCase,
+ InvalidDeriveTarget,
+ MacroError,
+ MalformedDerive,
+ MismatchedArgCount,
+ MissingFields,
+ MissingMatchArms,
+ MissingUnsafe,
+ NoSuchField,
+ ReplaceFilterMapNextWithFindMap,
+ TypeMismatch,
+ UnimplementedBuiltinMacro,
+ UnresolvedExternCrate,
+ UnresolvedImport,
+ UnresolvedMacroCall,
+ UnresolvedModule,
+ UnresolvedProcMacro,
+];
+
+#[derive(Debug)]
+pub struct UnresolvedModule {
+ pub decl: InFile<AstPtr<ast::Module>>,
+ pub candidates: Box<[String]>,
+}
+
+#[derive(Debug)]
+pub struct UnresolvedExternCrate {
+ pub decl: InFile<AstPtr<ast::ExternCrate>>,
+}
+
+#[derive(Debug)]
+pub struct UnresolvedImport {
+ pub decl: InFile<AstPtr<ast::UseTree>>,
+}
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct UnresolvedMacroCall {
+ pub macro_call: InFile<SyntaxNodePtr>,
+ pub precise_location: Option<TextRange>,
+ pub path: ModPath,
+ pub is_bang: bool,
+}
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct InactiveCode {
+ pub node: InFile<SyntaxNodePtr>,
+ pub cfg: CfgExpr,
+ pub opts: CfgOptions,
+}
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct UnresolvedProcMacro {
+ pub node: InFile<SyntaxNodePtr>,
+ /// If the diagnostic can be pinpointed more accurately than via `node`, this is the `TextRange`
+ /// to use instead.
+ pub precise_location: Option<TextRange>,
+ pub macro_name: Option<String>,
+ pub kind: MacroKind,
+ /// The crate id of the proc-macro this macro belongs to, or `None` if the proc-macro can't be found.
+ pub krate: CrateId,
+}
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct MacroError {
+ pub node: InFile<SyntaxNodePtr>,
+ pub precise_location: Option<TextRange>,
+ pub message: String,
+}
+
+#[derive(Debug)]
+pub struct UnimplementedBuiltinMacro {
+ pub node: InFile<SyntaxNodePtr>,
+}
+
+#[derive(Debug)]
+pub struct InvalidDeriveTarget {
+ pub node: InFile<SyntaxNodePtr>,
+}
+
+#[derive(Debug)]
+pub struct MalformedDerive {
+ pub node: InFile<SyntaxNodePtr>,
+}
+
+#[derive(Debug)]
+pub struct NoSuchField {
+ pub field: InFile<AstPtr<ast::RecordExprField>>,
+}
+
+#[derive(Debug)]
+pub struct BreakOutsideOfLoop {
+ pub expr: InFile<AstPtr<ast::Expr>>,
+ pub is_break: bool,
+}
+
+#[derive(Debug)]
+pub struct MissingUnsafe {
+ pub expr: InFile<AstPtr<ast::Expr>>,
+}
+
+#[derive(Debug)]
+pub struct MissingFields {
+ pub file: HirFileId,
+ pub field_list_parent: Either<AstPtr<ast::RecordExpr>, AstPtr<ast::RecordPat>>,
+ pub field_list_parent_path: Option<AstPtr<ast::Path>>,
+ pub missed_fields: Vec<Name>,
+}
+
+#[derive(Debug)]
+pub struct ReplaceFilterMapNextWithFindMap {
+ pub file: HirFileId,
+ /// This expression is the whole method chain up to and including `.filter_map(..).next()`.
+ pub next_expr: AstPtr<ast::Expr>,
+}
+
+#[derive(Debug)]
+pub struct MismatchedArgCount {
+ pub call_expr: InFile<AstPtr<ast::Expr>>,
+ pub expected: usize,
+ pub found: usize,
+}
+
+#[derive(Debug)]
+pub struct MissingMatchArms {
+ pub file: HirFileId,
+ pub match_expr: AstPtr<ast::Expr>,
+ pub uncovered_patterns: String,
+}
+
+#[derive(Debug)]
+pub struct TypeMismatch {
+ // FIXME: add mismatches in patterns as well
+ pub expr: InFile<AstPtr<ast::Expr>>,
+ pub expected: Type,
+ pub actual: Type,
+}
+
+pub use hir_ty::diagnostics::IncorrectCase;
--- /dev/null
+//! HirDisplay implementations for various hir types.
+use hir_def::{
+ adt::VariantData,
+ generics::{
+ TypeOrConstParamData, TypeParamProvenance, WherePredicate, WherePredicateTypeTarget,
+ },
+ type_ref::{TypeBound, TypeRef},
+ AdtId, GenericDefId,
+};
+use hir_ty::{
+ display::{
+ write_bounds_like_dyn_trait_with_prefix, write_visibility, HirDisplay, HirDisplayError,
+ HirFormatter, SizedByDefault,
+ },
+ Interner, TraitRefExt, WhereClause,
+};
+use syntax::SmolStr;
+
+use crate::{
+ Adt, Const, ConstParam, Enum, Field, Function, GenericParam, HasCrate, HasVisibility,
+ LifetimeParam, Macro, Module, Static, Struct, Trait, TyBuilder, Type, TypeAlias,
+ TypeOrConstParam, TypeParam, Union, Variant,
+};
+
+impl HirDisplay for Function {
+ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
+ let data = f.db.function_data(self.id);
+ write_visibility(self.module(f.db).id, self.visibility(f.db), f)?;
+ if data.has_default_kw() {
+ f.write_str("default ")?;
+ }
+ if data.has_const_kw() {
+ f.write_str("const ")?;
+ }
+ if data.has_async_kw() {
+ f.write_str("async ")?;
+ }
+ if self.is_unsafe_to_call(f.db) {
+ f.write_str("unsafe ")?;
+ }
+ if let Some(abi) = &data.abi {
+ // FIXME: String escape?
+ write!(f, "extern \"{}\" ", &**abi)?;
+ }
+ write!(f, "fn {}", data.name)?;
+
+ write_generic_params(GenericDefId::FunctionId(self.id), f)?;
+
+ f.write_char('(')?;
+
+ let write_self_param = |ty: &TypeRef, f: &mut HirFormatter<'_>| match ty {
+ TypeRef::Path(p) if p.is_self_type() => f.write_str("self"),
+ TypeRef::Reference(inner, lifetime, mut_) if matches!(&**inner,TypeRef::Path(p) if p.is_self_type()) =>
+ {
+ f.write_char('&')?;
+ if let Some(lifetime) = lifetime {
+ write!(f, "{} ", lifetime.name)?;
+ }
+ if let hir_def::type_ref::Mutability::Mut = mut_ {
+ f.write_str("mut ")?;
+ }
+ f.write_str("self")
+ }
+ _ => {
+ f.write_str("self: ")?;
+ ty.hir_fmt(f)
+ }
+ };
+
+ let mut first = true;
+ for (name, type_ref) in &data.params {
+ if !first {
+ f.write_str(", ")?;
+ } else {
+ first = false;
+ if data.has_self_param() {
+ write_self_param(type_ref, f)?;
+ continue;
+ }
+ }
+ match name {
+ Some(name) => write!(f, "{}: ", name)?,
+ None => f.write_str("_: ")?,
+ }
+ // FIXME: Use resolved `param.ty` or raw `type_ref`?
+ // The former will ignore lifetime arguments currently.
+ type_ref.hir_fmt(f)?;
+ }
+
+ if data.is_varargs() {
+ f.write_str(", ...")?;
+ }
+
+ f.write_char(')')?;
+
+ // `FunctionData::ret_type` will be `::core::future::Future<Output = ...>` for async fns.
+ // Use ugly pattern match to strip the Future trait.
+ // Better way?
+ let ret_type = if !data.has_async_kw() {
+ &data.ret_type
+ } else {
+ match &*data.ret_type {
+ TypeRef::ImplTrait(bounds) => match bounds[0].as_ref() {
+ TypeBound::Path(path, _) => {
+ path.segments().iter().last().unwrap().args_and_bindings.unwrap().bindings
+ [0]
+ .type_ref
+ .as_ref()
+ .unwrap()
+ }
+ _ => panic!("Async fn ret_type should be impl Future"),
+ },
+ _ => panic!("Async fn ret_type should be impl Future"),
+ }
+ };
+
+ match ret_type {
+ TypeRef::Tuple(tup) if tup.is_empty() => {}
+ ty => {
+ f.write_str(" -> ")?;
+ ty.hir_fmt(f)?;
+ }
+ }
+
+ write_where_clause(GenericDefId::FunctionId(self.id), f)?;
+
+ Ok(())
+ }
+}
+
+impl HirDisplay for Adt {
+ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
+ match self {
+ Adt::Struct(it) => it.hir_fmt(f),
+ Adt::Union(it) => it.hir_fmt(f),
+ Adt::Enum(it) => it.hir_fmt(f),
+ }
+ }
+}
+
+impl HirDisplay for Struct {
+ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
+ write_visibility(self.module(f.db).id, self.visibility(f.db), f)?;
+ f.write_str("struct ")?;
+ write!(f, "{}", self.name(f.db))?;
+ let def_id = GenericDefId::AdtId(AdtId::StructId(self.id));
+ write_generic_params(def_id, f)?;
+ write_where_clause(def_id, f)?;
+ Ok(())
+ }
+}
+
+impl HirDisplay for Enum {
+ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
+ write_visibility(self.module(f.db).id, self.visibility(f.db), f)?;
+ f.write_str("enum ")?;
+ write!(f, "{}", self.name(f.db))?;
+ let def_id = GenericDefId::AdtId(AdtId::EnumId(self.id));
+ write_generic_params(def_id, f)?;
+ write_where_clause(def_id, f)?;
+ Ok(())
+ }
+}
+
+impl HirDisplay for Union {
+ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
+ write_visibility(self.module(f.db).id, self.visibility(f.db), f)?;
+ f.write_str("union ")?;
+ write!(f, "{}", self.name(f.db))?;
+ let def_id = GenericDefId::AdtId(AdtId::UnionId(self.id));
+ write_generic_params(def_id, f)?;
+ write_where_clause(def_id, f)?;
+ Ok(())
+ }
+}
+
+impl HirDisplay for Field {
+ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
+ write_visibility(self.parent.module(f.db).id, self.visibility(f.db), f)?;
+ write!(f, "{}: ", self.name(f.db))?;
+ self.ty(f.db).hir_fmt(f)
+ }
+}
+
+impl HirDisplay for Variant {
+ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
+ write!(f, "{}", self.name(f.db))?;
+ let data = self.variant_data(f.db);
+ match &*data {
+ VariantData::Unit => {}
+ VariantData::Tuple(fields) => {
+ f.write_char('(')?;
+ let mut first = true;
+ for (_, field) in fields.iter() {
+ if first {
+ first = false;
+ } else {
+ f.write_str(", ")?;
+ }
+ // Enum variant fields must be pub.
+ field.type_ref.hir_fmt(f)?;
+ }
+ f.write_char(')')?;
+ }
+ VariantData::Record(fields) => {
+ f.write_str(" {")?;
+ let mut first = true;
+ for (_, field) in fields.iter() {
+ if first {
+ first = false;
+ f.write_char(' ')?;
+ } else {
+ f.write_str(", ")?;
+ }
+ // Enum variant fields must be pub.
+ write!(f, "{}: ", field.name)?;
+ field.type_ref.hir_fmt(f)?;
+ }
+ f.write_str(" }")?;
+ }
+ }
+ Ok(())
+ }
+}
+
+impl HirDisplay for Type {
+ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
+ self.ty.hir_fmt(f)
+ }
+}
+
+impl HirDisplay for GenericParam {
+ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
+ match self {
+ GenericParam::TypeParam(it) => it.hir_fmt(f),
+ GenericParam::ConstParam(it) => it.hir_fmt(f),
+ GenericParam::LifetimeParam(it) => it.hir_fmt(f),
+ }
+ }
+}
+
+impl HirDisplay for TypeOrConstParam {
+ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
+ match self.split(f.db) {
+ either::Either::Left(x) => x.hir_fmt(f),
+ either::Either::Right(x) => x.hir_fmt(f),
+ }
+ }
+}
+
+impl HirDisplay for TypeParam {
+ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
+ write!(f, "{}", self.name(f.db))?;
+ if f.omit_verbose_types() {
+ return Ok(());
+ }
+
+ let bounds = f.db.generic_predicates_for_param(self.id.parent(), self.id.into(), None);
+ let substs = TyBuilder::placeholder_subst(f.db, self.id.parent());
+ let predicates: Vec<_> =
+ bounds.iter().cloned().map(|b| b.substitute(Interner, &substs)).collect();
+ let krate = self.id.parent().krate(f.db).id;
+ let sized_trait =
+ f.db.lang_item(krate, SmolStr::new_inline("sized"))
+ .and_then(|lang_item| lang_item.as_trait());
+ let has_only_sized_bound = predicates.iter().all(move |pred| match pred.skip_binders() {
+ WhereClause::Implemented(it) => Some(it.hir_trait_id()) == sized_trait,
+ _ => false,
+ });
+ let has_only_not_sized_bound = predicates.is_empty();
+ if !has_only_sized_bound || has_only_not_sized_bound {
+ let default_sized = SizedByDefault::Sized { anchor: krate };
+ write_bounds_like_dyn_trait_with_prefix(":", &predicates, default_sized, f)?;
+ }
+ Ok(())
+ }
+}
+
+impl HirDisplay for LifetimeParam {
+ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
+ write!(f, "{}", self.name(f.db))
+ }
+}
+
+impl HirDisplay for ConstParam {
+ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
+ write!(f, "const {}: ", self.name(f.db))?;
+ self.ty(f.db).hir_fmt(f)
+ }
+}
+
+fn write_generic_params(
+ def: GenericDefId,
+ f: &mut HirFormatter<'_>,
+) -> Result<(), HirDisplayError> {
+ let params = f.db.generic_params(def);
+ if params.lifetimes.is_empty()
+ && params.type_or_consts.iter().all(|x| x.1.const_param().is_none())
+ && params
+ .type_or_consts
+ .iter()
+ .filter_map(|x| x.1.type_param())
+ .all(|param| !matches!(param.provenance, TypeParamProvenance::TypeParamList))
+ {
+ return Ok(());
+ }
+ f.write_char('<')?;
+
+ let mut first = true;
+ let mut delim = |f: &mut HirFormatter<'_>| {
+ if first {
+ first = false;
+ Ok(())
+ } else {
+ f.write_str(", ")
+ }
+ };
+ for (_, lifetime) in params.lifetimes.iter() {
+ delim(f)?;
+ write!(f, "{}", lifetime.name)?;
+ }
+ for (_, ty) in params.type_or_consts.iter() {
+ if let Some(name) = &ty.name() {
+ match ty {
+ TypeOrConstParamData::TypeParamData(ty) => {
+ if ty.provenance != TypeParamProvenance::TypeParamList {
+ continue;
+ }
+ delim(f)?;
+ write!(f, "{}", name)?;
+ if let Some(default) = &ty.default {
+ f.write_str(" = ")?;
+ default.hir_fmt(f)?;
+ }
+ }
+ TypeOrConstParamData::ConstParamData(c) => {
+ delim(f)?;
+ write!(f, "const {}: ", name)?;
+ c.ty.hir_fmt(f)?;
+ }
+ }
+ }
+ }
+
+ f.write_char('>')?;
+ Ok(())
+}
+
+fn write_where_clause(def: GenericDefId, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
+ let params = f.db.generic_params(def);
+
+ // unnamed type targets are displayed inline with the argument itself, e.g. `f: impl Y`.
+ let is_unnamed_type_target = |target: &WherePredicateTypeTarget| match target {
+ WherePredicateTypeTarget::TypeRef(_) => false,
+ WherePredicateTypeTarget::TypeOrConstParam(id) => {
+ params.type_or_consts[*id].name().is_none()
+ }
+ };
+
+ let has_displayable_predicate = params
+ .where_predicates
+ .iter()
+ .any(|pred| {
+ !matches!(pred, WherePredicate::TypeBound { target, .. } if is_unnamed_type_target(target))
+ });
+
+ if !has_displayable_predicate {
+ return Ok(());
+ }
+
+ let write_target = |target: &WherePredicateTypeTarget, f: &mut HirFormatter<'_>| match target {
+ WherePredicateTypeTarget::TypeRef(ty) => ty.hir_fmt(f),
+ WherePredicateTypeTarget::TypeOrConstParam(id) => {
+ match ¶ms.type_or_consts[*id].name() {
+ Some(name) => write!(f, "{}", name),
+ None => f.write_str("{unnamed}"),
+ }
+ }
+ };
+
+ f.write_str("\nwhere")?;
+
+ for (pred_idx, pred) in params.where_predicates.iter().enumerate() {
+ let prev_pred =
+ if pred_idx == 0 { None } else { Some(¶ms.where_predicates[pred_idx - 1]) };
+
+ let new_predicate = |f: &mut HirFormatter<'_>| {
+ f.write_str(if pred_idx == 0 { "\n " } else { ",\n " })
+ };
+
+ match pred {
+ WherePredicate::TypeBound { target, .. } if is_unnamed_type_target(target) => {}
+ WherePredicate::TypeBound { target, bound } => {
+ if matches!(prev_pred, Some(WherePredicate::TypeBound { target: target_, .. }) if target_ == target)
+ {
+ f.write_str(" + ")?;
+ } else {
+ new_predicate(f)?;
+ write_target(target, f)?;
+ f.write_str(": ")?;
+ }
+ bound.hir_fmt(f)?;
+ }
+ WherePredicate::Lifetime { target, bound } => {
+ if matches!(prev_pred, Some(WherePredicate::Lifetime { target: target_, .. }) if target_ == target)
+ {
+ write!(f, " + {}", bound.name)?;
+ } else {
+ new_predicate(f)?;
+ write!(f, "{}: {}", target.name, bound.name)?;
+ }
+ }
+ WherePredicate::ForLifetime { lifetimes, target, bound } => {
+ if matches!(
+ prev_pred,
+ Some(WherePredicate::ForLifetime { lifetimes: lifetimes_, target: target_, .. })
+ if lifetimes_ == lifetimes && target_ == target,
+ ) {
+ f.write_str(" + ")?;
+ } else {
+ new_predicate(f)?;
+ f.write_str("for<")?;
+ for (idx, lifetime) in lifetimes.iter().enumerate() {
+ if idx != 0 {
+ f.write_str(", ")?;
+ }
+ write!(f, "{}", lifetime)?;
+ }
+ f.write_str("> ")?;
+ write_target(target, f)?;
+ f.write_str(": ")?;
+ }
+ bound.hir_fmt(f)?;
+ }
+ }
+ }
+
+ // End of final predicate. There must be at least one predicate here.
+ f.write_char(',')?;
+
+ Ok(())
+}
+
+impl HirDisplay for Const {
+ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
+ write_visibility(self.module(f.db).id, self.visibility(f.db), f)?;
+ let data = f.db.const_data(self.id);
+ f.write_str("const ")?;
+ match &data.name {
+ Some(name) => write!(f, "{}: ", name)?,
+ None => f.write_str("_: ")?,
+ }
+ data.type_ref.hir_fmt(f)?;
+ Ok(())
+ }
+}
+
+impl HirDisplay for Static {
+ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
+ write_visibility(self.module(f.db).id, self.visibility(f.db), f)?;
+ let data = f.db.static_data(self.id);
+ f.write_str("static ")?;
+ if data.mutable {
+ f.write_str("mut ")?;
+ }
+ write!(f, "{}: ", &data.name)?;
+ data.type_ref.hir_fmt(f)?;
+ Ok(())
+ }
+}
+
+impl HirDisplay for Trait {
+ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
+ write_visibility(self.module(f.db).id, self.visibility(f.db), f)?;
+ let data = f.db.trait_data(self.id);
+ if data.is_unsafe {
+ f.write_str("unsafe ")?;
+ }
+ if data.is_auto {
+ f.write_str("auto ")?;
+ }
+ write!(f, "trait {}", data.name)?;
+ let def_id = GenericDefId::TraitId(self.id);
+ write_generic_params(def_id, f)?;
+ write_where_clause(def_id, f)?;
+ Ok(())
+ }
+}
+
+impl HirDisplay for TypeAlias {
+ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
+ write_visibility(self.module(f.db).id, self.visibility(f.db), f)?;
+ let data = f.db.type_alias_data(self.id);
+ write!(f, "type {}", data.name)?;
++ let def_id = GenericDefId::TypeAliasId(self.id);
++ write_generic_params(def_id, f)?;
++ write_where_clause(def_id, f)?;
+ if !data.bounds.is_empty() {
+ f.write_str(": ")?;
+ f.write_joined(&data.bounds, " + ")?;
+ }
+ if let Some(ty) = &data.type_ref {
+ f.write_str(" = ")?;
+ ty.hir_fmt(f)?;
+ }
+ Ok(())
+ }
+}
+
+impl HirDisplay for Module {
+ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
+ // FIXME: Module doesn't have visibility saved in data.
+ match self.name(f.db) {
+ Some(name) => write!(f, "mod {}", name),
+ None if self.is_crate_root(f.db) => match self.krate(f.db).display_name(f.db) {
+ Some(name) => write!(f, "extern crate {}", name),
+ None => f.write_str("extern crate {unknown}"),
+ },
+ None => f.write_str("mod {unnamed}"),
+ }
+ }
+}
+
+impl HirDisplay for Macro {
+ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
+ match self.id {
+ hir_def::MacroId::Macro2Id(_) => f.write_str("macro"),
+ hir_def::MacroId::MacroRulesId(_) => f.write_str("macro_rules!"),
+ hir_def::MacroId::ProcMacroId(_) => f.write_str("proc_macro"),
+ }?;
+ write!(f, " {}", self.name(f.db))
+ }
+}
--- /dev/null
- GenericDef::Variant(it) => {
- GenericDefId::EnumVariantId(EnumVariantId { parent: it.parent.id, local_id: it.id })
- }
+//! Utility module for converting between hir_def ids and code_model wrappers.
+//!
+//! It's unclear if we need this long-term, but it's definitely useful while we
+//! are splitting the hir.
+
+use hir_def::{
+ expr::{LabelId, PatId},
+ AdtId, AssocItemId, DefWithBodyId, EnumVariantId, FieldId, GenericDefId, GenericParamId,
+ ModuleDefId, VariantId,
+};
+
+use crate::{
+ Adt, AssocItem, BuiltinType, DefWithBody, Field, GenericDef, GenericParam, ItemInNs, Label,
+ Local, ModuleDef, Variant, VariantDef,
+};
+
+macro_rules! from_id {
+ ($(($id:path, $ty:path)),*) => {$(
+ impl From<$id> for $ty {
+ fn from(id: $id) -> $ty {
+ $ty { id }
+ }
+ }
+ impl From<$ty> for $id {
+ fn from(ty: $ty) -> $id {
+ ty.id
+ }
+ }
+ )*}
+}
+
+from_id![
+ (base_db::CrateId, crate::Crate),
+ (hir_def::ModuleId, crate::Module),
+ (hir_def::StructId, crate::Struct),
+ (hir_def::UnionId, crate::Union),
+ (hir_def::EnumId, crate::Enum),
+ (hir_def::TypeAliasId, crate::TypeAlias),
+ (hir_def::TraitId, crate::Trait),
+ (hir_def::StaticId, crate::Static),
+ (hir_def::ConstId, crate::Const),
+ (hir_def::FunctionId, crate::Function),
+ (hir_def::ImplId, crate::Impl),
+ (hir_def::TypeOrConstParamId, crate::TypeOrConstParam),
+ (hir_def::TypeParamId, crate::TypeParam),
+ (hir_def::ConstParamId, crate::ConstParam),
+ (hir_def::LifetimeParamId, crate::LifetimeParam),
+ (hir_def::MacroId, crate::Macro)
+];
+
+impl From<AdtId> for Adt {
+ fn from(id: AdtId) -> Self {
+ match id {
+ AdtId::StructId(it) => Adt::Struct(it.into()),
+ AdtId::UnionId(it) => Adt::Union(it.into()),
+ AdtId::EnumId(it) => Adt::Enum(it.into()),
+ }
+ }
+}
+
+impl From<Adt> for AdtId {
+ fn from(id: Adt) -> Self {
+ match id {
+ Adt::Struct(it) => AdtId::StructId(it.id),
+ Adt::Union(it) => AdtId::UnionId(it.id),
+ Adt::Enum(it) => AdtId::EnumId(it.id),
+ }
+ }
+}
+
+impl From<GenericParamId> for GenericParam {
+ fn from(id: GenericParamId) -> Self {
+ match id {
+ GenericParamId::TypeParamId(it) => GenericParam::TypeParam(it.into()),
+ GenericParamId::ConstParamId(it) => GenericParam::ConstParam(it.into()),
+ GenericParamId::LifetimeParamId(it) => GenericParam::LifetimeParam(it.into()),
+ }
+ }
+}
+
+impl From<GenericParam> for GenericParamId {
+ fn from(id: GenericParam) -> Self {
+ match id {
+ GenericParam::LifetimeParam(it) => GenericParamId::LifetimeParamId(it.id),
+ GenericParam::ConstParam(it) => GenericParamId::ConstParamId(it.id),
+ GenericParam::TypeParam(it) => GenericParamId::TypeParamId(it.id),
+ }
+ }
+}
+
+impl From<EnumVariantId> for Variant {
+ fn from(id: EnumVariantId) -> Self {
+ Variant { parent: id.parent.into(), id: id.local_id }
+ }
+}
+
+impl From<Variant> for EnumVariantId {
+ fn from(def: Variant) -> Self {
+ EnumVariantId { parent: def.parent.id, local_id: def.id }
+ }
+}
+
+impl From<ModuleDefId> for ModuleDef {
+ fn from(id: ModuleDefId) -> Self {
+ match id {
+ ModuleDefId::ModuleId(it) => ModuleDef::Module(it.into()),
+ ModuleDefId::FunctionId(it) => ModuleDef::Function(it.into()),
+ ModuleDefId::AdtId(it) => ModuleDef::Adt(it.into()),
+ ModuleDefId::EnumVariantId(it) => ModuleDef::Variant(it.into()),
+ ModuleDefId::ConstId(it) => ModuleDef::Const(it.into()),
+ ModuleDefId::StaticId(it) => ModuleDef::Static(it.into()),
+ ModuleDefId::TraitId(it) => ModuleDef::Trait(it.into()),
+ ModuleDefId::TypeAliasId(it) => ModuleDef::TypeAlias(it.into()),
+ ModuleDefId::BuiltinType(it) => ModuleDef::BuiltinType(it.into()),
+ ModuleDefId::MacroId(it) => ModuleDef::Macro(it.into()),
+ }
+ }
+}
+
+impl From<ModuleDef> for ModuleDefId {
+ fn from(id: ModuleDef) -> Self {
+ match id {
+ ModuleDef::Module(it) => ModuleDefId::ModuleId(it.into()),
+ ModuleDef::Function(it) => ModuleDefId::FunctionId(it.into()),
+ ModuleDef::Adt(it) => ModuleDefId::AdtId(it.into()),
+ ModuleDef::Variant(it) => ModuleDefId::EnumVariantId(it.into()),
+ ModuleDef::Const(it) => ModuleDefId::ConstId(it.into()),
+ ModuleDef::Static(it) => ModuleDefId::StaticId(it.into()),
+ ModuleDef::Trait(it) => ModuleDefId::TraitId(it.into()),
+ ModuleDef::TypeAlias(it) => ModuleDefId::TypeAliasId(it.into()),
+ ModuleDef::BuiltinType(it) => ModuleDefId::BuiltinType(it.into()),
+ ModuleDef::Macro(it) => ModuleDefId::MacroId(it.into()),
+ }
+ }
+}
+
+impl From<DefWithBody> for DefWithBodyId {
+ fn from(def: DefWithBody) -> Self {
+ match def {
+ DefWithBody::Function(it) => DefWithBodyId::FunctionId(it.id),
+ DefWithBody::Static(it) => DefWithBodyId::StaticId(it.id),
+ DefWithBody::Const(it) => DefWithBodyId::ConstId(it.id),
++ DefWithBody::Variant(it) => DefWithBodyId::VariantId(it.into()),
+ }
+ }
+}
+
+impl From<DefWithBodyId> for DefWithBody {
+ fn from(def: DefWithBodyId) -> Self {
+ match def {
+ DefWithBodyId::FunctionId(it) => DefWithBody::Function(it.into()),
+ DefWithBodyId::StaticId(it) => DefWithBody::Static(it.into()),
+ DefWithBodyId::ConstId(it) => DefWithBody::Const(it.into()),
++ DefWithBodyId::VariantId(it) => DefWithBody::Variant(it.into()),
+ }
+ }
+}
+
+impl From<AssocItemId> for AssocItem {
+ fn from(def: AssocItemId) -> Self {
+ match def {
+ AssocItemId::FunctionId(it) => AssocItem::Function(it.into()),
+ AssocItemId::TypeAliasId(it) => AssocItem::TypeAlias(it.into()),
+ AssocItemId::ConstId(it) => AssocItem::Const(it.into()),
+ }
+ }
+}
+
+impl From<GenericDef> for GenericDefId {
+ fn from(def: GenericDef) -> Self {
+ match def {
+ GenericDef::Function(it) => GenericDefId::FunctionId(it.id),
+ GenericDef::Adt(it) => GenericDefId::AdtId(it.into()),
+ GenericDef::Trait(it) => GenericDefId::TraitId(it.id),
+ GenericDef::TypeAlias(it) => GenericDefId::TypeAliasId(it.id),
+ GenericDef::Impl(it) => GenericDefId::ImplId(it.id),
- GenericDefId::EnumVariantId(it) => {
- GenericDef::Variant(Variant { parent: it.parent.into(), id: it.local_id })
- }
++ GenericDef::Variant(it) => GenericDefId::EnumVariantId(it.into()),
+ GenericDef::Const(it) => GenericDefId::ConstId(it.id),
+ }
+ }
+}
+
+impl From<GenericDefId> for GenericDef {
+ fn from(def: GenericDefId) -> Self {
+ match def {
+ GenericDefId::FunctionId(it) => GenericDef::Function(it.into()),
+ GenericDefId::AdtId(it) => GenericDef::Adt(it.into()),
+ GenericDefId::TraitId(it) => GenericDef::Trait(it.into()),
+ GenericDefId::TypeAliasId(it) => GenericDef::TypeAlias(it.into()),
+ GenericDefId::ImplId(it) => GenericDef::Impl(it.into()),
++ GenericDefId::EnumVariantId(it) => GenericDef::Variant(it.into()),
+ GenericDefId::ConstId(it) => GenericDef::Const(it.into()),
+ }
+ }
+}
+
+impl From<Adt> for GenericDefId {
+ fn from(id: Adt) -> Self {
+ match id {
+ Adt::Struct(it) => it.id.into(),
+ Adt::Union(it) => it.id.into(),
+ Adt::Enum(it) => it.id.into(),
+ }
+ }
+}
+
+impl From<VariantId> for VariantDef {
+ fn from(def: VariantId) -> Self {
+ match def {
+ VariantId::StructId(it) => VariantDef::Struct(it.into()),
+ VariantId::EnumVariantId(it) => VariantDef::Variant(it.into()),
+ VariantId::UnionId(it) => VariantDef::Union(it.into()),
+ }
+ }
+}
+
+impl From<VariantDef> for VariantId {
+ fn from(def: VariantDef) -> Self {
+ match def {
+ VariantDef::Struct(it) => VariantId::StructId(it.id),
+ VariantDef::Variant(it) => VariantId::EnumVariantId(it.into()),
+ VariantDef::Union(it) => VariantId::UnionId(it.id),
+ }
+ }
+}
+
+impl From<Field> for FieldId {
+ fn from(def: Field) -> Self {
+ FieldId { parent: def.parent.into(), local_id: def.id }
+ }
+}
+
+impl From<FieldId> for Field {
+ fn from(def: FieldId) -> Self {
+ Field { parent: def.parent.into(), id: def.local_id }
+ }
+}
+
+impl From<AssocItem> for GenericDefId {
+ fn from(item: AssocItem) -> Self {
+ match item {
+ AssocItem::Function(f) => f.id.into(),
+ AssocItem::Const(c) => c.id.into(),
+ AssocItem::TypeAlias(t) => t.id.into(),
+ }
+ }
+}
+
+impl From<(DefWithBodyId, PatId)> for Local {
+ fn from((parent, pat_id): (DefWithBodyId, PatId)) -> Self {
+ Local { parent, pat_id }
+ }
+}
+
+impl From<(DefWithBodyId, LabelId)> for Label {
+ fn from((parent, label_id): (DefWithBodyId, LabelId)) -> Self {
+ Label { parent, label_id }
+ }
+}
+
+impl From<hir_def::item_scope::ItemInNs> for ItemInNs {
+ fn from(it: hir_def::item_scope::ItemInNs) -> Self {
+ match it {
+ hir_def::item_scope::ItemInNs::Types(it) => ItemInNs::Types(it.into()),
+ hir_def::item_scope::ItemInNs::Values(it) => ItemInNs::Values(it.into()),
+ hir_def::item_scope::ItemInNs::Macros(it) => ItemInNs::Macros(it.into()),
+ }
+ }
+}
+
+impl From<ItemInNs> for hir_def::item_scope::ItemInNs {
+ fn from(it: ItemInNs) -> Self {
+ match it {
+ ItemInNs::Types(it) => Self::Types(it.into()),
+ ItemInNs::Values(it) => Self::Values(it.into()),
+ ItemInNs::Macros(it) => Self::Macros(it.into()),
+ }
+ }
+}
+
+impl From<hir_def::builtin_type::BuiltinType> for BuiltinType {
+ fn from(inner: hir_def::builtin_type::BuiltinType) -> Self {
+ Self { inner }
+ }
+}
+
+impl From<BuiltinType> for hir_def::builtin_type::BuiltinType {
+ fn from(it: BuiltinType) -> Self {
+ it.inner
+ }
+}
--- /dev/null
- adt::{ReprKind, VariantData},
+//! HIR (previously known as descriptors) provides a high-level object oriented
+//! access to Rust code.
+//!
+//! The principal difference between HIR and syntax trees is that HIR is bound
+//! to a particular crate instance. That is, it has cfg flags and features
+//! applied. So, the relation between syntax and HIR is many-to-one.
+//!
+//! HIR is the public API of the all of the compiler logic above syntax trees.
+//! It is written in "OO" style. Each type is self contained (as in, it knows it's
+//! parents and full context). It should be "clean code".
+//!
+//! `hir_*` crates are the implementation of the compiler logic.
+//! They are written in "ECS" style, with relatively little abstractions.
+//! Many types are not self-contained, and explicitly use local indexes, arenas, etc.
+//!
+//! `hir` is what insulates the "we don't know how to actually write an incremental compiler"
+//! from the ide with completions, hovers, etc. It is a (soft, internal) boundary:
+//! <https://www.tedinski.com/2018/02/06/system-boundaries.html>.
+
+#![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)]
+#![recursion_limit = "512"]
+
+mod semantics;
+mod source_analyzer;
+
+mod from_id;
+mod attrs;
+mod has_source;
+
+pub mod diagnostics;
+pub mod db;
+pub mod symbols;
+
+mod display;
+
+use std::{iter, ops::ControlFlow, sync::Arc};
+
+use arrayvec::ArrayVec;
+use base_db::{CrateDisplayName, CrateId, CrateOrigin, Edition, FileId, ProcMacroKind};
+use either::Either;
+use hir_def::{
- FunctionId, GenericDefId, HasModule, ImplId, ItemContainerId, LifetimeParamId,
++ adt::{ReprData, VariantData},
+ body::{BodyDiagnostic, SyntheticSyntax},
+ expr::{BindingAnnotation, LabelId, Pat, PatId},
+ generics::{TypeOrConstParamData, TypeParamProvenance},
+ item_tree::ItemTreeNode,
+ lang_item::LangItemTarget,
+ nameres::{self, diagnostics::DefDiagnostic},
+ per_ns::PerNs,
+ resolver::{HasResolver, Resolver},
+ src::HasSource as _,
+ AdtId, AssocItemId, AssocItemLoc, AttrDefId, ConstId, ConstParamId, DefWithBodyId, EnumId,
- subst_prefix,
++ EnumVariantId, FunctionId, GenericDefId, HasModule, ImplId, ItemContainerId, LifetimeParamId,
+ LocalEnumVariantId, LocalFieldId, Lookup, MacroExpander, MacroId, ModuleId, StaticId, StructId,
+ TraitId, TypeAliasId, TypeOrConstParamId, TypeParamId, UnionId,
+};
+use hir_expand::{name::name, MacroCallKind};
+use hir_ty::{
+ all_super_traits, autoderef,
+ consteval::{unknown_const_as_generic, ComputedExpr, ConstEvalError, ConstExt},
+ diagnostics::BodyValidationDiagnostic,
+ method_resolution::{self, TyFingerprint},
+ primitive::UintTy,
- ast::{self, HasAttrs as _, HasDocComments, HasName},
+ traits::FnTrait,
+ AliasTy, CallableDefId, CallableSig, Canonical, CanonicalVarKinds, Cast, ClosureId,
+ GenericArgData, Interner, ParamKind, QuantifiedWhereClause, Scalar, Substitution,
+ TraitEnvironment, TraitRefExt, Ty, TyBuilder, TyDefId, TyExt, TyKind, WhereClause,
+};
+use itertools::Itertools;
+use nameres::diagnostics::DefDiagnosticKind;
+use once_cell::unsync::Lazy;
+use rustc_hash::FxHashSet;
+use stdx::{impl_from, never};
+use syntax::{
- _ => return Vec::new(),
++ ast::{self, Expr, HasAttrs as _, HasDocComments, HasName},
+ AstNode, AstPtr, SmolStr, SyntaxNodePtr, TextRange, T,
+};
+
+use crate::db::{DefDatabase, HirDatabase};
+
+pub use crate::{
+ attrs::{HasAttrs, Namespace},
+ diagnostics::{
+ AnyDiagnostic, BreakOutsideOfLoop, InactiveCode, IncorrectCase, InvalidDeriveTarget,
+ MacroError, MalformedDerive, MismatchedArgCount, MissingFields, MissingMatchArms,
+ MissingUnsafe, NoSuchField, ReplaceFilterMapNextWithFindMap, TypeMismatch,
+ UnimplementedBuiltinMacro, UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall,
+ UnresolvedModule, UnresolvedProcMacro,
+ },
+ has_source::HasSource,
+ semantics::{PathResolution, Semantics, SemanticsScope, TypeInfo, VisibleTraits},
+};
+
+// Be careful with these re-exports.
+//
+// `hir` is the boundary between the compiler and the IDE. It should try hard to
+// isolate the compiler from the ide, to allow the two to be refactored
+// independently. Re-exporting something from the compiler is the sure way to
+// breach the boundary.
+//
+// Generally, a refactoring which *removes* a name from this list is a good
+// idea!
+pub use {
+ cfg::{CfgAtom, CfgExpr, CfgOptions},
+ hir_def::{
+ adt::StructKind,
+ attr::{Attr, Attrs, AttrsWithOwner, Documentation},
+ builtin_attr::AttributeTemplate,
+ find_path::PrefixKind,
+ import_map,
+ nameres::ModuleSource,
+ path::{ModPath, PathKind},
+ type_ref::{Mutability, TypeRef},
+ visibility::Visibility,
+ },
+ hir_expand::{
+ name::{known, Name},
+ ExpandResult, HirFileId, InFile, MacroFile, Origin,
+ },
+ hir_ty::display::HirDisplay,
+};
+
+// These are negative re-exports: pub using these names is forbidden, they
+// should remain private to hir internals.
+#[allow(unused)]
+use {
+ hir_def::path::Path,
+ hir_expand::{hygiene::Hygiene, name::AsName},
+};
+
+/// hir::Crate describes a single crate. It's the main interface with which
+/// a crate's dependencies interact. Mostly, it should be just a proxy for the
+/// root module.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct Crate {
+ pub(crate) id: CrateId,
+}
+
+#[derive(Debug)]
+pub struct CrateDependency {
+ pub krate: Crate,
+ pub name: Name,
+}
+
+impl Crate {
+ pub fn origin(self, db: &dyn HirDatabase) -> CrateOrigin {
+ db.crate_graph()[self.id].origin.clone()
+ }
+
+ pub fn is_builtin(self, db: &dyn HirDatabase) -> bool {
+ matches!(self.origin(db), CrateOrigin::Lang(_))
+ }
+
+ pub fn dependencies(self, db: &dyn HirDatabase) -> Vec<CrateDependency> {
+ db.crate_graph()[self.id]
+ .dependencies
+ .iter()
+ .map(|dep| {
+ let krate = Crate { id: dep.crate_id };
+ let name = dep.as_name();
+ CrateDependency { krate, name }
+ })
+ .collect()
+ }
+
+ pub fn reverse_dependencies(self, db: &dyn HirDatabase) -> Vec<Crate> {
+ let crate_graph = db.crate_graph();
+ crate_graph
+ .iter()
+ .filter(|&krate| {
+ crate_graph[krate].dependencies.iter().any(|it| it.crate_id == self.id)
+ })
+ .map(|id| Crate { id })
+ .collect()
+ }
+
+ pub fn transitive_reverse_dependencies(
+ self,
+ db: &dyn HirDatabase,
+ ) -> impl Iterator<Item = Crate> {
+ db.crate_graph().transitive_rev_deps(self.id).map(|id| Crate { id })
+ }
+
+ pub fn root_module(self, db: &dyn HirDatabase) -> Module {
+ let def_map = db.crate_def_map(self.id);
+ Module { id: def_map.module_id(def_map.root()) }
+ }
+
+ pub fn modules(self, db: &dyn HirDatabase) -> Vec<Module> {
+ let def_map = db.crate_def_map(self.id);
+ def_map.modules().map(|(id, _)| def_map.module_id(id).into()).collect()
+ }
+
+ pub fn root_file(self, db: &dyn HirDatabase) -> FileId {
+ db.crate_graph()[self.id].root_file_id
+ }
+
+ pub fn edition(self, db: &dyn HirDatabase) -> Edition {
+ db.crate_graph()[self.id].edition
+ }
+
+ pub fn version(self, db: &dyn HirDatabase) -> Option<String> {
+ db.crate_graph()[self.id].version.clone()
+ }
+
+ pub fn display_name(self, db: &dyn HirDatabase) -> Option<CrateDisplayName> {
+ db.crate_graph()[self.id].display_name.clone()
+ }
+
+ pub fn query_external_importables(
+ self,
+ db: &dyn DefDatabase,
+ query: import_map::Query,
+ ) -> impl Iterator<Item = Either<ModuleDef, Macro>> {
+ let _p = profile::span("query_external_importables");
+ import_map::search_dependencies(db, self.into(), query).into_iter().map(|item| {
+ match ItemInNs::from(item) {
+ ItemInNs::Types(mod_id) | ItemInNs::Values(mod_id) => Either::Left(mod_id),
+ ItemInNs::Macros(mac_id) => Either::Right(mac_id),
+ }
+ })
+ }
+
+ pub fn all(db: &dyn HirDatabase) -> Vec<Crate> {
+ db.crate_graph().iter().map(|id| Crate { id }).collect()
+ }
+
+ /// Try to get the root URL of the documentation of a crate.
+ pub fn get_html_root_url(self: &Crate, db: &dyn HirDatabase) -> Option<String> {
+ // Look for #![doc(html_root_url = "...")]
+ let attrs = db.attrs(AttrDefId::ModuleId(self.root_module(db).into()));
+ let doc_url = attrs.by_key("doc").find_string_value_in_tt("html_root_url");
+ doc_url.map(|s| s.trim_matches('"').trim_end_matches('/').to_owned() + "/")
+ }
+
+ pub fn cfg(&self, db: &dyn HirDatabase) -> CfgOptions {
+ db.crate_graph()[self.id].cfg_options.clone()
+ }
+
+ pub fn potential_cfg(&self, db: &dyn HirDatabase) -> CfgOptions {
+ db.crate_graph()[self.id].potential_cfg_options.clone()
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct Module {
+ pub(crate) id: ModuleId,
+}
+
+/// The defs which can be visible in the module.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum ModuleDef {
+ Module(Module),
+ Function(Function),
+ Adt(Adt),
+ // Can't be directly declared, but can be imported.
+ Variant(Variant),
+ Const(Const),
+ Static(Static),
+ Trait(Trait),
+ TypeAlias(TypeAlias),
+ BuiltinType(BuiltinType),
+ Macro(Macro),
+}
+impl_from!(
+ Module,
+ Function,
+ Adt(Struct, Enum, Union),
+ Variant,
+ Const,
+ Static,
+ Trait,
+ TypeAlias,
+ BuiltinType,
+ Macro
+ for ModuleDef
+);
+
+impl From<VariantDef> for ModuleDef {
+ fn from(var: VariantDef) -> Self {
+ match var {
+ VariantDef::Struct(t) => Adt::from(t).into(),
+ VariantDef::Union(t) => Adt::from(t).into(),
+ VariantDef::Variant(t) => t.into(),
+ }
+ }
+}
+
+impl ModuleDef {
+ pub fn module(self, db: &dyn HirDatabase) -> Option<Module> {
+ match self {
+ ModuleDef::Module(it) => it.parent(db),
+ ModuleDef::Function(it) => Some(it.module(db)),
+ ModuleDef::Adt(it) => Some(it.module(db)),
+ ModuleDef::Variant(it) => Some(it.module(db)),
+ ModuleDef::Const(it) => Some(it.module(db)),
+ ModuleDef::Static(it) => Some(it.module(db)),
+ ModuleDef::Trait(it) => Some(it.module(db)),
+ ModuleDef::TypeAlias(it) => Some(it.module(db)),
+ ModuleDef::Macro(it) => Some(it.module(db)),
+ ModuleDef::BuiltinType(_) => None,
+ }
+ }
+
+ pub fn canonical_path(&self, db: &dyn HirDatabase) -> Option<String> {
+ let mut segments = vec![self.name(db)?];
+ for m in self.module(db)?.path_to_root(db) {
+ segments.extend(m.name(db))
+ }
+ segments.reverse();
+ Some(segments.into_iter().join("::"))
+ }
+
+ pub fn canonical_module_path(
+ &self,
+ db: &dyn HirDatabase,
+ ) -> Option<impl Iterator<Item = Module>> {
+ self.module(db).map(|it| it.path_to_root(db).into_iter().rev())
+ }
+
+ pub fn name(self, db: &dyn HirDatabase) -> Option<Name> {
+ let name = match self {
+ ModuleDef::Module(it) => it.name(db)?,
+ ModuleDef::Const(it) => it.name(db)?,
+ ModuleDef::Adt(it) => it.name(db),
+ ModuleDef::Trait(it) => it.name(db),
+ ModuleDef::Function(it) => it.name(db),
+ ModuleDef::Variant(it) => it.name(db),
+ ModuleDef::TypeAlias(it) => it.name(db),
+ ModuleDef::Static(it) => it.name(db),
+ ModuleDef::Macro(it) => it.name(db),
+ ModuleDef::BuiltinType(it) => it.name(),
+ };
+ Some(name)
+ }
+
+ pub fn diagnostics(self, db: &dyn HirDatabase) -> Vec<AnyDiagnostic> {
+ let id = match self {
+ ModuleDef::Adt(it) => match it {
+ Adt::Struct(it) => it.id.into(),
+ Adt::Enum(it) => it.id.into(),
+ Adt::Union(it) => it.id.into(),
+ },
+ ModuleDef::Trait(it) => it.id.into(),
+ ModuleDef::Function(it) => it.id.into(),
+ ModuleDef::TypeAlias(it) => it.id.into(),
+ ModuleDef::Module(it) => it.id.into(),
+ ModuleDef::Const(it) => it.id.into(),
+ ModuleDef::Static(it) => it.id.into(),
- | ModuleDef::Variant(_)
++ ModuleDef::Variant(it) => {
++ EnumVariantId { parent: it.parent.into(), local_id: it.id }.into()
++ }
++ ModuleDef::BuiltinType(_) | ModuleDef::Macro(_) => return Vec::new(),
+ };
+
+ let module = match self.module(db) {
+ Some(it) => it,
+ None => return Vec::new(),
+ };
+
+ let mut acc = Vec::new();
+
+ match self.as_def_with_body() {
+ Some(def) => {
+ def.diagnostics(db, &mut acc);
+ }
+ None => {
+ for diag in hir_ty::diagnostics::incorrect_case(db, module.id.krate(), id) {
+ acc.push(diag.into())
+ }
+ }
+ }
+
+ acc
+ }
+
+ pub fn as_def_with_body(self) -> Option<DefWithBody> {
+ match self {
+ ModuleDef::Function(it) => Some(it.into()),
+ ModuleDef::Const(it) => Some(it.into()),
+ ModuleDef::Static(it) => Some(it.into()),
++ ModuleDef::Variant(it) => Some(it.into()),
+
+ ModuleDef::Module(_)
+ | ModuleDef::Adt(_)
- pub fn repr(self, db: &dyn HirDatabase) -> Option<ReprKind> {
+ | ModuleDef::Trait(_)
+ | ModuleDef::TypeAlias(_)
+ | ModuleDef::Macro(_)
+ | ModuleDef::BuiltinType(_) => None,
+ }
+ }
+
+ pub fn attrs(&self, db: &dyn HirDatabase) -> Option<AttrsWithOwner> {
+ Some(match self {
+ ModuleDef::Module(it) => it.attrs(db),
+ ModuleDef::Function(it) => it.attrs(db),
+ ModuleDef::Adt(it) => it.attrs(db),
+ ModuleDef::Variant(it) => it.attrs(db),
+ ModuleDef::Const(it) => it.attrs(db),
+ ModuleDef::Static(it) => it.attrs(db),
+ ModuleDef::Trait(it) => it.attrs(db),
+ ModuleDef::TypeAlias(it) => it.attrs(db),
+ ModuleDef::Macro(it) => it.attrs(db),
+ ModuleDef::BuiltinType(_) => return None,
+ })
+ }
+}
+
+impl HasVisibility for ModuleDef {
+ fn visibility(&self, db: &dyn HirDatabase) -> Visibility {
+ match *self {
+ ModuleDef::Module(it) => it.visibility(db),
+ ModuleDef::Function(it) => it.visibility(db),
+ ModuleDef::Adt(it) => it.visibility(db),
+ ModuleDef::Const(it) => it.visibility(db),
+ ModuleDef::Static(it) => it.visibility(db),
+ ModuleDef::Trait(it) => it.visibility(db),
+ ModuleDef::TypeAlias(it) => it.visibility(db),
+ ModuleDef::Variant(it) => it.visibility(db),
+ ModuleDef::Macro(it) => it.visibility(db),
+ ModuleDef::BuiltinType(_) => Visibility::Public,
+ }
+ }
+}
+
+impl Module {
+ /// Name of this module.
+ pub fn name(self, db: &dyn HirDatabase) -> Option<Name> {
+ let def_map = self.id.def_map(db.upcast());
+ let parent = def_map[self.id.local_id].parent?;
+ def_map[parent].children.iter().find_map(|(name, module_id)| {
+ if *module_id == self.id.local_id {
+ Some(name.clone())
+ } else {
+ None
+ }
+ })
+ }
+
+ /// Returns the crate this module is part of.
+ pub fn krate(self) -> 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: &dyn HirDatabase) -> Module {
+ let def_map = db.crate_def_map(self.id.krate());
+ Module { id: def_map.module_id(def_map.root()) }
+ }
+
+ pub fn is_crate_root(self, db: &dyn HirDatabase) -> bool {
+ let def_map = db.crate_def_map(self.id.krate());
+ def_map.root() == self.id.local_id
+ }
+
+ /// Iterates over all child modules.
+ pub fn children(self, db: &dyn HirDatabase) -> impl Iterator<Item = Module> {
+ let def_map = self.id.def_map(db.upcast());
+ let children = def_map[self.id.local_id]
+ .children
+ .iter()
+ .map(|(_, module_id)| Module { id: def_map.module_id(*module_id) })
+ .collect::<Vec<_>>();
+ children.into_iter()
+ }
+
+ /// Finds a parent module.
+ pub fn parent(self, db: &dyn HirDatabase) -> Option<Module> {
+ // FIXME: handle block expressions as modules (their parent is in a different DefMap)
+ let def_map = self.id.def_map(db.upcast());
+ let parent_id = def_map[self.id.local_id].parent?;
+ Some(Module { id: def_map.module_id(parent_id) })
+ }
+
+ pub fn path_to_root(self, db: &dyn HirDatabase) -> Vec<Module> {
+ let mut res = vec![self];
+ let mut curr = self;
+ while let Some(next) = curr.parent(db) {
+ res.push(next);
+ curr = next
+ }
+ res
+ }
+
+ /// Returns a `ModuleScope`: a set of items, visible in this module.
+ pub fn scope(
+ self,
+ db: &dyn HirDatabase,
+ visible_from: Option<Module>,
+ ) -> Vec<(Name, ScopeDef)> {
+ self.id.def_map(db.upcast())[self.id.local_id]
+ .scope
+ .entries()
+ .filter_map(|(name, def)| {
+ if let Some(m) = visible_from {
+ let filtered =
+ def.filter_visibility(|vis| vis.is_visible_from(db.upcast(), m.id));
+ if filtered.is_none() && !def.is_none() {
+ None
+ } else {
+ Some((name, filtered))
+ }
+ } else {
+ Some((name, def))
+ }
+ })
+ .flat_map(|(name, def)| {
+ ScopeDef::all_items(def).into_iter().map(move |item| (name.clone(), item))
+ })
+ .collect()
+ }
+
+ /// Fills `acc` with the module's diagnostics.
+ pub fn diagnostics(self, db: &dyn HirDatabase, acc: &mut Vec<AnyDiagnostic>) {
+ let _p = profile::span("Module::diagnostics").detail(|| {
+ format!("{:?}", self.name(db).map_or("<unknown>".into(), |name| name.to_string()))
+ });
+ let def_map = self.id.def_map(db.upcast());
+ for diag in def_map.diagnostics() {
+ if diag.in_module != self.id.local_id {
+ // FIXME: This is accidentally quadratic.
+ continue;
+ }
+ emit_def_diagnostic(db, acc, diag);
+ }
+ for decl in self.declarations(db) {
+ match decl {
+ ModuleDef::Module(m) => {
+ // Only add diagnostics from inline modules
+ if def_map[m.id.local_id].origin.is_inline() {
+ m.diagnostics(db, acc)
+ }
+ }
+ ModuleDef::Trait(t) => {
+ for diag in db.trait_data_with_diagnostics(t.id).1.iter() {
+ emit_def_diagnostic(db, acc, diag);
+ }
+ acc.extend(decl.diagnostics(db))
+ }
++ ModuleDef::Adt(adt) => {
++ match adt {
++ Adt::Struct(s) => {
++ for diag in db.struct_data_with_diagnostics(s.id).1.iter() {
++ emit_def_diagnostic(db, acc, diag);
++ }
++ }
++ Adt::Union(u) => {
++ for diag in db.union_data_with_diagnostics(u.id).1.iter() {
++ emit_def_diagnostic(db, acc, diag);
++ }
++ }
++ Adt::Enum(e) => {
++ for v in e.variants(db) {
++ acc.extend(ModuleDef::Variant(v).diagnostics(db));
++ }
++
++ for diag in db.enum_data_with_diagnostics(e.id).1.iter() {
++ emit_def_diagnostic(db, acc, diag);
++ }
++ }
++ }
++ acc.extend(decl.diagnostics(db))
++ }
+ _ => acc.extend(decl.diagnostics(db)),
+ }
+ }
+
+ for impl_def in self.impl_defs(db) {
+ for diag in db.impl_data_with_diagnostics(impl_def.id).1.iter() {
+ emit_def_diagnostic(db, acc, diag);
+ }
+
+ for item in impl_def.items(db) {
+ let def: DefWithBody = match item {
+ AssocItem::Function(it) => it.into(),
+ AssocItem::Const(it) => it.into(),
+ AssocItem::TypeAlias(_) => continue,
+ };
+
+ def.diagnostics(db, acc);
+ }
+ }
+ }
+
+ pub fn declarations(self, db: &dyn HirDatabase) -> Vec<ModuleDef> {
+ let def_map = self.id.def_map(db.upcast());
+ let scope = &def_map[self.id.local_id].scope;
+ scope
+ .declarations()
+ .map(ModuleDef::from)
+ .chain(scope.unnamed_consts().map(|id| ModuleDef::Const(Const::from(id))))
+ .collect()
+ }
+
+ pub fn legacy_macros(self, db: &dyn HirDatabase) -> Vec<Macro> {
+ let def_map = self.id.def_map(db.upcast());
+ let scope = &def_map[self.id.local_id].scope;
+ scope.legacy_macros().flat_map(|(_, it)| it).map(|&it| MacroId::from(it).into()).collect()
+ }
+
+ pub fn impl_defs(self, db: &dyn HirDatabase) -> Vec<Impl> {
+ let def_map = self.id.def_map(db.upcast());
+ def_map[self.id.local_id].scope.impls().map(Impl::from).collect()
+ }
+
+ /// Finds a path that can be used to refer to the given item from within
+ /// this module, if possible.
+ pub fn find_use_path(
+ self,
+ db: &dyn DefDatabase,
+ item: impl Into<ItemInNs>,
+ prefer_no_std: bool,
+ ) -> Option<ModPath> {
+ hir_def::find_path::find_path(db, item.into().into(), self.into(), prefer_no_std)
+ }
+
+ /// Finds a path that can be used to refer to the given item from within
+ /// this module, if possible. This is used for returning import paths for use-statements.
+ pub fn find_use_path_prefixed(
+ self,
+ db: &dyn DefDatabase,
+ item: impl Into<ItemInNs>,
+ prefix_kind: PrefixKind,
+ prefer_no_std: bool,
+ ) -> Option<ModPath> {
+ hir_def::find_path::find_path_prefixed(
+ db,
+ item.into().into(),
+ self.into(),
+ prefix_kind,
+ prefer_no_std,
+ )
+ }
+}
+
+fn emit_def_diagnostic(db: &dyn HirDatabase, acc: &mut Vec<AnyDiagnostic>, diag: &DefDiagnostic) {
+ match &diag.kind {
+ DefDiagnosticKind::UnresolvedModule { ast: declaration, candidates } => {
+ let decl = declaration.to_node(db.upcast());
+ acc.push(
+ UnresolvedModule {
+ decl: InFile::new(declaration.file_id, AstPtr::new(&decl)),
+ candidates: candidates.clone(),
+ }
+ .into(),
+ )
+ }
+ DefDiagnosticKind::UnresolvedExternCrate { ast } => {
+ let item = ast.to_node(db.upcast());
+ acc.push(
+ UnresolvedExternCrate { decl: InFile::new(ast.file_id, AstPtr::new(&item)) }.into(),
+ );
+ }
+
+ DefDiagnosticKind::UnresolvedImport { id, index } => {
+ let file_id = id.file_id();
+ let item_tree = id.item_tree(db.upcast());
+ let import = &item_tree[id.value];
+
+ let use_tree = import.use_tree_to_ast(db.upcast(), file_id, *index);
+ acc.push(
+ UnresolvedImport { decl: InFile::new(file_id, AstPtr::new(&use_tree)) }.into(),
+ );
+ }
+
+ DefDiagnosticKind::UnconfiguredCode { ast, cfg, opts } => {
+ let item = ast.to_node(db.upcast());
+ acc.push(
+ InactiveCode {
+ node: ast.with_value(AstPtr::new(&item).into()),
+ cfg: cfg.clone(),
+ opts: opts.clone(),
+ }
+ .into(),
+ );
+ }
+
+ DefDiagnosticKind::UnresolvedProcMacro { ast, krate } => {
+ let (node, precise_location, macro_name, kind) = precise_macro_call_location(ast, db);
+ acc.push(
+ UnresolvedProcMacro { node, precise_location, macro_name, kind, krate: *krate }
+ .into(),
+ );
+ }
+
+ DefDiagnosticKind::UnresolvedMacroCall { ast, path } => {
+ let (node, precise_location, _, _) = precise_macro_call_location(ast, db);
+ acc.push(
+ UnresolvedMacroCall {
+ macro_call: node,
+ precise_location,
+ path: path.clone(),
+ is_bang: matches!(ast, MacroCallKind::FnLike { .. }),
+ }
+ .into(),
+ );
+ }
+
+ DefDiagnosticKind::MacroError { ast, message } => {
+ let (node, precise_location, _, _) = precise_macro_call_location(ast, db);
+ acc.push(MacroError { node, precise_location, message: message.clone() }.into());
+ }
+
+ DefDiagnosticKind::UnimplementedBuiltinMacro { ast } => {
+ let node = ast.to_node(db.upcast());
+ // Must have a name, otherwise we wouldn't emit it.
+ let name = node.name().expect("unimplemented builtin macro with no name");
+ acc.push(
+ UnimplementedBuiltinMacro {
+ node: ast.with_value(SyntaxNodePtr::from(AstPtr::new(&name))),
+ }
+ .into(),
+ );
+ }
+ DefDiagnosticKind::InvalidDeriveTarget { ast, id } => {
+ let node = ast.to_node(db.upcast());
+ let derive = node.attrs().nth(*id as usize);
+ match derive {
+ Some(derive) => {
+ acc.push(
+ InvalidDeriveTarget {
+ node: ast.with_value(SyntaxNodePtr::from(AstPtr::new(&derive))),
+ }
+ .into(),
+ );
+ }
+ None => stdx::never!("derive diagnostic on item without derive attribute"),
+ }
+ }
+ DefDiagnosticKind::MalformedDerive { ast, id } => {
+ let node = ast.to_node(db.upcast());
+ let derive = node.attrs().nth(*id as usize);
+ match derive {
+ Some(derive) => {
+ acc.push(
+ MalformedDerive {
+ node: ast.with_value(SyntaxNodePtr::from(AstPtr::new(&derive))),
+ }
+ .into(),
+ );
+ }
+ None => stdx::never!("derive diagnostic on item without derive attribute"),
+ }
+ }
+ }
+}
+
+fn precise_macro_call_location(
+ ast: &MacroCallKind,
+ db: &dyn HirDatabase,
+) -> (InFile<SyntaxNodePtr>, Option<TextRange>, Option<String>, MacroKind) {
+ // FIXME: maaybe we actually want slightly different ranges for the different macro diagnostics
+ // - e.g. the full attribute for macro errors, but only the name for name resolution
+ match ast {
+ MacroCallKind::FnLike { ast_id, .. } => {
+ let node = ast_id.to_node(db.upcast());
+ (
+ ast_id.with_value(SyntaxNodePtr::from(AstPtr::new(&node))),
+ node.path()
+ .and_then(|it| it.segment())
+ .and_then(|it| it.name_ref())
+ .map(|it| it.syntax().text_range()),
+ node.path().and_then(|it| it.segment()).map(|it| it.to_string()),
+ MacroKind::ProcMacro,
+ )
+ }
+ MacroCallKind::Derive { ast_id, derive_attr_index, derive_index } => {
+ let node = ast_id.to_node(db.upcast());
+ // Compute the precise location of the macro name's token in the derive
+ // list.
+ let token = (|| {
+ let derive_attr = node
+ .doc_comments_and_attrs()
+ .nth(*derive_attr_index as usize)
+ .and_then(Either::left)?;
+ let token_tree = derive_attr.meta()?.token_tree()?;
+ let group_by = token_tree
+ .syntax()
+ .children_with_tokens()
+ .filter_map(|elem| match elem {
+ syntax::NodeOrToken::Token(tok) => Some(tok),
+ _ => None,
+ })
+ .group_by(|t| t.kind() == T![,]);
+ let (_, mut group) = group_by
+ .into_iter()
+ .filter(|&(comma, _)| !comma)
+ .nth(*derive_index as usize)?;
+ group.find(|t| t.kind() == T![ident])
+ })();
+ (
+ ast_id.with_value(SyntaxNodePtr::from(AstPtr::new(&node))),
+ token.as_ref().map(|tok| tok.text_range()),
+ token.as_ref().map(ToString::to_string),
+ MacroKind::Derive,
+ )
+ }
+ MacroCallKind::Attr { ast_id, invoc_attr_index, .. } => {
+ let node = ast_id.to_node(db.upcast());
+ let attr = node
+ .doc_comments_and_attrs()
+ .nth((*invoc_attr_index) as usize)
+ .and_then(Either::left)
+ .unwrap_or_else(|| panic!("cannot find attribute #{}", invoc_attr_index));
+
+ (
+ ast_id.with_value(SyntaxNodePtr::from(AstPtr::new(&attr))),
+ Some(attr.syntax().text_range()),
+ attr.path()
+ .and_then(|path| path.segment())
+ .and_then(|seg| seg.name_ref())
+ .as_ref()
+ .map(ToString::to_string),
+ MacroKind::Attr,
+ )
+ }
+ }
+}
+
+impl HasVisibility for Module {
+ fn visibility(&self, db: &dyn HirDatabase) -> Visibility {
+ let def_map = self.id.def_map(db.upcast());
+ let module_data = &def_map[self.id.local_id];
+ module_data.visibility
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct Field {
+ pub(crate) parent: VariantDef,
+ pub(crate) id: LocalFieldId,
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub enum FieldSource {
+ Named(ast::RecordField),
+ Pos(ast::TupleField),
+}
+
+impl Field {
+ pub fn name(&self, db: &dyn HirDatabase) -> Name {
+ self.parent.variant_data(db).fields()[self.id].name.clone()
+ }
+
+ /// Returns the type as in the signature of the struct (i.e., with
+ /// placeholder types for type parameters). Only use this in the context of
+ /// the field definition.
+ pub fn ty(&self, db: &dyn HirDatabase) -> Type {
+ let var_id = self.parent.into();
+ let generic_def_id: GenericDefId = match self.parent {
+ VariantDef::Struct(it) => it.id.into(),
+ VariantDef::Union(it) => it.id.into(),
+ VariantDef::Variant(it) => it.parent.id.into(),
+ };
+ let substs = TyBuilder::placeholder_subst(db, generic_def_id);
+ let ty = db.field_types(var_id)[self.id].clone().substitute(Interner, &substs);
+ Type::new(db, var_id, ty)
+ }
+
+ pub fn parent_def(&self, _db: &dyn HirDatabase) -> VariantDef {
+ self.parent
+ }
+}
+
+impl HasVisibility for Field {
+ fn visibility(&self, db: &dyn HirDatabase) -> Visibility {
+ let variant_data = self.parent.variant_data(db);
+ let visibility = &variant_data.fields()[self.id].visibility;
+ let parent_id: hir_def::VariantId = self.parent.into();
+ visibility.resolve(db.upcast(), &parent_id.resolver(db.upcast()))
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct Struct {
+ pub(crate) id: StructId,
+}
+
+impl Struct {
+ pub fn module(self, db: &dyn HirDatabase) -> Module {
+ Module { id: self.id.lookup(db.upcast()).container }
+ }
+
+ pub fn name(self, db: &dyn HirDatabase) -> Name {
+ db.struct_data(self.id).name.clone()
+ }
+
+ pub fn fields(self, db: &dyn HirDatabase) -> Vec<Field> {
+ db.struct_data(self.id)
+ .variant_data
+ .fields()
+ .iter()
+ .map(|(id, _)| Field { parent: self.into(), id })
+ .collect()
+ }
+
+ pub fn ty(self, db: &dyn HirDatabase) -> Type {
+ Type::from_def(db, self.id)
+ }
+
- let ty = TyBuilder::def_ty(db, id.into())
++ pub fn repr(self, db: &dyn HirDatabase) -> Option<ReprData> {
+ db.struct_data(self.id).repr.clone()
+ }
+
+ pub fn kind(self, db: &dyn HirDatabase) -> StructKind {
+ self.variant_data(db).kind()
+ }
+
+ fn variant_data(self, db: &dyn HirDatabase) -> Arc<VariantData> {
+ db.struct_data(self.id).variant_data.clone()
+ }
+}
+
+impl HasVisibility for Struct {
+ fn visibility(&self, db: &dyn HirDatabase) -> Visibility {
+ db.struct_data(self.id).visibility.resolve(db.upcast(), &self.id.resolver(db.upcast()))
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct Union {
+ pub(crate) id: UnionId,
+}
+
+impl Union {
+ pub fn name(self, db: &dyn HirDatabase) -> Name {
+ db.union_data(self.id).name.clone()
+ }
+
+ pub fn module(self, db: &dyn HirDatabase) -> Module {
+ Module { id: self.id.lookup(db.upcast()).container }
+ }
+
+ pub fn ty(self, db: &dyn HirDatabase) -> Type {
+ Type::from_def(db, self.id)
+ }
+
+ pub fn fields(self, db: &dyn HirDatabase) -> Vec<Field> {
+ db.union_data(self.id)
+ .variant_data
+ .fields()
+ .iter()
+ .map(|(id, _)| Field { parent: self.into(), id })
+ .collect()
+ }
+
+ fn variant_data(self, db: &dyn HirDatabase) -> Arc<VariantData> {
+ db.union_data(self.id).variant_data.clone()
+ }
+}
+
+impl HasVisibility for Union {
+ fn visibility(&self, db: &dyn HirDatabase) -> Visibility {
+ db.union_data(self.id).visibility.resolve(db.upcast(), &self.id.resolver(db.upcast()))
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct Enum {
+ pub(crate) id: EnumId,
+}
+
+impl Enum {
+ pub fn module(self, db: &dyn HirDatabase) -> Module {
+ Module { id: self.id.lookup(db.upcast()).container }
+ }
+
+ pub fn name(self, db: &dyn HirDatabase) -> Name {
+ db.enum_data(self.id).name.clone()
+ }
+
+ pub fn variants(self, db: &dyn HirDatabase) -> Vec<Variant> {
+ db.enum_data(self.id).variants.iter().map(|(id, _)| Variant { parent: self, id }).collect()
+ }
+
+ pub fn ty(self, db: &dyn HirDatabase) -> Type {
+ Type::from_def(db, self.id)
+ }
++
++ /// The type of the enum variant bodies.
++ pub fn variant_body_ty(self, db: &dyn HirDatabase) -> Type {
++ Type::new_for_crate(
++ self.id.lookup(db.upcast()).container.krate(),
++ TyBuilder::builtin(match db.enum_data(self.id).variant_body_type() {
++ Either::Left(builtin) => hir_def::builtin_type::BuiltinType::Int(builtin),
++ Either::Right(builtin) => hir_def::builtin_type::BuiltinType::Uint(builtin),
++ }),
++ )
++ }
++
++ pub fn is_data_carrying(self, db: &dyn HirDatabase) -> bool {
++ self.variants(db).iter().any(|v| !matches!(v.kind(db), StructKind::Unit))
++ }
+}
+
+impl HasVisibility for Enum {
+ fn visibility(&self, db: &dyn HirDatabase) -> Visibility {
+ db.enum_data(self.id).visibility.resolve(db.upcast(), &self.id.resolver(db.upcast()))
+ }
+}
+
++impl From<&Variant> for DefWithBodyId {
++ fn from(&v: &Variant) -> Self {
++ DefWithBodyId::VariantId(v.into())
++ }
++}
++
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct Variant {
+ pub(crate) parent: Enum,
+ pub(crate) id: LocalEnumVariantId,
+}
+
+impl Variant {
+ pub fn module(self, db: &dyn HirDatabase) -> Module {
+ self.parent.module(db)
+ }
+
+ pub fn parent_enum(self, _db: &dyn HirDatabase) -> Enum {
+ self.parent
+ }
+
+ pub fn name(self, db: &dyn HirDatabase) -> Name {
+ db.enum_data(self.parent.id).variants[self.id].name.clone()
+ }
+
+ pub fn fields(self, db: &dyn HirDatabase) -> Vec<Field> {
+ self.variant_data(db)
+ .fields()
+ .iter()
+ .map(|(id, _)| Field { parent: self.into(), id })
+ .collect()
+ }
+
+ pub fn kind(self, db: &dyn HirDatabase) -> StructKind {
+ self.variant_data(db).kind()
+ }
+
+ pub(crate) fn variant_data(self, db: &dyn HirDatabase) -> Arc<VariantData> {
+ db.enum_data(self.parent.id).variants[self.id].variant_data.clone()
+ }
++
++ pub fn value(self, db: &dyn HirDatabase) -> Option<Expr> {
++ self.source(db)?.value.expr()
++ }
++
++ pub fn eval(self, db: &dyn HirDatabase) -> Result<ComputedExpr, ConstEvalError> {
++ db.const_eval_variant(self.into())
++ }
+}
+
+/// Variants inherit visibility from the parent enum.
+impl HasVisibility for Variant {
+ fn visibility(&self, db: &dyn HirDatabase) -> Visibility {
+ self.parent_enum(db).visibility(db)
+ }
+}
+
+/// A Data Type
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub enum Adt {
+ Struct(Struct),
+ Union(Union),
+ Enum(Enum),
+}
+impl_from!(Struct, Union, Enum for Adt);
+
+impl Adt {
+ pub fn has_non_default_type_params(self, db: &dyn HirDatabase) -> bool {
+ let subst = db.generic_defaults(self.into());
+ subst.iter().any(|ty| match ty.skip_binders().data(Interner) {
+ GenericArgData::Ty(x) => x.is_unknown(),
+ _ => false,
+ })
+ }
+
+ /// Turns this ADT into a type. Any type parameters of the ADT will be
+ /// turned into unknown types, which is good for e.g. finding the most
+ /// general set of completions, but will not look very nice when printed.
+ pub fn ty(self, db: &dyn HirDatabase) -> Type {
+ let id = AdtId::from(self);
+ Type::from_def(db, id)
+ }
+
+ /// Turns this ADT into a type with the given type parameters. This isn't
+ /// the greatest API, FIXME find a better one.
+ pub fn ty_with_args(self, db: &dyn HirDatabase, args: &[Type]) -> Type {
+ let id = AdtId::from(self);
+ let mut it = args.iter().map(|t| t.ty.clone());
- impl_from!(Function, Const, Static for DefWithBody);
++ let ty = TyBuilder::def_ty(db, id.into(), None)
+ .fill(|x| {
+ let r = it.next().unwrap_or_else(|| TyKind::Error.intern(Interner));
+ match x {
+ ParamKind::Type => GenericArgData::Ty(r).intern(Interner),
+ ParamKind::Const(ty) => unknown_const_as_generic(ty.clone()),
+ }
+ })
+ .build();
+ Type::new(db, id, ty)
+ }
+
+ pub fn module(self, db: &dyn HirDatabase) -> Module {
+ match self {
+ Adt::Struct(s) => s.module(db),
+ Adt::Union(s) => s.module(db),
+ Adt::Enum(e) => e.module(db),
+ }
+ }
+
+ pub fn name(self, db: &dyn HirDatabase) -> Name {
+ match self {
+ Adt::Struct(s) => s.name(db),
+ Adt::Union(u) => u.name(db),
+ Adt::Enum(e) => e.name(db),
+ }
+ }
+
+ pub fn as_enum(&self) -> Option<Enum> {
+ if let Self::Enum(v) = self {
+ Some(*v)
+ } else {
+ None
+ }
+ }
+}
+
+impl HasVisibility for Adt {
+ fn visibility(&self, db: &dyn HirDatabase) -> Visibility {
+ match self {
+ Adt::Struct(it) => it.visibility(db),
+ Adt::Union(it) => it.visibility(db),
+ Adt::Enum(it) => it.visibility(db),
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub enum VariantDef {
+ Struct(Struct),
+ Union(Union),
+ Variant(Variant),
+}
+impl_from!(Struct, Union, Variant for VariantDef);
+
+impl VariantDef {
+ pub fn fields(self, db: &dyn HirDatabase) -> Vec<Field> {
+ match self {
+ VariantDef::Struct(it) => it.fields(db),
+ VariantDef::Union(it) => it.fields(db),
+ VariantDef::Variant(it) => it.fields(db),
+ }
+ }
+
+ pub fn module(self, db: &dyn HirDatabase) -> Module {
+ match self {
+ VariantDef::Struct(it) => it.module(db),
+ VariantDef::Union(it) => it.module(db),
+ VariantDef::Variant(it) => it.module(db),
+ }
+ }
+
+ pub fn name(&self, db: &dyn HirDatabase) -> Name {
+ match self {
+ VariantDef::Struct(s) => s.name(db),
+ VariantDef::Union(u) => u.name(db),
+ VariantDef::Variant(e) => e.name(db),
+ }
+ }
+
+ pub(crate) fn variant_data(self, db: &dyn HirDatabase) -> Arc<VariantData> {
+ match self {
+ VariantDef::Struct(it) => it.variant_data(db),
+ VariantDef::Union(it) => it.variant_data(db),
+ VariantDef::Variant(it) => it.variant_data(db),
+ }
+ }
+}
+
+/// The defs which have a body.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum DefWithBody {
+ Function(Function),
+ Static(Static),
+ Const(Const),
++ Variant(Variant),
+}
- let ty = ty.substitute(Interner, &subst_prefix(&subst, local_idx));
++impl_from!(Function, Const, Static, Variant for DefWithBody);
+
+impl DefWithBody {
+ pub fn module(self, db: &dyn HirDatabase) -> Module {
+ match self {
+ DefWithBody::Const(c) => c.module(db),
+ DefWithBody::Function(f) => f.module(db),
+ DefWithBody::Static(s) => s.module(db),
++ DefWithBody::Variant(v) => v.module(db),
+ }
+ }
+
+ pub fn name(self, db: &dyn HirDatabase) -> Option<Name> {
+ match self {
+ DefWithBody::Function(f) => Some(f.name(db)),
+ DefWithBody::Static(s) => Some(s.name(db)),
+ DefWithBody::Const(c) => c.name(db),
++ DefWithBody::Variant(v) => Some(v.name(db)),
+ }
+ }
+
+ /// Returns the type this def's body has to evaluate to.
+ pub fn body_type(self, db: &dyn HirDatabase) -> Type {
+ match self {
+ DefWithBody::Function(it) => it.ret_type(db),
+ DefWithBody::Static(it) => it.ty(db),
+ DefWithBody::Const(it) => it.ty(db),
++ DefWithBody::Variant(it) => it.parent.variant_body_ty(db),
+ }
+ }
+
+ fn id(&self) -> DefWithBodyId {
+ match self {
+ DefWithBody::Function(it) => it.id.into(),
+ DefWithBody::Static(it) => it.id.into(),
+ DefWithBody::Const(it) => it.id.into(),
++ DefWithBody::Variant(it) => it.into(),
+ }
+ }
+
+ /// A textual representation of the HIR of this def's body for debugging purposes.
+ pub fn debug_hir(self, db: &dyn HirDatabase) -> String {
+ let body = db.body(self.id());
+ body.pretty_print(db.upcast(), self.id())
+ }
+
+ pub fn diagnostics(self, db: &dyn HirDatabase, acc: &mut Vec<AnyDiagnostic>) {
+ let krate = self.module(db).id.krate();
+
+ let (body, source_map) = db.body_with_source_map(self.into());
+
+ for (_, def_map) in body.blocks(db.upcast()) {
+ for diag in def_map.diagnostics() {
+ emit_def_diagnostic(db, acc, diag);
+ }
+ }
+
+ for diag in source_map.diagnostics() {
+ match diag {
+ BodyDiagnostic::InactiveCode { node, cfg, opts } => acc.push(
+ InactiveCode { node: node.clone(), cfg: cfg.clone(), opts: opts.clone() }
+ .into(),
+ ),
+ BodyDiagnostic::MacroError { node, message } => acc.push(
+ MacroError {
+ node: node.clone().map(|it| it.into()),
+ precise_location: None,
+ message: message.to_string(),
+ }
+ .into(),
+ ),
+ BodyDiagnostic::UnresolvedProcMacro { node, krate } => acc.push(
+ UnresolvedProcMacro {
+ node: node.clone().map(|it| it.into()),
+ precise_location: None,
+ macro_name: None,
+ kind: MacroKind::ProcMacro,
+ krate: *krate,
+ }
+ .into(),
+ ),
+ BodyDiagnostic::UnresolvedMacroCall { node, path } => acc.push(
+ UnresolvedMacroCall {
+ macro_call: node.clone().map(|ast_ptr| ast_ptr.into()),
+ precise_location: None,
+ path: path.clone(),
+ is_bang: true,
+ }
+ .into(),
+ ),
+ }
+ }
+
+ let infer = db.infer(self.into());
+ let source_map = Lazy::new(|| db.body_with_source_map(self.into()).1);
+ for d in &infer.diagnostics {
+ match d {
+ hir_ty::InferenceDiagnostic::NoSuchField { expr } => {
+ let field = source_map.field_syntax(*expr);
+ acc.push(NoSuchField { field }.into())
+ }
+ &hir_ty::InferenceDiagnostic::BreakOutsideOfLoop { expr, is_break } => {
+ let expr = source_map
+ .expr_syntax(expr)
+ .expect("break outside of loop in synthetic syntax");
+ acc.push(BreakOutsideOfLoop { expr, is_break }.into())
+ }
+ hir_ty::InferenceDiagnostic::MismatchedArgCount { call_expr, expected, found } => {
+ match source_map.expr_syntax(*call_expr) {
+ Ok(source_ptr) => acc.push(
+ MismatchedArgCount {
+ call_expr: source_ptr,
+ expected: *expected,
+ found: *found,
+ }
+ .into(),
+ ),
+ Err(SyntheticSyntax) => (),
+ }
+ }
+ }
+ }
+ for (expr, mismatch) in infer.expr_type_mismatches() {
+ let expr = match source_map.expr_syntax(expr) {
+ Ok(expr) => expr,
+ Err(SyntheticSyntax) => continue,
+ };
+ acc.push(
+ TypeMismatch {
+ expr,
+ expected: Type::new(db, DefWithBodyId::from(self), mismatch.expected.clone()),
+ actual: Type::new(db, DefWithBodyId::from(self), mismatch.actual.clone()),
+ }
+ .into(),
+ );
+ }
+
+ for expr in hir_ty::diagnostics::missing_unsafe(db, self.into()) {
+ match source_map.expr_syntax(expr) {
+ Ok(expr) => acc.push(MissingUnsafe { expr }.into()),
+ Err(SyntheticSyntax) => {
+ // FIXME: Here and eslwhere in this file, the `expr` was
+ // desugared, report or assert that this doesn't happen.
+ }
+ }
+ }
+
+ for diagnostic in BodyValidationDiagnostic::collect(db, self.into()) {
+ match diagnostic {
+ BodyValidationDiagnostic::RecordMissingFields {
+ record,
+ variant,
+ missed_fields,
+ } => {
+ let variant_data = variant.variant_data(db.upcast());
+ let missed_fields = missed_fields
+ .into_iter()
+ .map(|idx| variant_data.fields()[idx].name.clone())
+ .collect();
+
+ match record {
+ Either::Left(record_expr) => match source_map.expr_syntax(record_expr) {
+ Ok(source_ptr) => {
+ let root = source_ptr.file_syntax(db.upcast());
+ if let ast::Expr::RecordExpr(record_expr) =
+ &source_ptr.value.to_node(&root)
+ {
+ if record_expr.record_expr_field_list().is_some() {
+ acc.push(
+ MissingFields {
+ file: source_ptr.file_id,
+ field_list_parent: Either::Left(AstPtr::new(
+ record_expr,
+ )),
+ field_list_parent_path: record_expr
+ .path()
+ .map(|path| AstPtr::new(&path)),
+ missed_fields,
+ }
+ .into(),
+ )
+ }
+ }
+ }
+ Err(SyntheticSyntax) => (),
+ },
+ Either::Right(record_pat) => match source_map.pat_syntax(record_pat) {
+ Ok(source_ptr) => {
+ if let Some(expr) = source_ptr.value.as_ref().left() {
+ let root = source_ptr.file_syntax(db.upcast());
+ if let ast::Pat::RecordPat(record_pat) = expr.to_node(&root) {
+ if record_pat.record_pat_field_list().is_some() {
+ acc.push(
+ MissingFields {
+ file: source_ptr.file_id,
+ field_list_parent: Either::Right(AstPtr::new(
+ &record_pat,
+ )),
+ field_list_parent_path: record_pat
+ .path()
+ .map(|path| AstPtr::new(&path)),
+ missed_fields,
+ }
+ .into(),
+ )
+ }
+ }
+ }
+ }
+ Err(SyntheticSyntax) => (),
+ },
+ }
+ }
+ BodyValidationDiagnostic::ReplaceFilterMapNextWithFindMap { method_call_expr } => {
+ if let Ok(next_source_ptr) = source_map.expr_syntax(method_call_expr) {
+ acc.push(
+ ReplaceFilterMapNextWithFindMap {
+ file: next_source_ptr.file_id,
+ next_expr: next_source_ptr.value,
+ }
+ .into(),
+ );
+ }
+ }
+ BodyValidationDiagnostic::MissingMatchArms { match_expr, uncovered_patterns } => {
+ match source_map.expr_syntax(match_expr) {
+ Ok(source_ptr) => {
+ let root = source_ptr.file_syntax(db.upcast());
+ if let ast::Expr::MatchExpr(match_expr) =
+ &source_ptr.value.to_node(&root)
+ {
+ if let Some(match_expr) = match_expr.expr() {
+ acc.push(
+ MissingMatchArms {
+ file: source_ptr.file_id,
+ match_expr: AstPtr::new(&match_expr),
+ uncovered_patterns,
+ }
+ .into(),
+ );
+ }
+ }
+ }
+ Err(SyntheticSyntax) => (),
+ }
+ }
+ }
+ }
+
+ let def: ModuleDef = match self {
+ DefWithBody::Function(it) => it.into(),
+ DefWithBody::Static(it) => it.into(),
+ DefWithBody::Const(it) => it.into(),
++ DefWithBody::Variant(it) => it.into(),
+ };
+ for diag in hir_ty::diagnostics::incorrect_case(db, krate, def.into()) {
+ acc.push(diag.into())
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct Function {
+ pub(crate) id: FunctionId,
+}
+
+impl Function {
+ pub fn module(self, db: &dyn HirDatabase) -> Module {
+ self.id.lookup(db.upcast()).module(db.upcast()).into()
+ }
+
+ pub fn name(self, db: &dyn HirDatabase) -> Name {
+ db.function_data(self.id).name.clone()
+ }
+
+ /// Get this function's return type
+ pub fn ret_type(self, db: &dyn HirDatabase) -> Type {
+ let resolver = self.id.resolver(db.upcast());
+ let substs = TyBuilder::placeholder_subst(db, self.id);
+ let callable_sig = db.callable_item_signature(self.id.into()).substitute(Interner, &substs);
+ let ty = callable_sig.ret().clone();
+ Type::new_with_resolver_inner(db, &resolver, ty)
+ }
+
+ pub fn async_ret_type(self, db: &dyn HirDatabase) -> Option<Type> {
+ if !self.is_async(db) {
+ return None;
+ }
+ let resolver = self.id.resolver(db.upcast());
+ let substs = TyBuilder::placeholder_subst(db, self.id);
+ let callable_sig = db.callable_item_signature(self.id.into()).substitute(Interner, &substs);
+ let ret_ty = callable_sig.ret().clone();
+ for pred in ret_ty.impl_trait_bounds(db).into_iter().flatten() {
+ if let WhereClause::AliasEq(output_eq) = pred.into_value_and_skipped_binders().0 {
+ return Type::new_with_resolver_inner(db, &resolver, output_eq.ty).into();
+ }
+ }
+ never!("Async fn ret_type should be impl Future");
+ None
+ }
+
+ pub fn has_self_param(self, db: &dyn HirDatabase) -> bool {
+ db.function_data(self.id).has_self_param()
+ }
+
+ pub fn self_param(self, db: &dyn HirDatabase) -> Option<SelfParam> {
+ self.has_self_param(db).then(|| SelfParam { func: self.id })
+ }
+
+ pub fn assoc_fn_params(self, db: &dyn HirDatabase) -> Vec<Param> {
+ let environment = db.trait_environment(self.id.into());
+ let substs = TyBuilder::placeholder_subst(db, self.id);
+ let callable_sig = db.callable_item_signature(self.id.into()).substitute(Interner, &substs);
+ callable_sig
+ .params()
+ .iter()
+ .enumerate()
+ .map(|(idx, ty)| {
+ let ty = Type { env: environment.clone(), ty: ty.clone() };
+ Param { func: self, ty, idx }
+ })
+ .collect()
+ }
+
+ pub fn method_params(self, db: &dyn HirDatabase) -> Option<Vec<Param>> {
+ if self.self_param(db).is_none() {
+ return None;
+ }
+ Some(self.params_without_self(db))
+ }
+
+ pub fn params_without_self(self, db: &dyn HirDatabase) -> Vec<Param> {
+ let environment = db.trait_environment(self.id.into());
+ let substs = TyBuilder::placeholder_subst(db, self.id);
+ let callable_sig = db.callable_item_signature(self.id.into()).substitute(Interner, &substs);
+ let skip = if db.function_data(self.id).has_self_param() { 1 } else { 0 };
+ callable_sig
+ .params()
+ .iter()
+ .enumerate()
+ .skip(skip)
+ .map(|(idx, ty)| {
+ let ty = Type { env: environment.clone(), ty: ty.clone() };
+ Param { func: self, ty, idx }
+ })
+ .collect()
+ }
+
+ pub fn is_const(self, db: &dyn HirDatabase) -> bool {
+ db.function_data(self.id).has_const_kw()
+ }
+
+ pub fn is_async(self, db: &dyn HirDatabase) -> bool {
+ db.function_data(self.id).has_async_kw()
+ }
+
+ pub fn is_unsafe_to_call(self, db: &dyn HirDatabase) -> bool {
+ hir_ty::is_fn_unsafe_to_call(db, self.id)
+ }
+
+ /// Whether this function declaration has a definition.
+ ///
+ /// This is false in the case of required (not provided) trait methods.
+ pub fn has_body(self, db: &dyn HirDatabase) -> bool {
+ db.function_data(self.id).has_body()
+ }
+
+ pub fn as_proc_macro(self, db: &dyn HirDatabase) -> Option<Macro> {
+ let function_data = db.function_data(self.id);
+ let attrs = &function_data.attrs;
+ // FIXME: Store this in FunctionData flags?
+ if !(attrs.is_proc_macro()
+ || attrs.is_proc_macro_attribute()
+ || attrs.is_proc_macro_derive())
+ {
+ return None;
+ }
+ let loc = self.id.lookup(db.upcast());
+ let def_map = db.crate_def_map(loc.krate(db).into());
+ def_map.fn_as_proc_macro(self.id).map(|id| Macro { id: id.into() })
+ }
+}
+
+// Note: logically, this belongs to `hir_ty`, but we are not using it there yet.
+#[derive(Clone, Copy, PartialEq, Eq)]
+pub enum Access {
+ Shared,
+ Exclusive,
+ Owned,
+}
+
+impl From<hir_ty::Mutability> for Access {
+ fn from(mutability: hir_ty::Mutability) -> Access {
+ match mutability {
+ hir_ty::Mutability::Not => Access::Shared,
+ hir_ty::Mutability::Mut => Access::Exclusive,
+ }
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct Param {
+ func: Function,
+ /// The index in parameter list, including self parameter.
+ idx: usize,
+ ty: Type,
+}
+
+impl Param {
+ pub fn ty(&self) -> &Type {
+ &self.ty
+ }
+
+ pub fn name(&self, db: &dyn HirDatabase) -> Option<Name> {
+ db.function_data(self.func.id).params[self.idx].0.clone()
+ }
+
+ pub fn as_local(&self, db: &dyn HirDatabase) -> Option<Local> {
+ let parent = DefWithBodyId::FunctionId(self.func.into());
+ let body = db.body(parent);
+ let pat_id = body.params[self.idx];
+ if let Pat::Bind { .. } = &body[pat_id] {
+ Some(Local { parent, pat_id: body.params[self.idx] })
+ } else {
+ None
+ }
+ }
+
+ pub fn pattern_source(&self, db: &dyn HirDatabase) -> Option<ast::Pat> {
+ self.source(db).and_then(|p| p.value.pat())
+ }
+
+ pub fn source(&self, db: &dyn HirDatabase) -> Option<InFile<ast::Param>> {
+ let InFile { file_id, value } = self.func.source(db)?;
+ let params = value.param_list()?;
+ if params.self_param().is_some() {
+ params.params().nth(self.idx.checked_sub(1)?)
+ } else {
+ params.params().nth(self.idx)
+ }
+ .map(|value| InFile { file_id, value })
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct SelfParam {
+ func: FunctionId,
+}
+
+impl SelfParam {
+ pub fn access(self, db: &dyn HirDatabase) -> Access {
+ let func_data = db.function_data(self.func);
+ func_data
+ .params
+ .first()
+ .map(|(_, param)| match &**param {
+ TypeRef::Reference(.., mutability) => match mutability {
+ hir_def::type_ref::Mutability::Shared => Access::Shared,
+ hir_def::type_ref::Mutability::Mut => Access::Exclusive,
+ },
+ _ => Access::Owned,
+ })
+ .unwrap_or(Access::Owned)
+ }
+
+ pub fn display(self, db: &dyn HirDatabase) -> &'static str {
+ match self.access(db) {
+ Access::Shared => "&self",
+ Access::Exclusive => "&mut self",
+ Access::Owned => "self",
+ }
+ }
+
+ pub fn source(&self, db: &dyn HirDatabase) -> Option<InFile<ast::SelfParam>> {
+ let InFile { file_id, value } = Function::from(self.func).source(db)?;
+ value
+ .param_list()
+ .and_then(|params| params.self_param())
+ .map(|value| InFile { file_id, value })
+ }
+
+ pub fn ty(&self, db: &dyn HirDatabase) -> Type {
+ let substs = TyBuilder::placeholder_subst(db, self.func);
+ let callable_sig =
+ db.callable_item_signature(self.func.into()).substitute(Interner, &substs);
+ let environment = db.trait_environment(self.func.into());
+ let ty = callable_sig.params()[0].clone();
+ Type { env: environment, ty }
+ }
+}
+
+impl HasVisibility for Function {
+ fn visibility(&self, db: &dyn HirDatabase) -> Visibility {
+ db.function_visibility(self.id)
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct Const {
+ pub(crate) id: ConstId,
+}
+
+impl Const {
+ pub fn module(self, db: &dyn HirDatabase) -> Module {
+ Module { id: self.id.lookup(db.upcast()).module(db.upcast()) }
+ }
+
+ pub fn name(self, db: &dyn HirDatabase) -> Option<Name> {
+ db.const_data(self.id).name.clone()
+ }
+
+ pub fn value(self, db: &dyn HirDatabase) -> Option<ast::Expr> {
+ self.source(db)?.value.body()
+ }
+
+ pub fn ty(self, db: &dyn HirDatabase) -> Type {
+ let data = db.const_data(self.id);
+ let resolver = self.id.resolver(db.upcast());
+ let ctx = hir_ty::TyLoweringContext::new(db, &resolver);
+ let ty = ctx.lower_ty(&data.type_ref);
+ Type::new_with_resolver_inner(db, &resolver, ty)
+ }
+
+ pub fn eval(self, db: &dyn HirDatabase) -> Result<ComputedExpr, ConstEvalError> {
+ db.const_eval(self.id)
+ }
+}
+
+impl HasVisibility for Const {
+ fn visibility(&self, db: &dyn HirDatabase) -> Visibility {
+ db.const_visibility(self.id)
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct Static {
+ pub(crate) id: StaticId,
+}
+
+impl Static {
+ pub fn module(self, db: &dyn HirDatabase) -> Module {
+ Module { id: self.id.lookup(db.upcast()).module(db.upcast()) }
+ }
+
+ pub fn name(self, db: &dyn HirDatabase) -> Name {
+ db.static_data(self.id).name.clone()
+ }
+
+ pub fn is_mut(self, db: &dyn HirDatabase) -> bool {
+ db.static_data(self.id).mutable
+ }
+
+ pub fn value(self, db: &dyn HirDatabase) -> Option<ast::Expr> {
+ self.source(db)?.value.body()
+ }
+
+ pub fn ty(self, db: &dyn HirDatabase) -> Type {
+ let data = db.static_data(self.id);
+ let resolver = self.id.resolver(db.upcast());
+ let ctx = hir_ty::TyLoweringContext::new(db, &resolver);
+ let ty = ctx.lower_ty(&data.type_ref);
+ Type::new_with_resolver_inner(db, &resolver, ty)
+ }
+}
+
+impl HasVisibility for Static {
+ fn visibility(&self, db: &dyn HirDatabase) -> Visibility {
+ db.static_data(self.id).visibility.resolve(db.upcast(), &self.id.resolver(db.upcast()))
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct Trait {
+ pub(crate) id: TraitId,
+}
+
+impl Trait {
+ pub fn lang(db: &dyn HirDatabase, krate: Crate, name: &Name) -> Option<Trait> {
+ db.lang_item(krate.into(), name.to_smol_str())
+ .and_then(LangItemTarget::as_trait)
+ .map(Into::into)
+ }
+
+ pub fn module(self, db: &dyn HirDatabase) -> Module {
+ Module { id: self.id.lookup(db.upcast()).container }
+ }
+
+ pub fn name(self, db: &dyn HirDatabase) -> Name {
+ db.trait_data(self.id).name.clone()
+ }
+
+ pub fn items(self, db: &dyn HirDatabase) -> Vec<AssocItem> {
+ db.trait_data(self.id).items.iter().map(|(_name, it)| (*it).into()).collect()
+ }
+
+ pub fn items_with_supertraits(self, db: &dyn HirDatabase) -> Vec<AssocItem> {
+ let traits = all_super_traits(db.upcast(), self.into());
+ traits.iter().flat_map(|tr| Trait::from(*tr).items(db)).collect()
+ }
+
+ pub fn is_auto(self, db: &dyn HirDatabase) -> bool {
+ db.trait_data(self.id).is_auto
+ }
+
+ pub fn is_unsafe(&self, db: &dyn HirDatabase) -> bool {
+ db.trait_data(self.id).is_unsafe
+ }
+
+ pub fn type_or_const_param_count(
+ &self,
+ db: &dyn HirDatabase,
+ count_required_only: bool,
+ ) -> usize {
+ db.generic_params(GenericDefId::from(self.id))
+ .type_or_consts
+ .iter()
+ .filter(|(_, ty)| match ty {
+ TypeOrConstParamData::TypeParamData(ty)
+ if ty.provenance != TypeParamProvenance::TypeParamList =>
+ {
+ false
+ }
+ _ => true,
+ })
+ .filter(|(_, ty)| !count_required_only || !ty.has_default())
+ .count()
+ }
+}
+
+impl HasVisibility for Trait {
+ fn visibility(&self, db: &dyn HirDatabase) -> Visibility {
+ db.trait_data(self.id).visibility.resolve(db.upcast(), &self.id.resolver(db.upcast()))
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct TypeAlias {
+ pub(crate) id: TypeAliasId,
+}
+
+impl TypeAlias {
+ pub fn has_non_default_type_params(self, db: &dyn HirDatabase) -> bool {
+ let subst = db.generic_defaults(self.id.into());
+ subst.iter().any(|ty| match ty.skip_binders().data(Interner) {
+ GenericArgData::Ty(x) => x.is_unknown(),
+ _ => false,
+ })
+ }
+
+ pub fn module(self, db: &dyn HirDatabase) -> Module {
+ Module { id: self.id.lookup(db.upcast()).module(db.upcast()) }
+ }
+
+ pub fn type_ref(self, db: &dyn HirDatabase) -> Option<TypeRef> {
+ db.type_alias_data(self.id).type_ref.as_deref().cloned()
+ }
+
+ pub fn ty(self, db: &dyn HirDatabase) -> Type {
+ Type::from_def(db, self.id)
+ }
+
+ pub fn name(self, db: &dyn HirDatabase) -> Name {
+ db.type_alias_data(self.id).name.clone()
+ }
+}
+
+impl HasVisibility for TypeAlias {
+ fn visibility(&self, db: &dyn HirDatabase) -> Visibility {
+ let function_data = db.type_alias_data(self.id);
+ let visibility = &function_data.visibility;
+ visibility.resolve(db.upcast(), &self.id.resolver(db.upcast()))
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct BuiltinType {
+ pub(crate) inner: hir_def::builtin_type::BuiltinType,
+}
+
+impl BuiltinType {
+ pub fn str() -> BuiltinType {
+ BuiltinType { inner: hir_def::builtin_type::BuiltinType::Str }
+ }
+
+ pub fn ty(self, db: &dyn HirDatabase) -> Type {
+ Type::new_for_crate(db.crate_graph().iter().next().unwrap(), TyBuilder::builtin(self.inner))
+ }
+
+ pub fn name(self) -> Name {
+ self.inner.as_name()
+ }
+
+ pub fn is_int(&self) -> bool {
+ matches!(self.inner, hir_def::builtin_type::BuiltinType::Int(_))
+ }
+
+ pub fn is_uint(&self) -> bool {
+ matches!(self.inner, hir_def::builtin_type::BuiltinType::Uint(_))
+ }
+
+ pub fn is_float(&self) -> bool {
+ matches!(self.inner, hir_def::builtin_type::BuiltinType::Float(_))
+ }
+
+ pub fn is_char(&self) -> bool {
+ matches!(self.inner, hir_def::builtin_type::BuiltinType::Char)
+ }
+
+ pub fn is_bool(&self) -> bool {
+ matches!(self.inner, hir_def::builtin_type::BuiltinType::Bool)
+ }
+
+ pub fn is_str(&self) -> bool {
+ matches!(self.inner, hir_def::builtin_type::BuiltinType::Str)
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum MacroKind {
+ /// `macro_rules!` or Macros 2.0 macro.
+ Declarative,
+ /// A built-in or custom derive.
+ Derive,
+ /// A built-in function-like macro.
+ BuiltIn,
+ /// A procedural attribute macro.
+ Attr,
+ /// A function-like procedural macro.
+ ProcMacro,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct Macro {
+ pub(crate) id: MacroId,
+}
+
+impl Macro {
+ pub fn module(self, db: &dyn HirDatabase) -> Module {
+ Module { id: self.id.module(db.upcast()) }
+ }
+
+ pub fn name(self, db: &dyn HirDatabase) -> Name {
+ match self.id {
+ MacroId::Macro2Id(id) => db.macro2_data(id).name.clone(),
+ MacroId::MacroRulesId(id) => db.macro_rules_data(id).name.clone(),
+ MacroId::ProcMacroId(id) => db.proc_macro_data(id).name.clone(),
+ }
+ }
+
+ pub fn is_macro_export(self, db: &dyn HirDatabase) -> bool {
+ matches!(self.id, MacroId::MacroRulesId(id) if db.macro_rules_data(id).macro_export)
+ }
+
+ pub fn kind(&self, db: &dyn HirDatabase) -> MacroKind {
+ match self.id {
+ MacroId::Macro2Id(it) => match it.lookup(db.upcast()).expander {
+ MacroExpander::Declarative => MacroKind::Declarative,
+ MacroExpander::BuiltIn(_) | MacroExpander::BuiltInEager(_) => MacroKind::BuiltIn,
+ MacroExpander::BuiltInAttr(_) => MacroKind::Attr,
+ MacroExpander::BuiltInDerive(_) => MacroKind::Derive,
+ },
+ MacroId::MacroRulesId(it) => match it.lookup(db.upcast()).expander {
+ MacroExpander::Declarative => MacroKind::Declarative,
+ MacroExpander::BuiltIn(_) | MacroExpander::BuiltInEager(_) => MacroKind::BuiltIn,
+ MacroExpander::BuiltInAttr(_) => MacroKind::Attr,
+ MacroExpander::BuiltInDerive(_) => MacroKind::Derive,
+ },
+ MacroId::ProcMacroId(it) => match it.lookup(db.upcast()).kind {
+ ProcMacroKind::CustomDerive => MacroKind::Derive,
+ ProcMacroKind::FuncLike => MacroKind::ProcMacro,
+ ProcMacroKind::Attr => MacroKind::Attr,
+ },
+ }
+ }
+
+ pub fn is_fn_like(&self, db: &dyn HirDatabase) -> bool {
+ match self.kind(db) {
+ MacroKind::Declarative | MacroKind::BuiltIn | MacroKind::ProcMacro => true,
+ MacroKind::Attr | MacroKind::Derive => false,
+ }
+ }
+
+ pub fn is_builtin_derive(&self, db: &dyn HirDatabase) -> bool {
+ match self.id {
+ MacroId::Macro2Id(it) => {
+ matches!(it.lookup(db.upcast()).expander, MacroExpander::BuiltInDerive(_))
+ }
+ MacroId::MacroRulesId(it) => {
+ matches!(it.lookup(db.upcast()).expander, MacroExpander::BuiltInDerive(_))
+ }
+ MacroId::ProcMacroId(_) => false,
+ }
+ }
+
+ pub fn is_attr(&self, db: &dyn HirDatabase) -> bool {
+ matches!(self.kind(db), MacroKind::Attr)
+ }
+
+ pub fn is_derive(&self, db: &dyn HirDatabase) -> bool {
+ matches!(self.kind(db), MacroKind::Derive)
+ }
+}
+
+impl HasVisibility for Macro {
+ fn visibility(&self, db: &dyn HirDatabase) -> Visibility {
+ match self.id {
+ MacroId::Macro2Id(id) => {
+ let data = db.macro2_data(id);
+ let visibility = &data.visibility;
+ visibility.resolve(db.upcast(), &self.id.resolver(db.upcast()))
+ }
+ MacroId::MacroRulesId(_) => Visibility::Public,
+ MacroId::ProcMacroId(_) => Visibility::Public,
+ }
+ }
+}
+
+#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
+pub enum ItemInNs {
+ Types(ModuleDef),
+ Values(ModuleDef),
+ Macros(Macro),
+}
+
+impl From<Macro> for ItemInNs {
+ fn from(it: Macro) -> Self {
+ Self::Macros(it)
+ }
+}
+
+impl From<ModuleDef> for ItemInNs {
+ fn from(module_def: ModuleDef) -> Self {
+ match module_def {
+ ModuleDef::Static(_) | ModuleDef::Const(_) | ModuleDef::Function(_) => {
+ ItemInNs::Values(module_def)
+ }
+ _ => ItemInNs::Types(module_def),
+ }
+ }
+}
+
+impl ItemInNs {
+ pub fn as_module_def(self) -> Option<ModuleDef> {
+ match self {
+ ItemInNs::Types(id) | ItemInNs::Values(id) => Some(id),
+ ItemInNs::Macros(_) => None,
+ }
+ }
+
+ /// Returns the crate defining this item (or `None` if `self` is built-in).
+ pub fn krate(&self, db: &dyn HirDatabase) -> Option<Crate> {
+ match self {
+ ItemInNs::Types(did) | ItemInNs::Values(did) => did.module(db).map(|m| m.krate()),
+ ItemInNs::Macros(id) => Some(id.module(db).krate()),
+ }
+ }
+
+ pub fn attrs(&self, db: &dyn HirDatabase) -> Option<AttrsWithOwner> {
+ match self {
+ ItemInNs::Types(it) | ItemInNs::Values(it) => it.attrs(db),
+ ItemInNs::Macros(it) => Some(it.attrs(db)),
+ }
+ }
+}
+
+/// Invariant: `inner.as_assoc_item(db).is_some()`
+/// We do not actively enforce this invariant.
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+pub enum AssocItem {
+ Function(Function),
+ Const(Const),
+ TypeAlias(TypeAlias),
+}
+#[derive(Debug)]
+pub enum AssocItemContainer {
+ Trait(Trait),
+ Impl(Impl),
+}
+pub trait AsAssocItem {
+ fn as_assoc_item(self, db: &dyn HirDatabase) -> Option<AssocItem>;
+}
+
+impl AsAssocItem for Function {
+ fn as_assoc_item(self, db: &dyn HirDatabase) -> Option<AssocItem> {
+ as_assoc_item(db, AssocItem::Function, self.id)
+ }
+}
+impl AsAssocItem for Const {
+ fn as_assoc_item(self, db: &dyn HirDatabase) -> Option<AssocItem> {
+ as_assoc_item(db, AssocItem::Const, self.id)
+ }
+}
+impl AsAssocItem for TypeAlias {
+ fn as_assoc_item(self, db: &dyn HirDatabase) -> Option<AssocItem> {
+ as_assoc_item(db, AssocItem::TypeAlias, self.id)
+ }
+}
+impl AsAssocItem for ModuleDef {
+ fn as_assoc_item(self, db: &dyn HirDatabase) -> Option<AssocItem> {
+ match self {
+ ModuleDef::Function(it) => it.as_assoc_item(db),
+ ModuleDef::Const(it) => it.as_assoc_item(db),
+ ModuleDef::TypeAlias(it) => it.as_assoc_item(db),
+ _ => None,
+ }
+ }
+}
+fn as_assoc_item<ID, DEF, CTOR, AST>(db: &dyn HirDatabase, ctor: CTOR, id: ID) -> Option<AssocItem>
+where
+ ID: Lookup<Data = AssocItemLoc<AST>>,
+ DEF: From<ID>,
+ CTOR: FnOnce(DEF) -> AssocItem,
+ AST: ItemTreeNode,
+{
+ match id.lookup(db.upcast()).container {
+ ItemContainerId::TraitId(_) | ItemContainerId::ImplId(_) => Some(ctor(DEF::from(id))),
+ ItemContainerId::ModuleId(_) | ItemContainerId::ExternBlockId(_) => None,
+ }
+}
+
+impl AssocItem {
+ pub fn name(self, db: &dyn HirDatabase) -> Option<Name> {
+ match self {
+ AssocItem::Function(it) => Some(it.name(db)),
+ AssocItem::Const(it) => it.name(db),
+ AssocItem::TypeAlias(it) => Some(it.name(db)),
+ }
+ }
+ pub fn module(self, db: &dyn HirDatabase) -> Module {
+ match self {
+ AssocItem::Function(f) => f.module(db),
+ AssocItem::Const(c) => c.module(db),
+ AssocItem::TypeAlias(t) => t.module(db),
+ }
+ }
+ pub fn container(self, db: &dyn HirDatabase) -> AssocItemContainer {
+ let container = match self {
+ AssocItem::Function(it) => it.id.lookup(db.upcast()).container,
+ AssocItem::Const(it) => it.id.lookup(db.upcast()).container,
+ AssocItem::TypeAlias(it) => it.id.lookup(db.upcast()).container,
+ };
+ match container {
+ ItemContainerId::TraitId(id) => AssocItemContainer::Trait(id.into()),
+ ItemContainerId::ImplId(id) => AssocItemContainer::Impl(id.into()),
+ ItemContainerId::ModuleId(_) | ItemContainerId::ExternBlockId(_) => {
+ panic!("invalid AssocItem")
+ }
+ }
+ }
+
+ pub fn containing_trait(self, db: &dyn HirDatabase) -> Option<Trait> {
+ match self.container(db) {
+ AssocItemContainer::Trait(t) => Some(t),
+ _ => None,
+ }
+ }
+
+ pub fn containing_trait_impl(self, db: &dyn HirDatabase) -> Option<Trait> {
+ match self.container(db) {
+ AssocItemContainer::Impl(i) => i.trait_(db),
+ _ => None,
+ }
+ }
+
+ pub fn containing_trait_or_trait_impl(self, db: &dyn HirDatabase) -> Option<Trait> {
+ match self.container(db) {
+ AssocItemContainer::Trait(t) => Some(t),
+ AssocItemContainer::Impl(i) => i.trait_(db),
+ }
+ }
+}
+
+impl HasVisibility for AssocItem {
+ fn visibility(&self, db: &dyn HirDatabase) -> Visibility {
+ match self {
+ AssocItem::Function(f) => f.visibility(db),
+ AssocItem::Const(c) => c.visibility(db),
+ AssocItem::TypeAlias(t) => t.visibility(db),
+ }
+ }
+}
+
+impl From<AssocItem> for ModuleDef {
+ fn from(assoc: AssocItem) -> Self {
+ match assoc {
+ AssocItem::Function(it) => ModuleDef::Function(it),
+ AssocItem::Const(it) => ModuleDef::Const(it),
+ AssocItem::TypeAlias(it) => ModuleDef::TypeAlias(it),
+ }
+ }
+}
+
+#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
+pub enum GenericDef {
+ Function(Function),
+ Adt(Adt),
+ Trait(Trait),
+ TypeAlias(TypeAlias),
+ Impl(Impl),
+ // enum variants cannot have generics themselves, but their parent enums
+ // can, and this makes some code easier to write
+ Variant(Variant),
+ // consts can have type parameters from their parents (i.e. associated consts of traits)
+ Const(Const),
+}
+impl_from!(
+ Function,
+ Adt(Struct, Enum, Union),
+ Trait,
+ TypeAlias,
+ Impl,
+ Variant,
+ Const
+ for GenericDef
+);
+
+impl GenericDef {
+ pub fn params(self, db: &dyn HirDatabase) -> Vec<GenericParam> {
+ let generics = db.generic_params(self.into());
+ let ty_params = generics.type_or_consts.iter().map(|(local_id, _)| {
+ let toc = TypeOrConstParam { id: TypeOrConstParamId { parent: self.into(), local_id } };
+ match toc.split(db) {
+ Either::Left(x) => GenericParam::ConstParam(x),
+ Either::Right(x) => GenericParam::TypeParam(x),
+ }
+ });
+ let lt_params = generics
+ .lifetimes
+ .iter()
+ .map(|(local_id, _)| LifetimeParam {
+ id: LifetimeParamId { parent: self.into(), local_id },
+ })
+ .map(GenericParam::LifetimeParam);
+ lt_params.chain(ty_params).collect()
+ }
+
+ pub fn type_params(self, db: &dyn HirDatabase) -> Vec<TypeOrConstParam> {
+ let generics = db.generic_params(self.into());
+ generics
+ .type_or_consts
+ .iter()
+ .map(|(local_id, _)| TypeOrConstParam {
+ id: TypeOrConstParamId { parent: self.into(), local_id },
+ })
+ .collect()
+ }
+}
+
+/// A single local definition.
+///
+/// If the definition of this is part of a "MultiLocal", that is a local that has multiple declarations due to or-patterns
+/// then this only references a single one of those.
+/// To retrieve the other locals you should use [`Local::associated_locals`]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub struct Local {
+ pub(crate) parent: DefWithBodyId,
+ pub(crate) pat_id: PatId,
+}
+
+impl Local {
+ pub fn is_param(self, db: &dyn HirDatabase) -> bool {
+ let src = self.source(db);
+ match src.value {
+ Either::Left(pat) => pat
+ .syntax()
+ .ancestors()
+ .map(|it| it.kind())
+ .take_while(|&kind| ast::Pat::can_cast(kind) || ast::Param::can_cast(kind))
+ .any(ast::Param::can_cast),
+ Either::Right(_) => true,
+ }
+ }
+
+ pub fn as_self_param(self, db: &dyn HirDatabase) -> Option<SelfParam> {
+ match self.parent {
+ DefWithBodyId::FunctionId(func) if self.is_self(db) => Some(SelfParam { func }),
+ _ => None,
+ }
+ }
+
+ pub fn name(self, db: &dyn HirDatabase) -> Name {
+ let body = db.body(self.parent);
+ match &body[self.pat_id] {
+ Pat::Bind { name, .. } => name.clone(),
+ _ => {
+ stdx::never!("hir::Local is missing a name!");
+ Name::missing()
+ }
+ }
+ }
+
+ pub fn is_self(self, db: &dyn HirDatabase) -> bool {
+ self.name(db) == name![self]
+ }
+
+ pub fn is_mut(self, db: &dyn HirDatabase) -> bool {
+ let body = db.body(self.parent);
+ matches!(&body[self.pat_id], Pat::Bind { mode: BindingAnnotation::Mutable, .. })
+ }
+
+ pub fn is_ref(self, db: &dyn HirDatabase) -> bool {
+ let body = db.body(self.parent);
+ matches!(
+ &body[self.pat_id],
+ Pat::Bind { mode: BindingAnnotation::Ref | BindingAnnotation::RefMut, .. }
+ )
+ }
+
+ pub fn parent(self, _db: &dyn HirDatabase) -> DefWithBody {
+ self.parent.into()
+ }
+
+ pub fn module(self, db: &dyn HirDatabase) -> Module {
+ self.parent(db).module(db)
+ }
+
+ pub fn ty(self, db: &dyn HirDatabase) -> Type {
+ let def = self.parent;
+ let infer = db.infer(def);
+ let ty = infer[self.pat_id].clone();
+ Type::new(db, def, ty)
+ }
+
+ pub fn associated_locals(self, db: &dyn HirDatabase) -> Box<[Local]> {
+ let body = db.body(self.parent);
+ body.ident_patterns_for(&self.pat_id)
+ .iter()
+ .map(|&pat_id| Local { parent: self.parent, pat_id })
+ .collect()
+ }
+
+ /// If this local is part of a multi-local, retrieve the representative local.
+ /// That is the local that references are being resolved to.
+ pub fn representative(self, db: &dyn HirDatabase) -> Local {
+ let body = db.body(self.parent);
+ Local { pat_id: body.pattern_representative(self.pat_id), ..self }
+ }
+
+ pub fn source(self, db: &dyn HirDatabase) -> InFile<Either<ast::IdentPat, ast::SelfParam>> {
+ let (_body, source_map) = db.body_with_source_map(self.parent);
+ let src = source_map.pat_syntax(self.pat_id).unwrap(); // Hmm...
+ let root = src.file_syntax(db.upcast());
+ src.map(|ast| match ast {
+ // Suspicious unwrap
+ Either::Left(it) => Either::Left(it.cast().unwrap().to_node(&root)),
+ Either::Right(it) => Either::Right(it.to_node(&root)),
+ })
+ }
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub struct DeriveHelper {
+ pub(crate) derive: MacroId,
+ pub(crate) idx: usize,
+}
+
+impl DeriveHelper {
+ pub fn derive(&self) -> Macro {
+ Macro { id: self.derive.into() }
+ }
+
+ pub fn name(&self, db: &dyn HirDatabase) -> Name {
+ match self.derive {
+ MacroId::Macro2Id(_) => None,
+ MacroId::MacroRulesId(_) => None,
+ MacroId::ProcMacroId(proc_macro) => db
+ .proc_macro_data(proc_macro)
+ .helpers
+ .as_ref()
+ .and_then(|it| it.get(self.idx))
+ .cloned(),
+ }
+ .unwrap_or_else(|| Name::missing())
+ }
+}
+
+// FIXME: Wrong name? This is could also be a registered attribute
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub struct BuiltinAttr {
+ krate: Option<CrateId>,
+ idx: usize,
+}
+
+impl BuiltinAttr {
+ // FIXME: consider crates\hir_def\src\nameres\attr_resolution.rs?
+ pub(crate) fn by_name(db: &dyn HirDatabase, krate: Crate, name: &str) -> Option<Self> {
+ if let builtin @ Some(_) = Self::builtin(name) {
+ return builtin;
+ }
+ let idx = db.crate_def_map(krate.id).registered_attrs().iter().position(|it| it == name)?;
+ Some(BuiltinAttr { krate: Some(krate.id), idx })
+ }
+
+ fn builtin(name: &str) -> Option<Self> {
+ hir_def::builtin_attr::INERT_ATTRIBUTES
+ .iter()
+ .position(|tool| tool.name == name)
+ .map(|idx| BuiltinAttr { krate: None, idx })
+ }
+
+ pub fn name(&self, db: &dyn HirDatabase) -> SmolStr {
+ // FIXME: Return a `Name` here
+ match self.krate {
+ Some(krate) => db.crate_def_map(krate).registered_attrs()[self.idx].clone(),
+ None => SmolStr::new(hir_def::builtin_attr::INERT_ATTRIBUTES[self.idx].name),
+ }
+ }
+
+ pub fn template(&self, _: &dyn HirDatabase) -> Option<AttributeTemplate> {
+ match self.krate {
+ Some(_) => None,
+ None => Some(hir_def::builtin_attr::INERT_ATTRIBUTES[self.idx].template),
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub struct ToolModule {
+ krate: Option<CrateId>,
+ idx: usize,
+}
+
+impl ToolModule {
+ // FIXME: consider crates\hir_def\src\nameres\attr_resolution.rs?
+ pub(crate) fn by_name(db: &dyn HirDatabase, krate: Crate, name: &str) -> Option<Self> {
+ if let builtin @ Some(_) = Self::builtin(name) {
+ return builtin;
+ }
+ let idx = db.crate_def_map(krate.id).registered_tools().iter().position(|it| it == name)?;
+ Some(ToolModule { krate: Some(krate.id), idx })
+ }
+
+ fn builtin(name: &str) -> Option<Self> {
+ hir_def::builtin_attr::TOOL_MODULES
+ .iter()
+ .position(|&tool| tool == name)
+ .map(|idx| ToolModule { krate: None, idx })
+ }
+
+ pub fn name(&self, db: &dyn HirDatabase) -> SmolStr {
+ // FIXME: Return a `Name` here
+ match self.krate {
+ Some(krate) => db.crate_def_map(krate).registered_tools()[self.idx].clone(),
+ None => SmolStr::new(hir_def::builtin_attr::TOOL_MODULES[self.idx]),
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub struct Label {
+ pub(crate) parent: DefWithBodyId,
+ pub(crate) label_id: LabelId,
+}
+
+impl Label {
+ pub fn module(self, db: &dyn HirDatabase) -> Module {
+ self.parent(db).module(db)
+ }
+
+ pub fn parent(self, _db: &dyn HirDatabase) -> DefWithBody {
+ self.parent.into()
+ }
+
+ pub fn name(self, db: &dyn HirDatabase) -> Name {
+ let body = db.body(self.parent);
+ body[self.label_id].name.clone()
+ }
+
+ pub fn source(self, db: &dyn HirDatabase) -> InFile<ast::Label> {
+ let (_body, source_map) = db.body_with_source_map(self.parent);
+ let src = source_map.label_syntax(self.label_id);
+ let root = src.file_syntax(db.upcast());
+ src.map(|ast| ast.to_node(&root))
+ }
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub enum GenericParam {
+ TypeParam(TypeParam),
+ ConstParam(ConstParam),
+ LifetimeParam(LifetimeParam),
+}
+impl_from!(TypeParam, ConstParam, LifetimeParam for GenericParam);
+
+impl GenericParam {
+ pub fn module(self, db: &dyn HirDatabase) -> Module {
+ match self {
+ GenericParam::TypeParam(it) => it.module(db),
+ GenericParam::ConstParam(it) => it.module(db),
+ GenericParam::LifetimeParam(it) => it.module(db),
+ }
+ }
+
+ pub fn name(self, db: &dyn HirDatabase) -> Name {
+ match self {
+ GenericParam::TypeParam(it) => it.name(db),
+ GenericParam::ConstParam(it) => it.name(db),
+ GenericParam::LifetimeParam(it) => it.name(db),
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub struct TypeParam {
+ pub(crate) id: TypeParamId,
+}
+
+impl TypeParam {
+ pub fn merge(self) -> TypeOrConstParam {
+ TypeOrConstParam { id: self.id.into() }
+ }
+
+ pub fn name(self, db: &dyn HirDatabase) -> Name {
+ self.merge().name(db)
+ }
+
+ pub fn module(self, db: &dyn HirDatabase) -> Module {
+ self.id.parent().module(db.upcast()).into()
+ }
+
+ /// Is this type parameter implicitly introduced (eg. `Self` in a trait or an `impl Trait`
+ /// argument)?
+ pub fn is_implicit(self, db: &dyn HirDatabase) -> bool {
+ let params = db.generic_params(self.id.parent());
+ let data = ¶ms.type_or_consts[self.id.local_id()];
+ match data.type_param().unwrap().provenance {
+ hir_def::generics::TypeParamProvenance::TypeParamList => false,
+ hir_def::generics::TypeParamProvenance::TraitSelf
+ | hir_def::generics::TypeParamProvenance::ArgumentImplTrait => true,
+ }
+ }
+
+ pub fn ty(self, db: &dyn HirDatabase) -> Type {
+ let resolver = self.id.parent().resolver(db.upcast());
+ let ty =
+ TyKind::Placeholder(hir_ty::to_placeholder_idx(db, self.id.into())).intern(Interner);
+ Type::new_with_resolver_inner(db, &resolver, ty)
+ }
+
+ /// FIXME: this only lists trait bounds from the item defining the type
+ /// parameter, not additional bounds that might be added e.g. by a method if
+ /// the parameter comes from an impl!
+ pub fn trait_bounds(self, db: &dyn HirDatabase) -> Vec<Trait> {
+ db.generic_predicates_for_param(self.id.parent(), self.id.into(), None)
+ .iter()
+ .filter_map(|pred| match &pred.skip_binders().skip_binders() {
+ hir_ty::WhereClause::Implemented(trait_ref) => {
+ Some(Trait::from(trait_ref.hir_trait_id()))
+ }
+ _ => None,
+ })
+ .collect()
+ }
+
+ pub fn default(self, db: &dyn HirDatabase) -> Option<Type> {
+ let params = db.generic_defaults(self.id.parent());
+ let local_idx = hir_ty::param_idx(db, self.id.into())?;
+ let resolver = self.id.parent().resolver(db.upcast());
+ let ty = params.get(local_idx)?.clone();
+ let subst = TyBuilder::placeholder_subst(db, self.id.parent());
- let ty = TyBuilder::def_ty(db, def.into()).fill_with_unknown().build();
++ let ty = ty.substitute(Interner, &subst);
+ match ty.data(Interner) {
+ GenericArgData::Ty(x) => Some(Type::new_with_resolver_inner(db, &resolver, x.clone())),
+ _ => None,
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub struct LifetimeParam {
+ pub(crate) id: LifetimeParamId,
+}
+
+impl LifetimeParam {
+ pub fn name(self, db: &dyn HirDatabase) -> Name {
+ let params = db.generic_params(self.id.parent);
+ params.lifetimes[self.id.local_id].name.clone()
+ }
+
+ pub fn module(self, db: &dyn HirDatabase) -> Module {
+ self.id.parent.module(db.upcast()).into()
+ }
+
+ pub fn parent(self, _db: &dyn HirDatabase) -> GenericDef {
+ self.id.parent.into()
+ }
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub struct ConstParam {
+ pub(crate) id: ConstParamId,
+}
+
+impl ConstParam {
+ pub fn merge(self) -> TypeOrConstParam {
+ TypeOrConstParam { id: self.id.into() }
+ }
+
+ pub fn name(self, db: &dyn HirDatabase) -> Name {
+ let params = db.generic_params(self.id.parent());
+ match params.type_or_consts[self.id.local_id()].name() {
+ Some(x) => x.clone(),
+ None => {
+ never!();
+ Name::missing()
+ }
+ }
+ }
+
+ pub fn module(self, db: &dyn HirDatabase) -> Module {
+ self.id.parent().module(db.upcast()).into()
+ }
+
+ pub fn parent(self, _db: &dyn HirDatabase) -> GenericDef {
+ self.id.parent().into()
+ }
+
+ pub fn ty(self, db: &dyn HirDatabase) -> Type {
+ Type::new(db, self.id.parent(), db.const_param_ty(self.id))
+ }
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub struct TypeOrConstParam {
+ pub(crate) id: TypeOrConstParamId,
+}
+
+impl TypeOrConstParam {
+ pub fn name(self, db: &dyn HirDatabase) -> Name {
+ let params = db.generic_params(self.id.parent);
+ match params.type_or_consts[self.id.local_id].name() {
+ Some(n) => n.clone(),
+ _ => Name::missing(),
+ }
+ }
+
+ pub fn module(self, db: &dyn HirDatabase) -> Module {
+ self.id.parent.module(db.upcast()).into()
+ }
+
+ pub fn parent(self, _db: &dyn HirDatabase) -> GenericDef {
+ self.id.parent.into()
+ }
+
+ pub fn split(self, db: &dyn HirDatabase) -> Either<ConstParam, TypeParam> {
+ let params = db.generic_params(self.id.parent);
+ match ¶ms.type_or_consts[self.id.local_id] {
+ hir_def::generics::TypeOrConstParamData::TypeParamData(_) => {
+ Either::Right(TypeParam { id: TypeParamId::from_unchecked(self.id) })
+ }
+ hir_def::generics::TypeOrConstParamData::ConstParamData(_) => {
+ Either::Left(ConstParam { id: ConstParamId::from_unchecked(self.id) })
+ }
+ }
+ }
+
+ pub fn ty(self, db: &dyn HirDatabase) -> Type {
+ match self.split(db) {
+ Either::Left(x) => x.ty(db),
+ Either::Right(x) => x.ty(db),
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct Impl {
+ pub(crate) id: ImplId,
+}
+
+impl Impl {
+ pub fn all_in_crate(db: &dyn HirDatabase, krate: Crate) -> Vec<Impl> {
+ let inherent = db.inherent_impls_in_crate(krate.id);
+ let trait_ = db.trait_impls_in_crate(krate.id);
+
+ inherent.all_impls().chain(trait_.all_impls()).map(Self::from).collect()
+ }
+
+ pub fn all_for_type(db: &dyn HirDatabase, Type { ty, env }: Type) -> Vec<Impl> {
+ let def_crates = match method_resolution::def_crates(db, &ty, env.krate) {
+ Some(def_crates) => def_crates,
+ None => return Vec::new(),
+ };
+
+ let filter = |impl_def: &Impl| {
+ let self_ty = impl_def.self_ty(db);
+ let rref = self_ty.remove_ref();
+ ty.equals_ctor(rref.as_ref().map_or(&self_ty.ty, |it| &it.ty))
+ };
+
+ let fp = TyFingerprint::for_inherent_impl(&ty);
+ let fp = match fp {
+ Some(fp) => fp,
+ None => return Vec::new(),
+ };
+
+ let mut all = Vec::new();
+ def_crates.iter().for_each(|&id| {
+ all.extend(
+ db.inherent_impls_in_crate(id)
+ .for_self_ty(&ty)
+ .iter()
+ .cloned()
+ .map(Self::from)
+ .filter(filter),
+ )
+ });
+ for id in def_crates
+ .iter()
+ .flat_map(|&id| Crate { id }.transitive_reverse_dependencies(db))
+ .map(|Crate { id }| id)
+ .chain(def_crates.iter().copied())
+ .unique()
+ {
+ all.extend(
+ db.trait_impls_in_crate(id)
+ .for_self_ty_without_blanket_impls(fp)
+ .map(Self::from)
+ .filter(filter),
+ );
+ }
+ all
+ }
+
+ pub fn all_for_trait(db: &dyn HirDatabase, trait_: Trait) -> Vec<Impl> {
+ let krate = trait_.module(db).krate();
+ let mut all = Vec::new();
+ for Crate { id } in krate.transitive_reverse_dependencies(db).into_iter() {
+ let impls = db.trait_impls_in_crate(id);
+ all.extend(impls.for_trait(trait_.id).map(Self::from))
+ }
+ all
+ }
+
+ // FIXME: the return type is wrong. This should be a hir version of
+ // `TraitRef` (to account for parameters and qualifiers)
+ pub fn trait_(self, db: &dyn HirDatabase) -> Option<Trait> {
+ let trait_ref = db.impl_trait(self.id)?.skip_binders().clone();
+ let id = hir_ty::from_chalk_trait_id(trait_ref.trait_id);
+ Some(Trait { id })
+ }
+
+ pub fn self_ty(self, db: &dyn HirDatabase) -> Type {
+ let resolver = self.id.resolver(db.upcast());
+ let substs = TyBuilder::placeholder_subst(db, self.id);
+ let ty = db.impl_self_ty(self.id).substitute(Interner, &substs);
+ Type::new_with_resolver_inner(db, &resolver, ty)
+ }
+
+ pub fn items(self, db: &dyn HirDatabase) -> Vec<AssocItem> {
+ db.impl_data(self.id).items.iter().map(|it| (*it).into()).collect()
+ }
+
+ pub fn is_negative(self, db: &dyn HirDatabase) -> bool {
+ db.impl_data(self.id).is_negative
+ }
+
+ pub fn module(self, db: &dyn HirDatabase) -> Module {
+ self.id.lookup(db.upcast()).container.into()
+ }
+
+ pub fn is_builtin_derive(self, db: &dyn HirDatabase) -> Option<InFile<ast::Attr>> {
+ let src = self.source(db)?;
+ src.file_id.is_builtin_derive(db.upcast())
+ }
+}
+
+#[derive(Clone, PartialEq, Eq, Debug)]
+pub struct Type {
+ env: Arc<TraitEnvironment>,
+ ty: Ty,
+}
+
+impl Type {
+ pub(crate) fn new_with_resolver(db: &dyn HirDatabase, resolver: &Resolver, ty: Ty) -> Type {
+ Type::new_with_resolver_inner(db, resolver, ty)
+ }
+
+ pub(crate) fn new_with_resolver_inner(
+ db: &dyn HirDatabase,
+ resolver: &Resolver,
+ ty: Ty,
+ ) -> Type {
+ let environment = resolver.generic_def().map_or_else(
+ || Arc::new(TraitEnvironment::empty(resolver.krate())),
+ |d| db.trait_environment(d),
+ );
+ Type { env: environment, ty }
+ }
+
+ pub(crate) fn new_for_crate(krate: CrateId, ty: Ty) -> Type {
+ Type { env: Arc::new(TraitEnvironment::empty(krate)), ty }
+ }
+
+ pub fn reference(inner: &Type, m: Mutability) -> Type {
+ inner.derived(
+ TyKind::Ref(
+ if m.is_mut() { hir_ty::Mutability::Mut } else { hir_ty::Mutability::Not },
+ hir_ty::static_lifetime(),
+ inner.ty.clone(),
+ )
+ .intern(Interner),
+ )
+ }
+
+ fn new(db: &dyn HirDatabase, lexical_env: impl HasResolver, ty: Ty) -> Type {
+ let resolver = lexical_env.resolver(db.upcast());
+ let environment = resolver.generic_def().map_or_else(
+ || Arc::new(TraitEnvironment::empty(resolver.krate())),
+ |d| db.trait_environment(d),
+ );
+ Type { env: environment, ty }
+ }
+
+ fn from_def(db: &dyn HirDatabase, def: impl HasResolver + Into<TyDefId>) -> Type {
- let projection = TyBuilder::assoc_type_projection(db, alias.id)
++ let ty_def = def.into();
++ let parent_subst = match ty_def {
++ TyDefId::TypeAliasId(id) => match id.lookup(db.upcast()).container {
++ ItemContainerId::TraitId(id) => {
++ let subst = TyBuilder::subst_for_def(db, id, None).fill_with_unknown().build();
++ Some(subst)
++ }
++ ItemContainerId::ImplId(id) => {
++ let subst = TyBuilder::subst_for_def(db, id, None).fill_with_unknown().build();
++ Some(subst)
++ }
++ _ => None,
++ },
++ _ => None,
++ };
++ let ty = TyBuilder::def_ty(db, ty_def, parent_subst).fill_with_unknown().build();
+ Type::new(db, def, ty)
+ }
+
+ pub fn new_slice(ty: Type) -> Type {
+ Type { env: ty.env, ty: TyBuilder::slice(ty.ty) }
+ }
+
+ pub fn is_unit(&self) -> bool {
+ matches!(self.ty.kind(Interner), TyKind::Tuple(0, ..))
+ }
+
+ pub fn is_bool(&self) -> bool {
+ matches!(self.ty.kind(Interner), TyKind::Scalar(Scalar::Bool))
+ }
+
+ pub fn is_never(&self) -> bool {
+ matches!(self.ty.kind(Interner), TyKind::Never)
+ }
+
+ pub fn is_mutable_reference(&self) -> bool {
+ matches!(self.ty.kind(Interner), TyKind::Ref(hir_ty::Mutability::Mut, ..))
+ }
+
+ pub fn is_reference(&self) -> bool {
+ matches!(self.ty.kind(Interner), TyKind::Ref(..))
+ }
+
+ pub fn as_reference(&self) -> Option<(Type, Mutability)> {
+ let (ty, _lt, m) = self.ty.as_reference()?;
+ let m = Mutability::from_mutable(matches!(m, hir_ty::Mutability::Mut));
+ Some((self.derived(ty.clone()), m))
+ }
+
+ pub fn is_slice(&self) -> bool {
+ matches!(self.ty.kind(Interner), TyKind::Slice(..))
+ }
+
+ pub fn is_usize(&self) -> bool {
+ matches!(self.ty.kind(Interner), TyKind::Scalar(Scalar::Uint(UintTy::Usize)))
+ }
+
+ pub fn remove_ref(&self) -> Option<Type> {
+ match &self.ty.kind(Interner) {
+ TyKind::Ref(.., ty) => Some(self.derived(ty.clone())),
+ _ => None,
+ }
+ }
+
+ pub fn strip_references(&self) -> Type {
+ self.derived(self.ty.strip_references().clone())
+ }
+
+ pub fn strip_reference(&self) -> Type {
+ self.derived(self.ty.strip_reference().clone())
+ }
+
+ pub fn is_unknown(&self) -> bool {
+ self.ty.is_unknown()
+ }
+
+ /// Checks that particular type `ty` implements `std::future::IntoFuture` or
+ /// `std::future::Future`.
+ /// This function is used in `.await` syntax completion.
+ pub fn impls_into_future(&self, db: &dyn HirDatabase) -> bool {
+ let trait_ = db
+ .lang_item(self.env.krate, SmolStr::new_inline("into_future"))
+ .and_then(|it| {
+ let into_future_fn = it.as_function()?;
+ let assoc_item = as_assoc_item(db, AssocItem::Function, into_future_fn)?;
+ let into_future_trait = assoc_item.containing_trait_or_trait_impl(db)?;
+ Some(into_future_trait.id)
+ })
+ .or_else(|| {
+ let future_trait =
+ db.lang_item(self.env.krate, SmolStr::new_inline("future_trait"))?;
+ future_trait.as_trait()
+ });
+
+ let trait_ = match trait_ {
+ Some(it) => it,
+ None => return false,
+ };
+
+ let canonical_ty =
+ Canonical { value: self.ty.clone(), binders: CanonicalVarKinds::empty(Interner) };
+ method_resolution::implements_trait(&canonical_ty, db, self.env.clone(), trait_)
+ }
+
+ /// Checks that particular type `ty` implements `std::ops::FnOnce`.
+ ///
+ /// This function can be used to check if a particular type is callable, since FnOnce is a
+ /// supertrait of Fn and FnMut, so all callable types implements at least FnOnce.
+ pub fn impls_fnonce(&self, db: &dyn HirDatabase) -> bool {
+ let fnonce_trait = match FnTrait::FnOnce.get_id(db, self.env.krate) {
+ Some(it) => it,
+ None => return false,
+ };
+
+ let canonical_ty =
+ Canonical { value: self.ty.clone(), binders: CanonicalVarKinds::empty(Interner) };
+ method_resolution::implements_trait_unique(
+ &canonical_ty,
+ db,
+ self.env.clone(),
+ fnonce_trait,
+ )
+ }
+
+ pub fn impls_trait(&self, db: &dyn HirDatabase, trait_: Trait, args: &[Type]) -> bool {
+ let mut it = args.iter().map(|t| t.ty.clone());
+ let trait_ref = TyBuilder::trait_ref(db, trait_.id)
+ .push(self.ty.clone())
+ .fill(|x| {
+ let r = it.next().unwrap();
+ match x {
+ ParamKind::Type => GenericArgData::Ty(r).intern(Interner),
+ ParamKind::Const(ty) => {
+ // FIXME: this code is not covered in tests.
+ unknown_const_as_generic(ty.clone())
+ }
+ }
+ })
+ .build();
+
+ let goal = Canonical {
+ value: hir_ty::InEnvironment::new(&self.env.env, trait_ref.cast(Interner)),
+ binders: CanonicalVarKinds::empty(Interner),
+ };
+
+ db.trait_solve(self.env.krate, goal).is_some()
+ }
+
+ pub fn normalize_trait_assoc_type(
+ &self,
+ db: &dyn HirDatabase,
+ args: &[Type],
+ alias: TypeAlias,
+ ) -> Option<Type> {
+ let mut args = args.iter();
- Adt::Struct(s) => matches!(s.repr(db), Some(ReprKind::Packed)),
++ let trait_id = match alias.id.lookup(db.upcast()).container {
++ ItemContainerId::TraitId(id) => id,
++ _ => unreachable!("non assoc type alias reached in normalize_trait_assoc_type()"),
++ };
++ let parent_subst = TyBuilder::subst_for_def(db, trait_id, None)
+ .push(self.ty.clone())
+ .fill(|x| {
+ // FIXME: this code is not covered in tests.
+ match x {
+ ParamKind::Type => {
+ GenericArgData::Ty(args.next().unwrap().ty.clone()).intern(Interner)
+ }
+ ParamKind::Const(ty) => unknown_const_as_generic(ty.clone()),
+ }
+ })
+ .build();
++ // FIXME: We don't handle GATs yet.
++ let projection = TyBuilder::assoc_type_projection(db, alias.id, Some(parent_subst)).build();
+
+ let ty = db.normalize_projection(projection, self.env.clone());
+ if ty.is_unknown() {
+ None
+ } else {
+ Some(self.derived(ty))
+ }
+ }
+
+ pub fn is_copy(&self, db: &dyn HirDatabase) -> bool {
+ let lang_item = db.lang_item(self.env.krate, SmolStr::new_inline("copy"));
+ let copy_trait = match lang_item {
+ Some(LangItemTarget::TraitId(it)) => it,
+ _ => return false,
+ };
+ self.impls_trait(db, copy_trait.into(), &[])
+ }
+
+ pub fn as_callable(&self, db: &dyn HirDatabase) -> Option<Callable> {
+ let callee = match self.ty.kind(Interner) {
+ TyKind::Closure(id, _) => Callee::Closure(*id),
+ TyKind::Function(_) => Callee::FnPtr,
+ _ => Callee::Def(self.ty.callable_def(db)?),
+ };
+
+ let sig = self.ty.callable_sig(db)?;
+ Some(Callable { ty: self.clone(), sig, callee, is_bound_method: false })
+ }
+
+ pub fn is_closure(&self) -> bool {
+ matches!(&self.ty.kind(Interner), TyKind::Closure { .. })
+ }
+
+ pub fn is_fn(&self) -> bool {
+ matches!(&self.ty.kind(Interner), TyKind::FnDef(..) | TyKind::Function { .. })
+ }
+
+ pub fn is_array(&self) -> bool {
+ matches!(&self.ty.kind(Interner), TyKind::Array(..))
+ }
+
+ pub fn is_packed(&self, db: &dyn HirDatabase) -> bool {
+ let adt_id = match *self.ty.kind(Interner) {
+ TyKind::Adt(hir_ty::AdtId(adt_id), ..) => adt_id,
+ _ => return false,
+ };
+
+ let adt = adt_id.into();
+ match adt {
++ Adt::Struct(s) => matches!(s.repr(db), Some(ReprData { packed: true, .. })),
+ _ => false,
+ }
+ }
+
+ pub fn is_raw_ptr(&self) -> bool {
+ matches!(&self.ty.kind(Interner), TyKind::Raw(..))
+ }
+
+ pub fn contains_unknown(&self) -> bool {
+ return go(&self.ty);
+
+ fn go(ty: &Ty) -> bool {
+ match ty.kind(Interner) {
+ TyKind::Error => true,
+
+ TyKind::Adt(_, substs)
+ | TyKind::AssociatedType(_, substs)
+ | TyKind::Tuple(_, substs)
+ | TyKind::OpaqueType(_, substs)
+ | TyKind::FnDef(_, substs)
+ | TyKind::Closure(_, substs) => {
+ substs.iter(Interner).filter_map(|a| a.ty(Interner)).any(go)
+ }
+
+ TyKind::Array(_ty, len) if len.is_unknown() => true,
+ TyKind::Array(ty, _)
+ | TyKind::Slice(ty)
+ | TyKind::Raw(_, ty)
+ | TyKind::Ref(_, _, ty) => go(ty),
+
+ TyKind::Scalar(_)
+ | TyKind::Str
+ | TyKind::Never
+ | TyKind::Placeholder(_)
+ | TyKind::BoundVar(_)
+ | TyKind::InferenceVar(_, _)
+ | TyKind::Dyn(_)
+ | TyKind::Function(_)
+ | TyKind::Alias(_)
+ | TyKind::Foreign(_)
+ | TyKind::Generator(..)
+ | TyKind::GeneratorWitness(..) => false,
+ }
+ }
+ }
+
+ pub fn fields(&self, db: &dyn HirDatabase) -> Vec<(Field, Type)> {
+ let (variant_id, substs) = match self.ty.kind(Interner) {
+ TyKind::Adt(hir_ty::AdtId(AdtId::StructId(s)), substs) => ((*s).into(), substs),
+ TyKind::Adt(hir_ty::AdtId(AdtId::UnionId(u)), substs) => ((*u).into(), substs),
+ _ => return Vec::new(),
+ };
+
+ db.field_types(variant_id)
+ .iter()
+ .map(|(local_id, ty)| {
+ let def = Field { parent: variant_id.into(), id: local_id };
+ let ty = ty.clone().substitute(Interner, substs);
+ (def, self.derived(ty))
+ })
+ .collect()
+ }
+
+ pub fn tuple_fields(&self, _db: &dyn HirDatabase) -> Vec<Type> {
+ if let TyKind::Tuple(_, substs) = &self.ty.kind(Interner) {
+ substs
+ .iter(Interner)
+ .map(|ty| self.derived(ty.assert_ty_ref(Interner).clone()))
+ .collect()
+ } else {
+ Vec::new()
+ }
+ }
+
+ pub fn autoderef<'a>(&'a self, db: &'a dyn HirDatabase) -> impl Iterator<Item = Type> + 'a {
+ self.autoderef_(db).map(move |ty| self.derived(ty))
+ }
+
+ fn autoderef_<'a>(&'a self, db: &'a dyn HirDatabase) -> impl Iterator<Item = Ty> + 'a {
+ // There should be no inference vars in types passed here
+ let canonical = hir_ty::replace_errors_with_variables(&self.ty);
+ let environment = self.env.clone();
+ autoderef(db, environment, canonical).map(|canonical| canonical.value)
+ }
+
+ // This would be nicer if it just returned an iterator, but that runs into
+ // lifetime problems, because we need to borrow temp `CrateImplDefs`.
+ pub fn iterate_assoc_items<T>(
+ &self,
+ db: &dyn HirDatabase,
+ krate: Crate,
+ mut callback: impl FnMut(AssocItem) -> Option<T>,
+ ) -> Option<T> {
+ let mut slot = None;
+ self.iterate_assoc_items_dyn(db, krate, &mut |assoc_item_id| {
+ slot = callback(assoc_item_id.into());
+ slot.is_some()
+ });
+ slot
+ }
+
+ fn iterate_assoc_items_dyn(
+ &self,
+ db: &dyn HirDatabase,
+ krate: Crate,
+ callback: &mut dyn FnMut(AssocItemId) -> bool,
+ ) {
+ let def_crates = match method_resolution::def_crates(db, &self.ty, krate.id) {
+ Some(it) => it,
+ None => return,
+ };
+ for krate in def_crates {
+ let impls = db.inherent_impls_in_crate(krate);
+
+ for impl_def in impls.for_self_ty(&self.ty) {
+ for &item in db.impl_data(*impl_def).items.iter() {
+ if callback(item) {
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ pub fn type_arguments(&self) -> impl Iterator<Item = Type> + '_ {
+ self.ty
+ .strip_references()
+ .as_adt()
+ .into_iter()
+ .flat_map(|(_, substs)| substs.iter(Interner))
+ .filter_map(|arg| arg.ty(Interner).cloned())
+ .map(move |ty| self.derived(ty))
+ }
+
+ pub fn iterate_method_candidates<T>(
+ &self,
+ db: &dyn HirDatabase,
+ scope: &SemanticsScope<'_>,
+ // FIXME this can be retrieved from `scope`, except autoimport uses this
+ // to specify a different set, so the method needs to be split
+ traits_in_scope: &FxHashSet<TraitId>,
+ with_local_impls: Option<Module>,
+ name: Option<&Name>,
+ mut callback: impl FnMut(Function) -> Option<T>,
+ ) -> Option<T> {
+ let _p = profile::span("iterate_method_candidates");
+ let mut slot = None;
+
+ self.iterate_method_candidates_dyn(
+ db,
+ scope,
+ traits_in_scope,
+ with_local_impls,
+ name,
+ &mut |assoc_item_id| {
+ if let AssocItemId::FunctionId(func) = assoc_item_id {
+ if let Some(res) = callback(func.into()) {
+ slot = Some(res);
+ return ControlFlow::Break(());
+ }
+ }
+ ControlFlow::Continue(())
+ },
+ );
+ slot
+ }
+
+ fn iterate_method_candidates_dyn(
+ &self,
+ db: &dyn HirDatabase,
+ scope: &SemanticsScope<'_>,
+ traits_in_scope: &FxHashSet<TraitId>,
+ with_local_impls: Option<Module>,
+ name: Option<&Name>,
+ callback: &mut dyn FnMut(AssocItemId) -> ControlFlow<()>,
+ ) {
+ // There should be no inference vars in types passed here
+ let canonical = hir_ty::replace_errors_with_variables(&self.ty);
+
+ let krate = scope.krate();
+ let environment = scope.resolver().generic_def().map_or_else(
+ || Arc::new(TraitEnvironment::empty(krate.id)),
+ |d| db.trait_environment(d),
+ );
+
+ method_resolution::iterate_method_candidates_dyn(
+ &canonical,
+ db,
+ environment,
+ traits_in_scope,
+ with_local_impls.and_then(|b| b.id.containing_block()).into(),
+ name,
+ method_resolution::LookupMode::MethodCall,
+ &mut |_adj, id| callback(id),
+ );
+ }
+
+ pub fn iterate_path_candidates<T>(
+ &self,
+ db: &dyn HirDatabase,
+ scope: &SemanticsScope<'_>,
+ traits_in_scope: &FxHashSet<TraitId>,
+ with_local_impls: Option<Module>,
+ name: Option<&Name>,
+ mut callback: impl FnMut(AssocItem) -> Option<T>,
+ ) -> Option<T> {
+ let _p = profile::span("iterate_path_candidates");
+ let mut slot = None;
+ self.iterate_path_candidates_dyn(
+ db,
+ scope,
+ traits_in_scope,
+ with_local_impls,
+ name,
+ &mut |assoc_item_id| {
+ if let Some(res) = callback(assoc_item_id.into()) {
+ slot = Some(res);
+ return ControlFlow::Break(());
+ }
+ ControlFlow::Continue(())
+ },
+ );
+ slot
+ }
+
+ fn iterate_path_candidates_dyn(
+ &self,
+ db: &dyn HirDatabase,
+ scope: &SemanticsScope<'_>,
+ traits_in_scope: &FxHashSet<TraitId>,
+ with_local_impls: Option<Module>,
+ name: Option<&Name>,
+ callback: &mut dyn FnMut(AssocItemId) -> ControlFlow<()>,
+ ) {
+ let canonical = hir_ty::replace_errors_with_variables(&self.ty);
+
+ let krate = scope.krate();
+ let environment = scope.resolver().generic_def().map_or_else(
+ || Arc::new(TraitEnvironment::empty(krate.id)),
+ |d| db.trait_environment(d),
+ );
+
+ method_resolution::iterate_path_candidates(
+ &canonical,
+ db,
+ environment,
+ traits_in_scope,
+ with_local_impls.and_then(|b| b.id.containing_block()).into(),
+ name,
+ &mut |id| callback(id),
+ );
+ }
+
+ pub fn as_adt(&self) -> Option<Adt> {
+ let (adt, _subst) = self.ty.as_adt()?;
+ Some(adt.into())
+ }
+
+ pub fn as_builtin(&self) -> Option<BuiltinType> {
+ self.ty.as_builtin().map(|inner| BuiltinType { inner })
+ }
+
+ pub fn as_dyn_trait(&self) -> Option<Trait> {
+ self.ty.dyn_trait().map(Into::into)
+ }
+
+ /// If a type can be represented as `dyn Trait`, returns all traits accessible via this type,
+ /// or an empty iterator otherwise.
+ pub fn applicable_inherent_traits<'a>(
+ &'a self,
+ db: &'a dyn HirDatabase,
+ ) -> impl Iterator<Item = Trait> + 'a {
+ let _p = profile::span("applicable_inherent_traits");
+ self.autoderef_(db)
+ .filter_map(|ty| ty.dyn_trait())
+ .flat_map(move |dyn_trait_id| hir_ty::all_super_traits(db.upcast(), dyn_trait_id))
+ .map(Trait::from)
+ }
+
+ pub fn env_traits<'a>(&'a self, db: &'a dyn HirDatabase) -> impl Iterator<Item = Trait> + 'a {
+ let _p = profile::span("env_traits");
+ self.autoderef_(db)
+ .filter(|ty| matches!(ty.kind(Interner), TyKind::Placeholder(_)))
+ .flat_map(|ty| {
+ self.env
+ .traits_in_scope_from_clauses(ty)
+ .flat_map(|t| hir_ty::all_super_traits(db.upcast(), t))
+ })
+ .map(Trait::from)
+ }
+
+ pub fn as_impl_traits(&self, db: &dyn HirDatabase) -> Option<impl Iterator<Item = Trait>> {
+ self.ty.impl_trait_bounds(db).map(|it| {
+ it.into_iter().filter_map(|pred| match pred.skip_binders() {
+ hir_ty::WhereClause::Implemented(trait_ref) => {
+ Some(Trait::from(trait_ref.hir_trait_id()))
+ }
+ _ => None,
+ })
+ })
+ }
+
+ pub fn as_associated_type_parent_trait(&self, db: &dyn HirDatabase) -> Option<Trait> {
+ self.ty.associated_type_parent_trait(db).map(Into::into)
+ }
+
+ fn derived(&self, ty: Ty) -> Type {
+ Type { env: self.env.clone(), ty }
+ }
+
+ pub fn walk(&self, db: &dyn HirDatabase, mut cb: impl FnMut(Type)) {
+ // TypeWalk::walk for a Ty at first visits parameters and only after that the Ty itself.
+ // We need a different order here.
+
+ fn walk_substs(
+ db: &dyn HirDatabase,
+ type_: &Type,
+ substs: &Substitution,
+ cb: &mut impl FnMut(Type),
+ ) {
+ for ty in substs.iter(Interner).filter_map(|a| a.ty(Interner)) {
+ walk_type(db, &type_.derived(ty.clone()), cb);
+ }
+ }
+
+ fn walk_bounds(
+ db: &dyn HirDatabase,
+ type_: &Type,
+ bounds: &[QuantifiedWhereClause],
+ cb: &mut impl FnMut(Type),
+ ) {
+ for pred in bounds {
+ if let WhereClause::Implemented(trait_ref) = pred.skip_binders() {
+ cb(type_.clone());
+ // skip the self type. it's likely the type we just got the bounds from
+ for ty in
+ trait_ref.substitution.iter(Interner).skip(1).filter_map(|a| a.ty(Interner))
+ {
+ walk_type(db, &type_.derived(ty.clone()), cb);
+ }
+ }
+ }
+ }
+
+ fn walk_type(db: &dyn HirDatabase, type_: &Type, cb: &mut impl FnMut(Type)) {
+ let ty = type_.ty.strip_references();
+ match ty.kind(Interner) {
+ TyKind::Adt(_, substs) => {
+ cb(type_.derived(ty.clone()));
+ walk_substs(db, type_, substs, cb);
+ }
+ TyKind::AssociatedType(_, substs) => {
+ if ty.associated_type_parent_trait(db).is_some() {
+ cb(type_.derived(ty.clone()));
+ }
+ walk_substs(db, type_, substs, cb);
+ }
+ TyKind::OpaqueType(_, subst) => {
+ if let Some(bounds) = ty.impl_trait_bounds(db) {
+ walk_bounds(db, &type_.derived(ty.clone()), &bounds, cb);
+ }
+
+ walk_substs(db, type_, subst, cb);
+ }
+ TyKind::Alias(AliasTy::Opaque(opaque_ty)) => {
+ if let Some(bounds) = ty.impl_trait_bounds(db) {
+ walk_bounds(db, &type_.derived(ty.clone()), &bounds, cb);
+ }
+
+ walk_substs(db, type_, &opaque_ty.substitution, cb);
+ }
+ TyKind::Placeholder(_) => {
+ if let Some(bounds) = ty.impl_trait_bounds(db) {
+ walk_bounds(db, &type_.derived(ty.clone()), &bounds, cb);
+ }
+ }
+ TyKind::Dyn(bounds) => {
+ walk_bounds(
+ db,
+ &type_.derived(ty.clone()),
+ bounds.bounds.skip_binders().interned(),
+ cb,
+ );
+ }
+
+ TyKind::Ref(_, _, ty)
+ | TyKind::Raw(_, ty)
+ | TyKind::Array(ty, _)
+ | TyKind::Slice(ty) => {
+ walk_type(db, &type_.derived(ty.clone()), cb);
+ }
+
+ TyKind::FnDef(_, substs)
+ | TyKind::Tuple(_, substs)
+ | TyKind::Closure(.., substs) => {
+ walk_substs(db, type_, substs, cb);
+ }
+ TyKind::Function(hir_ty::FnPointer { substitution, .. }) => {
+ walk_substs(db, type_, &substitution.0, cb);
+ }
+
+ _ => {}
+ }
+ }
+
+ walk_type(db, self, &mut cb);
+ }
+
+ pub fn could_unify_with(&self, db: &dyn HirDatabase, other: &Type) -> bool {
+ let tys = hir_ty::replace_errors_with_variables(&(self.ty.clone(), other.ty.clone()));
+ hir_ty::could_unify(db, self.env.clone(), &tys)
+ }
+
+ pub fn could_coerce_to(&self, db: &dyn HirDatabase, to: &Type) -> bool {
+ let tys = hir_ty::replace_errors_with_variables(&(self.ty.clone(), to.ty.clone()));
+ hir_ty::could_coerce(db, self.env.clone(), &tys)
+ }
+
+ pub fn as_type_param(&self, db: &dyn HirDatabase) -> Option<TypeParam> {
+ match self.ty.kind(Interner) {
+ TyKind::Placeholder(p) => Some(TypeParam {
+ id: TypeParamId::from_unchecked(hir_ty::from_placeholder_idx(db, *p)),
+ }),
+ _ => None,
+ }
+ }
+}
+
+#[derive(Debug)]
+pub struct Callable {
+ ty: Type,
+ sig: CallableSig,
+ callee: Callee,
+ pub(crate) is_bound_method: bool,
+}
+
+#[derive(Debug)]
+enum Callee {
+ Def(CallableDefId),
+ Closure(ClosureId),
+ FnPtr,
+}
+
+pub enum CallableKind {
+ Function(Function),
+ TupleStruct(Struct),
+ TupleEnumVariant(Variant),
+ Closure,
+ FnPtr,
+}
+
+impl Callable {
+ pub fn kind(&self) -> CallableKind {
+ use Callee::*;
+ match self.callee {
+ Def(CallableDefId::FunctionId(it)) => CallableKind::Function(it.into()),
+ Def(CallableDefId::StructId(it)) => CallableKind::TupleStruct(it.into()),
+ Def(CallableDefId::EnumVariantId(it)) => CallableKind::TupleEnumVariant(it.into()),
+ Closure(_) => CallableKind::Closure,
+ FnPtr => CallableKind::FnPtr,
+ }
+ }
+ pub fn receiver_param(&self, db: &dyn HirDatabase) -> Option<ast::SelfParam> {
+ let func = match self.callee {
+ Callee::Def(CallableDefId::FunctionId(it)) if self.is_bound_method => it,
+ _ => return None,
+ };
+ let src = func.lookup(db.upcast()).source(db.upcast());
+ let param_list = src.value.param_list()?;
+ param_list.self_param()
+ }
+ pub fn n_params(&self) -> usize {
+ self.sig.params().len() - if self.is_bound_method { 1 } else { 0 }
+ }
+ pub fn params(
+ &self,
+ db: &dyn HirDatabase,
+ ) -> Vec<(Option<Either<ast::SelfParam, ast::Pat>>, Type)> {
+ let types = self
+ .sig
+ .params()
+ .iter()
+ .skip(if self.is_bound_method { 1 } else { 0 })
+ .map(|ty| self.ty.derived(ty.clone()));
+ let map_param = |it: ast::Param| it.pat().map(Either::Right);
+ let patterns = match self.callee {
+ Callee::Def(CallableDefId::FunctionId(func)) => {
+ let src = func.lookup(db.upcast()).source(db.upcast());
+ src.value.param_list().map(|param_list| {
+ param_list
+ .self_param()
+ .map(|it| Some(Either::Left(it)))
+ .filter(|_| !self.is_bound_method)
+ .into_iter()
+ .chain(param_list.params().map(map_param))
+ })
+ }
+ Callee::Closure(closure_id) => match closure_source(db, closure_id) {
+ Some(src) => src.param_list().map(|param_list| {
+ param_list
+ .self_param()
+ .map(|it| Some(Either::Left(it)))
+ .filter(|_| !self.is_bound_method)
+ .into_iter()
+ .chain(param_list.params().map(map_param))
+ }),
+ None => None,
+ },
+ _ => None,
+ };
+ patterns.into_iter().flatten().chain(iter::repeat(None)).zip(types).collect()
+ }
+ pub fn return_type(&self) -> Type {
+ self.ty.derived(self.sig.ret().clone())
+ }
+}
+
+fn closure_source(db: &dyn HirDatabase, closure: ClosureId) -> Option<ast::ClosureExpr> {
+ let (owner, expr_id) = db.lookup_intern_closure(closure.into());
+ let (_, source_map) = db.body_with_source_map(owner);
+ let ast = source_map.expr_syntax(expr_id).ok()?;
+ let root = ast.file_syntax(db.upcast());
+ let expr = ast.value.to_node(&root);
+ match expr {
+ ast::Expr::ClosureExpr(it) => Some(it),
+ _ => None,
+ }
+}
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub enum BindingMode {
+ Move,
+ Ref(Mutability),
+}
+
+/// For IDE only
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
+pub enum ScopeDef {
+ ModuleDef(ModuleDef),
+ GenericParam(GenericParam),
+ ImplSelfType(Impl),
+ AdtSelfType(Adt),
+ Local(Local),
+ Label(Label),
+ Unknown,
+}
+
+impl ScopeDef {
+ pub fn all_items(def: PerNs) -> ArrayVec<Self, 3> {
+ let mut items = ArrayVec::new();
+
+ match (def.take_types(), def.take_values()) {
+ (Some(m1), None) => items.push(ScopeDef::ModuleDef(m1.into())),
+ (None, Some(m2)) => items.push(ScopeDef::ModuleDef(m2.into())),
+ (Some(m1), Some(m2)) => {
+ // Some items, like unit structs and enum variants, are
+ // returned as both a type and a value. Here we want
+ // to de-duplicate them.
+ if m1 != m2 {
+ items.push(ScopeDef::ModuleDef(m1.into()));
+ items.push(ScopeDef::ModuleDef(m2.into()));
+ } else {
+ items.push(ScopeDef::ModuleDef(m1.into()));
+ }
+ }
+ (None, None) => {}
+ };
+
+ if let Some(macro_def_id) = def.take_macros() {
+ items.push(ScopeDef::ModuleDef(ModuleDef::Macro(macro_def_id.into())));
+ }
+
+ if items.is_empty() {
+ items.push(ScopeDef::Unknown);
+ }
+
+ items
+ }
+
+ pub fn attrs(&self, db: &dyn HirDatabase) -> Option<AttrsWithOwner> {
+ match self {
+ ScopeDef::ModuleDef(it) => it.attrs(db),
+ ScopeDef::GenericParam(it) => Some(it.attrs(db)),
+ ScopeDef::ImplSelfType(_)
+ | ScopeDef::AdtSelfType(_)
+ | ScopeDef::Local(_)
+ | ScopeDef::Label(_)
+ | ScopeDef::Unknown => None,
+ }
+ }
+
+ pub fn krate(&self, db: &dyn HirDatabase) -> Option<Crate> {
+ match self {
+ ScopeDef::ModuleDef(it) => it.module(db).map(|m| m.krate()),
+ ScopeDef::GenericParam(it) => Some(it.module(db).krate()),
+ ScopeDef::ImplSelfType(_) => None,
+ ScopeDef::AdtSelfType(it) => Some(it.module(db).krate()),
+ ScopeDef::Local(it) => Some(it.module(db).krate()),
+ ScopeDef::Label(it) => Some(it.module(db).krate()),
+ ScopeDef::Unknown => None,
+ }
+ }
+}
+
+impl From<ItemInNs> for ScopeDef {
+ fn from(item: ItemInNs) -> Self {
+ match item {
+ ItemInNs::Types(id) => ScopeDef::ModuleDef(id),
+ ItemInNs::Values(id) => ScopeDef::ModuleDef(id),
+ ItemInNs::Macros(id) => ScopeDef::ModuleDef(ModuleDef::Macro(id)),
+ }
+ }
+}
+
+pub trait HasVisibility {
+ fn visibility(&self, db: &dyn HirDatabase) -> Visibility;
+ fn is_visible_from(&self, db: &dyn HirDatabase, module: Module) -> bool {
+ let vis = self.visibility(db);
+ vis.is_visible_from(db.upcast(), module.id)
+ }
+}
+
+/// Trait for obtaining the defining crate of an item.
+pub trait HasCrate {
+ fn krate(&self, db: &dyn HirDatabase) -> Crate;
+}
+
+impl<T: hir_def::HasModule> HasCrate for T {
+ fn krate(&self, db: &dyn HirDatabase) -> Crate {
+ self.module(db.upcast()).krate().into()
+ }
+}
+
+impl HasCrate for AssocItem {
+ fn krate(&self, db: &dyn HirDatabase) -> Crate {
+ self.module(db).krate()
+ }
+}
+
+impl HasCrate for Struct {
+ fn krate(&self, db: &dyn HirDatabase) -> Crate {
+ self.module(db).krate()
+ }
+}
+
+impl HasCrate for Union {
+ fn krate(&self, db: &dyn HirDatabase) -> Crate {
+ self.module(db).krate()
+ }
+}
+
+impl HasCrate for Field {
+ fn krate(&self, db: &dyn HirDatabase) -> Crate {
+ self.parent_def(db).module(db).krate()
+ }
+}
+
+impl HasCrate for Variant {
+ fn krate(&self, db: &dyn HirDatabase) -> Crate {
+ self.module(db).krate()
+ }
+}
+
+impl HasCrate for Function {
+ fn krate(&self, db: &dyn HirDatabase) -> Crate {
+ self.module(db).krate()
+ }
+}
+
+impl HasCrate for Const {
+ fn krate(&self, db: &dyn HirDatabase) -> Crate {
+ self.module(db).krate()
+ }
+}
+
+impl HasCrate for TypeAlias {
+ fn krate(&self, db: &dyn HirDatabase) -> Crate {
+ self.module(db).krate()
+ }
+}
+
+impl HasCrate for Type {
+ fn krate(&self, _db: &dyn HirDatabase) -> Crate {
+ self.env.krate.into()
+ }
+}
+
+impl HasCrate for Macro {
+ fn krate(&self, db: &dyn HirDatabase) -> Crate {
+ self.module(db).krate()
+ }
+}
+
+impl HasCrate for Trait {
+ fn krate(&self, db: &dyn HirDatabase) -> Crate {
+ self.module(db).krate()
+ }
+}
+
+impl HasCrate for Static {
+ fn krate(&self, db: &dyn HirDatabase) -> Crate {
+ self.module(db).krate()
+ }
+}
+
+impl HasCrate for Adt {
+ fn krate(&self, db: &dyn HirDatabase) -> Crate {
+ self.module(db).krate()
+ }
+}
+
+impl HasCrate for Module {
+ fn krate(&self, _: &dyn HirDatabase) -> Crate {
+ Module::krate(*self)
+ }
+}
--- /dev/null
+//! See `Semantics`.
+
+mod source_to_def;
+
+use std::{cell::RefCell, fmt, iter, ops};
+
+use base_db::{FileId, FileRange};
+use hir_def::{
+ body, macro_id_to_def_id,
+ resolver::{self, HasResolver, Resolver, TypeNs},
+ type_ref::Mutability,
+ AsMacroCall, FunctionId, MacroId, TraitId, VariantId,
+};
+use hir_expand::{
+ db::AstDatabase,
+ name::{known, AsName},
+ ExpansionInfo, MacroCallId,
+};
+use itertools::Itertools;
+use rustc_hash::{FxHashMap, FxHashSet};
+use smallvec::{smallvec, SmallVec};
+use syntax::{
+ algo::skip_trivia_token,
+ ast::{self, HasAttrs as _, HasGenericParams, HasLoopBody},
+ match_ast, AstNode, Direction, SyntaxKind, SyntaxNode, SyntaxNodePtr, SyntaxToken, TextSize,
+};
+
+use crate::{
+ db::HirDatabase,
+ semantics::source_to_def::{ChildContainer, SourceToDefCache, SourceToDefCtx},
+ source_analyzer::{resolve_hir_path, SourceAnalyzer},
+ Access, BindingMode, BuiltinAttr, Callable, ConstParam, Crate, DeriveHelper, Field, Function,
+ HasSource, HirFileId, Impl, InFile, Label, LifetimeParam, Local, Macro, Module, ModuleDef,
+ Name, Path, ScopeDef, ToolModule, Trait, Type, TypeAlias, TypeParam, VariantDef,
+};
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum PathResolution {
+ /// An item
+ Def(ModuleDef),
+ /// A local binding (only value namespace)
+ Local(Local),
+ /// A type parameter
+ TypeParam(TypeParam),
+ /// A const parameter
+ ConstParam(ConstParam),
+ SelfType(Impl),
+ BuiltinAttr(BuiltinAttr),
+ ToolModule(ToolModule),
+ DeriveHelper(DeriveHelper),
+}
+
+impl PathResolution {
+ pub(crate) fn in_type_ns(&self) -> Option<TypeNs> {
+ match self {
+ PathResolution::Def(ModuleDef::Adt(adt)) => Some(TypeNs::AdtId((*adt).into())),
+ PathResolution::Def(ModuleDef::BuiltinType(builtin)) => {
+ Some(TypeNs::BuiltinType((*builtin).into()))
+ }
+ PathResolution::Def(
+ ModuleDef::Const(_)
+ | ModuleDef::Variant(_)
+ | ModuleDef::Macro(_)
+ | ModuleDef::Function(_)
+ | ModuleDef::Module(_)
+ | ModuleDef::Static(_)
+ | ModuleDef::Trait(_),
+ ) => None,
+ PathResolution::Def(ModuleDef::TypeAlias(alias)) => {
+ Some(TypeNs::TypeAliasId((*alias).into()))
+ }
+ PathResolution::BuiltinAttr(_)
+ | PathResolution::ToolModule(_)
+ | PathResolution::Local(_)
+ | PathResolution::DeriveHelper(_)
+ | PathResolution::ConstParam(_) => None,
+ PathResolution::TypeParam(param) => Some(TypeNs::GenericParam((*param).into())),
+ PathResolution::SelfType(impl_def) => Some(TypeNs::SelfType((*impl_def).into())),
+ }
+ }
+}
+
+#[derive(Debug)]
+pub struct TypeInfo {
+ /// The original type of the expression or pattern.
+ pub original: Type,
+ /// The adjusted type, if an adjustment happened.
+ pub adjusted: Option<Type>,
+}
+
+impl TypeInfo {
+ pub fn original(self) -> Type {
+ self.original
+ }
+
+ pub fn has_adjustment(&self) -> bool {
+ self.adjusted.is_some()
+ }
+
+ /// The adjusted type, or the original in case no adjustments occurred.
+ pub fn adjusted(self) -> Type {
+ self.adjusted.unwrap_or(self.original)
+ }
+}
+
+/// Primary API to get semantic information, like types, from syntax trees.
+pub struct Semantics<'db, DB> {
+ pub db: &'db DB,
+ imp: SemanticsImpl<'db>,
+}
+
+pub struct SemanticsImpl<'db> {
+ pub db: &'db dyn HirDatabase,
+ s2d_cache: RefCell<SourceToDefCache>,
+ expansion_info_cache: RefCell<FxHashMap<HirFileId, Option<ExpansionInfo>>>,
+ // Rootnode to HirFileId cache
+ cache: RefCell<FxHashMap<SyntaxNode, HirFileId>>,
+ // MacroCall to its expansion's HirFileId cache
+ macro_call_cache: RefCell<FxHashMap<InFile<ast::MacroCall>, HirFileId>>,
+}
+
+impl<DB> fmt::Debug for Semantics<'_, DB> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "Semantics {{ ... }}")
+ }
+}
+
+impl<'db, DB: HirDatabase> Semantics<'db, DB> {
+ pub fn new(db: &DB) -> Semantics<'_, DB> {
+ let impl_ = SemanticsImpl::new(db);
+ Semantics { db, imp: impl_ }
+ }
+
+ pub fn parse(&self, file_id: FileId) -> ast::SourceFile {
+ self.imp.parse(file_id)
+ }
+
+ pub fn parse_or_expand(&self, file_id: HirFileId) -> Option<SyntaxNode> {
+ self.imp.parse_or_expand(file_id)
+ }
+
+ pub fn expand(&self, macro_call: &ast::MacroCall) -> Option<SyntaxNode> {
+ self.imp.expand(macro_call)
+ }
+
+ /// If `item` has an attribute macro attached to it, expands it.
+ pub fn expand_attr_macro(&self, item: &ast::Item) -> Option<SyntaxNode> {
+ self.imp.expand_attr_macro(item)
+ }
+
+ pub fn expand_derive_as_pseudo_attr_macro(&self, attr: &ast::Attr) -> Option<SyntaxNode> {
+ self.imp.expand_derive_as_pseudo_attr_macro(attr)
+ }
+
+ pub fn resolve_derive_macro(&self, derive: &ast::Attr) -> Option<Vec<Option<Macro>>> {
+ self.imp.resolve_derive_macro(derive)
+ }
+
+ pub fn expand_derive_macro(&self, derive: &ast::Attr) -> Option<Vec<SyntaxNode>> {
+ self.imp.expand_derive_macro(derive)
+ }
+
+ pub fn is_attr_macro_call(&self, item: &ast::Item) -> bool {
+ self.imp.is_attr_macro_call(item)
+ }
+
+ pub fn is_derive_annotated(&self, item: &ast::Adt) -> bool {
+ self.imp.is_derive_annotated(item)
+ }
+
+ pub fn speculative_expand(
+ &self,
+ actual_macro_call: &ast::MacroCall,
+ speculative_args: &ast::TokenTree,
+ token_to_map: SyntaxToken,
+ ) -> Option<(SyntaxNode, SyntaxToken)> {
+ self.imp.speculative_expand(actual_macro_call, speculative_args, token_to_map)
+ }
+
+ pub fn speculative_expand_attr_macro(
+ &self,
+ actual_macro_call: &ast::Item,
+ speculative_args: &ast::Item,
+ token_to_map: SyntaxToken,
+ ) -> Option<(SyntaxNode, SyntaxToken)> {
+ self.imp.speculative_expand_attr(actual_macro_call, speculative_args, token_to_map)
+ }
+
+ pub fn speculative_expand_derive_as_pseudo_attr_macro(
+ &self,
+ actual_macro_call: &ast::Attr,
+ speculative_args: &ast::Attr,
+ token_to_map: SyntaxToken,
+ ) -> Option<(SyntaxNode, SyntaxToken)> {
+ self.imp.speculative_expand_derive_as_pseudo_attr_macro(
+ actual_macro_call,
+ speculative_args,
+ token_to_map,
+ )
+ }
+
+ /// Descend the token into macrocalls to its first mapped counterpart.
+ pub fn descend_into_macros_single(&self, token: SyntaxToken) -> SyntaxToken {
+ self.imp.descend_into_macros_single(token)
+ }
+
+ /// Descend the token into macrocalls to all its mapped counterparts.
+ pub fn descend_into_macros(&self, token: SyntaxToken) -> SmallVec<[SyntaxToken; 1]> {
+ self.imp.descend_into_macros(token)
+ }
+
+ /// Descend the token into macrocalls to all its mapped counterparts that have the same text as the input token.
+ ///
+ /// Returns the original non descended token if none of the mapped counterparts have the same text.
+ pub fn descend_into_macros_with_same_text(
+ &self,
+ token: SyntaxToken,
+ ) -> SmallVec<[SyntaxToken; 1]> {
+ self.imp.descend_into_macros_with_same_text(token)
+ }
+
+ pub fn descend_into_macros_with_kind_preference(&self, token: SyntaxToken) -> SyntaxToken {
+ self.imp.descend_into_macros_with_kind_preference(token)
+ }
+
+ /// Maps a node down by mapping its first and last token down.
+ pub fn descend_node_into_attributes<N: AstNode>(&self, node: N) -> SmallVec<[N; 1]> {
+ self.imp.descend_node_into_attributes(node)
+ }
+
+ /// Search for a definition's source and cache its syntax tree
+ pub fn source<Def: HasSource>(&self, def: Def) -> Option<InFile<Def::Ast>>
+ where
+ Def::Ast: AstNode,
+ {
+ self.imp.source(def)
+ }
+
+ pub fn hir_file_for(&self, syntax_node: &SyntaxNode) -> HirFileId {
+ self.imp.find_file(syntax_node).file_id
+ }
+
+ /// Attempts to map the node out of macro expanded files returning the original file range.
+ /// If upmapping is not possible, this will fall back to the range of the macro call of the
+ /// macro file the node resides in.
+ pub fn original_range(&self, node: &SyntaxNode) -> FileRange {
+ self.imp.original_range(node)
+ }
+
+ /// Attempts to map the node out of macro expanded files returning the original file range.
+ pub fn original_range_opt(&self, node: &SyntaxNode) -> Option<FileRange> {
+ self.imp.original_range_opt(node)
+ }
+
+ /// Attempts to map the node out of macro expanded files.
+ /// This only work for attribute expansions, as other ones do not have nodes as input.
+ pub fn original_ast_node<N: AstNode>(&self, node: N) -> Option<N> {
+ self.imp.original_ast_node(node)
+ }
++ /// Attempts to map the node out of macro expanded files.
++ /// This only work for attribute expansions, as other ones do not have nodes as input.
++ pub fn original_syntax_node(&self, node: &SyntaxNode) -> Option<SyntaxNode> {
++ self.imp.original_syntax_node(node)
++ }
+
+ pub fn diagnostics_display_range(&self, diagnostics: InFile<SyntaxNodePtr>) -> FileRange {
+ self.imp.diagnostics_display_range(diagnostics)
+ }
+
+ pub fn token_ancestors_with_macros(
+ &self,
+ token: SyntaxToken,
+ ) -> impl Iterator<Item = SyntaxNode> + '_ {
+ token.parent().into_iter().flat_map(move |it| self.ancestors_with_macros(it))
+ }
+
+ /// Iterates the ancestors of the given node, climbing up macro expansions while doing so.
+ pub fn ancestors_with_macros(&self, node: SyntaxNode) -> impl Iterator<Item = SyntaxNode> + '_ {
+ self.imp.ancestors_with_macros(node)
+ }
+
+ pub fn ancestors_at_offset_with_macros(
+ &self,
+ node: &SyntaxNode,
+ offset: TextSize,
+ ) -> impl Iterator<Item = SyntaxNode> + '_ {
+ self.imp.ancestors_at_offset_with_macros(node, offset)
+ }
+
+ /// Find an AstNode by offset inside SyntaxNode, if it is inside *Macrofile*,
+ /// search up until it is of the target AstNode type
+ pub fn find_node_at_offset_with_macros<N: AstNode>(
+ &self,
+ node: &SyntaxNode,
+ offset: TextSize,
+ ) -> Option<N> {
+ self.imp.ancestors_at_offset_with_macros(node, offset).find_map(N::cast)
+ }
+
+ /// Find an AstNode by offset inside SyntaxNode, if it is inside *MacroCall*,
+ /// descend it and find again
+ pub fn find_node_at_offset_with_descend<N: AstNode>(
+ &self,
+ node: &SyntaxNode,
+ offset: TextSize,
+ ) -> Option<N> {
+ self.imp.descend_node_at_offset(node, offset).flatten().find_map(N::cast)
+ }
+
+ /// Find an AstNode by offset inside SyntaxNode, if it is inside *MacroCall*,
+ /// descend it and find again
+ pub fn find_nodes_at_offset_with_descend<'slf, N: AstNode + 'slf>(
+ &'slf self,
+ node: &SyntaxNode,
+ offset: TextSize,
+ ) -> impl Iterator<Item = N> + 'slf {
+ self.imp.descend_node_at_offset(node, offset).filter_map(|mut it| it.find_map(N::cast))
+ }
+
+ pub fn resolve_lifetime_param(&self, lifetime: &ast::Lifetime) -> Option<LifetimeParam> {
+ self.imp.resolve_lifetime_param(lifetime)
+ }
+
+ pub fn resolve_label(&self, lifetime: &ast::Lifetime) -> Option<Label> {
+ self.imp.resolve_label(lifetime)
+ }
+
+ pub fn resolve_type(&self, ty: &ast::Type) -> Option<Type> {
+ self.imp.resolve_type(ty)
+ }
+
+ pub fn resolve_trait(&self, trait_: &ast::Path) -> Option<Trait> {
+ self.imp.resolve_trait(trait_)
+ }
+
+ // FIXME: Figure out a nice interface to inspect adjustments
+ pub fn is_implicit_reborrow(&self, expr: &ast::Expr) -> Option<Mutability> {
+ self.imp.is_implicit_reborrow(expr)
+ }
+
+ pub fn type_of_expr(&self, expr: &ast::Expr) -> Option<TypeInfo> {
+ self.imp.type_of_expr(expr)
+ }
+
+ pub fn type_of_pat(&self, pat: &ast::Pat) -> Option<TypeInfo> {
+ self.imp.type_of_pat(pat)
+ }
+
+ pub fn type_of_self(&self, param: &ast::SelfParam) -> Option<Type> {
+ self.imp.type_of_self(param)
+ }
+
+ pub fn pattern_adjustments(&self, pat: &ast::Pat) -> SmallVec<[Type; 1]> {
+ self.imp.pattern_adjustments(pat)
+ }
+
+ pub fn binding_mode_of_pat(&self, pat: &ast::IdentPat) -> Option<BindingMode> {
+ self.imp.binding_mode_of_pat(pat)
+ }
+
+ pub fn resolve_method_call(&self, call: &ast::MethodCallExpr) -> Option<Function> {
+ self.imp.resolve_method_call(call).map(Function::from)
+ }
+
+ pub fn resolve_await_to_poll(&self, await_expr: &ast::AwaitExpr) -> Option<Function> {
+ self.imp.resolve_await_to_poll(await_expr).map(Function::from)
+ }
+
+ pub fn resolve_prefix_expr(&self, prefix_expr: &ast::PrefixExpr) -> Option<Function> {
+ self.imp.resolve_prefix_expr(prefix_expr).map(Function::from)
+ }
+
+ pub fn resolve_index_expr(&self, index_expr: &ast::IndexExpr) -> Option<Function> {
+ self.imp.resolve_index_expr(index_expr).map(Function::from)
+ }
+
+ pub fn resolve_bin_expr(&self, bin_expr: &ast::BinExpr) -> Option<Function> {
+ self.imp.resolve_bin_expr(bin_expr).map(Function::from)
+ }
+
+ pub fn resolve_try_expr(&self, try_expr: &ast::TryExpr) -> Option<Function> {
+ self.imp.resolve_try_expr(try_expr).map(Function::from)
+ }
+
+ pub fn resolve_method_call_as_callable(&self, call: &ast::MethodCallExpr) -> Option<Callable> {
+ self.imp.resolve_method_call_as_callable(call)
+ }
+
+ pub fn resolve_field(&self, field: &ast::FieldExpr) -> Option<Field> {
+ self.imp.resolve_field(field)
+ }
+
+ pub fn resolve_record_field(
+ &self,
+ field: &ast::RecordExprField,
+ ) -> Option<(Field, Option<Local>, Type)> {
+ self.imp.resolve_record_field(field)
+ }
+
+ pub fn resolve_record_pat_field(&self, field: &ast::RecordPatField) -> Option<Field> {
+ self.imp.resolve_record_pat_field(field)
+ }
+
+ pub fn resolve_macro_call(&self, macro_call: &ast::MacroCall) -> Option<Macro> {
+ self.imp.resolve_macro_call(macro_call)
+ }
+
+ pub fn is_unsafe_macro_call(&self, macro_call: &ast::MacroCall) -> bool {
+ self.imp.is_unsafe_macro_call(macro_call)
+ }
+
+ pub fn resolve_attr_macro_call(&self, item: &ast::Item) -> Option<Macro> {
+ self.imp.resolve_attr_macro_call(item)
+ }
+
+ pub fn resolve_path(&self, path: &ast::Path) -> Option<PathResolution> {
+ self.imp.resolve_path(path)
+ }
+
+ pub fn resolve_extern_crate(&self, extern_crate: &ast::ExternCrate) -> Option<Crate> {
+ self.imp.resolve_extern_crate(extern_crate)
+ }
+
+ pub fn resolve_variant(&self, record_lit: ast::RecordExpr) -> Option<VariantDef> {
+ self.imp.resolve_variant(record_lit).map(VariantDef::from)
+ }
+
+ pub fn resolve_bind_pat_to_const(&self, pat: &ast::IdentPat) -> Option<ModuleDef> {
+ self.imp.resolve_bind_pat_to_const(pat)
+ }
+
+ pub fn record_literal_missing_fields(&self, literal: &ast::RecordExpr) -> Vec<(Field, Type)> {
+ self.imp.record_literal_missing_fields(literal)
+ }
+
+ pub fn record_pattern_missing_fields(&self, pattern: &ast::RecordPat) -> Vec<(Field, Type)> {
+ self.imp.record_pattern_missing_fields(pattern)
+ }
+
+ pub fn to_def<T: ToDef>(&self, src: &T) -> Option<T::Def> {
+ let src = self.imp.find_file(src.syntax()).with_value(src).cloned();
+ T::to_def(&self.imp, src)
+ }
+
+ pub fn to_module_def(&self, file: FileId) -> Option<Module> {
+ self.imp.to_module_def(file).next()
+ }
+
+ pub fn to_module_defs(&self, file: FileId) -> impl Iterator<Item = Module> {
+ self.imp.to_module_def(file)
+ }
+
+ pub fn scope(&self, node: &SyntaxNode) -> Option<SemanticsScope<'db>> {
+ self.imp.scope(node)
+ }
+
+ pub fn scope_at_offset(
+ &self,
+ node: &SyntaxNode,
+ offset: TextSize,
+ ) -> Option<SemanticsScope<'db>> {
+ self.imp.scope_at_offset(node, offset)
+ }
+
+ pub fn scope_for_def(&self, def: Trait) -> SemanticsScope<'db> {
+ self.imp.scope_for_def(def)
+ }
+
+ pub fn assert_contains_node(&self, node: &SyntaxNode) {
+ self.imp.assert_contains_node(node)
+ }
+
+ pub fn is_unsafe_method_call(&self, method_call_expr: &ast::MethodCallExpr) -> bool {
+ self.imp.is_unsafe_method_call(method_call_expr)
+ }
+
+ pub fn is_unsafe_ref_expr(&self, ref_expr: &ast::RefExpr) -> bool {
+ self.imp.is_unsafe_ref_expr(ref_expr)
+ }
+
+ pub fn is_unsafe_ident_pat(&self, ident_pat: &ast::IdentPat) -> bool {
+ self.imp.is_unsafe_ident_pat(ident_pat)
+ }
+}
+
+impl<'db> SemanticsImpl<'db> {
+ fn new(db: &'db dyn HirDatabase) -> Self {
+ SemanticsImpl {
+ db,
+ s2d_cache: Default::default(),
+ cache: Default::default(),
+ expansion_info_cache: Default::default(),
+ macro_call_cache: Default::default(),
+ }
+ }
+
+ fn parse(&self, file_id: FileId) -> ast::SourceFile {
+ let tree = self.db.parse(file_id).tree();
+ self.cache(tree.syntax().clone(), file_id.into());
+ tree
+ }
+
+ fn parse_or_expand(&self, file_id: HirFileId) -> Option<SyntaxNode> {
+ let node = self.db.parse_or_expand(file_id)?;
+ self.cache(node.clone(), file_id);
+ Some(node)
+ }
+
+ fn expand(&self, macro_call: &ast::MacroCall) -> Option<SyntaxNode> {
+ let sa = self.analyze_no_infer(macro_call.syntax())?;
+ let file_id = sa.expand(self.db, InFile::new(sa.file_id, macro_call))?;
+ let node = self.parse_or_expand(file_id)?;
+ Some(node)
+ }
+
+ fn expand_attr_macro(&self, item: &ast::Item) -> Option<SyntaxNode> {
+ let src = self.wrap_node_infile(item.clone());
+ let macro_call_id = self.with_ctx(|ctx| ctx.item_to_macro_call(src))?;
+ self.parse_or_expand(macro_call_id.as_file())
+ }
+
+ fn expand_derive_as_pseudo_attr_macro(&self, attr: &ast::Attr) -> Option<SyntaxNode> {
+ let src = self.wrap_node_infile(attr.clone());
+ let adt = attr.syntax().parent().and_then(ast::Adt::cast)?;
+ let call_id = self.with_ctx(|ctx| {
+ ctx.attr_to_derive_macro_call(src.with_value(&adt), src).map(|(_, it, _)| it)
+ })?;
+ self.parse_or_expand(call_id.as_file())
+ }
+
+ fn resolve_derive_macro(&self, attr: &ast::Attr) -> Option<Vec<Option<Macro>>> {
+ let calls = self.derive_macro_calls(attr)?;
+ self.with_ctx(|ctx| {
+ Some(
+ calls
+ .into_iter()
+ .map(|call| {
+ macro_call_to_macro_id(ctx, self.db.upcast(), call?).map(|id| Macro { id })
+ })
+ .collect(),
+ )
+ })
+ }
+
+ fn expand_derive_macro(&self, attr: &ast::Attr) -> Option<Vec<SyntaxNode>> {
+ let res: Vec<_> = self
+ .derive_macro_calls(attr)?
+ .into_iter()
+ .flat_map(|call| {
+ let file_id = call?.as_file();
+ let node = self.db.parse_or_expand(file_id)?;
+ self.cache(node.clone(), file_id);
+ Some(node)
+ })
+ .collect();
+ Some(res)
+ }
+
+ fn derive_macro_calls(&self, attr: &ast::Attr) -> Option<Vec<Option<MacroCallId>>> {
+ let adt = attr.syntax().parent().and_then(ast::Adt::cast)?;
+ let file_id = self.find_file(adt.syntax()).file_id;
+ let adt = InFile::new(file_id, &adt);
+ let src = InFile::new(file_id, attr.clone());
+ self.with_ctx(|ctx| {
+ let (.., res) = ctx.attr_to_derive_macro_call(adt, src)?;
+ Some(res.to_vec())
+ })
+ }
+
+ fn is_derive_annotated(&self, adt: &ast::Adt) -> bool {
+ let file_id = self.find_file(adt.syntax()).file_id;
+ let adt = InFile::new(file_id, adt);
+ self.with_ctx(|ctx| ctx.has_derives(adt))
+ }
+
+ fn is_attr_macro_call(&self, item: &ast::Item) -> bool {
+ let file_id = self.find_file(item.syntax()).file_id;
+ let src = InFile::new(file_id, item.clone());
+ self.with_ctx(|ctx| ctx.item_to_macro_call(src).is_some())
+ }
+
+ fn speculative_expand(
+ &self,
+ actual_macro_call: &ast::MacroCall,
+ speculative_args: &ast::TokenTree,
+ token_to_map: SyntaxToken,
+ ) -> Option<(SyntaxNode, SyntaxToken)> {
+ let SourceAnalyzer { file_id, resolver, .. } =
+ self.analyze_no_infer(actual_macro_call.syntax())?;
+ let macro_call = InFile::new(file_id, actual_macro_call);
+ let krate = resolver.krate();
+ let macro_call_id = macro_call.as_call_id(self.db.upcast(), krate, |path| {
+ resolver
+ .resolve_path_as_macro(self.db.upcast(), &path)
+ .map(|it| macro_id_to_def_id(self.db.upcast(), it))
+ })?;
+ hir_expand::db::expand_speculative(
+ self.db.upcast(),
+ macro_call_id,
+ speculative_args.syntax(),
+ token_to_map,
+ )
+ }
+
+ fn speculative_expand_attr(
+ &self,
+ actual_macro_call: &ast::Item,
+ speculative_args: &ast::Item,
+ token_to_map: SyntaxToken,
+ ) -> Option<(SyntaxNode, SyntaxToken)> {
+ let macro_call = self.wrap_node_infile(actual_macro_call.clone());
+ let macro_call_id = self.with_ctx(|ctx| ctx.item_to_macro_call(macro_call))?;
+ hir_expand::db::expand_speculative(
+ self.db.upcast(),
+ macro_call_id,
+ speculative_args.syntax(),
+ token_to_map,
+ )
+ }
+
+ fn speculative_expand_derive_as_pseudo_attr_macro(
+ &self,
+ actual_macro_call: &ast::Attr,
+ speculative_args: &ast::Attr,
+ token_to_map: SyntaxToken,
+ ) -> Option<(SyntaxNode, SyntaxToken)> {
+ let attr = self.wrap_node_infile(actual_macro_call.clone());
+ let adt = actual_macro_call.syntax().parent().and_then(ast::Adt::cast)?;
+ let macro_call_id = self.with_ctx(|ctx| {
+ ctx.attr_to_derive_macro_call(attr.with_value(&adt), attr).map(|(_, it, _)| it)
+ })?;
+ hir_expand::db::expand_speculative(
+ self.db.upcast(),
+ macro_call_id,
+ speculative_args.syntax(),
+ token_to_map,
+ )
+ }
+
+ // This might not be the correct way to do this, but it works for now
+ fn descend_node_into_attributes<N: AstNode>(&self, node: N) -> SmallVec<[N; 1]> {
+ let mut res = smallvec![];
+ let tokens = (|| {
+ let first = skip_trivia_token(node.syntax().first_token()?, Direction::Next)?;
+ let last = skip_trivia_token(node.syntax().last_token()?, Direction::Prev)?;
+ Some((first, last))
+ })();
+ let (first, last) = match tokens {
+ Some(it) => it,
+ None => return res,
+ };
+
+ if first == last {
+ self.descend_into_macros_impl(first, &mut |InFile { value, .. }| {
+ if let Some(node) = value.parent_ancestors().find_map(N::cast) {
+ res.push(node)
+ }
+ false
+ });
+ } else {
+ // Descend first and last token, then zip them to look for the node they belong to
+ let mut scratch: SmallVec<[_; 1]> = smallvec![];
+ self.descend_into_macros_impl(first, &mut |token| {
+ scratch.push(token);
+ false
+ });
+
+ let mut scratch = scratch.into_iter();
+ self.descend_into_macros_impl(
+ last,
+ &mut |InFile { value: last, file_id: last_fid }| {
+ if let Some(InFile { value: first, file_id: first_fid }) = scratch.next() {
+ if first_fid == last_fid {
+ if let Some(p) = first.parent() {
+ let range = first.text_range().cover(last.text_range());
+ let node = find_root(&p)
+ .covering_element(range)
+ .ancestors()
+ .take_while(|it| it.text_range() == range)
+ .find_map(N::cast);
+ if let Some(node) = node {
+ res.push(node);
+ }
+ }
+ }
+ }
+ false
+ },
+ );
+ }
+ res
+ }
+
+ fn descend_into_macros(&self, token: SyntaxToken) -> SmallVec<[SyntaxToken; 1]> {
+ let mut res = smallvec![];
+ self.descend_into_macros_impl(token, &mut |InFile { value, .. }| {
+ res.push(value);
+ false
+ });
+ res
+ }
+
+ fn descend_into_macros_with_same_text(&self, token: SyntaxToken) -> SmallVec<[SyntaxToken; 1]> {
+ let text = token.text();
+ let mut res = smallvec![];
+ self.descend_into_macros_impl(token.clone(), &mut |InFile { value, .. }| {
+ if value.text() == text {
+ res.push(value);
+ }
+ false
+ });
+ if res.is_empty() {
+ res.push(token);
+ }
+ res
+ }
+
+ fn descend_into_macros_with_kind_preference(&self, token: SyntaxToken) -> SyntaxToken {
+ let fetch_kind = |token: &SyntaxToken| match token.parent() {
+ Some(node) => match node.kind() {
+ kind @ (SyntaxKind::NAME | SyntaxKind::NAME_REF) => {
+ node.parent().map_or(kind, |it| it.kind())
+ }
+ _ => token.kind(),
+ },
+ None => token.kind(),
+ };
+ let preferred_kind = fetch_kind(&token);
+ let mut res = None;
+ self.descend_into_macros_impl(token.clone(), &mut |InFile { value, .. }| {
+ if fetch_kind(&value) == preferred_kind {
+ res = Some(value);
+ true
+ } else {
+ if let None = res {
+ res = Some(value)
+ }
+ false
+ }
+ });
+ res.unwrap_or(token)
+ }
+
+ fn descend_into_macros_single(&self, token: SyntaxToken) -> SyntaxToken {
+ let mut res = token.clone();
+ self.descend_into_macros_impl(token, &mut |InFile { value, .. }| {
+ res = value;
+ true
+ });
+ res
+ }
+
+ fn descend_into_macros_impl(
+ &self,
+ token: SyntaxToken,
+ f: &mut dyn FnMut(InFile<SyntaxToken>) -> bool,
+ ) {
+ let _p = profile::span("descend_into_macros");
+ let parent = match token.parent() {
+ Some(it) => it,
+ None => return,
+ };
+ let sa = match self.analyze_no_infer(&parent) {
+ Some(it) => it,
+ None => return,
+ };
+ let def_map = sa.resolver.def_map();
+
+ let mut stack: SmallVec<[_; 4]> = smallvec![InFile::new(sa.file_id, token)];
+ let mut cache = self.expansion_info_cache.borrow_mut();
+ let mut mcache = self.macro_call_cache.borrow_mut();
+
+ let mut process_expansion_for_token =
+ |stack: &mut SmallVec<_>, macro_file, item, token: InFile<&_>| {
+ let expansion_info = cache
+ .entry(macro_file)
+ .or_insert_with(|| macro_file.expansion_info(self.db.upcast()))
+ .as_ref()?;
+
+ {
+ let InFile { file_id, value } = expansion_info.expanded();
+ self.cache(value, file_id);
+ }
+
+ let mapped_tokens = expansion_info.map_token_down(self.db.upcast(), item, token)?;
+ let len = stack.len();
+
+ // requeue the tokens we got from mapping our current token down
+ stack.extend(mapped_tokens);
+ // if the length changed we have found a mapping for the token
+ (stack.len() != len).then(|| ())
+ };
+
+ // Remap the next token in the queue into a macro call its in, if it is not being remapped
+ // either due to not being in a macro-call or because its unused push it into the result vec,
+ // otherwise push the remapped tokens back into the queue as they can potentially be remapped again.
+ while let Some(token) = stack.pop() {
+ self.db.unwind_if_cancelled();
+ let was_not_remapped = (|| {
+ // First expand into attribute invocations
+ let containing_attribute_macro_call = self.with_ctx(|ctx| {
+ token.value.parent_ancestors().filter_map(ast::Item::cast).find_map(|item| {
+ if item.attrs().next().is_none() {
+ // Don't force populate the dyn cache for items that don't have an attribute anyways
+ return None;
+ }
+ Some((ctx.item_to_macro_call(token.with_value(item.clone()))?, item))
+ })
+ });
+ if let Some((call_id, item)) = containing_attribute_macro_call {
+ let file_id = call_id.as_file();
+ return process_expansion_for_token(
+ &mut stack,
+ file_id,
+ Some(item),
+ token.as_ref(),
+ );
+ }
+
+ // Then check for token trees, that means we are either in a function-like macro or
+ // secondary attribute inputs
+ let tt = token.value.parent_ancestors().map_while(ast::TokenTree::cast).last()?;
+ let parent = tt.syntax().parent()?;
+
+ if tt.left_delimiter_token().map_or(false, |it| it == token.value) {
+ return None;
+ }
+ if tt.right_delimiter_token().map_or(false, |it| it == token.value) {
+ return None;
+ }
+
+ if let Some(macro_call) = ast::MacroCall::cast(parent.clone()) {
+ let mcall = token.with_value(macro_call);
+ let file_id = match mcache.get(&mcall) {
+ Some(&it) => it,
+ None => {
+ let it = sa.expand(self.db, mcall.as_ref())?;
+ mcache.insert(mcall, it);
+ it
+ }
+ };
+ process_expansion_for_token(&mut stack, file_id, None, token.as_ref())
+ } else if let Some(meta) = ast::Meta::cast(parent.clone()) {
+ // attribute we failed expansion for earlier, this might be a derive invocation
+ // or derive helper attribute
+ let attr = meta.parent_attr()?;
+
+ let adt = if let Some(adt) = attr.syntax().parent().and_then(ast::Adt::cast) {
+ // this might be a derive, or a derive helper on an ADT
+ let derive_call = self.with_ctx(|ctx| {
+ // so try downmapping the token into the pseudo derive expansion
+ // see [hir_expand::builtin_attr_macro] for how the pseudo derive expansion works
+ ctx.attr_to_derive_macro_call(
+ token.with_value(&adt),
+ token.with_value(attr.clone()),
+ )
+ .map(|(_, call_id, _)| call_id)
+ });
+
+ match derive_call {
+ Some(call_id) => {
+ // resolved to a derive
+ let file_id = call_id.as_file();
+ return process_expansion_for_token(
+ &mut stack,
+ file_id,
+ Some(adt.into()),
+ token.as_ref(),
+ );
+ }
+ None => Some(adt),
+ }
+ } else {
+ // Otherwise this could be a derive helper on a variant or field
+ if let Some(field) = attr.syntax().parent().and_then(ast::RecordField::cast)
+ {
+ field.syntax().ancestors().take(4).find_map(ast::Adt::cast)
+ } else if let Some(field) =
+ attr.syntax().parent().and_then(ast::TupleField::cast)
+ {
+ field.syntax().ancestors().take(4).find_map(ast::Adt::cast)
+ } else if let Some(variant) =
+ attr.syntax().parent().and_then(ast::Variant::cast)
+ {
+ variant.syntax().ancestors().nth(2).and_then(ast::Adt::cast)
+ } else {
+ None
+ }
+ }?;
+ if !self.with_ctx(|ctx| ctx.has_derives(InFile::new(token.file_id, &adt))) {
+ return None;
+ }
+ // Not an attribute, nor a derive, so it's either a builtin or a derive helper
+ // Try to resolve to a derive helper and downmap
+ let attr_name = attr.path().and_then(|it| it.as_single_name_ref())?.as_name();
+ let id = self.db.ast_id_map(token.file_id).ast_id(&adt);
+ let helpers =
+ def_map.derive_helpers_in_scope(InFile::new(token.file_id, id))?;
+ let item = Some(adt.into());
+ let mut res = None;
+ for (.., derive) in helpers.iter().filter(|(helper, ..)| *helper == attr_name) {
+ res = res.or(process_expansion_for_token(
+ &mut stack,
+ derive.as_file(),
+ item.clone(),
+ token.as_ref(),
+ ));
+ }
+ res
+ } else {
+ None
+ }
+ })()
+ .is_none();
+
+ if was_not_remapped && f(token) {
+ break;
+ }
+ }
+ }
+
+ // Note this return type is deliberate as [`find_nodes_at_offset_with_descend`] wants to stop
+ // traversing the inner iterator when it finds a node.
+ // The outer iterator is over the tokens descendants
+ // The inner iterator is the ancestors of a descendant
+ fn descend_node_at_offset(
+ &self,
+ node: &SyntaxNode,
+ offset: TextSize,
+ ) -> impl Iterator<Item = impl Iterator<Item = SyntaxNode> + '_> + '_ {
+ node.token_at_offset(offset)
+ .map(move |token| self.descend_into_macros(token))
+ .map(|descendants| {
+ descendants.into_iter().map(move |it| self.token_ancestors_with_macros(it))
+ })
+ // re-order the tokens from token_at_offset by returning the ancestors with the smaller first nodes first
+ // See algo::ancestors_at_offset, which uses the same approach
+ .kmerge_by(|left, right| {
+ left.clone()
+ .map(|node| node.text_range().len())
+ .lt(right.clone().map(|node| node.text_range().len()))
+ })
+ }
+
+ fn original_range(&self, node: &SyntaxNode) -> FileRange {
+ let node = self.find_file(node);
+ node.original_file_range(self.db.upcast())
+ }
+
+ fn original_range_opt(&self, node: &SyntaxNode) -> Option<FileRange> {
+ let node = self.find_file(node);
+ node.original_file_range_opt(self.db.upcast())
+ }
+
+ fn original_ast_node<N: AstNode>(&self, node: N) -> Option<N> {
+ self.wrap_node_infile(node).original_ast_node(self.db.upcast()).map(
+ |InFile { file_id, value }| {
+ self.cache(find_root(value.syntax()), file_id);
+ value
+ },
+ )
+ }
+
++ fn original_syntax_node(&self, node: &SyntaxNode) -> Option<SyntaxNode> {
++ let InFile { file_id, .. } = self.find_file(node);
++ InFile::new(file_id, node).original_syntax_node(self.db.upcast()).map(
++ |InFile { file_id, value }| {
++ self.cache(find_root(&value), file_id);
++ value
++ },
++ )
++ }
++
+ fn diagnostics_display_range(&self, src: InFile<SyntaxNodePtr>) -> FileRange {
+ let root = self.parse_or_expand(src.file_id).unwrap();
+ let node = src.map(|it| it.to_node(&root));
+ node.as_ref().original_file_range(self.db.upcast())
+ }
+
+ fn token_ancestors_with_macros(
+ &self,
+ token: SyntaxToken,
+ ) -> impl Iterator<Item = SyntaxNode> + Clone + '_ {
+ token.parent().into_iter().flat_map(move |parent| self.ancestors_with_macros(parent))
+ }
+
+ fn ancestors_with_macros(
+ &self,
+ node: SyntaxNode,
+ ) -> impl Iterator<Item = SyntaxNode> + Clone + '_ {
+ let node = self.find_file(&node);
+ let db = self.db.upcast();
+ iter::successors(Some(node.cloned()), move |&InFile { file_id, ref value }| {
+ match value.parent() {
+ Some(parent) => Some(InFile::new(file_id, parent)),
+ None => {
+ self.cache(value.clone(), file_id);
+ file_id.call_node(db)
+ }
+ }
+ })
+ .map(|it| it.value)
+ }
+
+ fn ancestors_at_offset_with_macros(
+ &self,
+ node: &SyntaxNode,
+ offset: TextSize,
+ ) -> impl Iterator<Item = SyntaxNode> + '_ {
+ node.token_at_offset(offset)
+ .map(|token| self.token_ancestors_with_macros(token))
+ .kmerge_by(|node1, node2| node1.text_range().len() < node2.text_range().len())
+ }
+
+ fn resolve_lifetime_param(&self, lifetime: &ast::Lifetime) -> Option<LifetimeParam> {
+ let text = lifetime.text();
+ let lifetime_param = lifetime.syntax().ancestors().find_map(|syn| {
+ let gpl = ast::AnyHasGenericParams::cast(syn)?.generic_param_list()?;
+ gpl.lifetime_params()
+ .find(|tp| tp.lifetime().as_ref().map(|lt| lt.text()).as_ref() == Some(&text))
+ })?;
+ let src = self.wrap_node_infile(lifetime_param);
+ ToDef::to_def(self, src)
+ }
+
+ fn resolve_label(&self, lifetime: &ast::Lifetime) -> Option<Label> {
+ let text = lifetime.text();
+ let label = lifetime.syntax().ancestors().find_map(|syn| {
+ let label = match_ast! {
+ match syn {
+ ast::ForExpr(it) => it.label(),
+ ast::WhileExpr(it) => it.label(),
+ ast::LoopExpr(it) => it.label(),
+ ast::BlockExpr(it) => it.label(),
+ _ => None,
+ }
+ };
+ label.filter(|l| {
+ l.lifetime()
+ .and_then(|lt| lt.lifetime_ident_token())
+ .map_or(false, |lt| lt.text() == text)
+ })
+ })?;
+ let src = self.wrap_node_infile(label);
+ ToDef::to_def(self, src)
+ }
+
+ fn resolve_type(&self, ty: &ast::Type) -> Option<Type> {
+ let analyze = self.analyze(ty.syntax())?;
+ let ctx = body::LowerCtx::new(self.db.upcast(), analyze.file_id);
+ let ty = hir_ty::TyLoweringContext::new(self.db, &analyze.resolver)
+ .lower_ty(&crate::TypeRef::from_ast(&ctx, ty.clone()));
+ Some(Type::new_with_resolver(self.db, &analyze.resolver, ty))
+ }
+
+ fn resolve_trait(&self, path: &ast::Path) -> Option<Trait> {
+ let analyze = self.analyze(path.syntax())?;
+ let hygiene = hir_expand::hygiene::Hygiene::new(self.db.upcast(), analyze.file_id);
+ let ctx = body::LowerCtx::with_hygiene(self.db.upcast(), &hygiene);
+ let hir_path = Path::from_src(path.clone(), &ctx)?;
+ match analyze
+ .resolver
+ .resolve_path_in_type_ns_fully(self.db.upcast(), hir_path.mod_path())?
+ {
+ TypeNs::TraitId(id) => Some(Trait { id }),
+ _ => None,
+ }
+ }
+
+ fn is_implicit_reborrow(&self, expr: &ast::Expr) -> Option<Mutability> {
+ self.analyze(expr.syntax())?.is_implicit_reborrow(self.db, expr)
+ }
+
+ fn type_of_expr(&self, expr: &ast::Expr) -> Option<TypeInfo> {
+ self.analyze(expr.syntax())?
+ .type_of_expr(self.db, expr)
+ .map(|(ty, coerced)| TypeInfo { original: ty, adjusted: coerced })
+ }
+
+ fn type_of_pat(&self, pat: &ast::Pat) -> Option<TypeInfo> {
+ self.analyze(pat.syntax())?
+ .type_of_pat(self.db, pat)
+ .map(|(ty, coerced)| TypeInfo { original: ty, adjusted: coerced })
+ }
+
+ fn type_of_self(&self, param: &ast::SelfParam) -> Option<Type> {
+ self.analyze(param.syntax())?.type_of_self(self.db, param)
+ }
+
+ fn pattern_adjustments(&self, pat: &ast::Pat) -> SmallVec<[Type; 1]> {
+ self.analyze(pat.syntax())
+ .and_then(|it| it.pattern_adjustments(self.db, pat))
+ .unwrap_or_default()
+ }
+
+ fn binding_mode_of_pat(&self, pat: &ast::IdentPat) -> Option<BindingMode> {
+ self.analyze(pat.syntax())?.binding_mode_of_pat(self.db, pat)
+ }
+
+ fn resolve_method_call(&self, call: &ast::MethodCallExpr) -> Option<FunctionId> {
+ self.analyze(call.syntax())?.resolve_method_call(self.db, call)
+ }
+
+ fn resolve_await_to_poll(&self, await_expr: &ast::AwaitExpr) -> Option<FunctionId> {
+ self.analyze(await_expr.syntax())?.resolve_await_to_poll(self.db, await_expr)
+ }
+
+ fn resolve_prefix_expr(&self, prefix_expr: &ast::PrefixExpr) -> Option<FunctionId> {
+ self.analyze(prefix_expr.syntax())?.resolve_prefix_expr(self.db, prefix_expr)
+ }
+
+ fn resolve_index_expr(&self, index_expr: &ast::IndexExpr) -> Option<FunctionId> {
+ self.analyze(index_expr.syntax())?.resolve_index_expr(self.db, index_expr)
+ }
+
+ fn resolve_bin_expr(&self, bin_expr: &ast::BinExpr) -> Option<FunctionId> {
+ self.analyze(bin_expr.syntax())?.resolve_bin_expr(self.db, bin_expr)
+ }
+
+ fn resolve_try_expr(&self, try_expr: &ast::TryExpr) -> Option<FunctionId> {
+ self.analyze(try_expr.syntax())?.resolve_try_expr(self.db, try_expr)
+ }
+
+ fn resolve_method_call_as_callable(&self, call: &ast::MethodCallExpr) -> Option<Callable> {
+ self.analyze(call.syntax())?.resolve_method_call_as_callable(self.db, call)
+ }
+
+ fn resolve_field(&self, field: &ast::FieldExpr) -> Option<Field> {
+ self.analyze(field.syntax())?.resolve_field(self.db, field)
+ }
+
+ fn resolve_record_field(
+ &self,
+ field: &ast::RecordExprField,
+ ) -> Option<(Field, Option<Local>, Type)> {
+ self.analyze(field.syntax())?.resolve_record_field(self.db, field)
+ }
+
+ fn resolve_record_pat_field(&self, field: &ast::RecordPatField) -> Option<Field> {
+ self.analyze(field.syntax())?.resolve_record_pat_field(self.db, field)
+ }
+
+ fn resolve_macro_call(&self, macro_call: &ast::MacroCall) -> Option<Macro> {
+ let sa = self.analyze(macro_call.syntax())?;
+ let macro_call = self.find_file(macro_call.syntax()).with_value(macro_call);
+ sa.resolve_macro_call(self.db, macro_call)
+ }
+
+ fn is_unsafe_macro_call(&self, macro_call: &ast::MacroCall) -> bool {
+ let sa = match self.analyze(macro_call.syntax()) {
+ Some(it) => it,
+ None => return false,
+ };
+ let macro_call = self.find_file(macro_call.syntax()).with_value(macro_call);
+ sa.is_unsafe_macro_call(self.db, macro_call)
+ }
+
+ fn resolve_attr_macro_call(&self, item: &ast::Item) -> Option<Macro> {
+ let item_in_file = self.wrap_node_infile(item.clone());
+ let id = self.with_ctx(|ctx| {
+ let macro_call_id = ctx.item_to_macro_call(item_in_file)?;
+ macro_call_to_macro_id(ctx, self.db.upcast(), macro_call_id)
+ })?;
+ Some(Macro { id })
+ }
+
+ fn resolve_path(&self, path: &ast::Path) -> Option<PathResolution> {
+ self.analyze(path.syntax())?.resolve_path(self.db, path)
+ }
+
+ fn resolve_extern_crate(&self, extern_crate: &ast::ExternCrate) -> Option<Crate> {
+ let krate = self.scope(extern_crate.syntax())?.krate();
+ let name = extern_crate.name_ref()?.as_name();
+ if name == known::SELF_PARAM {
+ return Some(krate);
+ }
+ krate
+ .dependencies(self.db)
+ .into_iter()
+ .find_map(|dep| (dep.name == name).then(|| dep.krate))
+ }
+
+ fn resolve_variant(&self, record_lit: ast::RecordExpr) -> Option<VariantId> {
+ self.analyze(record_lit.syntax())?.resolve_variant(self.db, record_lit)
+ }
+
+ fn resolve_bind_pat_to_const(&self, pat: &ast::IdentPat) -> Option<ModuleDef> {
+ self.analyze(pat.syntax())?.resolve_bind_pat_to_const(self.db, pat)
+ }
+
+ fn record_literal_missing_fields(&self, literal: &ast::RecordExpr) -> Vec<(Field, Type)> {
+ self.analyze(literal.syntax())
+ .and_then(|it| it.record_literal_missing_fields(self.db, literal))
+ .unwrap_or_default()
+ }
+
+ fn record_pattern_missing_fields(&self, pattern: &ast::RecordPat) -> Vec<(Field, Type)> {
+ self.analyze(pattern.syntax())
+ .and_then(|it| it.record_pattern_missing_fields(self.db, pattern))
+ .unwrap_or_default()
+ }
+
+ fn with_ctx<F: FnOnce(&mut SourceToDefCtx<'_, '_>) -> T, T>(&self, f: F) -> T {
+ let mut cache = self.s2d_cache.borrow_mut();
+ let mut ctx = SourceToDefCtx { db: self.db, cache: &mut *cache };
+ f(&mut ctx)
+ }
+
+ fn to_module_def(&self, file: FileId) -> impl Iterator<Item = Module> {
+ self.with_ctx(|ctx| ctx.file_to_def(file)).into_iter().map(Module::from)
+ }
+
+ fn scope(&self, node: &SyntaxNode) -> Option<SemanticsScope<'db>> {
+ self.analyze_no_infer(node).map(|SourceAnalyzer { file_id, resolver, .. }| SemanticsScope {
+ db: self.db,
+ file_id,
+ resolver,
+ })
+ }
+
+ fn scope_at_offset(&self, node: &SyntaxNode, offset: TextSize) -> Option<SemanticsScope<'db>> {
+ self.analyze_with_offset_no_infer(node, offset).map(
+ |SourceAnalyzer { file_id, resolver, .. }| SemanticsScope {
+ db: self.db,
+ file_id,
+ resolver,
+ },
+ )
+ }
+
+ fn scope_for_def(&self, def: Trait) -> SemanticsScope<'db> {
+ let file_id = self.db.lookup_intern_trait(def.id).id.file_id();
+ let resolver = def.id.resolver(self.db.upcast());
+ SemanticsScope { db: self.db, file_id, resolver }
+ }
+
+ fn source<Def: HasSource>(&self, def: Def) -> Option<InFile<Def::Ast>>
+ where
+ Def::Ast: AstNode,
+ {
+ let res = def.source(self.db)?;
+ self.cache(find_root(res.value.syntax()), res.file_id);
+ Some(res)
+ }
+
+ /// Returns none if the file of the node is not part of a crate.
+ fn analyze(&self, node: &SyntaxNode) -> Option<SourceAnalyzer> {
+ self.analyze_impl(node, None, true)
+ }
+
+ /// Returns none if the file of the node is not part of a crate.
+ fn analyze_no_infer(&self, node: &SyntaxNode) -> Option<SourceAnalyzer> {
+ self.analyze_impl(node, None, false)
+ }
+
+ fn analyze_with_offset_no_infer(
+ &self,
+ node: &SyntaxNode,
+ offset: TextSize,
+ ) -> Option<SourceAnalyzer> {
+ self.analyze_impl(node, Some(offset), false)
+ }
+
+ fn analyze_impl(
+ &self,
+ node: &SyntaxNode,
+ offset: Option<TextSize>,
+ infer_body: bool,
+ ) -> Option<SourceAnalyzer> {
+ let _p = profile::span("Semantics::analyze_impl");
+ let node = self.find_file(node);
+
+ let container = match self.with_ctx(|ctx| ctx.find_container(node)) {
+ Some(it) => it,
+ None => return None,
+ };
+
+ let resolver = match container {
+ ChildContainer::DefWithBodyId(def) => {
+ return Some(if infer_body {
+ SourceAnalyzer::new_for_body(self.db, def, node, offset)
+ } else {
+ SourceAnalyzer::new_for_body_no_infer(self.db, def, node, offset)
+ })
+ }
+ ChildContainer::TraitId(it) => it.resolver(self.db.upcast()),
+ ChildContainer::ImplId(it) => it.resolver(self.db.upcast()),
+ ChildContainer::ModuleId(it) => it.resolver(self.db.upcast()),
+ ChildContainer::EnumId(it) => it.resolver(self.db.upcast()),
+ ChildContainer::VariantId(it) => it.resolver(self.db.upcast()),
+ ChildContainer::TypeAliasId(it) => it.resolver(self.db.upcast()),
+ ChildContainer::GenericDefId(it) => it.resolver(self.db.upcast()),
+ };
+ Some(SourceAnalyzer::new_for_resolver(resolver, node))
+ }
+
+ fn cache(&self, root_node: SyntaxNode, file_id: HirFileId) {
+ assert!(root_node.parent().is_none());
+ let mut cache = self.cache.borrow_mut();
+ let prev = cache.insert(root_node, file_id);
+ assert!(prev == None || prev == Some(file_id))
+ }
+
+ fn assert_contains_node(&self, node: &SyntaxNode) {
+ self.find_file(node);
+ }
+
+ fn lookup(&self, root_node: &SyntaxNode) -> Option<HirFileId> {
+ let cache = self.cache.borrow();
+ cache.get(root_node).copied()
+ }
+
+ fn wrap_node_infile<N: AstNode>(&self, node: N) -> InFile<N> {
+ let InFile { file_id, .. } = self.find_file(node.syntax());
+ InFile::new(file_id, node)
+ }
+
+ /// Wraps the node in a [`InFile`] with the file id it belongs to.
+ fn find_file<'node>(&self, node: &'node SyntaxNode) -> InFile<&'node SyntaxNode> {
+ let root_node = find_root(node);
+ let file_id = self.lookup(&root_node).unwrap_or_else(|| {
+ panic!(
+ "\n\nFailed to lookup {:?} in this Semantics.\n\
+ Make sure to use only query nodes, derived from this instance of Semantics.\n\
+ root node: {:?}\n\
+ known nodes: {}\n\n",
+ node,
+ root_node,
+ self.cache
+ .borrow()
+ .keys()
+ .map(|it| format!("{:?}", it))
+ .collect::<Vec<_>>()
+ .join(", ")
+ )
+ });
+ InFile::new(file_id, node)
+ }
+
+ fn is_unsafe_method_call(&self, method_call_expr: &ast::MethodCallExpr) -> bool {
+ method_call_expr
+ .receiver()
+ .and_then(|expr| {
+ let field_expr = match expr {
+ ast::Expr::FieldExpr(field_expr) => field_expr,
+ _ => return None,
+ };
+ let ty = self.type_of_expr(&field_expr.expr()?)?.original;
+ if !ty.is_packed(self.db) {
+ return None;
+ }
+
+ let func = self.resolve_method_call(method_call_expr).map(Function::from)?;
+ let res = match func.self_param(self.db)?.access(self.db) {
+ Access::Shared | Access::Exclusive => true,
+ Access::Owned => false,
+ };
+ Some(res)
+ })
+ .unwrap_or(false)
+ }
+
+ fn is_unsafe_ref_expr(&self, ref_expr: &ast::RefExpr) -> bool {
+ ref_expr
+ .expr()
+ .and_then(|expr| {
+ let field_expr = match expr {
+ ast::Expr::FieldExpr(field_expr) => field_expr,
+ _ => return None,
+ };
+ let expr = field_expr.expr()?;
+ self.type_of_expr(&expr)
+ })
+ // Binding a reference to a packed type is possibly unsafe.
+ .map(|ty| ty.original.is_packed(self.db))
+ .unwrap_or(false)
+
+ // FIXME This needs layout computation to be correct. It will highlight
+ // more than it should with the current implementation.
+ }
+
+ fn is_unsafe_ident_pat(&self, ident_pat: &ast::IdentPat) -> bool {
+ if ident_pat.ref_token().is_none() {
+ return false;
+ }
+
+ ident_pat
+ .syntax()
+ .parent()
+ .and_then(|parent| {
+ // `IdentPat` can live under `RecordPat` directly under `RecordPatField` or
+ // `RecordPatFieldList`. `RecordPatField` also lives under `RecordPatFieldList`,
+ // so this tries to lookup the `IdentPat` anywhere along that structure to the
+ // `RecordPat` so we can get the containing type.
+ let record_pat = ast::RecordPatField::cast(parent.clone())
+ .and_then(|record_pat| record_pat.syntax().parent())
+ .or_else(|| Some(parent.clone()))
+ .and_then(|parent| {
+ ast::RecordPatFieldList::cast(parent)?
+ .syntax()
+ .parent()
+ .and_then(ast::RecordPat::cast)
+ });
+
+ // If this doesn't match a `RecordPat`, fallback to a `LetStmt` to see if
+ // this is initialized from a `FieldExpr`.
+ if let Some(record_pat) = record_pat {
+ self.type_of_pat(&ast::Pat::RecordPat(record_pat))
+ } else if let Some(let_stmt) = ast::LetStmt::cast(parent) {
+ let field_expr = match let_stmt.initializer()? {
+ ast::Expr::FieldExpr(field_expr) => field_expr,
+ _ => return None,
+ };
+
+ self.type_of_expr(&field_expr.expr()?)
+ } else {
+ None
+ }
+ })
+ // Binding a reference to a packed type is possibly unsafe.
+ .map(|ty| ty.original.is_packed(self.db))
+ .unwrap_or(false)
+ }
+}
+
+fn macro_call_to_macro_id(
+ ctx: &mut SourceToDefCtx<'_, '_>,
+ db: &dyn AstDatabase,
+ macro_call_id: MacroCallId,
+) -> Option<MacroId> {
+ let loc = db.lookup_intern_macro_call(macro_call_id);
+ match loc.def.kind {
+ hir_expand::MacroDefKind::Declarative(it)
+ | hir_expand::MacroDefKind::BuiltIn(_, it)
+ | hir_expand::MacroDefKind::BuiltInAttr(_, it)
+ | hir_expand::MacroDefKind::BuiltInDerive(_, it)
+ | hir_expand::MacroDefKind::BuiltInEager(_, it) => {
+ ctx.macro_to_def(InFile::new(it.file_id, it.to_node(db)))
+ }
+ hir_expand::MacroDefKind::ProcMacro(_, _, it) => {
+ ctx.proc_macro_to_def(InFile::new(it.file_id, it.to_node(db)))
+ }
+ }
+}
+
+pub trait ToDef: AstNode + Clone {
+ type Def;
+
+ fn to_def(sema: &SemanticsImpl<'_>, src: InFile<Self>) -> Option<Self::Def>;
+}
+
+macro_rules! to_def_impls {
+ ($(($def:path, $ast:path, $meth:ident)),* ,) => {$(
+ impl ToDef for $ast {
+ type Def = $def;
+ fn to_def(sema: &SemanticsImpl<'_>, src: InFile<Self>) -> Option<Self::Def> {
+ sema.with_ctx(|ctx| ctx.$meth(src)).map(<$def>::from)
+ }
+ }
+ )*}
+}
+
+to_def_impls![
+ (crate::Module, ast::Module, module_to_def),
+ (crate::Module, ast::SourceFile, source_file_to_def),
+ (crate::Struct, ast::Struct, struct_to_def),
+ (crate::Enum, ast::Enum, enum_to_def),
+ (crate::Union, ast::Union, union_to_def),
+ (crate::Trait, ast::Trait, trait_to_def),
+ (crate::Impl, ast::Impl, impl_to_def),
+ (crate::TypeAlias, ast::TypeAlias, type_alias_to_def),
+ (crate::Const, ast::Const, const_to_def),
+ (crate::Static, ast::Static, static_to_def),
+ (crate::Function, ast::Fn, fn_to_def),
+ (crate::Field, ast::RecordField, record_field_to_def),
+ (crate::Field, ast::TupleField, tuple_field_to_def),
+ (crate::Variant, ast::Variant, enum_variant_to_def),
+ (crate::TypeParam, ast::TypeParam, type_param_to_def),
+ (crate::LifetimeParam, ast::LifetimeParam, lifetime_param_to_def),
+ (crate::ConstParam, ast::ConstParam, const_param_to_def),
+ (crate::GenericParam, ast::GenericParam, generic_param_to_def),
+ (crate::Macro, ast::Macro, macro_to_def),
+ (crate::Local, ast::IdentPat, bind_pat_to_def),
+ (crate::Local, ast::SelfParam, self_param_to_def),
+ (crate::Label, ast::Label, label_to_def),
+ (crate::Adt, ast::Adt, adt_to_def),
+];
+
+fn find_root(node: &SyntaxNode) -> SyntaxNode {
+ node.ancestors().last().unwrap()
+}
+
+/// `SemanticScope` encapsulates the notion of a scope (the set of visible
+/// names) at a particular program point.
+///
+/// It is a bit tricky, as scopes do not really exist inside the compiler.
+/// Rather, the compiler directly computes for each reference the definition it
+/// refers to. It might transiently compute the explicit scope map while doing
+/// so, but, generally, this is not something left after the analysis.
+///
+/// However, we do very much need explicit scopes for IDE purposes --
+/// completion, at its core, lists the contents of the current scope. The notion
+/// of scope is also useful to answer questions like "what would be the meaning
+/// of this piece of code if we inserted it into this position?".
+///
+/// So `SemanticsScope` is constructed from a specific program point (a syntax
+/// node or just a raw offset) and provides access to the set of visible names
+/// on a somewhat best-effort basis.
+///
+/// Note that if you are wondering "what does this specific existing name mean?",
+/// you'd better use the `resolve_` family of methods.
+#[derive(Debug)]
+pub struct SemanticsScope<'a> {
+ pub db: &'a dyn HirDatabase,
+ file_id: HirFileId,
+ resolver: Resolver,
+}
+
+impl<'a> SemanticsScope<'a> {
+ pub fn module(&self) -> Module {
+ Module { id: self.resolver.module() }
+ }
+
+ pub fn krate(&self) -> Crate {
+ Crate { id: self.resolver.krate() }
+ }
+
+ pub(crate) fn resolver(&self) -> &Resolver {
+ &self.resolver
+ }
+
+ /// Note: `VisibleTraits` should be treated as an opaque type, passed into `Type
+ pub fn visible_traits(&self) -> VisibleTraits {
+ let resolver = &self.resolver;
+ VisibleTraits(resolver.traits_in_scope(self.db.upcast()))
+ }
+
+ pub fn process_all_names(&self, f: &mut dyn FnMut(Name, ScopeDef)) {
+ let scope = self.resolver.names_in_scope(self.db.upcast());
+ for (name, entries) in scope {
+ for entry in entries {
+ let def = match entry {
+ resolver::ScopeDef::ModuleDef(it) => ScopeDef::ModuleDef(it.into()),
+ resolver::ScopeDef::Unknown => ScopeDef::Unknown,
+ resolver::ScopeDef::ImplSelfType(it) => ScopeDef::ImplSelfType(it.into()),
+ resolver::ScopeDef::AdtSelfType(it) => ScopeDef::AdtSelfType(it.into()),
+ resolver::ScopeDef::GenericParam(id) => ScopeDef::GenericParam(id.into()),
+ resolver::ScopeDef::Local(pat_id) => match self.resolver.body_owner() {
+ Some(parent) => ScopeDef::Local(Local { parent, pat_id }),
+ None => continue,
+ },
+ resolver::ScopeDef::Label(label_id) => match self.resolver.body_owner() {
+ Some(parent) => ScopeDef::Label(Label { parent, label_id }),
+ None => continue,
+ },
+ };
+ f(name.clone(), def)
+ }
+ }
+ }
+
+ /// Resolve a path as-if it was written at the given scope. This is
+ /// necessary a heuristic, as it doesn't take hygiene into account.
+ pub fn speculative_resolve(&self, path: &ast::Path) -> Option<PathResolution> {
+ let ctx = body::LowerCtx::new(self.db.upcast(), self.file_id);
+ let path = Path::from_src(path.clone(), &ctx)?;
+ resolve_hir_path(self.db, &self.resolver, &path)
+ }
+
+ /// Iterates over associated types that may be specified after the given path (using
+ /// `Ty::Assoc` syntax).
+ pub fn assoc_type_shorthand_candidates<R>(
+ &self,
+ resolution: &PathResolution,
+ mut cb: impl FnMut(&Name, TypeAlias) -> Option<R>,
+ ) -> Option<R> {
+ let def = self.resolver.generic_def()?;
+ hir_ty::associated_type_shorthand_candidates(
+ self.db,
+ def,
+ resolution.in_type_ns()?,
+ |name, _, id| cb(name, id.into()),
+ )
+ }
+}
+
+pub struct VisibleTraits(pub FxHashSet<TraitId>);
+
+impl ops::Deref for VisibleTraits {
+ type Target = FxHashSet<TraitId>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
--- /dev/null
- pub(super) fn file_to_def(&mut self, file: FileId) -> SmallVec<[ModuleId; 1]> {
+//! Maps *syntax* of various definitions to their semantic ids.
+//!
+//! This is a very interesting module, and, in some sense, can be considered the
+//! heart of the IDE parts of rust-analyzer.
+//!
+//! This module solves the following problem:
+//!
+//! Given a piece of syntax, find the corresponding semantic definition (def).
+//!
+//! This problem is a part of more-or-less every IDE feature implemented. Every
+//! IDE functionality (like goto to definition), conceptually starts with a
+//! specific cursor position in a file. Starting with this text offset, we first
+//! figure out what syntactic construct are we at: is this a pattern, an
+//! expression, an item definition.
+//!
+//! Knowing only the syntax gives us relatively little info. For example,
+//! looking at the syntax of the function we can realise that it is a part of an
+//! `impl` block, but we won't be able to tell what trait function the current
+//! function overrides, and whether it does that correctly. For that, we need to
+//! go from [`ast::Fn`] to [`crate::Function`], and that's exactly what this
+//! module does.
+//!
+//! As syntax trees are values and don't know their place of origin/identity,
+//! this module also requires [`InFile`] wrappers to understand which specific
+//! real or macro-expanded file the tree comes from.
+//!
+//! The actual algorithm to resolve syntax to def is curious in two aspects:
+//!
+//! * It is recursive
+//! * It uses the inverse algorithm (what is the syntax for this def?)
+//!
+//! Specifically, the algorithm goes like this:
+//!
+//! 1. Find the syntactic container for the syntax. For example, field's
+//! container is the struct, and structs container is a module.
+//! 2. Recursively get the def corresponding to container.
+//! 3. Ask the container def for all child defs. These child defs contain
+//! the answer and answer's siblings.
+//! 4. For each child def, ask for it's source.
+//! 5. The child def whose source is the syntax node we've started with
+//! is the answer.
+//!
+//! It's interesting that both Roslyn and Kotlin contain very similar code
+//! shape.
+//!
+//! Let's take a look at Roslyn:
+//!
+//! <https://github.com/dotnet/roslyn/blob/36a0c338d6621cc5fe34b79d414074a95a6a489c/src/Compilers/CSharp/Portable/Compilation/SyntaxTreeSemanticModel.cs#L1403-L1429>
+//! <https://sourceroslyn.io/#Microsoft.CodeAnalysis.CSharp/Compilation/SyntaxTreeSemanticModel.cs,1403>
+//!
+//! The `GetDeclaredType` takes `Syntax` as input, and returns `Symbol` as
+//! output. First, it retrieves a `Symbol` for parent `Syntax`:
+//!
+//! * <https://sourceroslyn.io/#Microsoft.CodeAnalysis.CSharp/Compilation/SyntaxTreeSemanticModel.cs,1423>
+//!
+//! Then, it iterates parent symbol's children, looking for one which has the
+//! same text span as the original node:
+//!
+//! <https://sourceroslyn.io/#Microsoft.CodeAnalysis.CSharp/Compilation/SyntaxTreeSemanticModel.cs,1786>
+//!
+//! Now, let's look at Kotlin:
+//!
+//! <https://github.com/JetBrains/kotlin/blob/a288b8b00e4754a1872b164999c6d3f3b8c8994a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/FirModuleResolveStateImpl.kt#L93-L125>
+//!
+//! This function starts with a syntax node (`KtExpression` is syntax, like all
+//! `Kt` nodes), and returns a def. It uses
+//! `getNonLocalContainingOrThisDeclaration` to get syntactic container for a
+//! current node. Then, `findSourceNonLocalFirDeclaration` gets `Fir` for this
+//! parent. Finally, `findElementIn` function traverses `Fir` children to find
+//! one with the same source we originally started with.
+//!
+//! One question is left though -- where does the recursion stops? This happens
+//! when we get to the file syntax node, which doesn't have a syntactic parent.
+//! In that case, we loop through all the crates that might contain this file
+//! and look for a module whose source is the given file.
+//!
+//! Note that the logic in this module is somewhat fundamentally imprecise --
+//! due to conditional compilation and `#[path]` attributes, there's no
+//! injective mapping from syntax nodes to defs. This is not an edge case --
+//! more or less every item in a `lib.rs` is a part of two distinct crates: a
+//! library with `--cfg test` and a library without.
+//!
+//! At the moment, we don't really handle this well and return the first answer
+//! that works. Ideally, we should first let the caller to pick a specific
+//! active crate for a given position, and then provide an API to resolve all
+//! syntax nodes against this specific crate.
+
+use base_db::FileId;
+use hir_def::{
+ attr::AttrId,
+ child_by_source::ChildBySource,
+ dyn_map::DynMap,
+ expr::{LabelId, PatId},
+ keys::{self, Key},
+ AdtId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId, FieldId, FunctionId,
+ GenericDefId, GenericParamId, ImplId, LifetimeParamId, MacroId, ModuleId, StaticId, StructId,
+ TraitId, TypeAliasId, TypeParamId, UnionId, VariantId,
+};
+use hir_expand::{name::AsName, HirFileId, MacroCallId};
+use rustc_hash::FxHashMap;
+use smallvec::SmallVec;
+use stdx::impl_from;
+use syntax::{
+ ast::{self, HasName},
+ AstNode, SyntaxNode,
+};
+
+use crate::{db::HirDatabase, InFile};
+
+pub(super) type SourceToDefCache = FxHashMap<(ChildContainer, HirFileId), DynMap>;
+
+pub(super) struct SourceToDefCtx<'a, 'b> {
+ pub(super) db: &'b dyn HirDatabase,
+ pub(super) cache: &'a mut SourceToDefCache,
+}
+
+impl SourceToDefCtx<'_, '_> {
- pub(super) fn module_to_def(&mut self, src: InFile<ast::Module>) -> Option<ModuleId> {
++ pub(super) fn file_to_def(&self, file: FileId) -> SmallVec<[ModuleId; 1]> {
+ let _p = profile::span("SourceBinder::to_module_def");
+ let mut mods = SmallVec::new();
+ for &crate_id in self.db.relevant_crates(file).iter() {
+ // FIXME: inner items
+ let crate_def_map = self.db.crate_def_map(crate_id);
+ mods.extend(
+ crate_def_map
+ .modules_for_file(file)
+ .map(|local_id| crate_def_map.module_id(local_id)),
+ )
+ }
+ mods
+ }
+
- pub(super) fn source_file_to_def(&mut self, src: InFile<ast::SourceFile>) -> Option<ModuleId> {
++ pub(super) fn module_to_def(&self, src: InFile<ast::Module>) -> Option<ModuleId> {
+ let _p = profile::span("module_to_def");
+ let parent_declaration = src
+ .syntax()
+ .ancestors_with_macros_skip_attr_item(self.db.upcast())
+ .find_map(|it| it.map(ast::Module::cast).transpose());
+
+ let parent_module = match parent_declaration {
+ Some(parent_declaration) => self.module_to_def(parent_declaration),
+ None => {
+ let file_id = src.file_id.original_file(self.db.upcast());
+ self.file_to_def(file_id).get(0).copied()
+ }
+ }?;
+
+ let child_name = src.value.name()?.as_name();
+ let def_map = parent_module.def_map(self.db.upcast());
+ let &child_id = def_map[parent_module.local_id].children.get(&child_name)?;
+ Some(def_map.module_id(child_id))
+ }
+
- VariantId::from(def).into()
++ pub(super) fn source_file_to_def(&self, src: InFile<ast::SourceFile>) -> Option<ModuleId> {
+ let _p = profile::span("source_file_to_def");
+ let file_id = src.file_id.original_file(self.db.upcast());
+ self.file_to_def(file_id).get(0).copied()
+ }
+
+ pub(super) fn trait_to_def(&mut self, src: InFile<ast::Trait>) -> Option<TraitId> {
+ self.to_def(src, keys::TRAIT)
+ }
+ pub(super) fn impl_to_def(&mut self, src: InFile<ast::Impl>) -> Option<ImplId> {
+ self.to_def(src, keys::IMPL)
+ }
+ pub(super) fn fn_to_def(&mut self, src: InFile<ast::Fn>) -> Option<FunctionId> {
+ self.to_def(src, keys::FUNCTION)
+ }
+ pub(super) fn struct_to_def(&mut self, src: InFile<ast::Struct>) -> Option<StructId> {
+ self.to_def(src, keys::STRUCT)
+ }
+ pub(super) fn enum_to_def(&mut self, src: InFile<ast::Enum>) -> Option<EnumId> {
+ self.to_def(src, keys::ENUM)
+ }
+ pub(super) fn union_to_def(&mut self, src: InFile<ast::Union>) -> Option<UnionId> {
+ self.to_def(src, keys::UNION)
+ }
+ pub(super) fn static_to_def(&mut self, src: InFile<ast::Static>) -> Option<StaticId> {
+ self.to_def(src, keys::STATIC)
+ }
+ pub(super) fn const_to_def(&mut self, src: InFile<ast::Const>) -> Option<ConstId> {
+ self.to_def(src, keys::CONST)
+ }
+ pub(super) fn type_alias_to_def(&mut self, src: InFile<ast::TypeAlias>) -> Option<TypeAliasId> {
+ self.to_def(src, keys::TYPE_ALIAS)
+ }
+ pub(super) fn record_field_to_def(&mut self, src: InFile<ast::RecordField>) -> Option<FieldId> {
+ self.to_def(src, keys::RECORD_FIELD)
+ }
+ pub(super) fn tuple_field_to_def(&mut self, src: InFile<ast::TupleField>) -> Option<FieldId> {
+ self.to_def(src, keys::TUPLE_FIELD)
+ }
+ pub(super) fn enum_variant_to_def(
+ &mut self,
+ src: InFile<ast::Variant>,
+ ) -> Option<EnumVariantId> {
+ self.to_def(src, keys::VARIANT)
+ }
+ pub(super) fn adt_to_def(
+ &mut self,
+ InFile { file_id, value }: InFile<ast::Adt>,
+ ) -> Option<AdtId> {
+ match value {
+ ast::Adt::Enum(it) => self.enum_to_def(InFile::new(file_id, it)).map(AdtId::EnumId),
+ ast::Adt::Struct(it) => {
+ self.struct_to_def(InFile::new(file_id, it)).map(AdtId::StructId)
+ }
+ ast::Adt::Union(it) => self.union_to_def(InFile::new(file_id, it)).map(AdtId::UnionId),
+ }
+ }
+ pub(super) fn bind_pat_to_def(
+ &mut self,
+ src: InFile<ast::IdentPat>,
+ ) -> Option<(DefWithBodyId, PatId)> {
+ let container = self.find_pat_or_label_container(src.syntax())?;
+ let (body, source_map) = self.db.body_with_source_map(container);
+ let src = src.map(ast::Pat::from);
+ let pat_id = source_map.node_pat(src.as_ref())?;
+ // the pattern could resolve to a constant, verify that that is not the case
+ if let crate::Pat::Bind { .. } = body[pat_id] {
+ Some((container, pat_id))
+ } else {
+ None
+ }
+ }
+ pub(super) fn self_param_to_def(
+ &mut self,
+ src: InFile<ast::SelfParam>,
+ ) -> Option<(DefWithBodyId, PatId)> {
+ let container = self.find_pat_or_label_container(src.syntax())?;
+ let (_body, source_map) = self.db.body_with_source_map(container);
+ let pat_id = source_map.node_self_param(src.as_ref())?;
+ Some((container, pat_id))
+ }
+ pub(super) fn label_to_def(
+ &mut self,
+ src: InFile<ast::Label>,
+ ) -> Option<(DefWithBodyId, LabelId)> {
+ let container = self.find_pat_or_label_container(src.syntax())?;
+ let (_body, source_map) = self.db.body_with_source_map(container);
+ let label_id = source_map.node_label(src.as_ref())?;
+ Some((container, label_id))
+ }
+
+ pub(super) fn item_to_macro_call(&mut self, src: InFile<ast::Item>) -> Option<MacroCallId> {
+ let map = self.dyn_map(src.as_ref())?;
+ map[keys::ATTR_MACRO_CALL].get(&src.value).copied()
+ }
+
+ /// (AttrId, derive attribute call id, derive call ids)
+ pub(super) fn attr_to_derive_macro_call(
+ &mut self,
+ item: InFile<&ast::Adt>,
+ src: InFile<ast::Attr>,
+ ) -> Option<(AttrId, MacroCallId, &[Option<MacroCallId>])> {
+ let map = self.dyn_map(item)?;
+ map[keys::DERIVE_MACRO_CALL]
+ .get(&src.value)
+ .map(|&(attr_id, call_id, ref ids)| (attr_id, call_id, &**ids))
+ }
+
+ pub(super) fn has_derives(&mut self, adt: InFile<&ast::Adt>) -> bool {
+ self.dyn_map(adt).as_ref().map_or(false, |map| !map[keys::DERIVE_MACRO_CALL].is_empty())
+ }
+
+ fn to_def<Ast: AstNode + 'static, ID: Copy + 'static>(
+ &mut self,
+ src: InFile<Ast>,
+ key: Key<Ast, ID>,
+ ) -> Option<ID> {
+ self.dyn_map(src.as_ref())?[key].get(&src.value).copied()
+ }
+
+ fn dyn_map<Ast: AstNode + 'static>(&mut self, src: InFile<&Ast>) -> Option<&DynMap> {
+ let container = self.find_container(src.map(|it| it.syntax()))?;
+ Some(self.cache_for(container, src.file_id))
+ }
+
+ fn cache_for(&mut self, container: ChildContainer, file_id: HirFileId) -> &DynMap {
+ let db = self.db;
+ self.cache
+ .entry((container, file_id))
+ .or_insert_with(|| container.child_by_source(db, file_id))
+ }
+
+ pub(super) fn type_param_to_def(&mut self, src: InFile<ast::TypeParam>) -> Option<TypeParamId> {
+ let container: ChildContainer = self.find_generic_param_container(src.syntax())?.into();
+ let dyn_map = self.cache_for(container, src.file_id);
+ dyn_map[keys::TYPE_PARAM].get(&src.value).copied().map(|x| TypeParamId::from_unchecked(x))
+ }
+
+ pub(super) fn lifetime_param_to_def(
+ &mut self,
+ src: InFile<ast::LifetimeParam>,
+ ) -> Option<LifetimeParamId> {
+ let container: ChildContainer = self.find_generic_param_container(src.syntax())?.into();
+ let dyn_map = self.cache_for(container, src.file_id);
+ dyn_map[keys::LIFETIME_PARAM].get(&src.value).copied()
+ }
+
+ pub(super) fn const_param_to_def(
+ &mut self,
+ src: InFile<ast::ConstParam>,
+ ) -> Option<ConstParamId> {
+ let container: ChildContainer = self.find_generic_param_container(src.syntax())?.into();
+ let dyn_map = self.cache_for(container, src.file_id);
+ dyn_map[keys::CONST_PARAM].get(&src.value).copied().map(|x| ConstParamId::from_unchecked(x))
+ }
+
+ pub(super) fn generic_param_to_def(
+ &mut self,
+ InFile { file_id, value }: InFile<ast::GenericParam>,
+ ) -> Option<GenericParamId> {
+ match value {
+ ast::GenericParam::ConstParam(it) => {
+ self.const_param_to_def(InFile::new(file_id, it)).map(GenericParamId::ConstParamId)
+ }
+ ast::GenericParam::LifetimeParam(it) => self
+ .lifetime_param_to_def(InFile::new(file_id, it))
+ .map(GenericParamId::LifetimeParamId),
+ ast::GenericParam::TypeParam(it) => {
+ self.type_param_to_def(InFile::new(file_id, it)).map(GenericParamId::TypeParamId)
+ }
+ }
+ }
+
+ pub(super) fn macro_to_def(&mut self, src: InFile<ast::Macro>) -> Option<MacroId> {
+ self.dyn_map(src.as_ref()).and_then(|it| match &src.value {
+ ast::Macro::MacroRules(value) => {
+ it[keys::MACRO_RULES].get(value).copied().map(MacroId::from)
+ }
+ ast::Macro::MacroDef(value) => it[keys::MACRO2].get(value).copied().map(MacroId::from),
+ })
+ }
+
+ pub(super) fn proc_macro_to_def(&mut self, src: InFile<ast::Fn>) -> Option<MacroId> {
+ self.dyn_map(src.as_ref())
+ .and_then(|it| it[keys::PROC_MACRO].get(&src.value).copied().map(MacroId::from))
+ }
+
+ pub(super) fn find_container(&mut self, src: InFile<&SyntaxNode>) -> Option<ChildContainer> {
+ for container in src.ancestors_with_macros_skip_attr_item(self.db.upcast()) {
+ if let Some(res) = self.container_to_def(container) {
+ return Some(res);
+ }
+ }
+
+ let def = self.file_to_def(src.file_id.original_file(self.db.upcast())).get(0).copied()?;
+ Some(def.into())
+ }
+
+ fn container_to_def(&mut self, container: InFile<SyntaxNode>) -> Option<ChildContainer> {
+ let cont = if let Some(item) = ast::Item::cast(container.value.clone()) {
+ match item {
+ ast::Item::Module(it) => self.module_to_def(container.with_value(it))?.into(),
+ ast::Item::Trait(it) => self.trait_to_def(container.with_value(it))?.into(),
+ ast::Item::Impl(it) => self.impl_to_def(container.with_value(it))?.into(),
+ ast::Item::Enum(it) => self.enum_to_def(container.with_value(it))?.into(),
+ ast::Item::TypeAlias(it) => {
+ self.type_alias_to_def(container.with_value(it))?.into()
+ }
+ ast::Item::Struct(it) => {
+ let def = self.struct_to_def(container.with_value(it))?;
+ VariantId::from(def).into()
+ }
+ ast::Item::Union(it) => {
+ let def = self.union_to_def(container.with_value(it))?;
+ VariantId::from(def).into()
+ }
+ ast::Item::Fn(it) => {
+ let def = self.fn_to_def(container.with_value(it))?;
+ DefWithBodyId::from(def).into()
+ }
+ ast::Item::Static(it) => {
+ let def = self.static_to_def(container.with_value(it))?;
+ DefWithBodyId::from(def).into()
+ }
+ ast::Item::Const(it) => {
+ let def = self.const_to_def(container.with_value(it))?;
+ DefWithBodyId::from(def).into()
+ }
+ _ => return None,
+ }
+ } else {
+ let it = ast::Variant::cast(container.value)?;
+ let def = self.enum_variant_to_def(InFile::new(container.file_id, it))?;
++ DefWithBodyId::from(def).into()
+ };
+ Some(cont)
+ }
+
+ fn find_generic_param_container(&mut self, src: InFile<&SyntaxNode>) -> Option<GenericDefId> {
+ let ancestors = src.ancestors_with_macros_skip_attr_item(self.db.upcast());
+ for InFile { file_id, value } in ancestors {
+ let item = match ast::Item::cast(value) {
+ Some(it) => it,
+ None => continue,
+ };
+ let res: GenericDefId = match item {
+ ast::Item::Fn(it) => self.fn_to_def(InFile::new(file_id, it))?.into(),
+ ast::Item::Struct(it) => self.struct_to_def(InFile::new(file_id, it))?.into(),
+ ast::Item::Enum(it) => self.enum_to_def(InFile::new(file_id, it))?.into(),
+ ast::Item::Trait(it) => self.trait_to_def(InFile::new(file_id, it))?.into(),
+ ast::Item::TypeAlias(it) => {
+ self.type_alias_to_def(InFile::new(file_id, it))?.into()
+ }
+ ast::Item::Impl(it) => self.impl_to_def(InFile::new(file_id, it))?.into(),
+ _ => continue,
+ };
+ return Some(res);
+ }
+ None
+ }
+
+ fn find_pat_or_label_container(&mut self, src: InFile<&SyntaxNode>) -> Option<DefWithBodyId> {
+ let ancestors = src.ancestors_with_macros_skip_attr_item(self.db.upcast());
+ for InFile { file_id, value } in ancestors {
+ let item = match ast::Item::cast(value) {
+ Some(it) => it,
+ None => continue,
+ };
+ let res: DefWithBodyId = match item {
+ ast::Item::Const(it) => self.const_to_def(InFile::new(file_id, it))?.into(),
+ ast::Item::Static(it) => self.static_to_def(InFile::new(file_id, it))?.into(),
+ ast::Item::Fn(it) => self.fn_to_def(InFile::new(file_id, it))?.into(),
+ _ => continue,
+ };
+ return Some(res);
+ }
+ None
+ }
+}
+
+#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
+pub(crate) enum ChildContainer {
+ DefWithBodyId(DefWithBodyId),
+ ModuleId(ModuleId),
+ TraitId(TraitId),
+ ImplId(ImplId),
+ EnumId(EnumId),
+ VariantId(VariantId),
+ TypeAliasId(TypeAliasId),
+ /// XXX: this might be the same def as, for example an `EnumId`. However,
+ /// here the children are generic parameters, and not, eg enum variants.
+ GenericDefId(GenericDefId),
+}
+impl_from! {
+ DefWithBodyId,
+ ModuleId,
+ TraitId,
+ ImplId,
+ EnumId,
+ VariantId,
+ TypeAliasId,
+ GenericDefId
+ for ChildContainer
+}
+
+impl ChildContainer {
+ fn child_by_source(self, db: &dyn HirDatabase, file_id: HirFileId) -> DynMap {
+ let db = db.upcast();
+ match self {
+ ChildContainer::DefWithBodyId(it) => it.child_by_source(db, file_id),
+ ChildContainer::ModuleId(it) => it.child_by_source(db, file_id),
+ ChildContainer::TraitId(it) => it.child_by_source(db, file_id),
+ ChildContainer::ImplId(it) => it.child_by_source(db, file_id),
+ ChildContainer::EnumId(it) => it.child_by_source(db, file_id),
+ ChildContainer::VariantId(it) => it.child_by_source(db, file_id),
+ ChildContainer::TypeAliasId(_) => DynMap::default(),
+ ChildContainer::GenericDefId(it) => it.child_by_source(db, file_id),
+ }
+ }
+}
--- /dev/null
- Lookup, ModuleDefId, VariantId,
+//! Lookup hir elements using positions in the source code. This is a lossy
+//! transformation: in general, a single source might correspond to several
+//! modules, functions, etc, due to macros, cfgs and `#[path=]` attributes on
+//! modules.
+//!
+//! So, this modules should not be used during hir construction, it exists
+//! purely for "IDE needs".
+use std::{
+ iter::{self, once},
+ sync::Arc,
+};
+
+use hir_def::{
+ body::{
+ self,
+ scope::{ExprScopes, ScopeId},
+ Body, BodySourceMap,
+ },
+ expr::{ExprId, Pat, PatId},
+ macro_id_to_def_id,
+ path::{ModPath, Path, PathKind},
+ resolver::{resolver_for_scope, Resolver, TypeNs, ValueNs},
+ type_ref::Mutability,
+ AsMacroCall, AssocItemId, DefWithBodyId, FieldId, FunctionId, ItemContainerId, LocalFieldId,
- let substs = hir_ty::TyBuilder::subst_for_def(db, poll_fn).push(ty.clone()).build();
++ Lookup, ModuleDefId, TraitId, VariantId,
+};
+use hir_expand::{
+ builtin_fn_macro::BuiltinFnLikeExpander,
+ hygiene::Hygiene,
+ mod_path::path,
+ name,
+ name::{AsName, Name},
+ HirFileId, InFile,
+};
+use hir_ty::{
+ diagnostics::{
+ record_literal_missing_fields, record_pattern_missing_fields, unsafe_expressions,
+ UnsafeExpr,
+ },
+ method_resolution::{self, lang_names_for_bin_op},
+ Adjust, Adjustment, AutoBorrow, InferenceResult, Interner, Substitution, Ty, TyExt, TyKind,
+ TyLoweringContext,
+};
+use itertools::Itertools;
+use smallvec::SmallVec;
+use syntax::{
+ ast::{self, AstNode},
+ SyntaxKind, SyntaxNode, TextRange, TextSize,
+};
+
+use crate::{
+ db::HirDatabase, semantics::PathResolution, Adt, AssocItem, BindingMode, BuiltinAttr,
+ BuiltinType, Callable, Const, DeriveHelper, Field, Function, Local, Macro, ModuleDef, Static,
+ Struct, ToolModule, Trait, Type, TypeAlias, Variant,
+};
+
+/// `SourceAnalyzer` is a convenience wrapper which exposes HIR API in terms of
+/// original source files. It should not be used inside the HIR itself.
+#[derive(Debug)]
+pub(crate) struct SourceAnalyzer {
+ pub(crate) file_id: HirFileId,
+ pub(crate) resolver: Resolver,
+ def: Option<(DefWithBodyId, Arc<Body>, Arc<BodySourceMap>)>,
+ infer: Option<Arc<InferenceResult>>,
+}
+
+impl SourceAnalyzer {
+ pub(crate) fn new_for_body(
+ db: &dyn HirDatabase,
+ def: DefWithBodyId,
+ node @ InFile { file_id, .. }: InFile<&SyntaxNode>,
+ offset: Option<TextSize>,
+ ) -> SourceAnalyzer {
+ let (body, source_map) = db.body_with_source_map(def);
+ let scopes = db.expr_scopes(def);
+ let scope = match offset {
+ None => scope_for(&scopes, &source_map, node),
+ Some(offset) => scope_for_offset(db, &scopes, &source_map, node.file_id, offset),
+ };
+ let resolver = resolver_for_scope(db.upcast(), def, scope);
+ SourceAnalyzer {
+ resolver,
+ def: Some((def, body, source_map)),
+ infer: Some(db.infer(def)),
+ file_id,
+ }
+ }
+
+ pub(crate) fn new_for_body_no_infer(
+ db: &dyn HirDatabase,
+ def: DefWithBodyId,
+ node @ InFile { file_id, .. }: InFile<&SyntaxNode>,
+ offset: Option<TextSize>,
+ ) -> SourceAnalyzer {
+ let (body, source_map) = db.body_with_source_map(def);
+ let scopes = db.expr_scopes(def);
+ let scope = match offset {
+ None => scope_for(&scopes, &source_map, node),
+ Some(offset) => scope_for_offset(db, &scopes, &source_map, node.file_id, offset),
+ };
+ let resolver = resolver_for_scope(db.upcast(), def, scope);
+ SourceAnalyzer { resolver, def: Some((def, body, source_map)), infer: None, file_id }
+ }
+
+ pub(crate) fn new_for_resolver(
+ resolver: Resolver,
+ node: InFile<&SyntaxNode>,
+ ) -> SourceAnalyzer {
+ SourceAnalyzer { resolver, def: None, infer: None, file_id: node.file_id }
+ }
+
+ fn body_source_map(&self) -> Option<&BodySourceMap> {
+ self.def.as_ref().map(|(.., source_map)| &**source_map)
+ }
+ fn body(&self) -> Option<&Body> {
+ self.def.as_ref().map(|(_, body, _)| &**body)
+ }
+
+ fn expr_id(&self, db: &dyn HirDatabase, expr: &ast::Expr) -> Option<ExprId> {
+ let src = match expr {
+ ast::Expr::MacroExpr(expr) => {
+ self.expand_expr(db, InFile::new(self.file_id, expr.macro_call()?.clone()))?
+ }
+ _ => InFile::new(self.file_id, expr.clone()),
+ };
+ let sm = self.body_source_map()?;
+ sm.node_expr(src.as_ref())
+ }
+
+ fn pat_id(&self, pat: &ast::Pat) -> Option<PatId> {
+ // FIXME: macros, see `expr_id`
+ let src = InFile { file_id: self.file_id, value: pat };
+ self.body_source_map()?.node_pat(src)
+ }
+
+ fn expand_expr(
+ &self,
+ db: &dyn HirDatabase,
+ expr: InFile<ast::MacroCall>,
+ ) -> Option<InFile<ast::Expr>> {
+ let macro_file = self.body_source_map()?.node_macro_file(expr.as_ref())?;
+ let expanded = db.parse_or_expand(macro_file)?;
+ let res = if let Some(stmts) = ast::MacroStmts::cast(expanded.clone()) {
+ match stmts.expr()? {
+ ast::Expr::MacroExpr(mac) => {
+ self.expand_expr(db, InFile::new(macro_file, mac.macro_call()?))?
+ }
+ expr => InFile::new(macro_file, expr),
+ }
+ } else if let Some(call) = ast::MacroCall::cast(expanded.clone()) {
+ self.expand_expr(db, InFile::new(macro_file, call))?
+ } else {
+ InFile::new(macro_file, ast::Expr::cast(expanded)?)
+ };
+
+ Some(res)
+ }
+
+ pub(crate) fn is_implicit_reborrow(
+ &self,
+ db: &dyn HirDatabase,
+ expr: &ast::Expr,
+ ) -> Option<Mutability> {
+ let expr_id = self.expr_id(db, expr)?;
+ let infer = self.infer.as_ref()?;
+ let adjustments = infer.expr_adjustments.get(&expr_id)?;
+ adjustments.windows(2).find_map(|slice| match slice {
+ &[Adjustment {kind: Adjust::Deref(None), ..}, Adjustment {kind: Adjust::Borrow(AutoBorrow::Ref(m)), ..}] => Some(match m {
+ hir_ty::Mutability::Mut => Mutability::Mut,
+ hir_ty::Mutability::Not => Mutability::Shared,
+ }),
+ _ => None,
+ })
+ }
+
+ pub(crate) fn type_of_expr(
+ &self,
+ db: &dyn HirDatabase,
+ expr: &ast::Expr,
+ ) -> Option<(Type, Option<Type>)> {
+ let expr_id = self.expr_id(db, expr)?;
+ let infer = self.infer.as_ref()?;
+ let coerced = infer
+ .expr_adjustments
+ .get(&expr_id)
+ .and_then(|adjusts| adjusts.last().map(|adjust| adjust.target.clone()));
+ let ty = infer[expr_id].clone();
+ let mk_ty = |ty| Type::new_with_resolver(db, &self.resolver, ty);
+ Some((mk_ty(ty), coerced.map(mk_ty)))
+ }
+
+ pub(crate) fn type_of_pat(
+ &self,
+ db: &dyn HirDatabase,
+ pat: &ast::Pat,
+ ) -> Option<(Type, Option<Type>)> {
+ let pat_id = self.pat_id(pat)?;
+ let infer = self.infer.as_ref()?;
+ let coerced = infer
+ .pat_adjustments
+ .get(&pat_id)
+ .and_then(|adjusts| adjusts.last().map(|adjust| adjust.clone()));
+ let ty = infer[pat_id].clone();
+ let mk_ty = |ty| Type::new_with_resolver(db, &self.resolver, ty);
+ Some((mk_ty(ty), coerced.map(mk_ty)))
+ }
+
+ pub(crate) fn type_of_self(
+ &self,
+ db: &dyn HirDatabase,
+ param: &ast::SelfParam,
+ ) -> Option<Type> {
+ let src = InFile { file_id: self.file_id, value: param };
+ let pat_id = self.body_source_map()?.node_self_param(src)?;
+ let ty = self.infer.as_ref()?[pat_id].clone();
+ Some(Type::new_with_resolver(db, &self.resolver, ty))
+ }
+
+ pub(crate) fn binding_mode_of_pat(
+ &self,
+ _db: &dyn HirDatabase,
+ pat: &ast::IdentPat,
+ ) -> Option<BindingMode> {
+ let pat_id = self.pat_id(&pat.clone().into())?;
+ let infer = self.infer.as_ref()?;
+ infer.pat_binding_modes.get(&pat_id).map(|bm| match bm {
+ hir_ty::BindingMode::Move => BindingMode::Move,
+ hir_ty::BindingMode::Ref(hir_ty::Mutability::Mut) => BindingMode::Ref(Mutability::Mut),
+ hir_ty::BindingMode::Ref(hir_ty::Mutability::Not) => {
+ BindingMode::Ref(Mutability::Shared)
+ }
+ })
+ }
+ pub(crate) fn pattern_adjustments(
+ &self,
+ db: &dyn HirDatabase,
+ pat: &ast::Pat,
+ ) -> Option<SmallVec<[Type; 1]>> {
+ let pat_id = self.pat_id(&pat)?;
+ let infer = self.infer.as_ref()?;
+ Some(
+ infer
+ .pat_adjustments
+ .get(&pat_id)?
+ .iter()
+ .map(|ty| Type::new_with_resolver(db, &self.resolver, ty.clone()))
+ .collect(),
+ )
+ }
+
+ pub(crate) fn resolve_method_call_as_callable(
+ &self,
+ db: &dyn HirDatabase,
+ call: &ast::MethodCallExpr,
+ ) -> Option<Callable> {
+ let expr_id = self.expr_id(db, &call.clone().into())?;
+ let (func, substs) = self.infer.as_ref()?.method_resolution(expr_id)?;
+ let ty = db.value_ty(func.into()).substitute(Interner, &substs);
+ let ty = Type::new_with_resolver(db, &self.resolver, ty);
+ let mut res = ty.as_callable(db)?;
+ res.is_bound_method = true;
+ Some(res)
+ }
+
+ pub(crate) fn resolve_method_call(
+ &self,
+ db: &dyn HirDatabase,
+ call: &ast::MethodCallExpr,
+ ) -> Option<FunctionId> {
+ let expr_id = self.expr_id(db, &call.clone().into())?;
+ let (f_in_trait, substs) = self.infer.as_ref()?.method_resolution(expr_id)?;
+
+ Some(self.resolve_impl_method_or_trait_def(db, f_in_trait, &substs))
+ }
+
+ pub(crate) fn resolve_await_to_poll(
+ &self,
+ db: &dyn HirDatabase,
+ await_expr: &ast::AwaitExpr,
+ ) -> Option<FunctionId> {
+ let mut ty = self.ty_of_expr(db, &await_expr.expr()?.into())?.clone();
+
+ let into_future_trait = self
+ .resolver
+ .resolve_known_trait(db.upcast(), &path![core::future::IntoFuture])
+ .map(Trait::from);
+
+ if let Some(into_future_trait) = into_future_trait {
+ let type_ = Type::new_with_resolver(db, &self.resolver, ty.clone());
+ if type_.impls_trait(db, into_future_trait, &[]) {
+ let items = into_future_trait.items(db);
+ let into_future_type = items.into_iter().find_map(|item| match item {
+ AssocItem::TypeAlias(alias)
+ if alias.name(db) == hir_expand::name![IntoFuture] =>
+ {
+ Some(alias)
+ }
+ _ => None,
+ })?;
+ let future_trait = type_.normalize_trait_assoc_type(db, &[], into_future_type)?;
+ ty = future_trait.ty;
+ }
+ }
+
++ let future_trait = db
++ .lang_item(self.resolver.krate(), hir_expand::name![future_trait].to_smol_str())?
++ .as_trait()?;
+ let poll_fn = db
+ .lang_item(self.resolver.krate(), hir_expand::name![poll].to_smol_str())?
+ .as_function()?;
- let op_fn = self.lang_trait_fn(db, &lang_item_name, &lang_item_name)?;
- let substs = hir_ty::TyBuilder::subst_for_def(db, op_fn).push(ty.clone()).build();
++ // HACK: subst for `poll()` coincides with that for `Future` because `poll()` itself
++ // doesn't have any generic parameters, so we skip building another subst for `poll()`.
++ let substs = hir_ty::TyBuilder::subst_for_def(db, future_trait, None).push(ty).build();
+ Some(self.resolve_impl_method_or_trait_def(db, poll_fn, &substs))
+ }
+
+ pub(crate) fn resolve_prefix_expr(
+ &self,
+ db: &dyn HirDatabase,
+ prefix_expr: &ast::PrefixExpr,
+ ) -> Option<FunctionId> {
+ let lang_item_name = match prefix_expr.op_kind()? {
+ ast::UnaryOp::Deref => name![deref],
+ ast::UnaryOp::Not => name![not],
+ ast::UnaryOp::Neg => name![neg],
+ };
+ let ty = self.ty_of_expr(db, &prefix_expr.expr()?.into())?;
+
- let op_fn = self.lang_trait_fn(db, &lang_item_name, &lang_item_name)?;
- let substs = hir_ty::TyBuilder::subst_for_def(db, op_fn)
++ let (op_trait, op_fn) = self.lang_trait_fn(db, &lang_item_name, &lang_item_name)?;
++ // HACK: subst for all methods coincides with that for their trait because the methods
++ // don't have any generic parameters, so we skip building another subst for the methods.
++ let substs = hir_ty::TyBuilder::subst_for_def(db, op_trait, None).push(ty.clone()).build();
+
+ Some(self.resolve_impl_method_or_trait_def(db, op_fn, &substs))
+ }
+
+ pub(crate) fn resolve_index_expr(
+ &self,
+ db: &dyn HirDatabase,
+ index_expr: &ast::IndexExpr,
+ ) -> Option<FunctionId> {
+ let base_ty = self.ty_of_expr(db, &index_expr.base()?.into())?;
+ let index_ty = self.ty_of_expr(db, &index_expr.index()?.into())?;
+
+ let lang_item_name = name![index];
+
- let op_fn = lang_names_for_bin_op(op)
++ let (op_trait, op_fn) = self.lang_trait_fn(db, &lang_item_name, &lang_item_name)?;
++ // HACK: subst for all methods coincides with that for their trait because the methods
++ // don't have any generic parameters, so we skip building another subst for the methods.
++ let substs = hir_ty::TyBuilder::subst_for_def(db, op_trait, None)
+ .push(base_ty.clone())
+ .push(index_ty.clone())
+ .build();
+ Some(self.resolve_impl_method_or_trait_def(db, op_fn, &substs))
+ }
+
+ pub(crate) fn resolve_bin_expr(
+ &self,
+ db: &dyn HirDatabase,
+ binop_expr: &ast::BinExpr,
+ ) -> Option<FunctionId> {
+ let op = binop_expr.op_kind()?;
+ let lhs = self.ty_of_expr(db, &binop_expr.lhs()?.into())?;
+ let rhs = self.ty_of_expr(db, &binop_expr.rhs()?.into())?;
+
- let substs =
- hir_ty::TyBuilder::subst_for_def(db, op_fn).push(lhs.clone()).push(rhs.clone()).build();
++ let (op_trait, op_fn) = lang_names_for_bin_op(op)
+ .and_then(|(name, lang_item)| self.lang_trait_fn(db, &lang_item, &name))?;
- let substs = hir_ty::TyBuilder::subst_for_def(db, op_fn).push(ty.clone()).build();
++ // HACK: subst for `index()` coincides with that for `Index` because `index()` itself
++ // doesn't have any generic parameters, so we skip building another subst for `index()`.
++ let substs = hir_ty::TyBuilder::subst_for_def(db, op_trait, None)
++ .push(lhs.clone())
++ .push(rhs.clone())
++ .build();
+
+ Some(self.resolve_impl_method_or_trait_def(db, op_fn, &substs))
+ }
+
+ pub(crate) fn resolve_try_expr(
+ &self,
+ db: &dyn HirDatabase,
+ try_expr: &ast::TryExpr,
+ ) -> Option<FunctionId> {
+ let ty = self.ty_of_expr(db, &try_expr.expr()?.into())?;
+
+ let op_fn =
+ db.lang_item(self.resolver.krate(), name![branch].to_smol_str())?.as_function()?;
- ) -> Option<FunctionId> {
- db.trait_data(db.lang_item(self.resolver.krate(), lang_trait.to_smol_str())?.as_trait()?)
- .method_by_name(method_name)
++ let op_trait = match op_fn.lookup(db.upcast()).container {
++ ItemContainerId::TraitId(id) => id,
++ _ => return None,
++ };
++ // HACK: subst for `branch()` coincides with that for `Try` because `branch()` itself
++ // doesn't have any generic parameters, so we skip building another subst for `branch()`.
++ let substs = hir_ty::TyBuilder::subst_for_def(db, op_trait, None).push(ty.clone()).build();
+
+ Some(self.resolve_impl_method_or_trait_def(db, op_fn, &substs))
+ }
+
+ pub(crate) fn resolve_field(
+ &self,
+ db: &dyn HirDatabase,
+ field: &ast::FieldExpr,
+ ) -> Option<Field> {
+ let expr_id = self.expr_id(db, &field.clone().into())?;
+ self.infer.as_ref()?.field_resolution(expr_id).map(|it| it.into())
+ }
+
+ pub(crate) fn resolve_record_field(
+ &self,
+ db: &dyn HirDatabase,
+ field: &ast::RecordExprField,
+ ) -> Option<(Field, Option<Local>, Type)> {
+ let record_expr = ast::RecordExpr::cast(field.syntax().parent().and_then(|p| p.parent())?)?;
+ let expr = ast::Expr::from(record_expr);
+ let expr_id = self.body_source_map()?.node_expr(InFile::new(self.file_id, &expr))?;
+
+ let local_name = field.field_name()?.as_name();
+ let local = if field.name_ref().is_some() {
+ None
+ } else {
+ // Shorthand syntax, resolve to the local
+ let path = ModPath::from_segments(PathKind::Plain, once(local_name.clone()));
+ match self.resolver.resolve_path_in_value_ns_fully(db.upcast(), &path) {
+ Some(ValueNs::LocalBinding(pat_id)) => {
+ Some(Local { pat_id, parent: self.resolver.body_owner()? })
+ }
+ _ => None,
+ }
+ };
+ let (_, subst) = self.infer.as_ref()?.type_of_expr.get(expr_id)?.as_adt()?;
+ let variant = self.infer.as_ref()?.variant_resolution_for_expr(expr_id)?;
+ let variant_data = variant.variant_data(db.upcast());
+ let field = FieldId { parent: variant, local_id: variant_data.field(&local_name)? };
+ let field_ty =
+ db.field_types(variant).get(field.local_id)?.clone().substitute(Interner, subst);
+ Some((field.into(), local, Type::new_with_resolver(db, &self.resolver, field_ty)))
+ }
+
+ pub(crate) fn resolve_record_pat_field(
+ &self,
+ db: &dyn HirDatabase,
+ field: &ast::RecordPatField,
+ ) -> Option<Field> {
+ let field_name = field.field_name()?.as_name();
+ let record_pat = ast::RecordPat::cast(field.syntax().parent().and_then(|p| p.parent())?)?;
+ let pat_id = self.pat_id(&record_pat.into())?;
+ let variant = self.infer.as_ref()?.variant_resolution_for_pat(pat_id)?;
+ let variant_data = variant.variant_data(db.upcast());
+ let field = FieldId { parent: variant, local_id: variant_data.field(&field_name)? };
+ Some(field.into())
+ }
+
+ pub(crate) fn resolve_macro_call(
+ &self,
+ db: &dyn HirDatabase,
+ macro_call: InFile<&ast::MacroCall>,
+ ) -> Option<Macro> {
+ let ctx = body::LowerCtx::new(db.upcast(), macro_call.file_id);
+ let path = macro_call.value.path().and_then(|ast| Path::from_src(ast, &ctx))?;
+ self.resolver.resolve_path_as_macro(db.upcast(), path.mod_path()).map(|it| it.into())
+ }
+
+ pub(crate) fn resolve_bind_pat_to_const(
+ &self,
+ db: &dyn HirDatabase,
+ pat: &ast::IdentPat,
+ ) -> Option<ModuleDef> {
+ let pat_id = self.pat_id(&pat.clone().into())?;
+ let body = self.body()?;
+ let path = match &body[pat_id] {
+ Pat::Path(path) => path,
+ _ => return None,
+ };
+ let res = resolve_hir_path(db, &self.resolver, path)?;
+ match res {
+ PathResolution::Def(def) => Some(def),
+ _ => None,
+ }
+ }
+
+ pub(crate) fn resolve_path(
+ &self,
+ db: &dyn HirDatabase,
+ path: &ast::Path,
+ ) -> Option<PathResolution> {
+ let parent = path.syntax().parent();
+ let parent = || parent.clone();
+
+ let mut prefer_value_ns = false;
+ let resolved = (|| {
+ if let Some(path_expr) = parent().and_then(ast::PathExpr::cast) {
+ let expr_id = self.expr_id(db, &path_expr.into())?;
+ let infer = self.infer.as_ref()?;
+ if let Some(assoc) = infer.assoc_resolutions_for_expr(expr_id) {
+ let assoc = match assoc {
+ AssocItemId::FunctionId(f_in_trait) => {
+ match infer.type_of_expr.get(expr_id) {
+ None => assoc,
+ Some(func_ty) => {
+ if let TyKind::FnDef(_fn_def, subs) = func_ty.kind(Interner) {
+ self.resolve_impl_method(db, f_in_trait, subs)
+ .map(AssocItemId::FunctionId)
+ .unwrap_or(assoc)
+ } else {
+ assoc
+ }
+ }
+ }
+ }
+
+ _ => assoc,
+ };
+
+ return Some(PathResolution::Def(AssocItem::from(assoc).into()));
+ }
+ if let Some(VariantId::EnumVariantId(variant)) =
+ infer.variant_resolution_for_expr(expr_id)
+ {
+ return Some(PathResolution::Def(ModuleDef::Variant(variant.into())));
+ }
+ prefer_value_ns = true;
+ } else if let Some(path_pat) = parent().and_then(ast::PathPat::cast) {
+ let pat_id = self.pat_id(&path_pat.into())?;
+ if let Some(assoc) = self.infer.as_ref()?.assoc_resolutions_for_pat(pat_id) {
+ return Some(PathResolution::Def(AssocItem::from(assoc).into()));
+ }
+ if let Some(VariantId::EnumVariantId(variant)) =
+ self.infer.as_ref()?.variant_resolution_for_pat(pat_id)
+ {
+ return Some(PathResolution::Def(ModuleDef::Variant(variant.into())));
+ }
+ } else if let Some(rec_lit) = parent().and_then(ast::RecordExpr::cast) {
+ let expr_id = self.expr_id(db, &rec_lit.into())?;
+ if let Some(VariantId::EnumVariantId(variant)) =
+ self.infer.as_ref()?.variant_resolution_for_expr(expr_id)
+ {
+ return Some(PathResolution::Def(ModuleDef::Variant(variant.into())));
+ }
+ } else {
+ let record_pat = parent().and_then(ast::RecordPat::cast).map(ast::Pat::from);
+ let tuple_struct_pat =
+ || parent().and_then(ast::TupleStructPat::cast).map(ast::Pat::from);
+ if let Some(pat) = record_pat.or_else(tuple_struct_pat) {
+ let pat_id = self.pat_id(&pat)?;
+ let variant_res_for_pat =
+ self.infer.as_ref()?.variant_resolution_for_pat(pat_id);
+ if let Some(VariantId::EnumVariantId(variant)) = variant_res_for_pat {
+ return Some(PathResolution::Def(ModuleDef::Variant(variant.into())));
+ }
+ }
+ }
+ None
+ })();
+ if let Some(_) = resolved {
+ return resolved;
+ }
+
+ // This must be a normal source file rather than macro file.
+ let hygiene = Hygiene::new(db.upcast(), self.file_id);
+ let ctx = body::LowerCtx::with_hygiene(db.upcast(), &hygiene);
+ let hir_path = Path::from_src(path.clone(), &ctx)?;
+
+ // Case where path is a qualifier of a use tree, e.g. foo::bar::{Baz, Qux} where we are
+ // trying to resolve foo::bar.
+ if let Some(use_tree) = parent().and_then(ast::UseTree::cast) {
+ if use_tree.coloncolon_token().is_some() {
+ return resolve_hir_path_qualifier(db, &self.resolver, &hir_path);
+ }
+ }
+
+ let meta_path = path
+ .syntax()
+ .ancestors()
+ .take_while(|it| {
+ let kind = it.kind();
+ ast::Path::can_cast(kind) || ast::Meta::can_cast(kind)
+ })
+ .last()
+ .and_then(ast::Meta::cast);
+
+ // Case where path is a qualifier of another path, e.g. foo::bar::Baz where we are
+ // trying to resolve foo::bar.
+ if path.parent_path().is_some() {
+ return match resolve_hir_path_qualifier(db, &self.resolver, &hir_path) {
+ None if meta_path.is_some() => {
+ path.first_segment().and_then(|it| it.name_ref()).and_then(|name_ref| {
+ ToolModule::by_name(db, self.resolver.krate().into(), &name_ref.text())
+ .map(PathResolution::ToolModule)
+ })
+ }
+ res => res,
+ };
+ } else if let Some(meta_path) = meta_path {
+ // Case where we are resolving the final path segment of a path in an attribute
+ // in this case we have to check for inert/builtin attributes and tools and prioritize
+ // resolution of attributes over other namespaces
+ if let Some(name_ref) = path.as_single_name_ref() {
+ let builtin =
+ BuiltinAttr::by_name(db, self.resolver.krate().into(), &name_ref.text());
+ if let Some(_) = builtin {
+ return builtin.map(PathResolution::BuiltinAttr);
+ }
+
+ if let Some(attr) = meta_path.parent_attr() {
+ let adt = if let Some(field) =
+ attr.syntax().parent().and_then(ast::RecordField::cast)
+ {
+ field.syntax().ancestors().take(4).find_map(ast::Adt::cast)
+ } else if let Some(field) =
+ attr.syntax().parent().and_then(ast::TupleField::cast)
+ {
+ field.syntax().ancestors().take(4).find_map(ast::Adt::cast)
+ } else if let Some(variant) =
+ attr.syntax().parent().and_then(ast::Variant::cast)
+ {
+ variant.syntax().ancestors().nth(2).and_then(ast::Adt::cast)
+ } else {
+ None
+ };
+ if let Some(adt) = adt {
+ let ast_id = db.ast_id_map(self.file_id).ast_id(&adt);
+ if let Some(helpers) = self
+ .resolver
+ .def_map()
+ .derive_helpers_in_scope(InFile::new(self.file_id, ast_id))
+ {
+ // FIXME: Multiple derives can have the same helper
+ let name_ref = name_ref.as_name();
+ for (macro_id, mut helpers) in
+ helpers.iter().group_by(|(_, macro_id, ..)| macro_id).into_iter()
+ {
+ if let Some(idx) = helpers.position(|(name, ..)| *name == name_ref)
+ {
+ return Some(PathResolution::DeriveHelper(DeriveHelper {
+ derive: *macro_id,
+ idx,
+ }));
+ }
+ }
+ }
+ }
+ }
+ }
+ return match resolve_hir_path_as_macro(db, &self.resolver, &hir_path) {
+ Some(m) => Some(PathResolution::Def(ModuleDef::Macro(m))),
+ // this labels any path that starts with a tool module as the tool itself, this is technically wrong
+ // but there is no benefit in differentiating these two cases for the time being
+ None => path.first_segment().and_then(|it| it.name_ref()).and_then(|name_ref| {
+ ToolModule::by_name(db, self.resolver.krate().into(), &name_ref.text())
+ .map(PathResolution::ToolModule)
+ }),
+ };
+ }
+ if parent().map_or(false, |it| ast::Visibility::can_cast(it.kind())) {
+ resolve_hir_path_qualifier(db, &self.resolver, &hir_path)
+ } else {
+ resolve_hir_path_(db, &self.resolver, &hir_path, prefer_value_ns)
+ }
+ }
+
+ pub(crate) fn record_literal_missing_fields(
+ &self,
+ db: &dyn HirDatabase,
+ literal: &ast::RecordExpr,
+ ) -> Option<Vec<(Field, Type)>> {
+ let body = self.body()?;
+ let infer = self.infer.as_ref()?;
+
+ let expr_id = self.expr_id(db, &literal.clone().into())?;
+ let substs = infer.type_of_expr[expr_id].as_adt()?.1;
+
+ let (variant, missing_fields, _exhaustive) =
+ record_literal_missing_fields(db, infer, expr_id, &body[expr_id])?;
+ let res = self.missing_fields(db, substs, variant, missing_fields);
+ Some(res)
+ }
+
+ pub(crate) fn record_pattern_missing_fields(
+ &self,
+ db: &dyn HirDatabase,
+ pattern: &ast::RecordPat,
+ ) -> Option<Vec<(Field, Type)>> {
+ let body = self.body()?;
+ let infer = self.infer.as_ref()?;
+
+ let pat_id = self.pat_id(&pattern.clone().into())?;
+ let substs = infer.type_of_pat[pat_id].as_adt()?.1;
+
+ let (variant, missing_fields, _exhaustive) =
+ record_pattern_missing_fields(db, infer, pat_id, &body[pat_id])?;
+ let res = self.missing_fields(db, substs, variant, missing_fields);
+ Some(res)
+ }
+
+ fn missing_fields(
+ &self,
+ db: &dyn HirDatabase,
+ substs: &Substitution,
+ variant: VariantId,
+ missing_fields: Vec<LocalFieldId>,
+ ) -> Vec<(Field, Type)> {
+ let field_types = db.field_types(variant);
+
+ missing_fields
+ .into_iter()
+ .map(|local_id| {
+ let field = FieldId { parent: variant, local_id };
+ let ty = field_types[local_id].clone().substitute(Interner, substs);
+ (field.into(), Type::new_with_resolver_inner(db, &self.resolver, ty))
+ })
+ .collect()
+ }
+
+ pub(crate) fn expand(
+ &self,
+ db: &dyn HirDatabase,
+ macro_call: InFile<&ast::MacroCall>,
+ ) -> Option<HirFileId> {
+ let krate = self.resolver.krate();
+ let macro_call_id = macro_call.as_call_id(db.upcast(), krate, |path| {
+ self.resolver
+ .resolve_path_as_macro(db.upcast(), &path)
+ .map(|it| macro_id_to_def_id(db.upcast(), it))
+ })?;
+ Some(macro_call_id.as_file()).filter(|it| it.expansion_level(db.upcast()) < 64)
+ }
+
+ pub(crate) fn resolve_variant(
+ &self,
+ db: &dyn HirDatabase,
+ record_lit: ast::RecordExpr,
+ ) -> Option<VariantId> {
+ let infer = self.infer.as_ref()?;
+ let expr_id = self.expr_id(db, &record_lit.into())?;
+ infer.variant_resolution_for_expr(expr_id)
+ }
+
+ pub(crate) fn is_unsafe_macro_call(
+ &self,
+ db: &dyn HirDatabase,
+ macro_call: InFile<&ast::MacroCall>,
+ ) -> bool {
+ // check for asm/global_asm
+ if let Some(mac) = self.resolve_macro_call(db, macro_call) {
+ let ex = match mac.id {
+ hir_def::MacroId::Macro2Id(it) => it.lookup(db.upcast()).expander,
+ hir_def::MacroId::MacroRulesId(it) => it.lookup(db.upcast()).expander,
+ _ => hir_def::MacroExpander::Declarative,
+ };
+ match ex {
+ hir_def::MacroExpander::BuiltIn(e)
+ if e == BuiltinFnLikeExpander::Asm || e == BuiltinFnLikeExpander::GlobalAsm =>
+ {
+ return true
+ }
+ _ => (),
+ }
+ }
+ let macro_expr = match macro_call
+ .map(|it| it.syntax().parent().and_then(ast::MacroExpr::cast))
+ .transpose()
+ {
+ Some(it) => it,
+ None => return false,
+ };
+
+ if let (Some((def, body, sm)), Some(infer)) = (&self.def, &self.infer) {
+ if let Some(expanded_expr) = sm.macro_expansion_expr(macro_expr.as_ref()) {
+ let mut is_unsafe = false;
+ unsafe_expressions(
+ db,
+ infer,
+ *def,
+ body,
+ expanded_expr,
+ &mut |UnsafeExpr { inside_unsafe_block, .. }| is_unsafe |= !inside_unsafe_block,
+ );
+ return is_unsafe;
+ }
+ }
+ false
+ }
+
+ fn resolve_impl_method(
+ &self,
+ db: &dyn HirDatabase,
+ func: FunctionId,
+ substs: &Substitution,
+ ) -> Option<FunctionId> {
+ let impled_trait = match func.lookup(db.upcast()).container {
+ ItemContainerId::TraitId(trait_id) => trait_id,
+ _ => return None,
+ };
+ if substs.is_empty(Interner) {
+ return None;
+ }
+ let self_ty = substs.at(Interner, 0).ty(Interner)?;
+ let krate = self.resolver.krate();
+ let trait_env = self.resolver.body_owner()?.as_generic_def_id().map_or_else(
+ || Arc::new(hir_ty::TraitEnvironment::empty(krate)),
+ |d| db.trait_environment(d),
+ );
+
+ let fun_data = db.function_data(func);
+ method_resolution::lookup_impl_method(self_ty, db, trait_env, impled_trait, &fun_data.name)
+ }
+
+ fn resolve_impl_method_or_trait_def(
+ &self,
+ db: &dyn HirDatabase,
+ func: FunctionId,
+ substs: &Substitution,
+ ) -> FunctionId {
+ self.resolve_impl_method(db, func, substs).unwrap_or(func)
+ }
+
+ fn lang_trait_fn(
+ &self,
+ db: &dyn HirDatabase,
+ lang_trait: &Name,
+ method_name: &Name,
++ ) -> Option<(TraitId, FunctionId)> {
++ let trait_id = db.lang_item(self.resolver.krate(), lang_trait.to_smol_str())?.as_trait()?;
++ let fn_id = db.trait_data(trait_id).method_by_name(method_name)?;
++ Some((trait_id, fn_id))
+ }
+
+ fn ty_of_expr(&self, db: &dyn HirDatabase, expr: &ast::Expr) -> Option<&Ty> {
+ self.infer.as_ref()?.type_of_expr.get(self.expr_id(db, &expr)?)
+ }
+}
+
+fn scope_for(
+ scopes: &ExprScopes,
+ source_map: &BodySourceMap,
+ node: InFile<&SyntaxNode>,
+) -> Option<ScopeId> {
+ node.value
+ .ancestors()
+ .filter_map(ast::Expr::cast)
+ .filter_map(|it| source_map.node_expr(InFile::new(node.file_id, &it)))
+ .find_map(|it| scopes.scope_for(it))
+}
+
+fn scope_for_offset(
+ db: &dyn HirDatabase,
+ scopes: &ExprScopes,
+ source_map: &BodySourceMap,
+ from_file: HirFileId,
+ offset: TextSize,
+) -> Option<ScopeId> {
+ scopes
+ .scope_by_expr()
+ .iter()
+ .filter_map(|(id, scope)| {
+ let InFile { file_id, value } = source_map.expr_syntax(*id).ok()?;
+ if from_file == file_id {
+ return Some((value.text_range(), scope));
+ }
+
+ // FIXME handle attribute expansion
+ let source = iter::successors(file_id.call_node(db.upcast()), |it| {
+ it.file_id.call_node(db.upcast())
+ })
+ .find(|it| it.file_id == from_file)
+ .filter(|it| it.value.kind() == SyntaxKind::MACRO_CALL)?;
+ Some((source.value.text_range(), scope))
+ })
+ .filter(|(expr_range, _scope)| expr_range.start() <= offset && offset <= expr_range.end())
+ // find containing scope
+ .min_by_key(|(expr_range, _scope)| expr_range.len())
+ .map(|(expr_range, scope)| {
+ adjust(db, scopes, source_map, expr_range, from_file, offset).unwrap_or(*scope)
+ })
+}
+
+// XXX: during completion, cursor might be outside of any particular
+// expression. Try to figure out the correct scope...
+fn adjust(
+ db: &dyn HirDatabase,
+ scopes: &ExprScopes,
+ source_map: &BodySourceMap,
+ expr_range: TextRange,
+ from_file: HirFileId,
+ offset: TextSize,
+) -> Option<ScopeId> {
+ let child_scopes = scopes
+ .scope_by_expr()
+ .iter()
+ .filter_map(|(id, scope)| {
+ let source = source_map.expr_syntax(*id).ok()?;
+ // FIXME: correctly handle macro expansion
+ if source.file_id != from_file {
+ return None;
+ }
+ let root = source.file_syntax(db.upcast());
+ let node = source.value.to_node(&root);
+ Some((node.syntax().text_range(), scope))
+ })
+ .filter(|&(range, _)| {
+ range.start() <= offset && expr_range.contains_range(range) && range != expr_range
+ });
+
+ child_scopes
+ .max_by(|&(r1, _), &(r2, _)| {
+ if r1.contains_range(r2) {
+ std::cmp::Ordering::Greater
+ } else if r2.contains_range(r1) {
+ std::cmp::Ordering::Less
+ } else {
+ r1.start().cmp(&r2.start())
+ }
+ })
+ .map(|(_ptr, scope)| *scope)
+}
+
+#[inline]
+pub(crate) fn resolve_hir_path(
+ db: &dyn HirDatabase,
+ resolver: &Resolver,
+ path: &Path,
+) -> Option<PathResolution> {
+ resolve_hir_path_(db, resolver, path, false)
+}
+
+#[inline]
+pub(crate) fn resolve_hir_path_as_macro(
+ db: &dyn HirDatabase,
+ resolver: &Resolver,
+ path: &Path,
+) -> Option<Macro> {
+ resolver.resolve_path_as_macro(db.upcast(), path.mod_path()).map(Into::into)
+}
+
+fn resolve_hir_path_(
+ db: &dyn HirDatabase,
+ resolver: &Resolver,
+ path: &Path,
+ prefer_value_ns: bool,
+) -> Option<PathResolution> {
+ let types = || {
+ let (ty, unresolved) = match path.type_anchor() {
+ Some(type_ref) => {
+ let (_, res) = TyLoweringContext::new(db, resolver).lower_ty_ext(type_ref);
+ res.map(|ty_ns| (ty_ns, path.segments().first()))
+ }
+ None => {
+ let (ty, remaining) =
+ resolver.resolve_path_in_type_ns(db.upcast(), path.mod_path())?;
+ match remaining {
+ Some(remaining) if remaining > 1 => {
+ if remaining + 1 == path.segments().len() {
+ Some((ty, path.segments().last()))
+ } else {
+ None
+ }
+ }
+ _ => Some((ty, path.segments().get(1))),
+ }
+ }
+ }?;
+
+ // If we are in a TypeNs for a Trait, and we have an unresolved name, try to resolve it as a type
+ // within the trait's associated types.
+ if let (Some(unresolved), &TypeNs::TraitId(trait_id)) = (&unresolved, &ty) {
+ if let Some(type_alias_id) =
+ db.trait_data(trait_id).associated_type_by_name(unresolved.name)
+ {
+ return Some(PathResolution::Def(ModuleDefId::from(type_alias_id).into()));
+ }
+ }
+
+ let res = match ty {
+ TypeNs::SelfType(it) => PathResolution::SelfType(it.into()),
+ TypeNs::GenericParam(id) => PathResolution::TypeParam(id.into()),
+ TypeNs::AdtSelfType(it) | TypeNs::AdtId(it) => {
+ PathResolution::Def(Adt::from(it).into())
+ }
+ TypeNs::EnumVariantId(it) => PathResolution::Def(Variant::from(it).into()),
+ TypeNs::TypeAliasId(it) => PathResolution::Def(TypeAlias::from(it).into()),
+ TypeNs::BuiltinType(it) => PathResolution::Def(BuiltinType::from(it).into()),
+ TypeNs::TraitId(it) => PathResolution::Def(Trait::from(it).into()),
+ };
+ match unresolved {
+ Some(unresolved) => resolver
+ .generic_def()
+ .and_then(|def| {
+ hir_ty::associated_type_shorthand_candidates(
+ db,
+ def,
+ res.in_type_ns()?,
+ |name, _, id| (name == unresolved.name).then(|| id),
+ )
+ })
+ .map(TypeAlias::from)
+ .map(Into::into)
+ .map(PathResolution::Def),
+ None => Some(res),
+ }
+ };
+
+ let body_owner = resolver.body_owner();
+ let values = || {
+ resolver.resolve_path_in_value_ns_fully(db.upcast(), path.mod_path()).and_then(|val| {
+ let res = match val {
+ ValueNs::LocalBinding(pat_id) => {
+ let var = Local { parent: body_owner?, pat_id };
+ PathResolution::Local(var)
+ }
+ ValueNs::FunctionId(it) => PathResolution::Def(Function::from(it).into()),
+ ValueNs::ConstId(it) => PathResolution::Def(Const::from(it).into()),
+ ValueNs::StaticId(it) => PathResolution::Def(Static::from(it).into()),
+ ValueNs::StructId(it) => PathResolution::Def(Struct::from(it).into()),
+ ValueNs::EnumVariantId(it) => PathResolution::Def(Variant::from(it).into()),
+ ValueNs::ImplSelf(impl_id) => PathResolution::SelfType(impl_id.into()),
+ ValueNs::GenericParam(id) => PathResolution::ConstParam(id.into()),
+ };
+ Some(res)
+ })
+ };
+
+ let items = || {
+ resolver
+ .resolve_module_path_in_items(db.upcast(), path.mod_path())
+ .take_types()
+ .map(|it| PathResolution::Def(it.into()))
+ };
+
+ let macros = || {
+ resolver
+ .resolve_path_as_macro(db.upcast(), path.mod_path())
+ .map(|def| PathResolution::Def(ModuleDef::Macro(def.into())))
+ };
+
+ if prefer_value_ns { values().or_else(types) } else { types().or_else(values) }
+ .or_else(items)
+ .or_else(macros)
+}
+
+/// Resolves a path where we know it is a qualifier of another path.
+///
+/// For example, if we have:
+/// ```
+/// mod my {
+/// pub mod foo {
+/// struct Bar;
+/// }
+///
+/// pub fn foo() {}
+/// }
+/// ```
+/// then we know that `foo` in `my::foo::Bar` refers to the module, not the function.
+fn resolve_hir_path_qualifier(
+ db: &dyn HirDatabase,
+ resolver: &Resolver,
+ path: &Path,
+) -> Option<PathResolution> {
+ resolver
+ .resolve_path_in_type_ns_fully(db.upcast(), path.mod_path())
+ .map(|ty| match ty {
+ TypeNs::SelfType(it) => PathResolution::SelfType(it.into()),
+ TypeNs::GenericParam(id) => PathResolution::TypeParam(id.into()),
+ TypeNs::AdtSelfType(it) | TypeNs::AdtId(it) => {
+ PathResolution::Def(Adt::from(it).into())
+ }
+ TypeNs::EnumVariantId(it) => PathResolution::Def(Variant::from(it).into()),
+ TypeNs::TypeAliasId(it) => PathResolution::Def(TypeAlias::from(it).into()),
+ TypeNs::BuiltinType(it) => PathResolution::Def(BuiltinType::from(it).into()),
+ TypeNs::TraitId(it) => PathResolution::Def(Trait::from(it).into()),
+ })
+ .or_else(|| {
+ resolver
+ .resolve_module_path_in_items(db.upcast(), path.mod_path())
+ .take_types()
+ .map(|it| PathResolution::Def(it.into()))
+ })
+}
--- /dev/null
+//! File symbol extraction.
+
+use base_db::FileRange;
+use hir_def::{
+ item_tree::ItemTreeNode, src::HasSource, AdtId, AssocItemId, AssocItemLoc, DefWithBodyId,
+ HasModule, ImplId, ItemContainerId, Lookup, MacroId, ModuleDefId, ModuleId, TraitId,
+};
+use hir_expand::{HirFileId, InFile};
+use hir_ty::db::HirDatabase;
+use syntax::{ast::HasName, AstNode, SmolStr, SyntaxNode, SyntaxNodePtr};
+
+use crate::{Module, Semantics};
+
+/// The actual data that is stored in the index. It should be as compact as
+/// possible.
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct FileSymbol {
+ pub name: SmolStr,
+ pub loc: DeclarationLocation,
+ pub kind: FileSymbolKind,
+ pub container_name: Option<SmolStr>,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct DeclarationLocation {
+ /// The file id for both the `ptr` and `name_ptr`.
+ pub hir_file_id: HirFileId,
+ /// This points to the whole syntax node of the declaration.
+ pub ptr: SyntaxNodePtr,
+ /// This points to the [`syntax::ast::Name`] identifier of the declaration.
+ pub name_ptr: SyntaxNodePtr,
+}
+
+impl DeclarationLocation {
+ pub fn syntax<DB: HirDatabase>(&self, sema: &Semantics<'_, DB>) -> Option<SyntaxNode> {
+ let root = sema.parse_or_expand(self.hir_file_id)?;
+ Some(self.ptr.to_node(&root))
+ }
+
+ pub fn original_range(&self, db: &dyn HirDatabase) -> Option<FileRange> {
+ let node = resolve_node(db, self.hir_file_id, &self.ptr)?;
+ Some(node.as_ref().original_file_range(db.upcast()))
+ }
+
+ pub fn original_name_range(&self, db: &dyn HirDatabase) -> Option<FileRange> {
+ let node = resolve_node(db, self.hir_file_id, &self.name_ptr)?;
+ node.as_ref().original_file_range_opt(db.upcast())
+ }
+}
+
+fn resolve_node(
+ db: &dyn HirDatabase,
+ file_id: HirFileId,
+ ptr: &SyntaxNodePtr,
+) -> Option<InFile<SyntaxNode>> {
+ let root = db.parse_or_expand(file_id)?;
+ let node = ptr.to_node(&root);
+ Some(InFile::new(file_id, node))
+}
+
+#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
+pub enum FileSymbolKind {
+ Const,
+ Enum,
+ Function,
+ Macro,
+ Module,
+ Static,
+ Struct,
+ Trait,
+ TypeAlias,
+ Union,
+}
+
+impl FileSymbolKind {
+ pub fn is_type(self: FileSymbolKind) -> bool {
+ matches!(
+ self,
+ FileSymbolKind::Struct
+ | FileSymbolKind::Enum
+ | FileSymbolKind::Trait
+ | FileSymbolKind::TypeAlias
+ | FileSymbolKind::Union
+ )
+ }
+}
+
+/// Represents an outstanding module that the symbol collector must collect symbols from.
+struct SymbolCollectorWork {
+ module_id: ModuleId,
+ parent: Option<DefWithBodyId>,
+}
+
+pub struct SymbolCollector<'a> {
+ db: &'a dyn HirDatabase,
+ symbols: Vec<FileSymbol>,
+ work: Vec<SymbolCollectorWork>,
+ current_container_name: Option<SmolStr>,
+}
+
+/// Given a [`ModuleId`] and a [`HirDatabase`], use the DefMap for the module's crate to collect
+/// all symbols that should be indexed for the given module.
+impl<'a> SymbolCollector<'a> {
+ pub fn collect(db: &dyn HirDatabase, module: Module) -> Vec<FileSymbol> {
+ let mut symbol_collector = SymbolCollector {
+ db,
+ symbols: Default::default(),
+ current_container_name: None,
+ // The initial work is the root module we're collecting, additional work will
+ // be populated as we traverse the module's definitions.
+ work: vec![SymbolCollectorWork { module_id: module.into(), parent: None }],
+ };
+
+ while let Some(work) = symbol_collector.work.pop() {
+ symbol_collector.do_work(work);
+ }
+
+ symbol_collector.symbols
+ }
+
+ fn do_work(&mut self, work: SymbolCollectorWork) {
+ self.db.unwind_if_cancelled();
+
+ let parent_name = work.parent.and_then(|id| self.def_with_body_id_name(id));
+ self.with_container_name(parent_name, |s| s.collect_from_module(work.module_id));
+ }
+
+ fn collect_from_module(&mut self, module_id: ModuleId) {
+ let def_map = module_id.def_map(self.db.upcast());
+ let scope = &def_map[module_id.local_id].scope;
+
+ for module_def_id in scope.declarations() {
+ match module_def_id {
+ ModuleDefId::ModuleId(id) => self.push_module(id),
+ ModuleDefId::FunctionId(id) => {
+ self.push_decl_assoc(id, FileSymbolKind::Function);
+ self.collect_from_body(id);
+ }
+ ModuleDefId::AdtId(AdtId::StructId(id)) => {
+ self.push_decl(id, FileSymbolKind::Struct)
+ }
+ ModuleDefId::AdtId(AdtId::EnumId(id)) => self.push_decl(id, FileSymbolKind::Enum),
+ ModuleDefId::AdtId(AdtId::UnionId(id)) => self.push_decl(id, FileSymbolKind::Union),
+ ModuleDefId::ConstId(id) => {
+ self.push_decl_assoc(id, FileSymbolKind::Const);
+ self.collect_from_body(id);
+ }
+ ModuleDefId::StaticId(id) => {
+ self.push_decl_assoc(id, FileSymbolKind::Static);
+ self.collect_from_body(id);
+ }
+ ModuleDefId::TraitId(id) => {
+ self.push_decl(id, FileSymbolKind::Trait);
+ self.collect_from_trait(id);
+ }
+ ModuleDefId::TypeAliasId(id) => {
+ self.push_decl_assoc(id, FileSymbolKind::TypeAlias);
+ }
+ ModuleDefId::MacroId(id) => match id {
+ MacroId::Macro2Id(id) => self.push_decl(id, FileSymbolKind::Macro),
+ MacroId::MacroRulesId(id) => self.push_decl(id, FileSymbolKind::Macro),
+ MacroId::ProcMacroId(id) => self.push_decl(id, FileSymbolKind::Macro),
+ },
+ // Don't index these.
+ ModuleDefId::BuiltinType(_) => {}
+ ModuleDefId::EnumVariantId(_) => {}
+ }
+ }
+
+ for impl_id in scope.impls() {
+ self.collect_from_impl(impl_id);
+ }
+
+ for const_id in scope.unnamed_consts() {
+ self.collect_from_body(const_id);
+ }
+
+ for (_, id) in scope.legacy_macros() {
+ for &id in id {
+ if id.module(self.db.upcast()) == module_id {
+ match id {
+ MacroId::Macro2Id(id) => self.push_decl(id, FileSymbolKind::Macro),
+ MacroId::MacroRulesId(id) => self.push_decl(id, FileSymbolKind::Macro),
+ MacroId::ProcMacroId(id) => self.push_decl(id, FileSymbolKind::Macro),
+ }
+ }
+ }
+ }
+ }
+
+ fn collect_from_body(&mut self, body_id: impl Into<DefWithBodyId>) {
+ let body_id = body_id.into();
+ let body = self.db.body(body_id);
+
+ // Descend into the blocks and enqueue collection of all modules within.
+ for (_, def_map) in body.blocks(self.db.upcast()) {
+ for (id, _) in def_map.modules() {
+ self.work.push(SymbolCollectorWork {
+ module_id: def_map.module_id(id),
+ parent: Some(body_id),
+ });
+ }
+ }
+ }
+
+ fn collect_from_impl(&mut self, impl_id: ImplId) {
+ let impl_data = self.db.impl_data(impl_id);
+ for &assoc_item_id in &impl_data.items {
+ self.push_assoc_item(assoc_item_id)
+ }
+ }
+
+ fn collect_from_trait(&mut self, trait_id: TraitId) {
+ let trait_data = self.db.trait_data(trait_id);
+ self.with_container_name(trait_data.name.as_text(), |s| {
+ for &(_, assoc_item_id) in &trait_data.items {
+ s.push_assoc_item(assoc_item_id);
+ }
+ });
+ }
+
+ fn with_container_name(&mut self, container_name: Option<SmolStr>, f: impl FnOnce(&mut Self)) {
+ if let Some(container_name) = container_name {
+ let prev = self.current_container_name.replace(container_name);
+ f(self);
+ self.current_container_name = prev;
+ } else {
+ f(self);
+ }
+ }
+
+ fn current_container_name(&self) -> Option<SmolStr> {
+ self.current_container_name.clone()
+ }
+
+ fn def_with_body_id_name(&self, body_id: DefWithBodyId) -> Option<SmolStr> {
+ match body_id {
+ DefWithBodyId::FunctionId(id) => Some(
+ id.lookup(self.db.upcast()).source(self.db.upcast()).value.name()?.text().into(),
+ ),
+ DefWithBodyId::StaticId(id) => Some(
+ id.lookup(self.db.upcast()).source(self.db.upcast()).value.name()?.text().into(),
+ ),
+ DefWithBodyId::ConstId(id) => Some(
+ id.lookup(self.db.upcast()).source(self.db.upcast()).value.name()?.text().into(),
+ ),
++ DefWithBodyId::VariantId(id) => Some({
++ let db = self.db.upcast();
++ id.parent.lookup(db).source(db).value.name()?.text().into()
++ }),
+ }
+ }
+
+ fn push_assoc_item(&mut self, assoc_item_id: AssocItemId) {
+ match assoc_item_id {
+ AssocItemId::FunctionId(id) => self.push_decl_assoc(id, FileSymbolKind::Function),
+ AssocItemId::ConstId(id) => self.push_decl_assoc(id, FileSymbolKind::Const),
+ AssocItemId::TypeAliasId(id) => self.push_decl_assoc(id, FileSymbolKind::TypeAlias),
+ }
+ }
+
+ fn push_decl_assoc<L, T>(&mut self, id: L, kind: FileSymbolKind)
+ where
+ L: Lookup<Data = AssocItemLoc<T>>,
+ T: ItemTreeNode,
+ <T as ItemTreeNode>::Source: HasName,
+ {
+ fn container_name(db: &dyn HirDatabase, container: ItemContainerId) -> Option<SmolStr> {
+ match container {
+ ItemContainerId::ModuleId(module_id) => {
+ let module = Module::from(module_id);
+ module.name(db).and_then(|name| name.as_text())
+ }
+ ItemContainerId::TraitId(trait_id) => {
+ let trait_data = db.trait_data(trait_id);
+ trait_data.name.as_text()
+ }
+ ItemContainerId::ImplId(_) | ItemContainerId::ExternBlockId(_) => None,
+ }
+ }
+
+ self.push_file_symbol(|s| {
+ let loc = id.lookup(s.db.upcast());
+ let source = loc.source(s.db.upcast());
+ let name_node = source.value.name()?;
+ let container_name =
+ container_name(s.db, loc.container).or_else(|| s.current_container_name());
+
+ Some(FileSymbol {
+ name: name_node.text().into(),
+ kind,
+ container_name,
+ loc: DeclarationLocation {
+ hir_file_id: source.file_id,
+ ptr: SyntaxNodePtr::new(source.value.syntax()),
+ name_ptr: SyntaxNodePtr::new(name_node.syntax()),
+ },
+ })
+ })
+ }
+
+ fn push_decl<L>(&mut self, id: L, kind: FileSymbolKind)
+ where
+ L: Lookup,
+ <L as Lookup>::Data: HasSource,
+ <<L as Lookup>::Data as HasSource>::Value: HasName,
+ {
+ self.push_file_symbol(|s| {
+ let loc = id.lookup(s.db.upcast());
+ let source = loc.source(s.db.upcast());
+ let name_node = source.value.name()?;
+
+ Some(FileSymbol {
+ name: name_node.text().into(),
+ kind,
+ container_name: s.current_container_name(),
+ loc: DeclarationLocation {
+ hir_file_id: source.file_id,
+ ptr: SyntaxNodePtr::new(source.value.syntax()),
+ name_ptr: SyntaxNodePtr::new(name_node.syntax()),
+ },
+ })
+ })
+ }
+
+ fn push_module(&mut self, module_id: ModuleId) {
+ self.push_file_symbol(|s| {
+ let def_map = module_id.def_map(s.db.upcast());
+ let module_data = &def_map[module_id.local_id];
+ let declaration = module_data.origin.declaration()?;
+ let module = declaration.to_node(s.db.upcast());
+ let name_node = module.name()?;
+
+ Some(FileSymbol {
+ name: name_node.text().into(),
+ kind: FileSymbolKind::Module,
+ container_name: s.current_container_name(),
+ loc: DeclarationLocation {
+ hir_file_id: declaration.file_id,
+ ptr: SyntaxNodePtr::new(module.syntax()),
+ name_ptr: SyntaxNodePtr::new(name_node.syntax()),
+ },
+ })
+ })
+ }
+
+ fn push_file_symbol(&mut self, f: impl FnOnce(&Self) -> Option<FileSymbol>) {
+ if let Some(file_symbol) = f(self) {
+ self.symbols.push(file_symbol);
+ }
+ }
+}
--- /dev/null
- tt.token_trees_and_tokens().filter_map(NodeOrToken::into_token).collect_vec();
+use crate::{AssistContext, Assists};
+use ide_db::{
+ assists::{AssistId, AssistKind},
+ syntax_helpers::{
+ format_string::is_format_string,
+ format_string_exprs::{parse_format_exprs, Arg},
+ },
+};
+use itertools::Itertools;
++use stdx::format_to;
+use syntax::{ast, AstNode, AstToken, NodeOrToken, SyntaxKind::COMMA, TextRange};
+
+// Assist: move_format_string_arg
+//
+// Move an expression out of a format string.
+//
+// ```
+// macro_rules! format_args {
+// ($lit:literal $(tt:tt)*) => { 0 },
+// }
+// macro_rules! print {
+// ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*)));
+// }
+//
+// fn main() {
+// print!("{x + 1}$0");
+// }
+// ```
+// ->
+// ```
+// macro_rules! format_args {
+// ($lit:literal $(tt:tt)*) => { 0 },
+// }
+// macro_rules! print {
+// ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*)));
+// }
+//
+// fn main() {
+// print!("{}"$0, x + 1);
+// }
+// ```
+
+pub(crate) fn move_format_string_arg(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let fmt_string = ctx.find_token_at_offset::<ast::String>()?;
+ let tt = fmt_string.syntax().parent().and_then(ast::TokenTree::cast)?;
+
+ let expanded_t = ast::String::cast(
+ ctx.sema.descend_into_macros_with_kind_preference(fmt_string.syntax().clone()),
+ )?;
+ if !is_format_string(&expanded_t) {
+ return None;
+ }
+
+ let (new_fmt, extracted_args) = parse_format_exprs(fmt_string.text()).ok()?;
+ if extracted_args.is_empty() {
+ return None;
+ }
+
+ acc.add(
+ AssistId(
+ "move_format_string_arg",
+ // if there aren't any expressions, then make the assist a RefactorExtract
+ if extracted_args.iter().filter(|f| matches!(f, Arg::Expr(_))).count() == 0 {
+ AssistKind::RefactorExtract
+ } else {
+ AssistKind::QuickFix
+ },
+ ),
+ "Extract format args",
+ tt.syntax().text_range(),
+ |edit| {
+ let fmt_range = fmt_string.syntax().text_range();
+
+ // Replace old format string with new format string whose arguments have been extracted
+ edit.replace(fmt_range, new_fmt);
+
+ // Insert cursor at end of format string
+ edit.insert(fmt_range.end(), "$0");
+
+ // Extract existing arguments in macro
+ let tokens =
- if let [_opening_bracket, format_string, _args_start_comma, tokens @ .., end_bracket] =
++ tt.token_trees_and_tokens().collect_vec();
+
+ let mut existing_args: Vec<String> = vec![];
+
+ let mut current_arg = String::new();
- if t.kind() == COMMA {
- existing_args.push(current_arg.trim().into());
- current_arg.clear();
- } else {
- current_arg.push_str(t.text());
++ if let [_opening_bracket, NodeOrToken::Token(format_string), _args_start_comma, tokens @ .., NodeOrToken::Token(end_bracket)] =
+ tokens.as_slice()
+ {
+ for t in tokens {
++ match t {
++ NodeOrToken::Node(n) => {
++ format_to!(current_arg, "{n}");
++ },
++ NodeOrToken::Token(t) if t.kind() == COMMA=> {
++ existing_args.push(current_arg.trim().into());
++ current_arg.clear();
++ },
++ NodeOrToken::Token(t) => {
++ current_arg.push_str(t.text());
++ },
+ }
+ }
+ existing_args.push(current_arg.trim().into());
+
+ // delete everything after the format string till end bracket
+ // we're going to insert the new arguments later
+ edit.delete(TextRange::new(
+ format_string.text_range().end(),
+ end_bracket.text_range().start(),
+ ));
+ }
+
+ // Start building the new args
+ let mut existing_args = existing_args.into_iter();
+ let mut args = String::new();
+
+ let mut placeholder_idx = 1;
+
+ for extracted_args in extracted_args {
+ // remove expr from format string
+ args.push_str(", ");
+
+ match extracted_args {
+ Arg::Ident(s) | Arg::Expr(s) => {
+ // insert arg
+ args.push_str(&s);
+ }
+ Arg::Placeholder => {
+ // try matching with existing argument
+ match existing_args.next() {
+ Some(ea) => {
+ args.push_str(&ea);
+ }
+ None => {
+ // insert placeholder
+ args.push_str(&format!("${placeholder_idx}"));
+ placeholder_idx += 1;
+ }
+ }
+ }
+ }
+ }
+
+ // Insert new args
+ edit.insert(fmt_range.end(), args);
+ },
+ );
+
+ Some(())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::tests::check_assist;
+
+ const MACRO_DECL: &'static str = r#"
+macro_rules! format_args {
+ ($lit:literal $(tt:tt)*) => { 0 },
+}
+macro_rules! print {
+ ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*)));
+}
+"#;
+
+ fn add_macro_decl(s: &'static str) -> String {
+ MACRO_DECL.to_string() + s
+ }
+
+ #[test]
+ fn multiple_middle_arg() {
+ check_assist(
+ move_format_string_arg,
+ &add_macro_decl(
+ r#"
+fn main() {
+ print!("{} {x + 1:b} {}$0", y + 2, 2);
+}
+"#,
+ ),
+ &add_macro_decl(
+ r#"
+fn main() {
+ print!("{} {:b} {}"$0, y + 2, x + 1, 2);
+}
+"#,
+ ),
+ );
+ }
+
+ #[test]
+ fn single_arg() {
+ check_assist(
+ move_format_string_arg,
+ &add_macro_decl(
+ r#"
+fn main() {
+ print!("{obj.value:b}$0",);
+}
+"#,
+ ),
+ &add_macro_decl(
+ r#"
+fn main() {
+ print!("{:b}"$0, obj.value);
+}
+"#,
+ ),
+ );
+ }
+
+ #[test]
+ fn multiple_middle_placeholders_arg() {
+ check_assist(
+ move_format_string_arg,
+ &add_macro_decl(
+ r#"
+fn main() {
+ print!("{} {x + 1:b} {} {}$0", y + 2, 2);
+}
+"#,
+ ),
+ &add_macro_decl(
+ r#"
+fn main() {
+ print!("{} {:b} {} {}"$0, y + 2, x + 1, 2, $1);
+}
+"#,
+ ),
+ );
+ }
+
+ #[test]
+ fn multiple_trailing_args() {
+ check_assist(
+ move_format_string_arg,
+ &add_macro_decl(
+ r#"
+fn main() {
+ print!("{} {x + 1:b} {Struct(1, 2)}$0", 1);
+}
+"#,
+ ),
+ &add_macro_decl(
+ r#"
+fn main() {
+ print!("{} {:b} {}"$0, 1, x + 1, Struct(1, 2));
+}
+"#,
+ ),
+ );
+ }
+
+ #[test]
+ fn improper_commas() {
+ check_assist(
+ move_format_string_arg,
+ &add_macro_decl(
+ r#"
+fn main() {
+ print!("{} {x + 1:b} {Struct(1, 2)}$0", 1,);
+}
+"#,
+ ),
+ &add_macro_decl(
+ r#"
+fn main() {
+ print!("{} {:b} {}"$0, 1, x + 1, Struct(1, 2));
+}
++"#,
++ ),
++ );
++ }
++
++ #[test]
++ fn nested_tt() {
++ check_assist(
++ move_format_string_arg,
++ &add_macro_decl(
++ r#"
++fn main() {
++ print!("My name is {} {x$0 + x}", stringify!(Paperino))
++}
++"#,
++ ),
++ &add_macro_decl(
++ r#"
++fn main() {
++ print!("My name is {} {}"$0, stringify!(Paperino), x + x)
++}
+"#,
+ ),
+ );
+ }
+}
--- /dev/null
--- /dev/null
++use syntax::{
++ ast::{self, edit::AstNodeEdit},
++ AstNode, T,
++};
++
++use crate::{AssistContext, AssistId, AssistKind, Assists};
++
++// Assist: unwrap_tuple
++//
++// Unwrap the tuple to different variables.
++//
++// ```
++// # //- minicore: result
++// fn main() {
++// $0let (foo, bar) = ("Foo", "Bar");
++// }
++// ```
++// ->
++// ```
++// fn main() {
++// let foo = "Foo";
++// let bar = "Bar";
++// }
++// ```
++pub(crate) fn unwrap_tuple(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
++ let let_kw = ctx.find_token_syntax_at_offset(T![let])?;
++ let let_stmt = let_kw.parent().and_then(ast::LetStmt::cast)?;
++ let indent_level = let_stmt.indent_level().0 as usize;
++ let pat = let_stmt.pat()?;
++ let ty = let_stmt.ty();
++ let init = let_stmt.initializer()?;
++
++ // This only applies for tuple patterns, types, and initializers.
++ let tuple_pat = match pat {
++ ast::Pat::TuplePat(pat) => pat,
++ _ => return None,
++ };
++ let tuple_ty = ty.and_then(|it| match it {
++ ast::Type::TupleType(ty) => Some(ty),
++ _ => None,
++ });
++ let tuple_init = match init {
++ ast::Expr::TupleExpr(expr) => expr,
++ _ => return None,
++ };
++
++ if tuple_pat.fields().count() != tuple_init.fields().count() {
++ return None;
++ }
++ if let Some(tys) = &tuple_ty {
++ if tuple_pat.fields().count() != tys.fields().count() {
++ return None;
++ }
++ }
++
++ let parent = let_kw.parent()?;
++
++ acc.add(
++ AssistId("unwrap_tuple", AssistKind::RefactorRewrite),
++ "Unwrap tuple",
++ let_kw.text_range(),
++ |edit| {
++ let indents = " ".repeat(indent_level);
++
++ // If there is an ascribed type, insert that type for each declaration,
++ // otherwise, omit that type.
++ if let Some(tys) = tuple_ty {
++ let mut zipped_decls = String::new();
++ for (pat, ty, expr) in
++ itertools::izip!(tuple_pat.fields(), tys.fields(), tuple_init.fields())
++ {
++ zipped_decls.push_str(&format!("{}let {pat}: {ty} = {expr};\n", indents))
++ }
++ edit.replace(parent.text_range(), zipped_decls.trim());
++ } else {
++ let mut zipped_decls = String::new();
++ for (pat, expr) in itertools::izip!(tuple_pat.fields(), tuple_init.fields()) {
++ zipped_decls.push_str(&format!("{}let {pat} = {expr};\n", indents));
++ }
++ edit.replace(parent.text_range(), zipped_decls.trim());
++ }
++ },
++ )
++}
++
++#[cfg(test)]
++mod tests {
++ use crate::tests::check_assist;
++
++ use super::*;
++
++ #[test]
++ fn unwrap_tuples() {
++ check_assist(
++ unwrap_tuple,
++ r#"
++fn main() {
++ $0let (foo, bar) = ("Foo", "Bar");
++}
++"#,
++ r#"
++fn main() {
++ let foo = "Foo";
++ let bar = "Bar";
++}
++"#,
++ );
++
++ check_assist(
++ unwrap_tuple,
++ r#"
++fn main() {
++ $0let (foo, bar, baz) = ("Foo", "Bar", "Baz");
++}
++"#,
++ r#"
++fn main() {
++ let foo = "Foo";
++ let bar = "Bar";
++ let baz = "Baz";
++}
++"#,
++ );
++ }
++
++ #[test]
++ fn unwrap_tuple_with_types() {
++ check_assist(
++ unwrap_tuple,
++ r#"
++fn main() {
++ $0let (foo, bar): (u8, i32) = (5, 10);
++}
++"#,
++ r#"
++fn main() {
++ let foo: u8 = 5;
++ let bar: i32 = 10;
++}
++"#,
++ );
++
++ check_assist(
++ unwrap_tuple,
++ r#"
++fn main() {
++ $0let (foo, bar, baz): (u8, i32, f64) = (5, 10, 17.5);
++}
++"#,
++ r#"
++fn main() {
++ let foo: u8 = 5;
++ let bar: i32 = 10;
++ let baz: f64 = 17.5;
++}
++"#,
++ );
++ }
++}
--- /dev/null
+//! `assists` crate provides a bunch of code assists, also known as code actions
+//! (in LSP) or intentions (in IntelliJ).
+//!
+//! An assist is a micro-refactoring, which is automatically activated in
+//! certain context. For example, if the cursor is over `,`, a "swap `,`" assist
+//! becomes available.
+//!
+//! ## Assists Guidelines
+//!
+//! Assists are the main mechanism to deliver advanced IDE features to the user,
+//! so we should pay extra attention to the UX.
+//!
+//! The power of assists comes from their context-awareness. The main problem
+//! with IDE features is that there are a lot of them, and it's hard to teach
+//! the user what's available. Assists solve this problem nicely: 💡 signifies
+//! that *something* is possible, and clicking on it reveals a *short* list of
+//! actions. Contrast it with Emacs `M-x`, which just spits an infinite list of
+//! all the features.
+//!
+//! Here are some considerations when creating a new assist:
+//!
+//! * It's good to preserve semantics, and it's good to keep the code compiling,
+//! but it isn't necessary. Example: "flip binary operation" might change
+//! semantics.
+//! * Assist shouldn't necessary make the code "better". A lot of assist come in
+//! pairs: "if let <-> match".
+//! * Assists should have as narrow scope as possible. Each new assists greatly
+//! improves UX for cases where the user actually invokes it, but it makes UX
+//! worse for every case where the user clicks 💡 to invoke some *other*
+//! assist. So, a rarely useful assist which is always applicable can be a net
+//! negative.
+//! * Rarely useful actions are tricky. Sometimes there are features which are
+//! clearly useful to some users, but are just noise most of the time. We
+//! don't have a good solution here, our current approach is to make this
+//! functionality available only if assist is applicable to the whole
+//! selection. Example: `sort_items` sorts items alphabetically. Naively, it
+//! should be available more or less everywhere, which isn't useful. So
+//! instead we only show it if the user *selects* the items they want to sort.
+//! * Consider grouping related assists together (see [`Assists::add_group`]).
+//! * Make assists robust. If the assist depends on results of type-inference too
+//! much, it might only fire in fully-correct code. This makes assist less
+//! useful and (worse) less predictable. The user should have a clear
+//! intuition when each particular assist is available.
+//! * Make small assists, which compose. Example: rather than auto-importing
+//! enums in `add_missing_match_arms`, we use fully-qualified names. There's a
+//! separate assist to shorten a fully-qualified name.
+//! * Distinguish between assists and fixits for diagnostics. Internally, fixits
+//! and assists are equivalent. They have the same "show a list + invoke a
+//! single element" workflow, and both use [`Assist`] data structure. The main
+//! difference is in the UX: while 💡 looks only at the cursor position,
+//! diagnostics squigglies and fixits are calculated for the whole file and
+//! are presented to the user eagerly. So, diagnostics should be fixable
+//! errors, while assists can be just suggestions for an alternative way to do
+//! something. If something *could* be a diagnostic, it should be a
+//! diagnostic. Conversely, it might be valuable to turn a diagnostic with a
+//! lot of false errors into an assist.
+//!
+//! See also this post:
+//! <https://rust-analyzer.github.io/blog/2020/09/28/how-to-make-a-light-bulb.html>
+
+#![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)]
+
+#[allow(unused)]
+macro_rules! eprintln {
+ ($($tt:tt)*) => { stdx::eprintln!($($tt)*) };
+}
+
+mod assist_config;
+mod assist_context;
+#[cfg(test)]
+mod tests;
+pub mod utils;
+
+use hir::Semantics;
+use ide_db::{base_db::FileRange, RootDatabase};
+use syntax::TextRange;
+
+pub(crate) use crate::assist_context::{AssistContext, Assists};
+
+pub use assist_config::AssistConfig;
+pub use ide_db::assists::{
+ Assist, AssistId, AssistKind, AssistResolveStrategy, GroupLabel, SingleResolve,
+};
+
+/// Return all the assists applicable at the given position.
+///
+// NOTE: We don't have a `Feature: ` section for assists, they are special-cased
+// in the manual.
+pub fn assists(
+ db: &RootDatabase,
+ config: &AssistConfig,
+ resolve: AssistResolveStrategy,
+ range: FileRange,
+) -> Vec<Assist> {
+ let sema = Semantics::new(db);
+ let ctx = AssistContext::new(sema, config, range);
+ let mut acc = Assists::new(&ctx, resolve);
+ handlers::all().iter().for_each(|handler| {
+ handler(&mut acc, &ctx);
+ });
+ acc.finish()
+}
+
+mod handlers {
+ use crate::{AssistContext, Assists};
+
+ pub(crate) type Handler = fn(&mut Assists, &AssistContext<'_>) -> Option<()>;
+
+ mod add_explicit_type;
+ mod add_label_to_loop;
+ mod add_lifetime_to_type;
+ mod add_missing_impl_members;
+ mod add_turbo_fish;
+ mod apply_demorgan;
+ mod auto_import;
+ mod change_visibility;
+ mod convert_bool_then;
+ mod convert_comment_block;
+ mod convert_integer_literal;
+ mod convert_into_to_from;
+ mod convert_iter_for_each_to_for;
+ mod convert_let_else_to_match;
+ mod convert_tuple_struct_to_named_struct;
+ mod convert_to_guarded_return;
+ mod convert_two_arm_bool_match_to_matches_macro;
+ mod convert_while_to_loop;
+ mod destructure_tuple_binding;
+ mod expand_glob_import;
+ mod extract_function;
+ mod extract_module;
+ mod extract_struct_from_enum_variant;
+ mod extract_type_alias;
+ mod extract_variable;
+ mod add_missing_match_arms;
+ mod fix_visibility;
+ mod flip_binexpr;
+ mod flip_comma;
+ mod flip_trait_bound;
+ mod move_format_string_arg;
+ mod generate_constant;
+ mod generate_default_from_enum_variant;
+ mod generate_default_from_new;
+ mod generate_deref;
+ mod generate_derive;
+ mod generate_documentation_template;
+ mod generate_enum_is_method;
+ mod generate_enum_projection_method;
+ mod generate_enum_variant;
+ mod generate_from_impl_for_enum;
+ mod generate_function;
+ mod generate_getter;
+ mod generate_impl;
+ mod generate_is_empty_from_len;
+ mod generate_new;
+ mod generate_setter;
+ mod generate_delegate_methods;
+ mod add_return_type;
+ mod inline_call;
+ mod inline_local_variable;
+ mod inline_type_alias;
+ mod introduce_named_lifetime;
+ mod invert_if;
+ mod merge_imports;
+ mod merge_match_arms;
+ mod move_bounds;
+ mod move_guard;
+ mod move_module_to_file;
+ mod move_to_mod_rs;
+ mod move_from_mod_rs;
+ mod number_representation;
+ mod promote_local_to_const;
+ mod pull_assignment_up;
+ mod qualify_path;
+ mod qualify_method_call;
+ mod raw_string;
+ mod remove_dbg;
+ mod remove_mut;
+ mod remove_unused_param;
+ mod reorder_fields;
+ mod reorder_impl_items;
+ mod replace_try_expr_with_match;
+ mod replace_derive_with_manual_impl;
+ mod replace_if_let_with_match;
+ mod replace_or_with_or_else;
+ mod introduce_named_generic;
+ mod replace_let_with_if_let;
+ mod replace_qualified_name_with_use;
+ mod replace_string_with_char;
+ mod replace_turbofish_with_explicit_type;
+ mod split_import;
+ mod unmerge_match_arm;
++ mod unwrap_tuple;
+ mod sort_items;
+ mod toggle_ignore;
+ mod unmerge_use;
+ mod unnecessary_async;
+ mod unwrap_block;
+ mod unwrap_result_return_type;
+ mod wrap_return_type_in_result;
+
+ pub(crate) fn all() -> &'static [Handler] {
+ &[
+ // These are alphabetic for the foolish consistency
+ add_explicit_type::add_explicit_type,
+ add_label_to_loop::add_label_to_loop,
+ add_missing_match_arms::add_missing_match_arms,
+ add_lifetime_to_type::add_lifetime_to_type,
+ add_return_type::add_return_type,
+ add_turbo_fish::add_turbo_fish,
+ apply_demorgan::apply_demorgan,
+ auto_import::auto_import,
+ change_visibility::change_visibility,
+ convert_bool_then::convert_bool_then_to_if,
+ convert_bool_then::convert_if_to_bool_then,
+ convert_comment_block::convert_comment_block,
+ convert_integer_literal::convert_integer_literal,
+ convert_into_to_from::convert_into_to_from,
+ convert_iter_for_each_to_for::convert_iter_for_each_to_for,
+ convert_iter_for_each_to_for::convert_for_loop_with_for_each,
+ convert_let_else_to_match::convert_let_else_to_match,
+ convert_to_guarded_return::convert_to_guarded_return,
+ convert_tuple_struct_to_named_struct::convert_tuple_struct_to_named_struct,
+ convert_two_arm_bool_match_to_matches_macro::convert_two_arm_bool_match_to_matches_macro,
+ convert_while_to_loop::convert_while_to_loop,
+ destructure_tuple_binding::destructure_tuple_binding,
+ expand_glob_import::expand_glob_import,
+ extract_struct_from_enum_variant::extract_struct_from_enum_variant,
+ extract_type_alias::extract_type_alias,
+ fix_visibility::fix_visibility,
+ flip_binexpr::flip_binexpr,
+ flip_comma::flip_comma,
+ flip_trait_bound::flip_trait_bound,
+ generate_constant::generate_constant,
+ generate_default_from_enum_variant::generate_default_from_enum_variant,
+ generate_default_from_new::generate_default_from_new,
+ generate_derive::generate_derive,
+ generate_documentation_template::generate_documentation_template,
+ generate_documentation_template::generate_doc_example,
+ generate_enum_is_method::generate_enum_is_method,
+ generate_enum_projection_method::generate_enum_as_method,
+ generate_enum_projection_method::generate_enum_try_into_method,
+ generate_enum_variant::generate_enum_variant,
+ generate_from_impl_for_enum::generate_from_impl_for_enum,
+ generate_function::generate_function,
+ generate_impl::generate_impl,
+ generate_is_empty_from_len::generate_is_empty_from_len,
+ generate_new::generate_new,
+ inline_call::inline_call,
+ inline_call::inline_into_callers,
+ inline_local_variable::inline_local_variable,
+ inline_type_alias::inline_type_alias,
+ inline_type_alias::inline_type_alias_uses,
+ introduce_named_generic::introduce_named_generic,
+ introduce_named_lifetime::introduce_named_lifetime,
+ invert_if::invert_if,
+ merge_imports::merge_imports,
+ merge_match_arms::merge_match_arms,
+ move_bounds::move_bounds_to_where_clause,
+ move_format_string_arg::move_format_string_arg,
+ move_guard::move_arm_cond_to_match_guard,
+ move_guard::move_guard_to_arm_body,
+ move_module_to_file::move_module_to_file,
+ move_to_mod_rs::move_to_mod_rs,
+ move_from_mod_rs::move_from_mod_rs,
+ number_representation::reformat_number_literal,
+ pull_assignment_up::pull_assignment_up,
+ promote_local_to_const::promote_local_to_const,
+ qualify_path::qualify_path,
+ qualify_method_call::qualify_method_call,
+ raw_string::add_hash,
+ raw_string::make_usual_string,
+ raw_string::remove_hash,
+ remove_dbg::remove_dbg,
+ remove_mut::remove_mut,
+ remove_unused_param::remove_unused_param,
+ reorder_fields::reorder_fields,
+ reorder_impl_items::reorder_impl_items,
+ replace_try_expr_with_match::replace_try_expr_with_match,
+ replace_derive_with_manual_impl::replace_derive_with_manual_impl,
+ replace_if_let_with_match::replace_if_let_with_match,
+ replace_if_let_with_match::replace_match_with_if_let,
+ replace_let_with_if_let::replace_let_with_if_let,
+ replace_or_with_or_else::replace_or_else_with_or,
+ replace_or_with_or_else::replace_or_with_or_else,
+ replace_turbofish_with_explicit_type::replace_turbofish_with_explicit_type,
+ replace_qualified_name_with_use::replace_qualified_name_with_use,
+ sort_items::sort_items,
+ split_import::split_import,
+ toggle_ignore::toggle_ignore,
+ unmerge_match_arm::unmerge_match_arm,
+ unmerge_use::unmerge_use,
+ unnecessary_async::unnecessary_async,
+ unwrap_block::unwrap_block,
+ unwrap_result_return_type::unwrap_result_return_type,
++ unwrap_tuple::unwrap_tuple,
+ wrap_return_type_in_result::wrap_return_type_in_result,
+ // These are manually sorted for better priorities. By default,
+ // priority is determined by the size of the target range (smaller
+ // target wins). If the ranges are equal, position in this list is
+ // used as a tie-breaker.
+ add_missing_impl_members::add_missing_impl_members,
+ add_missing_impl_members::add_missing_default_members,
+ //
+ replace_string_with_char::replace_string_with_char,
+ replace_string_with_char::replace_char_with_string,
+ raw_string::make_raw_string,
+ //
+ extract_variable::extract_variable,
+ extract_function::extract_function,
+ extract_module::extract_module,
+ //
+ generate_getter::generate_getter,
+ generate_getter::generate_getter_mut,
+ generate_setter::generate_setter,
+ generate_delegate_methods::generate_delegate_methods,
+ generate_deref::generate_deref,
+ // Are you sure you want to add new assist here, and not to the
+ // sorted list above?
+ ]
+ }
+}
--- /dev/null
- let source_change =
- assist.source_change.expect("Assist did not contain any source changes");
+mod generated;
+#[cfg(not(feature = "in-rust-tree"))]
+mod sourcegen;
+
+use expect_test::expect;
+use hir::{db::DefDatabase, Semantics};
+use ide_db::{
+ base_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt},
+ imports::insert_use::{ImportGranularity, InsertUseConfig},
+ source_change::FileSystemEdit,
+ RootDatabase, SnippetCap,
+};
+use stdx::{format_to, trim_indent};
+use syntax::TextRange;
+use test_utils::{assert_eq_text, extract_offset};
+
+use crate::{
+ assists, handlers::Handler, Assist, AssistConfig, AssistContext, AssistKind,
+ AssistResolveStrategy, Assists, SingleResolve,
+};
+
+pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig {
+ snippet_cap: SnippetCap::new(true),
+ allowed: None,
+ insert_use: InsertUseConfig {
+ granularity: ImportGranularity::Crate,
+ prefix_kind: hir::PrefixKind::Plain,
+ enforce_granularity: true,
+ group: true,
+ skip_glob_imports: true,
+ },
+ prefer_no_std: false,
+};
+
+pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) {
+ RootDatabase::with_single_file(text)
+}
+
+#[track_caller]
+pub(crate) fn check_assist(assist: Handler, ra_fixture_before: &str, ra_fixture_after: &str) {
+ let ra_fixture_after = trim_indent(ra_fixture_after);
+ check(assist, ra_fixture_before, ExpectedResult::After(&ra_fixture_after), None);
+}
+
+// There is no way to choose what assist within a group you want to test against,
+// so this is here to allow you choose.
+pub(crate) fn check_assist_by_label(
+ assist: Handler,
+ ra_fixture_before: &str,
+ ra_fixture_after: &str,
+ label: &str,
+) {
+ let ra_fixture_after = trim_indent(ra_fixture_after);
+ check(assist, ra_fixture_before, ExpectedResult::After(&ra_fixture_after), Some(label));
+}
+
+// FIXME: instead of having a separate function here, maybe use
+// `extract_ranges` and mark the target as `<target> </target>` in the
+// fixture?
+#[track_caller]
+pub(crate) fn check_assist_target(assist: Handler, ra_fixture: &str, target: &str) {
+ check(assist, ra_fixture, ExpectedResult::Target(target), None);
+}
+
+#[track_caller]
+pub(crate) fn check_assist_not_applicable(assist: Handler, ra_fixture: &str) {
+ check(assist, ra_fixture, ExpectedResult::NotApplicable, None);
+}
+
+/// Check assist in unresolved state. Useful to check assists for lazy computation.
+#[track_caller]
+pub(crate) fn check_assist_unresolved(assist: Handler, ra_fixture: &str) {
+ check(assist, ra_fixture, ExpectedResult::Unresolved, None);
+}
+
+#[track_caller]
+fn check_doc_test(assist_id: &str, before: &str, after: &str) {
+ let after = trim_indent(after);
+ let (db, file_id, selection) = RootDatabase::with_range_or_offset(before);
+ let before = db.file_text(file_id).to_string();
+ let frange = FileRange { file_id, range: selection.into() };
+
+ let assist = assists(&db, &TEST_CONFIG, AssistResolveStrategy::All, frange)
+ .into_iter()
+ .find(|assist| assist.id.0 == assist_id)
+ .unwrap_or_else(|| {
+ panic!(
+ "\n\nAssist is not applicable: {}\nAvailable assists: {}",
+ assist_id,
+ assists(&db, &TEST_CONFIG, AssistResolveStrategy::None, frange)
+ .into_iter()
+ .map(|assist| assist.id.0)
+ .collect::<Vec<_>>()
+ .join(", ")
+ )
+ });
+
+ let actual = {
- let source_change =
- assist.source_change.expect("Assist did not contain any source changes");
++ let source_change = assist
++ .source_change
++ .filter(|it| !it.source_file_edits.is_empty() || !it.file_system_edits.is_empty())
++ .expect("Assist did not contain any source changes");
+ let mut actual = before;
+ if let Some(source_file_edit) = source_change.get_source_edit(file_id) {
+ source_file_edit.apply(&mut actual);
+ }
+ actual
+ };
+ assert_eq_text!(&after, &actual);
+}
+
+enum ExpectedResult<'a> {
+ NotApplicable,
+ Unresolved,
+ After(&'a str),
+ Target(&'a str),
+}
+
+#[track_caller]
+fn check(handler: Handler, before: &str, expected: ExpectedResult<'_>, assist_label: Option<&str>) {
+ let (mut db, file_with_caret_id, range_or_offset) = RootDatabase::with_range_or_offset(before);
+ db.set_enable_proc_attr_macros(true);
+ let text_without_caret = db.file_text(file_with_caret_id).to_string();
+
+ let frange = FileRange { file_id: file_with_caret_id, range: range_or_offset.into() };
+
+ let sema = Semantics::new(&db);
+ let config = TEST_CONFIG;
+ let ctx = AssistContext::new(sema, &config, frange);
+ let resolve = match expected {
+ ExpectedResult::Unresolved => AssistResolveStrategy::None,
+ _ => AssistResolveStrategy::All,
+ };
+ let mut acc = Assists::new(&ctx, resolve);
+ handler(&mut acc, &ctx);
+ let mut res = acc.finish();
+
+ let assist = match assist_label {
+ Some(label) => res.into_iter().find(|resolved| resolved.label == label),
+ None => res.pop(),
+ };
+
+ match (assist, expected) {
+ (Some(assist), ExpectedResult::After(after)) => {
++ let source_change = assist
++ .source_change
++ .filter(|it| !it.source_file_edits.is_empty() || !it.file_system_edits.is_empty())
++ .expect("Assist did not contain any source changes");
+ let skip_header = source_change.source_file_edits.len() == 1
+ && source_change.file_system_edits.len() == 0;
+
+ let mut buf = String::new();
+ for (file_id, edit) in source_change.source_file_edits {
+ let mut text = db.file_text(file_id).as_ref().to_owned();
+ edit.apply(&mut text);
+ if !skip_header {
+ let sr = db.file_source_root(file_id);
+ let sr = db.source_root(sr);
+ let path = sr.path_for_file(&file_id).unwrap();
+ format_to!(buf, "//- {}\n", path)
+ }
+ buf.push_str(&text);
+ }
+
+ for file_system_edit in source_change.file_system_edits {
+ let (dst, contents) = match file_system_edit {
+ FileSystemEdit::CreateFile { dst, initial_contents } => (dst, initial_contents),
+ FileSystemEdit::MoveFile { src, dst } => {
+ (dst, db.file_text(src).as_ref().to_owned())
+ }
+ FileSystemEdit::MoveDir { src, src_id, dst } => {
+ // temporary placeholder for MoveDir since we are not using MoveDir in ide assists yet.
+ (dst, format!("{:?}\n{:?}", src_id, src))
+ }
+ };
+ let sr = db.file_source_root(dst.anchor);
+ let sr = db.source_root(sr);
+ let mut base = sr.path_for_file(&dst.anchor).unwrap().clone();
+ base.pop();
+ let created_file_path = base.join(&dst.path).unwrap();
+ format_to!(buf, "//- {}\n", created_file_path);
+ buf.push_str(&contents);
+ }
+
+ assert_eq_text!(after, &buf);
+ }
+ (Some(assist), ExpectedResult::Target(target)) => {
+ let range = assist.target;
+ assert_eq_text!(&text_without_caret[range], target);
+ }
+ (Some(assist), ExpectedResult::Unresolved) => assert!(
+ assist.source_change.is_none(),
+ "unresolved assist should not contain source changes"
+ ),
+ (Some(_), ExpectedResult::NotApplicable) => panic!("assist should not be applicable!"),
+ (
+ None,
+ ExpectedResult::After(_) | ExpectedResult::Target(_) | ExpectedResult::Unresolved,
+ ) => {
+ panic!("code action is not applicable")
+ }
+ (None, ExpectedResult::NotApplicable) => (),
+ };
+}
+
+fn labels(assists: &[Assist]) -> String {
+ let mut labels = assists
+ .iter()
+ .map(|assist| {
+ let mut label = match &assist.group {
+ Some(g) => g.0.clone(),
+ None => assist.label.to_string(),
+ };
+ label.push('\n');
+ label
+ })
+ .collect::<Vec<_>>();
+ labels.dedup();
+ labels.into_iter().collect::<String>()
+}
+
+#[test]
+fn assist_order_field_struct() {
+ let before = "struct Foo { $0bar: u32 }";
+ let (before_cursor_pos, before) = extract_offset(before);
+ let (db, file_id) = with_single_file(&before);
+ let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) };
+ let assists = assists(&db, &TEST_CONFIG, AssistResolveStrategy::None, frange);
+ let mut assists = assists.iter();
+
+ assert_eq!(assists.next().expect("expected assist").label, "Change visibility to pub(crate)");
+ assert_eq!(assists.next().expect("expected assist").label, "Generate a getter method");
+ assert_eq!(assists.next().expect("expected assist").label, "Generate a mut getter method");
+ assert_eq!(assists.next().expect("expected assist").label, "Generate a setter method");
+ assert_eq!(assists.next().expect("expected assist").label, "Add `#[derive]`");
+}
+
+#[test]
+fn assist_order_if_expr() {
+ let (db, frange) = RootDatabase::with_range(
+ r#"
+pub fn test_some_range(a: int) -> bool {
+ if let 2..6 = $05$0 {
+ true
+ } else {
+ false
+ }
+}
+"#,
+ );
+
+ let assists = assists(&db, &TEST_CONFIG, AssistResolveStrategy::None, frange);
+ let expected = labels(&assists);
+
+ expect![[r#"
+ Convert integer base
+ Extract into variable
+ Extract into function
+ Replace if let with match
+ "#]]
+ .assert_eq(&expected);
+}
+
+#[test]
+fn assist_filter_works() {
+ let (db, frange) = RootDatabase::with_range(
+ r#"
+pub fn test_some_range(a: int) -> bool {
+ if let 2..6 = $05$0 {
+ true
+ } else {
+ false
+ }
+}
+"#,
+ );
+ {
+ let mut cfg = TEST_CONFIG;
+ cfg.allowed = Some(vec![AssistKind::Refactor]);
+
+ let assists = assists(&db, &cfg, AssistResolveStrategy::None, frange);
+ let expected = labels(&assists);
+
+ expect![[r#"
+ Convert integer base
+ Extract into variable
+ Extract into function
+ Replace if let with match
+ "#]]
+ .assert_eq(&expected);
+ }
+
+ {
+ let mut cfg = TEST_CONFIG;
+ cfg.allowed = Some(vec![AssistKind::RefactorExtract]);
+ let assists = assists(&db, &cfg, AssistResolveStrategy::None, frange);
+ let expected = labels(&assists);
+
+ expect![[r#"
+ Extract into variable
+ Extract into function
+ "#]]
+ .assert_eq(&expected);
+ }
+
+ {
+ let mut cfg = TEST_CONFIG;
+ cfg.allowed = Some(vec![AssistKind::QuickFix]);
+ let assists = assists(&db, &cfg, AssistResolveStrategy::None, frange);
+ let expected = labels(&assists);
+
+ expect![[r#""#]].assert_eq(&expected);
+ }
+}
+
+#[test]
+fn various_resolve_strategies() {
+ let (db, frange) = RootDatabase::with_range(
+ r#"
+pub fn test_some_range(a: int) -> bool {
+ if let 2..6 = $05$0 {
+ true
+ } else {
+ false
+ }
+}
+"#,
+ );
+
+ let mut cfg = TEST_CONFIG;
+ cfg.allowed = Some(vec![AssistKind::RefactorExtract]);
+
+ {
+ let assists = assists(&db, &cfg, AssistResolveStrategy::None, frange);
+ assert_eq!(2, assists.len());
+ let mut assists = assists.into_iter();
+
+ let extract_into_variable_assist = assists.next().unwrap();
+ expect![[r#"
+ Assist {
+ id: AssistId(
+ "extract_variable",
+ RefactorExtract,
+ ),
+ label: "Extract into variable",
+ group: None,
+ target: 59..60,
+ source_change: None,
+ trigger_signature_help: false,
+ }
+ "#]]
+ .assert_debug_eq(&extract_into_variable_assist);
+
+ let extract_into_function_assist = assists.next().unwrap();
+ expect![[r#"
+ Assist {
+ id: AssistId(
+ "extract_function",
+ RefactorExtract,
+ ),
+ label: "Extract into function",
+ group: None,
+ target: 59..60,
+ source_change: None,
+ trigger_signature_help: false,
+ }
+ "#]]
+ .assert_debug_eq(&extract_into_function_assist);
+ }
+
+ {
+ let assists = assists(
+ &db,
+ &cfg,
+ AssistResolveStrategy::Single(SingleResolve {
+ assist_id: "SOMETHING_MISMATCHING".to_string(),
+ assist_kind: AssistKind::RefactorExtract,
+ }),
+ frange,
+ );
+ assert_eq!(2, assists.len());
+ let mut assists = assists.into_iter();
+
+ let extract_into_variable_assist = assists.next().unwrap();
+ expect![[r#"
+ Assist {
+ id: AssistId(
+ "extract_variable",
+ RefactorExtract,
+ ),
+ label: "Extract into variable",
+ group: None,
+ target: 59..60,
+ source_change: None,
+ trigger_signature_help: false,
+ }
+ "#]]
+ .assert_debug_eq(&extract_into_variable_assist);
+
+ let extract_into_function_assist = assists.next().unwrap();
+ expect![[r#"
+ Assist {
+ id: AssistId(
+ "extract_function",
+ RefactorExtract,
+ ),
+ label: "Extract into function",
+ group: None,
+ target: 59..60,
+ source_change: None,
+ trigger_signature_help: false,
+ }
+ "#]]
+ .assert_debug_eq(&extract_into_function_assist);
+ }
+
+ {
+ let assists = assists(
+ &db,
+ &cfg,
+ AssistResolveStrategy::Single(SingleResolve {
+ assist_id: "extract_variable".to_string(),
+ assist_kind: AssistKind::RefactorExtract,
+ }),
+ frange,
+ );
+ assert_eq!(2, assists.len());
+ let mut assists = assists.into_iter();
+
+ let extract_into_variable_assist = assists.next().unwrap();
+ expect![[r#"
+ Assist {
+ id: AssistId(
+ "extract_variable",
+ RefactorExtract,
+ ),
+ label: "Extract into variable",
+ group: None,
+ target: 59..60,
+ source_change: Some(
+ SourceChange {
+ source_file_edits: {
+ FileId(
+ 0,
+ ): TextEdit {
+ indels: [
+ Indel {
+ insert: "let $0var_name = 5;\n ",
+ delete: 45..45,
+ },
+ Indel {
+ insert: "var_name",
+ delete: 59..60,
+ },
+ ],
+ },
+ },
+ file_system_edits: [],
+ is_snippet: true,
+ },
+ ),
+ trigger_signature_help: false,
+ }
+ "#]]
+ .assert_debug_eq(&extract_into_variable_assist);
+
+ let extract_into_function_assist = assists.next().unwrap();
+ expect![[r#"
+ Assist {
+ id: AssistId(
+ "extract_function",
+ RefactorExtract,
+ ),
+ label: "Extract into function",
+ group: None,
+ target: 59..60,
+ source_change: None,
+ trigger_signature_help: false,
+ }
+ "#]]
+ .assert_debug_eq(&extract_into_function_assist);
+ }
+
+ {
+ let assists = assists(&db, &cfg, AssistResolveStrategy::All, frange);
+ assert_eq!(2, assists.len());
+ let mut assists = assists.into_iter();
+
+ let extract_into_variable_assist = assists.next().unwrap();
+ expect![[r#"
+ Assist {
+ id: AssistId(
+ "extract_variable",
+ RefactorExtract,
+ ),
+ label: "Extract into variable",
+ group: None,
+ target: 59..60,
+ source_change: Some(
+ SourceChange {
+ source_file_edits: {
+ FileId(
+ 0,
+ ): TextEdit {
+ indels: [
+ Indel {
+ insert: "let $0var_name = 5;\n ",
+ delete: 45..45,
+ },
+ Indel {
+ insert: "var_name",
+ delete: 59..60,
+ },
+ ],
+ },
+ },
+ file_system_edits: [],
+ is_snippet: true,
+ },
+ ),
+ trigger_signature_help: false,
+ }
+ "#]]
+ .assert_debug_eq(&extract_into_variable_assist);
+
+ let extract_into_function_assist = assists.next().unwrap();
+ expect![[r#"
+ Assist {
+ id: AssistId(
+ "extract_function",
+ RefactorExtract,
+ ),
+ label: "Extract into function",
+ group: None,
+ target: 59..60,
+ source_change: Some(
+ SourceChange {
+ source_file_edits: {
+ FileId(
+ 0,
+ ): TextEdit {
+ indels: [
+ Indel {
+ insert: "fun_name()",
+ delete: 59..60,
+ },
+ Indel {
+ insert: "\n\nfn $0fun_name() -> i32 {\n 5\n}",
+ delete: 110..110,
+ },
+ ],
+ },
+ },
+ file_system_edits: [],
+ is_snippet: true,
+ },
+ ),
+ trigger_signature_help: false,
+ }
+ "#]]
+ .assert_debug_eq(&extract_into_function_assist);
+ }
+}
--- /dev/null
+//! Generated by `sourcegen_assists_docs`, do not edit by hand.
+
+use super::check_doc_test;
+
+#[test]
+fn doctest_add_explicit_type() {
+ check_doc_test(
+ "add_explicit_type",
+ r#####"
+fn main() {
+ let x$0 = 92;
+}
+"#####,
+ r#####"
+fn main() {
+ let x: i32 = 92;
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_add_hash() {
+ check_doc_test(
+ "add_hash",
+ r#####"
+fn main() {
+ r#"Hello,$0 World!"#;
+}
+"#####,
+ r#####"
+fn main() {
+ r##"Hello, World!"##;
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_add_impl_default_members() {
+ check_doc_test(
+ "add_impl_default_members",
+ r#####"
+trait Trait {
+ type X;
+ fn foo(&self);
+ fn bar(&self) {}
+}
+
+impl Trait for () {
+ type X = ();
+ fn foo(&self) {}$0
+}
+"#####,
+ r#####"
+trait Trait {
+ type X;
+ fn foo(&self);
+ fn bar(&self) {}
+}
+
+impl Trait for () {
+ type X = ();
+ fn foo(&self) {}
+
+ $0fn bar(&self) {}
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_add_impl_missing_members() {
+ check_doc_test(
+ "add_impl_missing_members",
+ r#####"
+trait Trait<T> {
+ type X;
+ fn foo(&self) -> T;
+ fn bar(&self) {}
+}
+
+impl Trait<u32> for () {$0
+
+}
+"#####,
+ r#####"
+trait Trait<T> {
+ type X;
+ fn foo(&self) -> T;
+ fn bar(&self) {}
+}
+
+impl Trait<u32> for () {
+ $0type X;
+
+ fn foo(&self) -> u32 {
+ todo!()
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_add_label_to_loop() {
+ check_doc_test(
+ "add_label_to_loop",
+ r#####"
+fn main() {
+ loop$0 {
+ break;
+ continue;
+ }
+}
+"#####,
+ r#####"
+fn main() {
+ 'l: loop {
+ break 'l;
+ continue 'l;
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_add_lifetime_to_type() {
+ check_doc_test(
+ "add_lifetime_to_type",
+ r#####"
+struct Point {
+ x: &$0u32,
+ y: u32,
+}
+"#####,
+ r#####"
+struct Point<'a> {
+ x: &'a u32,
+ y: u32,
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_add_missing_match_arms() {
+ check_doc_test(
+ "add_missing_match_arms",
+ r#####"
+enum Action { Move { distance: u32 }, Stop }
+
+fn handle(action: Action) {
+ match action {
+ $0
+ }
+}
+"#####,
+ r#####"
+enum Action { Move { distance: u32 }, Stop }
+
+fn handle(action: Action) {
+ match action {
+ $0Action::Move { distance } => todo!(),
+ Action::Stop => todo!(),
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_add_return_type() {
+ check_doc_test(
+ "add_return_type",
+ r#####"
+fn foo() { 4$02i32 }
+"#####,
+ r#####"
+fn foo() -> i32 { 42i32 }
+"#####,
+ )
+}
+
+#[test]
+fn doctest_add_turbo_fish() {
+ check_doc_test(
+ "add_turbo_fish",
+ r#####"
+fn make<T>() -> T { todo!() }
+fn main() {
+ let x = make$0();
+}
+"#####,
+ r#####"
+fn make<T>() -> T { todo!() }
+fn main() {
+ let x = make::<${0:_}>();
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_apply_demorgan() {
+ check_doc_test(
+ "apply_demorgan",
+ r#####"
+fn main() {
+ if x != 4 ||$0 y < 3.14 {}
+}
+"#####,
+ r#####"
+fn main() {
+ if !(x == 4 && y >= 3.14) {}
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_auto_import() {
+ check_doc_test(
+ "auto_import",
+ r#####"
+fn main() {
+ let map = HashMap$0::new();
+}
+pub mod std { pub mod collections { pub struct HashMap { } } }
+"#####,
+ r#####"
+use std::collections::HashMap;
+
+fn main() {
+ let map = HashMap::new();
+}
+pub mod std { pub mod collections { pub struct HashMap { } } }
+"#####,
+ )
+}
+
+#[test]
+fn doctest_change_visibility() {
+ check_doc_test(
+ "change_visibility",
+ r#####"
+$0fn frobnicate() {}
+"#####,
+ r#####"
+pub(crate) fn frobnicate() {}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_convert_bool_then_to_if() {
+ check_doc_test(
+ "convert_bool_then_to_if",
+ r#####"
+//- minicore: bool_impl
+fn main() {
+ (0 == 0).then$0(|| val)
+}
+"#####,
+ r#####"
+fn main() {
+ if 0 == 0 {
+ Some(val)
+ } else {
+ None
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_convert_for_loop_with_for_each() {
+ check_doc_test(
+ "convert_for_loop_with_for_each",
+ r#####"
+fn main() {
+ let x = vec![1, 2, 3];
+ for$0 v in x {
+ let y = v * 2;
+ }
+}
+"#####,
+ r#####"
+fn main() {
+ let x = vec![1, 2, 3];
+ x.into_iter().for_each(|v| {
+ let y = v * 2;
+ });
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_convert_if_to_bool_then() {
+ check_doc_test(
+ "convert_if_to_bool_then",
+ r#####"
+//- minicore: option
+fn main() {
+ if$0 cond {
+ Some(val)
+ } else {
+ None
+ }
+}
+"#####,
+ r#####"
+fn main() {
+ cond.then(|| val)
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_convert_integer_literal() {
+ check_doc_test(
+ "convert_integer_literal",
+ r#####"
+const _: i32 = 10$0;
+"#####,
+ r#####"
+const _: i32 = 0b1010;
+"#####,
+ )
+}
+
+#[test]
+fn doctest_convert_into_to_from() {
+ check_doc_test(
+ "convert_into_to_from",
+ r#####"
+//- minicore: from
+impl $0Into<Thing> for usize {
+ fn into(self) -> Thing {
+ Thing {
+ b: self.to_string(),
+ a: self
+ }
+ }
+}
+"#####,
+ r#####"
+impl From<usize> for Thing {
+ fn from(val: usize) -> Self {
+ Thing {
+ b: val.to_string(),
+ a: val
+ }
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_convert_iter_for_each_to_for() {
+ check_doc_test(
+ "convert_iter_for_each_to_for",
+ r#####"
+//- minicore: iterators
+use core::iter;
+fn main() {
+ let iter = iter::repeat((9, 2));
+ iter.for_each$0(|(x, y)| {
+ println!("x: {}, y: {}", x, y);
+ });
+}
+"#####,
+ r#####"
+use core::iter;
+fn main() {
+ let iter = iter::repeat((9, 2));
+ for (x, y) in iter {
+ println!("x: {}, y: {}", x, y);
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_convert_let_else_to_match() {
+ check_doc_test(
+ "convert_let_else_to_match",
+ r#####"
+fn main() {
+ let Ok(mut x) = f() else$0 { return };
+}
+"#####,
+ r#####"
+fn main() {
+ let mut x = match f() {
+ Ok(x) => x,
+ _ => return,
+ };
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_convert_to_guarded_return() {
+ check_doc_test(
+ "convert_to_guarded_return",
+ r#####"
+fn main() {
+ $0if cond {
+ foo();
+ bar();
+ }
+}
+"#####,
+ r#####"
+fn main() {
+ if !cond {
+ return;
+ }
+ foo();
+ bar();
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_convert_tuple_struct_to_named_struct() {
+ check_doc_test(
+ "convert_tuple_struct_to_named_struct",
+ r#####"
+struct Point$0(f32, f32);
+
+impl Point {
+ pub fn new(x: f32, y: f32) -> Self {
+ Point(x, y)
+ }
+
+ pub fn x(&self) -> f32 {
+ self.0
+ }
+
+ pub fn y(&self) -> f32 {
+ self.1
+ }
+}
+"#####,
+ r#####"
+struct Point { field1: f32, field2: f32 }
+
+impl Point {
+ pub fn new(x: f32, y: f32) -> Self {
+ Point { field1: x, field2: y }
+ }
+
+ pub fn x(&self) -> f32 {
+ self.field1
+ }
+
+ pub fn y(&self) -> f32 {
+ self.field2
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_convert_two_arm_bool_match_to_matches_macro() {
+ check_doc_test(
+ "convert_two_arm_bool_match_to_matches_macro",
+ r#####"
+fn main() {
+ match scrutinee$0 {
+ Some(val) if val.cond() => true,
+ _ => false,
+ }
+}
+"#####,
+ r#####"
+fn main() {
+ matches!(scrutinee, Some(val) if val.cond())
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_convert_while_to_loop() {
+ check_doc_test(
+ "convert_while_to_loop",
+ r#####"
+fn main() {
+ $0while cond {
+ foo();
+ }
+}
+"#####,
+ r#####"
+fn main() {
+ loop {
+ if !cond {
+ break;
+ }
+ foo();
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_destructure_tuple_binding() {
+ check_doc_test(
+ "destructure_tuple_binding",
+ r#####"
+fn main() {
+ let $0t = (1,2);
+ let v = t.0;
+}
+"#####,
+ r#####"
+fn main() {
+ let ($0_0, _1) = (1,2);
+ let v = _0;
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_expand_glob_import() {
+ check_doc_test(
+ "expand_glob_import",
+ r#####"
+mod foo {
+ pub struct Bar;
+ pub struct Baz;
+}
+
+use foo::*$0;
+
+fn qux(bar: Bar, baz: Baz) {}
+"#####,
+ r#####"
+mod foo {
+ pub struct Bar;
+ pub struct Baz;
+}
+
+use foo::{Bar, Baz};
+
+fn qux(bar: Bar, baz: Baz) {}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_extract_function() {
+ check_doc_test(
+ "extract_function",
+ r#####"
+fn main() {
+ let n = 1;
+ $0let m = n + 2;
+ // calculate
+ let k = m + n;$0
+ let g = 3;
+}
+"#####,
+ r#####"
+fn main() {
+ let n = 1;
+ fun_name(n);
+ let g = 3;
+}
+
+fn $0fun_name(n: i32) {
+ let m = n + 2;
+ // calculate
+ let k = m + n;
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_extract_module() {
+ check_doc_test(
+ "extract_module",
+ r#####"
+$0fn foo(name: i32) -> i32 {
+ name + 1
+}$0
+
+fn bar(name: i32) -> i32 {
+ name + 2
+}
+"#####,
+ r#####"
+mod modname {
+ pub(crate) fn foo(name: i32) -> i32 {
+ name + 1
+ }
+}
+
+fn bar(name: i32) -> i32 {
+ name + 2
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_extract_struct_from_enum_variant() {
+ check_doc_test(
+ "extract_struct_from_enum_variant",
+ r#####"
+enum A { $0One(u32, u32) }
+"#####,
+ r#####"
+struct One(u32, u32);
+
+enum A { One(One) }
+"#####,
+ )
+}
+
+#[test]
+fn doctest_extract_type_alias() {
+ check_doc_test(
+ "extract_type_alias",
+ r#####"
+struct S {
+ field: $0(u8, u8, u8)$0,
+}
+"#####,
+ r#####"
+type $0Type = (u8, u8, u8);
+
+struct S {
+ field: Type,
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_extract_variable() {
+ check_doc_test(
+ "extract_variable",
+ r#####"
+fn main() {
+ $0(1 + 2)$0 * 4;
+}
+"#####,
+ r#####"
+fn main() {
+ let $0var_name = (1 + 2);
+ var_name * 4;
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_fix_visibility() {
+ check_doc_test(
+ "fix_visibility",
+ r#####"
+mod m {
+ fn frobnicate() {}
+}
+fn main() {
+ m::frobnicate$0() {}
+}
+"#####,
+ r#####"
+mod m {
+ $0pub(crate) fn frobnicate() {}
+}
+fn main() {
+ m::frobnicate() {}
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_flip_binexpr() {
+ check_doc_test(
+ "flip_binexpr",
+ r#####"
+fn main() {
+ let _ = 90 +$0 2;
+}
+"#####,
+ r#####"
+fn main() {
+ let _ = 2 + 90;
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_flip_comma() {
+ check_doc_test(
+ "flip_comma",
+ r#####"
+fn main() {
+ ((1, 2),$0 (3, 4));
+}
+"#####,
+ r#####"
+fn main() {
+ ((3, 4), (1, 2));
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_flip_trait_bound() {
+ check_doc_test(
+ "flip_trait_bound",
+ r#####"
+fn foo<T: Clone +$0 Copy>() { }
+"#####,
+ r#####"
+fn foo<T: Copy + Clone>() { }
+"#####,
+ )
+}
+
+#[test]
+fn doctest_generate_constant() {
+ check_doc_test(
+ "generate_constant",
+ r#####"
+struct S { i: usize }
+impl S { pub fn new(n: usize) {} }
+fn main() {
+ let v = S::new(CAPA$0CITY);
+}
+"#####,
+ r#####"
+struct S { i: usize }
+impl S { pub fn new(n: usize) {} }
+fn main() {
+ const CAPACITY: usize = $0;
+ let v = S::new(CAPACITY);
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_generate_default_from_enum_variant() {
+ check_doc_test(
+ "generate_default_from_enum_variant",
+ r#####"
+enum Version {
+ Undefined,
+ Minor$0,
+ Major,
+}
+"#####,
+ r#####"
+enum Version {
+ Undefined,
+ Minor,
+ Major,
+}
+
+impl Default for Version {
+ fn default() -> Self {
+ Self::Minor
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_generate_default_from_new() {
+ check_doc_test(
+ "generate_default_from_new",
+ r#####"
+struct Example { _inner: () }
+
+impl Example {
+ pub fn n$0ew() -> Self {
+ Self { _inner: () }
+ }
+}
+"#####,
+ r#####"
+struct Example { _inner: () }
+
+impl Example {
+ pub fn new() -> Self {
+ Self { _inner: () }
+ }
+}
+
+impl Default for Example {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_generate_delegate_methods() {
+ check_doc_test(
+ "generate_delegate_methods",
+ r#####"
+struct Age(u8);
+impl Age {
+ fn age(&self) -> u8 {
+ self.0
+ }
+}
+
+struct Person {
+ ag$0e: Age,
+}
+"#####,
+ r#####"
+struct Age(u8);
+impl Age {
+ fn age(&self) -> u8 {
+ self.0
+ }
+}
+
+struct Person {
+ age: Age,
+}
+
+impl Person {
+ $0fn age(&self) -> u8 {
+ self.age.age()
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_generate_deref() {
+ check_doc_test(
+ "generate_deref",
+ r#####"
+//- minicore: deref, deref_mut
+struct A;
+struct B {
+ $0a: A
+}
+"#####,
+ r#####"
+struct A;
+struct B {
+ a: A
+}
+
+impl core::ops::Deref for B {
+ type Target = A;
+
+ fn deref(&self) -> &Self::Target {
+ &self.a
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_generate_derive() {
+ check_doc_test(
+ "generate_derive",
+ r#####"
+struct Point {
+ x: u32,
+ y: u32,$0
+}
+"#####,
+ r#####"
+#[derive($0)]
+struct Point {
+ x: u32,
+ y: u32,
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_generate_doc_example() {
+ check_doc_test(
+ "generate_doc_example",
+ r#####"
+/// Adds two numbers.$0
+pub fn add(a: i32, b: i32) -> i32 { a + b }
+"#####,
+ r#####"
+/// Adds two numbers.
+///
+/// # Examples
+///
+/// ```
+/// use test::add;
+///
+/// assert_eq!(add(a, b), );
+/// ```
+pub fn add(a: i32, b: i32) -> i32 { a + b }
+"#####,
+ )
+}
+
+#[test]
+fn doctest_generate_documentation_template() {
+ check_doc_test(
+ "generate_documentation_template",
+ r#####"
+pub struct S;
+impl S {
+ pub unsafe fn set_len$0(&mut self, len: usize) -> Result<(), std::io::Error> {
+ /* ... */
+ }
+}
+"#####,
+ r#####"
+pub struct S;
+impl S {
+ /// Sets the length of this [`S`].
+ ///
+ /// # Errors
+ ///
+ /// This function will return an error if .
+ ///
+ /// # Safety
+ ///
+ /// .
+ pub unsafe fn set_len(&mut self, len: usize) -> Result<(), std::io::Error> {
+ /* ... */
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_generate_enum_as_method() {
+ check_doc_test(
+ "generate_enum_as_method",
+ r#####"
+enum Value {
+ Number(i32),
+ Text(String)$0,
+}
+"#####,
+ r#####"
+enum Value {
+ Number(i32),
+ Text(String),
+}
+
+impl Value {
+ fn as_text(&self) -> Option<&String> {
+ if let Self::Text(v) = self {
+ Some(v)
+ } else {
+ None
+ }
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_generate_enum_is_method() {
+ check_doc_test(
+ "generate_enum_is_method",
+ r#####"
+enum Version {
+ Undefined,
+ Minor$0,
+ Major,
+}
+"#####,
+ r#####"
+enum Version {
+ Undefined,
+ Minor,
+ Major,
+}
+
+impl Version {
+ /// Returns `true` if the version is [`Minor`].
+ ///
+ /// [`Minor`]: Version::Minor
+ #[must_use]
+ fn is_minor(&self) -> bool {
+ matches!(self, Self::Minor)
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_generate_enum_try_into_method() {
+ check_doc_test(
+ "generate_enum_try_into_method",
+ r#####"
+enum Value {
+ Number(i32),
+ Text(String)$0,
+}
+"#####,
+ r#####"
+enum Value {
+ Number(i32),
+ Text(String),
+}
+
+impl Value {
+ fn try_into_text(self) -> Result<String, Self> {
+ if let Self::Text(v) = self {
+ Ok(v)
+ } else {
+ Err(self)
+ }
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_generate_enum_variant() {
+ check_doc_test(
+ "generate_enum_variant",
+ r#####"
+enum Countries {
+ Ghana,
+}
+
+fn main() {
+ let country = Countries::Lesotho$0;
+}
+"#####,
+ r#####"
+enum Countries {
+ Ghana,
+ Lesotho,
+}
+
+fn main() {
+ let country = Countries::Lesotho;
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_generate_from_impl_for_enum() {
+ check_doc_test(
+ "generate_from_impl_for_enum",
+ r#####"
+enum A { $0One(u32) }
+"#####,
+ r#####"
+enum A { One(u32) }
+
+impl From<u32> for A {
+ fn from(v: u32) -> Self {
+ Self::One(v)
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_generate_function() {
+ check_doc_test(
+ "generate_function",
+ r#####"
+struct Baz;
+fn baz() -> Baz { Baz }
+fn foo() {
+ bar$0("", baz());
+}
+
+"#####,
+ r#####"
+struct Baz;
+fn baz() -> Baz { Baz }
+fn foo() {
+ bar("", baz());
+}
+
+fn bar(arg: &str, baz: Baz) ${0:-> _} {
+ todo!()
+}
+
+"#####,
+ )
+}
+
+#[test]
+fn doctest_generate_getter() {
+ check_doc_test(
+ "generate_getter",
+ r#####"
+//- minicore: as_ref
+pub struct String;
+impl AsRef<str> for String {
+ fn as_ref(&self) -> &str {
+ ""
+ }
+}
+
+struct Person {
+ nam$0e: String,
+}
+"#####,
+ r#####"
+pub struct String;
+impl AsRef<str> for String {
+ fn as_ref(&self) -> &str {
+ ""
+ }
+}
+
+struct Person {
+ name: String,
+}
+
+impl Person {
+ fn $0name(&self) -> &str {
+ self.name.as_ref()
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_generate_getter_mut() {
+ check_doc_test(
+ "generate_getter_mut",
+ r#####"
+struct Person {
+ nam$0e: String,
+}
+"#####,
+ r#####"
+struct Person {
+ name: String,
+}
+
+impl Person {
+ fn $0name_mut(&mut self) -> &mut String {
+ &mut self.name
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_generate_impl() {
+ check_doc_test(
+ "generate_impl",
+ r#####"
+struct Ctx<T: Clone> {
+ data: T,$0
+}
+"#####,
+ r#####"
+struct Ctx<T: Clone> {
+ data: T,
+}
+
+impl<T: Clone> Ctx<T> {
+ $0
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_generate_is_empty_from_len() {
+ check_doc_test(
+ "generate_is_empty_from_len",
+ r#####"
+struct MyStruct { data: Vec<String> }
+
+impl MyStruct {
+ #[must_use]
+ p$0ub fn len(&self) -> usize {
+ self.data.len()
+ }
+}
+"#####,
+ r#####"
+struct MyStruct { data: Vec<String> }
+
+impl MyStruct {
+ #[must_use]
+ pub fn len(&self) -> usize {
+ self.data.len()
+ }
+
+ #[must_use]
+ pub fn is_empty(&self) -> bool {
+ self.len() == 0
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_generate_new() {
+ check_doc_test(
+ "generate_new",
+ r#####"
+struct Ctx<T: Clone> {
+ data: T,$0
+}
+"#####,
+ r#####"
+struct Ctx<T: Clone> {
+ data: T,
+}
+
+impl<T: Clone> Ctx<T> {
+ fn $0new(data: T) -> Self { Self { data } }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_generate_setter() {
+ check_doc_test(
+ "generate_setter",
+ r#####"
+struct Person {
+ nam$0e: String,
+}
+"#####,
+ r#####"
+struct Person {
+ name: String,
+}
+
+impl Person {
+ fn set_name(&mut self, name: String) {
+ self.name = name;
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_inline_call() {
+ check_doc_test(
+ "inline_call",
+ r#####"
+//- minicore: option
+fn foo(name: Option<&str>) {
+ let name = name.unwrap$0();
+}
+"#####,
+ r#####"
+fn foo(name: Option<&str>) {
+ let name = match name {
+ Some(val) => val,
+ None => panic!("called `Option::unwrap()` on a `None` value"),
+ };
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_inline_into_callers() {
+ check_doc_test(
+ "inline_into_callers",
+ r#####"
+fn print(_: &str) {}
+fn foo$0(word: &str) {
+ if !word.is_empty() {
+ print(word);
+ }
+}
+fn bar() {
+ foo("안녕하세요");
+ foo("여러분");
+}
+"#####,
+ r#####"
+fn print(_: &str) {}
+
+fn bar() {
+ {
+ let word = "안녕하세요";
+ if !word.is_empty() {
+ print(word);
+ }
+ };
+ {
+ let word = "여러분";
+ if !word.is_empty() {
+ print(word);
+ }
+ };
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_inline_local_variable() {
+ check_doc_test(
+ "inline_local_variable",
+ r#####"
+fn main() {
+ let x$0 = 1 + 2;
+ x * 4;
+}
+"#####,
+ r#####"
+fn main() {
+ (1 + 2) * 4;
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_inline_type_alias() {
+ check_doc_test(
+ "inline_type_alias",
+ r#####"
+type A<T = u32> = Vec<T>;
+
+fn main() {
+ let a: $0A;
+}
+"#####,
+ r#####"
+type A<T = u32> = Vec<T>;
+
+fn main() {
+ let a: Vec<u32>;
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_inline_type_alias_uses() {
+ check_doc_test(
+ "inline_type_alias_uses",
+ r#####"
+type $0A = i32;
+fn id(x: A) -> A {
+ x
+};
+fn foo() {
+ let _: A = 3;
+}
+"#####,
+ r#####"
+
+fn id(x: i32) -> i32 {
+ x
+};
+fn foo() {
+ let _: i32 = 3;
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_introduce_named_generic() {
+ check_doc_test(
+ "introduce_named_generic",
+ r#####"
+fn foo(bar: $0impl Bar) {}
+"#####,
+ r#####"
+fn foo<B: Bar>(bar: B) {}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_introduce_named_lifetime() {
+ check_doc_test(
+ "introduce_named_lifetime",
+ r#####"
+impl Cursor<'_$0> {
+ fn node(self) -> &SyntaxNode {
+ match self {
+ Cursor::Replace(node) | Cursor::Before(node) => node,
+ }
+ }
+}
+"#####,
+ r#####"
+impl<'a> Cursor<'a> {
+ fn node(self) -> &SyntaxNode {
+ match self {
+ Cursor::Replace(node) | Cursor::Before(node) => node,
+ }
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_invert_if() {
+ check_doc_test(
+ "invert_if",
+ r#####"
+fn main() {
+ if$0 !y { A } else { B }
+}
+"#####,
+ r#####"
+fn main() {
+ if y { B } else { A }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_line_to_block() {
+ check_doc_test(
+ "line_to_block",
+ r#####"
+ // Multi-line$0
+ // comment
+"#####,
+ r#####"
+ /*
+ Multi-line
+ comment
+ */
+"#####,
+ )
+}
+
+#[test]
+fn doctest_make_raw_string() {
+ check_doc_test(
+ "make_raw_string",
+ r#####"
+fn main() {
+ "Hello,$0 World!";
+}
+"#####,
+ r#####"
+fn main() {
+ r#"Hello, World!"#;
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_make_usual_string() {
+ check_doc_test(
+ "make_usual_string",
+ r#####"
+fn main() {
+ r#"Hello,$0 "World!""#;
+}
+"#####,
+ r#####"
+fn main() {
+ "Hello, \"World!\"";
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_merge_imports() {
+ check_doc_test(
+ "merge_imports",
+ r#####"
+use std::$0fmt::Formatter;
+use std::io;
+"#####,
+ r#####"
+use std::{fmt::Formatter, io};
+"#####,
+ )
+}
+
+#[test]
+fn doctest_merge_match_arms() {
+ check_doc_test(
+ "merge_match_arms",
+ r#####"
+enum Action { Move { distance: u32 }, Stop }
+
+fn handle(action: Action) {
+ match action {
+ $0Action::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_doc_test(
+ "move_arm_cond_to_match_guard",
+ r#####"
+enum Action { Move { distance: u32 }, Stop }
+
+fn handle(action: Action) {
+ match action {
+ Action::Move { distance } => $0if 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_doc_test(
+ "move_bounds_to_where_clause",
+ r#####"
+fn apply<T, U, $0F: FnOnce(T) -> U>(f: F, x: T) -> U {
+ f(x)
+}
+"#####,
+ r#####"
+fn apply<T, U, F>(f: F, x: T) -> U where F: FnOnce(T) -> U {
+ f(x)
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_move_format_string_arg() {
+ check_doc_test(
+ "move_format_string_arg",
+ r#####"
+macro_rules! format_args {
+ ($lit:literal $(tt:tt)*) => { 0 },
+}
+macro_rules! print {
+ ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*)));
+}
+
+fn main() {
+ print!("{x + 1}$0");
+}
+"#####,
+ r#####"
+macro_rules! format_args {
+ ($lit:literal $(tt:tt)*) => { 0 },
+}
+macro_rules! print {
+ ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*)));
+}
+
+fn main() {
+ print!("{}"$0, x + 1);
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_move_from_mod_rs() {
+ check_doc_test(
+ "move_from_mod_rs",
+ r#####"
+//- /main.rs
+mod a;
+//- /a/mod.rs
+$0fn t() {}$0
+"#####,
+ r#####"
+fn t() {}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_move_guard_to_arm_body() {
+ check_doc_test(
+ "move_guard_to_arm_body",
+ r#####"
+enum Action { Move { distance: u32 }, Stop }
+
+fn handle(action: Action) {
+ match action {
+ Action::Move { distance } $0if 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_module_to_file() {
+ check_doc_test(
+ "move_module_to_file",
+ r#####"
+mod $0foo {
+ fn t() {}
+}
+"#####,
+ r#####"
+mod foo;
+"#####,
+ )
+}
+
+#[test]
+fn doctest_move_to_mod_rs() {
+ check_doc_test(
+ "move_to_mod_rs",
+ r#####"
+//- /main.rs
+mod a;
+//- /a.rs
+$0fn t() {}$0
+"#####,
+ r#####"
+fn t() {}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_promote_local_to_const() {
+ check_doc_test(
+ "promote_local_to_const",
+ r#####"
+fn main() {
+ let foo$0 = true;
+
+ if foo {
+ println!("It's true");
+ } else {
+ println!("It's false");
+ }
+}
+"#####,
+ r#####"
+fn main() {
+ const $0FOO: bool = true;
+
+ if FOO {
+ println!("It's true");
+ } else {
+ println!("It's false");
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_pull_assignment_up() {
+ check_doc_test(
+ "pull_assignment_up",
+ r#####"
+fn main() {
+ let mut foo = 6;
+
+ if true {
+ $0foo = 5;
+ } else {
+ foo = 4;
+ }
+}
+"#####,
+ r#####"
+fn main() {
+ let mut foo = 6;
+
+ foo = if true {
+ 5
+ } else {
+ 4
+ };
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_qualify_method_call() {
+ check_doc_test(
+ "qualify_method_call",
+ r#####"
+struct Foo;
+impl Foo {
+ fn foo(&self) {}
+}
+fn main() {
+ let foo = Foo;
+ foo.fo$0o();
+}
+"#####,
+ r#####"
+struct Foo;
+impl Foo {
+ fn foo(&self) {}
+}
+fn main() {
+ let foo = Foo;
+ Foo::foo(&foo);
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_qualify_path() {
+ check_doc_test(
+ "qualify_path",
+ r#####"
+fn main() {
+ let map = HashMap$0::new();
+}
+pub mod std { pub mod collections { pub struct HashMap { } } }
+"#####,
+ r#####"
+fn main() {
+ let map = std::collections::HashMap::new();
+}
+pub mod std { pub mod collections { pub struct HashMap { } } }
+"#####,
+ )
+}
+
+#[test]
+fn doctest_reformat_number_literal() {
+ check_doc_test(
+ "reformat_number_literal",
+ r#####"
+const _: i32 = 1012345$0;
+"#####,
+ r#####"
+const _: i32 = 1_012_345;
+"#####,
+ )
+}
+
+#[test]
+fn doctest_remove_dbg() {
+ check_doc_test(
+ "remove_dbg",
+ r#####"
+fn main() {
+ $0dbg!(92);
+}
+"#####,
+ r#####"
+fn main() {
+ 92;
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_remove_hash() {
+ check_doc_test(
+ "remove_hash",
+ r#####"
+fn main() {
+ r#"Hello,$0 World!"#;
+}
+"#####,
+ r#####"
+fn main() {
+ r"Hello, World!";
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_remove_mut() {
+ check_doc_test(
+ "remove_mut",
+ r#####"
+impl Walrus {
+ fn feed(&mut$0 self, amount: u32) {}
+}
+"#####,
+ r#####"
+impl Walrus {
+ fn feed(&self, amount: u32) {}
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_remove_unused_param() {
+ check_doc_test(
+ "remove_unused_param",
+ r#####"
+fn frobnicate(x: i32$0) {}
+
+fn main() {
+ frobnicate(92);
+}
+"#####,
+ r#####"
+fn frobnicate() {}
+
+fn main() {
+ frobnicate();
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_reorder_fields() {
+ check_doc_test(
+ "reorder_fields",
+ r#####"
+struct Foo {foo: i32, bar: i32};
+const test: Foo = $0Foo {bar: 0, foo: 1}
+"#####,
+ r#####"
+struct Foo {foo: i32, bar: i32};
+const test: Foo = Foo {foo: 1, bar: 0}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_reorder_impl_items() {
+ check_doc_test(
+ "reorder_impl_items",
+ r#####"
+trait Foo {
+ type A;
+ const B: u8;
+ fn c();
+}
+
+struct Bar;
+$0impl Foo for Bar {
+ const B: u8 = 17;
+ fn c() {}
+ type A = String;
+}
+"#####,
+ r#####"
+trait Foo {
+ type A;
+ const B: u8;
+ fn c();
+}
+
+struct Bar;
+impl Foo for Bar {
+ type A = String;
+ const B: u8 = 17;
+ fn c() {}
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_replace_char_with_string() {
+ check_doc_test(
+ "replace_char_with_string",
+ r#####"
+fn main() {
+ find('{$0');
+}
+"#####,
+ r#####"
+fn main() {
+ find("{");
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_replace_derive_with_manual_impl() {
+ check_doc_test(
+ "replace_derive_with_manual_impl",
+ r#####"
+//- minicore: derive
+trait Debug { fn fmt(&self, f: &mut Formatter) -> Result<()>; }
+#[derive(Deb$0ug, Display)]
+struct S;
+"#####,
+ r#####"
+trait Debug { fn fmt(&self, f: &mut Formatter) -> Result<()>; }
+#[derive(Display)]
+struct S;
+
+impl Debug for S {
+ $0fn fmt(&self, f: &mut Formatter) -> Result<()> {
+ f.debug_struct("S").finish()
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_replace_if_let_with_match() {
+ check_doc_test(
+ "replace_if_let_with_match",
+ r#####"
+enum Action { Move { distance: u32 }, Stop }
+
+fn handle(action: Action) {
+ $0if 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_replace_let_with_if_let() {
+ check_doc_test(
+ "replace_let_with_if_let",
+ r#####"
+enum Option<T> { Some(T), None }
+
+fn main(action: Action) {
+ $0let x = compute();
+}
+
+fn compute() -> Option<i32> { None }
+"#####,
+ r#####"
+enum Option<T> { Some(T), None }
+
+fn main(action: Action) {
+ if let Some(x) = compute() {
+ }
+}
+
+fn compute() -> Option<i32> { None }
+"#####,
+ )
+}
+
+#[test]
+fn doctest_replace_match_with_if_let() {
+ check_doc_test(
+ "replace_match_with_if_let",
+ r#####"
+enum Action { Move { distance: u32 }, Stop }
+
+fn handle(action: Action) {
+ $0match action {
+ Action::Move { distance } => foo(distance),
+ _ => bar(),
+ }
+}
+"#####,
+ r#####"
+enum Action { Move { distance: u32 }, Stop }
+
+fn handle(action: Action) {
+ if let Action::Move { distance } = action {
+ foo(distance)
+ } else {
+ bar()
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_replace_or_else_with_or() {
+ check_doc_test(
+ "replace_or_else_with_or",
+ r#####"
+//- minicore:option
+fn foo() {
+ let a = Some(1);
+ a.unwra$0p_or_else(|| 2);
+}
+"#####,
+ r#####"
+fn foo() {
+ let a = Some(1);
+ a.unwrap_or(2);
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_replace_or_with_or_else() {
+ check_doc_test(
+ "replace_or_with_or_else",
+ r#####"
+//- minicore:option
+fn foo() {
+ let a = Some(1);
+ a.unwra$0p_or(2);
+}
+"#####,
+ r#####"
+fn foo() {
+ let a = Some(1);
+ a.unwrap_or_else(|| 2);
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_replace_qualified_name_with_use() {
+ check_doc_test(
+ "replace_qualified_name_with_use",
+ r#####"
+mod std { pub mod collections { pub struct HashMap<T, U>(T, U); } }
+fn process(map: std::collections::$0HashMap<String, String>) {}
+"#####,
+ r#####"
+use std::collections::HashMap;
+
+mod std { pub mod collections { pub struct HashMap<T, U>(T, U); } }
+fn process(map: HashMap<String, String>) {}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_replace_string_with_char() {
+ check_doc_test(
+ "replace_string_with_char",
+ r#####"
+fn main() {
+ find("{$0");
+}
+"#####,
+ r#####"
+fn main() {
+ find('{');
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_replace_try_expr_with_match() {
+ check_doc_test(
+ "replace_try_expr_with_match",
+ r#####"
+//- minicore:option
+fn handle() {
+ let pat = Some(true)$0?;
+}
+"#####,
+ r#####"
+fn handle() {
+ let pat = match Some(true) {
+ Some(it) => it,
+ None => return None,
+ };
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_replace_turbofish_with_explicit_type() {
+ check_doc_test(
+ "replace_turbofish_with_explicit_type",
+ r#####"
+fn make<T>() -> T { ) }
+fn main() {
+ let a = make$0::<i32>();
+}
+"#####,
+ r#####"
+fn make<T>() -> T { ) }
+fn main() {
+ let a: i32 = make();
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_sort_items() {
+ check_doc_test(
+ "sort_items",
+ r#####"
+struct $0Foo$0 { second: u32, first: String }
+"#####,
+ r#####"
+struct Foo { first: String, second: u32 }
+"#####,
+ )
+}
+
+#[test]
+fn doctest_sort_items_1() {
+ check_doc_test(
+ "sort_items",
+ r#####"
+trait $0Bar$0 {
+ fn second(&self) -> u32;
+ fn first(&self) -> String;
+}
+"#####,
+ r#####"
+trait Bar {
+ fn first(&self) -> String;
+ fn second(&self) -> u32;
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_sort_items_2() {
+ check_doc_test(
+ "sort_items",
+ r#####"
+struct Baz;
+impl $0Baz$0 {
+ fn second(&self) -> u32;
+ fn first(&self) -> String;
+}
+"#####,
+ r#####"
+struct Baz;
+impl Baz {
+ fn first(&self) -> String;
+ fn second(&self) -> u32;
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_sort_items_3() {
+ check_doc_test(
+ "sort_items",
+ r#####"
+enum $0Animal$0 {
+ Dog(String, f64),
+ Cat { weight: f64, name: String },
+}
+"#####,
+ r#####"
+enum Animal {
+ Cat { weight: f64, name: String },
+ Dog(String, f64),
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_sort_items_4() {
+ check_doc_test(
+ "sort_items",
+ r#####"
+enum Animal {
+ Dog(String, f64),
+ Cat $0{ weight: f64, name: String }$0,
+}
+"#####,
+ r#####"
+enum Animal {
+ Dog(String, f64),
+ Cat { name: String, weight: f64 },
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_split_import() {
+ check_doc_test(
+ "split_import",
+ r#####"
+use std::$0collections::HashMap;
+"#####,
+ r#####"
+use std::{collections::HashMap};
+"#####,
+ )
+}
+
+#[test]
+fn doctest_toggle_ignore() {
+ check_doc_test(
+ "toggle_ignore",
+ r#####"
+$0#[test]
+fn arithmetics {
+ assert_eq!(2 + 2, 5);
+}
+"#####,
+ r#####"
+#[test]
+#[ignore]
+fn arithmetics {
+ assert_eq!(2 + 2, 5);
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_unmerge_match_arm() {
+ check_doc_test(
+ "unmerge_match_arm",
+ r#####"
+enum Action { Move { distance: u32 }, Stop }
+
+fn handle(action: Action) {
+ match action {
+ Action::Move(..) $0| Action::Stop => foo(),
+ }
+}
+"#####,
+ r#####"
+enum Action { Move { distance: u32 }, Stop }
+
+fn handle(action: Action) {
+ match action {
+ Action::Move(..) => foo(),
+ Action::Stop => foo(),
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_unmerge_use() {
+ check_doc_test(
+ "unmerge_use",
+ r#####"
+use std::fmt::{Debug, Display$0};
+"#####,
+ r#####"
+use std::fmt::{Debug};
+use std::fmt::Display;
+"#####,
+ )
+}
+
+#[test]
+fn doctest_unnecessary_async() {
+ check_doc_test(
+ "unnecessary_async",
+ r#####"
+pub async f$0n foo() {}
+pub async fn bar() { foo().await }
+"#####,
+ r#####"
+pub fn foo() {}
+pub async fn bar() { foo() }
+"#####,
+ )
+}
+
+#[test]
+fn doctest_unwrap_block() {
+ check_doc_test(
+ "unwrap_block",
+ r#####"
+fn foo() {
+ if true {$0
+ println!("foo");
+ }
+}
+"#####,
+ r#####"
+fn foo() {
+ println!("foo");
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_unwrap_result_return_type() {
+ check_doc_test(
+ "unwrap_result_return_type",
+ r#####"
+//- minicore: result
+fn foo() -> Result<i32>$0 { Ok(42i32) }
+"#####,
+ r#####"
+fn foo() -> i32 { 42i32 }
+"#####,
+ )
+}
+
++#[test]
++fn doctest_unwrap_tuple() {
++ check_doc_test(
++ "unwrap_tuple",
++ r#####"
++//- minicore: result
++fn main() {
++ $0let (foo, bar) = ("Foo", "Bar");
++}
++"#####,
++ r#####"
++fn main() {
++ let foo = "Foo";
++ let bar = "Bar";
++}
++"#####,
++ )
++}
++
+#[test]
+fn doctest_wrap_return_type_in_result() {
+ check_doc_test(
+ "wrap_return_type_in_result",
+ r#####"
+//- minicore: result
+fn foo() -> i32$0 { 42i32 }
+"#####,
+ r#####"
+fn foo() -> Result<i32, ${0:_}> { Ok(42i32) }
+"#####,
+ )
+}
--- /dev/null
- AstNode, SyntaxElement, SyntaxKind, SyntaxNode, TextRange, T,
+//! Completion for associated items in a trait implementation.
+//!
+//! This module adds the completion items related to implementing associated
+//! items within an `impl Trait for Struct` block. The current context node
+//! must be within either a `FN`, `TYPE_ALIAS`, or `CONST` node
+//! and an direct child of an `IMPL`.
+//!
+//! # Examples
+//!
+//! Considering the following trait `impl`:
+//!
+//! ```ignore
+//! trait SomeTrait {
+//! fn foo();
+//! }
+//!
+//! impl SomeTrait for () {
+//! fn f$0
+//! }
+//! ```
+//!
+//! may result in the completion of the following method:
+//!
+//! ```ignore
+//! # trait SomeTrait {
+//! # fn foo();
+//! # }
+//!
+//! impl SomeTrait for () {
+//! fn foo() {}$0
+//! }
+//! ```
+
+use hir::{self, HasAttrs};
+use ide_db::{
+ path_transform::PathTransform, syntax_helpers::insert_whitespace_into_node,
+ traits::get_missing_assoc_items, SymbolKind,
+};
+use syntax::{
+ ast::{self, edit_in_place::AttrsOwnerEdit},
- let token = ctx.token.clone();
++ AstNode, SyntaxElement, SyntaxKind, TextRange, T,
+};
+use text_edit::TextEdit;
+
+use crate::{
+ context::PathCompletionCtx, CompletionContext, CompletionItem, CompletionItemKind,
+ CompletionRelevance, Completions,
+};
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+enum ImplCompletionKind {
+ All,
+ Fn,
+ TypeAlias,
+ Const,
+}
+
+pub(crate) fn complete_trait_impl_const(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ name: &Option<ast::Name>,
+) -> Option<()> {
+ complete_trait_impl_name(acc, ctx, name, ImplCompletionKind::Const)
+}
+
+pub(crate) fn complete_trait_impl_type_alias(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ name: &Option<ast::Name>,
+) -> Option<()> {
+ complete_trait_impl_name(acc, ctx, name, ImplCompletionKind::TypeAlias)
+}
+
+pub(crate) fn complete_trait_impl_fn(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ name: &Option<ast::Name>,
+) -> Option<()> {
+ complete_trait_impl_name(acc, ctx, name, ImplCompletionKind::Fn)
+}
+
+fn complete_trait_impl_name(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ name: &Option<ast::Name>,
+ kind: ImplCompletionKind,
+) -> Option<()> {
- None => if token.kind() == SyntaxKind::WHITESPACE { token.prev_token()? } else { token }
- .parent(),
+ let item = match name {
+ Some(name) => name.syntax().parent(),
- complete_trait_impl(
- acc,
- ctx,
- kind,
- replacement_range(ctx, &item),
- // item -> ASSOC_ITEM_LIST -> IMPL
- &ast::Impl::cast(item.parent()?.parent()?)?,
- );
++ None => {
++ let token = &ctx.token;
++ match token.kind() {
++ SyntaxKind::WHITESPACE => token.prev_token()?,
++ _ => token.clone(),
++ }
++ .parent()
++ }
+ }?;
- fn replacement_range(ctx: &CompletionContext<'_>, item: &SyntaxNode) -> TextRange {
- let first_child = item
- .children_with_tokens()
- .find(|child| {
- !matches!(child.kind(), SyntaxKind::COMMENT | SyntaxKind::WHITESPACE | SyntaxKind::ATTR)
- })
- .unwrap_or_else(|| SyntaxElement::Node(item.clone()));
-
- TextRange::new(first_child.text_range().start(), ctx.source_range().end())
- }
-
++ let item = ctx.sema.original_syntax_node(&item)?;
++ // item -> ASSOC_ITEM_LIST -> IMPL
++ let impl_def = ast::Impl::cast(item.parent()?.parent()?)?;
++ let replacement_range = {
++ // ctx.sema.original_ast_node(item)?;
++ let first_child = item
++ .children_with_tokens()
++ .find(|child| {
++ !matches!(
++ child.kind(),
++ SyntaxKind::COMMENT | SyntaxKind::WHITESPACE | SyntaxKind::ATTR
++ )
++ })
++ .unwrap_or_else(|| SyntaxElement::Node(item.clone()));
++
++ TextRange::new(first_child.text_range().start(), ctx.source_range().end())
++ };
++
++ complete_trait_impl(acc, ctx, kind, replacement_range, &impl_def);
+ Some(())
+}
+
+pub(crate) fn complete_trait_impl_item_by_name(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ name_ref: &Option<ast::NameRef>,
+ impl_: &Option<ast::Impl>,
+) {
+ if !path_ctx.is_trivial_path() {
+ return;
+ }
+ if let Some(impl_) = impl_ {
+ complete_trait_impl(
+ acc,
+ ctx,
+ ImplCompletionKind::All,
+ match name_ref {
+ Some(name) => name.syntax().text_range(),
+ None => ctx.source_range(),
+ },
+ impl_,
+ );
+ }
+}
+
+fn complete_trait_impl(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ kind: ImplCompletionKind,
+ replacement_range: TextRange,
+ impl_def: &ast::Impl,
+) {
+ if let Some(hir_impl) = ctx.sema.to_def(impl_def) {
+ get_missing_assoc_items(&ctx.sema, impl_def).into_iter().for_each(|item| {
+ use self::ImplCompletionKind::*;
+ match (item, kind) {
+ (hir::AssocItem::Function(func), All | Fn) => {
+ add_function_impl(acc, ctx, replacement_range, func, hir_impl)
+ }
+ (hir::AssocItem::TypeAlias(type_alias), All | TypeAlias) => {
+ add_type_alias_impl(acc, ctx, replacement_range, type_alias)
+ }
+ (hir::AssocItem::Const(const_), All | Const) => {
+ add_const_impl(acc, ctx, replacement_range, const_, hir_impl)
+ }
+ _ => {}
+ }
+ });
+ }
+}
+
+fn add_function_impl(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ replacement_range: TextRange,
+ func: hir::Function,
+ impl_def: hir::Impl,
+) {
+ let fn_name = func.name(ctx.db);
+
+ let label = format!(
+ "fn {}({})",
+ fn_name,
+ if func.assoc_fn_params(ctx.db).is_empty() { "" } else { ".." }
+ );
+
+ let completion_kind = if func.has_self_param(ctx.db) {
+ CompletionItemKind::Method
+ } else {
+ CompletionItemKind::SymbolKind(SymbolKind::Function)
+ };
+
+ let mut item = CompletionItem::new(completion_kind, replacement_range, label);
+ item.lookup_by(format!("fn {}", fn_name))
+ .set_documentation(func.docs(ctx.db))
+ .set_relevance(CompletionRelevance { is_item_from_trait: true, ..Default::default() });
+
+ if let Some(source) = ctx.sema.source(func) {
+ let assoc_item = ast::AssocItem::Fn(source.value);
+ if let Some(transformed_item) = get_transformed_assoc_item(ctx, assoc_item, impl_def) {
+ let transformed_fn = match transformed_item {
+ ast::AssocItem::Fn(func) => func,
+ _ => unreachable!(),
+ };
+
+ let function_decl = function_declaration(&transformed_fn, source.file_id.is_macro());
+ match ctx.config.snippet_cap {
+ Some(cap) => {
+ let snippet = format!("{} {{\n $0\n}}", function_decl);
+ item.snippet_edit(cap, TextEdit::replace(replacement_range, snippet));
+ }
+ None => {
+ let header = format!("{} {{", function_decl);
+ item.text_edit(TextEdit::replace(replacement_range, header));
+ }
+ };
+ item.add_to(acc);
+ }
+ }
+}
+
+/// Transform a relevant associated item to inline generics from the impl, remove attrs and docs, etc.
+fn get_transformed_assoc_item(
+ ctx: &CompletionContext<'_>,
+ assoc_item: ast::AssocItem,
+ impl_def: hir::Impl,
+) -> Option<ast::AssocItem> {
+ let assoc_item = assoc_item.clone_for_update();
+ let trait_ = impl_def.trait_(ctx.db)?;
+ let source_scope = &ctx.sema.scope_for_def(trait_);
+ let target_scope = &ctx.sema.scope(ctx.sema.source(impl_def)?.syntax().value)?;
+ let transform = PathTransform::trait_impl(
+ target_scope,
+ source_scope,
+ trait_,
+ ctx.sema.source(impl_def)?.value,
+ );
+
+ transform.apply(assoc_item.syntax());
+ if let ast::AssocItem::Fn(func) = &assoc_item {
+ func.remove_attrs_and_docs();
+ }
+ Some(assoc_item)
+}
+
+fn add_type_alias_impl(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ replacement_range: TextRange,
+ type_alias: hir::TypeAlias,
+) {
+ let alias_name = type_alias.name(ctx.db);
+ let (alias_name, escaped_name) =
+ (alias_name.unescaped().to_smol_str(), alias_name.to_smol_str());
+
+ let label = format!("type {} =", alias_name);
+ let replacement = format!("type {} = ", escaped_name);
+
+ let mut item = CompletionItem::new(SymbolKind::TypeAlias, replacement_range, label);
+ item.lookup_by(format!("type {}", alias_name))
+ .set_documentation(type_alias.docs(ctx.db))
+ .set_relevance(CompletionRelevance { is_item_from_trait: true, ..Default::default() });
+ match ctx.config.snippet_cap {
+ Some(cap) => item
+ .snippet_edit(cap, TextEdit::replace(replacement_range, format!("{}$0;", replacement))),
+ None => item.text_edit(TextEdit::replace(replacement_range, replacement)),
+ };
+ item.add_to(acc);
+}
+
+fn add_const_impl(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ replacement_range: TextRange,
+ const_: hir::Const,
+ impl_def: hir::Impl,
+) {
+ let const_name = const_.name(ctx.db).map(|n| n.to_smol_str());
+
+ if let Some(const_name) = const_name {
+ if let Some(source) = ctx.sema.source(const_) {
+ let assoc_item = ast::AssocItem::Const(source.value);
+ if let Some(transformed_item) = get_transformed_assoc_item(ctx, assoc_item, impl_def) {
+ let transformed_const = match transformed_item {
+ ast::AssocItem::Const(const_) => const_,
+ _ => unreachable!(),
+ };
+
+ let label = make_const_compl_syntax(&transformed_const, source.file_id.is_macro());
+ let replacement = format!("{} ", label);
+
+ let mut item = CompletionItem::new(SymbolKind::Const, replacement_range, label);
+ item.lookup_by(format!("const {}", const_name))
+ .set_documentation(const_.docs(ctx.db))
+ .set_relevance(CompletionRelevance {
+ is_item_from_trait: true,
+ ..Default::default()
+ });
+ match ctx.config.snippet_cap {
+ Some(cap) => item.snippet_edit(
+ cap,
+ TextEdit::replace(replacement_range, format!("{}$0;", replacement)),
+ ),
+ None => item.text_edit(TextEdit::replace(replacement_range, replacement)),
+ };
+ item.add_to(acc);
+ }
+ }
+ }
+}
+
+fn make_const_compl_syntax(const_: &ast::Const, needs_whitespace: bool) -> String {
+ const_.remove_attrs_and_docs();
+ let const_ = if needs_whitespace {
+ insert_whitespace_into_node::insert_ws_into(const_.syntax().clone())
+ } else {
+ const_.syntax().clone()
+ };
+
+ let start = const_.text_range().start();
+ let const_end = const_.text_range().end();
+
+ let end = const_
+ .children_with_tokens()
+ .find(|s| s.kind() == T![;] || s.kind() == T![=])
+ .map_or(const_end, |f| f.text_range().start());
+
+ let len = end - start;
+ let range = TextRange::new(0.into(), len);
+
+ let syntax = const_.text().slice(range).to_string();
+
+ format!("{} =", syntax.trim_end())
+}
+
+fn function_declaration(node: &ast::Fn, needs_whitespace: bool) -> String {
+ node.remove_attrs_and_docs();
+
+ let node = if needs_whitespace {
+ insert_whitespace_into_node::insert_ws_into(node.syntax().clone())
+ } else {
+ node.syntax().clone()
+ };
+
+ let start = node.text_range().start();
+ let end = node.text_range().end();
+
+ let end = node
+ .last_child_or_token()
+ .filter(|s| s.kind() == T![;] || s.kind() == SyntaxKind::BLOCK_EXPR)
+ .map_or(end, |f| f.text_range().start());
+
+ let len = end - start;
+ let range = TextRange::new(0.into(), len);
+
+ let syntax = node.text().slice(range).to_string();
+
+ syntax.trim_end().to_owned()
+}
+
+#[cfg(test)]
+mod tests {
+ use expect_test::{expect, Expect};
+
+ use crate::tests::{check_edit, completion_list_no_kw};
+
+ fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list_no_kw(ra_fixture);
+ expect.assert_eq(&actual)
+ }
+
+ #[test]
+ fn no_completion_inside_fn() {
+ check(
+ r"
+trait Test { fn test(); fn test2(); }
+struct T;
+
+impl Test for T {
+ fn test() {
+ t$0
+ }
+}
+",
+ expect![[r#"
+ sp Self
+ st T
+ tt Test
+ bt u32
+ "#]],
+ );
+
+ check(
+ r"
+trait Test { fn test(); fn test2(); }
+struct T;
+
+impl Test for T {
+ fn test() {
+ fn t$0
+ }
+}
+",
+ expect![[""]],
+ );
+
+ check(
+ r"
+trait Test { fn test(); fn test2(); }
+struct T;
+
+impl Test for T {
+ fn test() {
+ fn $0
+ }
+}
+",
+ expect![[""]],
+ );
+
+ // https://github.com/rust-lang/rust-analyzer/pull/5976#issuecomment-692332191
+ check(
+ r"
+trait Test { fn test(); fn test2(); }
+struct T;
+
+impl Test for T {
+ fn test() {
+ foo.$0
+ }
+}
+",
+ expect![[r#""#]],
+ );
+
+ check(
+ r"
+trait Test { fn test(_: i32); fn test2(); }
+struct T;
+
+impl Test for T {
+ fn test(t$0)
+}
+",
+ expect![[r#"
+ sp Self
+ st T
+ bn &mut self
+ bn &self
+ bn mut self
+ bn self
+ "#]],
+ );
+
+ check(
+ r"
+trait Test { fn test(_: fn()); fn test2(); }
+struct T;
+
+impl Test for T {
+ fn test(f: fn $0)
+}
+",
+ expect![[r#"
+ sp Self
+ st T
+ "#]],
+ );
+ }
+
+ #[test]
+ fn no_completion_inside_const() {
+ check(
+ r"
+trait Test { const TEST: fn(); const TEST2: u32; type Test; fn test(); }
+struct T;
+
+impl Test for T {
+ const TEST: fn $0
+}
+",
+ expect![[r#""#]],
+ );
+
+ check(
+ r"
+trait Test { const TEST: u32; const TEST2: u32; type Test; fn test(); }
+struct T;
+
+impl Test for T {
+ const TEST: T$0
+}
+",
+ expect![[r#"
+ sp Self
+ st T
+ tt Test
+ bt u32
+ "#]],
+ );
+
+ check(
+ r"
+trait Test { const TEST: u32; const TEST2: u32; type Test; fn test(); }
+struct T;
+
+impl Test for T {
+ const TEST: u32 = f$0
+}
+",
+ expect![[r#"
+ sp Self
+ st T
+ tt Test
+ bt u32
+ "#]],
+ );
+
+ check(
+ r"
+trait Test { const TEST: u32; const TEST2: u32; type Test; fn test(); }
+struct T;
+
+impl Test for T {
+ const TEST: u32 = {
+ t$0
+ };
+}
+",
+ expect![[r#"
+ sp Self
+ st T
+ tt Test
+ bt u32
+ "#]],
+ );
+
+ check(
+ r"
+trait Test { const TEST: u32; const TEST2: u32; type Test; fn test(); }
+struct T;
+
+impl Test for T {
+ const TEST: u32 = {
+ fn $0
+ };
+}
+",
+ expect![[""]],
+ );
+
+ check(
+ r"
+trait Test { const TEST: u32; const TEST2: u32; type Test; fn test(); }
+struct T;
+
+impl Test for T {
+ const TEST: u32 = {
+ fn t$0
+ };
+}
+",
+ expect![[""]],
+ );
+ }
+
+ #[test]
+ fn no_completion_inside_type() {
+ check(
+ r"
+trait Test { type Test; type Test2; fn test(); }
+struct T;
+
+impl Test for T {
+ type Test = T$0;
+}
+",
+ expect![[r#"
+ sp Self
+ st T
+ tt Test
+ bt u32
+ "#]],
+ );
+
+ check(
+ r"
+trait Test { type Test; type Test2; fn test(); }
+struct T;
+
+impl Test for T {
+ type Test = fn $0;
+}
+",
+ expect![[r#""#]],
+ );
+ }
+
+ #[test]
+ fn name_ref_single_function() {
+ check_edit(
+ "fn test",
+ r#"
+trait Test {
+ fn test();
+}
+struct T;
+
+impl Test for T {
+ t$0
+}
+"#,
+ r#"
+trait Test {
+ fn test();
+}
+struct T;
+
+impl Test for T {
+ fn test() {
+ $0
+}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn single_function() {
+ check_edit(
+ "fn test",
+ r#"
+trait Test {
+ fn test();
+}
+struct T;
+
+impl Test for T {
+ fn t$0
+}
+"#,
+ r#"
+trait Test {
+ fn test();
+}
+struct T;
+
+impl Test for T {
+ fn test() {
+ $0
+}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn generic_fn() {
+ check_edit(
+ "fn foo",
+ r#"
+trait Test {
+ fn foo<T>();
+}
+struct T;
+
+impl Test for T {
+ fn f$0
+}
+"#,
+ r#"
+trait Test {
+ fn foo<T>();
+}
+struct T;
+
+impl Test for T {
+ fn foo<T>() {
+ $0
+}
+}
+"#,
+ );
+ check_edit(
+ "fn foo",
+ r#"
+trait Test {
+ fn foo<T>() where T: Into<String>;
+}
+struct T;
+
+impl Test for T {
+ fn f$0
+}
+"#,
+ r#"
+trait Test {
+ fn foo<T>() where T: Into<String>;
+}
+struct T;
+
+impl Test for T {
+ fn foo<T>() where T: Into<String> {
+ $0
+}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn associated_type() {
+ check_edit(
+ "type SomeType",
+ r#"
+trait Test {
+ type SomeType;
+}
+
+impl Test for () {
+ type S$0
+}
+"#,
+ "
+trait Test {
+ type SomeType;
+}
+
+impl Test for () {
+ type SomeType = $0;\n\
+}
+",
+ );
+ check_edit(
+ "type SomeType",
+ r#"
+trait Test {
+ type SomeType;
+}
+
+impl Test for () {
+ type$0
+}
+"#,
+ "
+trait Test {
+ type SomeType;
+}
+
+impl Test for () {
+ type SomeType = $0;\n\
+}
+",
+ );
+ }
+
+ #[test]
+ fn associated_const() {
+ check_edit(
+ "const SOME_CONST",
+ r#"
+trait Test {
+ const SOME_CONST: u16;
+}
+
+impl Test for () {
+ const S$0
+}
+"#,
+ "
+trait Test {
+ const SOME_CONST: u16;
+}
+
+impl Test for () {
+ const SOME_CONST: u16 = $0;\n\
+}
+",
+ );
+
+ check_edit(
+ "const SOME_CONST",
+ r#"
+trait Test {
+ const SOME_CONST: u16 = 92;
+}
+
+impl Test for () {
+ const S$0
+}
+"#,
+ "
+trait Test {
+ const SOME_CONST: u16 = 92;
+}
+
+impl Test for () {
+ const SOME_CONST: u16 = $0;\n\
+}
+",
+ );
+ }
+
+ #[test]
+ fn complete_without_name() {
+ let test = |completion: &str, hint: &str, completed: &str, next_sibling: &str| {
+ check_edit(
+ completion,
+ &format!(
+ r#"
+trait Test {{
+ type Foo;
+ const CONST: u16;
+ fn bar();
+}}
+struct T;
+
+impl Test for T {{
+ {}
+ {}
+}}
+"#,
+ hint, next_sibling
+ ),
+ &format!(
+ r#"
+trait Test {{
+ type Foo;
+ const CONST: u16;
+ fn bar();
+}}
+struct T;
+
+impl Test for T {{
+ {}
+ {}
+}}
+"#,
+ completed, next_sibling
+ ),
+ )
+ };
+
+ // Enumerate some possible next siblings.
+ for next_sibling in &[
+ "",
+ "fn other_fn() {}", // `const $0 fn` -> `const fn`
+ "type OtherType = i32;",
+ "const OTHER_CONST: i32 = 0;",
+ "async fn other_fn() {}",
+ "unsafe fn other_fn() {}",
+ "default fn other_fn() {}",
+ "default type OtherType = i32;",
+ "default const OTHER_CONST: i32 = 0;",
+ ] {
+ test("fn bar", "fn $0", "fn bar() {\n $0\n}", next_sibling);
+ test("type Foo", "type $0", "type Foo = $0;", next_sibling);
+ test("const CONST", "const $0", "const CONST: u16 = $0;", next_sibling);
+ }
+ }
+
+ #[test]
+ fn snippet_does_not_overwrite_comment_or_attr() {
+ let test = |completion: &str, hint: &str, completed: &str| {
+ check_edit(
+ completion,
+ &format!(
+ r#"
+trait Foo {{
+ type Type;
+ fn function();
+ const CONST: i32 = 0;
+}}
+struct T;
+
+impl Foo for T {{
+ // Comment
+ #[bar]
+ {}
+}}
+"#,
+ hint
+ ),
+ &format!(
+ r#"
+trait Foo {{
+ type Type;
+ fn function();
+ const CONST: i32 = 0;
+}}
+struct T;
+
+impl Foo for T {{
+ // Comment
+ #[bar]
+ {}
+}}
+"#,
+ completed
+ ),
+ )
+ };
+ test("fn function", "fn f$0", "fn function() {\n $0\n}");
+ test("type Type", "type T$0", "type Type = $0;");
+ test("const CONST", "const C$0", "const CONST: i32 = $0;");
+ }
+
+ #[test]
+ fn generics_are_inlined_in_return_type() {
+ check_edit(
+ "fn function",
+ r#"
+trait Foo<T> {
+ fn function() -> T;
+}
+struct Bar;
+
+impl Foo<u32> for Bar {
+ fn f$0
+}
+"#,
+ r#"
+trait Foo<T> {
+ fn function() -> T;
+}
+struct Bar;
+
+impl Foo<u32> for Bar {
+ fn function() -> u32 {
+ $0
+}
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn generics_are_inlined_in_parameter() {
+ check_edit(
+ "fn function",
+ r#"
+trait Foo<T> {
+ fn function(bar: T);
+}
+struct Bar;
+
+impl Foo<u32> for Bar {
+ fn f$0
+}
+"#,
+ r#"
+trait Foo<T> {
+ fn function(bar: T);
+}
+struct Bar;
+
+impl Foo<u32> for Bar {
+ fn function(bar: u32) {
+ $0
+}
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn generics_are_inlined_when_part_of_other_types() {
+ check_edit(
+ "fn function",
+ r#"
+trait Foo<T> {
+ fn function(bar: Vec<T>);
+}
+struct Bar;
+
+impl Foo<u32> for Bar {
+ fn f$0
+}
+"#,
+ r#"
+trait Foo<T> {
+ fn function(bar: Vec<T>);
+}
+struct Bar;
+
+impl Foo<u32> for Bar {
+ fn function(bar: Vec<u32>) {
+ $0
+}
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn generics_are_inlined_complex() {
+ check_edit(
+ "fn function",
+ r#"
+trait Foo<T, U, V> {
+ fn function(bar: Vec<T>, baz: U) -> Arc<Vec<V>>;
+}
+struct Bar;
+
+impl Foo<u32, Vec<usize>, u8> for Bar {
+ fn f$0
+}
+"#,
+ r#"
+trait Foo<T, U, V> {
+ fn function(bar: Vec<T>, baz: U) -> Arc<Vec<V>>;
+}
+struct Bar;
+
+impl Foo<u32, Vec<usize>, u8> for Bar {
+ fn function(bar: Vec<u32>, baz: Vec<usize>) -> Arc<Vec<u8>> {
+ $0
+}
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn generics_are_inlined_in_associated_const() {
+ check_edit(
+ "const BAR",
+ r#"
+trait Foo<T> {
+ const BAR: T;
+}
+struct Bar;
+
+impl Foo<u32> for Bar {
+ const B$0
+}
+"#,
+ r#"
+trait Foo<T> {
+ const BAR: T;
+}
+struct Bar;
+
+impl Foo<u32> for Bar {
+ const BAR: u32 = $0;
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn generics_are_inlined_in_where_clause() {
+ check_edit(
+ "fn function",
+ r#"
+trait SomeTrait<T> {}
+
+trait Foo<T> {
+ fn function()
+ where Self: SomeTrait<T>;
+}
+struct Bar;
+
+impl Foo<u32> for Bar {
+ fn f$0
+}
+"#,
+ r#"
+trait SomeTrait<T> {}
+
+trait Foo<T> {
+ fn function()
+ where Self: SomeTrait<T>;
+}
+struct Bar;
+
+impl Foo<u32> for Bar {
+ fn function()
+ where Self: SomeTrait<u32> {
+ $0
+}
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn works_directly_in_impl() {
+ check(
+ r#"
+trait Tr {
+ fn required();
+}
+
+impl Tr for () {
+ $0
+}
+"#,
+ expect![[r#"
+ fn fn required()
+ "#]],
+ );
+ check(
+ r#"
+trait Tr {
+ fn provided() {}
+ fn required();
+}
+
+impl Tr for () {
+ fn provided() {}
+ $0
+}
+"#,
+ expect![[r#"
+ fn fn required()
+ "#]],
+ );
+ }
+
+ #[test]
+ fn fixes_up_macro_generated() {
+ check_edit(
+ "fn foo",
+ r#"
+macro_rules! noop {
+ ($($item: item)*) => {
+ $($item)*
+ }
+}
+
+noop! {
+ trait Foo {
+ fn foo(&mut self, bar: i64, baz: &mut u32) -> Result<(), u32>;
+ }
+}
+
+struct Test;
+
+impl Foo for Test {
+ $0
+}
+"#,
+ r#"
+macro_rules! noop {
+ ($($item: item)*) => {
+ $($item)*
+ }
+}
+
+noop! {
+ trait Foo {
+ fn foo(&mut self, bar: i64, baz: &mut u32) -> Result<(), u32>;
+ }
+}
+
+struct Test;
+
+impl Foo for Test {
+ fn foo(&mut self,bar:i64,baz: &mut u32) -> Result<(),u32> {
+ $0
+}
+}
+"#,
+ );
+ }
+}
--- /dev/null
+//! Implementation of find-usages functionality.
+//!
+//! It is based on the standard ide trick: first, we run a fast text search to
+//! get a super-set of matches. Then, we we confirm each match using precise
+//! name resolution.
+
+use std::{mem, sync::Arc};
+
+use base_db::{FileId, FileRange, SourceDatabase, SourceDatabaseExt};
+use hir::{DefWithBody, HasAttrs, HasSource, InFile, ModuleSource, Semantics, Visibility};
+use memchr::memmem::Finder;
+use once_cell::unsync::Lazy;
+use parser::SyntaxKind;
+use stdx::hash::NoHashHashMap;
+use syntax::{ast, match_ast, AstNode, TextRange, TextSize};
+
+use crate::{
+ defs::{Definition, NameClass, NameRefClass},
+ traits::{as_trait_assoc_def, convert_to_def_in_trait},
+ RootDatabase,
+};
+
+#[derive(Debug, Default, Clone)]
+pub struct UsageSearchResult {
+ pub references: NoHashHashMap<FileId, Vec<FileReference>>,
+}
+
+impl UsageSearchResult {
+ pub fn is_empty(&self) -> bool {
+ self.references.is_empty()
+ }
+
+ pub fn len(&self) -> usize {
+ self.references.len()
+ }
+
+ pub fn iter(&self) -> impl Iterator<Item = (&FileId, &[FileReference])> + '_ {
+ self.references.iter().map(|(file_id, refs)| (file_id, &**refs))
+ }
+
+ pub fn file_ranges(&self) -> impl Iterator<Item = FileRange> + '_ {
+ self.references.iter().flat_map(|(&file_id, refs)| {
+ refs.iter().map(move |&FileReference { range, .. }| FileRange { file_id, range })
+ })
+ }
+}
+
+impl IntoIterator for UsageSearchResult {
+ type Item = (FileId, Vec<FileReference>);
+ type IntoIter = <NoHashHashMap<FileId, Vec<FileReference>> as IntoIterator>::IntoIter;
+
+ fn into_iter(self) -> Self::IntoIter {
+ self.references.into_iter()
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct FileReference {
+ /// The range of the reference in the original file
+ pub range: TextRange,
+ /// The node of the reference in the (macro-)file
+ pub name: ast::NameLike,
+ pub category: Option<ReferenceCategory>,
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+pub enum ReferenceCategory {
+ // FIXME: Add this variant and delete the `retain_adt_literal_usages` function.
+ // Create
+ Write,
+ Read,
+ Import,
+ // FIXME: Some day should be able to search in doc comments. Would probably
+ // need to switch from enum to bitflags then?
+ // DocComment
+}
+
+/// Generally, `search_scope` returns files that might contain references for the element.
+/// For `pub(crate)` things it's a crate, for `pub` things it's a crate and dependant crates.
+/// In some cases, the location of the references is known to within a `TextRange`,
+/// e.g. for things like local variables.
+#[derive(Clone, Debug)]
+pub struct SearchScope {
+ entries: NoHashHashMap<FileId, Option<TextRange>>,
+}
+
+impl SearchScope {
+ fn new(entries: NoHashHashMap<FileId, Option<TextRange>>) -> SearchScope {
+ SearchScope { entries }
+ }
+
+ /// Build a search scope spanning the entire crate graph of files.
+ fn crate_graph(db: &RootDatabase) -> SearchScope {
+ let mut entries = NoHashHashMap::default();
+
+ let graph = db.crate_graph();
+ for krate in graph.iter() {
+ let root_file = graph[krate].root_file_id;
+ let source_root_id = db.file_source_root(root_file);
+ let source_root = db.source_root(source_root_id);
+ entries.extend(source_root.iter().map(|id| (id, None)));
+ }
+ SearchScope { entries }
+ }
+
+ /// Build a search scope spanning all the reverse dependencies of the given crate.
+ fn reverse_dependencies(db: &RootDatabase, of: hir::Crate) -> SearchScope {
+ let mut entries = NoHashHashMap::default();
+ for rev_dep in of.transitive_reverse_dependencies(db) {
+ let root_file = rev_dep.root_file(db);
+ let source_root_id = db.file_source_root(root_file);
+ let source_root = db.source_root(source_root_id);
+ entries.extend(source_root.iter().map(|id| (id, None)));
+ }
+ SearchScope { entries }
+ }
+
+ /// Build a search scope spanning the given crate.
+ fn krate(db: &RootDatabase, of: hir::Crate) -> SearchScope {
+ let root_file = of.root_file(db);
+ let source_root_id = db.file_source_root(root_file);
+ let source_root = db.source_root(source_root_id);
+ SearchScope { entries: source_root.iter().map(|id| (id, None)).collect() }
+ }
+
+ /// Build a search scope spanning the given module and all its submodules.
+ fn module_and_children(db: &RootDatabase, module: hir::Module) -> SearchScope {
+ let mut entries = NoHashHashMap::default();
+
+ let (file_id, range) = {
+ let InFile { file_id, value } = module.definition_source(db);
+ if let Some((file_id, call_source)) = file_id.original_call_node(db) {
+ (file_id, Some(call_source.text_range()))
+ } else {
+ (
+ file_id.original_file(db),
+ match value {
+ ModuleSource::SourceFile(_) => None,
+ ModuleSource::Module(it) => Some(it.syntax().text_range()),
+ ModuleSource::BlockExpr(it) => Some(it.syntax().text_range()),
+ },
+ )
+ }
+ };
+ entries.insert(file_id, range);
+
+ let mut to_visit: Vec<_> = module.children(db).collect();
+ while let Some(module) = to_visit.pop() {
+ if let InFile { file_id, value: ModuleSource::SourceFile(_) } =
+ module.definition_source(db)
+ {
+ entries.insert(file_id.original_file(db), None);
+ }
+ to_visit.extend(module.children(db));
+ }
+ SearchScope { entries }
+ }
+
+ /// Build an empty search scope.
+ pub fn empty() -> SearchScope {
+ SearchScope::new(NoHashHashMap::default())
+ }
+
+ /// Build a empty search scope spanning the given file.
+ pub fn single_file(file: FileId) -> SearchScope {
+ SearchScope::new(std::iter::once((file, None)).collect())
+ }
+
+ /// Build a empty search scope spanning the text range of the given file.
+ pub fn file_range(range: FileRange) -> SearchScope {
+ SearchScope::new(std::iter::once((range.file_id, Some(range.range))).collect())
+ }
+
+ /// Build a empty search scope spanning the given files.
+ pub fn files(files: &[FileId]) -> SearchScope {
+ SearchScope::new(files.iter().map(|f| (*f, None)).collect())
+ }
+
+ pub fn intersection(&self, other: &SearchScope) -> SearchScope {
+ let (mut small, mut large) = (&self.entries, &other.entries);
+ if small.len() > large.len() {
+ mem::swap(&mut small, &mut large)
+ }
+
+ let intersect_ranges =
+ |r1: Option<TextRange>, r2: Option<TextRange>| -> Option<Option<TextRange>> {
+ match (r1, r2) {
+ (None, r) | (r, None) => Some(r),
+ (Some(r1), Some(r2)) => r1.intersect(r2).map(Some),
+ }
+ };
+ let res = small
+ .iter()
+ .filter_map(|(&file_id, &r1)| {
+ let &r2 = large.get(&file_id)?;
+ let r = intersect_ranges(r1, r2)?;
+ Some((file_id, r))
+ })
+ .collect();
+
+ SearchScope::new(res)
+ }
+}
+
+impl IntoIterator for SearchScope {
+ type Item = (FileId, Option<TextRange>);
+ type IntoIter = std::collections::hash_map::IntoIter<FileId, Option<TextRange>>;
+
+ fn into_iter(self) -> Self::IntoIter {
+ self.entries.into_iter()
+ }
+}
+
+impl Definition {
+ fn search_scope(&self, db: &RootDatabase) -> SearchScope {
+ let _p = profile::span("search_scope");
+
+ if let Definition::BuiltinType(_) = self {
+ return SearchScope::crate_graph(db);
+ }
+
+ // def is crate root
+ // FIXME: We don't do searches for crates currently, as a crate does not actually have a single name
+ if let &Definition::Module(module) = self {
+ if module.is_crate_root(db) {
+ return SearchScope::reverse_dependencies(db, module.krate());
+ }
+ }
+
+ let module = match self.module(db) {
+ Some(it) => it,
+ None => return SearchScope::empty(),
+ };
+ let InFile { file_id, value: module_source } = module.definition_source(db);
+ let file_id = file_id.original_file(db);
+
+ if let Definition::Local(var) = self {
+ let def = match var.parent(db) {
+ DefWithBody::Function(f) => f.source(db).map(|src| src.syntax().cloned()),
+ DefWithBody::Const(c) => c.source(db).map(|src| src.syntax().cloned()),
+ DefWithBody::Static(s) => s.source(db).map(|src| src.syntax().cloned()),
++ DefWithBody::Variant(v) => v.source(db).map(|src| src.syntax().cloned()),
+ };
+ return match def {
+ Some(def) => SearchScope::file_range(def.as_ref().original_file_range(db)),
+ None => SearchScope::single_file(file_id),
+ };
+ }
+
+ if let Definition::SelfType(impl_) = self {
+ return match impl_.source(db).map(|src| src.syntax().cloned()) {
+ Some(def) => SearchScope::file_range(def.as_ref().original_file_range(db)),
+ None => SearchScope::single_file(file_id),
+ };
+ }
+
+ if let Definition::GenericParam(hir::GenericParam::LifetimeParam(param)) = self {
+ let def = match param.parent(db) {
+ hir::GenericDef::Function(it) => it.source(db).map(|src| src.syntax().cloned()),
+ hir::GenericDef::Adt(it) => it.source(db).map(|src| src.syntax().cloned()),
+ hir::GenericDef::Trait(it) => it.source(db).map(|src| src.syntax().cloned()),
+ hir::GenericDef::TypeAlias(it) => it.source(db).map(|src| src.syntax().cloned()),
+ hir::GenericDef::Impl(it) => it.source(db).map(|src| src.syntax().cloned()),
+ hir::GenericDef::Variant(it) => it.source(db).map(|src| src.syntax().cloned()),
+ hir::GenericDef::Const(it) => it.source(db).map(|src| src.syntax().cloned()),
+ };
+ return match def {
+ Some(def) => SearchScope::file_range(def.as_ref().original_file_range(db)),
+ None => SearchScope::single_file(file_id),
+ };
+ }
+
+ if let Definition::Macro(macro_def) = self {
+ return match macro_def.kind(db) {
+ hir::MacroKind::Declarative => {
+ if macro_def.attrs(db).by_key("macro_export").exists() {
+ SearchScope::reverse_dependencies(db, module.krate())
+ } else {
+ SearchScope::krate(db, module.krate())
+ }
+ }
+ hir::MacroKind::BuiltIn => SearchScope::crate_graph(db),
+ hir::MacroKind::Derive | hir::MacroKind::Attr | hir::MacroKind::ProcMacro => {
+ SearchScope::reverse_dependencies(db, module.krate())
+ }
+ };
+ }
+
+ if let Definition::DeriveHelper(_) = self {
+ return SearchScope::reverse_dependencies(db, module.krate());
+ }
+
+ let vis = self.visibility(db);
+ if let Some(Visibility::Public) = vis {
+ return SearchScope::reverse_dependencies(db, module.krate());
+ }
+ if let Some(Visibility::Module(module)) = vis {
+ return SearchScope::module_and_children(db, module.into());
+ }
+
+ let range = match module_source {
+ ModuleSource::Module(m) => Some(m.syntax().text_range()),
+ ModuleSource::BlockExpr(b) => Some(b.syntax().text_range()),
+ ModuleSource::SourceFile(_) => None,
+ };
+ match range {
+ Some(range) => SearchScope::file_range(FileRange { file_id, range }),
+ None => SearchScope::single_file(file_id),
+ }
+ }
+
+ pub fn usages<'a>(self, sema: &'a Semantics<'_, RootDatabase>) -> FindUsages<'a> {
+ FindUsages {
+ local_repr: match self {
+ Definition::Local(local) => Some(local.representative(sema.db)),
+ _ => None,
+ },
+ def: self,
+ trait_assoc_def: as_trait_assoc_def(sema.db, self),
+ sema,
+ scope: None,
+ include_self_kw_refs: None,
+ search_self_mod: false,
+ }
+ }
+}
+
+#[derive(Clone)]
+pub struct FindUsages<'a> {
+ def: Definition,
+ /// If def is an assoc item from a trait or trait impl, this is the corresponding item of the trait definition
+ trait_assoc_def: Option<Definition>,
+ sema: &'a Semantics<'a, RootDatabase>,
+ scope: Option<SearchScope>,
+ include_self_kw_refs: Option<hir::Type>,
+ local_repr: Option<hir::Local>,
+ search_self_mod: bool,
+}
+
+impl<'a> FindUsages<'a> {
+ /// Enable searching for `Self` when the definition is a type or `self` for modules.
+ pub fn include_self_refs(mut self) -> FindUsages<'a> {
+ self.include_self_kw_refs = def_to_ty(self.sema, &self.def);
+ self.search_self_mod = true;
+ self
+ }
+
+ /// Limit the search to a given [`SearchScope`].
+ pub fn in_scope(self, scope: SearchScope) -> FindUsages<'a> {
+ self.set_scope(Some(scope))
+ }
+
+ /// Limit the search to a given [`SearchScope`].
+ pub fn set_scope(mut self, scope: Option<SearchScope>) -> FindUsages<'a> {
+ assert!(self.scope.is_none());
+ self.scope = scope;
+ self
+ }
+
+ pub fn at_least_one(&self) -> bool {
+ let mut found = false;
+ self.search(&mut |_, _| {
+ found = true;
+ true
+ });
+ found
+ }
+
+ pub fn all(self) -> UsageSearchResult {
+ let mut res = UsageSearchResult::default();
+ self.search(&mut |file_id, reference| {
+ res.references.entry(file_id).or_default().push(reference);
+ false
+ });
+ res
+ }
+
+ fn search(&self, sink: &mut dyn FnMut(FileId, FileReference) -> bool) {
+ let _p = profile::span("FindUsages:search");
+ let sema = self.sema;
+
+ let search_scope = {
+ let base = self.trait_assoc_def.unwrap_or(self.def).search_scope(sema.db);
+ match &self.scope {
+ None => base,
+ Some(scope) => base.intersection(scope),
+ }
+ };
+
+ let name = match self.def {
+ // special case crate modules as these do not have a proper name
+ Definition::Module(module) if module.is_crate_root(self.sema.db) => {
+ // FIXME: This assumes the crate name is always equal to its display name when it really isn't
+ module
+ .krate()
+ .display_name(self.sema.db)
+ .map(|crate_name| crate_name.crate_name().as_smol_str().clone())
+ }
+ _ => {
+ let self_kw_refs = || {
+ self.include_self_kw_refs.as_ref().and_then(|ty| {
+ ty.as_adt()
+ .map(|adt| adt.name(self.sema.db))
+ .or_else(|| ty.as_builtin().map(|builtin| builtin.name()))
+ })
+ };
+ // We need to unescape the name in case it is written without "r#" in earlier
+ // editions of Rust where it isn't a keyword.
+ self.def.name(sema.db).or_else(self_kw_refs).map(|it| it.unescaped().to_smol_str())
+ }
+ };
+ let name = match &name {
+ Some(s) => s.as_str(),
+ None => return,
+ };
+ let finder = &Finder::new(name);
+ let include_self_kw_refs =
+ self.include_self_kw_refs.as_ref().map(|ty| (ty, Finder::new("Self")));
+
+ // for<'a> |text: &'a str, name: &'a str, search_range: TextRange| -> impl Iterator<Item = TextSize> + 'a { ... }
+ fn match_indices<'a>(
+ text: &'a str,
+ finder: &'a Finder<'a>,
+ search_range: TextRange,
+ ) -> impl Iterator<Item = TextSize> + 'a {
+ finder.find_iter(text.as_bytes()).filter_map(move |idx| {
+ let offset: TextSize = idx.try_into().unwrap();
+ if !search_range.contains_inclusive(offset) {
+ return None;
+ }
+ Some(offset)
+ })
+ }
+
+ // for<'a> |scope: &'a SearchScope| -> impl Iterator<Item = (Arc<String>, FileId, TextRange)> + 'a { ... }
+ fn scope_files<'a>(
+ sema: &'a Semantics<'_, RootDatabase>,
+ scope: &'a SearchScope,
+ ) -> impl Iterator<Item = (Arc<String>, FileId, TextRange)> + 'a {
+ scope.entries.iter().map(|(&file_id, &search_range)| {
+ let text = sema.db.file_text(file_id);
+ let search_range =
+ search_range.unwrap_or_else(|| TextRange::up_to(TextSize::of(text.as_str())));
+
+ (text, file_id, search_range)
+ })
+ }
+
+ // FIXME: There should be optimization potential here
+ // Currently we try to descend everything we find which
+ // means we call `Semantics::descend_into_macros` on
+ // every textual hit. That function is notoriously
+ // expensive even for things that do not get down mapped
+ // into macros.
+ for (text, file_id, search_range) in scope_files(sema, &search_scope) {
+ let tree = Lazy::new(move || sema.parse(file_id).syntax().clone());
+
+ // Search for occurrences of the items name
+ for offset in match_indices(&text, finder, search_range) {
+ for name in sema.find_nodes_at_offset_with_descend(&tree, offset) {
+ if match name {
+ ast::NameLike::NameRef(name_ref) => self.found_name_ref(&name_ref, sink),
+ ast::NameLike::Name(name) => self.found_name(&name, sink),
+ ast::NameLike::Lifetime(lifetime) => self.found_lifetime(&lifetime, sink),
+ } {
+ return;
+ }
+ }
+ }
+ // Search for occurrences of the `Self` referring to our type
+ if let Some((self_ty, finder)) = &include_self_kw_refs {
+ for offset in match_indices(&text, finder, search_range) {
+ for name_ref in sema.find_nodes_at_offset_with_descend(&tree, offset) {
+ if self.found_self_ty_name_ref(self_ty, &name_ref, sink) {
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ // Search for `super` and `crate` resolving to our module
+ match self.def {
+ Definition::Module(module) => {
+ let scope = search_scope
+ .intersection(&SearchScope::module_and_children(self.sema.db, module));
+
+ let is_crate_root =
+ module.is_crate_root(self.sema.db).then(|| Finder::new("crate"));
+ let finder = &Finder::new("super");
+
+ for (text, file_id, search_range) in scope_files(sema, &scope) {
+ let tree = Lazy::new(move || sema.parse(file_id).syntax().clone());
+
+ for offset in match_indices(&text, finder, search_range) {
+ for name_ref in sema.find_nodes_at_offset_with_descend(&tree, offset) {
+ if self.found_name_ref(&name_ref, sink) {
+ return;
+ }
+ }
+ }
+ if let Some(finder) = &is_crate_root {
+ for offset in match_indices(&text, finder, search_range) {
+ for name_ref in sema.find_nodes_at_offset_with_descend(&tree, offset) {
+ if self.found_name_ref(&name_ref, sink) {
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
+ _ => (),
+ }
+
+ // search for module `self` references in our module's definition source
+ match self.def {
+ Definition::Module(module) if self.search_self_mod => {
+ let src = module.definition_source(sema.db);
+ let file_id = src.file_id.original_file(sema.db);
+ let (file_id, search_range) = match src.value {
+ ModuleSource::Module(m) => (file_id, Some(m.syntax().text_range())),
+ ModuleSource::BlockExpr(b) => (file_id, Some(b.syntax().text_range())),
+ ModuleSource::SourceFile(_) => (file_id, None),
+ };
+
+ let search_range = if let Some(&range) = search_scope.entries.get(&file_id) {
+ match (range, search_range) {
+ (None, range) | (range, None) => range,
+ (Some(range), Some(search_range)) => match range.intersect(search_range) {
+ Some(range) => Some(range),
+ None => return,
+ },
+ }
+ } else {
+ return;
+ };
+
+ let text = sema.db.file_text(file_id);
+ let search_range =
+ search_range.unwrap_or_else(|| TextRange::up_to(TextSize::of(text.as_str())));
+
+ let tree = Lazy::new(|| sema.parse(file_id).syntax().clone());
+ let finder = &Finder::new("self");
+
+ for offset in match_indices(&text, finder, search_range) {
+ for name_ref in sema.find_nodes_at_offset_with_descend(&tree, offset) {
+ if self.found_self_module_name_ref(&name_ref, sink) {
+ return;
+ }
+ }
+ }
+ }
+ _ => {}
+ }
+ }
+
+ fn found_self_ty_name_ref(
+ &self,
+ self_ty: &hir::Type,
+ name_ref: &ast::NameRef,
+ sink: &mut dyn FnMut(FileId, FileReference) -> bool,
+ ) -> bool {
+ match NameRefClass::classify(self.sema, name_ref) {
+ Some(NameRefClass::Definition(Definition::SelfType(impl_)))
+ if impl_.self_ty(self.sema.db) == *self_ty =>
+ {
+ let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
+ let reference = FileReference {
+ range,
+ name: ast::NameLike::NameRef(name_ref.clone()),
+ category: None,
+ };
+ sink(file_id, reference)
+ }
+ _ => false,
+ }
+ }
+
+ fn found_self_module_name_ref(
+ &self,
+ name_ref: &ast::NameRef,
+ sink: &mut dyn FnMut(FileId, FileReference) -> bool,
+ ) -> bool {
+ match NameRefClass::classify(self.sema, name_ref) {
+ Some(NameRefClass::Definition(def @ Definition::Module(_))) if def == self.def => {
+ let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
+ let reference = FileReference {
+ range,
+ name: ast::NameLike::NameRef(name_ref.clone()),
+ category: is_name_ref_in_import(name_ref).then(|| ReferenceCategory::Import),
+ };
+ sink(file_id, reference)
+ }
+ _ => false,
+ }
+ }
+
+ fn found_lifetime(
+ &self,
+ lifetime: &ast::Lifetime,
+ sink: &mut dyn FnMut(FileId, FileReference) -> bool,
+ ) -> bool {
+ match NameRefClass::classify_lifetime(self.sema, lifetime) {
+ Some(NameRefClass::Definition(def)) if def == self.def => {
+ let FileRange { file_id, range } = self.sema.original_range(lifetime.syntax());
+ let reference = FileReference {
+ range,
+ name: ast::NameLike::Lifetime(lifetime.clone()),
+ category: None,
+ };
+ sink(file_id, reference)
+ }
+ _ => false,
+ }
+ }
+
+ fn found_name_ref(
+ &self,
+ name_ref: &ast::NameRef,
+ sink: &mut dyn FnMut(FileId, FileReference) -> bool,
+ ) -> bool {
+ match NameRefClass::classify(self.sema, name_ref) {
+ Some(NameRefClass::Definition(def @ Definition::Local(local)))
+ if matches!(
+ self.local_repr, Some(repr) if repr == local.representative(self.sema.db)
+ ) =>
+ {
+ let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
+ let reference = FileReference {
+ range,
+ name: ast::NameLike::NameRef(name_ref.clone()),
+ category: ReferenceCategory::new(&def, name_ref),
+ };
+ sink(file_id, reference)
+ }
+ Some(NameRefClass::Definition(def))
+ if match self.trait_assoc_def {
+ Some(trait_assoc_def) => {
+ // we have a trait assoc item, so force resolve all assoc items to their trait version
+ convert_to_def_in_trait(self.sema.db, def) == trait_assoc_def
+ }
+ None => self.def == def,
+ } =>
+ {
+ let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
+ let reference = FileReference {
+ range,
+ name: ast::NameLike::NameRef(name_ref.clone()),
+ category: ReferenceCategory::new(&def, name_ref),
+ };
+ sink(file_id, reference)
+ }
+ Some(NameRefClass::Definition(def)) if self.include_self_kw_refs.is_some() => {
+ if self.include_self_kw_refs == def_to_ty(self.sema, &def) {
+ let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
+ let reference = FileReference {
+ range,
+ name: ast::NameLike::NameRef(name_ref.clone()),
+ category: ReferenceCategory::new(&def, name_ref),
+ };
+ sink(file_id, reference)
+ } else {
+ false
+ }
+ }
+ Some(NameRefClass::FieldShorthand { local_ref: local, field_ref: field }) => {
+ let field = Definition::Field(field);
+ let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
+ let access = match self.def {
+ Definition::Field(_) if field == self.def => {
+ ReferenceCategory::new(&field, name_ref)
+ }
+ Definition::Local(_) if matches!(self.local_repr, Some(repr) if repr == local.representative(self.sema.db)) => {
+ ReferenceCategory::new(&Definition::Local(local), name_ref)
+ }
+ _ => return false,
+ };
+ let reference = FileReference {
+ range,
+ name: ast::NameLike::NameRef(name_ref.clone()),
+ category: access,
+ };
+ sink(file_id, reference)
+ }
+ _ => false,
+ }
+ }
+
+ fn found_name(
+ &self,
+ name: &ast::Name,
+ sink: &mut dyn FnMut(FileId, FileReference) -> bool,
+ ) -> bool {
+ match NameClass::classify(self.sema, name) {
+ Some(NameClass::PatFieldShorthand { local_def: _, field_ref })
+ if matches!(
+ self.def, Definition::Field(_) if Definition::Field(field_ref) == self.def
+ ) =>
+ {
+ let FileRange { file_id, range } = self.sema.original_range(name.syntax());
+ let reference = FileReference {
+ range,
+ name: ast::NameLike::Name(name.clone()),
+ // FIXME: mutable patterns should have `Write` access
+ category: Some(ReferenceCategory::Read),
+ };
+ sink(file_id, reference)
+ }
+ Some(NameClass::ConstReference(def)) if self.def == def => {
+ let FileRange { file_id, range } = self.sema.original_range(name.syntax());
+ let reference = FileReference {
+ range,
+ name: ast::NameLike::Name(name.clone()),
+ category: None,
+ };
+ sink(file_id, reference)
+ }
+ Some(NameClass::Definition(def @ Definition::Local(local))) if def != self.def => {
+ if matches!(
+ self.local_repr,
+ Some(repr) if local.representative(self.sema.db) == repr
+ ) {
+ let FileRange { file_id, range } = self.sema.original_range(name.syntax());
+ let reference = FileReference {
+ range,
+ name: ast::NameLike::Name(name.clone()),
+ category: None,
+ };
+ return sink(file_id, reference);
+ }
+ false
+ }
+ Some(NameClass::Definition(def)) if def != self.def => {
+ // if the def we are looking for is a trait (impl) assoc item, we'll have to resolve the items to trait definition assoc item
+ if !matches!(
+ self.trait_assoc_def,
+ Some(trait_assoc_def)
+ if convert_to_def_in_trait(self.sema.db, def) == trait_assoc_def
+ ) {
+ return false;
+ }
+ let FileRange { file_id, range } = self.sema.original_range(name.syntax());
+ let reference = FileReference {
+ range,
+ name: ast::NameLike::Name(name.clone()),
+ category: None,
+ };
+ sink(file_id, reference)
+ }
+ _ => false,
+ }
+ }
+}
+
+fn def_to_ty(sema: &Semantics<'_, RootDatabase>, def: &Definition) -> Option<hir::Type> {
+ match def {
+ Definition::Adt(adt) => Some(adt.ty(sema.db)),
+ Definition::TypeAlias(it) => Some(it.ty(sema.db)),
+ Definition::BuiltinType(it) => Some(it.ty(sema.db)),
+ Definition::SelfType(it) => Some(it.self_ty(sema.db)),
+ _ => None,
+ }
+}
+
+impl ReferenceCategory {
+ fn new(def: &Definition, r: &ast::NameRef) -> Option<ReferenceCategory> {
+ // Only Locals and Fields have accesses for now.
+ if !matches!(def, Definition::Local(_) | Definition::Field(_)) {
+ return is_name_ref_in_import(r).then(|| ReferenceCategory::Import);
+ }
+
+ let mode = r.syntax().ancestors().find_map(|node| {
+ match_ast! {
+ match node {
+ ast::BinExpr(expr) => {
+ if matches!(expr.op_kind()?, ast::BinaryOp::Assignment { .. }) {
+ // If the variable or field ends on the LHS's end then it's a Write (covers fields and locals).
+ // FIXME: This is not terribly accurate.
+ if let Some(lhs) = expr.lhs() {
+ if lhs.syntax().text_range().end() == r.syntax().text_range().end() {
+ return Some(ReferenceCategory::Write);
+ }
+ }
+ }
+ Some(ReferenceCategory::Read)
+ },
+ _ => None
+ }
+ }
+ });
+
+ // Default Locals and Fields to read
+ mode.or(Some(ReferenceCategory::Read))
+ }
+}
+
+fn is_name_ref_in_import(name_ref: &ast::NameRef) -> bool {
+ name_ref
+ .syntax()
+ .parent()
+ .and_then(ast::PathSegment::cast)
+ .and_then(|it| it.parent_path().top_path().syntax().parent())
+ .map_or(false, |it| it.kind() == SyntaxKind::USE_TREE)
+}
--- /dev/null
+use cfg::DnfExpr;
+use stdx::format_to;
+
+use crate::{Diagnostic, DiagnosticsContext, Severity};
+
+// Diagnostic: inactive-code
+//
+// This diagnostic is shown for code with inactive `#[cfg]` attributes.
+pub(crate) fn inactive_code(
+ ctx: &DiagnosticsContext<'_>,
+ d: &hir::InactiveCode,
+) -> Option<Diagnostic> {
+ // If there's inactive code somewhere in a macro, don't propagate to the call-site.
+ if d.node.file_id.is_macro() {
+ return None;
+ }
+
+ let inactive = DnfExpr::new(d.cfg.clone()).why_inactive(&d.opts);
+ let mut message = "code is inactive due to #[cfg] directives".to_string();
+
+ if let Some(inactive) = inactive {
+ let inactive_reasons = inactive.to_string();
+
+ if inactive_reasons.is_empty() {
+ format_to!(message);
+ } else {
+ format_to!(message, ": {}", inactive);
+ }
+ }
+
+ let res = Diagnostic::new(
+ "inactive-code",
+ message,
+ ctx.sema.diagnostics_display_range(d.node.clone()).range,
+ )
+ .severity(Severity::WeakWarning)
+ .with_unused(true);
+ Some(res)
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::{tests::check_diagnostics_with_config, DiagnosticsConfig};
+
+ pub(crate) fn check(ra_fixture: &str) {
+ let config = DiagnosticsConfig::test_sample();
+ check_diagnostics_with_config(config, ra_fixture)
+ }
+
+ #[test]
+ fn cfg_diagnostics() {
+ check(
+ r#"
+fn f() {
+ // The three g̶e̶n̶d̶e̶r̶s̶ statements:
+
+ #[cfg(a)] fn f() {} // Item statement
+ //^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled
+ #[cfg(a)] {} // Expression statement
+ //^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled
+ #[cfg(a)] let x = 0; // let statement
+ //^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled
+
+ abc(#[cfg(a)] 0);
+ //^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled
+ let x = Struct {
+ #[cfg(a)] f: 0,
+ //^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled
+ };
+ match () {
+ () => (),
+ #[cfg(a)] () => (),
+ //^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled
+ }
+
+ #[cfg(a)] 0 // Trailing expression of block
+ //^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn inactive_item() {
+ // Additional tests in `cfg` crate. This only tests disabled cfgs.
+
+ check(
+ r#"
+ #[cfg(no)] pub fn f() {}
+ //^^^^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: no is disabled
+
+ #[cfg(no)] #[cfg(no2)] mod m;
+ //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: no and no2 are disabled
+
+ #[cfg(all(not(a), b))] enum E {}
+ //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: b is disabled
+
+ #[cfg(feature = "std")] use std;
+ //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: feature = "std" is disabled
+
+ #[cfg(any())] pub fn f() {}
+ //^^^^^^^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives
+"#,
+ );
+ }
+
+ #[test]
+ fn inactive_assoc_item() {
+ check(
+ r#"
+struct Foo;
+impl Foo {
+ #[cfg(any())] pub fn f() {}
+ //^^^^^^^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives
+}
+
+trait Bar {
+ #[cfg(any())] pub fn f() {}
+ //^^^^^^^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives
+}
+"#,
+ );
+ }
+
+ /// Tests that `cfg` attributes behind `cfg_attr` is handled properly.
+ #[test]
+ fn inactive_via_cfg_attr() {
+ cov_mark::check!(cfg_attr_active);
+ check(
+ r#"
+ #[cfg_attr(not(never), cfg(no))] fn f() {}
+ //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: no is disabled
+
+ #[cfg_attr(not(never), cfg(not(no)))] fn f() {}
+
+ #[cfg_attr(never, cfg(no))] fn g() {}
+
+ #[cfg_attr(not(never), inline, cfg(no))] fn h() {}
+ //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: no is disabled
++"#,
++ );
++ }
++
++ #[test]
++ fn inactive_fields_and_variants() {
++ check(
++ r#"
++enum Foo {
++ #[cfg(a)] Bar,
++//^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled
++ Baz {
++ #[cfg(a)] baz: String,
++ //^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled
++ },
++ Qux(#[cfg(a)] String),
++ //^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled
++}
++
++struct Baz {
++ #[cfg(a)] baz: String,
++//^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled
++}
++
++struct Qux(#[cfg(a)] String);
++ //^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled
++
++union FooBar {
++ #[cfg(a)] baz: u32,
++//^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled
++}
+"#,
+ );
+ }
+}
--- /dev/null
- fn_references::find_all_methods,
+use hir::{HasSource, InFile, Semantics};
+use ide_db::{
+ base_db::{FileId, FilePosition, FileRange},
+ defs::Definition,
+ helpers::visit_file_defs,
+ RootDatabase,
+};
+use syntax::{ast::HasName, AstNode, TextRange};
+
+use crate::{
- HasImpls { file_id: FileId, data: Option<Vec<NavigationTarget>> },
- HasReferences { file_id: FileId, data: Option<Vec<FileRange>> },
++ annotations::fn_references::find_all_methods,
+ goto_implementation::goto_implementation,
+ references::find_all_refs,
+ runnables::{runnables, Runnable},
+ NavigationTarget, RunnableKind,
+};
+
++mod fn_references;
++
+// Feature: Annotations
+//
+// Provides user with annotations above items for looking up references or impl blocks
+// and running/debugging binaries.
+//
+// image::https://user-images.githubusercontent.com/48062697/113020672-b7c34f00-917a-11eb-8f6e-858735660a0e.png[]
+#[derive(Debug)]
+pub struct Annotation {
+ pub range: TextRange,
+ pub kind: AnnotationKind,
+}
+
+#[derive(Debug)]
+pub enum AnnotationKind {
+ Runnable(Runnable),
- konst.source(db).and_then(|node| name_range(db, config, node, file_id))
++ HasImpls { pos: FilePosition, data: Option<Vec<NavigationTarget>> },
++ HasReferences { pos: FilePosition, data: Option<Vec<FileRange>> },
+}
+
+pub struct AnnotationConfig {
+ pub binary_target: bool,
+ pub annotate_runnables: bool,
+ pub annotate_impls: bool,
+ pub annotate_references: bool,
+ pub annotate_method_references: bool,
+ pub annotate_enum_variant_references: bool,
+ pub location: AnnotationLocation,
+}
+
+pub enum AnnotationLocation {
+ AboveName,
+ AboveWholeItem,
+}
+
+pub(crate) fn annotations(
+ db: &RootDatabase,
+ config: &AnnotationConfig,
+ file_id: FileId,
+) -> Vec<Annotation> {
+ let mut annotations = Vec::default();
+
+ if config.annotate_runnables {
+ for runnable in runnables(db, file_id) {
+ if should_skip_runnable(&runnable.kind, config.binary_target) {
+ continue;
+ }
+
+ let range = runnable.nav.focus_or_full_range();
+
+ annotations.push(Annotation { range, kind: AnnotationKind::Runnable(runnable) });
+ }
+ }
+
++ let mk_ranges = |(range, focus): (_, Option<_>)| {
++ let cmd_target: TextRange = focus.unwrap_or(range);
++ let annotation_range = match config.location {
++ AnnotationLocation::AboveName => cmd_target,
++ AnnotationLocation::AboveWholeItem => range,
++ };
++ let target_pos = FilePosition { file_id, offset: cmd_target.start() };
++ (annotation_range, target_pos)
++ };
++
+ visit_file_defs(&Semantics::new(db), file_id, &mut |def| {
+ let range = match def {
+ Definition::Const(konst) if config.annotate_references => {
- trait_.source(db).and_then(|node| name_range(db, config, node, file_id))
++ konst.source(db).and_then(|node| name_range(db, node, file_id))
+ }
+ Definition::Trait(trait_) if config.annotate_references || config.annotate_impls => {
- variant
- .source(db)
- .and_then(|node| name_range(db, config, node, file_id))
++ trait_.source(db).and_then(|node| name_range(db, node, file_id))
+ }
+ Definition::Adt(adt) => match adt {
+ hir::Adt::Enum(enum_) => {
+ if config.annotate_enum_variant_references {
+ enum_
+ .variants(db)
+ .into_iter()
+ .map(|variant| {
- range,
- kind: AnnotationKind::HasReferences { file_id, data: None },
++ variant.source(db).and_then(|node| name_range(db, node, file_id))
+ })
+ .flatten()
+ .for_each(|range| {
++ let (annotation_range, target_position) = mk_ranges(range);
+ annotations.push(Annotation {
- enum_.source(db).and_then(|node| name_range(db, config, node, file_id))
++ range: annotation_range,
++ kind: AnnotationKind::HasReferences {
++ pos: target_position,
++ data: None,
++ },
+ })
+ })
+ }
+ if config.annotate_references || config.annotate_impls {
- adt.source(db).and_then(|node| name_range(db, config, node, file_id))
++ enum_.source(db).and_then(|node| name_range(db, node, file_id))
+ } else {
+ None
+ }
+ }
+ _ => {
+ if config.annotate_references || config.annotate_impls {
-
++ adt.source(db).and_then(|node| name_range(db, node, file_id))
+ } else {
+ None
+ }
+ }
+ },
+ _ => None,
+ };
+
+ let range = match range {
+ Some(range) => range,
+ None => return,
+ };
- annotations
- .push(Annotation { range, kind: AnnotationKind::HasImpls { file_id, data: None } });
++ let (annotation_range, target_pos) = mk_ranges(range);
+ if config.annotate_impls && !matches!(def, Definition::Const(_)) {
- range,
- kind: AnnotationKind::HasReferences { file_id, data: None },
++ annotations.push(Annotation {
++ range: annotation_range,
++ kind: AnnotationKind::HasImpls { pos: target_pos, data: None },
++ });
+ }
+
+ if config.annotate_references {
+ annotations.push(Annotation {
- config: &AnnotationConfig,
++ range: annotation_range,
++ kind: AnnotationKind::HasReferences { pos: target_pos, data: None },
+ });
+ }
+
+ fn name_range<T: HasName>(
+ db: &RootDatabase,
- ) -> Option<TextRange> {
+ node: InFile<T>,
+ source_file_id: FileId,
- return match config.location {
- AnnotationLocation::AboveName => {
- value.name().map(|name| name.syntax().text_range())
- }
- AnnotationLocation::AboveWholeItem => Some(value.syntax().text_range()),
- };
++ ) -> Option<(TextRange, Option<TextRange>)> {
+ if let Some(InFile { file_id, value }) = node.original_ast_node(db) {
+ if file_id == source_file_id.into() {
- annotations.extend(find_all_methods(db, file_id).into_iter().map(
- |FileRange { file_id, range }| Annotation {
- range,
- kind: AnnotationKind::HasReferences { file_id, data: None },
- },
- ));
++ return Some((
++ value.syntax().text_range(),
++ value.name().map(|name| name.syntax().text_range()),
++ ));
+ }
+ }
+ None
+ }
+ });
+
+ if config.annotate_method_references {
- AnnotationKind::HasImpls { file_id, ref mut data } => {
- *data =
- goto_implementation(db, FilePosition { file_id, offset: annotation.range.start() })
- .map(|range| range.info);
++ annotations.extend(find_all_methods(db, file_id).into_iter().map(|range| {
++ let (annotation_range, target_range) = mk_ranges(range);
++ Annotation {
++ range: annotation_range,
++ kind: AnnotationKind::HasReferences { pos: target_range, data: None },
++ }
++ }));
+ }
+
+ annotations
+}
+
+pub(crate) fn resolve_annotation(db: &RootDatabase, mut annotation: Annotation) -> Annotation {
+ match annotation.kind {
- AnnotationKind::HasReferences { file_id, ref mut data } => {
- *data = find_all_refs(
- &Semantics::new(db),
- FilePosition { file_id, offset: annotation.range.start() },
- None,
- )
- .map(|result| {
++ AnnotationKind::HasImpls { pos, ref mut data } => {
++ *data = goto_implementation(db, pos).map(|range| range.info);
+ }
- file_id: FileId(
- 0,
- ),
++ AnnotationKind::HasReferences { pos, ref mut data } => {
++ *data = find_all_refs(&Semantics::new(db), pos, None).map(|result| {
+ result
+ .into_iter()
+ .flat_map(|res| res.references)
+ .flat_map(|(file_id, access)| {
+ access.into_iter().map(move |(range, _)| FileRange { file_id, range })
+ })
+ .collect()
+ });
+ }
+ _ => {}
+ };
+
+ annotation
+}
+
+fn should_skip_runnable(kind: &RunnableKind, binary_target: bool) -> bool {
+ match kind {
+ RunnableKind::Bin => !binary_target,
+ _ => false,
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use expect_test::{expect, Expect};
+
+ use crate::{fixture, Annotation, AnnotationConfig};
+
+ use super::AnnotationLocation;
+
+ const DEFAULT_CONFIG: AnnotationConfig = AnnotationConfig {
+ binary_target: true,
+ annotate_runnables: true,
+ annotate_impls: true,
+ annotate_references: true,
+ annotate_method_references: true,
+ annotate_enum_variant_references: true,
+ location: AnnotationLocation::AboveName,
+ };
+
+ fn check_with_config(ra_fixture: &str, expect: Expect, config: &AnnotationConfig) {
+ let (analysis, file_id) = fixture::file(ra_fixture);
+
+ let annotations: Vec<Annotation> = analysis
+ .annotations(config, file_id)
+ .unwrap()
+ .into_iter()
+ .map(|annotation| analysis.resolve_annotation(annotation).unwrap())
+ .collect();
+
+ expect.assert_debug_eq(&annotations);
+ }
+
+ fn check(ra_fixture: &str, expect: Expect) {
+ check_with_config(ra_fixture, expect, &DEFAULT_CONFIG);
+ }
+
+ #[test]
+ fn const_annotations() {
+ check(
+ r#"
+const DEMO: i32 = 123;
+
+const UNUSED: i32 = 123;
+
+fn main() {
+ let hello = DEMO;
+}
+ "#,
+ expect![[r#"
+ [
+ Annotation {
+ range: 53..57,
+ kind: Runnable(
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 50..85,
+ focus_range: 53..57,
+ name: "main",
+ kind: Function,
+ },
+ kind: Bin,
+ cfg: None,
+ },
+ ),
+ },
+ Annotation {
+ range: 6..10,
+ kind: HasReferences {
- file_id: FileId(
- 0,
- ),
++ pos: FilePosition {
++ file_id: FileId(
++ 0,
++ ),
++ offset: 6,
++ },
+ data: Some(
+ [
+ FileRange {
+ file_id: FileId(
+ 0,
+ ),
+ range: 78..82,
+ },
+ ],
+ ),
+ },
+ },
+ Annotation {
+ range: 30..36,
+ kind: HasReferences {
- file_id: FileId(
- 0,
- ),
++ pos: FilePosition {
++ file_id: FileId(
++ 0,
++ ),
++ offset: 30,
++ },
+ data: Some(
+ [],
+ ),
+ },
+ },
+ Annotation {
+ range: 53..57,
+ kind: HasReferences {
- file_id: FileId(
- 0,
- ),
++ pos: FilePosition {
++ file_id: FileId(
++ 0,
++ ),
++ offset: 53,
++ },
+ data: Some(
+ [],
+ ),
+ },
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn struct_references_annotations() {
+ check(
+ r#"
+struct Test;
+
+fn main() {
+ let test = Test;
+}
+ "#,
+ expect![[r#"
+ [
+ Annotation {
+ range: 17..21,
+ kind: Runnable(
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 14..48,
+ focus_range: 17..21,
+ name: "main",
+ kind: Function,
+ },
+ kind: Bin,
+ cfg: None,
+ },
+ ),
+ },
+ Annotation {
+ range: 7..11,
+ kind: HasImpls {
- file_id: FileId(
- 0,
- ),
++ pos: FilePosition {
++ file_id: FileId(
++ 0,
++ ),
++ offset: 7,
++ },
+ data: Some(
+ [],
+ ),
+ },
+ },
+ Annotation {
+ range: 7..11,
+ kind: HasReferences {
- file_id: FileId(
- 0,
- ),
++ pos: FilePosition {
++ file_id: FileId(
++ 0,
++ ),
++ offset: 7,
++ },
+ data: Some(
+ [
+ FileRange {
+ file_id: FileId(
+ 0,
+ ),
+ range: 41..45,
+ },
+ ],
+ ),
+ },
+ },
+ Annotation {
+ range: 17..21,
+ kind: HasReferences {
- file_id: FileId(
- 0,
- ),
++ pos: FilePosition {
++ file_id: FileId(
++ 0,
++ ),
++ offset: 17,
++ },
+ data: Some(
+ [],
+ ),
+ },
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn struct_and_trait_impls_annotations() {
+ check(
+ r#"
+struct Test;
+
+trait MyCoolTrait {}
+
+impl MyCoolTrait for Test {}
+
+fn main() {
+ let test = Test;
+}
+ "#,
+ expect![[r#"
+ [
+ Annotation {
+ range: 69..73,
+ kind: Runnable(
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 66..100,
+ focus_range: 69..73,
+ name: "main",
+ kind: Function,
+ },
+ kind: Bin,
+ cfg: None,
+ },
+ ),
+ },
+ Annotation {
+ range: 7..11,
+ kind: HasImpls {
- file_id: FileId(
- 0,
- ),
++ pos: FilePosition {
++ file_id: FileId(
++ 0,
++ ),
++ offset: 7,
++ },
+ data: Some(
+ [
+ NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 36..64,
+ focus_range: 57..61,
+ name: "impl",
+ kind: Impl,
+ },
+ ],
+ ),
+ },
+ },
+ Annotation {
+ range: 7..11,
+ kind: HasReferences {
- file_id: FileId(
- 0,
- ),
++ pos: FilePosition {
++ file_id: FileId(
++ 0,
++ ),
++ offset: 7,
++ },
+ data: Some(
+ [
+ FileRange {
+ file_id: FileId(
+ 0,
+ ),
+ range: 57..61,
+ },
+ FileRange {
+ file_id: FileId(
+ 0,
+ ),
+ range: 93..97,
+ },
+ ],
+ ),
+ },
+ },
+ Annotation {
+ range: 20..31,
+ kind: HasImpls {
- file_id: FileId(
- 0,
- ),
++ pos: FilePosition {
++ file_id: FileId(
++ 0,
++ ),
++ offset: 20,
++ },
+ data: Some(
+ [
+ NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 36..64,
+ focus_range: 57..61,
+ name: "impl",
+ kind: Impl,
+ },
+ ],
+ ),
+ },
+ },
+ Annotation {
+ range: 20..31,
+ kind: HasReferences {
- file_id: FileId(
- 0,
- ),
++ pos: FilePosition {
++ file_id: FileId(
++ 0,
++ ),
++ offset: 20,
++ },
+ data: Some(
+ [
+ FileRange {
+ file_id: FileId(
+ 0,
+ ),
+ range: 41..52,
+ },
+ ],
+ ),
+ },
+ },
+ Annotation {
+ range: 69..73,
+ kind: HasReferences {
- file_id: FileId(
- 0,
- ),
++ pos: FilePosition {
++ file_id: FileId(
++ 0,
++ ),
++ offset: 69,
++ },
+ data: Some(
+ [],
+ ),
+ },
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn runnable_annotation() {
+ check(
+ r#"
+fn main() {}
+ "#,
+ expect![[r#"
+ [
+ Annotation {
+ range: 3..7,
+ kind: Runnable(
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..12,
+ focus_range: 3..7,
+ name: "main",
+ kind: Function,
+ },
+ kind: Bin,
+ cfg: None,
+ },
+ ),
+ },
+ Annotation {
+ range: 3..7,
+ kind: HasReferences {
- file_id: FileId(
- 0,
- ),
++ pos: FilePosition {
++ file_id: FileId(
++ 0,
++ ),
++ offset: 3,
++ },
+ data: Some(
+ [],
+ ),
+ },
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn method_annotations() {
+ check(
+ r#"
+struct Test;
+
+impl Test {
+ fn self_by_ref(&self) {}
+}
+
+fn main() {
+ Test.self_by_ref();
+}
+ "#,
+ expect![[r#"
+ [
+ Annotation {
+ range: 61..65,
+ kind: Runnable(
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 58..95,
+ focus_range: 61..65,
+ name: "main",
+ kind: Function,
+ },
+ kind: Bin,
+ cfg: None,
+ },
+ ),
+ },
+ Annotation {
+ range: 7..11,
+ kind: HasImpls {
- file_id: FileId(
- 0,
- ),
++ pos: FilePosition {
++ file_id: FileId(
++ 0,
++ ),
++ offset: 7,
++ },
+ data: Some(
+ [
+ NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 14..56,
+ focus_range: 19..23,
+ name: "impl",
+ kind: Impl,
+ },
+ ],
+ ),
+ },
+ },
+ Annotation {
+ range: 7..11,
+ kind: HasReferences {
- file_id: FileId(
- 0,
- ),
++ pos: FilePosition {
++ file_id: FileId(
++ 0,
++ ),
++ offset: 7,
++ },
+ data: Some(
+ [
+ FileRange {
+ file_id: FileId(
+ 0,
+ ),
+ range: 19..23,
+ },
+ FileRange {
+ file_id: FileId(
+ 0,
+ ),
+ range: 74..78,
+ },
+ ],
+ ),
+ },
+ },
+ Annotation {
+ range: 33..44,
+ kind: HasReferences {
- file_id: FileId(
- 0,
- ),
++ pos: FilePosition {
++ file_id: FileId(
++ 0,
++ ),
++ offset: 33,
++ },
+ data: Some(
+ [
+ FileRange {
+ file_id: FileId(
+ 0,
+ ),
+ range: 79..90,
+ },
+ ],
+ ),
+ },
+ },
+ Annotation {
+ range: 61..65,
+ kind: HasReferences {
- file_id: FileId(
- 0,
- ),
++ pos: FilePosition {
++ file_id: FileId(
++ 0,
++ ),
++ offset: 61,
++ },
+ data: Some(
+ [],
+ ),
+ },
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_annotations() {
+ check(
+ r#"
+fn main() {}
+
+mod tests {
+ #[test]
+ fn my_cool_test() {}
+}
+ "#,
+ expect![[r#"
+ [
+ Annotation {
+ range: 3..7,
+ kind: Runnable(
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..12,
+ focus_range: 3..7,
+ name: "main",
+ kind: Function,
+ },
+ kind: Bin,
+ cfg: None,
+ },
+ ),
+ },
+ Annotation {
+ range: 18..23,
+ kind: Runnable(
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 14..64,
+ focus_range: 18..23,
+ name: "tests",
+ kind: Module,
+ description: "mod tests",
+ },
+ kind: TestMod {
+ path: "tests",
+ },
+ cfg: None,
+ },
+ ),
+ },
+ Annotation {
+ range: 45..57,
+ kind: Runnable(
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 30..62,
+ focus_range: 45..57,
+ name: "my_cool_test",
+ kind: Function,
+ },
+ kind: Test {
+ test_id: Path(
+ "tests::my_cool_test",
+ ),
+ attr: TestAttr {
+ ignore: false,
+ },
+ },
+ cfg: None,
+ },
+ ),
+ },
+ Annotation {
+ range: 3..7,
+ kind: HasReferences {
- file_id: FileId(
- 0,
- ),
++ pos: FilePosition {
++ file_id: FileId(
++ 0,
++ ),
++ offset: 3,
++ },
+ data: Some(
+ [],
+ ),
+ },
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_no_annotations_outside_module_tree() {
+ check(
+ r#"
+//- /foo.rs
+struct Foo;
+//- /lib.rs
+// this file comes last since `check` checks the first file only
+"#,
+ expect![[r#"
+ []
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_no_annotations_macro_struct_def() {
+ check(
+ r#"
+//- /lib.rs
+macro_rules! m {
+ () => {
+ struct A {}
+ };
+}
+
+m!();
+"#,
+ expect![[r#"
+ []
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_annotations_appear_above_whole_item_when_configured_to_do_so() {
+ check_with_config(
+ r#"
+/// This is a struct named Foo, obviously.
+#[derive(Clone)]
+struct Foo;
+"#,
+ expect![[r#"
+ [
+ Annotation {
+ range: 0..71,
+ kind: HasImpls {
- file_id: FileId(
- 0,
++ pos: FilePosition {
++ file_id: FileId(
++ 0,
++ ),
++ offset: 67,
++ },
+ data: Some(
+ [],
+ ),
+ },
+ },
+ Annotation {
+ range: 0..71,
+ kind: HasReferences {
- data: None,
++ pos: FilePosition {
++ file_id: FileId(
++ 0,
++ ),
++ offset: 67,
++ },
++ data: Some(
++ [],
+ ),
+ },
+ },
+ ]
+ "#]],
+ &AnnotationConfig { location: AnnotationLocation::AboveWholeItem, ..DEFAULT_CONFIG },
+ );
+ }
+}
--- /dev/null
--- /dev/null
++//! This module implements a methods and free functions search in the specified file.
++//! We have to skip tests, so cannot reuse file_structure module.
++
++use hir::Semantics;
++use ide_assists::utils::test_related_attribute;
++use ide_db::RootDatabase;
++use syntax::{ast, ast::HasName, AstNode, SyntaxNode, TextRange};
++
++use crate::FileId;
++
++pub(super) fn find_all_methods(
++ db: &RootDatabase,
++ file_id: FileId,
++) -> Vec<(TextRange, Option<TextRange>)> {
++ let sema = Semantics::new(db);
++ let source_file = sema.parse(file_id);
++ source_file.syntax().descendants().filter_map(|it| method_range(it)).collect()
++}
++
++fn method_range(item: SyntaxNode) -> Option<(TextRange, Option<TextRange>)> {
++ ast::Fn::cast(item).and_then(|fn_def| {
++ if test_related_attribute(&fn_def).is_some() {
++ None
++ } else {
++ Some((
++ fn_def.syntax().text_range(),
++ fn_def.name().map(|name| name.syntax().text_range()),
++ ))
++ }
++ })
++}
++
++#[cfg(test)]
++mod tests {
++ use syntax::TextRange;
++
++ use crate::fixture;
++ use crate::TextSize;
++ use std::ops::RangeInclusive;
++
++ #[test]
++ fn test_find_all_methods() {
++ let (analysis, pos) = fixture::position(
++ r#"
++ fn private_fn() {$0}
++
++ pub fn pub_fn() {}
++
++ pub fn generic_fn<T>(arg: T) {}
++ "#,
++ );
++
++ let refs = super::find_all_methods(&analysis.db, pos.file_id);
++ check_result(&refs, &[3..=13, 27..=33, 47..=57]);
++ }
++
++ #[test]
++ fn test_find_trait_methods() {
++ let (analysis, pos) = fixture::position(
++ r#"
++ trait Foo {
++ fn bar() {$0}
++ fn baz() {}
++ }
++ "#,
++ );
++
++ let refs = super::find_all_methods(&analysis.db, pos.file_id);
++ check_result(&refs, &[19..=22, 35..=38]);
++ }
++
++ #[test]
++ fn test_skip_tests() {
++ let (analysis, pos) = fixture::position(
++ r#"
++ //- /lib.rs
++ #[test]
++ fn foo() {$0}
++
++ pub fn pub_fn() {}
++
++ mod tests {
++ #[test]
++ fn bar() {}
++ }
++ "#,
++ );
++
++ let refs = super::find_all_methods(&analysis.db, pos.file_id);
++ check_result(&refs, &[28..=34]);
++ }
++
++ fn check_result(refs: &[(TextRange, Option<TextRange>)], expected: &[RangeInclusive<u32>]) {
++ assert_eq!(refs.len(), expected.len());
++
++ for (i, &(full, focus)) in refs.iter().enumerate() {
++ let range = &expected[i];
++ let item = focus.unwrap_or(full);
++ assert_eq!(TextSize::from(*range.start()), item.start());
++ assert_eq!(TextSize::from(*range.end()), item.end());
++ }
++ }
++}
--- /dev/null
- ast::String(string) => doc_token.parent_ancestors().find_map(ast::Attr::cast)
- .filter(|attr| attr.simple_name().as_deref() == Some("doc")).and_then(|_| string.open_quote_text_range().map(|it| it.len())),
+//! Extracts, resolves and rewrites links and intra-doc links in markdown documentation.
+
+#[cfg(test)]
+mod tests;
+
+mod intra_doc_links;
+
+use pulldown_cmark::{BrokenLink, CowStr, Event, InlineStr, LinkType, Options, Parser, Tag};
+use pulldown_cmark_to_cmark::{cmark_resume_with_options, Options as CMarkOptions};
+use stdx::format_to;
+use url::Url;
+
+use hir::{db::HirDatabase, Adt, AsAssocItem, AssocItem, AssocItemContainer, HasAttrs};
+use ide_db::{
+ base_db::{CrateOrigin, LangCrateOrigin, SourceDatabase},
+ defs::{Definition, NameClass, NameRefClass},
+ helpers::pick_best_token,
+ RootDatabase,
+};
+use syntax::{
+ ast::{self, IsString},
+ match_ast, AstNode, AstToken,
+ SyntaxKind::*,
+ SyntaxNode, SyntaxToken, TextRange, TextSize, T,
+};
+
+use crate::{
+ doc_links::intra_doc_links::{parse_intra_doc_link, strip_prefixes_suffixes},
+ FilePosition, Semantics,
+};
+
+/// Weblink to an item's documentation.
+pub(crate) type DocumentationLink = String;
+
+const MARKDOWN_OPTIONS: Options =
+ Options::ENABLE_FOOTNOTES.union(Options::ENABLE_TABLES).union(Options::ENABLE_TASKLISTS);
+
+/// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs)
+pub(crate) fn rewrite_links(db: &RootDatabase, markdown: &str, definition: Definition) -> String {
+ let mut cb = broken_link_clone_cb;
+ let doc = Parser::new_with_broken_link_callback(markdown, MARKDOWN_OPTIONS, Some(&mut cb));
+
+ let doc = map_links(doc, |target, title| {
+ // This check is imperfect, there's some overlap between valid intra-doc links
+ // and valid URLs so we choose to be too eager to try to resolve what might be
+ // a URL.
+ if target.contains("://") {
+ (Some(LinkType::Inline), target.to_string(), title.to_string())
+ } else {
+ // Two possibilities:
+ // * path-based links: `../../module/struct.MyStruct.html`
+ // * module-based links (AKA intra-doc links): `super::super::module::MyStruct`
+ if let Some((target, title)) = rewrite_intra_doc_link(db, definition, target, title) {
+ return (None, target, title);
+ }
+ if let Some(target) = rewrite_url_link(db, definition, target) {
+ return (Some(LinkType::Inline), target, title.to_string());
+ }
+
+ (None, target.to_string(), title.to_string())
+ }
+ });
+ let mut out = String::new();
+ cmark_resume_with_options(
+ doc,
+ &mut out,
+ None,
+ CMarkOptions { code_block_token_count: 3, ..Default::default() },
+ )
+ .ok();
+ out
+}
+
+/// Remove all links in markdown documentation.
+pub(crate) fn remove_links(markdown: &str) -> String {
+ let mut drop_link = false;
+
+ let mut cb = |_: BrokenLink<'_>| {
+ let empty = InlineStr::try_from("").unwrap();
+ Some((CowStr::Inlined(empty), CowStr::Inlined(empty)))
+ };
+ let doc = Parser::new_with_broken_link_callback(markdown, MARKDOWN_OPTIONS, Some(&mut cb));
+ let doc = doc.filter_map(move |evt| match evt {
+ Event::Start(Tag::Link(link_type, target, title)) => {
+ if link_type == LinkType::Inline && target.contains("://") {
+ Some(Event::Start(Tag::Link(link_type, target, title)))
+ } else {
+ drop_link = true;
+ None
+ }
+ }
+ Event::End(_) if drop_link => {
+ drop_link = false;
+ None
+ }
+ _ => Some(evt),
+ });
+
+ let mut out = String::new();
+ cmark_resume_with_options(
+ doc,
+ &mut out,
+ None,
+ CMarkOptions { code_block_token_count: 3, ..Default::default() },
+ )
+ .ok();
+ out
+}
+
+/// Retrieve a link to documentation for the given symbol.
+pub(crate) fn external_docs(
+ db: &RootDatabase,
+ position: &FilePosition,
+) -> Option<DocumentationLink> {
+ let sema = &Semantics::new(db);
+ let file = sema.parse(position.file_id).syntax().clone();
+ let token = pick_best_token(file.token_at_offset(position.offset), |kind| match kind {
+ IDENT | INT_NUMBER | T![self] => 3,
+ T!['('] | T![')'] => 2,
+ kind if kind.is_trivia() => 0,
+ _ => 1,
+ })?;
+ let token = sema.descend_into_macros_single(token);
+
+ let node = token.parent()?;
+ let definition = match_ast! {
+ match node {
+ ast::NameRef(name_ref) => match NameRefClass::classify(sema, &name_ref)? {
+ NameRefClass::Definition(def) => def,
+ NameRefClass::FieldShorthand { local_ref: _, field_ref } => {
+ Definition::Field(field_ref)
+ }
+ },
+ ast::Name(name) => match NameClass::classify(sema, &name)? {
+ NameClass::Definition(it) | NameClass::ConstReference(it) => it,
+ NameClass::PatFieldShorthand { local_def: _, field_ref } => Definition::Field(field_ref),
+ },
+ _ => return None,
+ }
+ };
+
+ get_doc_link(db, definition)
+}
+
+/// Extracts all links from a given markdown text returning the definition text range, link-text
+/// and the namespace if known.
+pub(crate) fn extract_definitions_from_docs(
+ docs: &hir::Documentation,
+) -> Vec<(TextRange, String, Option<hir::Namespace>)> {
+ Parser::new_with_broken_link_callback(
+ docs.as_str(),
+ MARKDOWN_OPTIONS,
+ Some(&mut broken_link_clone_cb),
+ )
+ .into_offset_iter()
+ .filter_map(|(event, range)| match event {
+ Event::Start(Tag::Link(_, target, _)) => {
+ let (link, ns) = parse_intra_doc_link(&target);
+ Some((
+ TextRange::new(range.start.try_into().ok()?, range.end.try_into().ok()?),
+ link.to_string(),
+ ns,
+ ))
+ }
+ _ => None,
+ })
+ .collect()
+}
+
+pub(crate) fn resolve_doc_path_for_def(
+ db: &dyn HirDatabase,
+ def: Definition,
+ link: &str,
+ ns: Option<hir::Namespace>,
+) -> Option<Definition> {
+ match def {
+ Definition::Module(it) => it.resolve_doc_path(db, link, ns),
+ Definition::Function(it) => it.resolve_doc_path(db, link, ns),
+ Definition::Adt(it) => it.resolve_doc_path(db, link, ns),
+ Definition::Variant(it) => it.resolve_doc_path(db, link, ns),
+ Definition::Const(it) => it.resolve_doc_path(db, link, ns),
+ Definition::Static(it) => it.resolve_doc_path(db, link, ns),
+ Definition::Trait(it) => it.resolve_doc_path(db, link, ns),
+ Definition::TypeAlias(it) => it.resolve_doc_path(db, link, ns),
+ Definition::Macro(it) => it.resolve_doc_path(db, link, ns),
+ Definition::Field(it) => it.resolve_doc_path(db, link, ns),
+ Definition::SelfType(it) => it.resolve_doc_path(db, link, ns),
+ Definition::BuiltinAttr(_)
+ | Definition::ToolModule(_)
+ | Definition::BuiltinType(_)
+ | Definition::Local(_)
+ | Definition::GenericParam(_)
+ | Definition::Label(_)
+ | Definition::DeriveHelper(_) => None,
+ }
+ .map(Definition::from)
+}
+
+pub(crate) fn doc_attributes(
+ sema: &Semantics<'_, RootDatabase>,
+ node: &SyntaxNode,
+) -> Option<(hir::AttrsWithOwner, Definition)> {
+ match_ast! {
+ match node {
+ ast::SourceFile(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Module(def))),
+ ast::Module(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Module(def))),
+ ast::Fn(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Function(def))),
+ ast::Struct(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Adt(hir::Adt::Struct(def)))),
+ ast::Union(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Adt(hir::Adt::Union(def)))),
+ ast::Enum(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Adt(hir::Adt::Enum(def)))),
+ ast::Variant(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Variant(def))),
+ ast::Trait(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Trait(def))),
+ ast::Static(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Static(def))),
+ ast::Const(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Const(def))),
+ ast::TypeAlias(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::TypeAlias(def))),
+ ast::Impl(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::SelfType(def))),
+ ast::RecordField(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Field(def))),
+ ast::TupleField(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Field(def))),
+ ast::Macro(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Macro(def))),
+ // ast::Use(it) => sema.to_def(&it).map(|def| (Box::new(it) as _, def.attrs(sema.db))),
+ _ => None
+ }
+ }
+}
+
+pub(crate) struct DocCommentToken {
+ doc_token: SyntaxToken,
+ prefix_len: TextSize,
+}
+
+pub(crate) fn token_as_doc_comment(doc_token: &SyntaxToken) -> Option<DocCommentToken> {
+ (match_ast! {
+ match doc_token {
+ ast::Comment(comment) => TextSize::try_from(comment.prefix().len()).ok(),
++ ast::String(string) => {
++ doc_token.parent_ancestors().find_map(ast::Attr::cast).filter(|attr| attr.simple_name().as_deref() == Some("doc"))?;
++ if doc_token.parent_ancestors().find_map(ast::MacroCall::cast).filter(|mac| mac.path().and_then(|p| p.segment()?.name_ref()).as_ref().map(|n| n.text()).as_deref() == Some("include_str")).is_some() {
++ return None;
++ }
++ string.open_quote_text_range().map(|it| it.len())
++ },
+ _ => None,
+ }
+ }).map(|prefix_len| DocCommentToken { prefix_len, doc_token: doc_token.clone() })
+}
+
+impl DocCommentToken {
+ pub(crate) fn get_definition_with_descend_at<T>(
+ self,
+ sema: &Semantics<'_, RootDatabase>,
+ offset: TextSize,
+ // Definition, CommentOwner, range of intra doc link in original file
+ mut cb: impl FnMut(Definition, SyntaxNode, TextRange) -> Option<T>,
+ ) -> Option<T> {
+ let DocCommentToken { prefix_len, doc_token } = self;
+ // offset relative to the comments contents
+ let original_start = doc_token.text_range().start();
+ let relative_comment_offset = offset - original_start - prefix_len;
+
+ sema.descend_into_macros(doc_token).into_iter().find_map(|t| {
+ let (node, descended_prefix_len) = match_ast! {
+ match t {
+ ast::Comment(comment) => (t.parent()?, TextSize::try_from(comment.prefix().len()).ok()?),
+ ast::String(string) => (t.parent_ancestors().skip_while(|n| n.kind() != ATTR).nth(1)?, string.open_quote_text_range()?.len()),
+ _ => return None,
+ }
+ };
+ let token_start = t.text_range().start();
+ let abs_in_expansion_offset = token_start + relative_comment_offset + descended_prefix_len;
+
+ let (attributes, def) = doc_attributes(sema, &node)?;
+ let (docs, doc_mapping) = attributes.docs_with_rangemap(sema.db)?;
+ let (in_expansion_range, link, ns) =
+ extract_definitions_from_docs(&docs).into_iter().find_map(|(range, link, ns)| {
+ let mapped = doc_mapping.map(range)?;
+ (mapped.value.contains(abs_in_expansion_offset)).then(|| (mapped.value, link, ns))
+ })?;
+ // get the relative range to the doc/attribute in the expansion
+ let in_expansion_relative_range = in_expansion_range - descended_prefix_len - token_start;
+ // Apply relative range to the original input comment
+ let absolute_range = in_expansion_relative_range + original_start + prefix_len;
+ let def = resolve_doc_path_for_def(sema.db, def, &link, ns)?;
+ cb(def, node, absolute_range)
+ })
+ }
+}
+
+fn broken_link_clone_cb<'a>(link: BrokenLink<'a>) -> Option<(CowStr<'a>, CowStr<'a>)> {
+ Some((/*url*/ link.reference.clone(), /*title*/ link.reference))
+}
+
+// FIXME:
+// BUG: For Option::Some
+// Returns https://doc.rust-lang.org/nightly/core/prelude/v1/enum.Option.html#variant.Some
+// Instead of https://doc.rust-lang.org/nightly/core/option/enum.Option.html
+//
+// This should cease to be a problem if RFC2988 (Stable Rustdoc URLs) is implemented
+// https://github.com/rust-lang/rfcs/pull/2988
+fn get_doc_link(db: &RootDatabase, def: Definition) -> Option<String> {
+ let (target, file, frag) = filename_and_frag_for_def(db, def)?;
+
+ let mut url = get_doc_base_url(db, target)?;
+
+ if let Some(path) = mod_path_of_def(db, target) {
+ url = url.join(&path).ok()?;
+ }
+
+ url = url.join(&file).ok()?;
+ url.set_fragment(frag.as_deref());
+
+ Some(url.into())
+}
+
+fn rewrite_intra_doc_link(
+ db: &RootDatabase,
+ def: Definition,
+ target: &str,
+ title: &str,
+) -> Option<(String, String)> {
+ let (link, ns) = parse_intra_doc_link(target);
+
+ let resolved = resolve_doc_path_for_def(db, def, link, ns)?;
+ let mut url = get_doc_base_url(db, resolved)?;
+
+ let (_, file, frag) = filename_and_frag_for_def(db, resolved)?;
+ if let Some(path) = mod_path_of_def(db, resolved) {
+ url = url.join(&path).ok()?;
+ }
+
+ url = url.join(&file).ok()?;
+ url.set_fragment(frag.as_deref());
+
+ Some((url.into(), strip_prefixes_suffixes(title).to_string()))
+}
+
+/// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`).
+fn rewrite_url_link(db: &RootDatabase, def: Definition, target: &str) -> Option<String> {
+ if !(target.contains('#') || target.contains(".html")) {
+ return None;
+ }
+
+ let mut url = get_doc_base_url(db, def)?;
+ let (def, file, frag) = filename_and_frag_for_def(db, def)?;
+
+ if let Some(path) = mod_path_of_def(db, def) {
+ url = url.join(&path).ok()?;
+ }
+
+ url = url.join(&file).ok()?;
+ url.set_fragment(frag.as_deref());
+ url.join(target).ok().map(Into::into)
+}
+
+fn mod_path_of_def(db: &RootDatabase, def: Definition) -> Option<String> {
+ def.canonical_module_path(db).map(|it| {
+ let mut path = String::new();
+ it.flat_map(|it| it.name(db)).for_each(|name| format_to!(path, "{}/", name));
+ path
+ })
+}
+
+/// Rewrites a markdown document, applying 'callback' to each link.
+fn map_links<'e>(
+ events: impl Iterator<Item = Event<'e>>,
+ callback: impl Fn(&str, &str) -> (Option<LinkType>, String, String),
+) -> impl Iterator<Item = Event<'e>> {
+ let mut in_link = false;
+ // holds the origin link target on start event and the rewritten one on end event
+ let mut end_link_target: Option<CowStr<'_>> = None;
+ // normally link's type is determined by the type of link tag in the end event,
+ // however in some cases we want to change the link type, for example,
+ // `Shortcut` type parsed from Start/End tags doesn't make sense for url links
+ let mut end_link_type: Option<LinkType> = None;
+
+ events.map(move |evt| match evt {
+ Event::Start(Tag::Link(link_type, ref target, _)) => {
+ in_link = true;
+ end_link_target = Some(target.clone());
+ end_link_type = Some(link_type);
+ evt
+ }
+ Event::End(Tag::Link(link_type, target, _)) => {
+ in_link = false;
+ Event::End(Tag::Link(
+ end_link_type.unwrap_or(link_type),
+ end_link_target.take().unwrap_or(target),
+ CowStr::Borrowed(""),
+ ))
+ }
+ Event::Text(s) if in_link => {
+ let (link_type, link_target_s, link_name) =
+ callback(&end_link_target.take().unwrap(), &s);
+ end_link_target = Some(CowStr::Boxed(link_target_s.into()));
+ if !matches!(end_link_type, Some(LinkType::Autolink)) {
+ end_link_type = link_type;
+ }
+ Event::Text(CowStr::Boxed(link_name.into()))
+ }
+ Event::Code(s) if in_link => {
+ let (link_type, link_target_s, link_name) =
+ callback(&end_link_target.take().unwrap(), &s);
+ end_link_target = Some(CowStr::Boxed(link_target_s.into()));
+ if !matches!(end_link_type, Some(LinkType::Autolink)) {
+ end_link_type = link_type;
+ }
+ Event::Code(CowStr::Boxed(link_name.into()))
+ }
+ _ => evt,
+ })
+}
+
+/// Get the root URL for the documentation of a definition.
+///
+/// ```ignore
+/// https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next
+/// ^^^^^^^^^^^^^^^^^^^^^^^^^^
+/// ```
+fn get_doc_base_url(db: &RootDatabase, def: Definition) -> Option<Url> {
+ // special case base url of `BuiltinType` to core
+ // https://github.com/rust-lang/rust-analyzer/issues/12250
+ if let Definition::BuiltinType(..) = def {
+ return Url::parse("https://doc.rust-lang.org/nightly/core/").ok();
+ };
+
+ let krate = def.krate(db)?;
+ let display_name = krate.display_name(db)?;
+
+ let base = match db.crate_graph()[krate.into()].origin {
+ // std and co do not specify `html_root_url` any longer so we gotta handwrite this ourself.
+ // FIXME: Use the toolchains channel instead of nightly
+ CrateOrigin::Lang(
+ origin @ (LangCrateOrigin::Alloc
+ | LangCrateOrigin::Core
+ | LangCrateOrigin::ProcMacro
+ | LangCrateOrigin::Std
+ | LangCrateOrigin::Test),
+ ) => {
+ format!("https://doc.rust-lang.org/nightly/{origin}")
+ }
+ _ => {
+ krate.get_html_root_url(db).or_else(|| {
+ let version = krate.version(db);
+ // Fallback to docs.rs. This uses `display_name` and can never be
+ // correct, but that's what fallbacks are about.
+ //
+ // FIXME: clicking on the link should just open the file in the editor,
+ // instead of falling back to external urls.
+ Some(format!(
+ "https://docs.rs/{krate}/{version}/",
+ krate = display_name,
+ version = version.as_deref().unwrap_or("*")
+ ))
+ })?
+ }
+ };
+ Url::parse(&base).ok()?.join(&format!("{}/", display_name)).ok()
+}
+
+/// Get the filename and extension generated for a symbol by rustdoc.
+///
+/// ```ignore
+/// https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next
+/// ^^^^^^^^^^^^^^^^^^^
+/// ```
+fn filename_and_frag_for_def(
+ db: &dyn HirDatabase,
+ def: Definition,
+) -> Option<(Definition, String, Option<String>)> {
+ if let Some(assoc_item) = def.as_assoc_item(db) {
+ let def = match assoc_item.container(db) {
+ AssocItemContainer::Trait(t) => t.into(),
+ AssocItemContainer::Impl(i) => i.self_ty(db).as_adt()?.into(),
+ };
+ let (_, file, _) = filename_and_frag_for_def(db, def)?;
+ let frag = get_assoc_item_fragment(db, assoc_item)?;
+ return Some((def, file, Some(frag)));
+ }
+
+ let res = match def {
+ Definition::Adt(adt) => match adt {
+ Adt::Struct(s) => format!("struct.{}.html", s.name(db)),
+ Adt::Enum(e) => format!("enum.{}.html", e.name(db)),
+ Adt::Union(u) => format!("union.{}.html", u.name(db)),
+ },
+ Definition::Module(m) => match m.name(db) {
+ // `#[doc(keyword = "...")]` is internal used only by rust compiler
+ Some(name) => match m.attrs(db).by_key("doc").find_string_value_in_tt("keyword") {
+ Some(kw) => {
+ format!("keyword.{}.html", kw.trim_matches('"'))
+ }
+ None => format!("{}/index.html", name),
+ },
+ None => String::from("index.html"),
+ },
+ Definition::Trait(t) => format!("trait.{}.html", t.name(db)),
+ Definition::TypeAlias(t) => format!("type.{}.html", t.name(db)),
+ Definition::BuiltinType(t) => format!("primitive.{}.html", t.name()),
+ Definition::Function(f) => format!("fn.{}.html", f.name(db)),
+ Definition::Variant(ev) => {
+ format!("enum.{}.html#variant.{}", ev.parent_enum(db).name(db), ev.name(db))
+ }
+ Definition::Const(c) => format!("const.{}.html", c.name(db)?),
+ Definition::Static(s) => format!("static.{}.html", s.name(db)),
+ Definition::Macro(mac) => format!("macro.{}.html", mac.name(db)),
+ Definition::Field(field) => {
+ let def = match field.parent_def(db) {
+ hir::VariantDef::Struct(it) => Definition::Adt(it.into()),
+ hir::VariantDef::Union(it) => Definition::Adt(it.into()),
+ hir::VariantDef::Variant(it) => Definition::Variant(it),
+ };
+ let (_, file, _) = filename_and_frag_for_def(db, def)?;
+ return Some((def, file, Some(format!("structfield.{}", field.name(db)))));
+ }
+ Definition::SelfType(impl_) => {
+ let adt = impl_.self_ty(db).as_adt()?.into();
+ let (_, file, _) = filename_and_frag_for_def(db, adt)?;
+ // FIXME fragment numbering
+ return Some((adt, file, Some(String::from("impl"))));
+ }
+ Definition::Local(_)
+ | Definition::GenericParam(_)
+ | Definition::Label(_)
+ | Definition::BuiltinAttr(_)
+ | Definition::ToolModule(_)
+ | Definition::DeriveHelper(_) => return None,
+ };
+
+ Some((def, res, None))
+}
+
+/// Get the fragment required to link to a specific field, method, associated type, or associated constant.
+///
+/// ```ignore
+/// https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next
+/// ^^^^^^^^^^^^^^
+/// ```
+fn get_assoc_item_fragment(db: &dyn HirDatabase, assoc_item: hir::AssocItem) -> Option<String> {
+ Some(match assoc_item {
+ AssocItem::Function(function) => {
+ let is_trait_method =
+ function.as_assoc_item(db).and_then(|assoc| assoc.containing_trait(db)).is_some();
+ // This distinction may get more complicated when specialization is available.
+ // Rustdoc makes this decision based on whether a method 'has defaultness'.
+ // Currently this is only the case for provided trait methods.
+ if is_trait_method && !function.has_body(db) {
+ format!("tymethod.{}", function.name(db))
+ } else {
+ format!("method.{}", function.name(db))
+ }
+ }
+ AssocItem::Const(constant) => format!("associatedconstant.{}", constant.name(db)?),
+ AssocItem::TypeAlias(ty) => format!("associatedtype.{}", ty.name(db)),
+ })
+}
--- /dev/null
- if navs.is_empty() {
- panic!("unresolved reference")
- }
+use std::mem::discriminant;
+
+use crate::{doc_links::token_as_doc_comment, FilePosition, NavigationTarget, RangeInfo, TryToNav};
+use hir::{AsAssocItem, AssocItem, Semantics};
+use ide_db::{
+ base_db::{AnchoredPath, FileId, FileLoader},
+ defs::{Definition, IdentClass},
+ helpers::pick_best_token,
+ RootDatabase,
+};
+use itertools::Itertools;
+use syntax::{ast, AstNode, AstToken, SyntaxKind::*, SyntaxToken, TextRange, T};
+
+// Feature: Go to Definition
+//
+// Navigates to the definition of an identifier.
+//
+// For outline modules, this will navigate to the source file of the module.
+//
+// |===
+// | Editor | Shortcut
+//
+// | VS Code | kbd:[F12]
+// |===
+//
+// image::https://user-images.githubusercontent.com/48062697/113065563-025fbe00-91b1-11eb-83e4-a5a703610b23.gif[]
+pub(crate) fn goto_definition(
+ db: &RootDatabase,
+ position: FilePosition,
+) -> Option<RangeInfo<Vec<NavigationTarget>>> {
+ let sema = &Semantics::new(db);
+ let file = sema.parse(position.file_id).syntax().clone();
+ let original_token =
+ pick_best_token(file.token_at_offset(position.offset), |kind| match kind {
+ IDENT
+ | INT_NUMBER
+ | LIFETIME_IDENT
+ | T![self]
+ | T![super]
+ | T![crate]
+ | T![Self]
+ | COMMENT => 4,
+ // index and prefix ops
+ T!['['] | T![']'] | T![?] | T![*] | T![-] | T![!] => 3,
+ kind if kind.is_keyword() => 2,
+ T!['('] | T![')'] => 2,
+ kind if kind.is_trivia() => 0,
+ _ => 1,
+ })?;
+ if let Some(doc_comment) = token_as_doc_comment(&original_token) {
+ return doc_comment.get_definition_with_descend_at(sema, position.offset, |def, _, _| {
+ let nav = def.try_to_nav(db)?;
+ Some(RangeInfo::new(original_token.text_range(), vec![nav]))
+ });
+ }
+ let navs = sema
+ .descend_into_macros(original_token.clone())
+ .into_iter()
+ .filter_map(|token| {
+ let parent = token.parent()?;
+ if let Some(tt) = ast::TokenTree::cast(parent) {
+ if let Some(x) = try_lookup_include_path(sema, tt, token.clone(), position.file_id)
+ {
+ return Some(vec![x]);
+ }
+ }
+ Some(
+ IdentClass::classify_token(sema, &token)?
+ .definitions()
+ .into_iter()
+ .flat_map(|def| {
+ try_filter_trait_item_definition(sema, &def)
+ .unwrap_or_else(|| def_to_nav(sema.db, def))
+ })
+ .collect(),
+ )
+ })
+ .flatten()
+ .unique()
+ .collect::<Vec<NavigationTarget>>();
+
+ Some(RangeInfo::new(original_token.text_range(), navs))
+}
+
+fn try_lookup_include_path(
+ sema: &Semantics<'_, RootDatabase>,
+ tt: ast::TokenTree,
+ token: SyntaxToken,
+ file_id: FileId,
+) -> Option<NavigationTarget> {
+ let token = ast::String::cast(token)?;
+ let path = token.value()?.into_owned();
+ let macro_call = tt.syntax().parent().and_then(ast::MacroCall::cast)?;
+ let name = macro_call.path()?.segment()?.name_ref()?;
+ if !matches!(&*name.text(), "include" | "include_str" | "include_bytes") {
+ return None;
+ }
++
++ // Ignore non-built-in macros to account for shadowing
++ if let Some(it) = sema.resolve_macro_call(¯o_call) {
++ if !matches!(it.kind(sema.db), hir::MacroKind::BuiltIn) {
++ return None;
++ }
++ }
++
+ let file_id = sema.db.resolve_path(AnchoredPath { anchor: file_id, path: &path })?;
+ let size = sema.db.file_text(file_id).len().try_into().ok()?;
+ Some(NavigationTarget {
+ file_id,
+ full_range: TextRange::new(0.into(), size),
+ name: path.into(),
+ focus_range: None,
+ kind: None,
+ container_name: None,
+ description: None,
+ docs: None,
+ })
+}
+/// finds the trait definition of an impl'd item, except function
+/// e.g.
+/// ```rust
+/// trait A { type a; }
+/// struct S;
+/// impl A for S { type a = i32; } // <-- on this associate type, will get the location of a in the trait
+/// ```
+fn try_filter_trait_item_definition(
+ sema: &Semantics<'_, RootDatabase>,
+ def: &Definition,
+) -> Option<Vec<NavigationTarget>> {
+ let db = sema.db;
+ let assoc = def.as_assoc_item(db)?;
+ match assoc {
+ AssocItem::Function(..) => None,
+ AssocItem::Const(..) | AssocItem::TypeAlias(..) => {
+ let imp = match assoc.container(db) {
+ hir::AssocItemContainer::Impl(imp) => imp,
+ _ => return None,
+ };
+ let trait_ = imp.trait_(db)?;
+ let name = def.name(db)?;
+ let discri_value = discriminant(&assoc);
+ trait_
+ .items(db)
+ .iter()
+ .filter(|itm| discriminant(*itm) == discri_value)
+ .find_map(|itm| (itm.name(db)? == name).then(|| itm.try_to_nav(db)).flatten())
+ .map(|it| vec![it])
+ }
+ }
+}
+
+fn def_to_nav(db: &RootDatabase, def: Definition) -> Vec<NavigationTarget> {
+ def.try_to_nav(db).map(|it| vec![it]).unwrap_or_default()
+}
+
+#[cfg(test)]
+mod tests {
+ use ide_db::base_db::FileRange;
+ use itertools::Itertools;
+
+ use crate::fixture;
+
+ #[track_caller]
+ fn check(ra_fixture: &str) {
+ let (analysis, position, expected) = fixture::annotations(ra_fixture);
+ let navs = analysis.goto_definition(position).unwrap().expect("no definition found").info;
+
+ let cmp = |&FileRange { file_id, range }: &_| (file_id, range.start());
+ let navs = navs
+ .into_iter()
+ .map(|nav| FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() })
+ .sorted_by_key(cmp)
+ .collect::<Vec<_>>();
+ let expected = expected
+ .into_iter()
+ .map(|(FileRange { file_id, range }, _)| FileRange { file_id, range })
+ .sorted_by_key(cmp)
+ .collect::<Vec<_>>();
+ assert_eq!(expected, navs);
+ }
+
+ fn check_unresolved(ra_fixture: &str) {
+ let (analysis, position) = fixture::position(ra_fixture);
+ let navs = analysis.goto_definition(position).unwrap().expect("no definition found").info;
+
+ assert!(navs.is_empty(), "didn't expect this to resolve anywhere: {:?}", navs)
+ }
+
+ #[test]
+ fn goto_def_if_items_same_name() {
+ check(
+ r#"
+trait Trait {
+ type A;
+ const A: i32;
+ //^
+}
+
+struct T;
+impl Trait for T {
+ type A = i32;
+ const A$0: i32 = -9;
+}"#,
+ );
+ }
+ #[test]
+ fn goto_def_in_mac_call_in_attr_invoc() {
+ check(
+ r#"
+//- proc_macros: identity
+pub struct Struct {
+ // ^^^^^^
+ field: i32,
+}
+
+macro_rules! identity {
+ ($($tt:tt)*) => {$($tt)*};
+}
+
+#[proc_macros::identity]
+fn function() {
+ identity!(Struct$0 { field: 0 });
+}
+
+"#,
+ )
+ }
+
+ #[test]
+ fn goto_def_for_extern_crate() {
+ check(
+ r#"
+//- /main.rs crate:main deps:std
+extern crate std$0;
+//- /std/lib.rs crate:std
+// empty
+//^file
+"#,
+ )
+ }
+
+ #[test]
+ fn goto_def_for_renamed_extern_crate() {
+ check(
+ r#"
+//- /main.rs crate:main deps:std
+extern crate std as abc$0;
+//- /std/lib.rs crate:std
+// empty
+//^file
+"#,
+ )
+ }
+
+ #[test]
+ fn goto_def_in_items() {
+ check(
+ r#"
+struct Foo;
+ //^^^
+enum E { X(Foo$0) }
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_at_start_of_item() {
+ check(
+ r#"
+struct Foo;
+ //^^^
+enum E { X($0Foo) }
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_definition_resolves_correct_name() {
+ check(
+ r#"
+//- /lib.rs
+use a::Foo;
+mod a;
+mod b;
+enum E { X(Foo$0) }
+
+//- /a.rs
+struct Foo;
+ //^^^
+//- /b.rs
+struct Foo;
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_module_declaration() {
+ check(
+ r#"
+//- /lib.rs
+mod $0foo;
+
+//- /foo.rs
+// empty
+//^file
+"#,
+ );
+
+ check(
+ r#"
+//- /lib.rs
+mod $0foo;
+
+//- /foo/mod.rs
+// empty
+//^file
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_macros() {
+ check(
+ r#"
+macro_rules! foo { () => { () } }
+ //^^^
+fn bar() {
+ $0foo!();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_macros_from_other_crates() {
+ check(
+ r#"
+//- /lib.rs crate:main deps:foo
+use foo::foo;
+fn bar() {
+ $0foo!();
+}
+
+//- /foo/lib.rs crate:foo
+#[macro_export]
+macro_rules! foo { () => { () } }
+ //^^^
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_macros_in_use_tree() {
+ check(
+ r#"
+//- /lib.rs crate:main deps:foo
+use foo::foo$0;
+
+//- /foo/lib.rs crate:foo
+#[macro_export]
+macro_rules! foo { () => { () } }
+ //^^^
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_macro_defined_fn_with_arg() {
+ check(
+ r#"
+//- /lib.rs
+macro_rules! define_fn {
+ ($name:ident) => (fn $name() {})
+}
+
+define_fn!(foo);
+ //^^^
+
+fn bar() {
+ $0foo();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_macro_defined_fn_no_arg() {
+ check(
+ r#"
+//- /lib.rs
+macro_rules! define_fn {
+ () => (fn foo() {})
+}
+
+ define_fn!();
+//^^^^^^^^^^^^^
+
+fn bar() {
+ $0foo();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_definition_works_for_macro_inside_pattern() {
+ check(
+ r#"
+//- /lib.rs
+macro_rules! foo {() => {0}}
+ //^^^
+
+fn bar() {
+ match (0,1) {
+ ($0foo!(), _) => {}
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_definition_works_for_macro_inside_match_arm_lhs() {
+ check(
+ r#"
+//- /lib.rs
+macro_rules! foo {() => {0}}
+ //^^^
+fn bar() {
+ match 0 {
+ $0foo!() => {}
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_use_alias() {
+ check(
+ r#"
+//- /lib.rs crate:main deps:foo
+use foo as bar$0;
+
+//- /foo/lib.rs crate:foo
+// empty
+//^file
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_use_alias_foo_macro() {
+ check(
+ r#"
+//- /lib.rs crate:main deps:foo
+use foo::foo as bar$0;
+
+//- /foo/lib.rs crate:foo
+#[macro_export]
+macro_rules! foo { () => { () } }
+ //^^^
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_methods() {
+ check(
+ r#"
+struct Foo;
+impl Foo {
+ fn frobnicate(&self) { }
+ //^^^^^^^^^^
+}
+
+fn bar(foo: &Foo) {
+ foo.frobnicate$0();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_fields() {
+ check(
+ r#"
+struct Foo {
+ spam: u32,
+} //^^^^
+
+fn bar(foo: &Foo) {
+ foo.spam$0;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_record_fields() {
+ check(
+ r#"
+//- /lib.rs
+struct Foo {
+ spam: u32,
+} //^^^^
+
+fn bar() -> Foo {
+ Foo {
+ spam$0: 0,
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_record_pat_fields() {
+ check(
+ r#"
+//- /lib.rs
+struct Foo {
+ spam: u32,
+} //^^^^
+
+fn bar(foo: Foo) -> Foo {
+ let Foo { spam$0: _, } = foo
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_record_fields_macros() {
+ check(
+ r"
+macro_rules! m { () => { 92 };}
+struct Foo { spam: u32 }
+ //^^^^
+
+fn bar() -> Foo {
+ Foo { spam$0: m!() }
+}
+",
+ );
+ }
+
+ #[test]
+ fn goto_for_tuple_fields() {
+ check(
+ r#"
+struct Foo(u32);
+ //^^^
+
+fn bar() {
+ let foo = Foo(0);
+ foo.$00;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_ufcs_inherent_methods() {
+ check(
+ r#"
+struct Foo;
+impl Foo {
+ fn frobnicate() { }
+} //^^^^^^^^^^
+
+fn bar(foo: &Foo) {
+ Foo::frobnicate$0();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_ufcs_trait_methods_through_traits() {
+ check(
+ r#"
+trait Foo {
+ fn frobnicate();
+} //^^^^^^^^^^
+
+fn bar() {
+ Foo::frobnicate$0();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_ufcs_trait_methods_through_self() {
+ check(
+ r#"
+struct Foo;
+trait Trait {
+ fn frobnicate();
+} //^^^^^^^^^^
+impl Trait for Foo {}
+
+fn bar() {
+ Foo::frobnicate$0();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_definition_on_self() {
+ check(
+ r#"
+struct Foo;
+impl Foo {
+ //^^^
+ pub fn new() -> Self {
+ Self$0 {}
+ }
+}
+"#,
+ );
+ check(
+ r#"
+struct Foo;
+impl Foo {
+ //^^^
+ pub fn new() -> Self$0 {
+ Self {}
+ }
+}
+"#,
+ );
+
+ check(
+ r#"
+enum Foo { A }
+impl Foo {
+ //^^^
+ pub fn new() -> Self$0 {
+ Foo::A
+ }
+}
+"#,
+ );
+
+ check(
+ r#"
+enum Foo { A }
+impl Foo {
+ //^^^
+ pub fn thing(a: &Self$0) {
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_definition_on_self_in_trait_impl() {
+ check(
+ r#"
+struct Foo;
+trait Make {
+ fn new() -> Self;
+}
+impl Make for Foo {
+ //^^^
+ fn new() -> Self {
+ Self$0 {}
+ }
+}
+"#,
+ );
+
+ check(
+ r#"
+struct Foo;
+trait Make {
+ fn new() -> Self;
+}
+impl Make for Foo {
+ //^^^
+ fn new() -> Self$0 {
+ Self {}
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_when_used_on_definition_name_itself() {
+ check(
+ r#"
+struct Foo$0 { value: u32 }
+ //^^^
+ "#,
+ );
+
+ check(
+ r#"
+struct Foo {
+ field$0: string,
+} //^^^^^
+"#,
+ );
+
+ check(
+ r#"
+fn foo_test$0() { }
+ //^^^^^^^^
+"#,
+ );
+
+ check(
+ r#"
+enum Foo$0 { Variant }
+ //^^^
+"#,
+ );
+
+ check(
+ r#"
+enum Foo {
+ Variant1,
+ Variant2$0,
+ //^^^^^^^^
+ Variant3,
+}
+"#,
+ );
+
+ check(
+ r#"
+static INNER$0: &str = "";
+ //^^^^^
+"#,
+ );
+
+ check(
+ r#"
+const INNER$0: &str = "";
+ //^^^^^
+"#,
+ );
+
+ check(
+ r#"
+type Thing$0 = Option<()>;
+ //^^^^^
+"#,
+ );
+
+ check(
+ r#"
+trait Foo$0 { }
+ //^^^
+"#,
+ );
+
+ check(
+ r#"
+mod bar$0 { }
+ //^^^
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_from_macro() {
+ check(
+ r#"
+macro_rules! id {
+ ($($tt:tt)*) => { $($tt)* }
+}
+fn foo() {}
+ //^^^
+id! {
+ fn bar() {
+ fo$0o();
+ }
+}
+mod confuse_index { fn foo(); }
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_through_format() {
+ check(
+ r#"
+#[macro_export]
+macro_rules! format {
+ ($($arg:tt)*) => ($crate::fmt::format($crate::__export::format_args!($($arg)*)))
+}
+#[rustc_builtin_macro]
+#[macro_export]
+macro_rules! format_args {
+ ($fmt:expr) => ({ /* compiler built-in */ });
+ ($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ })
+}
+pub mod __export {
+ pub use crate::format_args;
+ fn foo() {} // for index confusion
+}
+fn foo() -> i8 {}
+ //^^^
+fn test() {
+ format!("{}", fo$0o())
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_through_included_file() {
+ check(
+ r#"
+//- /main.rs
+#[rustc_builtin_macro]
+macro_rules! include {}
+
+ include!("foo.rs");
+//^^^^^^^^^^^^^^^^^^^
+
+fn f() {
+ foo$0();
+}
+
+mod confuse_index {
+ pub fn foo() {}
+}
+
+//- /foo.rs
+fn foo() {}
+ "#,
+ );
+ }
+
+ #[test]
+ fn goto_for_type_param() {
+ check(
+ r#"
+struct Foo<T: Clone> { t: $0T }
+ //^
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_within_macro() {
+ check(
+ r#"
+macro_rules! id {
+ ($($tt:tt)*) => ($($tt)*)
+}
+
+fn foo() {
+ let x = 1;
+ //^
+ id!({
+ let y = $0x;
+ let z = y;
+ });
+}
+"#,
+ );
+
+ check(
+ r#"
+macro_rules! id {
+ ($($tt:tt)*) => ($($tt)*)
+}
+
+fn foo() {
+ let x = 1;
+ id!({
+ let y = x;
+ //^
+ let z = $0y;
+ });
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_in_local_fn() {
+ check(
+ r#"
+fn main() {
+ fn foo() {
+ let x = 92;
+ //^
+ $0x;
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_in_local_macro() {
+ check(
+ r#"
+fn bar() {
+ macro_rules! foo { () => { () } }
+ //^^^
+ $0foo!();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_field_init_shorthand() {
+ check(
+ r#"
+struct Foo { x: i32 }
+ //^
+fn main() {
+ let x = 92;
+ //^
+ Foo { x$0 };
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn goto_def_for_enum_variant_field() {
+ check(
+ r#"
+enum Foo {
+ Bar { x: i32 }
+ //^
+}
+fn baz(foo: Foo) {
+ match foo {
+ Foo::Bar { x$0 } => x
+ //^
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_enum_variant_self_pattern_const() {
+ check(
+ r#"
+enum Foo { Bar }
+ //^^^
+impl Foo {
+ fn baz(self) {
+ match self { Self::Bar$0 => {} }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_enum_variant_self_pattern_record() {
+ check(
+ r#"
+enum Foo { Bar { val: i32 } }
+ //^^^
+impl Foo {
+ fn baz(self) -> i32 {
+ match self { Self::Bar$0 { val } => {} }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_enum_variant_self_expr_const() {
+ check(
+ r#"
+enum Foo { Bar }
+ //^^^
+impl Foo {
+ fn baz(self) { Self::Bar$0; }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_enum_variant_self_expr_record() {
+ check(
+ r#"
+enum Foo { Bar { val: i32 } }
+ //^^^
+impl Foo {
+ fn baz(self) { Self::Bar$0 {val: 4}; }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_type_alias_generic_parameter() {
+ check(
+ r#"
+type Alias<T> = T$0;
+ //^
+"#,
+ )
+ }
+
+ #[test]
+ fn goto_def_for_macro_container() {
+ check(
+ r#"
+//- /lib.rs crate:main deps:foo
+foo::module$0::mac!();
+
+//- /foo/lib.rs crate:foo
+pub mod module {
+ //^^^^^^
+ #[macro_export]
+ macro_rules! _mac { () => { () } }
+ pub use crate::_mac as mac;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_assoc_ty_in_path() {
+ check(
+ r#"
+trait Iterator {
+ type Item;
+ //^^^^
+}
+
+fn f() -> impl Iterator<Item$0 = u8> {}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_super_assoc_ty_in_path() {
+ check(
+ r#"
+trait Super {
+ type Item;
+ //^^^^
+}
+
+trait Sub: Super {}
+
+fn f() -> impl Sub<Item$0 = u8> {}
+"#,
+ );
+ }
+
+ #[test]
+ fn unknown_assoc_ty() {
+ check_unresolved(
+ r#"
+trait Iterator { type Item; }
+fn f() -> impl Iterator<Invalid$0 = u8> {}
+"#,
+ )
+ }
+
+ #[test]
+ fn goto_def_for_assoc_ty_in_path_multiple() {
+ check(
+ r#"
+trait Iterator {
+ type A;
+ //^
+ type B;
+}
+
+fn f() -> impl Iterator<A$0 = u8, B = ()> {}
+"#,
+ );
+ check(
+ r#"
+trait Iterator {
+ type A;
+ type B;
+ //^
+}
+
+fn f() -> impl Iterator<A = u8, B$0 = ()> {}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_assoc_ty_ufcs() {
+ check(
+ r#"
+trait Iterator {
+ type Item;
+ //^^^^
+}
+
+fn g() -> <() as Iterator<Item$0 = ()>>::Item {}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_for_assoc_ty_ufcs_multiple() {
+ check(
+ r#"
+trait Iterator {
+ type A;
+ //^
+ type B;
+}
+
+fn g() -> <() as Iterator<A$0 = (), B = u8>>::B {}
+"#,
+ );
+ check(
+ r#"
+trait Iterator {
+ type A;
+ type B;
+ //^
+}
+
+fn g() -> <() as Iterator<A = (), B$0 = u8>>::A {}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_self_param_ty_specified() {
+ check(
+ r#"
+struct Foo {}
+
+impl Foo {
+ fn bar(self: &Foo) {
+ //^^^^
+ let foo = sel$0f;
+ }
+}"#,
+ )
+ }
+
+ #[test]
+ fn goto_self_param_on_decl() {
+ check(
+ r#"
+struct Foo {}
+
+impl Foo {
+ fn bar(&self$0) {
+ //^^^^
+ }
+}"#,
+ )
+ }
+
+ #[test]
+ fn goto_lifetime_param_on_decl() {
+ check(
+ r#"
+fn foo<'foobar$0>(_: &'foobar ()) {
+ //^^^^^^^
+}"#,
+ )
+ }
+
+ #[test]
+ fn goto_lifetime_param_decl() {
+ check(
+ r#"
+fn foo<'foobar>(_: &'foobar$0 ()) {
+ //^^^^^^^
+}"#,
+ )
+ }
+
+ #[test]
+ fn goto_lifetime_param_decl_nested() {
+ check(
+ r#"
+fn foo<'foobar>(_: &'foobar ()) {
+ fn foo<'foobar>(_: &'foobar$0 ()) {}
+ //^^^^^^^
+}"#,
+ )
+ }
+
+ #[test]
+ fn goto_lifetime_hrtb() {
+ // FIXME: requires the HIR to somehow track these hrtb lifetimes
+ check_unresolved(
+ r#"
+trait Foo<T> {}
+fn foo<T>() where for<'a> T: Foo<&'a$0 (u8, u16)>, {}
+ //^^
+"#,
+ );
+ check_unresolved(
+ r#"
+trait Foo<T> {}
+fn foo<T>() where for<'a$0> T: Foo<&'a (u8, u16)>, {}
+ //^^
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_lifetime_hrtb_for_type() {
+ // FIXME: requires ForTypes to be implemented
+ check_unresolved(
+ r#"trait Foo<T> {}
+fn foo<T>() where T: for<'a> Foo<&'a$0 (u8, u16)>, {}
+ //^^
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_label() {
+ check(
+ r#"
+fn foo<'foo>(_: &'foo ()) {
+ 'foo: {
+ //^^^^
+ 'bar: loop {
+ break 'foo$0;
+ }
+ }
+}"#,
+ )
+ }
+
+ #[test]
+ fn goto_def_for_intra_doc_link_same_file() {
+ check(
+ r#"
+/// Blah, [`bar`](bar) .. [`foo`](foo$0) has [`bar`](bar)
+pub fn bar() { }
+
+/// You might want to see [`std::fs::read()`] too.
+pub fn foo() { }
+ //^^^
+
+}"#,
+ )
+ }
+
+ #[test]
+ fn goto_def_for_intra_doc_link_inner() {
+ check(
+ r#"
+//- /main.rs
+mod m;
+struct S;
+ //^
+
+//- /m.rs
+//! [`super::S$0`]
+"#,
+ )
+ }
+
+ #[test]
+ fn goto_incomplete_field() {
+ check(
+ r#"
+struct A { a: u32 }
+ //^
+fn foo() { A { a$0: }; }
+"#,
+ )
+ }
+
+ #[test]
+ fn goto_proc_macro() {
+ check(
+ r#"
+//- /main.rs crate:main deps:mac
+use mac::fn_macro;
+
+fn_macro$0!();
+
+//- /mac.rs crate:mac
+#![crate_type="proc-macro"]
+#[proc_macro]
+fn fn_macro() {}
+ //^^^^^^^^
+ "#,
+ )
+ }
+
+ #[test]
+ fn goto_intra_doc_links() {
+ check(
+ r#"
+
+pub mod theitem {
+ /// This is the item. Cool!
+ pub struct TheItem;
+ //^^^^^^^
+}
+
+/// Gives you a [`TheItem$0`].
+///
+/// [`TheItem`]: theitem::TheItem
+pub fn gimme() -> theitem::TheItem {
+ theitem::TheItem
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_ident_from_pat_macro() {
+ check(
+ r#"
+macro_rules! pat {
+ ($name:ident) => { Enum::Variant1($name) }
+}
+
+enum Enum {
+ Variant1(u8),
+ Variant2,
+}
+
+fn f(e: Enum) {
+ match e {
+ pat!(bind) => {
+ //^^^^
+ bind$0
+ }
+ Enum::Variant2 => {}
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_include() {
+ check(
+ r#"
+//- /main.rs
++
++#[rustc_builtin_macro]
++macro_rules! include_str {}
++
+fn main() {
+ let str = include_str!("foo.txt$0");
+}
+//- /foo.txt
+// empty
+//^file
+"#,
+ );
+ }
++
++ #[test]
++ fn goto_doc_include_str() {
++ check(
++ r#"
++//- /main.rs
++#[rustc_builtin_macro]
++macro_rules! include_str {}
++
++#[doc = include_str!("docs.md$0")]
++struct Item;
++
++//- /docs.md
++// docs
++//^file
++"#,
++ );
++ }
++
++ #[test]
++ fn goto_shadow_include() {
++ check(
++ r#"
++//- /main.rs
++macro_rules! include {
++ ("included.rs") => {}
++}
++
++include!("included.rs$0");
++
++//- /included.rs
++// empty
++"#,
++ );
++ }
++
+ #[cfg(test)]
+ mod goto_impl_of_trait_fn {
+ use super::check;
+ #[test]
+ fn cursor_on_impl() {
+ check(
+ r#"
+trait Twait {
+ fn a();
+}
+
+struct Stwuct;
+
+impl Twait for Stwuct {
+ fn a$0();
+ //^
+}
+ "#,
+ );
+ }
+ #[test]
+ fn method_call() {
+ check(
+ r#"
+trait Twait {
+ fn a(&self);
+}
+
+struct Stwuct;
+
+impl Twait for Stwuct {
+ fn a(&self){};
+ //^
+}
+fn f() {
+ let s = Stwuct;
+ s.a$0();
+}
+ "#,
+ );
+ }
+ #[test]
+ fn path_call() {
+ check(
+ r#"
+trait Twait {
+ fn a(&self);
+}
+
+struct Stwuct;
+
+impl Twait for Stwuct {
+ fn a(&self){};
+ //^
+}
+fn f() {
+ let s = Stwuct;
+ Stwuct::a$0(&s);
+}
+ "#,
+ );
+ }
+ #[test]
+ fn where_clause_can_work() {
+ check(
+ r#"
+trait G {
+ fn g(&self);
+}
+trait Bound{}
+trait EA{}
+struct Gen<T>(T);
+impl <T:EA> G for Gen<T> {
+ fn g(&self) {
+ }
+}
+impl <T> G for Gen<T>
+where T : Bound
+{
+ fn g(&self){
+ //^
+ }
+}
+struct A;
+impl Bound for A{}
+fn f() {
+ let gen = Gen::<A>(A);
+ gen.g$0();
+}
+ "#,
+ );
+ }
+ #[test]
+ fn wc_case_is_ok() {
+ check(
+ r#"
+trait G {
+ fn g(&self);
+}
+trait BParent{}
+trait Bound: BParent{}
+struct Gen<T>(T);
+impl <T> G for Gen<T>
+where T : Bound
+{
+ fn g(&self){
+ //^
+ }
+}
+struct A;
+impl Bound for A{}
+fn f() {
+ let gen = Gen::<A>(A);
+ gen.g$0();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn method_call_defaulted() {
+ check(
+ r#"
+trait Twait {
+ fn a(&self) {}
+ //^
+}
+
+struct Stwuct;
+
+impl Twait for Stwuct {
+}
+fn f() {
+ let s = Stwuct;
+ s.a$0();
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn method_call_on_generic() {
+ check(
+ r#"
+trait Twait {
+ fn a(&self) {}
+ //^
+}
+
+fn f<T: Twait>(s: T) {
+ s.a$0();
+}
+ "#,
+ );
+ }
+ }
+
+ #[test]
+ fn goto_def_of_trait_impl_const() {
+ check(
+ r#"
+trait Twait {
+ const NOMS: bool;
+ // ^^^^
+}
+
+struct Stwuct;
+
+impl Twait for Stwuct {
+ const NOMS$0: bool = true;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_of_trait_impl_type_alias() {
+ check(
+ r#"
+trait Twait {
+ type IsBad;
+ // ^^^^^
+}
+
+struct Stwuct;
+
+impl Twait for Stwuct {
+ type IsBad$0 = !;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_def_derive_input() {
+ check(
+ r#"
+ //- minicore:derive
+ #[rustc_builtin_macro]
+ pub macro Copy {}
+ // ^^^^
+ #[derive(Copy$0)]
+ struct Foo;
+ "#,
+ );
+ check(
+ r#"
+//- minicore:derive
+#[rustc_builtin_macro]
+pub macro Copy {}
+ // ^^^^
+#[cfg_attr(feature = "false", derive)]
+#[derive(Copy$0)]
+struct Foo;
+ "#,
+ );
+ check(
+ r#"
+//- minicore:derive
+mod foo {
+ #[rustc_builtin_macro]
+ pub macro Copy {}
+ // ^^^^
+}
+#[derive(foo::Copy$0)]
+struct Foo;
+ "#,
+ );
+ check(
+ r#"
+//- minicore:derive
+mod foo {
+ // ^^^
+ #[rustc_builtin_macro]
+ pub macro Copy {}
+}
+#[derive(foo$0::Copy)]
+struct Foo;
+ "#,
+ );
+ }
+
+ #[test]
+ fn goto_def_in_macro_multi() {
+ check(
+ r#"
+struct Foo {
+ foo: ()
+ //^^^
+}
+macro_rules! foo {
+ ($ident:ident) => {
+ fn $ident(Foo { $ident }: Foo) {}
+ }
+}
+foo!(foo$0);
+ //^^^
+ //^^^
+"#,
+ );
+ check(
+ r#"
+fn bar() {}
+ //^^^
+struct bar;
+ //^^^
+macro_rules! foo {
+ ($ident:ident) => {
+ fn foo() {
+ let _: $ident = $ident;
+ }
+ }
+}
+
+foo!(bar$0);
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_await_poll() {
+ check(
+ r#"
+//- minicore: future
+
+struct MyFut;
+
+impl core::future::Future for MyFut {
+ type Output = ();
+
+ fn poll(
+ //^^^^
+ self: std::pin::Pin<&mut Self>,
+ cx: &mut std::task::Context<'_>
+ ) -> std::task::Poll<Self::Output>
+ {
+ ()
+ }
+}
+
+fn f() {
+ MyFut.await$0;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_await_into_future_poll() {
+ check(
+ r#"
+//- minicore: future
+
+struct Futurable;
+
+impl core::future::IntoFuture for Futurable {
+ type IntoFuture = MyFut;
+}
+
+struct MyFut;
+
+impl core::future::Future for MyFut {
+ type Output = ();
+
+ fn poll(
+ //^^^^
+ self: std::pin::Pin<&mut Self>,
+ cx: &mut std::task::Context<'_>
+ ) -> std::task::Poll<Self::Output>
+ {
+ ()
+ }
+}
+
+fn f() {
+ Futurable.await$0;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_try_op() {
+ check(
+ r#"
+//- minicore: try
+
+struct Struct;
+
+impl core::ops::Try for Struct {
+ fn branch(
+ //^^^^^^
+ self
+ ) {}
+}
+
+fn f() {
+ Struct?$0;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_index_op() {
+ check(
+ r#"
+//- minicore: index
+
+struct Struct;
+
+impl core::ops::Index<usize> for Struct {
+ fn index(
+ //^^^^^
+ self
+ ) {}
+}
+
+fn f() {
+ Struct[0]$0;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_prefix_op() {
+ check(
+ r#"
+//- minicore: deref
+
+struct Struct;
+
+impl core::ops::Deref for Struct {
+ fn deref(
+ //^^^^^
+ self
+ ) {}
+}
+
+fn f() {
+ $0*Struct;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_bin_op() {
+ check(
+ r#"
+//- minicore: add
+
+struct Struct;
+
+impl core::ops::Add for Struct {
+ fn add(
+ //^^^
+ self
+ ) {}
+}
+
+fn f() {
+ Struct +$0 Struct;
+}
+"#,
+ );
+ }
+}
--- /dev/null
+use hir::Semantics;
+use ide_db::{
+ base_db::{FileId, FilePosition},
+ defs::{Definition, IdentClass},
+ helpers::pick_best_token,
+ search::{FileReference, ReferenceCategory, SearchScope},
+ syntax_helpers::node_ext::{for_each_break_and_continue_expr, for_each_tail_expr, walk_expr},
+ FxHashSet, RootDatabase,
+};
+use syntax::{
+ ast::{self, HasLoopBody},
+ match_ast, AstNode,
+ SyntaxKind::{self, IDENT, INT_NUMBER},
+ SyntaxNode, SyntaxToken, TextRange, T,
+};
+
+use crate::{references, NavigationTarget, TryToNav};
+
+#[derive(PartialEq, Eq, Hash)]
+pub struct HighlightedRange {
+ pub range: TextRange,
+ // FIXME: This needs to be more precise. Reference category makes sense only
+ // for references, but we also have defs. And things like exit points are
+ // neither.
+ pub category: Option<ReferenceCategory>,
+}
+
+#[derive(Default, Clone)]
+pub struct HighlightRelatedConfig {
+ pub references: bool,
+ pub exit_points: bool,
+ pub break_points: bool,
+ pub yield_points: bool,
+}
+
+// Feature: Highlight Related
+//
+// Highlights constructs related to the thing under the cursor:
+//
+// . if on an identifier, highlights all references to that identifier in the current file
+// . if on an `async` or `await token, highlights all yield points for that async context
+// . if on a `return` or `fn` keyword, `?` character or `->` return type arrow, highlights all exit points for that context
+// . if on a `break`, `loop`, `while` or `for` token, highlights all break points for that loop or block context
+//
+// Note: `?` and `->` do not currently trigger this behavior in the VSCode editor.
+pub(crate) fn highlight_related(
+ sema: &Semantics<'_, RootDatabase>,
+ config: HighlightRelatedConfig,
+ FilePosition { offset, file_id }: FilePosition,
+) -> Option<Vec<HighlightedRange>> {
+ let _p = profile::span("highlight_related");
+ let syntax = sema.parse(file_id).syntax().clone();
+
+ let token = pick_best_token(syntax.token_at_offset(offset), |kind| match kind {
+ T![?] => 4, // prefer `?` when the cursor is sandwiched like in `await$0?`
+ T![->] => 3,
+ kind if kind.is_keyword() => 2,
+ IDENT | INT_NUMBER => 1,
+ _ => 0,
+ })?;
+ match token.kind() {
+ T![?] if config.exit_points && token.parent().and_then(ast::TryExpr::cast).is_some() => {
+ highlight_exit_points(sema, token)
+ }
+ T![fn] | T![return] | T![->] if config.exit_points => highlight_exit_points(sema, token),
+ T![await] | T![async] if config.yield_points => highlight_yield_points(token),
+ T![for] if config.break_points && token.parent().and_then(ast::ForExpr::cast).is_some() => {
+ highlight_break_points(token)
+ }
+ T![break] | T![loop] | T![while] | T![continue] if config.break_points => {
+ highlight_break_points(token)
+ }
+ _ if config.references => highlight_references(sema, &syntax, token, file_id),
+ _ => None,
+ }
+}
+
+fn highlight_references(
+ sema: &Semantics<'_, RootDatabase>,
+ node: &SyntaxNode,
+ token: SyntaxToken,
+ file_id: FileId,
+) -> Option<Vec<HighlightedRange>> {
+ let defs = find_defs(sema, token);
+ let usages = defs
+ .iter()
+ .filter_map(|&d| {
+ d.usages(sema)
+ .set_scope(Some(SearchScope::single_file(file_id)))
+ .include_self_refs()
+ .all()
+ .references
+ .remove(&file_id)
+ })
+ .flatten()
+ .map(|FileReference { category: access, range, .. }| HighlightedRange {
+ range,
+ category: access,
+ });
+ let mut res = FxHashSet::default();
+
+ let mut def_to_hl_range = |def| {
+ let hl_range = match def {
+ Definition::Module(module) => {
+ Some(NavigationTarget::from_module_to_decl(sema.db, module))
+ }
+ def => def.try_to_nav(sema.db),
+ }
+ .filter(|decl| decl.file_id == file_id)
+ .and_then(|decl| decl.focus_range)
+ .map(|range| {
+ let category =
+ references::decl_mutability(&def, node, range).then(|| ReferenceCategory::Write);
+ HighlightedRange { range, category }
+ });
+ if let Some(hl_range) = hl_range {
+ res.insert(hl_range);
+ }
+ };
+ for &def in &defs {
+ match def {
+ Definition::Local(local) => local
+ .associated_locals(sema.db)
+ .iter()
+ .for_each(|&local| def_to_hl_range(Definition::Local(local))),
+ def => def_to_hl_range(def),
+ }
+ }
+
+ res.extend(usages);
+ if res.is_empty() {
+ None
+ } else {
+ Some(res.into_iter().collect())
+ }
+}
+
+fn highlight_exit_points(
+ sema: &Semantics<'_, RootDatabase>,
+ token: SyntaxToken,
+) -> Option<Vec<HighlightedRange>> {
+ fn hl(
+ sema: &Semantics<'_, RootDatabase>,
+ body: Option<ast::Expr>,
+ ) -> Option<Vec<HighlightedRange>> {
+ let mut highlights = Vec::new();
+ let body = body?;
+ walk_expr(&body, &mut |expr| match expr {
+ ast::Expr::ReturnExpr(expr) => {
+ if let Some(token) = expr.return_token() {
+ highlights.push(HighlightedRange { category: None, range: token.text_range() });
+ }
+ }
+ ast::Expr::TryExpr(try_) => {
+ if let Some(token) = try_.question_mark_token() {
+ highlights.push(HighlightedRange { category: None, range: token.text_range() });
+ }
+ }
+ ast::Expr::MethodCallExpr(_) | ast::Expr::CallExpr(_) | ast::Expr::MacroExpr(_) => {
+ if sema.type_of_expr(&expr).map_or(false, |ty| ty.original.is_never()) {
+ highlights.push(HighlightedRange {
+ category: None,
+ range: expr.syntax().text_range(),
+ });
+ }
+ }
+ _ => (),
+ });
+ let tail = match body {
+ ast::Expr::BlockExpr(b) => b.tail_expr(),
+ e => Some(e),
+ };
+
+ if let Some(tail) = tail {
+ for_each_tail_expr(&tail, &mut |tail| {
+ let range = match tail {
+ ast::Expr::BreakExpr(b) => b
+ .break_token()
+ .map_or_else(|| tail.syntax().text_range(), |tok| tok.text_range()),
+ _ => tail.syntax().text_range(),
+ };
+ highlights.push(HighlightedRange { category: None, range })
+ });
+ }
+ Some(highlights)
+ }
+ for anc in token.parent_ancestors() {
+ return match_ast! {
+ match anc {
+ ast::Fn(fn_) => hl(sema, fn_.body().map(ast::Expr::BlockExpr)),
+ ast::ClosureExpr(closure) => hl(sema, closure.body()),
+ ast::BlockExpr(block_expr) => if matches!(block_expr.modifier(), Some(ast::BlockModifier::Async(_) | ast::BlockModifier::Try(_)| ast::BlockModifier::Const(_))) {
+ hl(sema, Some(block_expr.into()))
+ } else {
+ continue;
+ },
+ _ => continue,
+ }
+ };
+ }
+ None
+}
+
+fn highlight_break_points(token: SyntaxToken) -> Option<Vec<HighlightedRange>> {
+ fn hl(
+ cursor_token_kind: SyntaxKind,
+ token: Option<SyntaxToken>,
+ label: Option<ast::Label>,
+ body: Option<ast::StmtList>,
+ ) -> Option<Vec<HighlightedRange>> {
+ let mut highlights = Vec::new();
+ let range = cover_range(
+ token.map(|tok| tok.text_range()),
+ label.as_ref().map(|it| it.syntax().text_range()),
+ );
+ highlights.extend(range.map(|range| HighlightedRange { category: None, range }));
+ for_each_break_and_continue_expr(label, body, &mut |expr| {
+ let range: Option<TextRange> = match (cursor_token_kind, expr) {
+ (T![for] | T![while] | T![loop] | T![break], ast::Expr::BreakExpr(break_)) => {
+ cover_range(
+ break_.break_token().map(|it| it.text_range()),
+ break_.lifetime().map(|it| it.syntax().text_range()),
+ )
+ }
+ (
+ T![for] | T![while] | T![loop] | T![continue],
+ ast::Expr::ContinueExpr(continue_),
+ ) => cover_range(
+ continue_.continue_token().map(|it| it.text_range()),
+ continue_.lifetime().map(|it| it.syntax().text_range()),
+ ),
+ _ => None,
+ };
+ highlights.extend(range.map(|range| HighlightedRange { category: None, range }));
+ });
+ Some(highlights)
+ }
+ let parent = token.parent()?;
+ let lbl = match_ast! {
+ match parent {
+ ast::BreakExpr(b) => b.lifetime(),
+ ast::ContinueExpr(c) => c.lifetime(),
+ ast::LoopExpr(l) => l.label().and_then(|it| it.lifetime()),
+ ast::ForExpr(f) => f.label().and_then(|it| it.lifetime()),
+ ast::WhileExpr(w) => w.label().and_then(|it| it.lifetime()),
+ ast::BlockExpr(b) => Some(b.label().and_then(|it| it.lifetime())?),
+ _ => return None,
+ }
+ };
+ let lbl = lbl.as_ref();
+ let label_matches = |def_lbl: Option<ast::Label>| match lbl {
+ Some(lbl) => {
+ Some(lbl.text()) == def_lbl.and_then(|it| it.lifetime()).as_ref().map(|it| it.text())
+ }
+ None => true,
+ };
+ let token_kind = token.kind();
+ for anc in token.parent_ancestors().flat_map(ast::Expr::cast) {
+ return match anc {
+ ast::Expr::LoopExpr(l) if label_matches(l.label()) => hl(
+ token_kind,
+ l.loop_token(),
+ l.label(),
+ l.loop_body().and_then(|it| it.stmt_list()),
+ ),
+ ast::Expr::ForExpr(f) if label_matches(f.label()) => hl(
+ token_kind,
+ f.for_token(),
+ f.label(),
+ f.loop_body().and_then(|it| it.stmt_list()),
+ ),
+ ast::Expr::WhileExpr(w) if label_matches(w.label()) => hl(
+ token_kind,
+ w.while_token(),
+ w.label(),
+ w.loop_body().and_then(|it| it.stmt_list()),
+ ),
+ ast::Expr::BlockExpr(e) if e.label().is_some() && label_matches(e.label()) => {
+ hl(token_kind, None, e.label(), e.stmt_list())
+ }
+ _ => continue,
+ };
+ }
+ None
+}
+
+fn highlight_yield_points(token: SyntaxToken) -> Option<Vec<HighlightedRange>> {
+ fn hl(
+ async_token: Option<SyntaxToken>,
+ body: Option<ast::Expr>,
+ ) -> Option<Vec<HighlightedRange>> {
+ let mut highlights =
+ vec![HighlightedRange { category: None, range: async_token?.text_range() }];
+ if let Some(body) = body {
+ walk_expr(&body, &mut |expr| {
+ if let ast::Expr::AwaitExpr(expr) = expr {
+ if let Some(token) = expr.await_token() {
+ highlights
+ .push(HighlightedRange { category: None, range: token.text_range() });
+ }
+ }
+ });
+ }
+ Some(highlights)
+ }
+ for anc in token.parent_ancestors() {
+ return match_ast! {
+ match anc {
+ ast::Fn(fn_) => hl(fn_.async_token(), fn_.body().map(ast::Expr::BlockExpr)),
+ ast::BlockExpr(block_expr) => {
+ if block_expr.async_token().is_none() {
+ continue;
+ }
+ hl(block_expr.async_token(), Some(block_expr.into()))
+ },
+ ast::ClosureExpr(closure) => hl(closure.async_token(), closure.body()),
+ _ => continue,
+ }
+ };
+ }
+ None
+}
+
+fn cover_range(r0: Option<TextRange>, r1: Option<TextRange>) -> Option<TextRange> {
+ match (r0, r1) {
+ (Some(r0), Some(r1)) => Some(r0.cover(r1)),
+ (Some(range), None) => Some(range),
+ (None, Some(range)) => Some(range),
+ (None, None) => None,
+ }
+}
+
+fn find_defs(sema: &Semantics<'_, RootDatabase>, token: SyntaxToken) -> FxHashSet<Definition> {
+ sema.descend_into_macros(token)
+ .into_iter()
+ .filter_map(|token| IdentClass::classify_token(sema, &token))
+ .map(IdentClass::definitions_no_ops)
+ .flatten()
+ .collect()
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::fixture;
+
+ use super::*;
+
+ #[track_caller]
+ fn check(ra_fixture: &str) {
+ let config = HighlightRelatedConfig {
+ break_points: true,
+ exit_points: true,
+ references: true,
+ yield_points: true,
+ };
+
+ check_with_config(ra_fixture, config);
+ }
+
+ #[track_caller]
+ fn check_with_config(ra_fixture: &str, config: HighlightRelatedConfig) {
+ let (analysis, pos, annotations) = fixture::annotations(ra_fixture);
+
+ let hls = analysis.highlight_related(config, pos).unwrap().unwrap_or_default();
+
+ let mut expected = annotations
+ .into_iter()
+ .map(|(r, access)| (r.range, (!access.is_empty()).then(|| access)))
+ .collect::<Vec<_>>();
+
+ let mut actual = hls
+ .into_iter()
+ .map(|hl| {
+ (
+ hl.range,
+ hl.category.map(|it| {
+ match it {
+ ReferenceCategory::Read => "read",
+ ReferenceCategory::Write => "write",
+ ReferenceCategory::Import => "import",
+ }
+ .to_string()
+ }),
+ )
+ })
+ .collect::<Vec<_>>();
+ actual.sort_by_key(|(range, _)| range.start());
+ expected.sort_by_key(|(range, _)| range.start());
+
+ assert_eq!(expected, actual);
+ }
+
+ #[test]
+ fn test_hl_tuple_fields() {
+ check(
+ r#"
+struct Tuple(u32, u32);
+
+fn foo(t: Tuple) {
+ t.0$0;
+ // ^ read
+ t.0;
+ // ^ read
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_module() {
+ check(
+ r#"
+//- /lib.rs
+mod foo$0;
+ // ^^^
+//- /foo.rs
+struct Foo;
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_self_in_crate_root() {
+ check(
+ r#"
+use crate$0;
+ //^^^^^ import
+use self;
+ //^^^^ import
+mod __ {
+ use super;
+ //^^^^^ import
+}
+"#,
+ );
+ check(
+ r#"
+//- /main.rs crate:main deps:lib
+use lib$0;
+ //^^^ import
+//- /lib.rs crate:lib
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_self_in_module() {
+ check(
+ r#"
+//- /lib.rs
+mod foo;
+//- /foo.rs
+use self$0;
+ // ^^^^ import
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_local() {
+ check(
+ r#"
+fn foo() {
+ let mut bar = 3;
+ // ^^^ write
+ bar$0;
+ // ^^^ read
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_local_in_attr() {
+ check(
+ r#"
+//- proc_macros: identity
+#[proc_macros::identity]
+fn foo() {
+ let mut bar = 3;
+ // ^^^ write
+ bar$0;
+ // ^^^ read
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_multi_macro_usage() {
+ check(
+ r#"
+macro_rules! foo {
+ ($ident:ident) => {
+ fn $ident() -> $ident { loop {} }
+ struct $ident;
+ }
+}
+
+foo!(bar$0);
+ // ^^^
+fn foo() {
+ let bar: bar = bar();
+ // ^^^
+ // ^^^
+}
+"#,
+ );
+ check(
+ r#"
+macro_rules! foo {
+ ($ident:ident) => {
+ fn $ident() -> $ident { loop {} }
+ struct $ident;
+ }
+}
+
+foo!(bar);
+ // ^^^
+fn foo() {
+ let bar: bar$0 = bar();
+ // ^^^
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_yield_points() {
+ check(
+ r#"
+pub async fn foo() {
+ // ^^^^^
+ let x = foo()
+ .await$0
+ // ^^^^^
+ .await;
+ // ^^^^^
+ || { 0.await };
+ (async { 0.await }).await
+ // ^^^^^
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_yield_points2() {
+ check(
+ r#"
+pub async$0 fn foo() {
+ // ^^^^^
+ let x = foo()
+ .await
+ // ^^^^^
+ .await;
+ // ^^^^^
+ || { 0.await };
+ (async { 0.await }).await
+ // ^^^^^
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_yield_nested_fn() {
+ check(
+ r#"
+async fn foo() {
+ async fn foo2() {
+ // ^^^^^
+ async fn foo3() {
+ 0.await
+ }
+ 0.await$0
+ // ^^^^^
+ }
+ 0.await
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_yield_nested_async_blocks() {
+ check(
+ r#"
+async fn foo() {
+ (async {
+ // ^^^^^
+ (async {
+ 0.await
+ }).await$0 }
+ // ^^^^^
+ ).await;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_exit_points() {
+ check(
+ r#"
+fn foo() -> u32 {
+ if true {
+ return$0 0;
+ // ^^^^^^
+ }
+
+ 0?;
+ // ^
+ 0xDEAD_BEEF
+ // ^^^^^^^^^^^
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_exit_points2() {
+ check(
+ r#"
+fn foo() ->$0 u32 {
+ if true {
+ return 0;
+ // ^^^^^^
+ }
+
+ 0?;
+ // ^
+ 0xDEAD_BEEF
+ // ^^^^^^^^^^^
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_exit_points3() {
+ check(
+ r#"
+fn$0 foo() -> u32 {
+ if true {
+ return 0;
+ // ^^^^^^
+ }
+
+ 0?;
+ // ^
+ 0xDEAD_BEEF
+ // ^^^^^^^^^^^
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_prefer_ref_over_tail_exit() {
+ check(
+ r#"
+fn foo() -> u32 {
+// ^^^
+ if true {
+ return 0;
+ }
+
+ 0?;
+
+ foo$0()
+ // ^^^
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_never_call_is_exit_point() {
+ check(
+ r#"
+struct Never;
+impl Never {
+ fn never(self) -> ! { loop {} }
+}
+macro_rules! never {
+ () => { never() }
+}
+fn never() -> ! { loop {} }
+fn foo() ->$0 u32 {
+ never();
+ // ^^^^^^^
+ never!();
+ // ^^^^^^^^
+
+ Never.never();
+ // ^^^^^^^^^^^^^
+
+ 0
+ // ^
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_inner_tail_exit_points() {
+ check(
+ r#"
+fn foo() ->$0 u32 {
+ if true {
+ unsafe {
+ return 5;
+ // ^^^^^^
+ 5
+ // ^
+ }
+ } else if false {
+ 0
+ // ^
+ } else {
+ match 5 {
+ 6 => 100,
+ // ^^^
+ 7 => loop {
+ break 5;
+ // ^^^^^
+ }
+ 8 => 'a: loop {
+ 'b: loop {
+ break 'a 5;
+ // ^^^^^
+ break 'b 5;
+ break 5;
+ };
+ }
+ //
+ _ => 500,
+ // ^^^
+ }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_inner_tail_exit_points_labeled_block() {
+ check(
+ r#"
+fn foo() ->$0 u32 {
+ 'foo: {
+ break 'foo 0;
+ // ^^^^^
+ loop {
+ break;
+ break 'foo 0;
+ // ^^^^^
+ }
+ 0
+ // ^
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_break_loop() {
+ check(
+ r#"
+fn foo() {
+ 'outer: loop {
+ // ^^^^^^^^^^^^
+ break;
+ // ^^^^^
+ 'inner: loop {
+ break;
+ 'innermost: loop {
+ break 'outer;
+ // ^^^^^^^^^^^^
+ break 'inner;
+ }
+ break$0 'outer;
+ // ^^^^^^^^^^^^
+ break;
+ }
+ break;
+ // ^^^^^
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_break_loop2() {
+ check(
+ r#"
+fn foo() {
+ 'outer: loop {
+ break;
+ 'inner: loop {
+ // ^^^^^^^^^^^^
+ break;
+ // ^^^^^
+ 'innermost: loop {
+ break 'outer;
+ break 'inner;
+ // ^^^^^^^^^^^^
+ }
+ break 'outer;
+ break$0;
+ // ^^^^^
+ }
+ break;
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_break_for() {
+ check(
+ r#"
+fn foo() {
+ 'outer: for _ in () {
+ // ^^^^^^^^^^^
+ break;
+ // ^^^^^
+ 'inner: for _ in () {
+ break;
+ 'innermost: for _ in () {
+ break 'outer;
+ // ^^^^^^^^^^^^
+ break 'inner;
+ }
+ break$0 'outer;
+ // ^^^^^^^^^^^^
+ break;
+ }
+ break;
+ // ^^^^^
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_break_for_but_not_continue() {
+ check(
+ r#"
+fn foo() {
+ 'outer: for _ in () {
+ // ^^^^^^^^^^^
+ break;
+ // ^^^^^
+ continue;
+ 'inner: for _ in () {
+ break;
+ continue;
+ 'innermost: for _ in () {
+ continue 'outer;
+ break 'outer;
+ // ^^^^^^^^^^^^
+ continue 'inner;
+ break 'inner;
+ }
+ break$0 'outer;
+ // ^^^^^^^^^^^^
+ continue 'outer;
+ break;
+ continue;
+ }
+ break;
+ // ^^^^^
+ continue;
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_continue_for_but_not_break() {
+ check(
+ r#"
+fn foo() {
+ 'outer: for _ in () {
+ // ^^^^^^^^^^^
+ break;
+ continue;
+ // ^^^^^^^^
+ 'inner: for _ in () {
+ break;
+ continue;
+ 'innermost: for _ in () {
+ continue 'outer;
+ // ^^^^^^^^^^^^^^^
+ break 'outer;
+ continue 'inner;
+ break 'inner;
+ }
+ break 'outer;
+ continue$0 'outer;
+ // ^^^^^^^^^^^^^^^
+ break;
+ continue;
+ }
+ break;
+ continue;
+ // ^^^^^^^^
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_break_and_continue() {
+ check(
+ r#"
+fn foo() {
+ 'outer: fo$0r _ in () {
+ // ^^^^^^^^^^^
+ break;
+ // ^^^^^
+ continue;
+ // ^^^^^^^^
+ 'inner: for _ in () {
+ break;
+ continue;
+ 'innermost: for _ in () {
+ continue 'outer;
+ // ^^^^^^^^^^^^^^^
+ break 'outer;
+ // ^^^^^^^^^^^^
+ continue 'inner;
+ break 'inner;
+ }
+ break 'outer;
+ // ^^^^^^^^^^^^
+ continue 'outer;
+ // ^^^^^^^^^^^^^^^
+ break;
+ continue;
+ }
+ break;
+ // ^^^^^
+ continue;
+ // ^^^^^^^^
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_break_while() {
+ check(
+ r#"
+fn foo() {
+ 'outer: while true {
+ // ^^^^^^^^^^^^^
+ break;
+ // ^^^^^
+ 'inner: while true {
+ break;
+ 'innermost: while true {
+ break 'outer;
+ // ^^^^^^^^^^^^
+ break 'inner;
+ }
+ break$0 'outer;
+ // ^^^^^^^^^^^^
+ break;
+ }
+ break;
+ // ^^^^^
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_break_labeled_block() {
+ check(
+ r#"
+fn foo() {
+ 'outer: {
+ // ^^^^^^^
+ break;
+ // ^^^^^
+ 'inner: {
+ break;
+ 'innermost: {
+ break 'outer;
+ // ^^^^^^^^^^^^
+ break 'inner;
+ }
+ break$0 'outer;
+ // ^^^^^^^^^^^^
+ break;
+ }
+ break;
+ // ^^^^^
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_break_unlabeled_loop() {
+ check(
+ r#"
+fn foo() {
+ loop {
+ // ^^^^
+ break$0;
+ // ^^^^^
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_break_unlabeled_block_in_loop() {
+ check(
+ r#"
+fn foo() {
+ loop {
+ // ^^^^
+ {
+ break$0;
+ // ^^^^^
+ }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_field_shorthand() {
+ check(
+ r#"
+struct Struct { field: u32 }
+ //^^^^^
+fn function(field: u32) {
+ //^^^^^
+ Struct { field$0 }
+ //^^^^^ read
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_disabled_ref_local() {
+ let config = HighlightRelatedConfig {
+ references: false,
+ break_points: true,
+ exit_points: true,
+ yield_points: true,
+ };
+
+ check_with_config(
+ r#"
+fn foo() {
+ let x$0 = 5;
+ let y = x * 2;
+}
+"#,
+ config,
+ );
+ }
+
+ #[test]
+ fn test_hl_disabled_ref_local_preserved_break() {
+ let config = HighlightRelatedConfig {
+ references: false,
+ break_points: true,
+ exit_points: true,
+ yield_points: true,
+ };
+
+ check_with_config(
+ r#"
+fn foo() {
+ let x$0 = 5;
+ let y = x * 2;
+
+ loop {
+ break;
+ }
+}
+"#,
+ config.clone(),
+ );
+
+ check_with_config(
+ r#"
+fn foo() {
+ let x = 5;
+ let y = x * 2;
+
+ loop$0 {
+// ^^^^
+ break;
+// ^^^^^
+ }
+}
+"#,
+ config,
+ );
+ }
+
+ #[test]
+ fn test_hl_disabled_ref_local_preserved_yield() {
+ let config = HighlightRelatedConfig {
+ references: false,
+ break_points: true,
+ exit_points: true,
+ yield_points: true,
+ };
+
+ check_with_config(
+ r#"
+async fn foo() {
+ let x$0 = 5;
+ let y = x * 2;
+
+ 0.await;
+}
+"#,
+ config.clone(),
+ );
+
+ check_with_config(
+ r#"
+ async fn foo() {
+// ^^^^^
+ let x = 5;
+ let y = x * 2;
+
+ 0.await$0;
+// ^^^^^
+}
+"#,
+ config,
+ );
+ }
+
+ #[test]
+ fn test_hl_disabled_ref_local_preserved_exit() {
+ let config = HighlightRelatedConfig {
+ references: false,
+ break_points: true,
+ exit_points: true,
+ yield_points: true,
+ };
+
+ check_with_config(
+ r#"
+fn foo() -> i32 {
+ let x$0 = 5;
+ let y = x * 2;
+
+ if true {
+ return y;
+ }
+
+ 0?
+}
+"#,
+ config.clone(),
+ );
+
+ check_with_config(
+ r#"
+fn foo() ->$0 i32 {
+ let x = 5;
+ let y = x * 2;
+
+ if true {
+ return y;
+// ^^^^^^
+ }
+
+ 0?
+// ^
+"#,
+ config,
+ );
+ }
+
+ #[test]
+ fn test_hl_disabled_break() {
+ let config = HighlightRelatedConfig {
+ references: true,
+ break_points: false,
+ exit_points: true,
+ yield_points: true,
+ };
+
+ check_with_config(
+ r#"
+fn foo() {
+ loop {
+ break$0;
+ }
+}
+"#,
+ config,
+ );
+ }
+
+ #[test]
+ fn test_hl_disabled_yield() {
+ let config = HighlightRelatedConfig {
+ references: true,
+ break_points: true,
+ exit_points: true,
+ yield_points: false,
+ };
+
+ check_with_config(
+ r#"
+async$0 fn foo() {
+ 0.await;
+}
+"#,
+ config,
+ );
+ }
+
+ #[test]
+ fn test_hl_disabled_exit() {
+ let config = HighlightRelatedConfig {
+ references: true,
+ break_points: true,
+ exit_points: false,
+ yield_points: true,
+ };
+
+ check_with_config(
+ r#"
+fn foo() ->$0 i32 {
+ if true {
+ return -1;
+ }
+
+ 42
+}"#,
+ config,
+ );
+ }
+
+ #[test]
+ fn test_hl_multi_local() {
+ check(
+ r#"
+fn foo((
+ foo$0
+ //^^^
+ | foo
+ //^^^
+ | foo
+ //^^^
+): ()) {
+ foo;
+ //^^^read
+ let foo;
+}
+"#,
+ );
+ check(
+ r#"
+fn foo((
+ foo
+ //^^^
+ | foo$0
+ //^^^
+ | foo
+ //^^^
+): ()) {
+ foo;
+ //^^^read
+ let foo;
+}
+"#,
+ );
+ check(
+ r#"
+fn foo((
+ foo
+ //^^^
+ | foo
+ //^^^
+ | foo
+ //^^^
+): ()) {
+ foo$0;
+ //^^^read
+ let foo;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_hl_trait_impl_methods() {
+ check(
+ r#"
+trait Trait {
+ fn func$0(self) {}
+ //^^^^
+}
+
+impl Trait for () {
+ fn func(self) {}
+ //^^^^
+}
+
+fn main() {
+ <()>::func(());
+ //^^^^
+ ().func();
+ //^^^^
+}
+"#,
+ );
+ check(
+ r#"
+trait Trait {
+ fn func(self) {}
+ //^^^^
+}
+
+impl Trait for () {
+ fn func$0(self) {}
+ //^^^^
+}
+
+fn main() {
+ <()>::func(());
+ //^^^^
+ ().func();
+ //^^^^
+}
+"#,
+ );
+ check(
+ r#"
+trait Trait {
+ fn func(self) {}
+ //^^^^
+}
+
+impl Trait for () {
+ fn func(self) {}
+ //^^^^
+}
+
+fn main() {
+ <()>::func(());
+ //^^^^
+ ().func$0();
+ //^^^^
+}
++"#,
++ );
++ }
++
++ #[test]
++ fn test_assoc_type_highlighting() {
++ check(
++ r#"
++trait Trait {
++ type Output;
++ // ^^^^^^
++}
++impl Trait for () {
++ type Output$0 = ();
++ // ^^^^^^
++}
+"#,
+ );
+ }
+}
--- /dev/null
- Definition::Variant(it) => label_and_docs(db, it),
+//! Logic for rendering the different hover messages
+use std::fmt::Display;
+
+use either::Either;
+use hir::{AsAssocItem, AttributeTemplate, HasAttrs, HasSource, HirDisplay, Semantics, TypeInfo};
+use ide_db::{
+ base_db::SourceDatabase,
+ defs::Definition,
+ famous_defs::FamousDefs,
+ generated::lints::{CLIPPY_LINTS, DEFAULT_LINTS, FEATURES},
+ syntax_helpers::insert_whitespace_into_node,
+ RootDatabase,
+};
+use itertools::Itertools;
+use stdx::format_to;
+use syntax::{
+ algo, ast, match_ast, AstNode, Direction,
+ SyntaxKind::{LET_EXPR, LET_STMT},
+ SyntaxToken, T,
+};
+
+use crate::{
+ doc_links::{remove_links, rewrite_links},
+ hover::walk_and_push_ty,
+ markdown_remove::remove_markdown,
+ HoverAction, HoverConfig, HoverResult, Markup,
+};
+
+pub(super) fn type_info(
+ sema: &Semantics<'_, RootDatabase>,
+ config: &HoverConfig,
+ expr_or_pat: &Either<ast::Expr, ast::Pat>,
+) -> Option<HoverResult> {
+ let TypeInfo { original, adjusted } = match expr_or_pat {
+ Either::Left(expr) => sema.type_of_expr(expr)?,
+ Either::Right(pat) => sema.type_of_pat(pat)?,
+ };
+
+ let mut res = HoverResult::default();
+ let mut targets: Vec<hir::ModuleDef> = Vec::new();
+ let mut push_new_def = |item: hir::ModuleDef| {
+ if !targets.contains(&item) {
+ targets.push(item);
+ }
+ };
+ walk_and_push_ty(sema.db, &original, &mut push_new_def);
+
+ res.markup = if let Some(adjusted_ty) = adjusted {
+ walk_and_push_ty(sema.db, &adjusted_ty, &mut push_new_def);
+ let original = original.display(sema.db).to_string();
+ let adjusted = adjusted_ty.display(sema.db).to_string();
+ let static_text_diff_len = "Coerced to: ".len() - "Type: ".len();
+ format!(
+ "{bt_start}Type: {:>apad$}\nCoerced to: {:>opad$}\n{bt_end}",
+ original,
+ adjusted,
+ apad = static_text_diff_len + adjusted.len().max(original.len()),
+ opad = original.len(),
+ bt_start = if config.markdown() { "```text\n" } else { "" },
+ bt_end = if config.markdown() { "```\n" } else { "" }
+ )
+ .into()
+ } else {
+ if config.markdown() {
+ Markup::fenced_block(&original.display(sema.db))
+ } else {
+ original.display(sema.db).to_string().into()
+ }
+ };
+ res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
+ Some(res)
+}
+
+pub(super) fn try_expr(
+ sema: &Semantics<'_, RootDatabase>,
+ config: &HoverConfig,
+ try_expr: &ast::TryExpr,
+) -> Option<HoverResult> {
+ let inner_ty = sema.type_of_expr(&try_expr.expr()?)?.original;
+ let mut ancestors = try_expr.syntax().ancestors();
+ let mut body_ty = loop {
+ let next = ancestors.next()?;
+ break match_ast! {
+ match next {
+ ast::Fn(fn_) => sema.to_def(&fn_)?.ret_type(sema.db),
+ ast::Item(__) => return None,
+ ast::ClosureExpr(closure) => sema.type_of_expr(&closure.body()?)?.original,
+ ast::BlockExpr(block_expr) => if matches!(block_expr.modifier(), Some(ast::BlockModifier::Async(_) | ast::BlockModifier::Try(_)| ast::BlockModifier::Const(_))) {
+ sema.type_of_expr(&block_expr.into())?.original
+ } else {
+ continue;
+ },
+ _ => continue,
+ }
+ };
+ };
+
+ if inner_ty == body_ty {
+ return None;
+ }
+
+ let mut inner_ty = inner_ty;
+ let mut s = "Try Target".to_owned();
+
+ let adts = inner_ty.as_adt().zip(body_ty.as_adt());
+ if let Some((hir::Adt::Enum(inner), hir::Adt::Enum(body))) = adts {
+ let famous_defs = FamousDefs(sema, sema.scope(try_expr.syntax())?.krate());
+ // special case for two options, there is no value in showing them
+ if let Some(option_enum) = famous_defs.core_option_Option() {
+ if inner == option_enum && body == option_enum {
+ cov_mark::hit!(hover_try_expr_opt_opt);
+ return None;
+ }
+ }
+
+ // special case two results to show the error variants only
+ if let Some(result_enum) = famous_defs.core_result_Result() {
+ if inner == result_enum && body == result_enum {
+ let error_type_args =
+ inner_ty.type_arguments().nth(1).zip(body_ty.type_arguments().nth(1));
+ if let Some((inner, body)) = error_type_args {
+ inner_ty = inner;
+ body_ty = body;
+ s = "Try Error".to_owned();
+ }
+ }
+ }
+ }
+
+ let mut res = HoverResult::default();
+
+ let mut targets: Vec<hir::ModuleDef> = Vec::new();
+ let mut push_new_def = |item: hir::ModuleDef| {
+ if !targets.contains(&item) {
+ targets.push(item);
+ }
+ };
+ walk_and_push_ty(sema.db, &inner_ty, &mut push_new_def);
+ walk_and_push_ty(sema.db, &body_ty, &mut push_new_def);
+ res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
+
+ let inner_ty = inner_ty.display(sema.db).to_string();
+ let body_ty = body_ty.display(sema.db).to_string();
+ let ty_len_max = inner_ty.len().max(body_ty.len());
+
+ let l = "Propagated as: ".len() - " Type: ".len();
+ let static_text_len_diff = l as isize - s.len() as isize;
+ let tpad = static_text_len_diff.max(0) as usize;
+ let ppad = static_text_len_diff.min(0).abs() as usize;
+
+ res.markup = format!(
+ "{bt_start}{} Type: {:>pad0$}\nPropagated as: {:>pad1$}\n{bt_end}",
+ s,
+ inner_ty,
+ body_ty,
+ pad0 = ty_len_max + tpad,
+ pad1 = ty_len_max + ppad,
+ bt_start = if config.markdown() { "```text\n" } else { "" },
+ bt_end = if config.markdown() { "```\n" } else { "" }
+ )
+ .into();
+ Some(res)
+}
+
+pub(super) fn deref_expr(
+ sema: &Semantics<'_, RootDatabase>,
+ config: &HoverConfig,
+ deref_expr: &ast::PrefixExpr,
+) -> Option<HoverResult> {
+ let inner_ty = sema.type_of_expr(&deref_expr.expr()?)?.original;
+ let TypeInfo { original, adjusted } =
+ sema.type_of_expr(&ast::Expr::from(deref_expr.clone()))?;
+
+ let mut res = HoverResult::default();
+ let mut targets: Vec<hir::ModuleDef> = Vec::new();
+ let mut push_new_def = |item: hir::ModuleDef| {
+ if !targets.contains(&item) {
+ targets.push(item);
+ }
+ };
+ walk_and_push_ty(sema.db, &inner_ty, &mut push_new_def);
+ walk_and_push_ty(sema.db, &original, &mut push_new_def);
+
+ res.markup = if let Some(adjusted_ty) = adjusted {
+ walk_and_push_ty(sema.db, &adjusted_ty, &mut push_new_def);
+ let original = original.display(sema.db).to_string();
+ let adjusted = adjusted_ty.display(sema.db).to_string();
+ let inner = inner_ty.display(sema.db).to_string();
+ let type_len = "To type: ".len();
+ let coerced_len = "Coerced to: ".len();
+ let deref_len = "Dereferenced from: ".len();
+ let max_len = (original.len() + type_len)
+ .max(adjusted.len() + coerced_len)
+ .max(inner.len() + deref_len);
+ format!(
+ "{bt_start}Dereferenced from: {:>ipad$}\nTo type: {:>apad$}\nCoerced to: {:>opad$}\n{bt_end}",
+ inner,
+ original,
+ adjusted,
+ ipad = max_len - deref_len,
+ apad = max_len - type_len,
+ opad = max_len - coerced_len,
+ bt_start = if config.markdown() { "```text\n" } else { "" },
+ bt_end = if config.markdown() { "```\n" } else { "" }
+ )
+ .into()
+ } else {
+ let original = original.display(sema.db).to_string();
+ let inner = inner_ty.display(sema.db).to_string();
+ let type_len = "To type: ".len();
+ let deref_len = "Dereferenced from: ".len();
+ let max_len = (original.len() + type_len).max(inner.len() + deref_len);
+ format!(
+ "{bt_start}Dereferenced from: {:>ipad$}\nTo type: {:>apad$}\n{bt_end}",
+ inner,
+ original,
+ ipad = max_len - deref_len,
+ apad = max_len - type_len,
+ bt_start = if config.markdown() { "```text\n" } else { "" },
+ bt_end = if config.markdown() { "```\n" } else { "" }
+ )
+ .into()
+ };
+ res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
+
+ Some(res)
+}
+
+pub(super) fn keyword(
+ sema: &Semantics<'_, RootDatabase>,
+ config: &HoverConfig,
+ token: &SyntaxToken,
+) -> Option<HoverResult> {
+ if !token.kind().is_keyword() || !config.documentation.is_some() || !config.keywords {
+ return None;
+ }
+ let parent = token.parent()?;
+ let famous_defs = FamousDefs(sema, sema.scope(&parent)?.krate());
+
+ let KeywordHint { description, keyword_mod, actions } = keyword_hints(sema, token, parent);
+
+ let doc_owner = find_std_module(&famous_defs, &keyword_mod)?;
+ let docs = doc_owner.attrs(sema.db).docs()?;
+ let markup = process_markup(
+ sema.db,
+ Definition::Module(doc_owner),
+ &markup(Some(docs.into()), description, None)?,
+ config,
+ );
+ Some(HoverResult { markup, actions })
+}
+
+pub(super) fn try_for_lint(attr: &ast::Attr, token: &SyntaxToken) -> Option<HoverResult> {
+ let (path, tt) = attr.as_simple_call()?;
+ if !tt.syntax().text_range().contains(token.text_range().start()) {
+ return None;
+ }
+ let (is_clippy, lints) = match &*path {
+ "feature" => (false, FEATURES),
+ "allow" | "deny" | "forbid" | "warn" => {
+ let is_clippy = algo::non_trivia_sibling(token.clone().into(), Direction::Prev)
+ .filter(|t| t.kind() == T![:])
+ .and_then(|t| algo::non_trivia_sibling(t, Direction::Prev))
+ .filter(|t| t.kind() == T![:])
+ .and_then(|t| algo::non_trivia_sibling(t, Direction::Prev))
+ .map_or(false, |t| {
+ t.kind() == T![ident] && t.into_token().map_or(false, |t| t.text() == "clippy")
+ });
+ if is_clippy {
+ (true, CLIPPY_LINTS)
+ } else {
+ (false, DEFAULT_LINTS)
+ }
+ }
+ _ => return None,
+ };
+
+ let tmp;
+ let needle = if is_clippy {
+ tmp = format!("clippy::{}", token.text());
+ &tmp
+ } else {
+ &*token.text()
+ };
+
+ let lint =
+ lints.binary_search_by_key(&needle, |lint| lint.label).ok().map(|idx| &lints[idx])?;
+ Some(HoverResult {
+ markup: Markup::from(format!("```\n{}\n```\n___\n\n{}", lint.label, lint.description)),
+ ..Default::default()
+ })
+}
+
+pub(super) fn process_markup(
+ db: &RootDatabase,
+ def: Definition,
+ markup: &Markup,
+ config: &HoverConfig,
+) -> Markup {
+ let markup = markup.as_str();
+ let markup = if !config.markdown() {
+ remove_markdown(markup)
+ } else if config.links_in_hover {
+ rewrite_links(db, markup, def)
+ } else {
+ remove_links(markup)
+ };
+ Markup::from(markup)
+}
+
+fn definition_owner_name(db: &RootDatabase, def: &Definition) -> Option<String> {
+ match def {
+ Definition::Field(f) => Some(f.parent_def(db).name(db)),
+ Definition::Local(l) => l.parent(db).name(db),
+ Definition::Function(f) => match f.as_assoc_item(db)?.container(db) {
+ hir::AssocItemContainer::Trait(t) => Some(t.name(db)),
+ hir::AssocItemContainer::Impl(i) => i.self_ty(db).as_adt().map(|adt| adt.name(db)),
+ },
+ Definition::Variant(e) => Some(e.parent_enum(db).name(db)),
+ _ => None,
+ }
+ .map(|name| name.to_string())
+}
+
+pub(super) fn path(db: &RootDatabase, module: hir::Module, item_name: Option<String>) -> String {
+ let crate_name =
+ db.crate_graph()[module.krate().into()].display_name.as_ref().map(|it| it.to_string());
+ let module_path = module
+ .path_to_root(db)
+ .into_iter()
+ .rev()
+ .flat_map(|it| it.name(db).map(|name| name.to_string()));
+ crate_name.into_iter().chain(module_path).chain(item_name).join("::")
+}
+
+pub(super) fn definition(
+ db: &RootDatabase,
+ def: Definition,
+ famous_defs: Option<&FamousDefs<'_, '_>>,
+ config: &HoverConfig,
+) -> Option<Markup> {
+ let mod_path = definition_mod_path(db, &def);
+ let (label, docs) = match def {
+ Definition::Macro(it) => label_and_docs(db, it),
+ Definition::Field(it) => label_and_docs(db, it),
+ Definition::Module(it) => label_and_docs(db, it),
+ Definition::Function(it) => label_and_docs(db, it),
+ Definition::Adt(it) => label_and_docs(db, it),
++ Definition::Variant(it) => label_value_and_docs(db, it, |&it| {
++ if !it.parent_enum(db).is_data_carrying(db) {
++ match it.eval(db) {
++ Ok(x) => Some(format!("{}", x)),
++ Err(_) => it.value(db).map(|x| format!("{:?}", x)),
++ }
++ } else {
++ None
++ }
++ }),
+ Definition::Const(it) => label_value_and_docs(db, it, |it| {
+ let body = it.eval(db);
+ match body {
+ Ok(x) => Some(format!("{}", x)),
+ Err(_) => {
+ let source = it.source(db)?;
+ let mut body = source.value.body()?.syntax().clone();
+ if source.file_id.is_macro() {
+ body = insert_whitespace_into_node::insert_ws_into(body);
+ }
+ Some(body.to_string())
+ }
+ }
+ }),
+ Definition::Static(it) => label_value_and_docs(db, it, |it| {
+ let source = it.source(db)?;
+ let mut body = source.value.body()?.syntax().clone();
+ if source.file_id.is_macro() {
+ body = insert_whitespace_into_node::insert_ws_into(body);
+ }
+ Some(body.to_string())
+ }),
+ Definition::Trait(it) => label_and_docs(db, it),
+ Definition::TypeAlias(it) => label_and_docs(db, it),
+ Definition::BuiltinType(it) => {
+ return famous_defs
+ .and_then(|fd| builtin(fd, it))
+ .or_else(|| Some(Markup::fenced_block(&it.name())))
+ }
+ Definition::Local(it) => return local(db, it),
+ Definition::SelfType(impl_def) => {
+ impl_def.self_ty(db).as_adt().map(|adt| label_and_docs(db, adt))?
+ }
+ Definition::GenericParam(it) => label_and_docs(db, it),
+ Definition::Label(it) => return Some(Markup::fenced_block(&it.name(db))),
+ // FIXME: We should be able to show more info about these
+ Definition::BuiltinAttr(it) => return render_builtin_attr(db, it),
+ Definition::ToolModule(it) => return Some(Markup::fenced_block(&it.name(db))),
+ Definition::DeriveHelper(it) => (format!("derive_helper {}", it.name(db)), None),
+ };
+
+ let docs = match config.documentation {
+ Some(_) => docs.or_else(|| {
+ // docs are missing, for assoc items of trait impls try to fall back to the docs of the
+ // original item of the trait
+ let assoc = def.as_assoc_item(db)?;
+ let trait_ = assoc.containing_trait_impl(db)?;
+ let name = Some(assoc.name(db)?);
+ let item = trait_.items(db).into_iter().find(|it| it.name(db) == name)?;
+ item.docs(db)
+ }),
+ None => None,
+ };
+ let docs = docs.filter(|_| config.documentation.is_some()).map(Into::into);
+ markup(docs, label, mod_path)
+}
+
+fn render_builtin_attr(db: &RootDatabase, attr: hir::BuiltinAttr) -> Option<Markup> {
+ let name = attr.name(db);
+ let desc = format!("#[{}]", name);
+
+ let AttributeTemplate { word, list, name_value_str } = match attr.template(db) {
+ Some(template) => template,
+ None => return Some(Markup::fenced_block(&attr.name(db))),
+ };
+ let mut docs = "Valid forms are:".to_owned();
+ if word {
+ format_to!(docs, "\n - #\\[{}]", name);
+ }
+ if let Some(list) = list {
+ format_to!(docs, "\n - #\\[{}({})]", name, list);
+ }
+ if let Some(name_value_str) = name_value_str {
+ format_to!(docs, "\n - #\\[{} = {}]", name, name_value_str);
+ }
+ markup(Some(docs.replace('*', "\\*")), desc, None)
+}
+
+fn label_and_docs<D>(db: &RootDatabase, def: D) -> (String, Option<hir::Documentation>)
+where
+ D: HasAttrs + HirDisplay,
+{
+ let label = def.display(db).to_string();
+ let docs = def.attrs(db).docs();
+ (label, docs)
+}
+
+fn label_value_and_docs<D, E, V>(
+ db: &RootDatabase,
+ def: D,
+ value_extractor: E,
+) -> (String, Option<hir::Documentation>)
+where
+ D: HasAttrs + HirDisplay,
+ E: Fn(&D) -> Option<V>,
+ V: Display,
+{
+ let label = if let Some(value) = value_extractor(&def) {
+ format!("{} = {}", def.display(db), value)
+ } else {
+ def.display(db).to_string()
+ };
+ let docs = def.attrs(db).docs();
+ (label, docs)
+}
+
+fn definition_mod_path(db: &RootDatabase, def: &Definition) -> Option<String> {
+ if let Definition::GenericParam(_) = def {
+ return None;
+ }
+ def.module(db).map(|module| path(db, module, definition_owner_name(db, def)))
+}
+
+fn markup(docs: Option<String>, desc: String, mod_path: Option<String>) -> Option<Markup> {
+ let mut buf = String::new();
+
+ if let Some(mod_path) = mod_path {
+ if !mod_path.is_empty() {
+ format_to!(buf, "```rust\n{}\n```\n\n", mod_path);
+ }
+ }
+ format_to!(buf, "```rust\n{}\n```", desc);
+
+ if let Some(doc) = docs {
+ format_to!(buf, "\n___\n\n{}", doc);
+ }
+ Some(buf.into())
+}
+
+fn builtin(famous_defs: &FamousDefs<'_, '_>, builtin: hir::BuiltinType) -> Option<Markup> {
+ // std exposes prim_{} modules with docstrings on the root to document the builtins
+ let primitive_mod = format!("prim_{}", builtin.name());
+ let doc_owner = find_std_module(famous_defs, &primitive_mod)?;
+ let docs = doc_owner.attrs(famous_defs.0.db).docs()?;
+ markup(Some(docs.into()), builtin.name().to_string(), None)
+}
+
+fn find_std_module(famous_defs: &FamousDefs<'_, '_>, name: &str) -> Option<hir::Module> {
+ let db = famous_defs.0.db;
+ let std_crate = famous_defs.std()?;
+ let std_root_module = std_crate.root_module(db);
+ std_root_module
+ .children(db)
+ .find(|module| module.name(db).map_or(false, |module| module.to_string() == name))
+}
+
+fn local(db: &RootDatabase, it: hir::Local) -> Option<Markup> {
+ let ty = it.ty(db);
+ let ty = ty.display_truncated(db, None);
+ let is_mut = if it.is_mut(db) { "mut " } else { "" };
+ let desc = match it.source(db).value {
+ Either::Left(ident) => {
+ let name = it.name(db);
+ let let_kw = if ident
+ .syntax()
+ .parent()
+ .map_or(false, |p| p.kind() == LET_STMT || p.kind() == LET_EXPR)
+ {
+ "let "
+ } else {
+ ""
+ };
+ format!("{}{}{}: {}", let_kw, is_mut, name, ty)
+ }
+ Either::Right(_) => format!("{}self: {}", is_mut, ty),
+ };
+ markup(None, desc, None)
+}
+
+struct KeywordHint {
+ description: String,
+ keyword_mod: String,
+ actions: Vec<HoverAction>,
+}
+
+impl KeywordHint {
+ fn new(description: String, keyword_mod: String) -> Self {
+ Self { description, keyword_mod, actions: Vec::default() }
+ }
+}
+
+fn keyword_hints(
+ sema: &Semantics<'_, RootDatabase>,
+ token: &SyntaxToken,
+ parent: syntax::SyntaxNode,
+) -> KeywordHint {
+ match token.kind() {
+ T![await] | T![loop] | T![match] | T![unsafe] | T![as] | T![try] | T![if] | T![else] => {
+ let keyword_mod = format!("{}_keyword", token.text());
+
+ match ast::Expr::cast(parent).and_then(|site| sema.type_of_expr(&site)) {
+ // ignore the unit type ()
+ Some(ty) if !ty.adjusted.as_ref().unwrap_or(&ty.original).is_unit() => {
+ let mut targets: Vec<hir::ModuleDef> = Vec::new();
+ let mut push_new_def = |item: hir::ModuleDef| {
+ if !targets.contains(&item) {
+ targets.push(item);
+ }
+ };
+ walk_and_push_ty(sema.db, &ty.original, &mut push_new_def);
+
+ let ty = ty.adjusted();
+ let description = format!("{}: {}", token.text(), ty.display(sema.db));
+
+ KeywordHint {
+ description,
+ keyword_mod,
+ actions: vec![HoverAction::goto_type_from_targets(sema.db, targets)],
+ }
+ }
+ _ => KeywordHint {
+ description: token.text().to_string(),
+ keyword_mod,
+ actions: Vec::new(),
+ },
+ }
+ }
+ T![fn] => {
+ let module = match ast::FnPtrType::cast(parent) {
+ // treat fn keyword inside function pointer type as primitive
+ Some(_) => format!("prim_{}", token.text()),
+ None => format!("{}_keyword", token.text()),
+ };
+ KeywordHint::new(token.text().to_string(), module)
+ }
+ T![Self] => KeywordHint::new(token.text().to_string(), "self_upper_keyword".into()),
+ _ => KeywordHint::new(token.text().to_string(), format!("{}_keyword", token.text())),
+ }
+}
--- /dev/null
+use expect_test::{expect, Expect};
+use ide_db::base_db::{FileLoader, FileRange};
+use syntax::TextRange;
+
+use crate::{fixture, hover::HoverDocFormat, HoverConfig};
+
+fn check_hover_no_result(ra_fixture: &str) {
+ let (analysis, position) = fixture::position(ra_fixture);
+ let hover = analysis
+ .hover(
+ &HoverConfig {
+ links_in_hover: true,
+ documentation: Some(HoverDocFormat::Markdown),
+ keywords: true,
+ },
+ FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
+ )
+ .unwrap();
+ assert!(hover.is_none(), "hover not expected but found: {:?}", hover.unwrap());
+}
+
+#[track_caller]
+fn check(ra_fixture: &str, expect: Expect) {
+ let (analysis, position) = fixture::position(ra_fixture);
+ let hover = analysis
+ .hover(
+ &HoverConfig {
+ links_in_hover: true,
+ documentation: Some(HoverDocFormat::Markdown),
+ keywords: true,
+ },
+ FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
+ )
+ .unwrap()
+ .unwrap();
+
+ let content = analysis.db.file_text(position.file_id);
+ let hovered_element = &content[hover.range];
+
+ let actual = format!("*{}*\n{}\n", hovered_element, hover.info.markup);
+ expect.assert_eq(&actual)
+}
+
+fn check_hover_no_links(ra_fixture: &str, expect: Expect) {
+ let (analysis, position) = fixture::position(ra_fixture);
+ let hover = analysis
+ .hover(
+ &HoverConfig {
+ links_in_hover: false,
+ documentation: Some(HoverDocFormat::Markdown),
+ keywords: true,
+ },
+ FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
+ )
+ .unwrap()
+ .unwrap();
+
+ let content = analysis.db.file_text(position.file_id);
+ let hovered_element = &content[hover.range];
+
+ let actual = format!("*{}*\n{}\n", hovered_element, hover.info.markup);
+ expect.assert_eq(&actual)
+}
+
+fn check_hover_no_markdown(ra_fixture: &str, expect: Expect) {
+ let (analysis, position) = fixture::position(ra_fixture);
+ let hover = analysis
+ .hover(
+ &HoverConfig {
+ links_in_hover: true,
+ documentation: Some(HoverDocFormat::PlainText),
+ keywords: true,
+ },
+ FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
+ )
+ .unwrap()
+ .unwrap();
+
+ let content = analysis.db.file_text(position.file_id);
+ let hovered_element = &content[hover.range];
+
+ let actual = format!("*{}*\n{}\n", hovered_element, hover.info.markup);
+ expect.assert_eq(&actual)
+}
+
+fn check_actions(ra_fixture: &str, expect: Expect) {
+ let (analysis, file_id, position) = fixture::range_or_position(ra_fixture);
+ let hover = analysis
+ .hover(
+ &HoverConfig {
+ links_in_hover: true,
+ documentation: Some(HoverDocFormat::Markdown),
+ keywords: true,
+ },
+ FileRange { file_id, range: position.range_or_empty() },
+ )
+ .unwrap()
+ .unwrap();
+ expect.assert_debug_eq(&hover.info.actions)
+}
+
+fn check_hover_range(ra_fixture: &str, expect: Expect) {
+ let (analysis, range) = fixture::range(ra_fixture);
+ let hover = analysis
+ .hover(
+ &HoverConfig {
+ links_in_hover: false,
+ documentation: Some(HoverDocFormat::Markdown),
+ keywords: true,
+ },
+ range,
+ )
+ .unwrap()
+ .unwrap();
+ expect.assert_eq(hover.info.markup.as_str())
+}
+
+fn check_hover_range_no_results(ra_fixture: &str) {
+ let (analysis, range) = fixture::range(ra_fixture);
+ let hover = analysis
+ .hover(
+ &HoverConfig {
+ links_in_hover: false,
+ documentation: Some(HoverDocFormat::Markdown),
+ keywords: true,
+ },
+ range,
+ )
+ .unwrap();
+ assert!(hover.is_none());
+}
+
+#[test]
+fn hover_descend_macros_avoids_duplicates() {
+ check(
+ r#"
+macro_rules! dupe_use {
+ ($local:ident) => {
+ {
+ $local;
+ $local;
+ }
+ }
+}
+fn foo() {
+ let local = 0;
+ dupe_use!(local$0);
+}
+"#,
+ expect![[r#"
+ *local*
+
+ ```rust
+ let local: i32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_shows_all_macro_descends() {
+ check(
+ r#"
+macro_rules! m {
+ ($name:ident) => {
+ /// Outer
+ fn $name() {}
+
+ mod module {
+ /// Inner
+ fn $name() {}
+ }
+ };
+}
+
+m!(ab$0c);
+ "#,
+ expect![[r#"
+ *abc*
+
+ ```rust
+ test::module
+ ```
+
+ ```rust
+ fn abc()
+ ```
+
+ ---
+
+ Inner
+ ---
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ fn abc()
+ ```
+
+ ---
+
+ Outer
+ "#]],
+ );
+}
+
+#[test]
+fn hover_shows_type_of_an_expression() {
+ check(
+ r#"
+pub fn foo() -> u32 { 1 }
+
+fn main() {
+ let foo_test = foo()$0;
+}
+"#,
+ expect![[r#"
+ *foo()*
+ ```rust
+ u32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_remove_markdown_if_configured() {
+ check_hover_no_markdown(
+ r#"
+pub fn foo() -> u32 { 1 }
+
+fn main() {
+ let foo_test = foo()$0;
+}
+"#,
+ expect![[r#"
+ *foo()*
+ u32
+ "#]],
+ );
+}
+
+#[test]
+fn hover_shows_long_type_of_an_expression() {
+ check(
+ r#"
+struct Scan<A, B, C> { a: A, b: B, c: C }
+struct Iter<I> { inner: I }
+enum Option<T> { Some(T), None }
+
+struct OtherStruct<T> { i: T }
+
+fn scan<A, B, C>(a: A, b: B, c: C) -> Iter<Scan<OtherStruct<A>, B, C>> {
+ Iter { inner: Scan { a, b, c } }
+}
+
+fn main() {
+ let num: i32 = 55;
+ let closure = |memo: &mut u32, value: &u32, _another: &mut u32| -> Option<u32> {
+ Option::Some(*memo + value)
+ };
+ let number = 5u32;
+ let mut iter$0 = scan(OtherStruct { i: num }, closure, number);
+}
+"#,
+ expect![[r#"
+ *iter*
+
+ ```rust
+ let mut iter: Iter<Scan<OtherStruct<OtherStruct<i32>>, |&mut u32, &u32, &mut u32| -> Option<u32>, u32>>
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_shows_fn_signature() {
+ // Single file with result
+ check(
+ r#"
+pub fn foo() -> u32 { 1 }
+
+fn main() { let foo_test = fo$0o(); }
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub fn foo() -> u32
+ ```
+ "#]],
+ );
+
+ // Multiple candidates but results are ambiguous.
+ check(
+ r#"
+//- /a.rs
+pub fn foo() -> u32 { 1 }
+
+//- /b.rs
+pub fn foo() -> &str { "" }
+
+//- /c.rs
+pub fn foo(a: u32, b: u32) {}
+
+//- /main.rs
+mod a;
+mod b;
+mod c;
+
+fn main() { let foo_test = fo$0o(); }
+ "#,
+ expect![[r#"
+ *foo*
+ ```rust
+ {unknown}
+ ```
+ "#]],
+ );
+
+ // Use literal `crate` in path
+ check(
+ r#"
+pub struct X;
+
+fn foo() -> crate::X { X }
+
+fn main() { f$0oo(); }
+ "#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ fn foo() -> crate::X
+ ```
+ "#]],
+ );
+
+ // Check `super` in path
+ check(
+ r#"
+pub struct X;
+
+mod m { pub fn foo() -> super::X { super::X } }
+
+fn main() { m::f$0oo(); }
+ "#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test::m
+ ```
+
+ ```rust
+ pub fn foo() -> super::X
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_omits_unnamed_where_preds() {
+ check(
+ r#"
+pub fn foo(bar: impl T) { }
+
+fn main() { fo$0o(); }
+ "#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub fn foo(bar: impl T)
+ ```
+ "#]],
+ );
+ check(
+ r#"
+pub fn foo<V: AsRef<str>>(bar: impl T, baz: V) { }
+
+fn main() { fo$0o(); }
+ "#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub fn foo<V>(bar: impl T, baz: V)
+ where
+ V: AsRef<str>,
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_shows_fn_signature_with_type_params() {
+ check(
+ r#"
+pub fn foo<'a, T: AsRef<str>>(b: &'a T) -> &'a str { }
+
+fn main() { let foo_test = fo$0o(); }
+ "#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub fn foo<'a, T>(b: &'a T) -> &'a str
+ where
+ T: AsRef<str>,
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_shows_fn_signature_on_fn_name() {
+ check(
+ r#"
+pub fn foo$0(a: u32, b: u32) -> u32 {}
+
+fn main() { }
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub fn foo(a: u32, b: u32) -> u32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_shows_fn_doc() {
+ check(
+ r#"
+/// # Example
+/// ```
+/// # use std::path::Path;
+/// #
+/// foo(Path::new("hello, world!"))
+/// ```
+pub fn foo$0(_: &Path) {}
+
+fn main() { }
+"#,
+ expect![[r##"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub fn foo(_: &Path)
+ ```
+
+ ---
+
+ # Example
+
+ ```
+ # use std::path::Path;
+ #
+ foo(Path::new("hello, world!"))
+ ```
+ "##]],
+ );
+}
+
+#[test]
+fn hover_shows_fn_doc_attr_raw_string() {
+ check(
+ r##"
+#[doc = r#"Raw string doc attr"#]
+pub fn foo$0(_: &Path) {}
+
+fn main() { }
+"##,
+ expect![[r##"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub fn foo(_: &Path)
+ ```
+
+ ---
+
+ Raw string doc attr
+ "##]],
+ );
+}
+
+#[test]
+fn hover_shows_struct_field_info() {
+ // Hovering over the field when instantiating
+ check(
+ r#"
+struct Foo { field_a: u32 }
+
+fn main() {
+ let foo = Foo { field_a$0: 0, };
+}
+"#,
+ expect![[r#"
+ *field_a*
+
+ ```rust
+ test::Foo
+ ```
+
+ ```rust
+ field_a: u32
+ ```
+ "#]],
+ );
+
+ // Hovering over the field in the definition
+ check(
+ r#"
+struct Foo { field_a$0: u32 }
+
+fn main() {
+ let foo = Foo { field_a: 0 };
+}
+"#,
+ expect![[r#"
+ *field_a*
+
+ ```rust
+ test::Foo
+ ```
+
+ ```rust
+ field_a: u32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_const_static() {
+ check(
+ r#"const foo$0: u32 = 123;"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const foo: u32 = 123 (0x7B)
+ ```
+ "#]],
+ );
+ check(
+ r#"
+const foo$0: u32 = {
+ let x = foo();
+ x + 100
+};"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const foo: u32 = {
+ let x = foo();
+ x + 100
+ }
+ ```
+ "#]],
+ );
+
+ check(
+ r#"static foo$0: u32 = 456;"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ static foo: u32 = 456
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_default_generic_types() {
+ check(
+ r#"
+struct Test<K, T = u8> { k: K, t: T }
+
+fn main() {
+ let zz$0 = Test { t: 23u8, k: 33 };
+}"#,
+ expect![[r#"
+ *zz*
+
+ ```rust
+ let zz: Test<i32>
+ ```
+ "#]],
+ );
+ check_hover_range(
+ r#"
+struct Test<K, T = u8> { k: K, t: T }
+
+fn main() {
+ let $0zz$0 = Test { t: 23u8, k: 33 };
+}"#,
+ expect![[r#"
+ ```rust
+ Test<i32, u8>
+ ```"#]],
+ );
+}
+
+#[test]
+fn hover_some() {
+ check(
+ r#"
+enum Option<T> { Some(T) }
+use Option::Some;
+
+fn main() { So$0me(12); }
+"#,
+ expect![[r#"
+ *Some*
+
+ ```rust
+ test::Option
+ ```
+
+ ```rust
+ Some(T)
+ ```
+ "#]],
+ );
+
+ check(
+ r#"
+enum Option<T> { Some(T) }
+use Option::Some;
+
+fn main() { let b$0ar = Some(12); }
+"#,
+ expect![[r#"
+ *bar*
+
+ ```rust
+ let bar: Option<i32>
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_enum_variant() {
+ check(
+ r#"
+enum Option<T> {
++ Some(T)
+ /// The None variant
+ Non$0e
+}
+"#,
+ expect![[r#"
+ *None*
+
+ ```rust
+ test::Option
+ ```
+
+ ```rust
+ None
+ ```
+
+ ---
+
+ The None variant
+ "#]],
+ );
+
+ check(
+ r#"
+enum Option<T> {
+ /// The Some variant
+ Some(T)
+}
+fn main() {
+ let s = Option::Som$0e(12);
+}
+"#,
+ expect![[r#"
+ *Some*
+
+ ```rust
+ test::Option
+ ```
+
+ ```rust
+ Some(T)
+ ```
+
+ ---
+
+ The Some variant
+ "#]],
+ );
+}
+
+#[test]
+fn hover_for_local_variable() {
+ check(
+ r#"fn func(foo: i32) { fo$0o; }"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ foo: i32
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn hover_for_local_variable_pat() {
+ check(
+ r#"fn func(fo$0o: i32) {}"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ foo: i32
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn hover_local_var_edge() {
+ check(
+ r#"fn func(foo: i32) { if true { $0foo; }; }"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ foo: i32
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn hover_for_param_edge() {
+ check(
+ r#"fn func($0foo: i32) {}"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ foo: i32
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn hover_for_param_with_multiple_traits() {
+ check(
+ r#"
+ //- minicore: sized
+ trait Deref {
+ type Target: ?Sized;
+ }
+ trait DerefMut {
+ type Target: ?Sized;
+ }
+ fn f(_x$0: impl Deref<Target=u8> + DerefMut<Target=u8>) {}"#,
+ expect![[r#"
+ *_x*
+
+ ```rust
+ _x: impl Deref<Target = u8> + DerefMut<Target = u8>
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn test_hover_infer_associated_method_result() {
+ check(
+ r#"
+struct Thing { x: u32 }
+
+impl Thing {
+ fn new() -> Thing { Thing { x: 0 } }
+}
+
+fn main() { let foo_$0test = Thing::new(); }
+"#,
+ expect![[r#"
+ *foo_test*
+
+ ```rust
+ let foo_test: Thing
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn test_hover_infer_associated_method_exact() {
+ check(
+ r#"
+mod wrapper {
+ pub struct Thing { x: u32 }
+
+ impl Thing {
+ pub fn new() -> Thing { Thing { x: 0 } }
+ }
+}
+
+fn main() { let foo_test = wrapper::Thing::new$0(); }
+"#,
+ expect![[r#"
+ *new*
+
+ ```rust
+ test::wrapper::Thing
+ ```
+
+ ```rust
+ pub fn new() -> Thing
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn test_hover_infer_associated_const_in_pattern() {
+ check(
+ r#"
+struct X;
+impl X {
+ const C: u32 = 1;
+}
+
+fn main() {
+ match 1 {
+ X::C$0 => {},
+ 2 => {},
+ _ => {}
+ };
+}
+"#,
+ expect![[r#"
+ *C*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const C: u32 = 1
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn test_hover_self() {
+ check(
+ r#"
+struct Thing { x: u32 }
+impl Thing {
+ fn new() -> Self { Self$0 { x: 0 } }
+}
+"#,
+ expect![[r#"
+ *Self*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ struct Thing
+ ```
+ "#]],
+ );
+ check(
+ r#"
+struct Thing { x: u32 }
+impl Thing {
+ fn new() -> Self$0 { Self { x: 0 } }
+}
+"#,
+ expect![[r#"
+ *Self*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ struct Thing
+ ```
+ "#]],
+ );
+ check(
+ r#"
+enum Thing { A }
+impl Thing {
+ pub fn new() -> Self$0 { Thing::A }
+}
+"#,
+ expect![[r#"
+ *Self*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ enum Thing
+ ```
+ "#]],
+ );
+ check(
+ r#"
+ enum Thing { A }
+ impl Thing {
+ pub fn thing(a: Self$0) {}
+ }
+ "#,
+ expect![[r#"
+ *Self*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ enum Thing
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_shadowing_pat() {
+ check(
+ r#"
+fn x() {}
+
+fn y() {
+ let x = 0i32;
+ x$0;
+}
+"#,
+ expect![[r#"
+ *x*
+
+ ```rust
+ let x: i32
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn test_hover_macro_invocation() {
+ check(
+ r#"
+macro_rules! foo { () => {} }
+
+fn f() { fo$0o!(); }
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ macro_rules! foo
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn test_hover_macro2_invocation() {
+ check(
+ r#"
+/// foo bar
+///
+/// foo bar baz
+macro foo() {}
+
+fn f() { fo$0o!(); }
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ macro foo
+ ```
+
+ ---
+
+ foo bar
+
+ foo bar baz
+ "#]],
+ )
+}
+
+#[test]
+fn test_hover_tuple_field() {
+ check(
+ r#"struct TS(String, i32$0);"#,
+ expect![[r#"
+ *i32*
+
+ ```rust
+ i32
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn test_hover_through_macro() {
+ check(
+ r#"
+macro_rules! id { ($($tt:tt)*) => { $($tt)* } }
+fn foo() {}
+id! {
+ fn bar() { fo$0o(); }
+}
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ fn foo()
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_through_attr() {
+ check(
+ r#"
+//- proc_macros: identity
+#[proc_macros::identity]
+fn foo$0() {}
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ fn foo()
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_through_expr_in_macro() {
+ check(
+ r#"
+macro_rules! id { ($($tt:tt)*) => { $($tt)* } }
+fn foo(bar:u32) { let a = id!(ba$0r); }
+"#,
+ expect![[r#"
+ *bar*
+
+ ```rust
+ bar: u32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_through_expr_in_macro_recursive() {
+ check(
+ r#"
+macro_rules! id_deep { ($($tt:tt)*) => { $($tt)* } }
+macro_rules! id { ($($tt:tt)*) => { id_deep!($($tt)*) } }
+fn foo(bar:u32) { let a = id!(ba$0r); }
+"#,
+ expect![[r#"
+ *bar*
+
+ ```rust
+ bar: u32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_through_func_in_macro_recursive() {
+ check(
+ r#"
+macro_rules! id_deep { ($($tt:tt)*) => { $($tt)* } }
+macro_rules! id { ($($tt:tt)*) => { id_deep!($($tt)*) } }
+fn bar() -> u32 { 0 }
+fn foo() { let a = id!([0u32, bar($0)] ); }
+"#,
+ expect![[r#"
+ *bar()*
+ ```rust
+ u32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_through_literal_string_in_macro() {
+ check(
+ r#"
+macro_rules! arr { ($($tt:tt)*) => { [$($tt)*] } }
+fn foo() {
+ let mastered_for_itunes = "";
+ let _ = arr!("Tr$0acks", &mastered_for_itunes);
+}
+"#,
+ expect![[r#"
+ *"Tracks"*
+ ```rust
+ &str
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_through_assert_macro() {
+ check(
+ r#"
+#[rustc_builtin_macro]
+macro_rules! assert {}
+
+fn bar() -> bool { true }
+fn foo() {
+ assert!(ba$0r());
+}
+"#,
+ expect![[r#"
+ *bar*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ fn bar() -> bool
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_multiple_actions() {
+ check_actions(
+ r#"
+struct Bar;
+struct Foo { bar: Bar }
+
+fn foo(Foo { b$0ar }: &Foo) {}
+ "#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Bar",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..11,
+ focus_range: 7..10,
+ name: "Bar",
+ kind: Struct,
+ description: "struct Bar",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ )
+}
+
+#[test]
+fn test_hover_through_literal_string_in_builtin_macro() {
+ check_hover_no_result(
+ r#"
+ #[rustc_builtin_macro]
+ macro_rules! format {}
+
+ fn foo() {
+ format!("hel$0lo {}", 0);
+ }
+"#,
+ );
+}
+
+#[test]
+fn test_hover_non_ascii_space_doc() {
+ check(
+ "
+/// <- `\u{3000}` here
+fn foo() { }
+
+fn bar() { fo$0o(); }
+",
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ fn foo()
+ ```
+
+ ---
+
+ \<- ` ` here
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_function_show_qualifiers() {
+ check(
+ r#"async fn foo$0() {}"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ async fn foo()
+ ```
+ "#]],
+ );
+ check(
+ r#"pub const unsafe fn foo$0() {}"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub const unsafe fn foo()
+ ```
+ "#]],
+ );
+ // Top level `pub(crate)` will be displayed as no visibility.
+ check(
+ r#"mod m { pub(crate) async unsafe extern "C" fn foo$0() {} }"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test::m
+ ```
+
+ ```rust
+ pub(crate) async unsafe extern "C" fn foo()
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_function_show_types() {
+ check(
+ r#"fn foo$0(a: i32, b:i32) -> i32 { 0 }"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ fn foo(a: i32, b: i32) -> i32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_function_pointer_show_identifiers() {
+ check(
+ r#"type foo$0 = fn(a: i32, b: i32) -> i32;"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ type foo = fn(a: i32, b: i32) -> i32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_function_pointer_no_identifier() {
+ check(
+ r#"type foo$0 = fn(i32, _: i32) -> i32;"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ type foo = fn(i32, i32) -> i32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_trait_show_qualifiers() {
+ check_actions(
+ r"unsafe trait foo$0() {}",
+ expect![[r#"
+ [
+ Implementation(
+ FilePosition {
+ file_id: FileId(
+ 0,
+ ),
+ offset: 13,
+ },
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_extern_crate() {
+ check(
+ r#"
+//- /main.rs crate:main deps:std
+extern crate st$0d;
+//- /std/lib.rs crate:std
+//! Standard library for this test
+//!
+//! Printed?
+//! abc123
+"#,
+ expect![[r#"
+ *std*
+
+ ```rust
+ extern crate std
+ ```
+
+ ---
+
+ Standard library for this test
+
+ Printed?
+ abc123
+ "#]],
+ );
+ check(
+ r#"
+//- /main.rs crate:main deps:std
+extern crate std as ab$0c;
+//- /std/lib.rs crate:std
+//! Standard library for this test
+//!
+//! Printed?
+//! abc123
+"#,
+ expect![[r#"
+ *abc*
+
+ ```rust
+ extern crate std
+ ```
+
+ ---
+
+ Standard library for this test
+
+ Printed?
+ abc123
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_mod_with_same_name_as_function() {
+ check(
+ r#"
+use self::m$0y::Bar;
+mod my { pub struct Bar; }
+
+fn my() {}
+"#,
+ expect![[r#"
+ *my*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ mod my
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_struct_doc_comment() {
+ check(
+ r#"
+/// This is an example
+/// multiline doc
+///
+/// # Example
+///
+/// ```
+/// let five = 5;
+///
+/// assert_eq!(6, my_crate::add_one(5));
+/// ```
+struct Bar;
+
+fn foo() { let bar = Ba$0r; }
+"#,
+ expect![[r##"
+ *Bar*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ struct Bar
+ ```
+
+ ---
+
+ This is an example
+ multiline doc
+
+ # Example
+
+ ```
+ let five = 5;
+
+ assert_eq!(6, my_crate::add_one(5));
+ ```
+ "##]],
+ );
+}
+
+#[test]
+fn test_hover_struct_doc_attr() {
+ check(
+ r#"
+#[doc = "bar docs"]
+struct Bar;
+
+fn foo() { let bar = Ba$0r; }
+"#,
+ expect![[r#"
+ *Bar*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ struct Bar
+ ```
+
+ ---
+
+ bar docs
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_struct_doc_attr_multiple_and_mixed() {
+ check(
+ r#"
+/// bar docs 0
+#[doc = "bar docs 1"]
+#[doc = "bar docs 2"]
+struct Bar;
+
+fn foo() { let bar = Ba$0r; }
+"#,
+ expect![[r#"
+ *Bar*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ struct Bar
+ ```
+
+ ---
+
+ bar docs 0
+ bar docs 1
+ bar docs 2
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_external_url() {
+ check(
+ r#"
+pub struct Foo;
+/// [external](https://www.google.com)
+pub struct B$0ar
+"#,
+ expect![[r#"
+ *Bar*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub struct Bar
+ ```
+
+ ---
+
+ [external](https://www.google.com)
+ "#]],
+ );
+}
+
+// Check that we don't rewrite links which we can't identify
+#[test]
+fn test_hover_unknown_target() {
+ check(
+ r#"
+pub struct Foo;
+/// [baz](Baz)
+pub struct B$0ar
+"#,
+ expect![[r#"
+ *Bar*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub struct Bar
+ ```
+
+ ---
+
+ [baz](Baz)
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_no_links() {
+ check_hover_no_links(
+ r#"
+/// Test cases:
+/// case 1. bare URL: https://www.example.com/
+/// case 2. inline URL with title: [example](https://www.example.com/)
+/// case 3. code reference: [`Result`]
+/// case 4. code reference but miss footnote: [`String`]
+/// case 5. autolink: <http://www.example.com/>
+/// case 6. email address: <test@example.com>
+/// case 7. reference: [example][example]
+/// case 8. collapsed link: [example][]
+/// case 9. shortcut link: [example]
+/// case 10. inline without URL: [example]()
+/// case 11. reference: [foo][foo]
+/// case 12. reference: [foo][bar]
+/// case 13. collapsed link: [foo][]
+/// case 14. shortcut link: [foo]
+/// case 15. inline without URL: [foo]()
+/// case 16. just escaped text: \[foo]
+/// case 17. inline link: [Foo](foo::Foo)
+///
+/// [`Result`]: ../../std/result/enum.Result.html
+/// [^example]: https://www.example.com/
+pub fn fo$0o() {}
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub fn foo()
+ ```
+
+ ---
+
+ Test cases:
+ case 1. bare URL: https://www.example.com/
+ case 2. inline URL with title: [example](https://www.example.com/)
+ case 3. code reference: `Result`
+ case 4. code reference but miss footnote: `String`
+ case 5. autolink: http://www.example.com/
+ case 6. email address: test@example.com
+ case 7. reference: example
+ case 8. collapsed link: example
+ case 9. shortcut link: example
+ case 10. inline without URL: example
+ case 11. reference: foo
+ case 12. reference: foo
+ case 13. collapsed link: foo
+ case 14. shortcut link: foo
+ case 15. inline without URL: foo
+ case 16. just escaped text: \[foo\]
+ case 17. inline link: Foo
+
+ [^example]: https://www.example.com/
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_macro_generated_struct_fn_doc_comment() {
+ cov_mark::check!(hover_macro_generated_struct_fn_doc_comment);
+
+ check(
+ r#"
+macro_rules! bar {
+ () => {
+ struct Bar;
+ impl Bar {
+ /// Do the foo
+ fn foo(&self) {}
+ }
+ }
+}
+
+bar!();
+
+fn foo() { let bar = Bar; bar.fo$0o(); }
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test::Bar
+ ```
+
+ ```rust
+ fn foo(&self)
+ ```
+
+ ---
+
+ Do the foo
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_macro_generated_struct_fn_doc_attr() {
+ cov_mark::check!(hover_macro_generated_struct_fn_doc_attr);
+
+ check(
+ r#"
+macro_rules! bar {
+ () => {
+ struct Bar;
+ impl Bar {
+ #[doc = "Do the foo"]
+ fn foo(&self) {}
+ }
+ }
+}
+
+bar!();
+
+fn foo() { let bar = Bar; bar.fo$0o(); }
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test::Bar
+ ```
+
+ ```rust
+ fn foo(&self)
+ ```
+
+ ---
+
+ Do the foo
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_variadic_function() {
+ check(
+ r#"
+extern "C" {
+ pub fn foo(bar: i32, ...) -> i32;
+}
+
+fn main() { let foo_test = unsafe { fo$0o(1, 2, 3); } }
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub unsafe fn foo(bar: i32, ...) -> i32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_trait_has_impl_action() {
+ check_actions(
+ r#"trait foo$0() {}"#,
+ expect![[r#"
+ [
+ Implementation(
+ FilePosition {
+ file_id: FileId(
+ 0,
+ ),
+ offset: 6,
+ },
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_struct_has_impl_action() {
+ check_actions(
+ r"struct foo$0() {}",
+ expect![[r#"
+ [
+ Implementation(
+ FilePosition {
+ file_id: FileId(
+ 0,
+ ),
+ offset: 7,
+ },
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_union_has_impl_action() {
+ check_actions(
+ r#"union foo$0() {}"#,
+ expect![[r#"
+ [
+ Implementation(
+ FilePosition {
+ file_id: FileId(
+ 0,
+ ),
+ offset: 6,
+ },
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_enum_has_impl_action() {
+ check_actions(
+ r"enum foo$0() { A, B }",
+ expect![[r#"
+ [
+ Implementation(
+ FilePosition {
+ file_id: FileId(
+ 0,
+ ),
+ offset: 5,
+ },
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_self_has_impl_action() {
+ check_actions(
+ r#"struct foo where Self$0:;"#,
+ expect![[r#"
+ [
+ Implementation(
+ FilePosition {
+ file_id: FileId(
+ 0,
+ ),
+ offset: 7,
+ },
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_test_has_action() {
+ check_actions(
+ r#"
+#[test]
+fn foo_$0test() {}
+"#,
+ expect![[r#"
+ [
+ Reference(
+ FilePosition {
+ file_id: FileId(
+ 0,
+ ),
+ offset: 11,
+ },
+ ),
+ Runnable(
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..24,
+ focus_range: 11..19,
+ name: "foo_test",
+ kind: Function,
+ },
+ kind: Test {
+ test_id: Path(
+ "foo_test",
+ ),
+ attr: TestAttr {
+ ignore: false,
+ },
+ },
+ cfg: None,
+ },
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_test_mod_has_action() {
+ check_actions(
+ r#"
+mod tests$0 {
+ #[test]
+ fn foo_test() {}
+}
+"#,
+ expect![[r#"
+ [
+ Runnable(
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..46,
+ focus_range: 4..9,
+ name: "tests",
+ kind: Module,
+ description: "mod tests",
+ },
+ kind: TestMod {
+ path: "tests",
+ },
+ cfg: None,
+ },
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_struct_has_goto_type_action() {
+ check_actions(
+ r#"
+struct S{ f1: u32 }
+
+fn main() { let s$0t = S{ f1:0 }; }
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::S",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..19,
+ focus_range: 7..8,
+ name: "S",
+ kind: Struct,
+ description: "struct S",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_generic_struct_has_goto_type_actions() {
+ check_actions(
+ r#"
+struct Arg(u32);
+struct S<T>{ f1: T }
+
+fn main() { let s$0t = S{ f1:Arg(0) }; }
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::S",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 17..37,
+ focus_range: 24..25,
+ name: "S",
+ kind: Struct,
+ description: "struct S<T>",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::Arg",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..16,
+ focus_range: 7..10,
+ name: "Arg",
+ kind: Struct,
+ description: "struct Arg",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_generic_struct_has_flattened_goto_type_actions() {
+ check_actions(
+ r#"
+struct Arg(u32);
+struct S<T>{ f1: T }
+
+fn main() { let s$0t = S{ f1: S{ f1: Arg(0) } }; }
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::S",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 17..37,
+ focus_range: 24..25,
+ name: "S",
+ kind: Struct,
+ description: "struct S<T>",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::Arg",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..16,
+ focus_range: 7..10,
+ name: "Arg",
+ kind: Struct,
+ description: "struct Arg",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_tuple_has_goto_type_actions() {
+ check_actions(
+ r#"
+struct A(u32);
+struct B(u32);
+mod M {
+ pub struct C(u32);
+}
+
+fn main() { let s$0t = (A(1), B(2), M::C(3) ); }
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::A",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..14,
+ focus_range: 7..8,
+ name: "A",
+ kind: Struct,
+ description: "struct A",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::B",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 15..29,
+ focus_range: 22..23,
+ name: "B",
+ kind: Struct,
+ description: "struct B",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::M::C",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 42..60,
+ focus_range: 53..54,
+ name: "C",
+ kind: Struct,
+ description: "pub struct C",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_return_impl_trait_has_goto_type_action() {
+ check_actions(
+ r#"
+trait Foo {}
+fn foo() -> impl Foo {}
+
+fn main() { let s$0t = foo(); }
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..12,
+ focus_range: 6..9,
+ name: "Foo",
+ kind: Trait,
+ description: "trait Foo",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_generic_return_impl_trait_has_goto_type_action() {
+ check_actions(
+ r#"
+trait Foo<T> {}
+struct S;
+fn foo() -> impl Foo<S> {}
+
+fn main() { let s$0t = foo(); }
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..15,
+ focus_range: 6..9,
+ name: "Foo",
+ kind: Trait,
+ description: "trait Foo<T>",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::S",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 16..25,
+ focus_range: 23..24,
+ name: "S",
+ kind: Struct,
+ description: "struct S",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_return_impl_traits_has_goto_type_action() {
+ check_actions(
+ r#"
+trait Foo {}
+trait Bar {}
+fn foo() -> impl Foo + Bar {}
+
+fn main() { let s$0t = foo(); }
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..12,
+ focus_range: 6..9,
+ name: "Foo",
+ kind: Trait,
+ description: "trait Foo",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::Bar",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 13..25,
+ focus_range: 19..22,
+ name: "Bar",
+ kind: Trait,
+ description: "trait Bar",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_generic_return_impl_traits_has_goto_type_action() {
+ check_actions(
+ r#"
+trait Foo<T> {}
+trait Bar<T> {}
+struct S1 {}
+struct S2 {}
+
+fn foo() -> impl Foo<S1> + Bar<S2> {}
+
+fn main() { let s$0t = foo(); }
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..15,
+ focus_range: 6..9,
+ name: "Foo",
+ kind: Trait,
+ description: "trait Foo<T>",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::Bar",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 16..31,
+ focus_range: 22..25,
+ name: "Bar",
+ kind: Trait,
+ description: "trait Bar<T>",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::S1",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 32..44,
+ focus_range: 39..41,
+ name: "S1",
+ kind: Struct,
+ description: "struct S1",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::S2",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 45..57,
+ focus_range: 52..54,
+ name: "S2",
+ kind: Struct,
+ description: "struct S2",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_arg_impl_trait_has_goto_type_action() {
+ check_actions(
+ r#"
+trait Foo {}
+fn foo(ar$0g: &impl Foo) {}
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..12,
+ focus_range: 6..9,
+ name: "Foo",
+ kind: Trait,
+ description: "trait Foo",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_arg_impl_traits_has_goto_type_action() {
+ check_actions(
+ r#"
+trait Foo {}
+trait Bar<T> {}
+struct S{}
+
+fn foo(ar$0g: &impl Foo + Bar<S>) {}
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..12,
+ focus_range: 6..9,
+ name: "Foo",
+ kind: Trait,
+ description: "trait Foo",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::Bar",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 13..28,
+ focus_range: 19..22,
+ name: "Bar",
+ kind: Trait,
+ description: "trait Bar<T>",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::S",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 29..39,
+ focus_range: 36..37,
+ name: "S",
+ kind: Struct,
+ description: "struct S",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_async_block_impl_trait_has_goto_type_action() {
+ check_actions(
+ r#"
+//- /main.rs crate:main deps:core
+// we don't use minicore here so that this test doesn't randomly fail
+// when someone edits minicore
+struct S;
+fn foo() {
+ let fo$0o = async { S };
+}
+//- /core.rs crate:core
+pub mod future {
+ #[lang = "future_trait"]
+ pub trait Future {}
+}
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "core::future::Future",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 1,
+ ),
+ full_range: 21..69,
+ focus_range: 60..66,
+ name: "Future",
+ kind: Trait,
+ description: "pub trait Future",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "main::S",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..110,
+ focus_range: 108..109,
+ name: "S",
+ kind: Struct,
+ description: "struct S",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_arg_generic_impl_trait_has_goto_type_action() {
+ check_actions(
+ r#"
+trait Foo<T> {}
+struct S {}
+fn foo(ar$0g: &impl Foo<S>) {}
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..15,
+ focus_range: 6..9,
+ name: "Foo",
+ kind: Trait,
+ description: "trait Foo<T>",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::S",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 16..27,
+ focus_range: 23..24,
+ name: "S",
+ kind: Struct,
+ description: "struct S",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_dyn_return_has_goto_type_action() {
+ check_actions(
+ r#"
+trait Foo {}
+struct S;
+impl Foo for S {}
+
+struct B<T>{}
+fn foo() -> B<dyn Foo> {}
+
+fn main() { let s$0t = foo(); }
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::B",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 42..55,
+ focus_range: 49..50,
+ name: "B",
+ kind: Struct,
+ description: "struct B<T>",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..12,
+ focus_range: 6..9,
+ name: "Foo",
+ kind: Trait,
+ description: "trait Foo",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_dyn_arg_has_goto_type_action() {
+ check_actions(
+ r#"
+trait Foo {}
+fn foo(ar$0g: &dyn Foo) {}
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..12,
+ focus_range: 6..9,
+ name: "Foo",
+ kind: Trait,
+ description: "trait Foo",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_generic_dyn_arg_has_goto_type_action() {
+ check_actions(
+ r#"
+trait Foo<T> {}
+struct S {}
+fn foo(ar$0g: &dyn Foo<S>) {}
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..15,
+ focus_range: 6..9,
+ name: "Foo",
+ kind: Trait,
+ description: "trait Foo<T>",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::S",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 16..27,
+ focus_range: 23..24,
+ name: "S",
+ kind: Struct,
+ description: "struct S",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_goto_type_action_links_order() {
+ check_actions(
+ r#"
+trait ImplTrait<T> {}
+trait DynTrait<T> {}
+struct B<T> {}
+struct S {}
+
+fn foo(a$0rg: &impl ImplTrait<B<dyn DynTrait<B<S>>>>) {}
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::ImplTrait",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..21,
+ focus_range: 6..15,
+ name: "ImplTrait",
+ kind: Trait,
+ description: "trait ImplTrait<T>",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::B",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 43..57,
+ focus_range: 50..51,
+ name: "B",
+ kind: Struct,
+ description: "struct B<T>",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::DynTrait",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 22..42,
+ focus_range: 28..36,
+ name: "DynTrait",
+ kind: Trait,
+ description: "trait DynTrait<T>",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::S",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 58..69,
+ focus_range: 65..66,
+ name: "S",
+ kind: Struct,
+ description: "struct S",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_associated_type_has_goto_type_action() {
+ check_actions(
+ r#"
+trait Foo {
+ type Item;
+ fn get(self) -> Self::Item {}
+}
+
+struct Bar{}
+struct S{}
+
+impl Foo for S { type Item = Bar; }
+
+fn test() -> impl Foo { S {} }
+
+fn main() { let s$0t = test().get(); }
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..62,
+ focus_range: 6..9,
+ name: "Foo",
+ kind: Trait,
+ description: "trait Foo",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_const_param_has_goto_type_action() {
+ check_actions(
+ r#"
+struct Bar;
+struct Foo<const BAR: Bar>;
+
+impl<const BAR: Bar> Foo<BAR$0> {}
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Bar",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..11,
+ focus_range: 7..10,
+ name: "Bar",
+ kind: Struct,
+ description: "struct Bar",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_type_param_has_goto_type_action() {
+ check_actions(
+ r#"
+trait Foo {}
+
+fn foo<T: Foo>(t: T$0){}
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..12,
+ focus_range: 6..9,
+ name: "Foo",
+ kind: Trait,
+ description: "trait Foo",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_self_has_go_to_type() {
+ check_actions(
+ r#"
+struct Foo;
+impl Foo {
+ fn foo(&self$0) {}
+}
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..11,
+ focus_range: 7..10,
+ name: "Foo",
+ kind: Struct,
+ description: "struct Foo",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn hover_displays_normalized_crate_names() {
+ check(
+ r#"
+//- /lib.rs crate:name-with-dashes
+pub mod wrapper {
+ pub struct Thing { x: u32 }
+
+ impl Thing {
+ pub fn new() -> Thing { Thing { x: 0 } }
+ }
+}
+
+//- /main.rs crate:main deps:name-with-dashes
+fn main() { let foo_test = name_with_dashes::wrapper::Thing::new$0(); }
+"#,
+ expect![[r#"
+ *new*
+
+ ```rust
+ name_with_dashes::wrapper::Thing
+ ```
+
+ ```rust
+ pub fn new() -> Thing
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn hover_field_pat_shorthand_ref_match_ergonomics() {
+ check(
+ r#"
+struct S {
+ f: i32,
+}
+
+fn main() {
+ let s = S { f: 0 };
+ let S { f$0 } = &s;
+}
+"#,
+ expect![[r#"
+ *f*
+
+ ```rust
+ f: &i32
+ ```
+ ---
+
+ ```rust
+ test::S
+ ```
+
+ ```rust
+ f: i32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn const_generic_order() {
+ check(
+ r#"
+struct Foo;
+struct S$0T<const C: usize = 1, T = Foo>(T);
+"#,
+ expect![[r#"
+ *ST*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ struct ST<const C: usize, T = Foo>
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn const_generic_positive_i8_literal() {
+ check(
+ r#"
+struct Const<const N: i8>;
+
+fn main() {
+ let v$0alue = Const::<1>;
+}
+"#,
+ expect![[r#"
+ *value*
+
+ ```rust
+ let value: Const<1>
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn const_generic_zero_i8_literal() {
+ check(
+ r#"
+struct Const<const N: i8>;
+
+fn main() {
+ let v$0alue = Const::<0>;
+}
+"#,
+ expect![[r#"
+ *value*
+
+ ```rust
+ let value: Const<0>
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn const_generic_negative_i8_literal() {
+ check(
+ r#"
+struct Const<const N: i8>;
+
+fn main() {
+ let v$0alue = Const::<-1>;
+}
+"#,
+ expect![[r#"
+ *value*
+
+ ```rust
+ let value: Const<-1>
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn const_generic_bool_literal() {
+ check(
+ r#"
+struct Const<const F: bool>;
+
+fn main() {
+ let v$0alue = Const::<true>;
+}
+"#,
+ expect![[r#"
+ *value*
+
+ ```rust
+ let value: Const<true>
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn const_generic_char_literal() {
+ check(
+ r#"
+struct Const<const C: char>;
+
+fn main() {
+ let v$0alue = Const::<'🦀'>;
+}
+"#,
+ expect![[r#"
+ *value*
+
+ ```rust
+ let value: Const<'🦀'>
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_self_param_shows_type() {
+ check(
+ r#"
+struct Foo {}
+impl Foo {
+ fn bar(&sel$0f) {}
+}
+"#,
+ expect![[r#"
+ *self*
+
+ ```rust
+ self: &Foo
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_self_param_shows_type_for_arbitrary_self_type() {
+ check(
+ r#"
+struct Arc<T>(T);
+struct Foo {}
+impl Foo {
+ fn bar(sel$0f: Arc<Foo>) {}
+}
+"#,
+ expect![[r#"
+ *self*
+
+ ```rust
+ self: Arc<Foo>
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_doc_outer_inner() {
+ check(
+ r#"
+/// Be quick;
+mod Foo$0 {
+ //! time is mana
+
+ /// This comment belongs to the function
+ fn foo() {}
+}
+"#,
+ expect![[r#"
+ *Foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ mod Foo
+ ```
+
+ ---
+
+ Be quick;
+ time is mana
+ "#]],
+ );
+}
+
+#[test]
+fn hover_doc_outer_inner_attribue() {
+ check(
+ r#"
+#[doc = "Be quick;"]
+mod Foo$0 {
+ #![doc = "time is mana"]
+
+ #[doc = "This comment belongs to the function"]
+ fn foo() {}
+}
+"#,
+ expect![[r#"
+ *Foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ mod Foo
+ ```
+
+ ---
+
+ Be quick;
+ time is mana
+ "#]],
+ );
+}
+
+#[test]
+fn hover_doc_block_style_indentend() {
+ check(
+ r#"
+/**
+ foo
+ ```rust
+ let x = 3;
+ ```
+*/
+fn foo$0() {}
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ fn foo()
+ ```
+
+ ---
+
+ foo
+
+ ```rust
+ let x = 3;
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_comments_dont_highlight_parent() {
+ cov_mark::check!(no_highlight_on_comment_hover);
+ check_hover_no_result(
+ r#"
+fn no_hover() {
+ // no$0hover
+}
+"#,
+ );
+}
+
+#[test]
+fn hover_label() {
+ check(
+ r#"
+fn foo() {
+ 'label$0: loop {}
+}
+"#,
+ expect![[r#"
+ *'label*
+
+ ```rust
+ 'label
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_lifetime() {
+ check(
+ r#"fn foo<'lifetime>(_: &'lifetime$0 ()) {}"#,
+ expect![[r#"
+ *'lifetime*
+
+ ```rust
+ 'lifetime
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_type_param() {
+ check(
+ r#"
+//- minicore: sized
+struct Foo<T>(T);
+trait TraitA {}
+trait TraitB {}
+impl<T: TraitA + TraitB> Foo<T$0> where T: Sized {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T: TraitA + TraitB
+ ```
+ "#]],
+ );
+ check(
+ r#"
+//- minicore: sized
+struct Foo<T>(T);
+impl<T> Foo<T$0> {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T
+ ```
+ "#]],
+ );
+ // lifetimes bounds arent being tracked yet
+ check(
+ r#"
+//- minicore: sized
+struct Foo<T>(T);
+impl<T: 'static> Foo<T$0> {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_type_param_sized_bounds() {
+ // implicit `: Sized` bound
+ check(
+ r#"
+//- minicore: sized
+trait Trait {}
+struct Foo<T>(T);
+impl<T: Trait> Foo<T$0> {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T: Trait
+ ```
+ "#]],
+ );
+ check(
+ r#"
+//- minicore: sized
+trait Trait {}
+struct Foo<T>(T);
+impl<T: Trait + ?Sized> Foo<T$0> {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T: Trait + ?Sized
+ ```
+ "#]],
+ );
+}
+
+mod type_param_sized_bounds {
+ use super::*;
+
+ #[test]
+ fn single_implicit() {
+ check(
+ r#"
+//- minicore: sized
+fn foo<T$0>() {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T
+ ```
+ "#]],
+ );
+ }
+
+ #[test]
+ fn single_explicit() {
+ check(
+ r#"
+//- minicore: sized
+fn foo<T$0: Sized>() {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T
+ ```
+ "#]],
+ );
+ }
+
+ #[test]
+ fn single_relaxed() {
+ check(
+ r#"
+//- minicore: sized
+fn foo<T$0: ?Sized>() {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T: ?Sized
+ ```
+ "#]],
+ );
+ }
+
+ #[test]
+ fn multiple_implicit() {
+ check(
+ r#"
+//- minicore: sized
+trait Trait {}
+fn foo<T$0: Trait>() {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T: Trait
+ ```
+ "#]],
+ );
+ }
+
+ #[test]
+ fn multiple_explicit() {
+ check(
+ r#"
+//- minicore: sized
+trait Trait {}
+fn foo<T$0: Trait + Sized>() {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T: Trait
+ ```
+ "#]],
+ );
+ }
+
+ #[test]
+ fn multiple_relaxed() {
+ check(
+ r#"
+//- minicore: sized
+trait Trait {}
+fn foo<T$0: Trait + ?Sized>() {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T: Trait + ?Sized
+ ```
+ "#]],
+ );
+ }
+
+ #[test]
+ fn mixed() {
+ check(
+ r#"
+//- minicore: sized
+fn foo<T$0: ?Sized + Sized + Sized>() {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T
+ ```
+ "#]],
+ );
+ check(
+ r#"
+//- minicore: sized
+trait Trait {}
+fn foo<T$0: Sized + ?Sized + Sized + Trait>() {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T: Trait
+ ```
+ "#]],
+ );
+ }
+}
+
+#[test]
+fn hover_const_generic_type_alias() {
+ check(
+ r#"
+struct Foo<const LEN: usize>;
+type Fo$0o2 = Foo<2>;
+"#,
+ expect![[r#"
+ *Foo2*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ type Foo2 = Foo<2>
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_const_param() {
+ check(
+ r#"
+struct Foo<const LEN: usize>;
+impl<const LEN: usize> Foo<LEN$0> {}
+"#,
+ expect![[r#"
+ *LEN*
+
+ ```rust
+ const LEN: usize
+ ```
+ "#]],
+ );
+}
+
++#[test]
++fn hover_const_eval_variant() {
++ // show hex for <10
++ check(
++ r#"
++#[repr(u8)]
++enum E {
++ /// This is a doc
++ A$0 = 1 << 3,
++}
++"#,
++ expect![[r#"
++ *A*
++
++ ```rust
++ test::E
++ ```
++
++ ```rust
++ A = 8
++ ```
++
++ ---
++
++ This is a doc
++ "#]],
++ );
++ // show hex for >10
++ check(
++ r#"
++#[repr(u8)]
++enum E {
++ /// This is a doc
++ A$0 = (1 << 3) + (1 << 2),
++}
++"#,
++ expect![[r#"
++ *A*
++
++ ```rust
++ test::E
++ ```
++
++ ```rust
++ A = 12 (0xC)
++ ```
++
++ ---
++
++ This is a doc
++ "#]],
++ );
++ // enums in const eval
++ check(
++ r#"
++#[repr(u8)]
++enum E {
++ A = 1,
++ /// This is a doc
++ B$0 = E::A as u8 + 1,
++}
++"#,
++ expect![[r#"
++ *B*
++
++ ```rust
++ test::E
++ ```
++
++ ```rust
++ B = 2
++ ```
++
++ ---
++
++ This is a doc
++ "#]],
++ );
++ // unspecified variant should increment by one
++ check(
++ r#"
++#[repr(u8)]
++enum E {
++ A = 4,
++ /// This is a doc
++ B$0,
++}
++"#,
++ expect![[r#"
++ *B*
++
++ ```rust
++ test::E
++ ```
++
++ ```rust
++ B = 5
++ ```
++
++ ---
++
++ This is a doc
++ "#]],
++ );
++}
++
+#[test]
+fn hover_const_eval() {
+ // show hex for <10
+ check(
+ r#"
+/// This is a doc
+const FOO$0: usize = 1 << 3;
+"#,
+ expect![[r#"
+ *FOO*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const FOO: usize = 8
+ ```
+
+ ---
+
+ This is a doc
+ "#]],
+ );
+ // show hex for >10
+ check(
+ r#"
+/// This is a doc
+const FOO$0: usize = (1 << 3) + (1 << 2);
+"#,
+ expect![[r#"
+ *FOO*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const FOO: usize = 12 (0xC)
+ ```
+
+ ---
+
+ This is a doc
+ "#]],
+ );
+ // show original body when const eval fails
+ check(
+ r#"
+/// This is a doc
+const FOO$0: usize = 2 - 3;
+"#,
+ expect![[r#"
+ *FOO*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const FOO: usize = 2 - 3
+ ```
+
+ ---
+
+ This is a doc
+ "#]],
+ );
+ // don't show hex for negatives
+ check(
+ r#"
+/// This is a doc
+const FOO$0: i32 = 2 - 3;
+"#,
+ expect![[r#"
+ *FOO*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const FOO: i32 = -1
+ ```
+
+ ---
+
+ This is a doc
+ "#]],
+ );
+ check(
+ r#"
+/// This is a doc
+const FOO$0: &str = "bar";
+"#,
+ expect![[r#"
+ *FOO*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const FOO: &str = "bar"
+ ```
+
+ ---
+
+ This is a doc
+ "#]],
+ );
+ // show char literal
+ check(
+ r#"
+/// This is a doc
+const FOO$0: char = 'a';
+"#,
+ expect![[r#"
+ *FOO*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const FOO: char = 'a'
+ ```
+
+ ---
+
+ This is a doc
+ "#]],
+ );
+ // show escaped char literal
+ check(
+ r#"
+/// This is a doc
+const FOO$0: char = '\x61';
+"#,
+ expect![[r#"
+ *FOO*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const FOO: char = 'a'
+ ```
+
+ ---
+
+ This is a doc
+ "#]],
+ );
+ // show byte literal
+ check(
+ r#"
+/// This is a doc
+const FOO$0: u8 = b'a';
+"#,
+ expect![[r#"
+ *FOO*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const FOO: u8 = 97 (0x61)
+ ```
+
+ ---
+
+ This is a doc
+ "#]],
+ );
+ // show escaped byte literal
+ check(
+ r#"
+/// This is a doc
+const FOO$0: u8 = b'\x61';
+"#,
+ expect![[r#"
+ *FOO*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const FOO: u8 = 97 (0x61)
+ ```
+
+ ---
+
+ This is a doc
+ "#]],
+ );
+ // show float literal
+ check(
+ r#"
+ /// This is a doc
+ const FOO$0: f64 = 1.0234;
+ "#,
+ expect![[r#"
+ *FOO*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const FOO: f64 = 1.0234
+ ```
+
+ ---
+
+ This is a doc
+ "#]],
+ );
+ //show float typecasted from int
+ check(
+ r#"
+/// This is a doc
+const FOO$0: f32 = 1f32;
+"#,
+ expect![[r#"
+ *FOO*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const FOO: f32 = 1.0
+ ```
+
+ ---
+
+ This is a doc
+ "#]],
+ );
+ //show f64 typecasted from float
+ check(
+ r#"
+/// This is a doc
+const FOO$0: f64 = 1.0f64;
+"#,
+ expect![[r#"
+ *FOO*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const FOO: f64 = 1.0
+ ```
+
+ ---
+
+ This is a doc
+ "#]],
+ );
+}
+
+#[test]
+fn hover_const_pat() {
+ check(
+ r#"
+/// This is a doc
+const FOO: usize = 3;
+fn foo() {
+ match 5 {
+ FOO$0 => (),
+ _ => ()
+ }
+}
+"#,
+ expect![[r#"
+ *FOO*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const FOO: usize = 3
+ ```
+
+ ---
+
++ This is a doc
++ "#]],
++ );
++ check(
++ r#"
++enum E {
++ /// This is a doc
++ A = 3,
++}
++fn foo(e: E) {
++ match e {
++ E::A$0 => (),
++ _ => ()
++ }
++}
++"#,
++ expect![[r#"
++ *A*
++
++ ```rust
++ test::E
++ ```
++
++ ```rust
++ A = 3
++ ```
++
++ ---
++
+ This is a doc
+ "#]],
+ );
+}
+
+#[test]
+fn array_repeat_exp() {
+ check(
+ r#"
+fn main() {
+ let til$0e4 = [0_u32; (4 * 8 * 8) / 32];
+}
+ "#,
+ expect![[r#"
+ *tile4*
+
+ ```rust
+ let tile4: [u32; 8]
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_mod_def() {
+ check(
+ r#"
+//- /main.rs
+mod foo$0;
+//- /foo.rs
+//! For the horde!
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ mod foo
+ ```
+
+ ---
+
+ For the horde!
+ "#]],
+ );
+}
+
+#[test]
+fn hover_self_in_use() {
+ check(
+ r#"
+//! This should not appear
+mod foo {
+ /// But this should appear
+ pub mod bar {}
+}
+use foo::bar::{self$0};
+"#,
+ expect![[r#"
+ *self*
+
+ ```rust
+ test::foo
+ ```
+
+ ```rust
+ mod bar
+ ```
+
+ ---
+
+ But this should appear
+ "#]],
+ )
+}
+
+#[test]
+fn hover_keyword() {
+ check(
+ r#"
+//- /main.rs crate:main deps:std
+fn f() { retur$0n; }
+//- /libstd.rs crate:std
+/// Docs for return_keyword
+mod return_keyword {}
+"#,
+ expect![[r#"
+ *return*
+
+ ```rust
+ return
+ ```
+
+ ---
+
+ Docs for return_keyword
+ "#]],
+ );
+}
+
+#[test]
+fn hover_keyword_doc() {
+ check(
+ r#"
+//- /main.rs crate:main deps:std
+fn foo() {
+ let bar = mov$0e || {};
+}
+//- /libstd.rs crate:std
+#[doc(keyword = "move")]
+/// [closure]
+/// [closures][closure]
+/// [threads]
+/// <https://doc.rust-lang.org/nightly/book/ch13-01-closures.html>
+///
+/// [closure]: ../book/ch13-01-closures.html
+/// [threads]: ../book/ch16-01-threads.html#using-move-closures-with-threads
+mod move_keyword {}
+"#,
+ expect![[r##"
+ *move*
+
+ ```rust
+ move
+ ```
+
+ ---
+
+ [closure](https://doc.rust-lang.org/nightly/book/ch13-01-closures.html)
+ [closures](https://doc.rust-lang.org/nightly/book/ch13-01-closures.html)
+ [threads](https://doc.rust-lang.org/nightly/book/ch16-01-threads.html#using-move-closures-with-threads)
+ <https://doc.rust-lang.org/nightly/book/ch13-01-closures.html>
+ "##]],
+ );
+}
+
+#[test]
+fn hover_keyword_as_primitive() {
+ check(
+ r#"
+//- /main.rs crate:main deps:std
+type F = f$0n(i32) -> i32;
+//- /libstd.rs crate:std
+/// Docs for prim_fn
+mod prim_fn {}
+"#,
+ expect![[r#"
+ *fn*
+
+ ```rust
+ fn
+ ```
+
+ ---
+
+ Docs for prim_fn
+ "#]],
+ );
+}
+
+#[test]
+fn hover_builtin() {
+ check(
+ r#"
+//- /main.rs crate:main deps:std
+cosnt _: &str$0 = ""; }
+
+//- /libstd.rs crate:std
+/// Docs for prim_str
+/// [`foo`](../std/keyword.foo.html)
+mod prim_str {}
+"#,
+ expect![[r#"
+ *str*
+
+ ```rust
+ str
+ ```
+
+ ---
+
+ Docs for prim_str
+ [`foo`](https://doc.rust-lang.org/nightly/std/keyword.foo.html)
+ "#]],
+ );
+}
+
+#[test]
+fn hover_macro_expanded_function() {
+ check(
+ r#"
+struct S<'a, T>(&'a T);
+trait Clone {}
+macro_rules! foo {
+ () => {
+ fn bar<'t, T: Clone + 't>(s: &mut S<'t, T>, t: u32) -> *mut u32 where
+ 't: 't + 't,
+ for<'a> T: Clone + 'a
+ { 0 as _ }
+ };
+}
+
+foo!();
+
+fn main() {
+ bar$0;
+}
+"#,
+ expect![[r#"
+ *bar*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ fn bar<'t, T>(s: &mut S<'t, T>, t: u32) -> *mut u32
+ where
+ T: Clone + 't,
+ 't: 't + 't,
+ for<'a> T: Clone + 'a,
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn hover_intra_doc_links() {
+ check(
+ r#"
+
+pub mod theitem {
+ /// This is the item. Cool!
+ pub struct TheItem;
+}
+
+/// Gives you a [`TheItem$0`].
+///
+/// [`TheItem`]: theitem::TheItem
+pub fn gimme() -> theitem::TheItem {
+ theitem::TheItem
+}
+"#,
+ expect![[r#"
+ *[`TheItem`]*
+
+ ```rust
+ test::theitem
+ ```
+
+ ```rust
+ pub struct TheItem
+ ```
+
+ ---
+
+ This is the item. Cool!
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_trait_assoc_typealias() {
+ check(
+ r#"
+ fn main() {}
+
+trait T1 {
+ type Bar;
+ type Baz;
+}
+
+struct Foo;
+
+mod t2 {
+ pub trait T2 {
+ type Bar;
+ }
+}
+
+use t2::T2;
+
+impl T2 for Foo {
+ type Bar = String;
+}
+
+impl T1 for Foo {
+ type Bar = <Foo as t2::T2>::Ba$0r;
+ // ^^^ unresolvedReference
+}
+ "#,
+ expect![[r#"
+*Bar*
+
+```rust
+test::t2
+```
+
+```rust
+pub type Bar
+```
+"#]],
+ );
+}
+#[test]
+fn hover_generic_assoc() {
+ check(
+ r#"
+fn foo<T: A>() where T::Assoc$0: {}
+
+trait A {
+ type Assoc;
+}"#,
+ expect![[r#"
+ *Assoc*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ type Assoc
+ ```
+ "#]],
+ );
+ check(
+ r#"
+fn foo<T: A>() {
+ let _: <T>::Assoc$0;
+}
+
+trait A {
+ type Assoc;
+}"#,
+ expect![[r#"
+ *Assoc*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ type Assoc
+ ```
+ "#]],
+ );
+ check(
+ r#"
+trait A where
+ Self::Assoc$0: ,
+{
+ type Assoc;
+}"#,
+ expect![[r#"
+ *Assoc*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ type Assoc
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn string_shadowed_with_inner_items() {
+ check(
+ r#"
+//- /main.rs crate:main deps:alloc
+
+/// Custom `String` type.
+struct String;
+
+fn f() {
+ let _: String$0;
+
+ fn inner() {}
+}
+
+//- /alloc.rs crate:alloc
+#[prelude_import]
+pub use string::*;
+
+mod string {
+ /// This is `alloc::String`.
+ pub struct String;
+}
+"#,
+ expect![[r#"
+ *String*
+
+ ```rust
+ main
+ ```
+
+ ```rust
+ struct String
+ ```
+
+ ---
+
+ Custom `String` type.
+ "#]],
+ )
+}
+
+#[test]
+fn function_doesnt_shadow_crate_in_use_tree() {
+ check(
+ r#"
+//- /main.rs crate:main deps:foo
+use foo$0::{foo};
+
+//- /foo.rs crate:foo
+pub fn foo() {}
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ extern crate foo
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn hover_feature() {
+ check(
+ r#"#![feature(box_syntax$0)]"#,
+ expect![[r##"
+ *box_syntax*
+ ```
+ box_syntax
+ ```
+ ___
+
+ # `box_syntax`
+
+ The tracking issue for this feature is: [#49733]
+
+ [#49733]: https://github.com/rust-lang/rust/issues/49733
+
+ See also [`box_patterns`](box-patterns.md)
+
+ ------------------------
+
+ Currently the only stable way to create a `Box` is via the `Box::new` method.
+ Also it is not possible in stable Rust to destructure a `Box` in a match
+ pattern. The unstable `box` keyword can be used to create a `Box`. An example
+ usage would be:
+
+ ```rust
+ #![feature(box_syntax)]
+
+ fn main() {
+ let b = box 5;
+ }
+ ```
+
+ "##]],
+ )
+}
+
+#[test]
+fn hover_lint() {
+ check(
+ r#"#![allow(arithmetic_overflow$0)]"#,
+ expect![[r#"
+ *arithmetic_overflow*
+ ```
+ arithmetic_overflow
+ ```
+ ___
+
+ arithmetic operation overflows
+ "#]],
+ )
+}
+
+#[test]
+fn hover_clippy_lint() {
+ check(
+ r#"#![allow(clippy::almost_swapped$0)]"#,
+ expect![[r#"
+ *almost_swapped*
+ ```
+ clippy::almost_swapped
+ ```
+ ___
+
+ Checks for `foo = bar; bar = foo` sequences.
+ "#]],
+ )
+}
+
+#[test]
+fn hover_attr_path_qualifier() {
+ check(
+ r#"
+//- /foo.rs crate:foo
+
+//- /lib.rs crate:main.rs deps:foo
+#[fo$0o::bar()]
+struct Foo;
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ extern crate foo
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn hover_rename() {
+ check(
+ r#"
+use self as foo$0;
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ extern crate test
+ ```
+ "#]],
+ );
+ check(
+ r#"
+mod bar {}
+use bar::{self as foo$0};
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ mod bar
+ ```
+ "#]],
+ );
+ check(
+ r#"
+mod bar {
+ use super as foo$0;
+}
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ extern crate test
+ ```
+ "#]],
+ );
+ check(
+ r#"
+use crate as foo$0;
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ extern crate test
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_attribute_in_macro() {
+ check(
+ r#"
+//- minicore:derive
+macro_rules! identity {
+ ($struct:item) => {
+ $struct
+ };
+}
+#[rustc_builtin_macro]
+pub macro Copy {}
+identity!{
+ #[derive(Copy$0)]
+ struct Foo;
+}
+"#,
+ expect![[r#"
+ *Copy*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ macro Copy
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_derive_input() {
+ check(
+ r#"
+//- minicore:derive
+#[rustc_builtin_macro]
+pub macro Copy {}
+#[derive(Copy$0)]
+struct Foo;
+"#,
+ expect![[r#"
+ *Copy*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ macro Copy
+ ```
+ "#]],
+ );
+ check(
+ r#"
+//- minicore:derive
+mod foo {
+ #[rustc_builtin_macro]
+ pub macro Copy {}
+}
+#[derive(foo::Copy$0)]
+struct Foo;
+"#,
+ expect![[r#"
+ *Copy*
+
+ ```rust
+ test::foo
+ ```
+
+ ```rust
+ macro Copy
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_range_math() {
+ check_hover_range(
+ r#"
+fn f() { let expr = $01 + 2 * 3$0 }
+"#,
+ expect![[r#"
+ ```rust
+ i32
+ ```"#]],
+ );
+
+ check_hover_range(
+ r#"
+fn f() { let expr = 1 $0+ 2 * $03 }
+"#,
+ expect![[r#"
+ ```rust
+ i32
+ ```"#]],
+ );
+
+ check_hover_range(
+ r#"
+fn f() { let expr = 1 + $02 * 3$0 }
+"#,
+ expect![[r#"
+ ```rust
+ i32
+ ```"#]],
+ );
+}
+
+#[test]
+fn hover_range_arrays() {
+ check_hover_range(
+ r#"
+fn f() { let expr = $0[1, 2, 3, 4]$0 }
+"#,
+ expect![[r#"
+ ```rust
+ [i32; 4]
+ ```"#]],
+ );
+
+ check_hover_range(
+ r#"
+fn f() { let expr = [1, 2, $03, 4]$0 }
+"#,
+ expect![[r#"
+ ```rust
+ [i32; 4]
+ ```"#]],
+ );
+
+ check_hover_range(
+ r#"
+fn f() { let expr = [1, 2, $03$0, 4] }
+"#,
+ expect![[r#"
+ ```rust
+ i32
+ ```"#]],
+ );
+}
+
+#[test]
+fn hover_range_functions() {
+ check_hover_range(
+ r#"
+fn f<T>(a: &[T]) { }
+fn b() { $0f$0(&[1, 2, 3, 4, 5]); }
+"#,
+ expect![[r#"
+ ```rust
+ fn f<i32>(&[i32])
+ ```"#]],
+ );
+
+ check_hover_range(
+ r#"
+fn f<T>(a: &[T]) { }
+fn b() { f($0&[1, 2, 3, 4, 5]$0); }
+"#,
+ expect![[r#"
+ ```rust
+ &[i32; 5]
+ ```"#]],
+ );
+}
+
+#[test]
+fn hover_range_shows_nothing_when_invalid() {
+ check_hover_range_no_results(
+ r#"
+fn f<T>(a: &[T]) { }
+fn b()$0 { f(&[1, 2, 3, 4, 5]); }$0
+"#,
+ );
+
+ check_hover_range_no_results(
+ r#"
+fn f<T>$0(a: &[T]) { }
+fn b() { f(&[1, 2, 3,$0 4, 5]); }
+"#,
+ );
+
+ check_hover_range_no_results(
+ r#"
+fn $0f() { let expr = [1, 2, 3, 4]$0 }
+"#,
+ );
+}
+
+#[test]
+fn hover_range_shows_unit_for_statements() {
+ check_hover_range(
+ r#"
+fn f<T>(a: &[T]) { }
+fn b() { $0f(&[1, 2, 3, 4, 5]); }$0
+"#,
+ expect![[r#"
+ ```rust
+ ()
+ ```"#]],
+ );
+
+ check_hover_range(
+ r#"
+fn f() { let expr$0 = $0[1, 2, 3, 4] }
+"#,
+ expect![[r#"
+ ```rust
+ ()
+ ```"#]],
+ );
+}
+
+#[test]
+fn hover_range_for_pat() {
+ check_hover_range(
+ r#"
+fn foo() {
+ let $0x$0 = 0;
+}
+"#,
+ expect![[r#"
+ ```rust
+ i32
+ ```"#]],
+ );
+
+ check_hover_range(
+ r#"
+fn foo() {
+ let $0x$0 = "";
+}
+"#,
+ expect![[r#"
+ ```rust
+ &str
+ ```"#]],
+ );
+}
+
+#[test]
+fn hover_range_shows_coercions_if_applicable_expr() {
+ check_hover_range(
+ r#"
+fn foo() {
+ let x: &u32 = $0&&&&&0$0;
+}
+"#,
+ expect![[r#"
+ ```text
+ Type: &&&&&u32
+ Coerced to: &u32
+ ```
+ "#]],
+ );
+ check_hover_range(
+ r#"
+fn foo() {
+ let x: *const u32 = $0&0$0;
+}
+"#,
+ expect![[r#"
+ ```text
+ Type: &u32
+ Coerced to: *const u32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_range_shows_type_actions() {
+ check_actions(
+ r#"
+struct Foo;
+fn foo() {
+ let x: &Foo = $0&&&&&Foo$0;
+}
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..11,
+ focus_range: 7..10,
+ name: "Foo",
+ kind: Struct,
+ description: "struct Foo",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn hover_try_expr_res() {
+ check_hover_range(
+ r#"
+//- minicore:result
+struct FooError;
+
+fn foo() -> Result<(), FooError> {
+ Ok($0Result::<(), FooError>::Ok(())?$0)
+}
+"#,
+ expect![[r#"
+ ```rust
+ ()
+ ```"#]],
+ );
+ check_hover_range(
+ r#"
+//- minicore:result
+struct FooError;
+struct BarError;
+
+fn foo() -> Result<(), FooError> {
+ Ok($0Result::<(), BarError>::Ok(())?$0)
+}
+"#,
+ expect![[r#"
+ ```text
+ Try Error Type: BarError
+ Propagated as: FooError
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_try_expr() {
+ check_hover_range(
+ r#"
+struct NotResult<T, U>(T, U);
+struct Short;
+struct Looooong;
+
+fn foo() -> NotResult<(), Looooong> {
+ $0NotResult((), Short)?$0;
+}
+"#,
+ expect![[r#"
+ ```text
+ Try Target Type: NotResult<(), Short>
+ Propagated as: NotResult<(), Looooong>
+ ```
+ "#]],
+ );
+ check_hover_range(
+ r#"
+struct NotResult<T, U>(T, U);
+struct Short;
+struct Looooong;
+
+fn foo() -> NotResult<(), Short> {
+ $0NotResult((), Looooong)?$0;
+}
+"#,
+ expect![[r#"
+ ```text
+ Try Target Type: NotResult<(), Looooong>
+ Propagated as: NotResult<(), Short>
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_try_expr_option() {
+ cov_mark::check!(hover_try_expr_opt_opt);
+ check_hover_range(
+ r#"
+//- minicore: option, try
+
+fn foo() -> Option<()> {
+ $0Some(0)?$0;
+ None
+}
+"#,
+ expect![[r#"
+ ```rust
+ <Option<i32> as Try>::Output
+ ```"#]],
+ );
+}
+
+#[test]
+fn hover_deref_expr() {
+ check_hover_range(
+ r#"
+//- minicore: deref
+use core::ops::Deref;
+
+struct DerefExample<T> {
+ value: T
+}
+
+impl<T> Deref for DerefExample<T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ &self.value
+ }
+}
+
+fn foo() {
+ let x = DerefExample { value: 0 };
+ let y: i32 = $0*x$0;
+}
+"#,
+ expect![[r#"
+ ```text
+ Dereferenced from: DerefExample<i32>
+ To type: i32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_deref_expr_with_coercion() {
+ check_hover_range(
+ r#"
+//- minicore: deref
+use core::ops::Deref;
+
+struct DerefExample<T> {
+ value: T
+}
+
+impl<T> Deref for DerefExample<T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ &self.value
+ }
+}
+
+fn foo() {
+ let x = DerefExample { value: &&&&&0 };
+ let y: &i32 = $0*x$0;
+}
+"#,
+ expect![[r#"
+ ```text
+ Dereferenced from: DerefExample<&&&&&i32>
+ To type: &&&&&i32
+ Coerced to: &i32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_intra_in_macro() {
+ check(
+ r#"
+macro_rules! foo_macro {
+ ($(#[$attr:meta])* $name:ident) => {
+ $(#[$attr])*
+ pub struct $name;
+ }
+}
+
+foo_macro!(
+ /// Doc comment for [`Foo$0`]
+ Foo
+);
+"#,
+ expect![[r#"
+ *[`Foo`]*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub struct Foo
+ ```
+
+ ---
+
+ Doc comment for [`Foo`](https://docs.rs/test/*/test/struct.Foo.html)
+ "#]],
+ );
+}
+
+#[test]
+fn hover_intra_in_attr() {
+ check(
+ r#"
+#[doc = "Doc comment for [`Foo$0`]"]
+pub struct Foo;
+"#,
+ expect![[r#"
+ *[`Foo`]*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub struct Foo
+ ```
+
+ ---
+
+ Doc comment for [`Foo`](https://docs.rs/test/*/test/struct.Foo.html)
+ "#]],
+ );
+}
+
+#[test]
+fn hover_inert_attr() {
+ check(
+ r#"
+#[doc$0 = ""]
+pub struct Foo;
+"#,
+ expect![[r##"
+ *doc*
+
+ ```rust
+ #[doc]
+ ```
+
+ ---
+
+ Valid forms are:
+
+ * \#\[doc(hidden|inline|...)\]
+ * \#\[doc = string\]
+ "##]],
+ );
+ check(
+ r#"
+#[allow$0()]
+pub struct Foo;
+"#,
+ expect![[r##"
+ *allow*
+
+ ```rust
+ #[allow]
+ ```
+
+ ---
+
+ Valid forms are:
+
+ * \#\[allow(lint1, lint2, ..., /\*opt\*/ reason = "...")\]
+ "##]],
+ );
+}
+
+#[test]
+fn hover_dollar_crate() {
+ // $crate should be resolved to the right crate name.
+
+ check(
+ r#"
+//- /main.rs crate:main deps:dep
+dep::m!(KONST$0);
+//- /dep.rs crate:dep
+#[macro_export]
+macro_rules! m {
+ ( $name:ident ) => { const $name: $crate::Type = $crate::Type; };
+}
+
+pub struct Type;
+"#,
+ expect![[r#"
+ *KONST*
+
+ ```rust
+ main
+ ```
+
+ ```rust
+ const KONST: dep::Type = $crate::Type
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_record_variant() {
+ check(
+ r#"
+enum Enum {
+ RecordV$0 { field: u32 }
+}
+"#,
+ expect![[r#"
+ *RecordV*
+
+ ```rust
+ test::Enum
+ ```
+
+ ```rust
+ RecordV { field: u32 }
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_trait_impl_assoc_item_def_doc_forwarding() {
+ check(
+ r#"
+trait T {
+ /// Trait docs
+ fn func() {}
+}
+impl T for () {
+ fn func$0() {}
+}
+"#,
+ expect![[r#"
+ *func*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ fn func()
+ ```
+
+ ---
+
+ Trait docs
+ "#]],
+ );
+}
+
+#[test]
+fn hover_ranged_macro_call() {
+ check_hover_range(
+ r#"
+macro_rules! __rust_force_expr {
+ ($e:expr) => {
+ $e
+ };
+}
+macro_rules! vec {
+ ($elem:expr) => {
+ __rust_force_expr!($elem)
+ };
+}
+
+struct Struct;
+impl Struct {
+ fn foo(self) {}
+}
+
+fn f() {
+ $0vec![Struct]$0;
+}
+"#,
+ expect![[r#"
+ ```rust
+ Struct
+ ```"#]],
+ );
+}
+
+#[test]
+fn hover_deref() {
+ check(
+ r#"
+//- minicore: deref
+
+struct Struct(usize);
+
+impl core::ops::Deref for Struct {
+ type Target = usize;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+fn f() {
+ $0*Struct(0);
+}
+"#,
+ expect![[r#"
+ ***
+
+ ```rust
+ test::Struct
+ ```
+
+ ```rust
+ fn deref(&self) -> &Self::Target
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn static_const_macro_expanded_body() {
+ check(
+ r#"
+macro_rules! m {
+ () => {
+ pub const V: i8 = {
+ let e = 123;
+ f(e) // Prevent const eval from evaluating this constant, we want to print the body's code.
+ };
+ };
+}
+m!();
+fn main() { $0V; }
+"#,
+ expect![[r#"
+ *V*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub const V: i8 = {
+ let e = 123;
+ f(e)
+ }
+ ```
+ "#]],
+ );
+ check(
+ r#"
+macro_rules! m {
+ () => {
+ pub static V: i8 = {
+ let e = 123;
+ };
+ };
+}
+m!();
+fn main() { $0V; }
+"#,
+ expect![[r#"
+ *V*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub static V: i8 = {
+ let e = 123;
+ }
+ ```
+ "#]],
+ );
+}
--- /dev/null
- // |===
- // | Editor | Action Name
- //
- // | VS Code | **rust-analyzer: Toggle inlay hints*
- // |===
- //
+use std::fmt;
+
+use either::Either;
+use hir::{known, Callable, HasVisibility, HirDisplay, Mutability, Semantics, TypeInfo};
+use ide_db::{
+ base_db::FileRange, famous_defs::FamousDefs, syntax_helpers::node_ext::walk_ty, FxHashMap,
+ RootDatabase,
+};
+use itertools::Itertools;
+use stdx::to_lower_snake_case;
+use syntax::{
+ ast::{self, AstNode, HasArgList, HasGenericParams, HasName, UnaryOp},
+ match_ast, Direction, NodeOrToken, SmolStr, SyntaxKind, SyntaxNode, SyntaxToken, TextRange,
+ TextSize, T,
+};
+
+use crate::FileId;
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct InlayHintsConfig {
+ pub render_colons: bool,
+ pub type_hints: bool,
+ pub parameter_hints: bool,
+ pub chaining_hints: bool,
+ pub reborrow_hints: ReborrowHints,
+ pub closure_return_type_hints: ClosureReturnTypeHints,
+ pub binding_mode_hints: bool,
+ pub lifetime_elision_hints: LifetimeElisionHints,
+ pub param_names_for_lifetime_elision_hints: bool,
+ pub hide_named_constructor_hints: bool,
+ pub hide_closure_initialization_hints: bool,
+ pub max_length: Option<usize>,
+ pub closing_brace_hints_min_lines: Option<usize>,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum ClosureReturnTypeHints {
+ Always,
+ WithBlock,
+ Never,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum LifetimeElisionHints {
+ Always,
+ SkipTrivial,
+ Never,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum ReborrowHints {
+ Always,
+ MutableOnly,
+ Never,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum InlayKind {
+ BindingModeHint,
+ ChainingHint,
+ ClosingBraceHint,
+ ClosureReturnTypeHint,
+ GenericParamListHint,
+ ImplicitReborrowHint,
+ LifetimeHint,
+ ParameterHint,
+ TypeHint,
+}
+
+#[derive(Debug)]
+pub struct InlayHint {
+ pub range: TextRange,
+ pub kind: InlayKind,
+ pub label: InlayHintLabel,
+ pub tooltip: Option<InlayTooltip>,
+}
+
+#[derive(Debug)]
+pub enum InlayTooltip {
+ String(String),
+ HoverRanged(FileId, TextRange),
+ HoverOffset(FileId, TextSize),
+}
+
+pub struct InlayHintLabel {
+ pub parts: Vec<InlayHintLabelPart>,
+}
+
+impl InlayHintLabel {
+ pub fn as_simple_str(&self) -> Option<&str> {
+ match &*self.parts {
+ [part] => part.as_simple_str(),
+ _ => None,
+ }
+ }
+
+ pub fn prepend_str(&mut self, s: &str) {
+ match &mut *self.parts {
+ [part, ..] if part.as_simple_str().is_some() => part.text = format!("{s}{}", part.text),
+ _ => self.parts.insert(0, InlayHintLabelPart { text: s.into(), linked_location: None }),
+ }
+ }
+
+ pub fn append_str(&mut self, s: &str) {
+ match &mut *self.parts {
+ [.., part] if part.as_simple_str().is_some() => part.text.push_str(s),
+ _ => self.parts.push(InlayHintLabelPart { text: s.into(), linked_location: None }),
+ }
+ }
+}
+
+impl From<String> for InlayHintLabel {
+ fn from(s: String) -> Self {
+ Self { parts: vec![InlayHintLabelPart { text: s, linked_location: None }] }
+ }
+}
+
+impl fmt::Display for InlayHintLabel {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}", self.parts.iter().map(|part| &part.text).format(""))
+ }
+}
+
+impl fmt::Debug for InlayHintLabel {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_list().entries(&self.parts).finish()
+ }
+}
+
+pub struct InlayHintLabelPart {
+ pub text: String,
+ /// Source location represented by this label part. The client will use this to fetch the part's
+ /// hover tooltip, and Ctrl+Clicking the label part will navigate to the definition the location
+ /// refers to (not necessarily the location itself).
+ /// When setting this, no tooltip must be set on the containing hint, or VS Code will display
+ /// them both.
+ pub linked_location: Option<FileRange>,
+}
+
+impl InlayHintLabelPart {
+ pub fn as_simple_str(&self) -> Option<&str> {
+ match self {
+ Self { text, linked_location: None } => Some(text),
+ _ => None,
+ }
+ }
+}
+
+impl fmt::Debug for InlayHintLabelPart {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self.as_simple_str() {
+ Some(string) => string.fmt(f),
+ None => f
+ .debug_struct("InlayHintLabelPart")
+ .field("text", &self.text)
+ .field("linked_location", &self.linked_location)
+ .finish(),
+ }
+ }
+}
+
+// Feature: Inlay Hints
+//
+// rust-analyzer shows additional information inline with the source code.
+// Editors usually render this using read-only virtual text snippets interspersed with code.
+//
+// rust-analyzer by default shows hints for
+//
+// * types of local variables
+// * names of function arguments
+// * types of chained expressions
+//
+// Optionally, one can enable additional hints for
+//
+// * return types of closure expressions
+// * elided lifetimes
+// * compiler inserted reborrows
+//
- type Item=T;
+// image::https://user-images.githubusercontent.com/48062697/113020660-b5f98b80-917a-11eb-8d70-3be3fd558cdd.png[]
+pub(crate) fn inlay_hints(
+ db: &RootDatabase,
+ file_id: FileId,
+ range_limit: Option<FileRange>,
+ config: &InlayHintsConfig,
+) -> Vec<InlayHint> {
+ let _p = profile::span("inlay_hints");
+ let sema = Semantics::new(db);
+ let file = sema.parse(file_id);
+ let file = file.syntax();
+
+ let mut acc = Vec::new();
+
+ if let Some(scope) = sema.scope(&file) {
+ let famous_defs = FamousDefs(&sema, scope.krate());
+
+ let hints = |node| hints(&mut acc, &famous_defs, config, file_id, node);
+ match range_limit {
+ Some(FileRange { range, .. }) => match file.covering_element(range) {
+ NodeOrToken::Token(_) => return acc,
+ NodeOrToken::Node(n) => n
+ .descendants()
+ .filter(|descendant| range.intersect(descendant.text_range()).is_some())
+ .for_each(hints),
+ },
+ None => file.descendants().for_each(hints),
+ };
+ }
+
+ acc
+}
+
+fn hints(
+ hints: &mut Vec<InlayHint>,
+ famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>,
+ config: &InlayHintsConfig,
+ file_id: FileId,
+ node: SyntaxNode,
+) {
+ closing_brace_hints(hints, sema, config, file_id, node.clone());
+ match_ast! {
+ match node {
+ ast::Expr(expr) => {
+ chaining_hints(hints, sema, &famous_defs, config, file_id, &expr);
+ match expr {
+ ast::Expr::CallExpr(it) => param_name_hints(hints, sema, config, ast::Expr::from(it)),
+ ast::Expr::MethodCallExpr(it) => {
+ param_name_hints(hints, sema, config, ast::Expr::from(it))
+ }
+ ast::Expr::ClosureExpr(it) => closure_ret_hints(hints, sema, &famous_defs, config, file_id, it),
+ // We could show reborrows for all expressions, but usually that is just noise to the user
+ // and the main point here is to show why "moving" a mutable reference doesn't necessarily move it
+ ast::Expr::PathExpr(_) => reborrow_hints(hints, sema, config, &expr),
+ _ => None,
+ }
+ },
+ ast::Pat(it) => {
+ binding_mode_hints(hints, sema, config, &it);
+ if let ast::Pat::IdentPat(it) = it {
+ bind_pat_hints(hints, sema, config, file_id, &it);
+ }
+ Some(())
+ },
+ ast::Item(it) => match it {
+ // FIXME: record impl lifetimes so they aren't being reused in assoc item lifetime inlay hints
+ ast::Item::Impl(_) => None,
+ ast::Item::Fn(it) => fn_lifetime_fn_hints(hints, config, it),
+ // static type elisions
+ ast::Item::Static(it) => implicit_static_hints(hints, config, Either::Left(it)),
+ ast::Item::Const(it) => implicit_static_hints(hints, config, Either::Right(it)),
+ _ => None,
+ },
+ // FIXME: fn-ptr type, dyn fn type, and trait object type elisions
+ ast::Type(_) => None,
+ _ => None,
+ }
+ };
+}
+
+fn closing_brace_hints(
+ acc: &mut Vec<InlayHint>,
+ sema: &Semantics<'_, RootDatabase>,
+ config: &InlayHintsConfig,
+ file_id: FileId,
+ node: SyntaxNode,
+) -> Option<()> {
+ let min_lines = config.closing_brace_hints_min_lines?;
+
+ let name = |it: ast::Name| it.syntax().text_range();
+
+ let mut closing_token;
+ let (label, name_range) = if let Some(item_list) = ast::AssocItemList::cast(node.clone()) {
+ closing_token = item_list.r_curly_token()?;
+
+ let parent = item_list.syntax().parent()?;
+ match_ast! {
+ match parent {
+ ast::Impl(imp) => {
+ let imp = sema.to_def(&imp)?;
+ let ty = imp.self_ty(sema.db);
+ let trait_ = imp.trait_(sema.db);
+ let hint_text = match trait_ {
+ Some(tr) => format!("impl {} for {}", tr.name(sema.db), ty.display_truncated(sema.db, config.max_length)),
+ None => format!("impl {}", ty.display_truncated(sema.db, config.max_length)),
+ };
+ (hint_text, None)
+ },
+ ast::Trait(tr) => {
+ (format!("trait {}", tr.name()?), tr.name().map(name))
+ },
+ _ => return None,
+ }
+ }
+ } else if let Some(list) = ast::ItemList::cast(node.clone()) {
+ closing_token = list.r_curly_token()?;
+
+ let module = ast::Module::cast(list.syntax().parent()?)?;
+ (format!("mod {}", module.name()?), module.name().map(name))
+ } else if let Some(block) = ast::BlockExpr::cast(node.clone()) {
+ closing_token = block.stmt_list()?.r_curly_token()?;
+
+ let parent = block.syntax().parent()?;
+ match_ast! {
+ match parent {
+ ast::Fn(it) => {
+ // FIXME: this could include parameters, but `HirDisplay` prints too much info
+ // and doesn't respect the max length either, so the hints end up way too long
+ (format!("fn {}", it.name()?), it.name().map(name))
+ },
+ ast::Static(it) => (format!("static {}", it.name()?), it.name().map(name)),
+ ast::Const(it) => {
+ if it.underscore_token().is_some() {
+ ("const _".into(), None)
+ } else {
+ (format!("const {}", it.name()?), it.name().map(name))
+ }
+ },
+ _ => return None,
+ }
+ }
+ } else if let Some(mac) = ast::MacroCall::cast(node.clone()) {
+ let last_token = mac.syntax().last_token()?;
+ if last_token.kind() != T![;] && last_token.kind() != SyntaxKind::R_CURLY {
+ return None;
+ }
+ closing_token = last_token;
+
+ (
+ format!("{}!", mac.path()?),
+ mac.path().and_then(|it| it.segment()).map(|it| it.syntax().text_range()),
+ )
+ } else {
+ return None;
+ };
+
+ if let Some(mut next) = closing_token.next_token() {
+ if next.kind() == T![;] {
+ if let Some(tok) = next.next_token() {
+ closing_token = next;
+ next = tok;
+ }
+ }
+ if !(next.kind() == SyntaxKind::WHITESPACE && next.text().contains('\n')) {
+ // Only display the hint if the `}` is the last token on the line
+ return None;
+ }
+ }
+
+ let mut lines = 1;
+ node.text().for_each_chunk(|s| lines += s.matches('\n').count());
+ if lines < min_lines {
+ return None;
+ }
+
+ let linked_location = name_range.map(|range| FileRange { file_id, range });
+ acc.push(InlayHint {
+ range: closing_token.text_range(),
+ kind: InlayKind::ClosingBraceHint,
+ label: InlayHintLabel { parts: vec![InlayHintLabelPart { text: label, linked_location }] },
+ tooltip: None, // provided by label part location
+ });
+
+ None
+}
+
+fn implicit_static_hints(
+ acc: &mut Vec<InlayHint>,
+ config: &InlayHintsConfig,
+ statik_or_const: Either<ast::Static, ast::Const>,
+) -> Option<()> {
+ if config.lifetime_elision_hints != LifetimeElisionHints::Always {
+ return None;
+ }
+
+ if let Either::Right(it) = &statik_or_const {
+ if ast::AssocItemList::can_cast(
+ it.syntax().parent().map_or(SyntaxKind::EOF, |it| it.kind()),
+ ) {
+ return None;
+ }
+ }
+
+ if let Some(ast::Type::RefType(ty)) = statik_or_const.either(|it| it.ty(), |it| it.ty()) {
+ if ty.lifetime().is_none() {
+ let t = ty.amp_token()?;
+ acc.push(InlayHint {
+ range: t.text_range(),
+ kind: InlayKind::LifetimeHint,
+ label: "'static".to_owned().into(),
+ tooltip: Some(InlayTooltip::String("Elided static lifetime".into())),
+ });
+ }
+ }
+
+ Some(())
+}
+
+fn fn_lifetime_fn_hints(
+ acc: &mut Vec<InlayHint>,
+ config: &InlayHintsConfig,
+ func: ast::Fn,
+) -> Option<()> {
+ if config.lifetime_elision_hints == LifetimeElisionHints::Never {
+ return None;
+ }
+
+ let mk_lt_hint = |t: SyntaxToken, label: String| InlayHint {
+ range: t.text_range(),
+ kind: InlayKind::LifetimeHint,
+ label: label.into(),
+ tooltip: Some(InlayTooltip::String("Elided lifetime".into())),
+ };
+
+ let param_list = func.param_list()?;
+ let generic_param_list = func.generic_param_list();
+ let ret_type = func.ret_type();
+ let self_param = param_list.self_param().filter(|it| it.amp_token().is_some());
+
+ let is_elided = |lt: &Option<ast::Lifetime>| match lt {
+ Some(lt) => matches!(lt.text().as_str(), "'_"),
+ None => true,
+ };
+
+ let potential_lt_refs = {
+ let mut acc: Vec<_> = vec![];
+ if let Some(self_param) = &self_param {
+ let lifetime = self_param.lifetime();
+ let is_elided = is_elided(&lifetime);
+ acc.push((None, self_param.amp_token(), lifetime, is_elided));
+ }
+ param_list.params().filter_map(|it| Some((it.pat(), it.ty()?))).for_each(|(pat, ty)| {
+ // FIXME: check path types
+ walk_ty(&ty, &mut |ty| match ty {
+ ast::Type::RefType(r) => {
+ let lifetime = r.lifetime();
+ let is_elided = is_elided(&lifetime);
+ acc.push((
+ pat.as_ref().and_then(|it| match it {
+ ast::Pat::IdentPat(p) => p.name(),
+ _ => None,
+ }),
+ r.amp_token(),
+ lifetime,
+ is_elided,
+ ))
+ }
+ _ => (),
+ })
+ });
+ acc
+ };
+
+ // allocate names
+ let mut gen_idx_name = {
+ let mut gen = (0u8..).map(|idx| match idx {
+ idx if idx < 10 => SmolStr::from_iter(['\'', (idx + 48) as char]),
+ idx => format!("'{idx}").into(),
+ });
+ move || gen.next().unwrap_or_default()
+ };
+ let mut allocated_lifetimes = vec![];
+
+ let mut used_names: FxHashMap<SmolStr, usize> =
+ match config.param_names_for_lifetime_elision_hints {
+ true => generic_param_list
+ .iter()
+ .flat_map(|gpl| gpl.lifetime_params())
+ .filter_map(|param| param.lifetime())
+ .filter_map(|lt| Some((SmolStr::from(lt.text().as_str().get(1..)?), 0)))
+ .collect(),
+ false => Default::default(),
+ };
+ {
+ let mut potential_lt_refs = potential_lt_refs.iter().filter(|&&(.., is_elided)| is_elided);
+ if let Some(_) = &self_param {
+ if let Some(_) = potential_lt_refs.next() {
+ allocated_lifetimes.push(if config.param_names_for_lifetime_elision_hints {
+ // self can't be used as a lifetime, so no need to check for collisions
+ "'self".into()
+ } else {
+ gen_idx_name()
+ });
+ }
+ }
+ potential_lt_refs.for_each(|(name, ..)| {
+ let name = match name {
+ Some(it) if config.param_names_for_lifetime_elision_hints => {
+ if let Some(c) = used_names.get_mut(it.text().as_str()) {
+ *c += 1;
+ SmolStr::from(format!("'{text}{c}", text = it.text().as_str()))
+ } else {
+ used_names.insert(it.text().as_str().into(), 0);
+ SmolStr::from_iter(["\'", it.text().as_str()])
+ }
+ }
+ _ => gen_idx_name(),
+ };
+ allocated_lifetimes.push(name);
+ });
+ }
+
+ // fetch output lifetime if elision rule applies
+ let output = match potential_lt_refs.as_slice() {
+ [(_, _, lifetime, _), ..] if self_param.is_some() || potential_lt_refs.len() == 1 => {
+ match lifetime {
+ Some(lt) => match lt.text().as_str() {
+ "'_" => allocated_lifetimes.get(0).cloned(),
+ "'static" => None,
+ name => Some(name.into()),
+ },
+ None => allocated_lifetimes.get(0).cloned(),
+ }
+ }
+ [..] => None,
+ };
+
+ if allocated_lifetimes.is_empty() && output.is_none() {
+ return None;
+ }
+
+ // apply hints
+ // apply output if required
+ let mut is_trivial = true;
+ if let (Some(output_lt), Some(r)) = (&output, ret_type) {
+ if let Some(ty) = r.ty() {
+ walk_ty(&ty, &mut |ty| match ty {
+ ast::Type::RefType(ty) if ty.lifetime().is_none() => {
+ if let Some(amp) = ty.amp_token() {
+ is_trivial = false;
+ acc.push(mk_lt_hint(amp, output_lt.to_string()));
+ }
+ }
+ _ => (),
+ })
+ }
+ }
+
+ if config.lifetime_elision_hints == LifetimeElisionHints::SkipTrivial && is_trivial {
+ return None;
+ }
+
+ let mut a = allocated_lifetimes.iter();
+ for (_, amp_token, _, is_elided) in potential_lt_refs {
+ if is_elided {
+ let t = amp_token?;
+ let lt = a.next()?;
+ acc.push(mk_lt_hint(t, lt.to_string()));
+ }
+ }
+
+ // generate generic param list things
+ match (generic_param_list, allocated_lifetimes.as_slice()) {
+ (_, []) => (),
+ (Some(gpl), allocated_lifetimes) => {
+ let angle_tok = gpl.l_angle_token()?;
+ let is_empty = gpl.generic_params().next().is_none();
+ acc.push(InlayHint {
+ range: angle_tok.text_range(),
+ kind: InlayKind::LifetimeHint,
+ label: format!(
+ "{}{}",
+ allocated_lifetimes.iter().format(", "),
+ if is_empty { "" } else { ", " }
+ )
+ .into(),
+ tooltip: Some(InlayTooltip::String("Elided lifetimes".into())),
+ });
+ }
+ (None, allocated_lifetimes) => acc.push(InlayHint {
+ range: func.name()?.syntax().text_range(),
+ kind: InlayKind::GenericParamListHint,
+ label: format!("<{}>", allocated_lifetimes.iter().format(", "),).into(),
+ tooltip: Some(InlayTooltip::String("Elided lifetimes".into())),
+ }),
+ }
+ Some(())
+}
+
+fn closure_ret_hints(
+ acc: &mut Vec<InlayHint>,
+ sema: &Semantics<'_, RootDatabase>,
+ famous_defs: &FamousDefs<'_, '_>,
+ config: &InlayHintsConfig,
+ file_id: FileId,
+ closure: ast::ClosureExpr,
+) -> Option<()> {
+ if config.closure_return_type_hints == ClosureReturnTypeHints::Never {
+ return None;
+ }
+
+ if closure.ret_type().is_some() {
+ return None;
+ }
+
+ if !closure_has_block_body(&closure)
+ && config.closure_return_type_hints == ClosureReturnTypeHints::WithBlock
+ {
+ return None;
+ }
+
+ let param_list = closure.param_list()?;
+
+ let closure = sema.descend_node_into_attributes(closure.clone()).pop()?;
+ let ty = sema.type_of_expr(&ast::Expr::ClosureExpr(closure))?.adjusted();
+ let callable = ty.as_callable(sema.db)?;
+ let ty = callable.return_type();
+ if ty.is_unit() {
+ return None;
+ }
+ acc.push(InlayHint {
+ range: param_list.syntax().text_range(),
+ kind: InlayKind::ClosureReturnTypeHint,
+ label: hint_iterator(sema, &famous_defs, config, &ty)
+ .unwrap_or_else(|| ty.display_truncated(sema.db, config.max_length).to_string())
+ .into(),
+ tooltip: Some(InlayTooltip::HoverRanged(file_id, param_list.syntax().text_range())),
+ });
+ Some(())
+}
+
+fn reborrow_hints(
+ acc: &mut Vec<InlayHint>,
+ sema: &Semantics<'_, RootDatabase>,
+ config: &InlayHintsConfig,
+ expr: &ast::Expr,
+) -> Option<()> {
+ if config.reborrow_hints == ReborrowHints::Never {
+ return None;
+ }
+
+ let descended = sema.descend_node_into_attributes(expr.clone()).pop();
+ let desc_expr = descended.as_ref().unwrap_or(expr);
+ let mutability = sema.is_implicit_reborrow(desc_expr)?;
+ let label = match mutability {
+ hir::Mutability::Shared if config.reborrow_hints != ReborrowHints::MutableOnly => "&*",
+ hir::Mutability::Mut => "&mut *",
+ _ => return None,
+ };
+ acc.push(InlayHint {
+ range: expr.syntax().text_range(),
+ kind: InlayKind::ImplicitReborrowHint,
+ label: label.to_string().into(),
+ tooltip: Some(InlayTooltip::String("Compiler inserted reborrow".into())),
+ });
+ Some(())
+}
+
+fn chaining_hints(
+ acc: &mut Vec<InlayHint>,
+ sema: &Semantics<'_, RootDatabase>,
+ famous_defs: &FamousDefs<'_, '_>,
+ config: &InlayHintsConfig,
+ file_id: FileId,
+ expr: &ast::Expr,
+) -> Option<()> {
+ if !config.chaining_hints {
+ return None;
+ }
+
+ if matches!(expr, ast::Expr::RecordExpr(_)) {
+ return None;
+ }
+
+ let descended = sema.descend_node_into_attributes(expr.clone()).pop();
+ let desc_expr = descended.as_ref().unwrap_or(expr);
+
+ let mut tokens = expr
+ .syntax()
+ .siblings_with_tokens(Direction::Next)
+ .filter_map(NodeOrToken::into_token)
+ .filter(|t| match t.kind() {
+ SyntaxKind::WHITESPACE if !t.text().contains('\n') => false,
+ SyntaxKind::COMMENT => false,
+ _ => true,
+ });
+
+ // Chaining can be defined as an expression whose next sibling tokens are newline and dot
+ // Ignoring extra whitespace and comments
+ let next = tokens.next()?.kind();
+ if next == SyntaxKind::WHITESPACE {
+ let mut next_next = tokens.next()?.kind();
+ while next_next == SyntaxKind::WHITESPACE {
+ next_next = tokens.next()?.kind();
+ }
+ if next_next == T![.] {
+ let ty = sema.type_of_expr(desc_expr)?.original;
+ if ty.is_unknown() {
+ return None;
+ }
+ if matches!(expr, ast::Expr::PathExpr(_)) {
+ if let Some(hir::Adt::Struct(st)) = ty.as_adt() {
+ if st.fields(sema.db).is_empty() {
+ return None;
+ }
+ }
+ }
+ acc.push(InlayHint {
+ range: expr.syntax().text_range(),
+ kind: InlayKind::ChainingHint,
+ label: hint_iterator(sema, &famous_defs, config, &ty)
+ .unwrap_or_else(|| ty.display_truncated(sema.db, config.max_length).to_string())
+ .into(),
+ tooltip: Some(InlayTooltip::HoverRanged(file_id, expr.syntax().text_range())),
+ });
+ }
+ }
+ Some(())
+}
+
+fn param_name_hints(
+ acc: &mut Vec<InlayHint>,
+ sema: &Semantics<'_, RootDatabase>,
+ config: &InlayHintsConfig,
+ expr: ast::Expr,
+) -> Option<()> {
+ if !config.parameter_hints {
+ return None;
+ }
+
+ let (callable, arg_list) = get_callable(sema, &expr)?;
+ let hints = callable
+ .params(sema.db)
+ .into_iter()
+ .zip(arg_list.args())
+ .filter_map(|((param, _ty), arg)| {
+ // Only annotate hints for expressions that exist in the original file
+ let range = sema.original_range_opt(arg.syntax())?;
+ let (param_name, name_syntax) = match param.as_ref()? {
+ Either::Left(pat) => ("self".to_string(), pat.name()),
+ Either::Right(pat) => match pat {
+ ast::Pat::IdentPat(it) => (it.name()?.to_string(), it.name()),
+ _ => return None,
+ },
+ };
+ Some((name_syntax, param_name, arg, range))
+ })
+ .filter(|(_, param_name, arg, _)| {
+ !should_hide_param_name_hint(sema, &callable, param_name, arg)
+ })
+ .map(|(param, param_name, _, FileRange { range, .. })| {
+ let mut tooltip = None;
+ if let Some(name) = param {
+ if let hir::CallableKind::Function(f) = callable.kind() {
+ // assert the file is cached so we can map out of macros
+ if let Some(_) = sema.source(f) {
+ tooltip = sema.original_range_opt(name.syntax());
+ }
+ }
+ }
+
+ InlayHint {
+ range,
+ kind: InlayKind::ParameterHint,
+ label: param_name.into(),
+ tooltip: tooltip.map(|it| InlayTooltip::HoverOffset(it.file_id, it.range.start())),
+ }
+ });
+
+ acc.extend(hints);
+ Some(())
+}
+
+fn binding_mode_hints(
+ acc: &mut Vec<InlayHint>,
+ sema: &Semantics<'_, RootDatabase>,
+ config: &InlayHintsConfig,
+ pat: &ast::Pat,
+) -> Option<()> {
+ if !config.binding_mode_hints {
+ return None;
+ }
+
+ let range = pat.syntax().text_range();
+ sema.pattern_adjustments(&pat).iter().for_each(|ty| {
+ let reference = ty.is_reference();
+ let mut_reference = ty.is_mutable_reference();
+ let r = match (reference, mut_reference) {
+ (true, true) => "&mut",
+ (true, false) => "&",
+ _ => return,
+ };
+ acc.push(InlayHint {
+ range,
+ kind: InlayKind::BindingModeHint,
+ label: r.to_string().into(),
+ tooltip: Some(InlayTooltip::String("Inferred binding mode".into())),
+ });
+ });
+ match pat {
+ ast::Pat::IdentPat(pat) if pat.ref_token().is_none() && pat.mut_token().is_none() => {
+ let bm = sema.binding_mode_of_pat(pat)?;
+ let bm = match bm {
+ hir::BindingMode::Move => return None,
+ hir::BindingMode::Ref(Mutability::Mut) => "ref mut",
+ hir::BindingMode::Ref(Mutability::Shared) => "ref",
+ };
+ acc.push(InlayHint {
+ range,
+ kind: InlayKind::BindingModeHint,
+ label: bm.to_string().into(),
+ tooltip: Some(InlayTooltip::String("Inferred binding mode".into())),
+ });
+ }
+ _ => (),
+ }
+
+ Some(())
+}
+
+fn bind_pat_hints(
+ acc: &mut Vec<InlayHint>,
+ sema: &Semantics<'_, RootDatabase>,
+ config: &InlayHintsConfig,
+ file_id: FileId,
+ pat: &ast::IdentPat,
+) -> Option<()> {
+ if !config.type_hints {
+ return None;
+ }
+
+ let descended = sema.descend_node_into_attributes(pat.clone()).pop();
+ let desc_pat = descended.as_ref().unwrap_or(pat);
+ let ty = sema.type_of_pat(&desc_pat.clone().into())?.original;
+
+ if should_not_display_type_hint(sema, config, pat, &ty) {
+ return None;
+ }
+
+ let krate = sema.scope(desc_pat.syntax())?.krate();
+ let famous_defs = FamousDefs(sema, krate);
+ let label = hint_iterator(sema, &famous_defs, config, &ty);
+
+ let label = match label {
+ Some(label) => label,
+ None => {
+ let ty_name = ty.display_truncated(sema.db, config.max_length).to_string();
+ if config.hide_named_constructor_hints
+ && is_named_constructor(sema, pat, &ty_name).is_some()
+ {
+ return None;
+ }
+ ty_name
+ }
+ };
+
+ acc.push(InlayHint {
+ range: match pat.name() {
+ Some(name) => name.syntax().text_range(),
+ None => pat.syntax().text_range(),
+ },
+ kind: InlayKind::TypeHint,
+ label: label.into(),
+ tooltip: pat
+ .name()
+ .map(|it| it.syntax().text_range())
+ .map(|it| InlayTooltip::HoverRanged(file_id, it)),
+ });
+
+ Some(())
+}
+
+fn is_named_constructor(
+ sema: &Semantics<'_, RootDatabase>,
+ pat: &ast::IdentPat,
+ ty_name: &str,
+) -> Option<()> {
+ let let_node = pat.syntax().parent()?;
+ let expr = match_ast! {
+ match let_node {
+ ast::LetStmt(it) => it.initializer(),
+ ast::LetExpr(it) => it.expr(),
+ _ => None,
+ }
+ }?;
+
+ let expr = sema.descend_node_into_attributes(expr.clone()).pop().unwrap_or(expr);
+ // unwrap postfix expressions
+ let expr = match expr {
+ ast::Expr::TryExpr(it) => it.expr(),
+ ast::Expr::AwaitExpr(it) => it.expr(),
+ expr => Some(expr),
+ }?;
+ let expr = match expr {
+ ast::Expr::CallExpr(call) => match call.expr()? {
+ ast::Expr::PathExpr(path) => path,
+ _ => return None,
+ },
+ ast::Expr::PathExpr(path) => path,
+ _ => return None,
+ };
+ let path = expr.path()?;
+
+ let callable = sema.type_of_expr(&ast::Expr::PathExpr(expr))?.original.as_callable(sema.db);
+ let callable_kind = callable.map(|it| it.kind());
+ let qual_seg = match callable_kind {
+ Some(hir::CallableKind::Function(_) | hir::CallableKind::TupleEnumVariant(_)) => {
+ path.qualifier()?.segment()
+ }
+ _ => path.segment(),
+ }?;
+
+ let ctor_name = match qual_seg.kind()? {
+ ast::PathSegmentKind::Name(name_ref) => {
+ match qual_seg.generic_arg_list().map(|it| it.generic_args()) {
+ Some(generics) => format!("{}<{}>", name_ref, generics.format(", ")),
+ None => name_ref.to_string(),
+ }
+ }
+ ast::PathSegmentKind::Type { type_ref: Some(ty), trait_ref: None } => ty.to_string(),
+ _ => return None,
+ };
+ (ctor_name == ty_name).then(|| ())
+}
+
+/// Checks if the type is an Iterator from std::iter and replaces its hint with an `impl Iterator<Item = Ty>`.
+fn hint_iterator(
+ sema: &Semantics<'_, RootDatabase>,
+ famous_defs: &FamousDefs<'_, '_>,
+ config: &InlayHintsConfig,
+ ty: &hir::Type,
+) -> Option<String> {
+ let db = sema.db;
+ let strukt = ty.strip_references().as_adt()?;
+ let krate = strukt.module(db).krate();
+ if krate != famous_defs.core()? {
+ return None;
+ }
+ let iter_trait = famous_defs.core_iter_Iterator()?;
+ let iter_mod = famous_defs.core_iter()?;
+
+ // Assert that this struct comes from `core::iter`.
+ if !(strukt.visibility(db) == hir::Visibility::Public
+ && strukt.module(db).path_to_root(db).contains(&iter_mod))
+ {
+ return None;
+ }
+
+ if ty.impls_trait(db, iter_trait, &[]) {
+ let assoc_type_item = iter_trait.items(db).into_iter().find_map(|item| match item {
+ hir::AssocItem::TypeAlias(alias) if alias.name(db) == known::Item => Some(alias),
+ _ => None,
+ })?;
+ if let Some(ty) = ty.normalize_trait_assoc_type(db, &[], assoc_type_item) {
+ const LABEL_START: &str = "impl Iterator<Item = ";
+ const LABEL_END: &str = ">";
+
+ let ty_display = hint_iterator(sema, famous_defs, config, &ty)
+ .map(|assoc_type_impl| assoc_type_impl.to_string())
+ .unwrap_or_else(|| {
+ ty.display_truncated(
+ db,
+ config
+ .max_length
+ .map(|len| len.saturating_sub(LABEL_START.len() + LABEL_END.len())),
+ )
+ .to_string()
+ });
+ return Some(format!("{}{}{}", LABEL_START, ty_display, LABEL_END));
+ }
+ }
+
+ None
+}
+
+fn pat_is_enum_variant(db: &RootDatabase, bind_pat: &ast::IdentPat, pat_ty: &hir::Type) -> bool {
+ if let Some(hir::Adt::Enum(enum_data)) = pat_ty.as_adt() {
+ let pat_text = bind_pat.to_string();
+ enum_data
+ .variants(db)
+ .into_iter()
+ .map(|variant| variant.name(db).to_smol_str())
+ .any(|enum_name| enum_name == pat_text)
+ } else {
+ false
+ }
+}
+
+fn should_not_display_type_hint(
+ sema: &Semantics<'_, RootDatabase>,
+ config: &InlayHintsConfig,
+ bind_pat: &ast::IdentPat,
+ pat_ty: &hir::Type,
+) -> bool {
+ let db = sema.db;
+
+ if pat_ty.is_unknown() {
+ return true;
+ }
+
+ if let Some(hir::Adt::Struct(s)) = pat_ty.as_adt() {
+ if s.fields(db).is_empty() && s.name(db).to_smol_str() == bind_pat.to_string() {
+ return true;
+ }
+ }
+
+ if config.hide_closure_initialization_hints {
+ if let Some(parent) = bind_pat.syntax().parent() {
+ if let Some(it) = ast::LetStmt::cast(parent.clone()) {
+ if let Some(ast::Expr::ClosureExpr(closure)) = it.initializer() {
+ if closure_has_block_body(&closure) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ for node in bind_pat.syntax().ancestors() {
+ match_ast! {
+ match node {
+ ast::LetStmt(it) => return it.ty().is_some(),
+ // FIXME: We might wanna show type hints in parameters for non-top level patterns as well
+ ast::Param(it) => return it.ty().is_some(),
+ ast::MatchArm(_) => return pat_is_enum_variant(db, bind_pat, pat_ty),
+ ast::LetExpr(_) => return pat_is_enum_variant(db, bind_pat, pat_ty),
+ ast::IfExpr(_) => return false,
+ ast::WhileExpr(_) => return false,
+ ast::ForExpr(it) => {
+ // We *should* display hint only if user provided "in {expr}" and we know the type of expr (and it's not unit).
+ // Type of expr should be iterable.
+ return it.in_token().is_none() ||
+ it.iterable()
+ .and_then(|iterable_expr| sema.type_of_expr(&iterable_expr))
+ .map(TypeInfo::original)
+ .map_or(true, |iterable_ty| iterable_ty.is_unknown() || iterable_ty.is_unit())
+ },
+ _ => (),
+ }
+ }
+ }
+ false
+}
+
+fn closure_has_block_body(closure: &ast::ClosureExpr) -> bool {
+ matches!(closure.body(), Some(ast::Expr::BlockExpr(_)))
+}
+
+fn should_hide_param_name_hint(
+ sema: &Semantics<'_, RootDatabase>,
+ callable: &hir::Callable,
+ param_name: &str,
+ argument: &ast::Expr,
+) -> bool {
+ // These are to be tested in the `parameter_hint_heuristics` test
+ // hide when:
+ // - the parameter name is a suffix of the function's name
+ // - the argument is a qualified constructing or call expression where the qualifier is an ADT
+ // - exact argument<->parameter match(ignoring leading underscore) or parameter is a prefix/suffix
+ // of argument with _ splitting it off
+ // - param starts with `ra_fixture`
+ // - param is a well known name in a unary function
+
+ let param_name = param_name.trim_start_matches('_');
+ if param_name.is_empty() {
+ return true;
+ }
+
+ if matches!(argument, ast::Expr::PrefixExpr(prefix) if prefix.op_kind() == Some(UnaryOp::Not)) {
+ return false;
+ }
+
+ let fn_name = match callable.kind() {
+ hir::CallableKind::Function(it) => Some(it.name(sema.db).to_smol_str()),
+ _ => None,
+ };
+ let fn_name = fn_name.as_deref();
+ is_param_name_suffix_of_fn_name(param_name, callable, fn_name)
+ || is_argument_similar_to_param_name(argument, param_name)
+ || param_name.starts_with("ra_fixture")
+ || (callable.n_params() == 1 && is_obvious_param(param_name))
+ || is_adt_constructor_similar_to_param_name(sema, argument, param_name)
+}
+
+fn is_argument_similar_to_param_name(argument: &ast::Expr, param_name: &str) -> bool {
+ // check whether param_name and argument are the same or
+ // whether param_name is a prefix/suffix of argument(split at `_`)
+ let argument = match get_string_representation(argument) {
+ Some(argument) => argument,
+ None => return false,
+ };
+
+ // std is honestly too panic happy...
+ let str_split_at = |str: &str, at| str.is_char_boundary(at).then(|| argument.split_at(at));
+
+ let param_name = param_name.trim_start_matches('_');
+ let argument = argument.trim_start_matches('_');
+
+ match str_split_at(argument, param_name.len()) {
+ Some((prefix, rest)) if prefix.eq_ignore_ascii_case(param_name) => {
+ return rest.is_empty() || rest.starts_with('_');
+ }
+ _ => (),
+ }
+ match argument.len().checked_sub(param_name.len()).and_then(|at| str_split_at(argument, at)) {
+ Some((rest, suffix)) if param_name.eq_ignore_ascii_case(suffix) => {
+ return rest.is_empty() || rest.ends_with('_');
+ }
+ _ => (),
+ }
+ false
+}
+
+/// Hide the parameter name of a unary function if it is a `_` - prefixed suffix of the function's name, or equal.
+///
+/// `fn strip_suffix(suffix)` will be hidden.
+/// `fn stripsuffix(suffix)` will not be hidden.
+fn is_param_name_suffix_of_fn_name(
+ param_name: &str,
+ callable: &Callable,
+ fn_name: Option<&str>,
+) -> bool {
+ match (callable.n_params(), fn_name) {
+ (1, Some(function)) => {
+ function == param_name
+ || function
+ .len()
+ .checked_sub(param_name.len())
+ .and_then(|at| function.is_char_boundary(at).then(|| function.split_at(at)))
+ .map_or(false, |(prefix, suffix)| {
+ suffix.eq_ignore_ascii_case(param_name) && prefix.ends_with('_')
+ })
+ }
+ _ => false,
+ }
+}
+
+fn is_adt_constructor_similar_to_param_name(
+ sema: &Semantics<'_, RootDatabase>,
+ argument: &ast::Expr,
+ param_name: &str,
+) -> bool {
+ let path = match argument {
+ ast::Expr::CallExpr(c) => c.expr().and_then(|e| match e {
+ ast::Expr::PathExpr(p) => p.path(),
+ _ => None,
+ }),
+ ast::Expr::PathExpr(p) => p.path(),
+ ast::Expr::RecordExpr(r) => r.path(),
+ _ => return false,
+ };
+ let path = match path {
+ Some(it) => it,
+ None => return false,
+ };
+ (|| match sema.resolve_path(&path)? {
+ hir::PathResolution::Def(hir::ModuleDef::Adt(_)) => {
+ Some(to_lower_snake_case(&path.segment()?.name_ref()?.text()) == param_name)
+ }
+ hir::PathResolution::Def(hir::ModuleDef::Function(_) | hir::ModuleDef::Variant(_)) => {
+ if to_lower_snake_case(&path.segment()?.name_ref()?.text()) == param_name {
+ return Some(true);
+ }
+ let qual = path.qualifier()?;
+ match sema.resolve_path(&qual)? {
+ hir::PathResolution::Def(hir::ModuleDef::Adt(_)) => {
+ Some(to_lower_snake_case(&qual.segment()?.name_ref()?.text()) == param_name)
+ }
+ _ => None,
+ }
+ }
+ _ => None,
+ })()
+ .unwrap_or(false)
+}
+
+fn get_string_representation(expr: &ast::Expr) -> Option<String> {
+ match expr {
+ ast::Expr::MethodCallExpr(method_call_expr) => {
+ let name_ref = method_call_expr.name_ref()?;
+ match name_ref.text().as_str() {
+ "clone" | "as_ref" => method_call_expr.receiver().map(|rec| rec.to_string()),
+ name_ref => Some(name_ref.to_owned()),
+ }
+ }
+ ast::Expr::MacroExpr(macro_expr) => {
+ Some(macro_expr.macro_call()?.path()?.segment()?.to_string())
+ }
+ ast::Expr::FieldExpr(field_expr) => Some(field_expr.name_ref()?.to_string()),
+ ast::Expr::PathExpr(path_expr) => Some(path_expr.path()?.segment()?.to_string()),
+ ast::Expr::PrefixExpr(prefix_expr) => get_string_representation(&prefix_expr.expr()?),
+ ast::Expr::RefExpr(ref_expr) => get_string_representation(&ref_expr.expr()?),
+ ast::Expr::CastExpr(cast_expr) => get_string_representation(&cast_expr.expr()?),
+ _ => None,
+ }
+}
+
+fn is_obvious_param(param_name: &str) -> bool {
+ // avoid displaying hints for common functions like map, filter, etc.
+ // or other obvious words used in std
+ let is_obvious_param_name =
+ matches!(param_name, "predicate" | "value" | "pat" | "rhs" | "other");
+ param_name.len() == 1 || is_obvious_param_name
+}
+
+fn get_callable(
+ sema: &Semantics<'_, RootDatabase>,
+ expr: &ast::Expr,
+) -> Option<(hir::Callable, ast::ArgList)> {
+ match expr {
+ ast::Expr::CallExpr(expr) => {
+ let descended = sema.descend_node_into_attributes(expr.clone()).pop();
+ let expr = descended.as_ref().unwrap_or(expr);
+ sema.type_of_expr(&expr.expr()?)?.original.as_callable(sema.db).zip(expr.arg_list())
+ }
+ ast::Expr::MethodCallExpr(expr) => {
+ let descended = sema.descend_node_into_attributes(expr.clone()).pop();
+ let expr = descended.as_ref().unwrap_or(expr);
+ sema.resolve_method_call_as_callable(expr).zip(expr.arg_list())
+ }
+ _ => None,
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use expect_test::{expect, Expect};
+ use ide_db::base_db::FileRange;
+ use itertools::Itertools;
+ use syntax::{TextRange, TextSize};
+ use test_utils::extract_annotations;
+
+ use crate::inlay_hints::ReborrowHints;
+ use crate::{fixture, inlay_hints::InlayHintsConfig, LifetimeElisionHints};
+
+ use super::ClosureReturnTypeHints;
+
+ const DISABLED_CONFIG: InlayHintsConfig = InlayHintsConfig {
+ render_colons: false,
+ type_hints: false,
+ parameter_hints: false,
+ chaining_hints: false,
+ lifetime_elision_hints: LifetimeElisionHints::Never,
+ closure_return_type_hints: ClosureReturnTypeHints::Never,
+ reborrow_hints: ReborrowHints::Always,
+ binding_mode_hints: false,
+ hide_named_constructor_hints: false,
+ hide_closure_initialization_hints: false,
+ param_names_for_lifetime_elision_hints: false,
+ max_length: None,
+ closing_brace_hints_min_lines: None,
+ };
+ const TEST_CONFIG: InlayHintsConfig = InlayHintsConfig {
+ type_hints: true,
+ parameter_hints: true,
+ chaining_hints: true,
+ reborrow_hints: ReborrowHints::Always,
+ closure_return_type_hints: ClosureReturnTypeHints::WithBlock,
+ binding_mode_hints: true,
+ lifetime_elision_hints: LifetimeElisionHints::Always,
+ ..DISABLED_CONFIG
+ };
+
+ #[track_caller]
+ fn check(ra_fixture: &str) {
+ check_with_config(TEST_CONFIG, ra_fixture);
+ }
+
+ #[track_caller]
+ fn check_params(ra_fixture: &str) {
+ check_with_config(
+ InlayHintsConfig { parameter_hints: true, ..DISABLED_CONFIG },
+ ra_fixture,
+ );
+ }
+
+ #[track_caller]
+ fn check_types(ra_fixture: &str) {
+ check_with_config(InlayHintsConfig { type_hints: true, ..DISABLED_CONFIG }, ra_fixture);
+ }
+
+ #[track_caller]
+ fn check_chains(ra_fixture: &str) {
+ check_with_config(InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG }, ra_fixture);
+ }
+
+ #[track_caller]
+ fn check_with_config(config: InlayHintsConfig, ra_fixture: &str) {
+ let (analysis, file_id) = fixture::file(ra_fixture);
+ let mut expected = extract_annotations(&*analysis.file_text(file_id).unwrap());
+ let inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap();
+ let actual = inlay_hints
+ .into_iter()
+ .map(|it| (it.range, it.label.to_string()))
+ .sorted_by_key(|(range, _)| range.start())
+ .collect::<Vec<_>>();
+ expected.sort_by_key(|(range, _)| range.start());
+
+ assert_eq!(expected, actual, "\nExpected:\n{:#?}\n\nActual:\n{:#?}", expected, actual);
+ }
+
+ #[track_caller]
+ fn check_expect(config: InlayHintsConfig, ra_fixture: &str, expect: Expect) {
+ let (analysis, file_id) = fixture::file(ra_fixture);
+ let inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap();
+ expect.assert_debug_eq(&inlay_hints)
+ }
+
+ #[test]
+ fn hints_disabled() {
+ check_with_config(
+ InlayHintsConfig { render_colons: true, ..DISABLED_CONFIG },
+ r#"
+fn foo(a: i32, b: i32) -> i32 { a + b }
+fn main() {
+ let _x = foo(4, 4);
+}"#,
+ );
+ }
+
+ // Parameter hint tests
+
+ #[test]
+ fn param_hints_only() {
+ check_params(
+ r#"
+fn foo(a: i32, b: i32) -> i32 { a + b }
+fn main() {
+ let _x = foo(
+ 4,
+ //^ a
+ 4,
+ //^ b
+ );
+}"#,
+ );
+ }
+
+ #[test]
+ fn param_hints_on_closure() {
+ check_params(
+ r#"
+fn main() {
+ let clo = |a: u8, b: u8| a + b;
+ clo(
+ 1,
+ //^ a
+ 2,
+ //^ b
+ );
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn param_name_similar_to_fn_name_still_hints() {
+ check_params(
+ r#"
+fn max(x: i32, y: i32) -> i32 { x + y }
+fn main() {
+ let _x = max(
+ 4,
+ //^ x
+ 4,
+ //^ y
+ );
+}"#,
+ );
+ }
+
+ #[test]
+ fn param_name_similar_to_fn_name() {
+ check_params(
+ r#"
+fn param_with_underscore(with_underscore: i32) -> i32 { with_underscore }
+fn main() {
+ let _x = param_with_underscore(
+ 4,
+ );
+}"#,
+ );
+ check_params(
+ r#"
+fn param_with_underscore(underscore: i32) -> i32 { underscore }
+fn main() {
+ let _x = param_with_underscore(
+ 4,
+ );
+}"#,
+ );
+ }
+
+ #[test]
+ fn param_name_same_as_fn_name() {
+ check_params(
+ r#"
+fn foo(foo: i32) -> i32 { foo }
+fn main() {
+ let _x = foo(
+ 4,
+ );
+}"#,
+ );
+ }
+
+ #[test]
+ fn never_hide_param_when_multiple_params() {
+ check_params(
+ r#"
+fn foo(foo: i32, bar: i32) -> i32 { bar + baz }
+fn main() {
+ let _x = foo(
+ 4,
+ //^ foo
+ 8,
+ //^ bar
+ );
+}"#,
+ );
+ }
+
+ #[test]
+ fn param_hints_look_through_as_ref_and_clone() {
+ check_params(
+ r#"
+fn foo(bar: i32, baz: f32) {}
+
+fn main() {
+ let bar = 3;
+ let baz = &"baz";
+ let fez = 1.0;
+ foo(bar.clone(), bar.clone());
+ //^^^^^^^^^^^ baz
+ foo(bar.as_ref(), bar.as_ref());
+ //^^^^^^^^^^^^ baz
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn self_param_hints() {
+ check_params(
+ r#"
+struct Foo;
+
+impl Foo {
+ fn foo(self: Self) {}
+ fn bar(self: &Self) {}
+}
+
+fn main() {
+ Foo::foo(Foo);
+ //^^^ self
+ Foo::bar(&Foo);
+ //^^^^ self
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn param_name_hints_show_for_literals() {
+ check_params(
+ r#"pub fn test(a: i32, b: i32) -> [i32; 2] { [a, b] }
+fn main() {
+ test(
+ 0xa_b,
+ //^^^^^ a
+ 0xa_b,
+ //^^^^^ b
+ );
+}"#,
+ )
+ }
+
+ #[test]
+ fn function_call_parameter_hint() {
+ check_params(
+ r#"
+//- minicore: option
+struct FileId {}
+struct SmolStr {}
+
+struct TextRange {}
+struct SyntaxKind {}
+struct NavigationTarget {}
+
+struct Test {}
+
+impl Test {
+ fn method(&self, mut param: i32) -> i32 { param * 2 }
+
+ fn from_syntax(
+ file_id: FileId,
+ name: SmolStr,
+ focus_range: Option<TextRange>,
+ full_range: TextRange,
+ kind: SyntaxKind,
+ docs: Option<String>,
+ ) -> NavigationTarget {
+ NavigationTarget {}
+ }
+}
+
+fn test_func(mut foo: i32, bar: i32, msg: &str, _: i32, last: i32) -> i32 {
+ foo + bar
+}
+
+fn main() {
+ let not_literal = 1;
+ let _: i32 = test_func(1, 2, "hello", 3, not_literal);
+ //^ foo ^ bar ^^^^^^^ msg ^^^^^^^^^^^ last
+ let t: Test = Test {};
+ t.method(123);
+ //^^^ param
+ Test::method(&t, 3456);
+ //^^ self ^^^^ param
+ Test::from_syntax(
+ FileId {},
+ "impl".into(),
+ //^^^^^^^^^^^^^ name
+ None,
+ //^^^^ focus_range
+ TextRange {},
+ //^^^^^^^^^^^^ full_range
+ SyntaxKind {},
+ //^^^^^^^^^^^^^ kind
+ None,
+ //^^^^ docs
+ );
+}"#,
+ );
+ }
+
+ #[test]
+ fn parameter_hint_heuristics() {
+ check_params(
+ r#"
+fn check(ra_fixture_thing: &str) {}
+
+fn map(f: i32) {}
+fn filter(predicate: i32) {}
+
+fn strip_suffix(suffix: &str) {}
+fn stripsuffix(suffix: &str) {}
+fn same(same: u32) {}
+fn same2(_same2: u32) {}
+
+fn enum_matches_param_name(completion_kind: CompletionKind) {}
+
+fn foo(param: u32) {}
+fn bar(param_eter: u32) {}
+
+enum CompletionKind {
+ Keyword,
+}
+
+fn non_ident_pat((a, b): (u32, u32)) {}
+
+fn main() {
+ const PARAM: u32 = 0;
+ foo(PARAM);
+ foo(!PARAM);
+ // ^^^^^^ param
+ check("");
+
+ map(0);
+ filter(0);
+
+ strip_suffix("");
+ stripsuffix("");
+ //^^ suffix
+ same(0);
+ same2(0);
+
+ enum_matches_param_name(CompletionKind::Keyword);
+
+ let param = 0;
+ foo(param);
+ foo(param as _);
+ let param_end = 0;
+ foo(param_end);
+ let start_param = 0;
+ foo(start_param);
+ let param2 = 0;
+ foo(param2);
+ //^^^^^^ param
+
+ macro_rules! param {
+ () => {};
+ };
+ foo(param!());
+
+ let param_eter = 0;
+ bar(param_eter);
+ let param_eter_end = 0;
+ bar(param_eter_end);
+ let start_param_eter = 0;
+ bar(start_param_eter);
+ let param_eter2 = 0;
+ bar(param_eter2);
+ //^^^^^^^^^^^ param_eter
+
+ non_ident_pat((0, 0));
+}"#,
+ );
+ }
+
+ // Type-Hint tests
+
+ #[test]
+ fn type_hints_only() {
+ check_types(
+ r#"
+fn foo(a: i32, b: i32) -> i32 { a + b }
+fn main() {
+ let _x = foo(4, 4);
+ //^^ i32
+}"#,
+ );
+ }
+
+ #[test]
+ fn type_hints_bindings_after_at() {
+ check_types(
+ r#"
+//- minicore: option
+fn main() {
+ let ref foo @ bar @ ref mut baz = 0;
+ //^^^ &i32
+ //^^^ i32
+ //^^^ &mut i32
+ let [x @ ..] = [0];
+ //^ [i32; 1]
+ if let x @ Some(_) = Some(0) {}
+ //^ Option<i32>
+ let foo @ (bar, baz) = (3, 3);
+ //^^^ (i32, i32)
+ //^^^ i32
+ //^^^ i32
+}"#,
+ );
+ }
+
+ #[test]
+ fn default_generic_types_should_not_be_displayed() {
+ check(
+ r#"
+struct Test<K, T = u8> { k: K, t: T }
+
+fn main() {
+ let zz = Test { t: 23u8, k: 33 };
+ //^^ Test<i32>
+ let zz_ref = &zz;
+ //^^^^^^ &Test<i32>
+ let test = || zz;
+ //^^^^ || -> Test<i32>
+}"#,
+ );
+ }
+
+ #[test]
+ fn shorten_iterators_in_associated_params() {
+ check_types(
+ r#"
+//- minicore: iterators
+use core::iter;
+
+pub struct SomeIter<T> {}
+
+impl<T> SomeIter<T> {
+ pub fn new() -> Self { SomeIter {} }
+ pub fn push(&mut self, t: T) {}
+}
+
+impl<T> Iterator for SomeIter<T> {
+ type Item = T;
+ fn next(&mut self) -> Option<Self::Item> {
+ None
+ }
+}
+
+fn main() {
+ let mut some_iter = SomeIter::new();
+ //^^^^^^^^^ SomeIter<Take<Repeat<i32>>>
+ some_iter.push(iter::repeat(2).take(2));
+ let iter_of_iters = some_iter.take(2);
+ //^^^^^^^^^^^^^ impl Iterator<Item = impl Iterator<Item = i32>>
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn iterator_hint_regression_issue_12674() {
+ // Ensure we don't crash while solving the projection type of iterators.
+ check_expect(
+ InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG },
+ r#"
+//- minicore: iterators
+struct S<T>(T);
+impl<T> S<T> {
+ fn iter(&self) -> Iter<'_, T> { loop {} }
+}
+struct Iter<'a, T: 'a>(&'a T);
+impl<'a, T> Iterator for Iter<'a, T> {
+ type Item = &'a T;
+ fn next(&mut self) -> Option<Self::Item> { loop {} }
+}
+struct Container<'a> {
+ elements: S<&'a str>,
+}
+struct SliceIter<'a, T>(&'a T);
+impl<'a, T> Iterator for SliceIter<'a, T> {
+ type Item = &'a T;
+ fn next(&mut self) -> Option<Self::Item> { loop {} }
+}
+
+fn main(a: SliceIter<'_, Container>) {
+ a
+ .filter_map(|c| Some(c.elements.iter().filter_map(|v| Some(v))))
+ .map(|e| e);
+}
+ "#,
+ expect![[r#"
+ [
+ InlayHint {
+ range: 484..554,
+ kind: ChainingHint,
+ label: [
+ "impl Iterator<Item = impl Iterator<Item = &&str>>",
+ ],
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 484..554,
+ ),
+ ),
+ },
+ InlayHint {
+ range: 484..485,
+ kind: ChainingHint,
+ label: [
+ "SliceIter<Container>",
+ ],
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 484..485,
+ ),
+ ),
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn infer_call_method_return_associated_types_with_generic() {
+ check_types(
+ r#"
+ pub trait Default {
+ fn default() -> Self;
+ }
+ pub trait Foo {
+ type Bar: Default;
+ }
+
+ pub fn quux<T: Foo>() -> T::Bar {
+ let y = Default::default();
+ //^ <T as Foo>::Bar
+
+ y
+ }
+ "#,
+ );
+ }
+
+ #[test]
+ fn fn_hints() {
+ check_types(
+ r#"
+//- minicore: fn, sized
+fn foo() -> impl Fn() { loop {} }
+fn foo1() -> impl Fn(f64) { loop {} }
+fn foo2() -> impl Fn(f64, f64) { loop {} }
+fn foo3() -> impl Fn(f64, f64) -> u32 { loop {} }
+fn foo4() -> &'static dyn Fn(f64, f64) -> u32 { loop {} }
+fn foo5() -> &'static dyn Fn(&'static dyn Fn(f64, f64) -> u32, f64) -> u32 { loop {} }
+fn foo6() -> impl Fn(f64, f64) -> u32 + Sized { loop {} }
+fn foo7() -> *const (impl Fn(f64, f64) -> u32 + Sized) { loop {} }
+
+fn main() {
+ let foo = foo();
+ // ^^^ impl Fn()
+ let foo = foo1();
+ // ^^^ impl Fn(f64)
+ let foo = foo2();
+ // ^^^ impl Fn(f64, f64)
+ let foo = foo3();
+ // ^^^ impl Fn(f64, f64) -> u32
+ let foo = foo4();
+ // ^^^ &dyn Fn(f64, f64) -> u32
+ let foo = foo5();
+ // ^^^ &dyn Fn(&dyn Fn(f64, f64) -> u32, f64) -> u32
+ let foo = foo6();
+ // ^^^ impl Fn(f64, f64) -> u32
+ let foo = foo7();
+ // ^^^ *const impl Fn(f64, f64) -> u32
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn check_hint_range_limit() {
+ let fixture = r#"
+ //- minicore: fn, sized
+ fn foo() -> impl Fn() { loop {} }
+ fn foo1() -> impl Fn(f64) { loop {} }
+ fn foo2() -> impl Fn(f64, f64) { loop {} }
+ fn foo3() -> impl Fn(f64, f64) -> u32 { loop {} }
+ fn foo4() -> &'static dyn Fn(f64, f64) -> u32 { loop {} }
+ fn foo5() -> &'static dyn Fn(&'static dyn Fn(f64, f64) -> u32, f64) -> u32 { loop {} }
+ fn foo6() -> impl Fn(f64, f64) -> u32 + Sized { loop {} }
+ fn foo7() -> *const (impl Fn(f64, f64) -> u32 + Sized) { loop {} }
+
+ fn main() {
+ let foo = foo();
+ let foo = foo1();
+ let foo = foo2();
+ // ^^^ impl Fn(f64, f64)
+ let foo = foo3();
+ // ^^^ impl Fn(f64, f64) -> u32
+ let foo = foo4();
+ let foo = foo5();
+ let foo = foo6();
+ let foo = foo7();
+ }
+ "#;
+ let (analysis, file_id) = fixture::file(fixture);
+ let expected = extract_annotations(&*analysis.file_text(file_id).unwrap());
+ let inlay_hints = analysis
+ .inlay_hints(
+ &InlayHintsConfig { type_hints: true, ..DISABLED_CONFIG },
+ file_id,
+ Some(FileRange {
+ file_id,
+ range: TextRange::new(TextSize::from(500), TextSize::from(600)),
+ }),
+ )
+ .unwrap();
+ let actual =
+ inlay_hints.into_iter().map(|it| (it.range, it.label.to_string())).collect::<Vec<_>>();
+ assert_eq!(expected, actual, "\nExpected:\n{:#?}\n\nActual:\n{:#?}", expected, actual);
+ }
+
+ #[test]
+ fn fn_hints_ptr_rpit_fn_parentheses() {
+ check_types(
+ r#"
+//- minicore: fn, sized
+trait Trait {}
+
+fn foo1() -> *const impl Fn() { loop {} }
+fn foo2() -> *const (impl Fn() + Sized) { loop {} }
+fn foo3() -> *const (impl Fn() + ?Sized) { loop {} }
+fn foo4() -> *const (impl Sized + Fn()) { loop {} }
+fn foo5() -> *const (impl ?Sized + Fn()) { loop {} }
+fn foo6() -> *const (impl Fn() + Trait) { loop {} }
+fn foo7() -> *const (impl Fn() + Sized + Trait) { loop {} }
+fn foo8() -> *const (impl Fn() + ?Sized + Trait) { loop {} }
+fn foo9() -> *const (impl Fn() -> u8 + ?Sized) { loop {} }
+fn foo10() -> *const (impl Fn() + Sized + ?Sized) { loop {} }
+
+fn main() {
+ let foo = foo1();
+ // ^^^ *const impl Fn()
+ let foo = foo2();
+ // ^^^ *const impl Fn()
+ let foo = foo3();
+ // ^^^ *const (impl Fn() + ?Sized)
+ let foo = foo4();
+ // ^^^ *const impl Fn()
+ let foo = foo5();
+ // ^^^ *const (impl Fn() + ?Sized)
+ let foo = foo6();
+ // ^^^ *const (impl Fn() + Trait)
+ let foo = foo7();
+ // ^^^ *const (impl Fn() + Trait)
+ let foo = foo8();
+ // ^^^ *const (impl Fn() + Trait + ?Sized)
+ let foo = foo9();
+ // ^^^ *const (impl Fn() -> u8 + ?Sized)
+ let foo = foo10();
+ // ^^^ *const impl Fn()
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn unit_structs_have_no_type_hints() {
+ check_types(
+ r#"
+//- minicore: result
+struct SyntheticSyntax;
+
+fn main() {
+ match Ok(()) {
+ Ok(_) => (),
+ Err(SyntheticSyntax) => (),
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn let_statement() {
+ check_types(
+ r#"
+#[derive(PartialEq)]
+enum Option<T> { None, Some(T) }
+
+#[derive(PartialEq)]
+struct Test { a: Option<u32>, b: u8 }
+
+fn main() {
+ struct InnerStruct {}
+
+ let test = 54;
+ //^^^^ i32
+ let test: i32 = 33;
+ let mut test = 33;
+ //^^^^ i32
+ let _ = 22;
+ let test = "test";
+ //^^^^ &str
+ let test = InnerStruct {};
+ //^^^^ InnerStruct
+
+ let test = unresolved();
+
+ let test = (42, 'a');
+ //^^^^ (i32, char)
+ let (a, (b, (c,)) = (2, (3, (9.2,));
+ //^ i32 ^ i32 ^ f64
+ let &x = &92;
+ //^ i32
+}"#,
+ );
+ }
+
+ #[test]
+ fn if_expr() {
+ check_types(
+ r#"
+//- minicore: option
+struct Test { a: Option<u32>, b: u8 }
+
+fn main() {
+ let test = Some(Test { a: Some(3), b: 1 });
+ //^^^^ Option<Test>
+ if let None = &test {};
+ if let test = &test {};
+ //^^^^ &Option<Test>
+ if let Some(test) = &test {};
+ //^^^^ &Test
+ if let Some(Test { a, b }) = &test {};
+ //^ &Option<u32> ^ &u8
+ if let Some(Test { a: x, b: y }) = &test {};
+ //^ &Option<u32> ^ &u8
+ if let Some(Test { a: Some(x), b: y }) = &test {};
+ //^ &u32 ^ &u8
+ if let Some(Test { a: None, b: y }) = &test {};
+ //^ &u8
+ if let Some(Test { b: y, .. }) = &test {};
+ //^ &u8
+ if test == None {}
+}"#,
+ );
+ }
+
+ #[test]
+ fn while_expr() {
+ check_types(
+ r#"
+//- minicore: option
+struct Test { a: Option<u32>, b: u8 }
+
+fn main() {
+ let test = Some(Test { a: Some(3), b: 1 });
+ //^^^^ Option<Test>
+ while let Some(Test { a: Some(x), b: y }) = &test {};
+ //^ &u32 ^ &u8
+}"#,
+ );
+ }
+
+ #[test]
+ fn match_arm_list() {
+ check_types(
+ r#"
+//- minicore: option
+struct Test { a: Option<u32>, b: u8 }
+
+fn main() {
+ match Some(Test { a: Some(3), b: 1 }) {
+ None => (),
+ test => (),
+ //^^^^ Option<Test>
+ Some(Test { a: Some(x), b: y }) => (),
+ //^ u32 ^ u8
+ _ => {}
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn complete_for_hint() {
+ check_types(
+ r#"
+//- minicore: iterator
+pub struct Vec<T> {}
+
+impl<T> Vec<T> {
+ pub fn new() -> Self { Vec {} }
+ pub fn push(&mut self, t: T) {}
+}
+
+impl<T> IntoIterator for Vec<T> {
++ type Item = T;
++ type IntoIter = IntoIter<T>;
++}
++
++struct IntoIter<T> {}
++
++impl<T> Iterator for IntoIter<T> {
++ type Item = T;
+}
+
+fn main() {
+ let mut data = Vec::new();
+ //^^^^ Vec<&str>
+ data.push("foo");
+ for i in data {
+ //^ &str
+ let z = i;
+ //^ &str
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn multi_dyn_trait_bounds() {
+ check_types(
+ r#"
+pub struct Vec<T> {}
+
+impl<T> Vec<T> {
+ pub fn new() -> Self { Vec {} }
+}
+
+pub struct Box<T> {}
+
+trait Display {}
+auto trait Sync {}
+
+fn main() {
+ // The block expression wrapping disables the constructor hint hiding logic
+ let _v = { Vec::<Box<&(dyn Display + Sync)>>::new() };
+ //^^ Vec<Box<&(dyn Display + Sync)>>
+ let _v = { Vec::<Box<*const (dyn Display + Sync)>>::new() };
+ //^^ Vec<Box<*const (dyn Display + Sync)>>
+ let _v = { Vec::<Box<dyn Display + Sync>>::new() };
+ //^^ Vec<Box<dyn Display + Sync>>
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn shorten_iterator_hints() {
+ check_types(
+ r#"
+//- minicore: iterators
+use core::iter;
+
+struct MyIter;
+
+impl Iterator for MyIter {
+ type Item = ();
+ fn next(&mut self) -> Option<Self::Item> {
+ None
+ }
+}
+
+fn main() {
+ let _x = MyIter;
+ //^^ MyIter
+ let _x = iter::repeat(0);
+ //^^ impl Iterator<Item = i32>
+ fn generic<T: Clone>(t: T) {
+ let _x = iter::repeat(t);
+ //^^ impl Iterator<Item = T>
+ let _chained = iter::repeat(t).take(10);
+ //^^^^^^^^ impl Iterator<Item = T>
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn skip_constructor_and_enum_type_hints() {
+ check_with_config(
+ InlayHintsConfig {
+ type_hints: true,
+ hide_named_constructor_hints: true,
+ ..DISABLED_CONFIG
+ },
+ r#"
+//- minicore: try, option
+use core::ops::ControlFlow;
+
+mod x {
+ pub mod y { pub struct Foo; }
+ pub struct Foo;
+ pub enum AnotherEnum {
+ Variant()
+ };
+}
+struct Struct;
+struct TupleStruct();
+
+impl Struct {
+ fn new() -> Self {
+ Struct
+ }
+ fn try_new() -> ControlFlow<(), Self> {
+ ControlFlow::Continue(Struct)
+ }
+}
+
+struct Generic<T>(T);
+impl Generic<i32> {
+ fn new() -> Self {
+ Generic(0)
+ }
+}
+
+enum Enum {
+ Variant(u32)
+}
+
+fn times2(value: i32) -> i32 {
+ 2 * value
+}
+
+fn main() {
+ let enumb = Enum::Variant(0);
+
+ let strukt = x::Foo;
+ let strukt = x::y::Foo;
+ let strukt = Struct;
+ let strukt = Struct::new();
+
+ let tuple_struct = TupleStruct();
+
+ let generic0 = Generic::new();
+ // ^^^^^^^^ Generic<i32>
+ let generic1 = Generic(0);
+ // ^^^^^^^^ Generic<i32>
+ let generic2 = Generic::<i32>::new();
+ let generic3 = <Generic<i32>>::new();
+ let generic4 = Generic::<i32>(0);
+
+
+ let option = Some(0);
+ // ^^^^^^ Option<i32>
+ let func = times2;
+ // ^^^^ fn times2(i32) -> i32
+ let closure = |x: i32| x * 2;
+ // ^^^^^^^ |i32| -> i32
+}
+
+fn fallible() -> ControlFlow<()> {
+ let strukt = Struct::try_new()?;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn shows_constructor_type_hints_when_enabled() {
+ check_types(
+ r#"
+//- minicore: try
+use core::ops::ControlFlow;
+
+struct Struct;
+struct TupleStruct();
+
+impl Struct {
+ fn new() -> Self {
+ Struct
+ }
+ fn try_new() -> ControlFlow<(), Self> {
+ ControlFlow::Continue(Struct)
+ }
+}
+
+struct Generic<T>(T);
+impl Generic<i32> {
+ fn new() -> Self {
+ Generic(0)
+ }
+}
+
+fn main() {
+ let strukt = Struct::new();
+ // ^^^^^^ Struct
+ let tuple_struct = TupleStruct();
+ // ^^^^^^^^^^^^ TupleStruct
+ let generic0 = Generic::new();
+ // ^^^^^^^^ Generic<i32>
+ let generic1 = Generic::<i32>::new();
+ // ^^^^^^^^ Generic<i32>
+ let generic2 = <Generic<i32>>::new();
+ // ^^^^^^^^ Generic<i32>
+}
+
+fn fallible() -> ControlFlow<()> {
+ let strukt = Struct::try_new()?;
+ // ^^^^^^ Struct
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn closures() {
+ check(
+ r#"
+fn main() {
+ let mut start = 0;
+ //^^^^^ i32
+ (0..2).for_each(|increment | { start += increment; });
+ //^^^^^^^^^ i32
+
+ let multiply =
+ //^^^^^^^^ |i32, i32| -> i32
+ | a, b| a * b
+ //^ i32 ^ i32
+
+ ;
+
+ let _: i32 = multiply(1, 2);
+ //^ a ^ b
+ let multiply_ref = &multiply;
+ //^^^^^^^^^^^^ &|i32, i32| -> i32
+
+ let return_42 = || 42;
+ //^^^^^^^^^ || -> i32
+ || { 42 };
+ //^^ i32
+}"#,
+ );
+ }
+
+ #[test]
+ fn return_type_hints_for_closure_without_block() {
+ check_with_config(
+ InlayHintsConfig {
+ closure_return_type_hints: ClosureReturnTypeHints::Always,
+ ..DISABLED_CONFIG
+ },
+ r#"
+fn main() {
+ let a = || { 0 };
+ //^^ i32
+ let b = || 0;
+ //^^ i32
+}"#,
+ );
+ }
+
+ #[test]
+ fn skip_closure_type_hints() {
+ check_with_config(
+ InlayHintsConfig {
+ type_hints: true,
+ hide_closure_initialization_hints: true,
+ ..DISABLED_CONFIG
+ },
+ r#"
+//- minicore: fn
+fn main() {
+ let multiple_2 = |x: i32| { x * 2 };
+
+ let multiple_2 = |x: i32| x * 2;
+ // ^^^^^^^^^^ |i32| -> i32
+
+ let (not) = (|x: bool| { !x });
+ // ^^^ |bool| -> bool
+
+ let (is_zero, _b) = (|x: usize| { x == 0 }, false);
+ // ^^^^^^^ |usize| -> bool
+ // ^^ bool
+
+ let plus_one = |x| { x + 1 };
+ // ^ u8
+ foo(plus_one);
+
+ let add_mul = bar(|x: u8| { x + 1 });
+ // ^^^^^^^ impl FnOnce(u8) -> u8 + ?Sized
+
+ let closure = if let Some(6) = add_mul(2).checked_sub(1) {
+ // ^^^^^^^ fn(i32) -> i32
+ |x: i32| { x * 2 }
+ } else {
+ |x: i32| { x * 3 }
+ };
+}
+
+fn foo(f: impl FnOnce(u8) -> u8) {}
+
+fn bar(f: impl FnOnce(u8) -> u8) -> impl FnOnce(u8) -> u8 {
+ move |x: u8| f(x) * 2
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn hint_truncation() {
+ check_with_config(
+ InlayHintsConfig { max_length: Some(8), ..TEST_CONFIG },
+ r#"
+struct Smol<T>(T);
+
+struct VeryLongOuterName<T>(T);
+
+fn main() {
+ let a = Smol(0u32);
+ //^ Smol<u32>
+ let b = VeryLongOuterName(0usize);
+ //^ VeryLongOuterName<…>
+ let c = Smol(Smol(0u32))
+ //^ Smol<Smol<…>>
+}"#,
+ );
+ }
+
+ // Chaining hint tests
+
+ #[test]
+ fn chaining_hints_ignore_comments() {
+ check_expect(
+ InlayHintsConfig { type_hints: false, chaining_hints: true, ..DISABLED_CONFIG },
+ r#"
+struct A(B);
+impl A { fn into_b(self) -> B { self.0 } }
+struct B(C);
+impl B { fn into_c(self) -> C { self.0 } }
+struct C;
+
+fn main() {
+ let c = A(B(C))
+ .into_b() // This is a comment
+ // This is another comment
+ .into_c();
+}
+"#,
+ expect![[r#"
+ [
+ InlayHint {
+ range: 147..172,
+ kind: ChainingHint,
+ label: [
+ "B",
+ ],
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 147..172,
+ ),
+ ),
+ },
+ InlayHint {
+ range: 147..154,
+ kind: ChainingHint,
+ label: [
+ "A",
+ ],
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 147..154,
+ ),
+ ),
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn chaining_hints_without_newlines() {
+ check_chains(
+ r#"
+struct A(B);
+impl A { fn into_b(self) -> B { self.0 } }
+struct B(C);
+impl B { fn into_c(self) -> C { self.0 } }
+struct C;
+
+fn main() {
+ let c = A(B(C)).into_b().into_c();
+}"#,
+ );
+ }
+
+ #[test]
+ fn struct_access_chaining_hints() {
+ check_expect(
+ InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG },
+ r#"
+struct A { pub b: B }
+struct B { pub c: C }
+struct C(pub bool);
+struct D;
+
+impl D {
+ fn foo(&self) -> i32 { 42 }
+}
+
+fn main() {
+ let x = A { b: B { c: C(true) } }
+ .b
+ .c
+ .0;
+ let x = D
+ .foo();
+}"#,
+ expect![[r#"
+ [
+ InlayHint {
+ range: 143..190,
+ kind: ChainingHint,
+ label: [
+ "C",
+ ],
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 143..190,
+ ),
+ ),
+ },
+ InlayHint {
+ range: 143..179,
+ kind: ChainingHint,
+ label: [
+ "B",
+ ],
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 143..179,
+ ),
+ ),
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn generic_chaining_hints() {
+ check_expect(
+ InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG },
+ r#"
+struct A<T>(T);
+struct B<T>(T);
+struct C<T>(T);
+struct X<T,R>(T, R);
+
+impl<T> A<T> {
+ fn new(t: T) -> Self { A(t) }
+ fn into_b(self) -> B<T> { B(self.0) }
+}
+impl<T> B<T> {
+ fn into_c(self) -> C<T> { C(self.0) }
+}
+fn main() {
+ let c = A::new(X(42, true))
+ .into_b()
+ .into_c();
+}
+"#,
+ expect![[r#"
+ [
+ InlayHint {
+ range: 246..283,
+ kind: ChainingHint,
+ label: [
+ "B<X<i32, bool>>",
+ ],
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 246..283,
+ ),
+ ),
+ },
+ InlayHint {
+ range: 246..265,
+ kind: ChainingHint,
+ label: [
+ "A<X<i32, bool>>",
+ ],
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 246..265,
+ ),
+ ),
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn shorten_iterator_chaining_hints() {
+ check_expect(
+ InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG },
+ r#"
+//- minicore: iterators
+use core::iter;
+
+struct MyIter;
+
+impl Iterator for MyIter {
+ type Item = ();
+ fn next(&mut self) -> Option<Self::Item> {
+ None
+ }
+}
+
+fn main() {
+ let _x = MyIter.by_ref()
+ .take(5)
+ .by_ref()
+ .take(5)
+ .by_ref();
+}
+"#,
+ expect![[r#"
+ [
+ InlayHint {
+ range: 174..241,
+ kind: ChainingHint,
+ label: [
+ "impl Iterator<Item = ()>",
+ ],
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 174..241,
+ ),
+ ),
+ },
+ InlayHint {
+ range: 174..224,
+ kind: ChainingHint,
+ label: [
+ "impl Iterator<Item = ()>",
+ ],
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 174..224,
+ ),
+ ),
+ },
+ InlayHint {
+ range: 174..206,
+ kind: ChainingHint,
+ label: [
+ "impl Iterator<Item = ()>",
+ ],
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 174..206,
+ ),
+ ),
+ },
+ InlayHint {
+ range: 174..189,
+ kind: ChainingHint,
+ label: [
+ "&mut MyIter",
+ ],
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 174..189,
+ ),
+ ),
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn hints_in_attr_call() {
+ check_expect(
+ TEST_CONFIG,
+ r#"
+//- proc_macros: identity, input_replace
+struct Struct;
+impl Struct {
+ fn chain(self) -> Self {
+ self
+ }
+}
+#[proc_macros::identity]
+fn main() {
+ let strukt = Struct;
+ strukt
+ .chain()
+ .chain()
+ .chain();
+ Struct::chain(strukt);
+}
+"#,
+ expect![[r#"
+ [
+ InlayHint {
+ range: 124..130,
+ kind: TypeHint,
+ label: [
+ "Struct",
+ ],
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 124..130,
+ ),
+ ),
+ },
+ InlayHint {
+ range: 145..185,
+ kind: ChainingHint,
+ label: [
+ "Struct",
+ ],
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 145..185,
+ ),
+ ),
+ },
+ InlayHint {
+ range: 145..168,
+ kind: ChainingHint,
+ label: [
+ "Struct",
+ ],
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 145..168,
+ ),
+ ),
+ },
+ InlayHint {
+ range: 222..228,
+ kind: ParameterHint,
+ label: [
+ "self",
+ ],
+ tooltip: Some(
+ HoverOffset(
+ FileId(
+ 0,
+ ),
+ 42,
+ ),
+ ),
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn hints_lifetimes() {
+ check(
+ r#"
+fn empty() {}
+
+fn no_gpl(a: &()) {}
+ //^^^^^^<'0>
+ // ^'0
+fn empty_gpl<>(a: &()) {}
+ // ^'0 ^'0
+fn partial<'b>(a: &(), b: &'b ()) {}
+// ^'0, $ ^'0
+fn partial<'a>(a: &'a (), b: &()) {}
+// ^'0, $ ^'0
+
+fn single_ret(a: &()) -> &() {}
+// ^^^^^^^^^^<'0>
+ // ^'0 ^'0
+fn full_mul(a: &(), b: &()) {}
+// ^^^^^^^^<'0, '1>
+ // ^'0 ^'1
+
+fn foo<'c>(a: &'c ()) -> &() {}
+ // ^'c
+
+fn nested_in(a: & &X< &()>) {}
+// ^^^^^^^^^<'0, '1, '2>
+ //^'0 ^'1 ^'2
+fn nested_out(a: &()) -> & &X< &()>{}
+// ^^^^^^^^^^<'0>
+ //^'0 ^'0 ^'0 ^'0
+
+impl () {
+ fn foo(&self) {}
+ // ^^^<'0>
+ // ^'0
+ fn foo(&self) -> &() {}
+ // ^^^<'0>
+ // ^'0 ^'0
+ fn foo(&self, a: &()) -> &() {}
+ // ^^^<'0, '1>
+ // ^'0 ^'1 ^'0
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn hints_lifetimes_named() {
+ check_with_config(
+ InlayHintsConfig { param_names_for_lifetime_elision_hints: true, ..TEST_CONFIG },
+ r#"
+fn nested_in<'named>(named: & &X< &()>) {}
+// ^'named1, 'named2, 'named3, $
+ //^'named1 ^'named2 ^'named3
+"#,
+ );
+ }
+
+ #[test]
+ fn hints_lifetimes_trivial_skip() {
+ check_with_config(
+ InlayHintsConfig {
+ lifetime_elision_hints: LifetimeElisionHints::SkipTrivial,
+ ..TEST_CONFIG
+ },
+ r#"
+fn no_gpl(a: &()) {}
+fn empty_gpl<>(a: &()) {}
+fn partial<'b>(a: &(), b: &'b ()) {}
+fn partial<'a>(a: &'a (), b: &()) {}
+
+fn single_ret(a: &()) -> &() {}
+// ^^^^^^^^^^<'0>
+ // ^'0 ^'0
+fn full_mul(a: &(), b: &()) {}
+
+fn foo<'c>(a: &'c ()) -> &() {}
+ // ^'c
+
+fn nested_in(a: & &X< &()>) {}
+fn nested_out(a: &()) -> & &X< &()>{}
+// ^^^^^^^^^^<'0>
+ //^'0 ^'0 ^'0 ^'0
+
+impl () {
+ fn foo(&self) {}
+ fn foo(&self) -> &() {}
+ // ^^^<'0>
+ // ^'0 ^'0
+ fn foo(&self, a: &()) -> &() {}
+ // ^^^<'0, '1>
+ // ^'0 ^'1 ^'0
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn hints_lifetimes_static() {
+ check_with_config(
+ InlayHintsConfig {
+ lifetime_elision_hints: LifetimeElisionHints::Always,
+ ..TEST_CONFIG
+ },
+ r#"
+trait Trait {}
+static S: &str = "";
+// ^'static
+const C: &str = "";
+// ^'static
+const C: &dyn Trait = panic!();
+// ^'static
+
+impl () {
+ const C: &str = "";
+ const C: &dyn Trait = panic!();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn hints_implicit_reborrow() {
+ check_with_config(
+ InlayHintsConfig {
+ reborrow_hints: ReborrowHints::Always,
+ parameter_hints: true,
+ ..DISABLED_CONFIG
+ },
+ r#"
+fn __() {
+ let unique = &mut ();
+ let r_mov = unique;
+ let foo: &mut _ = unique;
+ //^^^^^^ &mut *
+ ref_mut_id(unique);
+ //^^^^^^ mut_ref
+ //^^^^^^ &mut *
+ let shared = ref_id(unique);
+ //^^^^^^ shared_ref
+ //^^^^^^ &*
+ let mov = shared;
+ let r_mov: &_ = shared;
+ ref_id(shared);
+ //^^^^^^ shared_ref
+
+ identity(unique);
+ identity(shared);
+}
+fn identity<T>(t: T) -> T {
+ t
+}
+fn ref_mut_id(mut_ref: &mut ()) -> &mut () {
+ mut_ref
+ //^^^^^^^ &mut *
+}
+fn ref_id(shared_ref: &()) -> &() {
+ shared_ref
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn hints_binding_modes() {
+ check_with_config(
+ InlayHintsConfig { binding_mode_hints: true, ..DISABLED_CONFIG },
+ r#"
+fn __(
+ (x,): (u32,),
+ (x,): &(u32,),
+ //^^^^&
+ //^ ref
+ (x,): &mut (u32,)
+ //^^^^&mut
+ //^ ref mut
+) {
+ let (x,) = (0,);
+ let (x,) = &(0,);
+ //^^^^ &
+ //^ ref
+ let (x,) = &mut (0,);
+ //^^^^ &mut
+ //^ ref mut
+ let &mut (x,) = &mut (0,);
+ let (ref mut x,) = &mut (0,);
+ //^^^^^^^^^^^^ &mut
+ let &mut (ref mut x,) = &mut (0,);
+ let (mut x,) = &mut (0,);
+ //^^^^^^^^ &mut
+ match (0,) {
+ (x,) => ()
+ }
+ match &(0,) {
+ (x,) => ()
+ //^^^^ &
+ //^ ref
+ }
+ match &mut (0,) {
+ (x,) => ()
+ //^^^^ &mut
+ //^ ref mut
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn hints_closing_brace() {
+ check_with_config(
+ InlayHintsConfig { closing_brace_hints_min_lines: Some(2), ..DISABLED_CONFIG },
+ r#"
+fn a() {}
+
+fn f() {
+} // no hint unless `}` is the last token on the line
+
+fn g() {
+ }
+//^ fn g
+
+fn h<T>(with: T, arguments: u8, ...) {
+ }
+//^ fn h
+
+trait Tr {
+ fn f();
+ fn g() {
+ }
+ //^ fn g
+ }
+//^ trait Tr
+impl Tr for () {
+ }
+//^ impl Tr for ()
+impl dyn Tr {
+ }
+//^ impl dyn Tr
+
+static S0: () = 0;
+static S1: () = {};
+static S2: () = {
+ };
+//^ static S2
+const _: () = {
+ };
+//^ const _
+
+mod m {
+ }
+//^ mod m
+
+m! {}
+m!();
+m!(
+ );
+//^ m!
+
+m! {
+ }
+//^ m!
+
+fn f() {
+ let v = vec![
+ ];
+ }
+//^ fn f
+"#,
+ );
+ }
+}
--- /dev/null
- mod fn_references;
+//! ide crate provides "ide-centric" APIs for the rust-analyzer. That is,
+//! it generally operates with files and text ranges, and returns results as
+//! Strings, suitable for displaying to the human.
+//!
+//! What powers this API are the `RootDatabase` struct, which defines a `salsa`
+//! database, and the `hir` crate, where majority of the analysis happens.
+//! However, IDE specific bits of the analysis (most notably completion) happen
+//! in this crate.
+
+// For proving that RootDatabase is RefUnwindSafe.
+#![recursion_limit = "128"]
+#![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)]
+
+#[allow(unused)]
+macro_rules! eprintln {
+ ($($tt:tt)*) => { stdx::eprintln!($($tt)*) };
+}
+
+#[cfg(test)]
+mod fixture;
+
+mod markup;
+mod prime_caches;
+mod navigation_target;
+
+mod annotations;
+mod call_hierarchy;
+mod signature_help;
+mod doc_links;
+mod highlight_related;
+mod expand_macro;
+mod extend_selection;
+mod file_structure;
- CrateOrigin::CratesIo { repo: None },
+mod folding_ranges;
+mod goto_declaration;
+mod goto_definition;
+mod goto_implementation;
+mod goto_type_definition;
+mod hover;
+mod inlay_hints;
+mod join_lines;
+mod markdown_remove;
+mod matching_brace;
+mod moniker;
+mod move_item;
+mod parent_module;
+mod references;
+mod rename;
+mod runnables;
+mod ssr;
+mod static_index;
+mod status;
+mod syntax_highlighting;
+mod syntax_tree;
+mod typing;
+mod view_crate_graph;
+mod view_hir;
+mod view_item_tree;
+mod shuffle_crate_graph;
+
+use std::sync::Arc;
+
+use cfg::CfgOptions;
+use ide_db::{
+ base_db::{
+ salsa::{self, ParallelDatabase},
+ CrateOrigin, Env, FileLoader, FileSet, SourceDatabase, VfsPath,
+ },
+ symbol_index, LineIndexDatabase,
+};
+use syntax::SourceFile;
+
+use crate::navigation_target::{ToNav, TryToNav};
+
+pub use crate::{
+ annotations::{Annotation, AnnotationConfig, AnnotationKind, AnnotationLocation},
+ call_hierarchy::CallItem,
+ expand_macro::ExpandedMacro,
+ file_structure::{StructureNode, StructureNodeKind},
+ folding_ranges::{Fold, FoldKind},
+ highlight_related::{HighlightRelatedConfig, HighlightedRange},
+ hover::{HoverAction, HoverConfig, HoverDocFormat, HoverGotoTypeData, HoverResult},
+ inlay_hints::{
+ ClosureReturnTypeHints, InlayHint, InlayHintLabel, InlayHintsConfig, InlayKind,
+ InlayTooltip, LifetimeElisionHints, ReborrowHints,
+ },
+ join_lines::JoinLinesConfig,
+ markup::Markup,
+ moniker::{MonikerDescriptorKind, MonikerKind, MonikerResult, PackageInformation},
+ move_item::Direction,
+ navigation_target::NavigationTarget,
+ prime_caches::ParallelPrimeCachesProgress,
+ references::ReferenceSearchResult,
+ rename::RenameError,
+ runnables::{Runnable, RunnableKind, TestId},
+ signature_help::SignatureHelp,
+ static_index::{StaticIndex, StaticIndexedFile, TokenId, TokenStaticData},
+ syntax_highlighting::{
+ tags::{Highlight, HlMod, HlMods, HlOperator, HlPunct, HlTag},
+ HighlightConfig, HlRange,
+ },
+};
+pub use hir::{Documentation, Semantics};
+pub use ide_assists::{
+ Assist, AssistConfig, AssistId, AssistKind, AssistResolveStrategy, SingleResolve,
+};
+pub use ide_completion::{
+ CallableSnippets, CompletionConfig, CompletionItem, CompletionItemKind, CompletionRelevance,
+ Snippet, SnippetScope,
+};
+pub use ide_db::{
+ base_db::{
+ Cancelled, Change, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange,
+ SourceRoot, SourceRootId,
+ },
+ label::Label,
+ line_index::{LineCol, LineColUtf16, LineIndex},
+ search::{ReferenceCategory, SearchScope},
+ source_change::{FileSystemEdit, SourceChange},
+ symbol_index::Query,
+ RootDatabase, SymbolKind,
+};
+pub use ide_diagnostics::{Diagnostic, DiagnosticsConfig, ExprFillDefaultMode, Severity};
+pub use ide_ssr::SsrError;
+pub use syntax::{TextRange, TextSize};
+pub use text_edit::{Indel, TextEdit};
+
+pub type Cancellable<T> = Result<T, Cancelled>;
+
+/// Info associated with a text range.
+#[derive(Debug)]
+pub struct RangeInfo<T> {
+ pub range: TextRange,
+ pub info: T,
+}
+
+impl<T> RangeInfo<T> {
+ pub fn new(range: TextRange, info: T) -> RangeInfo<T> {
+ RangeInfo { range, info }
+ }
+}
+
+/// `AnalysisHost` stores the current state of the world.
+#[derive(Debug)]
+pub struct AnalysisHost {
+ db: RootDatabase,
+}
+
+impl AnalysisHost {
+ pub fn new(lru_capacity: Option<usize>) -> AnalysisHost {
+ AnalysisHost { db: RootDatabase::new(lru_capacity) }
+ }
+
+ pub fn update_lru_capacity(&mut self, lru_capacity: Option<usize>) {
+ self.db.update_lru_capacity(lru_capacity);
+ }
+
+ /// Returns a snapshot of the current state, which you can query for
+ /// semantic information.
+ pub fn analysis(&self) -> Analysis {
+ Analysis { db: self.db.snapshot() }
+ }
+
+ /// Applies changes to the current state of the world. If there are
+ /// outstanding snapshots, they will be canceled.
+ pub fn apply_change(&mut self, change: Change) {
+ self.db.apply_change(change)
+ }
+
+ /// NB: this clears the database
+ pub fn per_query_memory_usage(&mut self) -> Vec<(String, profile::Bytes)> {
+ self.db.per_query_memory_usage()
+ }
+ pub fn request_cancellation(&mut self) {
+ self.db.request_cancellation();
+ }
+ pub fn raw_database(&self) -> &RootDatabase {
+ &self.db
+ }
+ pub fn raw_database_mut(&mut self) -> &mut RootDatabase {
+ &mut self.db
+ }
+
+ pub fn shuffle_crate_graph(&mut self) {
+ shuffle_crate_graph::shuffle_crate_graph(&mut self.db);
+ }
+}
+
+impl Default for AnalysisHost {
+ fn default() -> AnalysisHost {
+ AnalysisHost::new(None)
+ }
+}
+
+/// Analysis is a snapshot of a world state at a moment in time. It is the main
+/// entry point for asking semantic information about the world. When the world
+/// state is advanced using `AnalysisHost::apply_change` method, all existing
+/// `Analysis` are canceled (most method return `Err(Canceled)`).
+#[derive(Debug)]
+pub struct Analysis {
+ db: salsa::Snapshot<RootDatabase>,
+}
+
+// As a general design guideline, `Analysis` API are intended to be independent
+// from the language server protocol. That is, when exposing some functionality
+// we should think in terms of "what API makes most sense" and not in terms of
+// "what types LSP uses". Although currently LSP is the only consumer of the
+// API, the API should in theory be usable as a library, or via a different
+// protocol.
+impl Analysis {
+ // Creates an analysis instance for a single file, without any external
+ // dependencies, stdlib support or ability to apply changes. See
+ // `AnalysisHost` for creating a fully-featured analysis.
+ pub fn from_single_file(text: String) -> (Analysis, FileId) {
+ let mut host = AnalysisHost::default();
+ let file_id = FileId(0);
+ let mut file_set = FileSet::default();
+ file_set.insert(file_id, VfsPath::new_virtual_path("/main.rs".to_string()));
+ let source_root = SourceRoot::new_local(file_set);
+
+ let mut change = Change::new();
+ change.set_roots(vec![source_root]);
+ let mut crate_graph = CrateGraph::default();
+ // FIXME: cfg options
+ // Default to enable test for single file.
+ let mut cfg_options = CfgOptions::default();
+ cfg_options.insert_atom("test".into());
+ crate_graph.add_crate_root(
+ file_id,
+ Edition::CURRENT,
+ None,
+ None,
+ cfg_options.clone(),
+ cfg_options,
+ Env::default(),
+ Ok(Vec::new()),
+ false,
- /// Finds all methods and free functions for the file. Does not return tests!
- pub fn find_all_methods(&self, file_id: FileId) -> Cancellable<Vec<FileRange>> {
- self.with_db(|db| fn_references::find_all_methods(db, file_id))
- }
-
++ CrateOrigin::CratesIo { repo: None, name: None },
+ );
+ change.change_file(file_id, Some(Arc::new(text)));
+ change.set_crate_graph(crate_graph);
+ host.apply_change(change);
+ (host.analysis(), file_id)
+ }
+
+ /// Debug info about the current state of the analysis.
+ pub fn status(&self, file_id: Option<FileId>) -> Cancellable<String> {
+ self.with_db(|db| status::status(&*db, file_id))
+ }
+
+ pub fn parallel_prime_caches<F>(&self, num_worker_threads: u8, cb: F) -> Cancellable<()>
+ where
+ F: Fn(ParallelPrimeCachesProgress) + Sync + std::panic::UnwindSafe,
+ {
+ self.with_db(move |db| prime_caches::parallel_prime_caches(db, num_worker_threads, &cb))
+ }
+
+ /// Gets the text of the source file.
+ pub fn file_text(&self, file_id: FileId) -> Cancellable<Arc<String>> {
+ self.with_db(|db| db.file_text(file_id))
+ }
+
+ /// Gets the syntax tree of the file.
+ pub fn parse(&self, file_id: FileId) -> Cancellable<SourceFile> {
+ self.with_db(|db| db.parse(file_id).tree())
+ }
+
+ /// Returns true if this file belongs to an immutable library.
+ pub fn is_library_file(&self, file_id: FileId) -> Cancellable<bool> {
+ use ide_db::base_db::SourceDatabaseExt;
+ self.with_db(|db| db.source_root(db.file_source_root(file_id)).is_library)
+ }
+
+ /// Gets the file's `LineIndex`: data structure to convert between absolute
+ /// offsets and line/column representation.
+ pub fn file_line_index(&self, file_id: FileId) -> Cancellable<Arc<LineIndex>> {
+ self.with_db(|db| db.line_index(file_id))
+ }
+
+ /// Selects the next syntactic nodes encompassing the range.
+ pub fn extend_selection(&self, frange: FileRange) -> Cancellable<TextRange> {
+ self.with_db(|db| extend_selection::extend_selection(db, frange))
+ }
+
+ /// Returns position of the matching brace (all types of braces are
+ /// supported).
+ pub fn matching_brace(&self, position: FilePosition) -> Cancellable<Option<TextSize>> {
+ self.with_db(|db| {
+ let parse = db.parse(position.file_id);
+ let file = parse.tree();
+ matching_brace::matching_brace(&file, position.offset)
+ })
+ }
+
+ /// Returns a syntax tree represented as `String`, for debug purposes.
+ // FIXME: use a better name here.
+ pub fn syntax_tree(
+ &self,
+ file_id: FileId,
+ text_range: Option<TextRange>,
+ ) -> Cancellable<String> {
+ self.with_db(|db| syntax_tree::syntax_tree(db, file_id, text_range))
+ }
+
+ pub fn view_hir(&self, position: FilePosition) -> Cancellable<String> {
+ self.with_db(|db| view_hir::view_hir(db, position))
+ }
+
+ pub fn view_item_tree(&self, file_id: FileId) -> Cancellable<String> {
+ self.with_db(|db| view_item_tree::view_item_tree(db, file_id))
+ }
+
+ /// Renders the crate graph to GraphViz "dot" syntax.
+ pub fn view_crate_graph(&self, full: bool) -> Cancellable<Result<String, String>> {
+ self.with_db(|db| view_crate_graph::view_crate_graph(db, full))
+ }
+
+ pub fn expand_macro(&self, position: FilePosition) -> Cancellable<Option<ExpandedMacro>> {
+ self.with_db(|db| expand_macro::expand_macro(db, position))
+ }
+
+ /// Returns an edit to remove all newlines in the range, cleaning up minor
+ /// stuff like trailing commas.
+ pub fn join_lines(&self, config: &JoinLinesConfig, frange: FileRange) -> Cancellable<TextEdit> {
+ self.with_db(|db| {
+ let parse = db.parse(frange.file_id);
+ join_lines::join_lines(config, &parse.tree(), frange.range)
+ })
+ }
+
+ /// Returns an edit which should be applied when opening a new line, fixing
+ /// up minor stuff like continuing the comment.
+ /// The edit will be a snippet (with `$0`).
+ pub fn on_enter(&self, position: FilePosition) -> Cancellable<Option<TextEdit>> {
+ self.with_db(|db| typing::on_enter(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,
+ autoclose: bool,
+ ) -> Cancellable<Option<SourceChange>> {
+ // Fast path to not even parse the file.
+ if !typing::TRIGGER_CHARS.contains(char_typed) {
+ return Ok(None);
+ }
+ if char_typed == '<' && !autoclose {
+ 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
+ /// file outline.
+ pub fn file_structure(&self, file_id: FileId) -> Cancellable<Vec<StructureNode>> {
+ self.with_db(|db| file_structure::file_structure(&db.parse(file_id).tree()))
+ }
+
+ /// Returns a list of the places in the file where type hints can be displayed.
+ pub fn inlay_hints(
+ &self,
+ config: &InlayHintsConfig,
+ file_id: FileId,
+ range: Option<FileRange>,
+ ) -> Cancellable<Vec<InlayHint>> {
+ self.with_db(|db| inlay_hints::inlay_hints(db, file_id, range, config))
+ }
+
+ /// Returns the set of folding ranges.
+ pub fn folding_ranges(&self, file_id: FileId) -> Cancellable<Vec<Fold>> {
+ self.with_db(|db| folding_ranges::folding_ranges(&db.parse(file_id).tree()))
+ }
+
+ /// Fuzzy searches for a symbol.
+ pub fn symbol_search(&self, query: Query) -> Cancellable<Vec<NavigationTarget>> {
+ self.with_db(|db| {
+ symbol_index::world_symbols(db, query)
+ .into_iter() // xx: should we make this a par iter?
+ .filter_map(|s| s.try_to_nav(db))
+ .collect::<Vec<_>>()
+ })
+ }
+
+ /// Returns the definitions from the symbol at `position`.
+ pub fn goto_definition(
+ &self,
+ position: FilePosition,
+ ) -> Cancellable<Option<RangeInfo<Vec<NavigationTarget>>>> {
+ self.with_db(|db| goto_definition::goto_definition(db, position))
+ }
+
+ /// Returns the declaration from the symbol at `position`.
+ pub fn goto_declaration(
+ &self,
+ position: FilePosition,
+ ) -> Cancellable<Option<RangeInfo<Vec<NavigationTarget>>>> {
+ self.with_db(|db| goto_declaration::goto_declaration(db, position))
+ }
+
+ /// Returns the impls from the symbol at `position`.
+ pub fn goto_implementation(
+ &self,
+ position: FilePosition,
+ ) -> Cancellable<Option<RangeInfo<Vec<NavigationTarget>>>> {
+ self.with_db(|db| goto_implementation::goto_implementation(db, position))
+ }
+
+ /// Returns the type definitions for the symbol at `position`.
+ pub fn goto_type_definition(
+ &self,
+ position: FilePosition,
+ ) -> Cancellable<Option<RangeInfo<Vec<NavigationTarget>>>> {
+ self.with_db(|db| goto_type_definition::goto_type_definition(db, position))
+ }
+
+ /// Finds all usages of the reference at point.
+ pub fn find_all_refs(
+ &self,
+ position: FilePosition,
+ search_scope: Option<SearchScope>,
+ ) -> Cancellable<Option<Vec<ReferenceSearchResult>>> {
+ self.with_db(|db| references::find_all_refs(&Semantics::new(db), position, search_scope))
+ }
+
+ /// Returns a short text describing element at position.
+ pub fn hover(
+ &self,
+ config: &HoverConfig,
+ range: FileRange,
+ ) -> Cancellable<Option<RangeInfo<HoverResult>>> {
+ self.with_db(|db| hover::hover(db, range, config))
+ }
+
+ /// Returns moniker of symbol at position.
+ pub fn moniker(
+ &self,
+ position: FilePosition,
+ ) -> Cancellable<Option<RangeInfo<Vec<moniker::MonikerResult>>>> {
+ self.with_db(|db| moniker::moniker(db, position))
+ }
+
+ /// Return URL(s) for the documentation of the symbol under the cursor.
+ pub fn external_docs(
+ &self,
+ position: FilePosition,
+ ) -> Cancellable<Option<doc_links::DocumentationLink>> {
+ self.with_db(|db| doc_links::external_docs(db, &position))
+ }
+
+ /// Computes parameter information at the given position.
+ pub fn signature_help(&self, position: FilePosition) -> Cancellable<Option<SignatureHelp>> {
+ self.with_db(|db| signature_help::signature_help(db, position))
+ }
+
+ /// Computes call hierarchy candidates for the given file position.
+ pub fn call_hierarchy(
+ &self,
+ position: FilePosition,
+ ) -> Cancellable<Option<RangeInfo<Vec<NavigationTarget>>>> {
+ self.with_db(|db| call_hierarchy::call_hierarchy(db, position))
+ }
+
+ /// Computes incoming calls for the given file position.
+ pub fn incoming_calls(&self, position: FilePosition) -> Cancellable<Option<Vec<CallItem>>> {
+ self.with_db(|db| call_hierarchy::incoming_calls(db, position))
+ }
+
+ /// Computes outgoing calls for the given file position.
+ pub fn outgoing_calls(&self, position: FilePosition) -> Cancellable<Option<Vec<CallItem>>> {
+ self.with_db(|db| call_hierarchy::outgoing_calls(db, position))
+ }
+
+ /// Returns a `mod name;` declaration which created the current module.
+ pub fn parent_module(&self, position: FilePosition) -> Cancellable<Vec<NavigationTarget>> {
+ self.with_db(|db| parent_module::parent_module(db, position))
+ }
+
+ /// Returns crates this file belongs too.
+ pub fn crate_for(&self, file_id: FileId) -> Cancellable<Vec<CrateId>> {
+ self.with_db(|db| parent_module::crate_for(db, file_id))
+ }
+
+ /// Returns the edition of the given crate.
+ pub fn crate_edition(&self, crate_id: CrateId) -> Cancellable<Edition> {
+ self.with_db(|db| db.crate_graph()[crate_id].edition)
+ }
+
+ /// Returns the root file of the given crate.
+ pub fn crate_root(&self, crate_id: CrateId) -> Cancellable<FileId> {
+ self.with_db(|db| db.crate_graph()[crate_id].root_file_id)
+ }
+
+ /// Returns the set of possible targets to run for the current file.
+ pub fn runnables(&self, file_id: FileId) -> Cancellable<Vec<Runnable>> {
+ self.with_db(|db| runnables::runnables(db, file_id))
+ }
+
+ /// Returns the set of tests for the given file position.
+ pub fn related_tests(
+ &self,
+ position: FilePosition,
+ search_scope: Option<SearchScope>,
+ ) -> Cancellable<Vec<Runnable>> {
+ self.with_db(|db| runnables::related_tests(db, position, search_scope))
+ }
+
+ /// Computes syntax highlighting for the given file
+ pub fn highlight(
+ &self,
+ highlight_config: HighlightConfig,
+ file_id: FileId,
+ ) -> Cancellable<Vec<HlRange>> {
+ self.with_db(|db| syntax_highlighting::highlight(db, highlight_config, file_id, None))
+ }
+
+ /// Computes all ranges to highlight for a given item in a file.
+ pub fn highlight_related(
+ &self,
+ config: HighlightRelatedConfig,
+ position: FilePosition,
+ ) -> Cancellable<Option<Vec<HighlightedRange>>> {
+ self.with_db(|db| {
+ highlight_related::highlight_related(&Semantics::new(db), config, position)
+ })
+ }
+
+ /// Computes syntax highlighting for the given file range.
+ pub fn highlight_range(
+ &self,
+ highlight_config: HighlightConfig,
+ frange: FileRange,
+ ) -> Cancellable<Vec<HlRange>> {
+ self.with_db(|db| {
+ syntax_highlighting::highlight(db, highlight_config, frange.file_id, Some(frange.range))
+ })
+ }
+
+ /// Computes syntax highlighting for the given file.
+ pub fn highlight_as_html(&self, file_id: FileId, rainbow: bool) -> Cancellable<String> {
+ self.with_db(|db| syntax_highlighting::highlight_as_html(db, file_id, rainbow))
+ }
+
+ /// Computes completions at the given position.
+ pub fn completions(
+ &self,
+ config: &CompletionConfig,
+ position: FilePosition,
+ trigger_character: Option<char>,
+ ) -> Cancellable<Option<Vec<CompletionItem>>> {
+ self.with_db(|db| {
+ ide_completion::completions(db, config, position, trigger_character).map(Into::into)
+ })
+ }
+
+ /// Resolves additional completion data at the position given.
+ pub fn resolve_completion_edits(
+ &self,
+ config: &CompletionConfig,
+ position: FilePosition,
+ imports: impl IntoIterator<Item = (String, String)> + std::panic::UnwindSafe,
+ ) -> Cancellable<Vec<TextEdit>> {
+ Ok(self
+ .with_db(|db| ide_completion::resolve_completion_edits(db, config, position, imports))?
+ .unwrap_or_default())
+ }
+
+ /// Computes the set of diagnostics for the given file.
+ pub fn diagnostics(
+ &self,
+ config: &DiagnosticsConfig,
+ resolve: AssistResolveStrategy,
+ file_id: FileId,
+ ) -> Cancellable<Vec<Diagnostic>> {
+ self.with_db(|db| ide_diagnostics::diagnostics(db, config, &resolve, file_id))
+ }
+
+ /// Convenience function to return assists + quick fixes for diagnostics
+ pub fn assists_with_fixes(
+ &self,
+ assist_config: &AssistConfig,
+ diagnostics_config: &DiagnosticsConfig,
+ resolve: AssistResolveStrategy,
+ frange: FileRange,
+ ) -> Cancellable<Vec<Assist>> {
+ let include_fixes = match &assist_config.allowed {
+ Some(it) => it.iter().any(|&it| it == AssistKind::None || it == AssistKind::QuickFix),
+ None => true,
+ };
+
+ self.with_db(|db| {
+ let diagnostic_assists = if include_fixes {
+ ide_diagnostics::diagnostics(db, diagnostics_config, &resolve, frange.file_id)
+ .into_iter()
+ .flat_map(|it| it.fixes.unwrap_or_default())
+ .filter(|it| it.target.intersect(frange.range).is_some())
+ .collect()
+ } else {
+ Vec::new()
+ };
+ let ssr_assists = ssr::ssr_assists(db, &resolve, frange);
+ let assists = ide_assists::assists(db, assist_config, resolve, frange);
+
+ let mut res = diagnostic_assists;
+ res.extend(ssr_assists.into_iter());
+ res.extend(assists.into_iter());
+
+ res
+ })
+ }
+
+ /// Returns the edit required to rename reference at the position to the new
+ /// name.
+ pub fn rename(
+ &self,
+ position: FilePosition,
+ new_name: &str,
+ ) -> Cancellable<Result<SourceChange, RenameError>> {
+ self.with_db(|db| rename::rename(db, position, new_name))
+ }
+
+ pub fn prepare_rename(
+ &self,
+ position: FilePosition,
+ ) -> Cancellable<Result<RangeInfo<()>, RenameError>> {
+ self.with_db(|db| rename::prepare_rename(db, position))
+ }
+
+ pub fn will_rename_file(
+ &self,
+ file_id: FileId,
+ new_name_stem: &str,
+ ) -> Cancellable<Option<SourceChange>> {
+ self.with_db(|db| rename::will_rename_file(db, file_id, new_name_stem))
+ }
+
+ pub fn structural_search_replace(
+ &self,
+ query: &str,
+ parse_only: bool,
+ resolve_context: FilePosition,
+ selections: Vec<FileRange>,
+ ) -> Cancellable<Result<SourceChange, SsrError>> {
+ self.with_db(|db| {
+ let rule: ide_ssr::SsrRule = query.parse()?;
+ let mut match_finder =
+ ide_ssr::MatchFinder::in_context(db, resolve_context, selections)?;
+ match_finder.add_rule(rule)?;
+ let edits = if parse_only { Default::default() } else { match_finder.edits() };
+ Ok(SourceChange::from(edits))
+ })
+ }
+
+ pub fn annotations(
+ &self,
+ config: &AnnotationConfig,
+ file_id: FileId,
+ ) -> Cancellable<Vec<Annotation>> {
+ self.with_db(|db| annotations::annotations(db, config, file_id))
+ }
+
+ pub fn resolve_annotation(&self, annotation: Annotation) -> Cancellable<Annotation> {
+ self.with_db(|db| annotations::resolve_annotation(db, annotation))
+ }
+
+ pub fn move_item(
+ &self,
+ range: FileRange,
+ direction: Direction,
+ ) -> Cancellable<Option<TextEdit>> {
+ self.with_db(|db| move_item::move_item(db, range, direction))
+ }
+
+ /// Performs an operation on the database that may be canceled.
+ ///
+ /// rust-analyzer needs to be able to answer semantic questions about the
+ /// code while the code is being modified. A common problem is that a
+ /// long-running query is being calculated when a new change arrives.
+ ///
+ /// We can't just apply the change immediately: this will cause the pending
+ /// query to see inconsistent state (it will observe an absence of
+ /// repeatable read). So what we do is we **cancel** all pending queries
+ /// before applying the change.
+ ///
+ /// Salsa implements cancellation by unwinding with a special value and
+ /// catching it on the API boundary.
+ fn with_db<F, T>(&self, f: F) -> Cancellable<T>
+ where
+ F: FnOnce(&RootDatabase) -> T + std::panic::UnwindSafe,
+ {
+ Cancelled::catch(|| f(&self.db))
+ }
+}
+
+#[test]
+fn analysis_is_send() {
+ fn is_send<T: Send>() {}
+ is_send::<Analysis>();
+}
--- /dev/null
- let name = krate.display_name(db)?.to_string();
- let (repo, version) = match krate.origin(db) {
- CrateOrigin::CratesIo { repo } => (repo?, krate.version(db)?),
+//! This module generates [moniker](https://microsoft.github.io/language-server-protocol/specifications/lsif/0.6.0/specification/#exportsImports)
+//! for LSIF and LSP.
+
+use hir::{db::DefDatabase, AsAssocItem, AssocItemContainer, Crate, Name, Semantics};
+use ide_db::{
+ base_db::{CrateOrigin, FileId, FileLoader, FilePosition, LangCrateOrigin},
+ defs::{Definition, IdentClass},
+ helpers::pick_best_token,
+ RootDatabase,
+};
+use itertools::Itertools;
+use syntax::{AstNode, SyntaxKind::*, T};
+
+use crate::{doc_links::token_as_doc_comment, RangeInfo};
+
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum MonikerDescriptorKind {
+ Namespace,
+ Type,
+ Term,
+ Method,
+ TypeParameter,
+ Parameter,
+ Macro,
+ Meta,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct MonikerDescriptor {
+ pub name: Name,
+ pub desc: MonikerDescriptorKind,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct MonikerIdentifier {
+ pub crate_name: String,
+ pub description: Vec<MonikerDescriptor>,
+}
+
+impl ToString for MonikerIdentifier {
+ fn to_string(&self) -> String {
+ match self {
+ MonikerIdentifier { description, crate_name } => {
+ format!(
+ "{}::{}",
+ crate_name,
+ description.iter().map(|x| x.name.to_string()).join("::")
+ )
+ }
+ }
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum MonikerKind {
+ Import,
+ Export,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct MonikerResult {
+ pub identifier: MonikerIdentifier,
+ pub kind: MonikerKind,
+ pub package_information: PackageInformation,
+}
+
+impl MonikerResult {
+ pub fn from_def(db: &RootDatabase, def: Definition, from_crate: Crate) -> Option<Self> {
+ def_to_moniker(db, def, from_crate)
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct PackageInformation {
+ pub name: String,
+ pub repo: String,
+ pub version: String,
+}
+
+pub(crate) fn crate_for_file(db: &RootDatabase, file_id: FileId) -> Option<Crate> {
+ for &krate in db.relevant_crates(file_id).iter() {
+ let crate_def_map = db.crate_def_map(krate);
+ for (_, data) in crate_def_map.modules() {
+ if data.origin.file_id() == Some(file_id) {
+ return Some(krate.into());
+ }
+ }
+ }
+ None
+}
+
+pub(crate) fn moniker(
+ db: &RootDatabase,
+ FilePosition { file_id, offset }: FilePosition,
+) -> Option<RangeInfo<Vec<MonikerResult>>> {
+ let sema = &Semantics::new(db);
+ let file = sema.parse(file_id).syntax().clone();
+ let current_crate = crate_for_file(db, file_id)?;
+ let original_token = pick_best_token(file.token_at_offset(offset), |kind| match kind {
+ IDENT
+ | INT_NUMBER
+ | LIFETIME_IDENT
+ | T![self]
+ | T![super]
+ | T![crate]
+ | T![Self]
+ | COMMENT => 2,
+ kind if kind.is_trivia() => 0,
+ _ => 1,
+ })?;
+ if let Some(doc_comment) = token_as_doc_comment(&original_token) {
+ return doc_comment.get_definition_with_descend_at(sema, offset, |def, _, _| {
+ let m = def_to_moniker(db, def, current_crate)?;
+ Some(RangeInfo::new(original_token.text_range(), vec![m]))
+ });
+ }
+ let navs = sema
+ .descend_into_macros(original_token.clone())
+ .into_iter()
+ .filter_map(|token| {
+ IdentClass::classify_token(sema, &token).map(IdentClass::definitions_no_ops).map(|it| {
+ it.into_iter().flat_map(|def| def_to_moniker(sema.db, def, current_crate))
+ })
+ })
+ .flatten()
+ .unique()
+ .collect::<Vec<_>>();
+ Some(RangeInfo::new(original_token.text_range(), navs))
+}
+
+pub(crate) fn def_to_moniker(
+ db: &RootDatabase,
+ def: Definition,
+ from_crate: Crate,
+) -> Option<MonikerResult> {
+ if matches!(
+ def,
+ Definition::GenericParam(_)
+ | Definition::Label(_)
+ | Definition::DeriveHelper(_)
+ | Definition::BuiltinAttr(_)
+ | Definition::ToolModule(_)
+ ) {
+ return None;
+ }
+
+ let module = def.module(db)?;
+ let krate = module.krate();
+ let mut description = vec![];
+ description.extend(module.path_to_root(db).into_iter().filter_map(|x| {
+ Some(MonikerDescriptor { name: x.name(db)?, desc: MonikerDescriptorKind::Namespace })
+ }));
+
+ // Handle associated items within a trait
+ if let Some(assoc) = def.as_assoc_item(db) {
+ let container = assoc.container(db);
+ match container {
+ AssocItemContainer::Trait(trait_) => {
+ // Because different traits can have functions with the same name,
+ // we have to include the trait name as part of the moniker for uniqueness.
+ description.push(MonikerDescriptor {
+ name: trait_.name(db),
+ desc: MonikerDescriptorKind::Type,
+ });
+ }
+ AssocItemContainer::Impl(impl_) => {
+ // Because a struct can implement multiple traits, for implementations
+ // we add both the struct name and the trait name to the path
+ if let Some(adt) = impl_.self_ty(db).as_adt() {
+ description.push(MonikerDescriptor {
+ name: adt.name(db),
+ desc: MonikerDescriptorKind::Type,
+ });
+ }
+
+ if let Some(trait_) = impl_.trait_(db) {
+ description.push(MonikerDescriptor {
+ name: trait_.name(db),
+ desc: MonikerDescriptorKind::Type,
+ });
+ }
+ }
+ }
+ }
+
+ if let Definition::Field(it) = def {
+ description.push(MonikerDescriptor {
+ name: it.parent_def(db).name(db),
+ desc: MonikerDescriptorKind::Type,
+ });
+ }
+
+ let name_desc = match def {
+ // These are handled by top-level guard (for performance).
+ Definition::GenericParam(_)
+ | Definition::Label(_)
+ | Definition::DeriveHelper(_)
+ | Definition::BuiltinAttr(_)
+ | Definition::ToolModule(_) => return None,
+
+ Definition::Local(local) => {
+ if !local.is_param(db) {
+ return None;
+ }
+
+ MonikerDescriptor { name: local.name(db), desc: MonikerDescriptorKind::Parameter }
+ }
+ Definition::Macro(m) => {
+ MonikerDescriptor { name: m.name(db), desc: MonikerDescriptorKind::Macro }
+ }
+ Definition::Function(f) => {
+ MonikerDescriptor { name: f.name(db), desc: MonikerDescriptorKind::Method }
+ }
+ Definition::Variant(v) => {
+ MonikerDescriptor { name: v.name(db), desc: MonikerDescriptorKind::Type }
+ }
+ Definition::Const(c) => {
+ MonikerDescriptor { name: c.name(db)?, desc: MonikerDescriptorKind::Term }
+ }
+ Definition::Trait(trait_) => {
+ MonikerDescriptor { name: trait_.name(db), desc: MonikerDescriptorKind::Type }
+ }
+ Definition::TypeAlias(ta) => {
+ MonikerDescriptor { name: ta.name(db), desc: MonikerDescriptorKind::TypeParameter }
+ }
+ Definition::Module(m) => {
+ MonikerDescriptor { name: m.name(db)?, desc: MonikerDescriptorKind::Namespace }
+ }
+ Definition::BuiltinType(b) => {
+ MonikerDescriptor { name: b.name(), desc: MonikerDescriptorKind::Type }
+ }
+ Definition::SelfType(imp) => MonikerDescriptor {
+ name: imp.self_ty(db).as_adt()?.name(db),
+ desc: MonikerDescriptorKind::Type,
+ },
+ Definition::Field(it) => {
+ MonikerDescriptor { name: it.name(db), desc: MonikerDescriptorKind::Term }
+ }
+ Definition::Adt(adt) => {
+ MonikerDescriptor { name: adt.name(db), desc: MonikerDescriptorKind::Type }
+ }
+ Definition::Static(s) => {
+ MonikerDescriptor { name: s.name(db), desc: MonikerDescriptorKind::Meta }
+ }
+ };
+
+ description.push(name_desc);
+
+ Some(MonikerResult {
+ identifier: MonikerIdentifier {
+ crate_name: krate.display_name(db)?.crate_name().to_string(),
+ description,
+ },
+ kind: if krate == from_crate { MonikerKind::Export } else { MonikerKind::Import },
+ package_information: {
++ let (name, repo, version) = match krate.origin(db) {
++ CrateOrigin::CratesIo { repo, name } => (
++ name.unwrap_or(krate.display_name(db)?.canonical_name().to_string()),
++ repo?,
++ krate.version(db)?,
++ ),
+ CrateOrigin::Lang(lang) => (
++ krate.display_name(db)?.canonical_name().to_string(),
+ "https://github.com/rust-lang/rust/".to_string(),
+ match lang {
+ LangCrateOrigin::Other => {
+ "https://github.com/rust-lang/rust/library/".into()
+ }
+ lang => format!("https://github.com/rust-lang/rust/library/{lang}",),
+ },
+ ),
+ };
+ PackageInformation { name, repo, version }
+ },
+ })
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::fixture;
+
+ use super::MonikerKind;
+
+ #[track_caller]
+ fn no_moniker(ra_fixture: &str) {
+ let (analysis, position) = fixture::position(ra_fixture);
+ if let Some(x) = analysis.moniker(position).unwrap() {
+ assert_eq!(x.info.len(), 0, "Moniker founded but no moniker expected: {:?}", x);
+ }
+ }
+
+ #[track_caller]
+ fn check_moniker(ra_fixture: &str, identifier: &str, package: &str, kind: MonikerKind) {
+ let (analysis, position) = fixture::position(ra_fixture);
+ let x = analysis.moniker(position).unwrap().expect("no moniker found").info;
+ assert_eq!(x.len(), 1);
+ let x = x.into_iter().next().unwrap();
+ assert_eq!(identifier, x.identifier.to_string());
+ assert_eq!(package, format!("{:?}", x.package_information));
+ assert_eq!(kind, x.kind);
+ }
+
+ #[test]
+ fn basic() {
+ check_moniker(
+ r#"
+//- /lib.rs crate:main deps:foo
+use foo::module::func;
+fn main() {
+ func$0();
+}
+//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+pub mod module {
+ pub fn func() {}
+}
+"#,
+ "foo::module::func",
+ r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
+ MonikerKind::Import,
+ );
+ check_moniker(
+ r#"
+//- /lib.rs crate:main deps:foo
+use foo::module::func;
+fn main() {
+ func();
+}
+//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+pub mod module {
+ pub fn func$0() {}
+}
+"#,
+ "foo::module::func",
+ r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
+ MonikerKind::Export,
+ );
+ }
+
+ #[test]
+ fn moniker_for_trait() {
+ check_moniker(
+ r#"
+//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+pub mod module {
+ pub trait MyTrait {
+ pub fn func$0() {}
+ }
+}
+"#,
+ "foo::module::MyTrait::func",
+ r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
+ MonikerKind::Export,
+ );
+ }
+
+ #[test]
+ fn moniker_for_trait_constant() {
+ check_moniker(
+ r#"
+//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+pub mod module {
+ pub trait MyTrait {
+ const MY_CONST$0: u8;
+ }
+}
+"#,
+ "foo::module::MyTrait::MY_CONST",
+ r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
+ MonikerKind::Export,
+ );
+ }
+
+ #[test]
+ fn moniker_for_trait_type() {
+ check_moniker(
+ r#"
+//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+pub mod module {
+ pub trait MyTrait {
+ type MyType$0;
+ }
+}
+"#,
+ "foo::module::MyTrait::MyType",
+ r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
+ MonikerKind::Export,
+ );
+ }
+
+ #[test]
+ fn moniker_for_trait_impl_function() {
+ check_moniker(
+ r#"
+//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+pub mod module {
+ pub trait MyTrait {
+ pub fn func() {}
+ }
+
+ struct MyStruct {}
+
+ impl MyTrait for MyStruct {
+ pub fn func$0() {}
+ }
+}
+"#,
+ "foo::module::MyStruct::MyTrait::func",
+ r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
+ MonikerKind::Export,
+ );
+ }
+
+ #[test]
+ fn moniker_for_field() {
+ check_moniker(
+ r#"
+//- /lib.rs crate:main deps:foo
+use foo::St;
+fn main() {
+ let x = St { a$0: 2 };
+}
+//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+pub struct St {
+ pub a: i32,
+}
+"#,
+ "foo::St::a",
+ r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
+ MonikerKind::Import,
+ );
+ }
+
+ #[test]
+ fn no_moniker_for_local() {
+ no_moniker(
+ r#"
+//- /lib.rs crate:main deps:foo
+use foo::module::func;
+fn main() {
+ func();
+}
+//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+pub mod module {
+ pub fn func() {
+ let x$0 = 2;
+ }
+}
+"#,
+ );
+ }
+}
--- /dev/null
- (T![?], _) => HlTag::Operator(HlOperator::Other) | HlMod::ControlFlow,
+//! Computes color for a single element.
+
+use hir::{AsAssocItem, HasVisibility, Semantics};
+use ide_db::{
+ defs::{Definition, IdentClass, NameClass, NameRefClass},
+ FxHashMap, RootDatabase, SymbolKind,
+};
+use syntax::{
+ ast, match_ast, AstNode, AstToken, NodeOrToken,
+ SyntaxKind::{self, *},
+ SyntaxNode, SyntaxToken, T,
+};
+
+use crate::{
+ syntax_highlighting::tags::{HlOperator, HlPunct},
+ Highlight, HlMod, HlTag,
+};
+
+pub(super) fn token(sema: &Semantics<'_, RootDatabase>, token: SyntaxToken) -> Option<Highlight> {
+ if let Some(comment) = ast::Comment::cast(token.clone()) {
+ let h = HlTag::Comment;
+ return Some(match comment.kind().doc {
+ Some(_) => h | HlMod::Documentation,
+ None => h.into(),
+ });
+ }
+
+ let highlight: Highlight = match token.kind() {
+ STRING | BYTE_STRING => HlTag::StringLiteral.into(),
+ INT_NUMBER if token.parent_ancestors().nth(1).map(|it| it.kind()) == Some(FIELD_EXPR) => {
+ SymbolKind::Field.into()
+ }
+ INT_NUMBER | FLOAT_NUMBER => HlTag::NumericLiteral.into(),
+ BYTE => HlTag::ByteLiteral.into(),
+ CHAR => HlTag::CharLiteral.into(),
+ IDENT if token.parent().and_then(ast::TokenTree::cast).is_some() => {
+ // from this point on we are inside a token tree, this only happens for identifiers
+ // that were not mapped down into macro invocations
+ HlTag::None.into()
+ }
+ p if p.is_punct() => punctuation(sema, token, p),
+ k if k.is_keyword() => keyword(sema, token, k)?,
+ _ => return None,
+ };
+ Some(highlight)
+}
+
+pub(super) fn name_like(
+ sema: &Semantics<'_, RootDatabase>,
+ krate: hir::Crate,
+ bindings_shadow_count: &mut FxHashMap<hir::Name, u32>,
+ syntactic_name_ref_highlighting: bool,
+ name_like: ast::NameLike,
+) -> Option<(Highlight, Option<u64>)> {
+ let mut binding_hash = None;
+ let highlight = match name_like {
+ ast::NameLike::NameRef(name_ref) => highlight_name_ref(
+ sema,
+ krate,
+ bindings_shadow_count,
+ &mut binding_hash,
+ syntactic_name_ref_highlighting,
+ name_ref,
+ ),
+ ast::NameLike::Name(name) => {
+ highlight_name(sema, bindings_shadow_count, &mut binding_hash, krate, name)
+ }
+ ast::NameLike::Lifetime(lifetime) => match IdentClass::classify_lifetime(sema, &lifetime) {
+ Some(IdentClass::NameClass(NameClass::Definition(def))) => {
+ highlight_def(sema, krate, def) | HlMod::Definition
+ }
+ Some(IdentClass::NameRefClass(NameRefClass::Definition(def))) => {
+ highlight_def(sema, krate, def)
+ }
+ // FIXME: Fallback for 'static and '_, as we do not resolve these yet
+ _ => SymbolKind::LifetimeParam.into(),
+ },
+ };
+ Some((highlight, binding_hash))
+}
+
+fn punctuation(
+ sema: &Semantics<'_, RootDatabase>,
+ token: SyntaxToken,
+ kind: SyntaxKind,
+) -> Highlight {
+ let parent = token.parent();
+ let parent_kind = parent.as_ref().map_or(EOF, SyntaxNode::kind);
+ match (kind, parent_kind) {
- (T![&], _) => {
++ (T![?], TRY_EXPR) => HlTag::Operator(HlOperator::Other) | HlMod::ControlFlow,
+ (T![&], BIN_EXPR) => HlOperator::Bitwise.into(),
- (T![::] | T![->] | T![=>] | T![..] | T![=] | T![@] | T![.], _) => HlOperator::Other.into(),
++ (T![&], REF_EXPR) => {
+ let h = HlTag::Operator(HlOperator::Other).into();
+ let is_unsafe = parent
+ .and_then(ast::RefExpr::cast)
+ .map(|ref_expr| sema.is_unsafe_ref_expr(&ref_expr));
+ if let Some(true) = is_unsafe {
+ h | HlMod::Unsafe
+ } else {
+ h
+ }
+ }
- (T![|] | T![&] | T![!] | T![^] | T![>>] | T![<<], BIN_EXPR) => HlOperator::Bitwise.into(),
++ (T![::] | T![->] | T![=>] | T![..] | T![..=] | T![=] | T![@] | T![.], _) => {
++ HlOperator::Other.into()
++ }
+ (T![!], MACRO_CALL | MACRO_RULES) => HlPunct::MacroBang.into(),
+ (T![!], NEVER_TYPE) => HlTag::BuiltinType.into(),
+ (T![!], PREFIX_EXPR) => HlOperator::Logical.into(),
+ (T![*], PTR_TYPE) => HlTag::Keyword.into(),
+ (T![*], PREFIX_EXPR) => {
+ let is_raw_ptr = (|| {
+ let prefix_expr = parent.and_then(ast::PrefixExpr::cast)?;
+ let expr = prefix_expr.expr()?;
+ sema.type_of_expr(&expr)?.original.is_raw_ptr().then(|| ())
+ })();
+ if let Some(()) = is_raw_ptr {
+ HlTag::Operator(HlOperator::Other) | HlMod::Unsafe
+ } else {
+ HlOperator::Other.into()
+ }
+ }
+ (T![-], PREFIX_EXPR) => {
+ let prefix_expr = parent.and_then(ast::PrefixExpr::cast).and_then(|e| e.expr());
+ match prefix_expr {
+ Some(ast::Expr::Literal(_)) => HlTag::NumericLiteral,
+ _ => HlTag::Operator(HlOperator::Other),
+ }
+ .into()
+ }
+ (T![+] | T![-] | T![*] | T![/] | T![%], BIN_EXPR) => HlOperator::Arithmetic.into(),
+ (T![+=] | T![-=] | T![*=] | T![/=] | T![%=], BIN_EXPR) => {
+ Highlight::from(HlOperator::Arithmetic) | HlMod::Mutable
+ }
- (_, PREFIX_EXPR | BIN_EXPR | RANGE_EXPR | RANGE_PAT | REST_PAT) => HlOperator::Other.into(),
++ (T![|] | T![&] | T![^] | T![>>] | T![<<], BIN_EXPR) => HlOperator::Bitwise.into(),
+ (T![|=] | T![&=] | T![^=] | T![>>=] | T![<<=], BIN_EXPR) => {
+ Highlight::from(HlOperator::Bitwise) | HlMod::Mutable
+ }
+ (T![&&] | T![||], BIN_EXPR) => HlOperator::Logical.into(),
+ (T![>] | T![<] | T![==] | T![>=] | T![<=] | T![!=], BIN_EXPR) => {
+ HlOperator::Comparison.into()
+ }
+ (_, ATTR) => HlTag::AttributeBracket.into(),
+ (kind, _) => match kind {
+ T!['['] | T![']'] => HlPunct::Bracket,
+ T!['{'] | T!['}'] => HlPunct::Brace,
+ T!['('] | T![')'] => HlPunct::Parenthesis,
+ T![<] | T![>] => HlPunct::Angle,
+ T![,] => HlPunct::Comma,
+ T![:] => HlPunct::Colon,
+ T![;] => HlPunct::Semi,
+ T![.] => HlPunct::Dot,
+ _ => HlPunct::Other,
+ }
+ .into(),
+ }
+}
+
+fn keyword(
+ sema: &Semantics<'_, RootDatabase>,
+ token: SyntaxToken,
+ kind: SyntaxKind,
+) -> Option<Highlight> {
+ let h = Highlight::new(HlTag::Keyword);
+ let h = match kind {
+ T![await] => h | HlMod::Async | HlMod::ControlFlow,
+ T![async] => h | HlMod::Async,
+ T![break]
+ | T![continue]
+ | T![else]
+ | T![if]
+ | T![in]
+ | T![loop]
+ | T![match]
+ | T![return]
+ | T![while]
+ | T![yield] => h | HlMod::ControlFlow,
+ T![for] if parent_matches::<ast::ForExpr>(&token) => h | HlMod::ControlFlow,
+ T![unsafe] => h | HlMod::Unsafe,
+ T![true] | T![false] => HlTag::BoolLiteral.into(),
+ // crate is handled just as a token if it's in an `extern crate`
+ T![crate] if parent_matches::<ast::ExternCrate>(&token) => h,
+ // self, crate, super and `Self` are handled as either a Name or NameRef already, unless they
+ // are inside unmapped token trees
+ T![self] | T![crate] | T![super] | T![Self] if parent_matches::<ast::NameRef>(&token) => {
+ return None
+ }
+ T![self] if parent_matches::<ast::Name>(&token) => return None,
+ T![ref] => match token.parent().and_then(ast::IdentPat::cast) {
+ Some(ident) if sema.is_unsafe_ident_pat(&ident) => h | HlMod::Unsafe,
+ _ => h,
+ },
+ _ => h,
+ };
+ Some(h)
+}
+
+fn highlight_name_ref(
+ sema: &Semantics<'_, RootDatabase>,
+ krate: hir::Crate,
+ bindings_shadow_count: &mut FxHashMap<hir::Name, u32>,
+ binding_hash: &mut Option<u64>,
+ syntactic_name_ref_highlighting: bool,
+ name_ref: ast::NameRef,
+) -> Highlight {
+ let db = sema.db;
+ if let Some(res) = highlight_method_call_by_name_ref(sema, krate, &name_ref) {
+ return res;
+ }
+
+ let name_class = match NameRefClass::classify(sema, &name_ref) {
+ Some(name_kind) => name_kind,
+ None if syntactic_name_ref_highlighting => {
+ return highlight_name_ref_by_syntax(name_ref, sema, krate)
+ }
+ // FIXME: This is required for helper attributes used by proc-macros, as those do not map down
+ // to anything when used.
+ // We can fix this for derive attributes since derive helpers are recorded, but not for
+ // general attributes.
+ None if name_ref.syntax().ancestors().any(|it| it.kind() == ATTR) => {
+ return HlTag::Symbol(SymbolKind::Attribute).into();
+ }
+ None => return HlTag::UnresolvedReference.into(),
+ };
+ let mut h = match name_class {
+ NameRefClass::Definition(def) => {
+ if let Definition::Local(local) = &def {
+ let name = local.name(db);
+ let shadow_count = bindings_shadow_count.entry(name.clone()).or_default();
+ *binding_hash = Some(calc_binding_hash(&name, *shadow_count))
+ };
+
+ let mut h = highlight_def(sema, krate, def);
+
+ match def {
+ Definition::Local(local) if is_consumed_lvalue(name_ref.syntax(), &local, db) => {
+ h |= HlMod::Consuming;
+ }
+ Definition::Trait(trait_) if trait_.is_unsafe(db) => {
+ if ast::Impl::for_trait_name_ref(&name_ref)
+ .map_or(false, |impl_| impl_.unsafe_token().is_some())
+ {
+ h |= HlMod::Unsafe;
+ }
+ }
+ Definition::Field(field) => {
+ if let Some(parent) = name_ref.syntax().parent() {
+ if matches!(parent.kind(), FIELD_EXPR | RECORD_PAT_FIELD) {
+ if let hir::VariantDef::Union(_) = field.parent_def(db) {
+ h |= HlMod::Unsafe;
+ }
+ }
+ }
+ }
+ Definition::Macro(_) => {
+ if let Some(macro_call) =
+ ide_db::syntax_helpers::node_ext::full_path_of_name_ref(&name_ref)
+ .and_then(|it| it.syntax().parent().and_then(ast::MacroCall::cast))
+ {
+ if sema.is_unsafe_macro_call(¯o_call) {
+ h |= HlMod::Unsafe;
+ }
+ }
+ }
+ _ => (),
+ }
+
+ h
+ }
+ NameRefClass::FieldShorthand { .. } => SymbolKind::Field.into(),
+ };
+
+ h.tag = match name_ref.token_kind() {
+ T![Self] => HlTag::Symbol(SymbolKind::SelfType),
+ T![self] => HlTag::Symbol(SymbolKind::SelfParam),
+ T![super] | T![crate] => HlTag::Keyword,
+ _ => h.tag,
+ };
+ h
+}
+
+fn highlight_name(
+ sema: &Semantics<'_, RootDatabase>,
+ bindings_shadow_count: &mut FxHashMap<hir::Name, u32>,
+ binding_hash: &mut Option<u64>,
+ krate: hir::Crate,
+ name: ast::Name,
+) -> Highlight {
+ let name_kind = NameClass::classify(sema, &name);
+ if let Some(NameClass::Definition(Definition::Local(local))) = &name_kind {
+ let name = local.name(sema.db);
+ let shadow_count = bindings_shadow_count.entry(name.clone()).or_default();
+ *shadow_count += 1;
+ *binding_hash = Some(calc_binding_hash(&name, *shadow_count))
+ };
+ match name_kind {
+ Some(NameClass::Definition(def)) => {
+ let mut h = highlight_def(sema, krate, def) | HlMod::Definition;
+ if let Definition::Trait(trait_) = &def {
+ if trait_.is_unsafe(sema.db) {
+ h |= HlMod::Unsafe;
+ }
+ }
+ h
+ }
+ Some(NameClass::ConstReference(def)) => highlight_def(sema, krate, def),
+ Some(NameClass::PatFieldShorthand { field_ref, .. }) => {
+ let mut h = HlTag::Symbol(SymbolKind::Field).into();
+ if let hir::VariantDef::Union(_) = field_ref.parent_def(sema.db) {
+ h |= HlMod::Unsafe;
+ }
+ h
+ }
+ None => highlight_name_by_syntax(name) | HlMod::Definition,
+ }
+}
+
+fn calc_binding_hash(name: &hir::Name, shadow_count: u32) -> u64 {
+ fn hash<T: std::hash::Hash + std::fmt::Debug>(x: T) -> u64 {
+ use std::{collections::hash_map::DefaultHasher, hash::Hasher};
+
+ let mut hasher = DefaultHasher::new();
+ x.hash(&mut hasher);
+ hasher.finish()
+ }
+
+ hash((name, shadow_count))
+}
+
+fn highlight_def(
+ sema: &Semantics<'_, RootDatabase>,
+ krate: hir::Crate,
+ def: Definition,
+) -> Highlight {
+ let db = sema.db;
+ let mut h = match def {
+ Definition::Macro(m) => Highlight::new(HlTag::Symbol(m.kind(sema.db).into())),
+ Definition::Field(_) => Highlight::new(HlTag::Symbol(SymbolKind::Field)),
+ Definition::Module(module) => {
+ let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Module));
+ if module.is_crate_root(db) {
+ h |= HlMod::CrateRoot;
+ }
+ h
+ }
+ Definition::Function(func) => {
+ let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Function));
+ if let Some(item) = func.as_assoc_item(db) {
+ h |= HlMod::Associated;
+ match func.self_param(db) {
+ Some(sp) => match sp.access(db) {
+ hir::Access::Exclusive => {
+ h |= HlMod::Mutable;
+ h |= HlMod::Reference;
+ }
+ hir::Access::Shared => h |= HlMod::Reference,
+ hir::Access::Owned => h |= HlMod::Consuming,
+ },
+ None => h |= HlMod::Static,
+ }
+
+ match item.container(db) {
+ hir::AssocItemContainer::Impl(i) => {
+ if i.trait_(db).is_some() {
+ h |= HlMod::Trait;
+ }
+ }
+ hir::AssocItemContainer::Trait(_t) => {
+ h |= HlMod::Trait;
+ }
+ }
+ }
+
+ if func.is_unsafe_to_call(db) {
+ h |= HlMod::Unsafe;
+ }
+ if func.is_async(db) {
+ h |= HlMod::Async;
+ }
+
+ h
+ }
+ Definition::Adt(adt) => {
+ let h = match adt {
+ hir::Adt::Struct(_) => HlTag::Symbol(SymbolKind::Struct),
+ hir::Adt::Enum(_) => HlTag::Symbol(SymbolKind::Enum),
+ hir::Adt::Union(_) => HlTag::Symbol(SymbolKind::Union),
+ };
+
+ Highlight::new(h)
+ }
+ Definition::Variant(_) => Highlight::new(HlTag::Symbol(SymbolKind::Variant)),
+ Definition::Const(konst) => {
+ let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Const));
+
+ if let Some(item) = konst.as_assoc_item(db) {
+ h |= HlMod::Associated;
+ match item.container(db) {
+ hir::AssocItemContainer::Impl(i) => {
+ if i.trait_(db).is_some() {
+ h |= HlMod::Trait;
+ }
+ }
+ hir::AssocItemContainer::Trait(_t) => {
+ h |= HlMod::Trait;
+ }
+ }
+ }
+
+ h
+ }
+ Definition::Trait(_) => Highlight::new(HlTag::Symbol(SymbolKind::Trait)),
+ Definition::TypeAlias(type_) => {
+ let mut h = Highlight::new(HlTag::Symbol(SymbolKind::TypeAlias));
+
+ if let Some(item) = type_.as_assoc_item(db) {
+ h |= HlMod::Associated;
+ match item.container(db) {
+ hir::AssocItemContainer::Impl(i) => {
+ if i.trait_(db).is_some() {
+ h |= HlMod::Trait;
+ }
+ }
+ hir::AssocItemContainer::Trait(_t) => {
+ h |= HlMod::Trait;
+ }
+ }
+ }
+
+ h
+ }
+ Definition::BuiltinType(_) => Highlight::new(HlTag::BuiltinType),
+ Definition::Static(s) => {
+ let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Static));
+
+ if s.is_mut(db) {
+ h |= HlMod::Mutable;
+ h |= HlMod::Unsafe;
+ }
+
+ h
+ }
+ Definition::SelfType(_) => Highlight::new(HlTag::Symbol(SymbolKind::Impl)),
+ Definition::GenericParam(it) => match it {
+ hir::GenericParam::TypeParam(_) => Highlight::new(HlTag::Symbol(SymbolKind::TypeParam)),
+ hir::GenericParam::ConstParam(_) => {
+ Highlight::new(HlTag::Symbol(SymbolKind::ConstParam))
+ }
+ hir::GenericParam::LifetimeParam(_) => {
+ Highlight::new(HlTag::Symbol(SymbolKind::LifetimeParam))
+ }
+ },
+ Definition::Local(local) => {
+ let tag = if local.is_self(db) {
+ HlTag::Symbol(SymbolKind::SelfParam)
+ } else if local.is_param(db) {
+ HlTag::Symbol(SymbolKind::ValueParam)
+ } else {
+ HlTag::Symbol(SymbolKind::Local)
+ };
+ let mut h = Highlight::new(tag);
+ let ty = local.ty(db);
+ if local.is_mut(db) || ty.is_mutable_reference() {
+ h |= HlMod::Mutable;
+ }
+ if local.is_ref(db) || ty.is_reference() {
+ h |= HlMod::Reference;
+ }
+ if ty.as_callable(db).is_some() || ty.impls_fnonce(db) {
+ h |= HlMod::Callable;
+ }
+ h
+ }
+ Definition::Label(_) => Highlight::new(HlTag::Symbol(SymbolKind::Label)),
+ Definition::BuiltinAttr(_) => Highlight::new(HlTag::Symbol(SymbolKind::BuiltinAttr)),
+ Definition::ToolModule(_) => Highlight::new(HlTag::Symbol(SymbolKind::ToolModule)),
+ Definition::DeriveHelper(_) => Highlight::new(HlTag::Symbol(SymbolKind::DeriveHelper)),
+ };
+
+ let def_crate = def.krate(db);
+ let is_from_other_crate = def_crate != Some(krate);
+ let is_from_builtin_crate = def_crate.map_or(false, |def_crate| def_crate.is_builtin(db));
+ let is_builtin_type = matches!(def, Definition::BuiltinType(_));
+ let is_public = def.visibility(db) == Some(hir::Visibility::Public);
+
+ match (is_from_other_crate, is_builtin_type, is_public) {
+ (true, false, _) => h |= HlMod::Library,
+ (false, _, true) => h |= HlMod::Public,
+ _ => {}
+ }
+
+ if is_from_builtin_crate {
+ h |= HlMod::DefaultLibrary;
+ }
+
+ h
+}
+
+fn highlight_method_call_by_name_ref(
+ sema: &Semantics<'_, RootDatabase>,
+ krate: hir::Crate,
+ name_ref: &ast::NameRef,
+) -> Option<Highlight> {
+ let mc = name_ref.syntax().parent().and_then(ast::MethodCallExpr::cast)?;
+ highlight_method_call(sema, krate, &mc)
+}
+
+fn highlight_method_call(
+ sema: &Semantics<'_, RootDatabase>,
+ krate: hir::Crate,
+ method_call: &ast::MethodCallExpr,
+) -> Option<Highlight> {
+ let func = sema.resolve_method_call(method_call)?;
+
+ let mut h = SymbolKind::Function.into();
+ h |= HlMod::Associated;
+
+ if func.is_unsafe_to_call(sema.db) || sema.is_unsafe_method_call(method_call) {
+ h |= HlMod::Unsafe;
+ }
+ if func.is_async(sema.db) {
+ h |= HlMod::Async;
+ }
+ if func
+ .as_assoc_item(sema.db)
+ .and_then(|it| it.containing_trait_or_trait_impl(sema.db))
+ .is_some()
+ {
+ h |= HlMod::Trait;
+ }
+
+ let def_crate = func.module(sema.db).krate();
+ let is_from_other_crate = def_crate != krate;
+ let is_from_builtin_crate = def_crate.is_builtin(sema.db);
+ let is_public = func.visibility(sema.db) == hir::Visibility::Public;
+
+ if is_from_other_crate {
+ h |= HlMod::Library;
+ } else if is_public {
+ h |= HlMod::Public;
+ }
+
+ if is_from_builtin_crate {
+ h |= HlMod::DefaultLibrary;
+ }
+
+ if let Some(self_param) = func.self_param(sema.db) {
+ match self_param.access(sema.db) {
+ hir::Access::Shared => h |= HlMod::Reference,
+ hir::Access::Exclusive => {
+ h |= HlMod::Mutable;
+ h |= HlMod::Reference;
+ }
+ hir::Access::Owned => {
+ if let Some(receiver_ty) =
+ method_call.receiver().and_then(|it| sema.type_of_expr(&it))
+ {
+ if !receiver_ty.adjusted().is_copy(sema.db) {
+ h |= HlMod::Consuming
+ }
+ }
+ }
+ }
+ }
+ Some(h)
+}
+
+fn highlight_name_by_syntax(name: ast::Name) -> Highlight {
+ let default = HlTag::UnresolvedReference;
+
+ let parent = match name.syntax().parent() {
+ Some(it) => it,
+ _ => return default.into(),
+ };
+
+ let tag = match parent.kind() {
+ STRUCT => SymbolKind::Struct,
+ ENUM => SymbolKind::Enum,
+ VARIANT => SymbolKind::Variant,
+ UNION => SymbolKind::Union,
+ TRAIT => SymbolKind::Trait,
+ TYPE_ALIAS => SymbolKind::TypeAlias,
+ TYPE_PARAM => SymbolKind::TypeParam,
+ RECORD_FIELD => SymbolKind::Field,
+ MODULE => SymbolKind::Module,
+ FN => SymbolKind::Function,
+ CONST => SymbolKind::Const,
+ STATIC => SymbolKind::Static,
+ IDENT_PAT => SymbolKind::Local,
+ _ => return default.into(),
+ };
+
+ tag.into()
+}
+
+fn highlight_name_ref_by_syntax(
+ name: ast::NameRef,
+ sema: &Semantics<'_, RootDatabase>,
+ krate: hir::Crate,
+) -> Highlight {
+ let default = HlTag::UnresolvedReference;
+
+ let parent = match name.syntax().parent() {
+ Some(it) => it,
+ _ => return default.into(),
+ };
+
+ match parent.kind() {
+ METHOD_CALL_EXPR => ast::MethodCallExpr::cast(parent)
+ .and_then(|it| highlight_method_call(sema, krate, &it))
+ .unwrap_or_else(|| SymbolKind::Function.into()),
+ FIELD_EXPR => {
+ let h = HlTag::Symbol(SymbolKind::Field);
+ let is_union = ast::FieldExpr::cast(parent)
+ .and_then(|field_expr| sema.resolve_field(&field_expr))
+ .map_or(false, |field| {
+ matches!(field.parent_def(sema.db), hir::VariantDef::Union(_))
+ });
+ if is_union {
+ h | HlMod::Unsafe
+ } else {
+ h.into()
+ }
+ }
+ PATH_SEGMENT => {
+ let name_based_fallback = || {
+ if name.text().chars().next().unwrap_or_default().is_uppercase() {
+ SymbolKind::Struct.into()
+ } else {
+ SymbolKind::Module.into()
+ }
+ };
+ let path = match parent.parent().and_then(ast::Path::cast) {
+ Some(it) => it,
+ _ => return name_based_fallback(),
+ };
+ let expr = match path.syntax().parent() {
+ Some(parent) => match_ast! {
+ match parent {
+ ast::PathExpr(path) => path,
+ ast::MacroCall(_) => return SymbolKind::Macro.into(),
+ _ => return name_based_fallback(),
+ }
+ },
+ // within path, decide whether it is module or adt by checking for uppercase name
+ None => return name_based_fallback(),
+ };
+ let parent = match expr.syntax().parent() {
+ Some(it) => it,
+ None => return default.into(),
+ };
+
+ match parent.kind() {
+ CALL_EXPR => SymbolKind::Function.into(),
+ _ => if name.text().chars().next().unwrap_or_default().is_uppercase() {
+ SymbolKind::Struct
+ } else {
+ SymbolKind::Const
+ }
+ .into(),
+ }
+ }
+ _ => default.into(),
+ }
+}
+
+fn is_consumed_lvalue(node: &SyntaxNode, local: &hir::Local, db: &RootDatabase) -> bool {
+ // When lvalues are passed as arguments and they're not Copy, then mark them as Consuming.
+ parents_match(node.clone().into(), &[PATH_SEGMENT, PATH, PATH_EXPR, ARG_LIST])
+ && !local.ty(db).is_copy(db)
+}
+
+/// Returns true if the parent nodes of `node` all match the `SyntaxKind`s in `kinds` exactly.
+fn parents_match(mut node: NodeOrToken<SyntaxNode, SyntaxToken>, mut kinds: &[SyntaxKind]) -> bool {
+ while let (Some(parent), [kind, rest @ ..]) = (&node.parent(), kinds) {
+ if parent.kind() != *kind {
+ return false;
+ }
+
+ // FIXME: Would be nice to get parent out of the match, but binding by-move and by-value
+ // in the same pattern is unstable: rust-lang/rust#68354.
+ node = node.parent().unwrap().into();
+ kinds = rest;
+ }
+
+ // Only true if we matched all expected kinds
+ kinds.is_empty()
+}
+
+fn parent_matches<N: AstNode>(token: &SyntaxToken) -> bool {
+ token.parent().map_or(false, |it| N::can_cast(it.kind()))
+}
--- /dev/null
- <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function associated declaration public reference">is_not_static</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration reference">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
+
+<style>
+body { margin: 0; }
+pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
+
+.lifetime { color: #DFAF8F; font-style: italic; }
+.label { color: #DFAF8F; font-style: italic; }
+.comment { color: #7F9F7F; }
+.documentation { color: #629755; }
+.intra_doc_link { font-style: italic; }
+.injected { opacity: 0.65 ; }
+.struct, .enum { color: #7CB8BB; }
+.enum_variant { color: #BDE0F3; }
+.string_literal { color: #CC9393; }
+.field { color: #94BFF3; }
+.function { color: #93E0E3; }
+.function.unsafe { color: #BC8383; }
+.trait.unsafe { color: #BC8383; }
+.operator.unsafe { color: #BC8383; }
+.mutable.unsafe { color: #BC8383; text-decoration: underline; }
+.keyword.unsafe { color: #BC8383; font-weight: bold; }
+.macro.unsafe { color: #BC8383; }
+.parameter { color: #94BFF3; }
+.text { color: #DCDCCC; }
+.type { color: #7CB8BB; }
+.builtin_type { color: #8CD0D3; }
+.type_param { color: #DFAF8F; }
+.attribute { color: #94BFF3; }
+.numeric_literal { color: #BFEBBF; }
+.bool_literal { color: #BFE6EB; }
+.macro { color: #94BFF3; }
+.derive { color: #94BFF3; font-style: italic; }
+.module { color: #AFD8AF; }
+.value_param { color: #DCDCCC; }
+.variable { color: #DCDCCC; }
+.format_specifier { color: #CC696B; }
+.mutable { text-decoration: underline; }
+.escape_sequence { color: #94BFF3; }
+.keyword { color: #F0DFAF; font-weight: bold; }
+.control { font-style: italic; }
+.reference { font-style: italic; font-weight: bold; }
+
+.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+</style>
+<pre><code><span class="keyword">fn</span> <span class="function declaration">not_static</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
+
+<span class="keyword">struct</span> <span class="struct declaration">foo</span> <span class="brace">{</span><span class="brace">}</span>
+
+<span class="keyword">impl</span> <span class="struct">foo</span> <span class="brace">{</span>
+ <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function associated declaration public static">is_static</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
- <span class="keyword">fn</span> <span class="function associated declaration reference trait">t_is_not_static</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration reference">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
++ <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function associated declaration public reference">is_not_static</span><span class="parenthesis">(</span><span class="punctuation">&</span><span class="self_keyword declaration reference">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
+<span class="brace">}</span>
+
+<span class="keyword">trait</span> <span class="trait declaration">t</span> <span class="brace">{</span>
+ <span class="keyword">fn</span> <span class="function associated declaration static trait">t_is_static</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
- <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function associated declaration public reference trait">is_not_static</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration reference">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
++ <span class="keyword">fn</span> <span class="function associated declaration reference trait">t_is_not_static</span><span class="parenthesis">(</span><span class="punctuation">&</span><span class="self_keyword declaration reference">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
+<span class="brace">}</span>
+
+<span class="keyword">impl</span> <span class="trait">t</span> <span class="keyword">for</span> <span class="struct">foo</span> <span class="brace">{</span>
+ <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function associated declaration public static trait">is_static</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
++ <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function associated declaration public reference trait">is_not_static</span><span class="parenthesis">(</span><span class="punctuation">&</span><span class="self_keyword declaration reference">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
+<span class="brace">}</span></code></pre>
--- /dev/null
- <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function associated declaration public reference">foo</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration reference">self</span><span class="parenthesis">)</span> <span class="operator">-></span> <span class="builtin_type">bool</span> <span class="brace">{</span>
+
+<style>
+body { margin: 0; }
+pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
+
+.lifetime { color: #DFAF8F; font-style: italic; }
+.label { color: #DFAF8F; font-style: italic; }
+.comment { color: #7F9F7F; }
+.documentation { color: #629755; }
+.intra_doc_link { font-style: italic; }
+.injected { opacity: 0.65 ; }
+.struct, .enum { color: #7CB8BB; }
+.enum_variant { color: #BDE0F3; }
+.string_literal { color: #CC9393; }
+.field { color: #94BFF3; }
+.function { color: #93E0E3; }
+.function.unsafe { color: #BC8383; }
+.trait.unsafe { color: #BC8383; }
+.operator.unsafe { color: #BC8383; }
+.mutable.unsafe { color: #BC8383; text-decoration: underline; }
+.keyword.unsafe { color: #BC8383; font-weight: bold; }
+.macro.unsafe { color: #BC8383; }
+.parameter { color: #94BFF3; }
+.text { color: #DCDCCC; }
+.type { color: #7CB8BB; }
+.builtin_type { color: #8CD0D3; }
+.type_param { color: #DFAF8F; }
+.attribute { color: #94BFF3; }
+.numeric_literal { color: #BFEBBF; }
+.bool_literal { color: #BFE6EB; }
+.macro { color: #94BFF3; }
+.derive { color: #94BFF3; font-style: italic; }
+.module { color: #AFD8AF; }
+.value_param { color: #DCDCCC; }
+.variable { color: #DCDCCC; }
+.format_specifier { color: #CC696B; }
+.mutable { text-decoration: underline; }
+.escape_sequence { color: #94BFF3; }
+.keyword { color: #F0DFAF; font-weight: bold; }
+.control { font-style: italic; }
+.reference { font-style: italic; font-weight: bold; }
+
+.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+</style>
+<pre><code><span class="comment documentation">//! This is a module to test doc injection.</span>
+<span class="comment documentation">//! ```</span>
+<span class="comment documentation">//!</span><span class="comment documentation"> </span><span class="keyword injected">fn</span><span class="none injected"> </span><span class="function declaration injected">test</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="none injected"> </span><span class="brace injected">{</span><span class="brace injected">}</span>
+<span class="comment documentation">//! ```</span>
+
+<span class="keyword">mod</span> <span class="module declaration">outline_module</span><span class="semicolon">;</span>
+
+<span class="comment documentation">/// ```</span>
+<span class="comment documentation">///</span><span class="comment documentation"> </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="punctuation injected">_</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="string_literal injected">"early doctests should not go boom"</span><span class="semicolon injected">;</span>
+<span class="comment documentation">/// ```</span>
+<span class="keyword">struct</span> <span class="struct declaration">Foo</span> <span class="brace">{</span>
+ <span class="field declaration">bar</span><span class="colon">:</span> <span class="builtin_type">bool</span><span class="comma">,</span>
+<span class="brace">}</span>
+
+<span class="comment documentation">/// This is an impl of </span><span class="struct documentation injected intra_doc_link">[`Foo`]</span><span class="comment documentation"> with a code block.</span>
+<span class="comment documentation">///</span>
+<span class="comment documentation">/// ```</span>
+<span class="comment documentation">///</span><span class="comment documentation"> </span><span class="keyword injected">fn</span><span class="none injected"> </span><span class="function declaration injected">foo</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="none injected"> </span><span class="brace injected">{</span>
+<span class="comment documentation">///</span>
+<span class="comment documentation">///</span><span class="comment documentation"> </span><span class="brace injected">}</span>
+<span class="comment documentation">/// ```</span>
+<span class="keyword">impl</span> <span class="struct">Foo</span> <span class="brace">{</span>
+ <span class="comment documentation">/// ```</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="punctuation injected">_</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="string_literal injected">"Call me</span>
+ <span class="comment">// KILLER WHALE</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="string_literal injected"> Ishmael."</span><span class="semicolon injected">;</span>
+ <span class="comment documentation">/// ```</span>
+ <span class="keyword">pub</span> <span class="keyword">const</span> <span class="constant associated declaration public">bar</span><span class="colon">:</span> <span class="builtin_type">bool</span> <span class="operator">=</span> <span class="bool_literal">true</span><span class="semicolon">;</span>
+
+ <span class="comment documentation">/// Constructs a new `Foo`.</span>
+ <span class="comment documentation">///</span>
+ <span class="comment documentation">/// # Examples</span>
+ <span class="comment documentation">///</span>
+ <span class="comment documentation">/// ```</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> #</span><span class="none injected"> </span><span class="attribute_bracket attribute injected">#</span><span class="attribute_bracket attribute injected">!</span><span class="attribute_bracket attribute injected">[</span><span class="builtin_attr attribute injected library">allow</span><span class="parenthesis attribute injected">(</span><span class="none attribute injected">unused_mut</span><span class="parenthesis attribute injected">)</span><span class="attribute_bracket attribute injected">]</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="keyword injected">mut</span><span class="none injected"> </span><span class="variable declaration injected mutable">foo</span><span class="colon injected">:</span><span class="none injected"> </span><span class="struct injected">Foo</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span>
+ <span class="comment documentation">/// ```</span>
+ <span class="keyword">pub</span> <span class="keyword">const</span> <span class="keyword">fn</span> <span class="function associated declaration public static">new</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">-></span> <span class="struct">Foo</span> <span class="brace">{</span>
+ <span class="struct">Foo</span> <span class="brace">{</span> <span class="field">bar</span><span class="colon">:</span> <span class="bool_literal">true</span> <span class="brace">}</span>
+ <span class="brace">}</span>
+
+ <span class="comment documentation">/// `bar` method on `Foo`.</span>
+ <span class="comment documentation">///</span>
+ <span class="comment documentation">/// # Examples</span>
+ <span class="comment documentation">///</span>
+ <span class="comment documentation">/// ```</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="keyword injected">use</span><span class="none injected"> </span><span class="module injected">x</span><span class="operator injected">::</span><span class="module injected">y</span><span class="semicolon injected">;</span>
+ <span class="comment documentation">///</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="variable declaration injected">foo</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span>
+ <span class="comment documentation">///</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="comment injected">// calls bar on foo</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="macro injected">assert</span><span class="macro_bang injected">!</span><span class="parenthesis injected">(</span><span class="none injected">foo</span><span class="operator injected">.</span><span class="none injected">bar</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span>
+ <span class="comment documentation">///</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="variable declaration injected">bar</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="variable injected">foo</span><span class="operator injected">.</span><span class="field injected">bar</span><span class="none injected"> </span><span class="logical injected">||</span><span class="none injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="constant injected">bar</span><span class="semicolon injected">;</span>
+ <span class="comment documentation">///</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="comment injected">/* multi-line</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="comment injected"> comment */</span>
+ <span class="comment documentation">///</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="variable declaration injected reference">multi_line_string</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="string_literal injected">"Foo</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="string_literal injected"> bar</span><span class="escape_sequence injected">\n</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="string_literal injected"> "</span><span class="semicolon injected">;</span>
+ <span class="comment documentation">///</span>
+ <span class="comment documentation">/// ```</span>
+ <span class="comment documentation">///</span>
+ <span class="comment documentation">/// ```rust,no_run</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="variable declaration injected">foobar</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="operator injected">.</span><span class="function injected">bar</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span>
+ <span class="comment documentation">/// ```</span>
+ <span class="comment documentation">///</span>
+ <span class="comment documentation">/// ~~~rust,no_run</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="comment injected">// code block with tilde.</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="variable declaration injected">foobar</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="operator injected">.</span><span class="function injected">bar</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span>
+ <span class="comment documentation">/// ~~~</span>
+ <span class="comment documentation">///</span>
+ <span class="comment documentation">/// ```</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="comment injected">// functions</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="keyword injected">fn</span><span class="none injected"> </span><span class="function declaration injected">foo</span><span class="angle injected"><</span><span class="type_param declaration injected">T</span><span class="comma injected">,</span><span class="none injected"> </span><span class="keyword injected">const</span><span class="none injected"> </span><span class="const_param declaration injected">X</span><span class="colon injected">:</span><span class="none injected"> </span><span class="builtin_type injected">usize</span><span class="angle injected">></span><span class="parenthesis injected">(</span><span class="value_param declaration injected">arg</span><span class="colon injected">:</span><span class="none injected"> </span><span class="builtin_type injected">i32</span><span class="parenthesis injected">)</span><span class="none injected"> </span><span class="brace injected">{</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="none injected"> </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="variable declaration injected">x</span><span class="colon injected">:</span><span class="none injected"> </span><span class="type_param injected">T</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="const_param injected">X</span><span class="semicolon injected">;</span>
+ <span class="comment documentation">///</span><span class="comment documentation"> </span><span class="brace injected">}</span>
+ <span class="comment documentation">/// ```</span>
+ <span class="comment documentation">///</span>
+ <span class="comment documentation">/// ```sh</span>
+ <span class="comment documentation">/// echo 1</span>
+ <span class="comment documentation">/// ```</span>
++ <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function associated declaration public reference">foo</span><span class="parenthesis">(</span><span class="punctuation">&</span><span class="self_keyword declaration reference">self</span><span class="parenthesis">)</span> <span class="operator">-></span> <span class="builtin_type">bool</span> <span class="brace">{</span>
+ <span class="bool_literal">true</span>
+ <span class="brace">}</span>
+<span class="brace">}</span>
+
+<span class="comment documentation">/// </span><span class="struct documentation injected intra_doc_link">[`Foo`](Foo)</span><span class="comment documentation"> is a struct</span>
+<span class="comment documentation">/// This function is > </span><span class="function documentation injected intra_doc_link">[`all_the_links`](all_the_links)</span><span class="comment documentation"> <</span>
+<span class="comment documentation">/// </span><span class="macro documentation injected intra_doc_link">[`noop`](noop)</span><span class="comment documentation"> is a macro below</span>
+<span class="comment documentation">/// </span><span class="struct documentation injected intra_doc_link">[`Item`]</span><span class="comment documentation"> is a struct in the module </span><span class="module documentation injected intra_doc_link">[`module`]</span>
+<span class="comment documentation">///</span>
+<span class="comment documentation">/// [`Item`]: module::Item</span>
+<span class="comment documentation">/// [mix_and_match]: ThisShouldntResolve</span>
+<span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration public">all_the_links</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
+
+<span class="keyword">pub</span> <span class="keyword">mod</span> <span class="module declaration public">module</span> <span class="brace">{</span>
+ <span class="keyword">pub</span> <span class="keyword">struct</span> <span class="struct declaration public">Item</span><span class="semicolon">;</span>
+<span class="brace">}</span>
+
+<span class="comment documentation">/// ```</span>
+<span class="comment documentation">///</span><span class="comment documentation"> </span><span class="keyword injected">macro_rules</span><span class="macro_bang injected">!</span><span class="none injected"> </span><span class="macro declaration injected">noop</span><span class="none injected"> </span><span class="brace injected">{</span><span class="none injected"> </span><span class="parenthesis injected">(</span><span class="punctuation injected">$</span><span class="none injected">expr</span><span class="colon injected">:</span><span class="none injected">expr</span><span class="parenthesis injected">)</span><span class="none injected"> </span><span class="operator injected">=</span><span class="angle injected">></span><span class="none injected"> </span><span class="brace injected">{</span><span class="none injected"> </span><span class="punctuation injected">$</span><span class="none injected">expr </span><span class="brace injected">}</span><span class="brace injected">}</span>
+<span class="comment documentation">///</span><span class="comment documentation"> </span><span class="macro injected">noop</span><span class="macro_bang injected">!</span><span class="parenthesis injected">(</span><span class="numeric_literal injected">1</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span>
+<span class="comment documentation">/// ```</span>
+<span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">noop</span> <span class="brace">{</span>
+ <span class="parenthesis">(</span><span class="punctuation">$</span>expr<span class="colon">:</span>expr<span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="brace">{</span>
+ <span class="punctuation">$</span>expr
+ <span class="brace">}</span>
+<span class="brace">}</span>
+
+<span class="comment documentation">/// ```rust</span>
+<span class="comment documentation">///</span><span class="comment documentation"> </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="punctuation injected">_</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="function injected">example</span><span class="parenthesis injected">(</span><span class="operator injected">&</span><span class="bracket injected">[</span><span class="numeric_literal injected">1</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">2</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">3</span><span class="bracket injected">]</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span>
+<span class="comment documentation">/// ```</span>
+<span class="comment documentation">///</span>
+<span class="comment documentation">/// ```</span>
+<span class="comment documentation">///</span><span class="comment documentation"> </span><span class="keyword control injected">loop</span><span class="none injected"> </span><span class="brace injected">{</span><span class="brace injected">}</span>
+<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">cfg_attr</span><span class="parenthesis attribute">(</span><span class="none attribute">not</span><span class="parenthesis attribute">(</span><span class="none attribute">feature</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"false"</span><span class="parenthesis attribute">)</span><span class="comma attribute">,</span> <span class="none attribute">doc</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"</span><span class="keyword control injected">loop</span><span class="none injected"> </span><span class="brace injected">{</span><span class="brace injected">}</span><span class="string_literal attribute">"</span><span class="parenthesis attribute">)</span><span class="attribute_bracket attribute">]</span>
+<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">doc</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"</span><span class="keyword control injected">loop</span><span class="none injected"> </span><span class="brace injected">{</span><span class="brace injected">}</span><span class="string_literal attribute">"</span><span class="attribute_bracket attribute">]</span>
+<span class="comment documentation">/// ```</span>
+<span class="comment documentation">///</span>
+<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">cfg_attr</span><span class="parenthesis attribute">(</span><span class="none attribute">feature</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"alloc"</span><span class="comma attribute">,</span> <span class="none attribute">doc</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"```rust"</span><span class="parenthesis attribute">)</span><span class="attribute_bracket attribute">]</span>
+<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">cfg_attr</span><span class="parenthesis attribute">(</span><span class="none attribute">not</span><span class="parenthesis attribute">(</span><span class="none attribute">feature</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"alloc"</span><span class="parenthesis attribute">)</span><span class="comma attribute">,</span> <span class="none attribute">doc</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"```ignore"</span><span class="parenthesis attribute">)</span><span class="attribute_bracket attribute">]</span>
+<span class="comment documentation">///</span><span class="comment documentation"> </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="punctuation injected">_</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="function injected">example</span><span class="parenthesis injected">(</span><span class="operator injected">&</span><span class="module injected">alloc</span><span class="operator injected">::</span><span class="macro injected">vec</span><span class="macro_bang injected">!</span><span class="bracket injected">[</span><span class="numeric_literal injected">1</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">2</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">3</span><span class="bracket injected">]</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span>
+<span class="comment documentation">/// ```</span>
+<span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration public">mix_and_match</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
+
+<span class="comment documentation">/**
+It is beyond me why you'd use these when you got ///
+```rust
+</span><span class="keyword injected">let</span><span class="none injected"> </span><span class="punctuation injected">_</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="function injected">example</span><span class="parenthesis injected">(</span><span class="operator injected">&</span><span class="bracket injected">[</span><span class="numeric_literal injected">1</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">2</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">3</span><span class="bracket injected">]</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span><span class="comment documentation">
+```
+</span><span class="function documentation injected intra_doc_link">[`block_comments2`]</span><span class="comment documentation"> tests these with indentation
+ */</span>
+<span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration public">block_comments</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
+
+<span class="comment documentation">/**
+ Really, I don't get it
+ ```rust
+</span><span class="comment documentation"> </span><span class="none injected"> </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="punctuation injected">_</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="function injected">example</span><span class="parenthesis injected">(</span><span class="operator injected">&</span><span class="bracket injected">[</span><span class="numeric_literal injected">1</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">2</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">3</span><span class="bracket injected">]</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span><span class="comment documentation">
+ ```
+ </span><span class="function documentation injected intra_doc_link">[`block_comments`]</span><span class="comment documentation"> tests these without indentation
+*/</span>
+<span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration public">block_comments2</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
+
+</code></pre>
--- /dev/null
- <span class="keyword">fn</span> <span class="function associated declaration reference trait">bar</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration reference">self</span><span class="parenthesis">)</span> <span class="operator">-></span> <span class="builtin_type">i32</span><span class="semicolon">;</span>
+
+<style>
+body { margin: 0; }
+pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
+
+.lifetime { color: #DFAF8F; font-style: italic; }
+.label { color: #DFAF8F; font-style: italic; }
+.comment { color: #7F9F7F; }
+.documentation { color: #629755; }
+.intra_doc_link { font-style: italic; }
+.injected { opacity: 0.65 ; }
+.struct, .enum { color: #7CB8BB; }
+.enum_variant { color: #BDE0F3; }
+.string_literal { color: #CC9393; }
+.field { color: #94BFF3; }
+.function { color: #93E0E3; }
+.function.unsafe { color: #BC8383; }
+.trait.unsafe { color: #BC8383; }
+.operator.unsafe { color: #BC8383; }
+.mutable.unsafe { color: #BC8383; text-decoration: underline; }
+.keyword.unsafe { color: #BC8383; font-weight: bold; }
+.macro.unsafe { color: #BC8383; }
+.parameter { color: #94BFF3; }
+.text { color: #DCDCCC; }
+.type { color: #7CB8BB; }
+.builtin_type { color: #8CD0D3; }
+.type_param { color: #DFAF8F; }
+.attribute { color: #94BFF3; }
+.numeric_literal { color: #BFEBBF; }
+.bool_literal { color: #BFE6EB; }
+.macro { color: #94BFF3; }
+.derive { color: #94BFF3; font-style: italic; }
+.module { color: #AFD8AF; }
+.value_param { color: #DCDCCC; }
+.variable { color: #DCDCCC; }
+.format_specifier { color: #CC696B; }
+.mutable { text-decoration: underline; }
+.escape_sequence { color: #94BFF3; }
+.keyword { color: #F0DFAF; font-weight: bold; }
+.control { font-style: italic; }
+.reference { font-style: italic; font-weight: bold; }
+
+.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+</style>
+<pre><code><span class="keyword">use</span> <span class="module">inner</span><span class="operator">::</span><span class="brace">{</span><span class="self_keyword">self</span> <span class="keyword">as</span> <span class="module declaration">inner_mod</span><span class="brace">}</span><span class="semicolon">;</span>
+<span class="keyword">mod</span> <span class="module declaration">inner</span> <span class="brace">{</span><span class="brace">}</span>
+
+<span class="keyword">pub</span> <span class="keyword">mod</span> <span class="module declaration public">ops</span> <span class="brace">{</span>
+ <span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">lang</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"fn_once"</span><span class="attribute_bracket attribute">]</span>
+ <span class="keyword">pub</span> <span class="keyword">trait</span> <span class="trait declaration public">FnOnce</span><span class="angle"><</span><span class="type_param declaration">Args</span><span class="angle">></span> <span class="brace">{</span><span class="brace">}</span>
+
+ <span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">lang</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"fn_mut"</span><span class="attribute_bracket attribute">]</span>
+ <span class="keyword">pub</span> <span class="keyword">trait</span> <span class="trait declaration public">FnMut</span><span class="angle"><</span><span class="type_param declaration">Args</span><span class="angle">></span><span class="colon">:</span> <span class="trait public">FnOnce</span><span class="angle"><</span><span class="type_param">Args</span><span class="angle">></span> <span class="brace">{</span><span class="brace">}</span>
+
+ <span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">lang</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"fn"</span><span class="attribute_bracket attribute">]</span>
+ <span class="keyword">pub</span> <span class="keyword">trait</span> <span class="trait declaration public">Fn</span><span class="angle"><</span><span class="type_param declaration">Args</span><span class="angle">></span><span class="colon">:</span> <span class="trait public">FnMut</span><span class="angle"><</span><span class="type_param">Args</span><span class="angle">></span> <span class="brace">{</span><span class="brace">}</span>
+<span class="brace">}</span>
+
+<span class="keyword">struct</span> <span class="struct declaration">Foo</span> <span class="brace">{</span>
+ <span class="field declaration">x</span><span class="colon">:</span> <span class="builtin_type">u32</span><span class="comma">,</span>
+<span class="brace">}</span>
+
+<span class="keyword">trait</span> <span class="trait declaration">Bar</span> <span class="brace">{</span>
- <span class="keyword">fn</span> <span class="function associated declaration reference trait">bar</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration reference">self</span><span class="parenthesis">)</span> <span class="operator">-></span> <span class="builtin_type">i32</span> <span class="brace">{</span>
++ <span class="keyword">fn</span> <span class="function associated declaration reference trait">bar</span><span class="parenthesis">(</span><span class="punctuation">&</span><span class="self_keyword declaration reference">self</span><span class="parenthesis">)</span> <span class="operator">-></span> <span class="builtin_type">i32</span><span class="semicolon">;</span>
+<span class="brace">}</span>
+
+<span class="keyword">impl</span> <span class="trait">Bar</span> <span class="keyword">for</span> <span class="struct">Foo</span> <span class="brace">{</span>
- <span class="keyword">fn</span> <span class="function associated declaration mutable reference">qux</span><span class="parenthesis">(</span><span class="operator">&</span><span class="keyword">mut</span> <span class="self_keyword declaration mutable reference">self</span><span class="parenthesis">)</span> <span class="brace">{</span>
++ <span class="keyword">fn</span> <span class="function associated declaration reference trait">bar</span><span class="parenthesis">(</span><span class="punctuation">&</span><span class="self_keyword declaration reference">self</span><span class="parenthesis">)</span> <span class="operator">-></span> <span class="builtin_type">i32</span> <span class="brace">{</span>
+ <span class="self_keyword reference">self</span><span class="operator">.</span><span class="field">x</span>
+ <span class="brace">}</span>
+<span class="brace">}</span>
+
+<span class="keyword">impl</span> <span class="struct">Foo</span> <span class="brace">{</span>
+ <span class="keyword">fn</span> <span class="function associated consuming declaration">baz</span><span class="parenthesis">(</span><span class="keyword">mut</span> <span class="self_keyword declaration mutable">self</span><span class="comma">,</span> <span class="value_param declaration">f</span><span class="colon">:</span> <span class="struct">Foo</span><span class="parenthesis">)</span> <span class="operator">-></span> <span class="builtin_type">i32</span> <span class="brace">{</span>
+ <span class="value_param">f</span><span class="operator">.</span><span class="function associated consuming">baz</span><span class="parenthesis">(</span><span class="self_keyword consuming mutable">self</span><span class="parenthesis">)</span>
+ <span class="brace">}</span>
+
- <span class="keyword">fn</span> <span class="function associated declaration reference">quop</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration reference">self</span><span class="parenthesis">)</span> <span class="operator">-></span> <span class="builtin_type">i32</span> <span class="brace">{</span>
++ <span class="keyword">fn</span> <span class="function associated declaration mutable reference">qux</span><span class="parenthesis">(</span><span class="punctuation">&</span><span class="keyword">mut</span> <span class="self_keyword declaration mutable reference">self</span><span class="parenthesis">)</span> <span class="brace">{</span>
+ <span class="self_keyword mutable reference">self</span><span class="operator">.</span><span class="field">x</span> <span class="operator">=</span> <span class="numeric_literal">0</span><span class="semicolon">;</span>
+ <span class="brace">}</span>
+
- <span class="keyword">fn</span> <span class="function associated declaration mutable reference">qux</span><span class="parenthesis">(</span><span class="operator">&</span><span class="keyword">mut</span> <span class="self_keyword declaration mutable reference">self</span><span class="parenthesis">)</span> <span class="brace">{</span>
++ <span class="keyword">fn</span> <span class="function associated declaration reference">quop</span><span class="parenthesis">(</span><span class="punctuation">&</span><span class="self_keyword declaration reference">self</span><span class="parenthesis">)</span> <span class="operator">-></span> <span class="builtin_type">i32</span> <span class="brace">{</span>
+ <span class="self_keyword reference">self</span><span class="operator">.</span><span class="field">x</span>
+ <span class="brace">}</span>
+<span class="brace">}</span>
+
+<span class="keyword">use</span> <span class="self_keyword crate_root public">self</span><span class="operator">::</span><span class="struct">FooCopy</span><span class="operator">::</span><span class="brace">{</span><span class="self_keyword">self</span> <span class="keyword">as</span> <span class="struct declaration">BarCopy</span><span class="brace">}</span><span class="semicolon">;</span>
+
+<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="attribute attribute default_library library">derive</span><span class="parenthesis attribute">(</span><span class="derive attribute default_library library">Copy</span><span class="parenthesis attribute">)</span><span class="attribute_bracket attribute">]</span>
+<span class="keyword">struct</span> <span class="struct declaration">FooCopy</span> <span class="brace">{</span>
+ <span class="field declaration">x</span><span class="colon">:</span> <span class="builtin_type">u32</span><span class="comma">,</span>
+<span class="brace">}</span>
+
+<span class="keyword">impl</span> <span class="struct">FooCopy</span> <span class="brace">{</span>
+ <span class="keyword">fn</span> <span class="function associated consuming declaration">baz</span><span class="parenthesis">(</span><span class="self_keyword declaration">self</span><span class="comma">,</span> <span class="value_param declaration">f</span><span class="colon">:</span> <span class="struct">FooCopy</span><span class="parenthesis">)</span> <span class="operator">-></span> <span class="builtin_type">u32</span> <span class="brace">{</span>
+ <span class="value_param">f</span><span class="operator">.</span><span class="function associated">baz</span><span class="parenthesis">(</span><span class="self_keyword">self</span><span class="parenthesis">)</span>
+ <span class="brace">}</span>
+
- <span class="keyword">fn</span> <span class="function associated declaration reference">quop</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration reference">self</span><span class="parenthesis">)</span> <span class="operator">-></span> <span class="builtin_type">u32</span> <span class="brace">{</span>
++ <span class="keyword">fn</span> <span class="function associated declaration mutable reference">qux</span><span class="parenthesis">(</span><span class="punctuation">&</span><span class="keyword">mut</span> <span class="self_keyword declaration mutable reference">self</span><span class="parenthesis">)</span> <span class="brace">{</span>
+ <span class="self_keyword mutable reference">self</span><span class="operator">.</span><span class="field">x</span> <span class="operator">=</span> <span class="numeric_literal">0</span><span class="semicolon">;</span>
+ <span class="brace">}</span>
+
++ <span class="keyword">fn</span> <span class="function associated declaration reference">quop</span><span class="parenthesis">(</span><span class="punctuation">&</span><span class="self_keyword declaration reference">self</span><span class="parenthesis">)</span> <span class="operator">-></span> <span class="builtin_type">u32</span> <span class="brace">{</span>
+ <span class="self_keyword reference">self</span><span class="operator">.</span><span class="field">x</span>
+ <span class="brace">}</span>
+<span class="brace">}</span>
+
+<span class="keyword">fn</span> <span class="function declaration">str</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
+ <span class="function">str</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+<span class="brace">}</span>
+
+<span class="keyword">fn</span> <span class="function declaration">foo</span><span class="angle"><</span><span class="lifetime declaration">'a</span><span class="comma">,</span> <span class="type_param declaration">T</span><span class="angle">></span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">-></span> <span class="type_param">T</span> <span class="brace">{</span>
+ <span class="function">foo</span><span class="operator">::</span><span class="angle"><</span><span class="lifetime">'a</span><span class="comma">,</span> <span class="builtin_type">i32</span><span class="angle">></span><span class="parenthesis">(</span><span class="parenthesis">)</span>
+<span class="brace">}</span>
+
+<span class="keyword">fn</span> <span class="function declaration">never</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">-></span> <span class="builtin_type">!</span> <span class="brace">{</span>
+ <span class="keyword control">loop</span> <span class="brace">{</span><span class="brace">}</span>
+<span class="brace">}</span>
+
+<span class="keyword">fn</span> <span class="function declaration">const_param</span><span class="angle"><</span><span class="keyword">const</span> <span class="const_param declaration">FOO</span><span class="colon">:</span> <span class="builtin_type">usize</span><span class="angle">></span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">-></span> <span class="builtin_type">usize</span> <span class="brace">{</span>
+ <span class="function">const_param</span><span class="operator">::</span><span class="angle"><</span><span class="brace">{</span> <span class="const_param">FOO</span> <span class="brace">}</span><span class="angle">></span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="const_param">FOO</span>
+<span class="brace">}</span>
+
+<span class="keyword">use</span> <span class="module public">ops</span><span class="operator">::</span><span class="trait public">Fn</span><span class="semicolon">;</span>
+<span class="keyword">fn</span> <span class="function declaration">baz</span><span class="angle"><</span><span class="type_param declaration">F</span><span class="colon">:</span> <span class="trait public">Fn</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">-></span> <span class="parenthesis">(</span><span class="parenthesis">)</span><span class="angle">></span><span class="parenthesis">(</span><span class="value_param callable declaration">f</span><span class="colon">:</span> <span class="type_param">F</span><span class="parenthesis">)</span> <span class="brace">{</span>
+ <span class="value_param callable">f</span><span class="parenthesis">(</span><span class="parenthesis">)</span>
+<span class="brace">}</span>
+
+<span class="keyword">fn</span> <span class="function declaration">foobar</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">-></span> <span class="keyword">impl</span> <span class="trait default_library library">Copy</span> <span class="brace">{</span><span class="brace">}</span>
+
+<span class="keyword">fn</span> <span class="function declaration">foo</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
+ <span class="keyword">let</span> <span class="variable declaration">bar</span> <span class="operator">=</span> <span class="function">foobar</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+<span class="brace">}</span>
+
+<span class="comment">// comment</span>
+<span class="keyword">fn</span> <span class="function declaration">main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
+ <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">x</span> <span class="operator">=</span> <span class="numeric_literal">42</span><span class="semicolon">;</span>
+ <span class="variable mutable">x</span> <span class="arithmetic mutable">+=</span> <span class="numeric_literal">1</span><span class="semicolon">;</span>
+ <span class="keyword">let</span> <span class="variable declaration mutable reference">y</span> <span class="operator">=</span> <span class="operator">&</span><span class="keyword">mut</span> <span class="variable mutable">x</span><span class="semicolon">;</span>
+ <span class="keyword">let</span> <span class="variable declaration reference">z</span> <span class="operator">=</span> <span class="operator">&</span><span class="variable mutable reference">y</span><span class="semicolon">;</span>
+
+ <span class="keyword">let</span> <span class="struct">Foo</span> <span class="brace">{</span> <span class="field">x</span><span class="colon">:</span> <span class="variable declaration">z</span><span class="comma">,</span> <span class="variable declaration">y</span> <span class="brace">}</span> <span class="operator">=</span> <span class="struct">Foo</span> <span class="brace">{</span> <span class="field">x</span><span class="colon">:</span> <span class="variable reference">z</span><span class="comma">,</span> <span class="variable mutable reference">y</span> <span class="brace">}</span><span class="semicolon">;</span>
+
+ <span class="variable">y</span><span class="semicolon">;</span>
+
+ <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">foo</span> <span class="operator">=</span> <span class="struct">Foo</span> <span class="brace">{</span> <span class="field">x</span><span class="comma">,</span> <span class="unresolved_reference">y</span><span class="colon">:</span> <span class="variable mutable">x</span> <span class="brace">}</span><span class="semicolon">;</span>
+ <span class="keyword">let</span> <span class="variable declaration">foo2</span> <span class="operator">=</span> <span class="struct">Foo</span> <span class="brace">{</span> <span class="field">x</span><span class="comma">,</span> <span class="unresolved_reference">y</span><span class="colon">:</span> <span class="variable mutable">x</span> <span class="brace">}</span><span class="semicolon">;</span>
+ <span class="variable mutable">foo</span><span class="operator">.</span><span class="function associated reference">quop</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="variable mutable">foo</span><span class="operator">.</span><span class="function associated mutable reference">qux</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="variable mutable">foo</span><span class="operator">.</span><span class="function associated consuming">baz</span><span class="parenthesis">(</span><span class="variable consuming">foo2</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+
+ <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">copy</span> <span class="operator">=</span> <span class="struct">FooCopy</span> <span class="brace">{</span> <span class="field">x</span> <span class="brace">}</span><span class="semicolon">;</span>
+ <span class="variable mutable">copy</span><span class="operator">.</span><span class="function associated reference">quop</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="variable mutable">copy</span><span class="operator">.</span><span class="function associated mutable reference">qux</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="variable mutable">copy</span><span class="operator">.</span><span class="function associated">baz</span><span class="parenthesis">(</span><span class="variable mutable">copy</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+
+ <span class="keyword">let</span> <span class="variable callable declaration">a</span> <span class="operator">=</span> <span class="punctuation">|</span><span class="value_param declaration">x</span><span class="punctuation">|</span> <span class="value_param">x</span><span class="semicolon">;</span>
+ <span class="keyword">let</span> <span class="variable callable declaration">bar</span> <span class="operator">=</span> <span class="struct">Foo</span><span class="operator">::</span><span class="function associated consuming">baz</span><span class="semicolon">;</span>
+
+ <span class="keyword">let</span> <span class="variable declaration">baz</span> <span class="operator">=</span> <span class="parenthesis">(</span><span class="numeric_literal">-</span><span class="numeric_literal">42</span><span class="comma">,</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="keyword">let</span> <span class="variable declaration">baz</span> <span class="operator">=</span> <span class="operator">-</span><span class="variable">baz</span><span class="operator">.</span><span class="field">0</span><span class="semicolon">;</span>
+
+ <span class="keyword">let</span> <span class="punctuation">_</span> <span class="operator">=</span> <span class="logical">!</span><span class="bool_literal">true</span><span class="semicolon">;</span>
+
+ <span class="label declaration">'foo</span><span class="colon">:</span> <span class="keyword control">loop</span> <span class="brace">{</span>
+ <span class="keyword control">break</span> <span class="label">'foo</span><span class="semicolon">;</span>
+ <span class="keyword control">continue</span> <span class="label">'foo</span><span class="semicolon">;</span>
+ <span class="brace">}</span>
+<span class="brace">}</span>
+
+<span class="keyword">enum</span> <span class="enum declaration">Option</span><span class="angle"><</span><span class="type_param declaration">T</span><span class="angle">></span> <span class="brace">{</span>
+ <span class="enum_variant declaration">Some</span><span class="parenthesis">(</span><span class="type_param">T</span><span class="parenthesis">)</span><span class="comma">,</span>
+ <span class="enum_variant declaration">None</span><span class="comma">,</span>
+<span class="brace">}</span>
+<span class="keyword">use</span> <span class="enum">Option</span><span class="operator">::</span><span class="punctuation">*</span><span class="semicolon">;</span>
+
+<span class="keyword">impl</span><span class="angle"><</span><span class="type_param declaration">T</span><span class="angle">></span> <span class="enum">Option</span><span class="angle"><</span><span class="type_param">T</span><span class="angle">></span> <span class="brace">{</span>
+ <span class="keyword">fn</span> <span class="function associated consuming declaration">and</span><span class="angle"><</span><span class="type_param declaration">U</span><span class="angle">></span><span class="parenthesis">(</span><span class="self_keyword declaration">self</span><span class="comma">,</span> <span class="value_param declaration">other</span><span class="colon">:</span> <span class="enum">Option</span><span class="angle"><</span><span class="type_param">U</span><span class="angle">></span><span class="parenthesis">)</span> <span class="operator">-></span> <span class="enum">Option</span><span class="angle"><</span><span class="parenthesis">(</span><span class="type_param">T</span><span class="comma">,</span> <span class="type_param">U</span><span class="parenthesis">)</span><span class="angle">></span> <span class="brace">{</span>
+ <span class="keyword control">match</span> <span class="value_param">other</span> <span class="brace">{</span>
+ <span class="enum_variant">None</span> <span class="operator">=></span> <span class="unresolved_reference">unimplemented</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="comma">,</span>
+ <span class="variable declaration">Nope</span> <span class="operator">=></span> <span class="variable">Nope</span><span class="comma">,</span>
+ <span class="brace">}</span>
+ <span class="brace">}</span>
+<span class="brace">}</span>
+
+<span class="keyword async">async</span> <span class="keyword">fn</span> <span class="function async declaration">learn_and_sing</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
+ <span class="keyword">let</span> <span class="variable declaration">song</span> <span class="operator">=</span> <span class="unresolved_reference">learn_song</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="operator">.</span><span class="keyword async control">await</span><span class="semicolon">;</span>
+ <span class="unresolved_reference">sing_song</span><span class="parenthesis">(</span><span class="variable consuming">song</span><span class="parenthesis">)</span><span class="operator">.</span><span class="keyword async control">await</span><span class="semicolon">;</span>
+<span class="brace">}</span>
+
+<span class="keyword async">async</span> <span class="keyword">fn</span> <span class="function async declaration">async_main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
+ <span class="keyword">let</span> <span class="variable declaration">f1</span> <span class="operator">=</span> <span class="function async">learn_and_sing</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="keyword">let</span> <span class="variable declaration">f2</span> <span class="operator">=</span> <span class="unresolved_reference">dance</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="unresolved_reference">futures</span><span class="operator">::</span><span class="unresolved_reference">join</span><span class="macro_bang">!</span><span class="parenthesis">(</span>f1<span class="comma">,</span> f2<span class="parenthesis">)</span><span class="semicolon">;</span>
+<span class="brace">}</span>
+
+<span class="keyword">fn</span> <span class="function declaration">use_foo_items</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
+ <span class="keyword">let</span> <span class="variable declaration">bob</span> <span class="operator">=</span> <span class="module crate_root library">foo</span><span class="operator">::</span><span class="struct library">Person</span> <span class="brace">{</span>
+ <span class="field library">name</span><span class="colon">:</span> <span class="string_literal">"Bob"</span><span class="comma">,</span>
+ <span class="field library">age</span><span class="colon">:</span> <span class="module crate_root library">foo</span><span class="operator">::</span><span class="module library">consts</span><span class="operator">::</span><span class="constant library">NUMBER</span><span class="comma">,</span>
+ <span class="brace">}</span><span class="semicolon">;</span>
+
+ <span class="keyword">let</span> <span class="variable declaration">control_flow</span> <span class="operator">=</span> <span class="module crate_root library">foo</span><span class="operator">::</span><span class="function library">identity</span><span class="parenthesis">(</span><span class="module crate_root library">foo</span><span class="operator">::</span><span class="enum library">ControlFlow</span><span class="operator">::</span><span class="enum_variant library">Continue</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+
+ <span class="keyword control">if</span> <span class="variable">control_flow</span><span class="operator">.</span><span class="function associated consuming library">should_die</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
+ <span class="module crate_root library">foo</span><span class="operator">::</span><span class="unresolved_reference">die</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="brace">}</span>
+<span class="brace">}</span>
+
+<span class="keyword">pub</span> <span class="keyword">enum</span> <span class="enum declaration public">Bool</span> <span class="brace">{</span> <span class="enum_variant declaration public">True</span><span class="comma">,</span> <span class="enum_variant declaration public">False</span> <span class="brace">}</span>
+
+<span class="keyword">impl</span> <span class="enum public">Bool</span> <span class="brace">{</span>
+ <span class="keyword">pub</span> <span class="keyword">const</span> <span class="keyword">fn</span> <span class="function associated consuming declaration public">to_primitive</span><span class="parenthesis">(</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="operator">-></span> <span class="builtin_type">bool</span> <span class="brace">{</span>
+ <span class="bool_literal">true</span>
+ <span class="brace">}</span>
+<span class="brace">}</span>
+<span class="keyword">const</span> <span class="constant declaration">USAGE_OF_BOOL</span><span class="colon">:</span><span class="builtin_type">bool</span> <span class="operator">=</span> <span class="enum public">Bool</span><span class="operator">::</span><span class="enum_variant public">True</span><span class="operator">.</span><span class="function associated consuming public">to_primitive</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+
+<span class="keyword">trait</span> <span class="trait declaration">Baz</span> <span class="brace">{</span>
+ <span class="keyword">type</span> <span class="type_alias associated declaration trait">Qux</span><span class="semicolon">;</span>
+<span class="brace">}</span>
+
+<span class="keyword">fn</span> <span class="function declaration">baz</span><span class="angle"><</span><span class="type_param declaration">T</span><span class="angle">></span><span class="parenthesis">(</span><span class="value_param declaration">t</span><span class="colon">:</span> <span class="type_param">T</span><span class="parenthesis">)</span>
+<span class="keyword">where</span>
+ <span class="type_param">T</span><span class="colon">:</span> <span class="trait">Baz</span><span class="comma">,</span>
+ <span class="angle"><</span><span class="type_param">T</span> <span class="keyword">as</span> <span class="trait">Baz</span><span class="angle">></span><span class="operator">::</span><span class="type_alias associated trait">Qux</span><span class="colon">:</span> <span class="trait">Bar</span> <span class="brace">{</span><span class="brace">}</span>
+
+<span class="keyword">fn</span> <span class="function declaration">gp_shadows_trait</span><span class="angle"><</span><span class="type_param declaration">Baz</span><span class="colon">:</span> <span class="trait">Bar</span><span class="angle">></span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
+ <span class="type_param">Baz</span><span class="operator">::</span><span class="function associated reference trait">bar</span><span class="semicolon">;</span>
+<span class="brace">}</span>
+
+</code></pre>
--- /dev/null
- <pre><code><span class="keyword">fn</span> <span class="function declaration">fixture</span><span class="parenthesis">(</span><span class="value_param declaration reference">ra_fixture</span><span class="colon">:</span> <span class="operator">&</span><span class="builtin_type">str</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
+
+<style>
+body { margin: 0; }
+pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
+
+.lifetime { color: #DFAF8F; font-style: italic; }
+.label { color: #DFAF8F; font-style: italic; }
+.comment { color: #7F9F7F; }
+.documentation { color: #629755; }
+.intra_doc_link { font-style: italic; }
+.injected { opacity: 0.65 ; }
+.struct, .enum { color: #7CB8BB; }
+.enum_variant { color: #BDE0F3; }
+.string_literal { color: #CC9393; }
+.field { color: #94BFF3; }
+.function { color: #93E0E3; }
+.function.unsafe { color: #BC8383; }
+.trait.unsafe { color: #BC8383; }
+.operator.unsafe { color: #BC8383; }
+.mutable.unsafe { color: #BC8383; text-decoration: underline; }
+.keyword.unsafe { color: #BC8383; font-weight: bold; }
+.macro.unsafe { color: #BC8383; }
+.parameter { color: #94BFF3; }
+.text { color: #DCDCCC; }
+.type { color: #7CB8BB; }
+.builtin_type { color: #8CD0D3; }
+.type_param { color: #DFAF8F; }
+.attribute { color: #94BFF3; }
+.numeric_literal { color: #BFEBBF; }
+.bool_literal { color: #BFE6EB; }
+.macro { color: #94BFF3; }
+.derive { color: #94BFF3; font-style: italic; }
+.module { color: #AFD8AF; }
+.value_param { color: #DCDCCC; }
+.variable { color: #DCDCCC; }
+.format_specifier { color: #CC696B; }
+.mutable { text-decoration: underline; }
+.escape_sequence { color: #94BFF3; }
+.keyword { color: #F0DFAF; font-weight: bold; }
+.control { font-style: italic; }
+.reference { font-style: italic; font-weight: bold; }
+
+.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+</style>
++<pre><code><span class="keyword">fn</span> <span class="function declaration">fixture</span><span class="parenthesis">(</span><span class="value_param declaration reference">ra_fixture</span><span class="colon">:</span> <span class="punctuation">&</span><span class="builtin_type">str</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
+
+<span class="keyword">fn</span> <span class="function declaration">main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
+ <span class="function">fixture</span><span class="parenthesis">(</span><span class="string_literal">r#"</span>
+<span class="keyword">trait</span> <span class="trait declaration">Foo</span> <span class="brace">{</span>
+ <span class="keyword">fn</span> <span class="function associated declaration static trait">foo</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
+ <span class="unresolved_reference">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"2 + 2 = {}"</span><span class="comma">,</span> <span class="numeric_literal">4</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="brace">}</span>
+<span class="brace">}</span><span class="string_literal">"#</span>
+ <span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="function">fixture</span><span class="parenthesis">(</span><span class="string_literal">r"</span>
+<span class="keyword">fn</span> <span class="function declaration">foo</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
+ <span class="function">foo</span><span class="parenthesis">(</span><span class="keyword">$0</span><span class="brace">{</span>
+ <span class="numeric_literal">92</span>
+ <span class="brace">}</span><span class="keyword">$0</span><span class="parenthesis">)</span>
+<span class="brace">}</span><span class="string_literal">"</span>
+ <span class="parenthesis">)</span><span class="semicolon">;</span>
+<span class="brace">}</span></code></pre>
--- /dev/null
- <span class="field declaration">field</span><span class="colon">:</span> <span class="operator">&</span><span class="lifetime">'a</span> <span class="parenthesis">(</span><span class="parenthesis">)</span><span class="comma">,</span>
- <span class="field declaration">field2</span><span class="colon">:</span> <span class="operator">&</span><span class="lifetime">'static</span> <span class="parenthesis">(</span><span class="parenthesis">)</span><span class="comma">,</span>
+
+<style>
+body { margin: 0; }
+pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
+
+.lifetime { color: #DFAF8F; font-style: italic; }
+.label { color: #DFAF8F; font-style: italic; }
+.comment { color: #7F9F7F; }
+.documentation { color: #629755; }
+.intra_doc_link { font-style: italic; }
+.injected { opacity: 0.65 ; }
+.struct, .enum { color: #7CB8BB; }
+.enum_variant { color: #BDE0F3; }
+.string_literal { color: #CC9393; }
+.field { color: #94BFF3; }
+.function { color: #93E0E3; }
+.function.unsafe { color: #BC8383; }
+.trait.unsafe { color: #BC8383; }
+.operator.unsafe { color: #BC8383; }
+.mutable.unsafe { color: #BC8383; text-decoration: underline; }
+.keyword.unsafe { color: #BC8383; font-weight: bold; }
+.macro.unsafe { color: #BC8383; }
+.parameter { color: #94BFF3; }
+.text { color: #DCDCCC; }
+.type { color: #7CB8BB; }
+.builtin_type { color: #8CD0D3; }
+.type_param { color: #DFAF8F; }
+.attribute { color: #94BFF3; }
+.numeric_literal { color: #BFEBBF; }
+.bool_literal { color: #BFE6EB; }
+.macro { color: #94BFF3; }
+.derive { color: #94BFF3; font-style: italic; }
+.module { color: #AFD8AF; }
+.value_param { color: #DCDCCC; }
+.variable { color: #DCDCCC; }
+.format_specifier { color: #CC696B; }
+.mutable { text-decoration: underline; }
+.escape_sequence { color: #94BFF3; }
+.keyword { color: #F0DFAF; font-weight: bold; }
+.control { font-style: italic; }
+.reference { font-style: italic; font-weight: bold; }
+
+.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+</style>
+<pre><code>
+<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="attribute attribute default_library library">derive</span><span class="parenthesis attribute">(</span><span class="parenthesis attribute">)</span><span class="attribute_bracket attribute">]</span>
+<span class="keyword">struct</span> <span class="struct declaration">Foo</span><span class="angle"><</span><span class="lifetime declaration">'a</span><span class="comma">,</span> <span class="lifetime declaration">'b</span><span class="comma">,</span> <span class="lifetime declaration">'c</span><span class="angle">></span> <span class="keyword">where</span> <span class="lifetime">'a</span><span class="colon">:</span> <span class="lifetime">'a</span><span class="comma">,</span> <span class="lifetime">'static</span><span class="colon">:</span> <span class="lifetime">'static</span> <span class="brace">{</span>
++ <span class="field declaration">field</span><span class="colon">:</span> <span class="punctuation">&</span><span class="lifetime">'a</span> <span class="parenthesis">(</span><span class="parenthesis">)</span><span class="comma">,</span>
++ <span class="field declaration">field2</span><span class="colon">:</span> <span class="punctuation">&</span><span class="lifetime">'static</span> <span class="parenthesis">(</span><span class="parenthesis">)</span><span class="comma">,</span>
+<span class="brace">}</span>
+<span class="keyword">impl</span><span class="angle"><</span><span class="lifetime declaration">'a</span><span class="angle">></span> <span class="struct">Foo</span><span class="angle"><</span><span class="lifetime">'_</span><span class="comma">,</span> <span class="lifetime">'a</span><span class="comma">,</span> <span class="lifetime">'static</span><span class="angle">></span>
+<span class="keyword">where</span>
+ <span class="lifetime">'a</span><span class="colon">:</span> <span class="lifetime">'a</span><span class="comma">,</span>
+ <span class="lifetime">'static</span><span class="colon">:</span> <span class="lifetime">'static</span>
+<span class="brace">{</span><span class="brace">}</span></code></pre>
--- /dev/null
- <span class="parenthesis">(</span><span class="punctuation">$</span>msg<span class="colon">:</span>literal <span class="punctuation">$</span><span class="parenthesis">(</span><span class="comma">,</span><span class="parenthesis">)</span><span class="operator control">?</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="parenthesis">(</span>
+
+<style>
+body { margin: 0; }
+pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
+
+.lifetime { color: #DFAF8F; font-style: italic; }
+.label { color: #DFAF8F; font-style: italic; }
+.comment { color: #7F9F7F; }
+.documentation { color: #629755; }
+.intra_doc_link { font-style: italic; }
+.injected { opacity: 0.65 ; }
+.struct, .enum { color: #7CB8BB; }
+.enum_variant { color: #BDE0F3; }
+.string_literal { color: #CC9393; }
+.field { color: #94BFF3; }
+.function { color: #93E0E3; }
+.function.unsafe { color: #BC8383; }
+.trait.unsafe { color: #BC8383; }
+.operator.unsafe { color: #BC8383; }
+.mutable.unsafe { color: #BC8383; text-decoration: underline; }
+.keyword.unsafe { color: #BC8383; font-weight: bold; }
+.macro.unsafe { color: #BC8383; }
+.parameter { color: #94BFF3; }
+.text { color: #DCDCCC; }
+.type { color: #7CB8BB; }
+.builtin_type { color: #8CD0D3; }
+.type_param { color: #DFAF8F; }
+.attribute { color: #94BFF3; }
+.numeric_literal { color: #BFEBBF; }
+.bool_literal { color: #BFE6EB; }
+.macro { color: #94BFF3; }
+.derive { color: #94BFF3; font-style: italic; }
+.module { color: #AFD8AF; }
+.value_param { color: #DCDCCC; }
+.variable { color: #DCDCCC; }
+.format_specifier { color: #CC696B; }
+.mutable { text-decoration: underline; }
+.escape_sequence { color: #94BFF3; }
+.keyword { color: #F0DFAF; font-weight: bold; }
+.control { font-style: italic; }
+.reference { font-style: italic; font-weight: bold; }
+
+.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+</style>
+<pre><code><span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">println</span> <span class="brace">{</span>
+ <span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>arg<span class="colon">:</span>tt<span class="parenthesis">)</span><span class="punctuation">*</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="parenthesis">(</span><span class="brace">{</span>
+ <span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>io<span class="colon">:</span><span class="colon">:</span>_print<span class="parenthesis">(</span><span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>format_args_nl<span class="punctuation">!</span><span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>arg<span class="parenthesis">)</span><span class="punctuation">*</span><span class="parenthesis">)</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="brace">}</span><span class="parenthesis">)</span>
+<span class="brace">}</span>
+<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">rustc_builtin_macro</span><span class="attribute_bracket attribute">]</span>
+<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">macro_export</span><span class="attribute_bracket attribute">]</span>
+<span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">format_args</span> <span class="brace">{</span><span class="brace">}</span>
+<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">rustc_builtin_macro</span><span class="attribute_bracket attribute">]</span>
+<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">macro_export</span><span class="attribute_bracket attribute">]</span>
+<span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">const_format_args</span> <span class="brace">{</span><span class="brace">}</span>
+<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">rustc_builtin_macro</span><span class="attribute_bracket attribute">]</span>
+<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">macro_export</span><span class="attribute_bracket attribute">]</span>
+<span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">format_args_nl</span> <span class="brace">{</span><span class="brace">}</span>
+
+<span class="keyword">mod</span> <span class="module declaration">panic</span> <span class="brace">{</span>
+ <span class="keyword">pub</span> <span class="keyword">macro</span> <span class="macro declaration">panic_2015</span> <span class="brace">{</span>
+ <span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="parenthesis">(</span>
+ <span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>panicking<span class="colon">:</span><span class="colon">:</span>panic<span class="parenthesis">(</span><span class="string_literal">"explicit panic"</span><span class="parenthesis">)</span>
+ <span class="parenthesis">)</span><span class="comma">,</span>
- <span class="parenthesis">(</span><span class="punctuation">$</span>msg<span class="colon">:</span>expr <span class="punctuation">$</span><span class="parenthesis">(</span><span class="comma">,</span><span class="parenthesis">)</span><span class="operator control">?</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="parenthesis">(</span>
++ <span class="parenthesis">(</span><span class="punctuation">$</span>msg<span class="colon">:</span>literal <span class="punctuation">$</span><span class="parenthesis">(</span><span class="comma">,</span><span class="parenthesis">)</span><span class="punctuation">?</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="parenthesis">(</span>
+ <span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>panicking<span class="colon">:</span><span class="colon">:</span>panic<span class="parenthesis">(</span><span class="punctuation">$</span>msg<span class="parenthesis">)</span>
+ <span class="parenthesis">)</span><span class="comma">,</span>
+ <span class="comment">// Use `panic_str` instead of `panic_display::<&str>` for non_fmt_panic lint.</span>
- <span class="parenthesis">(</span><span class="string_literal">"{}"</span><span class="comma">,</span> <span class="punctuation">$</span>arg<span class="colon">:</span>expr <span class="punctuation">$</span><span class="parenthesis">(</span><span class="comma">,</span><span class="parenthesis">)</span><span class="operator control">?</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="parenthesis">(</span>
- <span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>panicking<span class="colon">:</span><span class="colon">:</span>panic_display<span class="parenthesis">(</span><span class="operator">&</span><span class="punctuation">$</span>arg<span class="parenthesis">)</span>
++ <span class="parenthesis">(</span><span class="punctuation">$</span>msg<span class="colon">:</span>expr <span class="punctuation">$</span><span class="parenthesis">(</span><span class="comma">,</span><span class="parenthesis">)</span><span class="punctuation">?</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="parenthesis">(</span>
+ <span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>panicking<span class="colon">:</span><span class="colon">:</span>panic_str<span class="parenthesis">(</span><span class="punctuation">$</span>msg<span class="parenthesis">)</span>
+ <span class="parenthesis">)</span><span class="comma">,</span>
+ <span class="comment">// Special-case the single-argument case for const_panic.</span>
++ <span class="parenthesis">(</span><span class="string_literal">"{}"</span><span class="comma">,</span> <span class="punctuation">$</span>arg<span class="colon">:</span>expr <span class="punctuation">$</span><span class="parenthesis">(</span><span class="comma">,</span><span class="parenthesis">)</span><span class="punctuation">?</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="parenthesis">(</span>
++ <span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>panicking<span class="colon">:</span><span class="colon">:</span>panic_display<span class="parenthesis">(</span><span class="punctuation">&</span><span class="punctuation">$</span>arg<span class="parenthesis">)</span>
+ <span class="parenthesis">)</span><span class="comma">,</span>
+ <span class="parenthesis">(</span><span class="punctuation">$</span>fmt<span class="colon">:</span>expr<span class="comma">,</span> <span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>arg<span class="colon">:</span>tt<span class="parenthesis">)</span><span class="punctuation">+</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="parenthesis">(</span>
+ <span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>panicking<span class="colon">:</span><span class="colon">:</span>panic_fmt<span class="parenthesis">(</span><span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>const_format_args<span class="punctuation">!</span><span class="parenthesis">(</span><span class="punctuation">$</span>fmt<span class="comma">,</span> <span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>arg<span class="parenthesis">)</span><span class="punctuation">+</span><span class="parenthesis">)</span><span class="parenthesis">)</span>
+ <span class="parenthesis">)</span><span class="comma">,</span>
+ <span class="brace">}</span>
+<span class="brace">}</span>
+
+<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">rustc_builtin_macro</span><span class="parenthesis attribute">(</span><span class="none attribute">std_panic</span><span class="parenthesis attribute">)</span><span class="attribute_bracket attribute">]</span>
+<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">macro_export</span><span class="attribute_bracket attribute">]</span>
+<span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">panic</span> <span class="brace">{</span><span class="brace">}</span>
+<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">rustc_builtin_macro</span><span class="attribute_bracket attribute">]</span>
+<span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">assert</span> <span class="brace">{</span><span class="brace">}</span>
+<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">rustc_builtin_macro</span><span class="attribute_bracket attribute">]</span>
+<span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">asm</span> <span class="brace">{</span><span class="brace">}</span>
+
+<span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">toho</span> <span class="brace">{</span>
+ <span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="parenthesis">(</span><span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>panic<span class="punctuation">!</span><span class="parenthesis">(</span><span class="string_literal">"not yet implemented"</span><span class="parenthesis">)</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>arg<span class="colon">:</span>tt<span class="parenthesis">)</span><span class="punctuation">+</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="parenthesis">(</span><span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>panic<span class="punctuation">!</span><span class="parenthesis">(</span><span class="string_literal">"not yet implemented: {}"</span><span class="comma">,</span> <span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>format_args<span class="punctuation">!</span><span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>arg<span class="parenthesis">)</span><span class="punctuation">+</span><span class="parenthesis">)</span><span class="parenthesis">)</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+<span class="brace">}</span>
+
+<span class="keyword">fn</span> <span class="function declaration">main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello </span><span class="escape_sequence">{{</span><span class="string_literal">Hello</span><span class="escape_sequence">}}</span><span class="string_literal">"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="comment">// from https://doc.rust-lang.org/std/fmt/index.html</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello"</span><span class="parenthesis">)</span><span class="semicolon">;</span> <span class="comment">// => "Hello"</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello, </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="comma">,</span> <span class="string_literal">"world"</span><span class="parenthesis">)</span><span class="semicolon">;</span> <span class="comment">// => "Hello, world!"</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"The number is </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> <span class="numeric_literal">1</span><span class="parenthesis">)</span><span class="semicolon">;</span> <span class="comment">// => "The number is 1"</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">?</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> <span class="parenthesis">(</span><span class="numeric_literal">3</span><span class="comma">,</span> <span class="numeric_literal">4</span><span class="parenthesis">)</span><span class="parenthesis">)</span><span class="semicolon">;</span> <span class="comment">// => "(3, 4)"</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">value</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> value<span class="operator">=</span><span class="numeric_literal">4</span><span class="parenthesis">)</span><span class="semicolon">;</span> <span class="comment">// => "4"</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> <span class="numeric_literal">1</span><span class="comma">,</span> <span class="numeric_literal">2</span><span class="parenthesis">)</span><span class="semicolon">;</span> <span class="comment">// => "1 2"</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">0</span><span class="numeric_literal">4</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> <span class="numeric_literal">42</span><span class="parenthesis">)</span><span class="semicolon">;</span> <span class="comment">// => "0042" with leading zerosV</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="numeric_literal">1</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="numeric_literal">0</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> <span class="numeric_literal">1</span><span class="comma">,</span> <span class="numeric_literal">2</span><span class="parenthesis">)</span><span class="semicolon">;</span> <span class="comment">// => "2 1 1 2"</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">argument</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> argument <span class="operator">=</span> <span class="string_literal">"test"</span><span class="parenthesis">)</span><span class="semicolon">;</span> <span class="comment">// => "test"</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">name</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> <span class="numeric_literal">1</span><span class="comma">,</span> name <span class="operator">=</span> <span class="numeric_literal">2</span><span class="parenthesis">)</span><span class="semicolon">;</span> <span class="comment">// => "2 1"</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">a</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="variable">c</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="variable">b</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> a<span class="operator">=</span><span class="string_literal">"a"</span><span class="comma">,</span> b<span class="operator">=</span><span class="char_literal">'b'</span><span class="comma">,</span> c<span class="operator">=</span><span class="numeric_literal">3</span><span class="parenthesis">)</span><span class="semicolon">;</span> <span class="comment">// => "a 3 b"</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="escape_sequence">{{</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="escape_sequence">}}</span><span class="string_literal">"</span><span class="comma">,</span> <span class="numeric_literal">2</span><span class="parenthesis">)</span><span class="semicolon">;</span> <span class="comment">// => "{2}"</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="comma">,</span> <span class="string_literal">"x"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">1</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="comma">,</span> <span class="string_literal">"x"</span><span class="comma">,</span> <span class="numeric_literal">5</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="numeric_literal">1</span><span class="format_specifier">:</span><span class="numeric_literal">0</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="comma">,</span> <span class="numeric_literal">5</span><span class="comma">,</span> <span class="string_literal">"x"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="variable">width</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="comma">,</span> <span class="string_literal">"x"</span><span class="comma">,</span> width <span class="operator">=</span> <span class="numeric_literal">5</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier"><</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="comma">,</span> <span class="string_literal">"x"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">-</span><span class="format_specifier"><</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="comma">,</span> <span class="string_literal">"x"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">^</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="comma">,</span> <span class="string_literal">"x"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">></span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="comma">,</span> <span class="string_literal">"x"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">+</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="comma">,</span> <span class="numeric_literal">5</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">#</span><span class="variable">x</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="comma">,</span> <span class="numeric_literal">27</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">0</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="comma">,</span> <span class="numeric_literal">5</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">0</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="comma">,</span> <span class="numeric_literal">-</span><span class="numeric_literal">5</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">#</span><span class="numeric_literal">0</span><span class="numeric_literal">10</span><span class="variable">x</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="comma">,</span> <span class="numeric_literal">27</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="numeric_literal">0</span><span class="format_specifier">}</span><span class="string_literal"> is </span><span class="format_specifier">{</span><span class="numeric_literal">1</span><span class="format_specifier">:</span><span class="format_specifier">.</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> <span class="string_literal">"x"</span><span class="comma">,</span> <span class="numeric_literal">0.01</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="numeric_literal">1</span><span class="format_specifier">}</span><span class="string_literal"> is </span><span class="format_specifier">{</span><span class="numeric_literal">2</span><span class="format_specifier">:</span><span class="format_specifier">.</span><span class="numeric_literal">0</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> <span class="numeric_literal">5</span><span class="comma">,</span> <span class="string_literal">"x"</span><span class="comma">,</span> <span class="numeric_literal">0.01</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="numeric_literal">0</span><span class="format_specifier">}</span><span class="string_literal"> is </span><span class="format_specifier">{</span><span class="numeric_literal">2</span><span class="format_specifier">:</span><span class="format_specifier">.</span><span class="numeric_literal">1</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> <span class="string_literal">"x"</span><span class="comma">,</span> <span class="numeric_literal">5</span><span class="comma">,</span> <span class="numeric_literal">0.01</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal"> is </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">.</span><span class="format_specifier">*</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> <span class="string_literal">"x"</span><span class="comma">,</span> <span class="numeric_literal">5</span><span class="comma">,</span> <span class="numeric_literal">0.01</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal"> is </span><span class="format_specifier">{</span><span class="numeric_literal">2</span><span class="format_specifier">:</span><span class="format_specifier">.</span><span class="format_specifier">*</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> <span class="string_literal">"x"</span><span class="comma">,</span> <span class="numeric_literal">5</span><span class="comma">,</span> <span class="numeric_literal">0.01</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal"> is </span><span class="format_specifier">{</span><span class="variable">number</span><span class="format_specifier">:</span><span class="format_specifier">.</span><span class="variable">prec</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> <span class="string_literal">"x"</span><span class="comma">,</span> prec <span class="operator">=</span> <span class="numeric_literal">5</span><span class="comma">,</span> number <span class="operator">=</span> <span class="numeric_literal">0.01</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">, `</span><span class="format_specifier">{</span><span class="variable">name</span><span class="format_specifier">:</span><span class="format_specifier">.</span><span class="format_specifier">*</span><span class="format_specifier">}</span><span class="string_literal">` has 3 fractional digits"</span><span class="comma">,</span> <span class="string_literal">"Hello"</span><span class="comma">,</span> <span class="numeric_literal">3</span><span class="comma">,</span> name<span class="operator">=</span><span class="numeric_literal">1234.56</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">, `</span><span class="format_specifier">{</span><span class="variable">name</span><span class="format_specifier">:</span><span class="format_specifier">.</span><span class="format_specifier">*</span><span class="format_specifier">}</span><span class="string_literal">` has 3 characters"</span><span class="comma">,</span> <span class="string_literal">"Hello"</span><span class="comma">,</span> <span class="numeric_literal">3</span><span class="comma">,</span> name<span class="operator">=</span><span class="string_literal">"1234.56"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">, `</span><span class="format_specifier">{</span><span class="variable">name</span><span class="format_specifier">:</span><span class="format_specifier">></span><span class="numeric_literal">8</span><span class="format_specifier">.</span><span class="format_specifier">*</span><span class="format_specifier">}</span><span class="string_literal">` has 3 right-aligned characters"</span><span class="comma">,</span> <span class="string_literal">"Hello"</span><span class="comma">,</span> <span class="numeric_literal">3</span><span class="comma">,</span> name<span class="operator">=</span><span class="string_literal">"1234.56"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+
+ <span class="keyword">let</span> <span class="punctuation">_</span> <span class="operator">=</span> <span class="string_literal">"{}"</span>
+ <span class="keyword">let</span> <span class="punctuation">_</span> <span class="operator">=</span> <span class="string_literal">"{{}}"</span><span class="semicolon">;</span>
+
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello </span><span class="escape_sequence">{{</span><span class="escape_sequence">}}</span><span class="string_literal">"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="escape_sequence">{{</span><span class="string_literal"> Hello"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello </span><span class="escape_sequence">}}</span><span class="string_literal">"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="escape_sequence">{{</span><span class="string_literal">Hello</span><span class="escape_sequence">}}</span><span class="string_literal">"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="escape_sequence">{{</span><span class="string_literal"> Hello </span><span class="escape_sequence">}}</span><span class="string_literal">"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="escape_sequence">{{</span><span class="string_literal">Hello </span><span class="escape_sequence">}}</span><span class="string_literal">"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="escape_sequence">{{</span><span class="string_literal"> Hello</span><span class="escape_sequence">}}</span><span class="string_literal">"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">r"Hello, </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">!"</span><span class="comma">,</span> <span class="string_literal">"world"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+
+ <span class="comment">// escape sequences</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"Hello</span><span class="escape_sequence">\n</span><span class="string_literal">World"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="escape_sequence">\u{48}</span><span class="escape_sequence">\x65</span><span class="escape_sequence">\x6C</span><span class="escape_sequence">\x6C</span><span class="escape_sequence">\x6F</span><span class="string_literal"> World"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+
+ <span class="keyword">let</span> <span class="punctuation">_</span> <span class="operator">=</span> <span class="string_literal">"</span><span class="escape_sequence">\x28</span><span class="escape_sequence">\x28</span><span class="escape_sequence">\x00</span><span class="escape_sequence">\x63</span><span class="escape_sequence">\n</span><span class="string_literal">"</span><span class="semicolon">;</span>
+ <span class="keyword">let</span> <span class="punctuation">_</span> <span class="operator">=</span> <span class="string_literal">b"</span><span class="escape_sequence">\x28</span><span class="escape_sequence">\x28</span><span class="escape_sequence">\x00</span><span class="escape_sequence">\x63</span><span class="escape_sequence">\n</span><span class="string_literal">"</span><span class="semicolon">;</span>
+
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="escape_sequence">\x41</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> A <span class="operator">=</span> <span class="numeric_literal">92</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">ничоси</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> ничоси <span class="operator">=</span> <span class="numeric_literal">92</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+
+ <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="variable">x</span><span class="format_specifier">?</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal"> "</span><span class="comma">,</span> <span class="unresolved_reference">thingy</span><span class="comma">,</span> <span class="unresolved_reference">n2</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">panic</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> <span class="numeric_literal">0</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">panic</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"more </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> <span class="numeric_literal">1</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">assert</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="bool_literal">true</span><span class="comma">,</span> <span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="comma">,</span> <span class="numeric_literal">1</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">assert</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="bool_literal">true</span><span class="comma">,</span> <span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal"> asdasd"</span><span class="comma">,</span> <span class="numeric_literal">1</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">toho</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">fmt"</span><span class="comma">,</span> <span class="numeric_literal">0</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro unsafe">asm</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="string_literal">"mov eax, </span><span class="format_specifier">{</span><span class="numeric_literal">0</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro">format_args</span><span class="macro_bang">!</span><span class="parenthesis">(</span>concat<span class="punctuation">!</span><span class="parenthesis">(</span><span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">"</span><span class="parenthesis">)</span><span class="comma">,</span> <span class="string_literal">"{}"</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+<span class="brace">}</span></code></pre>
--- /dev/null
- <span class="punctuation">*</span><span class="parenthesis">(</span><span class="operator">&</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="keyword">as</span> <span class="punctuation">*</span><span class="keyword">const</span> <span class="parenthesis">(</span><span class="parenthesis">)</span><span class="parenthesis">)</span>
+
+<style>
+body { margin: 0; }
+pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
+
+.lifetime { color: #DFAF8F; font-style: italic; }
+.label { color: #DFAF8F; font-style: italic; }
+.comment { color: #7F9F7F; }
+.documentation { color: #629755; }
+.intra_doc_link { font-style: italic; }
+.injected { opacity: 0.65 ; }
+.struct, .enum { color: #7CB8BB; }
+.enum_variant { color: #BDE0F3; }
+.string_literal { color: #CC9393; }
+.field { color: #94BFF3; }
+.function { color: #93E0E3; }
+.function.unsafe { color: #BC8383; }
+.trait.unsafe { color: #BC8383; }
+.operator.unsafe { color: #BC8383; }
+.mutable.unsafe { color: #BC8383; text-decoration: underline; }
+.keyword.unsafe { color: #BC8383; font-weight: bold; }
+.macro.unsafe { color: #BC8383; }
+.parameter { color: #94BFF3; }
+.text { color: #DCDCCC; }
+.type { color: #7CB8BB; }
+.builtin_type { color: #8CD0D3; }
+.type_param { color: #DFAF8F; }
+.attribute { color: #94BFF3; }
+.numeric_literal { color: #BFEBBF; }
+.bool_literal { color: #BFE6EB; }
+.macro { color: #94BFF3; }
+.derive { color: #94BFF3; font-style: italic; }
+.module { color: #AFD8AF; }
+.value_param { color: #DCDCCC; }
+.variable { color: #DCDCCC; }
+.format_specifier { color: #CC696B; }
+.mutable { text-decoration: underline; }
+.escape_sequence { color: #94BFF3; }
+.keyword { color: #F0DFAF; font-weight: bold; }
+.control { font-style: italic; }
+.reference { font-style: italic; font-weight: bold; }
+
+.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+</style>
+<pre><code><span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">id</span> <span class="brace">{</span>
+ <span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>tt<span class="colon">:</span>tt<span class="parenthesis">)</span><span class="punctuation">*</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="brace">{</span>
+ <span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>tt<span class="parenthesis">)</span><span class="punctuation">*</span>
+ <span class="brace">}</span><span class="semicolon">;</span>
+<span class="brace">}</span>
+<span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">unsafe_deref</span> <span class="brace">{</span>
+ <span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="brace">{</span>
- <span class="keyword unsafe">unsafe</span> <span class="keyword">fn</span> <span class="function associated declaration reference unsafe">unsafe_method</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration reference">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
++ <span class="punctuation">*</span><span class="parenthesis">(</span><span class="punctuation">&</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="keyword">as</span> <span class="punctuation">*</span><span class="keyword">const</span> <span class="parenthesis">(</span><span class="parenthesis">)</span><span class="parenthesis">)</span>
+ <span class="brace">}</span><span class="semicolon">;</span>
+<span class="brace">}</span>
+<span class="keyword">static</span> <span class="keyword">mut</span> <span class="static declaration mutable unsafe">MUT_GLOBAL</span><span class="colon">:</span> <span class="struct">Struct</span> <span class="operator">=</span> <span class="struct">Struct</span> <span class="brace">{</span> <span class="field">field</span><span class="colon">:</span> <span class="numeric_literal">0</span> <span class="brace">}</span><span class="semicolon">;</span>
+<span class="keyword">static</span> <span class="static declaration">GLOBAL</span><span class="colon">:</span> <span class="struct">Struct</span> <span class="operator">=</span> <span class="struct">Struct</span> <span class="brace">{</span> <span class="field">field</span><span class="colon">:</span> <span class="numeric_literal">0</span> <span class="brace">}</span><span class="semicolon">;</span>
+<span class="keyword unsafe">unsafe</span> <span class="keyword">fn</span> <span class="function declaration unsafe">unsafe_fn</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
+
+<span class="keyword">union</span> <span class="union declaration">Union</span> <span class="brace">{</span>
+ <span class="field declaration">a</span><span class="colon">:</span> <span class="builtin_type">u32</span><span class="comma">,</span>
+ <span class="field declaration">b</span><span class="colon">:</span> <span class="builtin_type">f32</span><span class="comma">,</span>
+<span class="brace">}</span>
+
+<span class="keyword">struct</span> <span class="struct declaration">Struct</span> <span class="brace">{</span> <span class="field declaration">field</span><span class="colon">:</span> <span class="builtin_type">i32</span> <span class="brace">}</span>
+<span class="keyword">impl</span> <span class="struct">Struct</span> <span class="brace">{</span>
- <span class="keyword">fn</span> <span class="function associated declaration reference trait">calls_autoref</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration reference">self</span><span class="parenthesis">)</span><span class="semicolon">;</span>
++ <span class="keyword unsafe">unsafe</span> <span class="keyword">fn</span> <span class="function associated declaration reference unsafe">unsafe_method</span><span class="parenthesis">(</span><span class="punctuation">&</span><span class="self_keyword declaration reference">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
+<span class="brace">}</span>
+
+<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">repr</span><span class="parenthesis attribute">(</span><span class="none attribute">packed</span><span class="parenthesis attribute">)</span><span class="attribute_bracket attribute">]</span>
+<span class="keyword">struct</span> <span class="struct declaration">Packed</span> <span class="brace">{</span>
+ <span class="field declaration">a</span><span class="colon">:</span> <span class="builtin_type">u16</span><span class="comma">,</span>
+<span class="brace">}</span>
+
+<span class="keyword unsafe">unsafe</span> <span class="keyword">trait</span> <span class="trait declaration unsafe">UnsafeTrait</span> <span class="brace">{</span><span class="brace">}</span>
+<span class="keyword unsafe">unsafe</span> <span class="keyword">impl</span> <span class="trait unsafe">UnsafeTrait</span> <span class="keyword">for</span> <span class="struct">Packed</span> <span class="brace">{</span><span class="brace">}</span>
+<span class="keyword">impl</span> <span class="punctuation">!</span><span class="trait">UnsafeTrait</span> <span class="keyword">for</span> <span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
+
+<span class="keyword">fn</span> <span class="function declaration">unsafe_trait_bound</span><span class="angle"><</span><span class="type_param declaration">T</span><span class="colon">:</span> <span class="trait">UnsafeTrait</span><span class="angle">></span><span class="parenthesis">(</span><span class="punctuation">_</span><span class="colon">:</span> <span class="type_param">T</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
+
+<span class="keyword">trait</span> <span class="trait declaration">DoTheAutoref</span> <span class="brace">{</span>
- <span class="keyword">fn</span> <span class="function associated declaration reference trait">calls_autoref</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration reference">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
++ <span class="keyword">fn</span> <span class="function associated declaration reference trait">calls_autoref</span><span class="parenthesis">(</span><span class="punctuation">&</span><span class="self_keyword declaration reference">self</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+<span class="brace">}</span>
+
+<span class="keyword">impl</span> <span class="trait">DoTheAutoref</span> <span class="keyword">for</span> <span class="builtin_type">u16</span> <span class="brace">{</span>
++ <span class="keyword">fn</span> <span class="function associated declaration reference trait">calls_autoref</span><span class="parenthesis">(</span><span class="punctuation">&</span><span class="self_keyword declaration reference">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
+<span class="brace">}</span>
+
+<span class="keyword">fn</span> <span class="function declaration">main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
+ <span class="keyword">let</span> <span class="variable declaration">x</span> <span class="operator">=</span> <span class="operator">&</span><span class="numeric_literal">5</span> <span class="keyword">as</span> <span class="keyword">*</span><span class="keyword">const</span> <span class="punctuation">_</span> <span class="keyword">as</span> <span class="keyword">*</span><span class="keyword">const</span> <span class="builtin_type">usize</span><span class="semicolon">;</span>
+ <span class="keyword">let</span> <span class="variable declaration">u</span> <span class="operator">=</span> <span class="union">Union</span> <span class="brace">{</span> <span class="field">b</span><span class="colon">:</span> <span class="numeric_literal">0</span> <span class="brace">}</span><span class="semicolon">;</span>
+
+ <span class="macro">id</span><span class="macro_bang">!</span> <span class="brace">{</span>
+ <span class="keyword unsafe">unsafe</span> <span class="brace">{</span> <span class="macro unsafe">unsafe_deref</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">}</span>
+ <span class="brace">}</span><span class="semicolon">;</span>
+
+ <span class="keyword unsafe">unsafe</span> <span class="brace">{</span>
+ <span class="macro unsafe">unsafe_deref</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="macro unsafe">id</span><span class="macro_bang">!</span> <span class="brace">{</span> <span class="macro unsafe">unsafe_deref</span><span class="macro_bang">!</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">}</span><span class="semicolon">;</span>
+
+ <span class="comment">// unsafe fn and method calls</span>
+ <span class="function unsafe">unsafe_fn</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="keyword">let</span> <span class="variable declaration">b</span> <span class="operator">=</span> <span class="variable">u</span><span class="operator">.</span><span class="field unsafe">b</span><span class="semicolon">;</span>
+ <span class="keyword control">match</span> <span class="variable">u</span> <span class="brace">{</span>
+ <span class="union">Union</span> <span class="brace">{</span> <span class="field unsafe">b</span><span class="colon">:</span> <span class="numeric_literal">0</span> <span class="brace">}</span> <span class="operator">=></span> <span class="parenthesis">(</span><span class="parenthesis">)</span><span class="comma">,</span>
+ <span class="union">Union</span> <span class="brace">{</span> <span class="field unsafe">a</span> <span class="brace">}</span> <span class="operator">=></span> <span class="parenthesis">(</span><span class="parenthesis">)</span><span class="comma">,</span>
+ <span class="brace">}</span>
+ <span class="struct">Struct</span> <span class="brace">{</span> <span class="field">field</span><span class="colon">:</span> <span class="numeric_literal">0</span> <span class="brace">}</span><span class="operator">.</span><span class="function associated reference unsafe">unsafe_method</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+
+ <span class="comment">// unsafe deref</span>
+ <span class="operator unsafe">*</span><span class="variable">x</span><span class="semicolon">;</span>
+
+ <span class="comment">// unsafe access to a static mut</span>
+ <span class="static mutable unsafe">MUT_GLOBAL</span><span class="operator">.</span><span class="field">field</span><span class="semicolon">;</span>
+ <span class="static">GLOBAL</span><span class="operator">.</span><span class="field">field</span><span class="semicolon">;</span>
+
+ <span class="comment">// unsafe ref of packed fields</span>
+ <span class="keyword">let</span> <span class="variable declaration">packed</span> <span class="operator">=</span> <span class="struct">Packed</span> <span class="brace">{</span> <span class="field">a</span><span class="colon">:</span> <span class="numeric_literal">0</span> <span class="brace">}</span><span class="semicolon">;</span>
+ <span class="keyword">let</span> <span class="variable declaration reference">a</span> <span class="operator">=</span> <span class="operator unsafe">&</span><span class="variable">packed</span><span class="operator">.</span><span class="field">a</span><span class="semicolon">;</span>
+ <span class="keyword">let</span> <span class="keyword unsafe">ref</span> <span class="variable declaration reference">a</span> <span class="operator">=</span> <span class="variable">packed</span><span class="operator">.</span><span class="field">a</span><span class="semicolon">;</span>
+ <span class="keyword">let</span> <span class="struct">Packed</span> <span class="brace">{</span> <span class="keyword unsafe">ref</span> <span class="field">a</span> <span class="brace">}</span> <span class="operator">=</span> <span class="variable">packed</span><span class="semicolon">;</span>
+ <span class="keyword">let</span> <span class="struct">Packed</span> <span class="brace">{</span> <span class="field">a</span><span class="colon">:</span> <span class="keyword unsafe">ref</span> <span class="variable declaration reference">_a</span> <span class="brace">}</span> <span class="operator">=</span> <span class="variable">packed</span><span class="semicolon">;</span>
+
+ <span class="comment">// unsafe auto ref of packed field</span>
+ <span class="variable">packed</span><span class="operator">.</span><span class="field">a</span><span class="operator">.</span><span class="function associated reference trait unsafe">calls_autoref</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="brace">}</span>
+<span class="brace">}</span></code></pre>
--- /dev/null
+//! Proc macro ABI
+
+extern crate proc_macro;
+
+#[allow(dead_code)]
+#[doc(hidden)]
+mod ra_server;
+
+use libloading::Library;
+use proc_macro_api::ProcMacroKind;
+
+use super::PanicMessage;
+
++pub use ra_server::TokenStream;
++
+pub(crate) struct Abi {
+ exported_macros: Vec<proc_macro::bridge::client::ProcMacro>,
+}
+
+impl From<proc_macro::bridge::PanicMessage> for PanicMessage {
+ fn from(p: proc_macro::bridge::PanicMessage) -> Self {
+ Self { message: p.as_str().map(|s| s.to_string()) }
+ }
+}
+
+impl Abi {
+ pub unsafe fn from_lib(lib: &Library, symbol_name: String) -> Result<Abi, libloading::Error> {
+ let macros: libloading::Symbol<'_, &&[proc_macro::bridge::client::ProcMacro]> =
+ lib.get(symbol_name.as_bytes())?;
+ Ok(Self { exported_macros: macros.to_vec() })
+ }
+
+ pub fn expand(
+ &self,
+ macro_name: &str,
+ macro_body: &tt::Subtree,
+ attributes: Option<&tt::Subtree>,
+ ) -> Result<tt::Subtree, PanicMessage> {
+ let parsed_body = ra_server::TokenStream::with_subtree(macro_body.clone());
+
+ let parsed_attributes = attributes.map_or(ra_server::TokenStream::new(), |attr| {
+ ra_server::TokenStream::with_subtree(attr.clone())
+ });
+
+ for proc_macro in &self.exported_macros {
+ match proc_macro {
+ proc_macro::bridge::client::ProcMacro::CustomDerive {
+ trait_name, client, ..
+ } if *trait_name == macro_name => {
+ let res = client.run(
+ &proc_macro::bridge::server::SameThread,
+ ra_server::RustAnalyzer::default(),
+ parsed_body,
+ true,
+ );
+ return res.map(|it| it.into_subtree()).map_err(PanicMessage::from);
+ }
+ proc_macro::bridge::client::ProcMacro::Bang { name, client }
+ if *name == macro_name =>
+ {
+ let res = client.run(
+ &proc_macro::bridge::server::SameThread,
+ ra_server::RustAnalyzer::default(),
+ parsed_body,
+ true,
+ );
+ return res.map(|it| it.into_subtree()).map_err(PanicMessage::from);
+ }
+ proc_macro::bridge::client::ProcMacro::Attr { name, client }
+ if *name == macro_name =>
+ {
+ let res = client.run(
+ &proc_macro::bridge::server::SameThread,
+ ra_server::RustAnalyzer::default(),
+ parsed_attributes,
+ parsed_body,
+ true,
+ );
+ return res.map(|it| it.into_subtree()).map_err(PanicMessage::from);
+ }
+ _ => continue,
+ }
+ }
+
+ Err(proc_macro::bridge::PanicMessage::String("Nothing to expand".to_string()).into())
+ }
+
+ pub fn list_macros(&self) -> Vec<(String, ProcMacroKind)> {
+ self.exported_macros
+ .iter()
+ .map(|proc_macro| match proc_macro {
+ proc_macro::bridge::client::ProcMacro::CustomDerive { trait_name, .. } => {
+ (trait_name.to_string(), ProcMacroKind::CustomDerive)
+ }
+ proc_macro::bridge::client::ProcMacro::Bang { name, .. } => {
+ (name.to_string(), ProcMacroKind::FuncLike)
+ }
+ proc_macro::bridge::client::ProcMacro::Attr { name, .. } => {
+ (name.to_string(), ProcMacroKind::Attr)
+ }
+ })
+ .collect()
+ }
+}
--- /dev/null
- #[cfg(test)]
- pub(crate) use abi_1_63::TokenStream as TestTokenStream;
+//! Procedural macros are implemented by compiling the macro providing crate
+//! to a dynamic library with a particular ABI which the compiler uses to expand
+//! macros. Unfortunately this ABI is not specified and can change from version
+//! to version of the compiler. To support this we copy the ABI from the rust
+//! compiler into submodules of this module (e.g proc_macro_srv::abis::abi_1_47).
+//!
+//! All of these ABIs are subsumed in the `Abi` enum, which exposes a simple
+//! interface the rest of rust-analyzer can use to talk to the macro
+//! provider.
+//!
+//! # Adding a new ABI
+//!
+//! To add a new ABI you'll need to copy the source of the target proc_macro
+//! crate from the source tree of the Rust compiler into this directory tree.
+//! Then you'll need to modify it
+//! - Remove any feature! or other things which won't compile on stable
+//! - change any absolute imports to relative imports within the ABI tree
+//!
+//! Then you'll need to add a branch to the `Abi` enum and an implementation of
+//! `Abi::expand`, `Abi::list_macros` and `Abi::from_lib` for the new ABI. See
+//! `proc_macro_srv/src/abis/abi_1_47/mod.rs` for an example. Finally you'll
+//! need to update the conditionals in `Abi::from_lib` to return your new ABI
+//! for the relevant versions of the rust compiler
+//!
+
+mod abi_1_58;
+mod abi_1_63;
+#[cfg(feature = "sysroot-abi")]
+mod abi_sysroot;
+
+// see `build.rs`
+include!(concat!(env!("OUT_DIR"), "/rustc_version.rs"));
+
+// Used by `test/utils.rs`
++#[cfg(all(test, feature = "sysroot-abi"))]
++pub(crate) use abi_sysroot::TokenStream as TestTokenStream;
+
+use super::dylib::LoadProcMacroDylibError;
+pub(crate) use abi_1_58::Abi as Abi_1_58;
+pub(crate) use abi_1_63::Abi as Abi_1_63;
+#[cfg(feature = "sysroot-abi")]
+pub(crate) use abi_sysroot::Abi as Abi_Sysroot;
+use libloading::Library;
+use proc_macro_api::{ProcMacroKind, RustCInfo};
+
+pub struct PanicMessage {
+ message: Option<String>,
+}
+
+impl PanicMessage {
+ pub fn as_str(&self) -> Option<String> {
+ self.message.clone()
+ }
+}
+
+pub(crate) enum Abi {
+ Abi1_58(Abi_1_58),
+ Abi1_63(Abi_1_63),
+ #[cfg(feature = "sysroot-abi")]
+ AbiSysroot(Abi_Sysroot),
+}
+
+impl Abi {
+ /// Load a new ABI.
+ ///
+ /// # Arguments
+ ///
+ /// *`lib` - The dynamic library containing the macro implementations
+ /// *`symbol_name` - The symbol name the macros can be found attributes
+ /// *`info` - RustCInfo about the compiler that was used to compile the
+ /// macro crate. This is the information we use to figure out
+ /// which ABI to return
+ pub fn from_lib(
+ lib: &Library,
+ symbol_name: String,
+ info: RustCInfo,
+ ) -> Result<Abi, LoadProcMacroDylibError> {
+ // the sysroot ABI relies on `extern proc_macro` with unstable features,
+ // instead of a snapshot of the proc macro bridge's source code. it's only
+ // enabled if we have an exact version match.
+ #[cfg(feature = "sysroot-abi")]
+ {
+ if info.version_string == RUSTC_VERSION_STRING {
+ let inner = unsafe { Abi_Sysroot::from_lib(lib, symbol_name) }?;
+ return Ok(Abi::AbiSysroot(inner));
+ }
+
+ // if we reached this point, versions didn't match. in testing, we
+ // want that to panic - this could mean that the format of `rustc
+ // --version` no longer matches the format of the version string
+ // stored in the `.rustc` section, and we want to catch that in-tree
+ // with `x.py test`
+ #[cfg(test)]
+ {
+ let allow_mismatch = std::env::var("PROC_MACRO_SRV_ALLOW_SYSROOT_MISMATCH");
+ if let Ok("1") = allow_mismatch.as_deref() {
+ // only used by rust-analyzer developers, when working on the
+ // sysroot ABI from the rust-analyzer repository - which should
+ // only happen pre-subtree. this can be removed later.
+ } else {
+ panic!(
+ "sysroot ABI mismatch: dylib rustc version (read from .rustc section): {:?} != proc-macro-srv version (read from 'rustc --version'): {:?}",
+ info.version_string, RUSTC_VERSION_STRING
+ );
+ }
+ }
+ }
+
+ // FIXME: this should use exclusive ranges when they're stable
+ // https://github.com/rust-lang/rust/issues/37854
+ match (info.version.0, info.version.1) {
+ (1, 58..=62) => {
+ let inner = unsafe { Abi_1_58::from_lib(lib, symbol_name) }?;
+ Ok(Abi::Abi1_58(inner))
+ }
+ (1, 63) => {
+ let inner = unsafe { Abi_1_63::from_lib(lib, symbol_name) }?;
+ Ok(Abi::Abi1_63(inner))
+ }
+ _ => Err(LoadProcMacroDylibError::UnsupportedABI),
+ }
+ }
+
+ pub fn expand(
+ &self,
+ macro_name: &str,
+ macro_body: &tt::Subtree,
+ attributes: Option<&tt::Subtree>,
+ ) -> Result<tt::Subtree, PanicMessage> {
+ match self {
+ Self::Abi1_58(abi) => abi.expand(macro_name, macro_body, attributes),
+ Self::Abi1_63(abi) => abi.expand(macro_name, macro_body, attributes),
+ #[cfg(feature = "sysroot-abi")]
+ Self::AbiSysroot(abi) => abi.expand(macro_name, macro_body, attributes),
+ }
+ }
+
+ pub fn list_macros(&self) -> Vec<(String, ProcMacroKind)> {
+ match self {
+ Self::Abi1_58(abi) => abi.list_macros(),
+ Self::Abi1_63(abi) => abi.list_macros(),
+ #[cfg(feature = "sysroot-abi")]
+ Self::AbiSysroot(abi) => abi.list_macros(),
+ }
+ }
+}
++
++#[test]
++fn test_version_check() {
++ let path = paths::AbsPathBuf::assert(crate::proc_macro_test_dylib_path());
++ let info = proc_macro_api::read_dylib_info(&path).unwrap();
++ assert!(info.version.1 >= 50);
++}
--- /dev/null
- pub mod cli;
+//! RA Proc Macro Server
+//!
+//! This library is able to call compiled Rust custom derive dynamic libraries on arbitrary code.
+//! The general idea here is based on <https://github.com/fedochet/rust-proc-macro-expander>.
+//!
+//! But we adapt it to better fit RA needs:
+//!
+//! * We use `tt` for proc-macro `TokenStream` server, it is easier to manipulate and interact with
+//! RA than `proc-macro2` token stream.
+//! * By **copying** the whole rustc `lib_proc_macro` code, we are able to build this with `stable`
+//! rustc rather than `unstable`. (Although in general ABI compatibility is still an issue)…
+
+#![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)]
+#![cfg_attr(
+ feature = "sysroot-abi",
+ feature(proc_macro_internals, proc_macro_diagnostic, proc_macro_span)
+)]
+#![allow(unreachable_pub)]
+
+mod dylib;
+mod abis;
+
++pub mod cli;
++
+use std::{
+ collections::{hash_map::Entry, HashMap},
+ env,
+ ffi::OsString,
+ fs,
+ path::{Path, PathBuf},
+ thread,
+ time::SystemTime,
+};
+
+use proc_macro_api::{
+ msg::{ExpandMacro, FlatTree, PanicMessage},
+ ProcMacroKind,
+};
+
+#[derive(Default)]
+pub(crate) struct ProcMacroSrv {
+ expanders: HashMap<(PathBuf, SystemTime), dylib::Expander>,
+}
+
+const EXPANDER_STACK_SIZE: usize = 8 * 1024 * 1024;
+
+impl ProcMacroSrv {
+ pub fn expand(&mut self, task: ExpandMacro) -> Result<FlatTree, PanicMessage> {
+ let expander = self.expander(task.lib.as_ref()).map_err(|err| {
+ debug_assert!(false, "should list macros before asking to expand");
+ PanicMessage(format!("failed to load macro: {}", err))
+ })?;
+
+ let prev_env = EnvSnapshot::new();
+ for (k, v) in &task.env {
+ env::set_var(k, v);
+ }
+ let prev_working_dir = match task.current_dir {
+ Some(dir) => {
+ let prev_working_dir = std::env::current_dir().ok();
+ if let Err(err) = std::env::set_current_dir(&dir) {
+ eprintln!("Failed to set the current working dir to {}. Error: {:?}", dir, err)
+ }
+ prev_working_dir
+ }
+ None => None,
+ };
+
+ let macro_body = task.macro_body.to_subtree();
+ let attributes = task.attributes.map(|it| it.to_subtree());
+ let result = thread::scope(|s| {
+ let thread = thread::Builder::new()
+ .stack_size(EXPANDER_STACK_SIZE)
+ .name(task.macro_name.clone())
+ .spawn_scoped(s, || {
+ expander
+ .expand(&task.macro_name, ¯o_body, attributes.as_ref())
+ .map(|it| FlatTree::new(&it))
+ });
+ let res = match thread {
+ Ok(handle) => handle.join(),
+ Err(e) => std::panic::resume_unwind(Box::new(e)),
+ };
+
+ match res {
+ Ok(res) => res,
+ Err(e) => std::panic::resume_unwind(e),
+ }
+ });
+
+ prev_env.rollback();
+
+ if let Some(dir) = prev_working_dir {
+ if let Err(err) = std::env::set_current_dir(&dir) {
+ eprintln!(
+ "Failed to set the current working dir to {}. Error: {:?}",
+ dir.display(),
+ err
+ )
+ }
+ }
+
+ result.map_err(PanicMessage)
+ }
+
+ pub(crate) fn list_macros(
+ &mut self,
+ dylib_path: &Path,
+ ) -> Result<Vec<(String, ProcMacroKind)>, String> {
+ let expander = self.expander(dylib_path)?;
+ Ok(expander.list_macros())
+ }
+
+ fn expander(&mut self, path: &Path) -> Result<&dylib::Expander, String> {
+ let time = fs::metadata(path).and_then(|it| it.modified()).map_err(|err| {
+ format!("Failed to get file metadata for {}: {:?}", path.display(), err)
+ })?;
+
+ Ok(match self.expanders.entry((path.to_path_buf(), time)) {
+ Entry::Vacant(v) => v.insert(dylib::Expander::new(path).map_err(|err| {
+ format!("Cannot create expander for {}: {:?}", path.display(), err)
+ })?),
+ Entry::Occupied(e) => e.into_mut(),
+ })
+ }
+}
+
+struct EnvSnapshot {
+ vars: HashMap<OsString, OsString>,
+}
+
+impl EnvSnapshot {
+ fn new() -> EnvSnapshot {
+ EnvSnapshot { vars: env::vars_os().collect() }
+ }
+
+ fn rollback(self) {
+ let mut old_vars = self.vars;
+ for (name, value) in env::vars_os() {
+ let old_value = old_vars.remove(&name);
+ if old_value != Some(value) {
+ match old_value {
+ None => env::remove_var(name),
+ Some(old_value) => env::set_var(name, old_value),
+ }
+ }
+ }
+ for (name, old_value) in old_vars {
+ env::set_var(name, old_value)
+ }
+ }
+}
+
- mod tests;
++#[cfg(all(feature = "sysroot-abi", test))]
++mod tests;
+
+#[cfg(test)]
++pub fn proc_macro_test_dylib_path() -> std::path::PathBuf {
++ proc_macro_test::PROC_MACRO_TEST_LOCATION.into()
++}
--- /dev/null
- use expect_test::expect;
- use paths::AbsPathBuf;
+//! proc-macro tests
+
+#[macro_use]
+mod utils;
-
- #[test]
- fn test_version_check() {
- let path = AbsPathBuf::assert(fixtures::proc_macro_test_dylib_path());
- let info = proc_macro_api::read_dylib_info(&path).unwrap();
- assert!(info.version.1 >= 50);
- }
+use utils::*;
+
++use expect_test::expect;
++
+#[test]
+fn test_derive_empty() {
+ assert_expand("DeriveEmpty", r#"struct S;"#, expect![[r#"SUBTREE $"#]]);
+}
+
+#[test]
+fn test_derive_error() {
+ assert_expand(
+ "DeriveError",
+ r#"struct S;"#,
+ expect![[r##"
+ SUBTREE $
+ IDENT compile_error 4294967295
+ PUNCH ! [joint] 4294967295
+ SUBTREE () 4294967295
+ LITERAL "#[derive(DeriveError)] struct S ;" 4294967295
+ PUNCH ; [alone] 4294967295"##]],
+ );
+}
+
+#[test]
+fn test_fn_like_macro_noop() {
+ assert_expand(
+ "fn_like_noop",
+ r#"ident, 0, 1, []"#,
+ expect![[r#"
+ SUBTREE $
+ IDENT ident 4294967295
+ PUNCH , [alone] 4294967295
+ LITERAL 0 4294967295
+ PUNCH , [alone] 4294967295
+ LITERAL 1 4294967295
+ PUNCH , [alone] 4294967295
+ SUBTREE [] 4294967295"#]],
+ );
+}
+
+#[test]
+fn test_fn_like_macro_clone_ident_subtree() {
+ assert_expand(
+ "fn_like_clone_tokens",
+ r#"ident, []"#,
+ expect![[r#"
+ SUBTREE $
+ IDENT ident 4294967295
+ PUNCH , [alone] 4294967295
+ SUBTREE [] 4294967295"#]],
+ );
+}
+
+#[test]
+fn test_fn_like_macro_clone_raw_ident() {
+ assert_expand(
+ "fn_like_clone_tokens",
+ "r#async",
+ expect![[r#"
+ SUBTREE $
+ IDENT async 4294967295"#]],
+ );
+}
+
+#[test]
+fn test_fn_like_mk_literals() {
+ assert_expand(
+ "fn_like_mk_literals",
+ r#""#,
+ expect![[r#"
+ SUBTREE $
+ LITERAL b"byte_string" 4294967295
+ LITERAL 'c' 4294967295
+ LITERAL "string" 4294967295
+ LITERAL 3.14f64 4294967295
+ LITERAL 3.14 4294967295
+ LITERAL 123i64 4294967295
+ LITERAL 123 4294967295"#]],
+ );
+}
+
+#[test]
+fn test_fn_like_mk_idents() {
+ // FIXME: this test is wrong: raw should be 'r#raw' but ABIs 1.64 and below
+ // simply ignore `is_raw` when implementing the `Ident` interface.
+ assert_expand(
+ "fn_like_mk_idents",
+ r#""#,
+ expect![[r#"
+ SUBTREE $
+ IDENT standard 4294967295
+ IDENT raw 4294967295"#]],
+ );
+}
+
+#[test]
+fn test_fn_like_macro_clone_literals() {
+ assert_expand(
+ "fn_like_clone_tokens",
+ r#"1u16, 2_u32, -4i64, 3.14f32, "hello bridge""#,
+ expect![[r#"
+ SUBTREE $
+ LITERAL 1u16 4294967295
+ PUNCH , [alone] 4294967295
+ LITERAL 2_u32 4294967295
+ PUNCH , [alone] 4294967295
+ PUNCH - [joint] 4294967295
+ LITERAL 4i64 4294967295
+ PUNCH , [alone] 4294967295
+ LITERAL 3.14f32 4294967295
+ PUNCH , [alone] 4294967295
+ LITERAL "hello bridge" 4294967295"#]],
+ );
+}
+
+#[test]
+fn test_attr_macro() {
+ // Corresponds to
+ // #[proc_macro_test::attr_error(some arguments)]
+ // mod m {}
+ assert_expand_attr(
+ "attr_error",
+ r#"mod m {}"#,
+ r#"some arguments"#,
+ expect![[r##"
+ SUBTREE $
+ IDENT compile_error 4294967295
+ PUNCH ! [joint] 4294967295
+ SUBTREE () 4294967295
+ LITERAL "#[attr_error(some arguments)] mod m {}" 4294967295
+ PUNCH ; [alone] 4294967295"##]],
+ );
+}
+
+/// Tests that we find and classify all proc macros correctly.
+#[test]
+fn list_test_macros() {
+ let res = list().join("\n");
+
+ expect![[r#"
+ fn_like_noop [FuncLike]
+ fn_like_panic [FuncLike]
+ fn_like_error [FuncLike]
+ fn_like_clone_tokens [FuncLike]
+ fn_like_mk_literals [FuncLike]
+ fn_like_mk_idents [FuncLike]
+ attr_noop [Attr]
+ attr_panic [Attr]
+ attr_error [Attr]
+ DeriveEmpty [CustomDerive]
+ DerivePanic [CustomDerive]
+ DeriveError [CustomDerive]"#]]
+ .assert_eq(&res);
+}
--- /dev/null
- use crate::dylib;
- use crate::ProcMacroSrv;
+//! utils used in proc-macro tests
+
- pub mod fixtures {
- pub fn proc_macro_test_dylib_path() -> std::path::PathBuf {
- proc_macro_test::PROC_MACRO_TEST_LOCATION.into()
- }
- }
+use expect_test::Expect;
+use std::str::FromStr;
+
- let path = fixtures::proc_macro_test_dylib_path();
++use crate::{dylib, proc_macro_test_dylib_path, ProcMacroSrv};
+
+fn parse_string(code: &str) -> Option<crate::abis::TestTokenStream> {
+ // This is a bit strange. We need to parse a string into a token stream into
+ // order to create a tt::SubTree from it in fixtures. `into_subtree` is
+ // implemented by all the ABIs we have so we arbitrarily choose one ABI to
+ // write a `parse_string` function for and use that. The tests don't really
+ // care which ABI we're using as the `into_subtree` function isn't part of
+ // the ABI and shouldn't change between ABI versions.
+ crate::abis::TestTokenStream::from_str(code).ok()
+}
+
+pub fn assert_expand(macro_name: &str, ra_fixture: &str, expect: Expect) {
+ assert_expand_impl(macro_name, ra_fixture, None, expect);
+}
+
+pub fn assert_expand_attr(macro_name: &str, ra_fixture: &str, attr_args: &str, expect: Expect) {
+ assert_expand_impl(macro_name, ra_fixture, Some(attr_args), expect);
+}
+
+fn assert_expand_impl(macro_name: &str, input: &str, attr: Option<&str>, expect: Expect) {
- let dylib_path = fixtures::proc_macro_test_dylib_path();
++ let path = proc_macro_test_dylib_path();
+ let expander = dylib::Expander::new(&path).unwrap();
+ let fixture = parse_string(input).unwrap();
+ let attr = attr.map(|attr| parse_string(attr).unwrap().into_subtree());
+
+ let res = expander.expand(macro_name, &fixture.into_subtree(), attr.as_ref()).unwrap();
+ expect.assert_eq(&format!("{:?}", res));
+}
+
+pub(crate) fn list() -> Vec<String> {
++ let dylib_path = proc_macro_test_dylib_path();
+ let mut srv = ProcMacroSrv::default();
+ let res = srv.list_macros(&dylib_path).unwrap();
+ res.into_iter().map(|(name, kind)| format!("{} [{:?}]", name, kind)).collect()
+}
--- /dev/null
- use crate::{cfg_flag::CfgFlag, CargoConfig, CargoWorkspace, Package};
+//! Workspace information we get from cargo consists of two pieces. The first is
+//! the output of `cargo metadata`. The second is the output of running
+//! `build.rs` files (`OUT_DIR` env var, extra cfg flags) and compiling proc
+//! macro.
+//!
+//! This module implements this second part. We use "build script" terminology
+//! here, but it covers procedural macros as well.
+
+use std::{cell::RefCell, io, path::PathBuf, process::Command};
+
+use cargo_metadata::{camino::Utf8Path, Message};
+use la_arena::ArenaMap;
+use paths::AbsPathBuf;
+use rustc_hash::FxHashMap;
+use semver::Version;
+use serde::Deserialize;
+
-
++use crate::{cfg_flag::CfgFlag, CargoConfig, CargoFeatures, CargoWorkspace, Package};
+
+#[derive(Debug, Default, Clone, PartialEq, Eq)]
+pub struct WorkspaceBuildScripts {
+ outputs: ArenaMap<Package, Option<BuildScriptOutput>>,
+ error: Option<String>,
+}
+
+#[derive(Debug, Clone, Default, PartialEq, Eq)]
+pub(crate) struct BuildScriptOutput {
+ /// List of config flags defined by this package's build script.
+ pub(crate) cfgs: Vec<CfgFlag>,
+ /// List of cargo-related environment variables with their value.
+ ///
+ /// If the package has a build script which defines environment variables,
+ /// they can also be found here.
+ pub(crate) envs: Vec<(String, String)>,
+ /// Directory where a build script might place its output.
+ pub(crate) out_dir: Option<AbsPathBuf>,
+ /// Path to the proc-macro library file if this package exposes proc-macros.
+ pub(crate) proc_macro_dylib_path: Option<AbsPathBuf>,
+}
+
+impl WorkspaceBuildScripts {
+ fn build_command(config: &CargoConfig) -> Command {
+ if let Some([program, args @ ..]) = config.run_build_script_command.as_deref() {
+ let mut cmd = Command::new(program);
+ cmd.args(args);
+ cmd.envs(&config.extra_env);
+ return cmd;
+ }
+
+ let mut cmd = Command::new(toolchain::cargo());
+ cmd.envs(&config.extra_env);
- if config.all_features {
- cmd.arg("--all-features");
- } else {
- if config.no_default_features {
- cmd.arg("--no-default-features");
+ cmd.args(&["check", "--quiet", "--workspace", "--message-format=json"]);
+
+ // --all-targets includes tests, benches and examples in addition to the
+ // default lib and bins. This is an independent concept from the --targets
+ // flag below.
+ cmd.arg("--all-targets");
+
+ if let Some(target) = &config.target {
+ cmd.args(&["--target", target]);
+ }
+
- if !config.features.is_empty() {
- cmd.arg("--features");
- cmd.arg(config.features.join(" "));
++ match &config.features {
++ CargoFeatures::All => {
++ cmd.arg("--all-features");
+ }
++ CargoFeatures::Selected { features, no_default_features } => {
++ if *no_default_features {
++ cmd.arg("--no-default-features");
++ }
++ if !features.is_empty() {
++ cmd.arg("--features");
++ cmd.arg(features.join(" "));
++ }
+ }
+ }
+
+ cmd
+ }
+
+ pub(crate) fn run(
+ config: &CargoConfig,
+ workspace: &CargoWorkspace,
+ progress: &dyn Fn(String),
+ toolchain: &Option<Version>,
+ ) -> io::Result<WorkspaceBuildScripts> {
+ const RUST_1_62: Version = Version::new(1, 62, 0);
+
+ match Self::run_(Self::build_command(config), config, workspace, progress) {
+ Ok(WorkspaceBuildScripts { error: Some(error), .. })
+ if toolchain.as_ref().map_or(false, |it| *it >= RUST_1_62) =>
+ {
+ // building build scripts failed, attempt to build with --keep-going so
+ // that we potentially get more build data
+ let mut cmd = Self::build_command(config);
+ cmd.args(&["-Z", "unstable-options", "--keep-going"]).env("RUSTC_BOOTSTRAP", "1");
+ let mut res = Self::run_(cmd, config, workspace, progress)?;
+ res.error = Some(error);
+ Ok(res)
+ }
+ res => res,
+ }
+ }
+
+ fn run_(
+ mut cmd: Command,
+ config: &CargoConfig,
+ workspace: &CargoWorkspace,
+ progress: &dyn Fn(String),
+ ) -> io::Result<WorkspaceBuildScripts> {
+ if config.wrap_rustc_in_build_scripts {
+ // Setup RUSTC_WRAPPER to point to `rust-analyzer` binary itself. We use
+ // that to compile only proc macros and build scripts during the initial
+ // `cargo check`.
+ let myself = std::env::current_exe()?;
+ cmd.env("RUSTC_WRAPPER", myself);
+ cmd.env("RA_RUSTC_WRAPPER", "1");
+ }
+
+ cmd.current_dir(workspace.workspace_root());
+
+ let mut res = WorkspaceBuildScripts::default();
+ let outputs = &mut res.outputs;
+ // NB: Cargo.toml could have been modified between `cargo metadata` and
+ // `cargo check`. We shouldn't assume that package ids we see here are
+ // exactly those from `config`.
+ let mut by_id: FxHashMap<String, Package> = FxHashMap::default();
+ for package in workspace.packages() {
+ outputs.insert(package, None);
+ by_id.insert(workspace[package].id.clone(), package);
+ }
+
+ let errors = RefCell::new(String::new());
+ let push_err = |err: &str| {
+ let mut e = errors.borrow_mut();
+ e.push_str(err);
+ e.push('\n');
+ };
+
+ tracing::info!("Running build scripts: {:?}", cmd);
+ let output = stdx::process::spawn_with_streaming_output(
+ cmd,
+ &mut |line| {
+ // Copy-pasted from existing cargo_metadata. It seems like we
+ // should be using serde_stacker here?
+ let mut deserializer = serde_json::Deserializer::from_str(line);
+ deserializer.disable_recursion_limit();
+ let message = Message::deserialize(&mut deserializer)
+ .unwrap_or_else(|_| Message::TextLine(line.to_string()));
+
+ match message {
+ Message::BuildScriptExecuted(message) => {
+ let package = match by_id.get(&message.package_id.repr) {
+ Some(&it) => it,
+ None => return,
+ };
+ let cfgs = {
+ let mut acc = Vec::new();
+ for cfg in message.cfgs {
+ match cfg.parse::<CfgFlag>() {
+ Ok(it) => acc.push(it),
+ Err(err) => {
+ push_err(&format!(
+ "invalid cfg from cargo-metadata: {}",
+ err
+ ));
+ return;
+ }
+ };
+ }
+ acc
+ };
+ // cargo_metadata crate returns default (empty) path for
+ // older cargos, which is not absolute, so work around that.
+ let out_dir = message.out_dir.into_os_string();
+ if !out_dir.is_empty() {
+ let data = outputs[package].get_or_insert_with(Default::default);
+ data.out_dir = Some(AbsPathBuf::assert(PathBuf::from(out_dir)));
+ data.cfgs = cfgs;
+ }
+ if !message.env.is_empty() {
+ outputs[package].get_or_insert_with(Default::default).envs =
+ message.env;
+ }
+ }
+ Message::CompilerArtifact(message) => {
+ let package = match by_id.get(&message.package_id.repr) {
+ Some(it) => *it,
+ None => return,
+ };
+
+ progress(format!("metadata {}", message.target.name));
+
+ if message.target.kind.iter().any(|k| k == "proc-macro") {
+ // Skip rmeta file
+ if let Some(filename) =
+ message.filenames.iter().find(|name| is_dylib(name))
+ {
+ let filename = AbsPathBuf::assert(PathBuf::from(&filename));
+ outputs[package]
+ .get_or_insert_with(Default::default)
+ .proc_macro_dylib_path = Some(filename);
+ }
+ }
+ }
+ Message::CompilerMessage(message) => {
+ progress(message.target.name);
+
+ if let Some(diag) = message.message.rendered.as_deref() {
+ push_err(diag);
+ }
+ }
+ Message::BuildFinished(_) => {}
+ Message::TextLine(_) => {}
+ _ => {}
+ }
+ },
+ &mut |line| {
+ push_err(line);
+ },
+ )?;
+
+ for package in workspace.packages() {
+ if let Some(package_build_data) = &mut outputs[package] {
+ tracing::info!(
+ "{}: {:?}",
+ workspace[package].manifest.parent().display(),
+ package_build_data,
+ );
+ // inject_cargo_env(package, package_build_data);
+ if let Some(out_dir) = &package_build_data.out_dir {
+ // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!()
+ if let Some(out_dir) = out_dir.as_os_str().to_str().map(|s| s.to_owned()) {
+ package_build_data.envs.push(("OUT_DIR".to_string(), out_dir));
+ }
+ }
+ }
+ }
+
+ let mut errors = errors.into_inner();
+ if !output.status.success() {
+ if errors.is_empty() {
+ errors = "cargo check failed".to_string();
+ }
+ res.error = Some(errors);
+ }
+
+ Ok(res)
+ }
+
+ pub fn error(&self) -> Option<&str> {
+ self.error.as_deref()
+ }
+
+ pub(crate) fn get_output(&self, idx: Package) -> Option<&BuildScriptOutput> {
+ self.outputs.get(idx)?.as_ref()
+ }
+}
+
+// FIXME: File a better way to know if it is a dylib.
+fn is_dylib(path: &Utf8Path) -> bool {
+ match path.extension().map(|e| e.to_string().to_lowercase()) {
+ None => false,
+ Some(ext) => matches!(ext.as_str(), "dll" | "dylib" | "so"),
+ }
+}
--- /dev/null
- #[derive(Default, Clone, Debug, PartialEq, Eq)]
- pub struct CargoConfig {
- /// Do not activate the `default` feature.
- pub no_default_features: bool,
+//! See [`CargoWorkspace`].
+
+use std::iter;
+use std::path::PathBuf;
+use std::str::from_utf8;
+use std::{ops, process::Command};
+
+use anyhow::{Context, Result};
+use base_db::Edition;
+use cargo_metadata::{CargoOpt, MetadataCommand};
+use la_arena::{Arena, Idx};
+use paths::{AbsPath, AbsPathBuf};
+use rustc_hash::FxHashMap;
+use serde::Deserialize;
+use serde_json::from_value;
+
+use crate::CfgOverrides;
+use crate::{utf8_stdout, ManifestPath};
+
+/// [`CargoWorkspace`] represents the logical structure of, well, a Cargo
+/// workspace. It pretty closely mirrors `cargo metadata` output.
+///
+/// Note that internally, rust-analyzer uses a different structure:
+/// `CrateGraph`. `CrateGraph` is lower-level: it knows only about the crates,
+/// while this knows about `Packages` & `Targets`: purely cargo-related
+/// concepts.
+///
+/// We use absolute paths here, `cargo metadata` guarantees to always produce
+/// abs paths.
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct CargoWorkspace {
+ packages: Arena<PackageData>,
+ targets: Arena<TargetData>,
+ workspace_root: AbsPathBuf,
+}
+
+impl ops::Index<Package> for CargoWorkspace {
+ type Output = PackageData;
+ fn index(&self, index: Package) -> &PackageData {
+ &self.packages[index]
+ }
+}
+
+impl ops::Index<Target> for CargoWorkspace {
+ type Output = TargetData;
+ fn index(&self, index: Target) -> &TargetData {
+ &self.targets[index]
+ }
+}
+
+/// Describes how to set the rustc source directory.
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum RustcSource {
+ /// Explicit path for the rustc source directory.
+ Path(AbsPathBuf),
+ /// Try to automatically detect where the rustc source directory is.
+ Discover,
+}
+
+/// Crates to disable `#[cfg(test)]` on.
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum UnsetTestCrates {
+ None,
+ Only(Vec<String>),
+ All,
+}
+
+impl Default for UnsetTestCrates {
+ fn default() -> Self {
+ Self::None
+ }
+}
+
- /// Activate all available features
- pub all_features: bool,
++#[derive(Clone, Debug, PartialEq, Eq)]
++pub enum CargoFeatures {
++ All,
++ Selected {
++ /// List of features to activate.
++ features: Vec<String>,
++ /// Do not activate the `default` feature.
++ no_default_features: bool,
++ },
++}
+
- /// This will be ignored if `cargo_all_features` is true.
- pub features: Vec<String>,
-
++impl Default for CargoFeatures {
++ fn default() -> Self {
++ CargoFeatures::Selected { features: vec![], no_default_features: false }
++ }
++}
+
++#[derive(Default, Clone, Debug, PartialEq, Eq)]
++pub struct CargoConfig {
+ /// List of features to activate.
-
- /// Don't load sysroot crates (`std`, `core` & friends). Might be useful
- /// when debugging isolated issues.
- pub no_sysroot: bool,
-
++ pub features: CargoFeatures,
+ /// rustc target
+ pub target: Option<String>,
-
++ /// Sysroot loading behavior
++ pub sysroot: Option<RustcSource>,
+ /// rustc private crate source
+ pub rustc_source: Option<RustcSource>,
-
+ /// crates to disable `#[cfg(test)]` on
+ pub unset_test_crates: UnsetTestCrates,
-
++ /// Invoke `cargo check` through the RUSTC_WRAPPER.
+ pub wrap_rustc_in_build_scripts: bool,
-
++ /// The command to run instead of `cargo check` for building build scripts.
+ pub run_build_script_command: Option<Vec<String>>,
- // Whether this package is a member of the workspace
++ /// Extra env vars to set when invoking the cargo command
+ pub extra_env: FxHashMap<String, String>,
+}
+
+impl CargoConfig {
+ pub fn cfg_overrides(&self) -> CfgOverrides {
+ match &self.unset_test_crates {
+ UnsetTestCrates::None => CfgOverrides::Selective(iter::empty().collect()),
+ UnsetTestCrates::Only(unset_test_crates) => CfgOverrides::Selective(
+ unset_test_crates
+ .iter()
+ .cloned()
+ .zip(iter::repeat_with(|| {
+ cfg::CfgDiff::new(Vec::new(), vec![cfg::CfgAtom::Flag("test".into())])
+ .unwrap()
+ }))
+ .collect(),
+ ),
+ UnsetTestCrates::All => CfgOverrides::Wildcard(
+ cfg::CfgDiff::new(Vec::new(), vec![cfg::CfgAtom::Flag("test".into())]).unwrap(),
+ ),
+ }
+ }
+}
+
+pub type Package = Idx<PackageData>;
+
+pub type Target = Idx<TargetData>;
+
+/// Information associated with a cargo crate
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct PackageData {
+ /// Version given in the `Cargo.toml`
+ pub version: semver::Version,
+ /// Name as given in the `Cargo.toml`
+ pub name: String,
+ /// Repository as given in the `Cargo.toml`
+ pub repository: Option<String>,
+ /// Path containing the `Cargo.toml`
+ pub manifest: ManifestPath,
+ /// Targets provided by the crate (lib, bin, example, test, ...)
+ pub targets: Vec<Target>,
+ /// Does this package come from the local filesystem (and is editable)?
+ pub is_local: bool,
- // Deserialise helper for the cargo metadata
++ /// Whether this package is a member of the workspace
+ pub is_member: bool,
+ /// List of packages this package depends on
+ pub dependencies: Vec<PackageDependency>,
+ /// Rust edition for this package
+ pub edition: Edition,
+ /// Features provided by the crate, mapped to the features required by that feature.
+ pub features: FxHashMap<String, Vec<String>>,
+ /// List of features enabled on this package
+ pub active_features: Vec<String>,
+ /// String representation of package id
+ pub id: String,
+ /// The contents of [package.metadata.rust-analyzer]
+ pub metadata: RustAnalyzerPackageMetaData,
+}
+
+#[derive(Deserialize, Default, Debug, Clone, Eq, PartialEq)]
+pub struct RustAnalyzerPackageMetaData {
+ pub rustc_private: bool,
+}
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct PackageDependency {
+ pub pkg: Package,
+ pub name: String,
+ pub kind: DepKind,
+}
+
+#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord)]
+pub enum DepKind {
+ /// Available to the library, binary, and dev targets in the package (but not the build script).
+ Normal,
+ /// Available only to test and bench targets (and the library target, when built with `cfg(test)`).
+ Dev,
+ /// Available only to the build script target.
+ Build,
+}
+
+impl DepKind {
+ fn iter(list: &[cargo_metadata::DepKindInfo]) -> impl Iterator<Item = Self> + '_ {
+ let mut dep_kinds = Vec::new();
+ if list.is_empty() {
+ dep_kinds.push(Self::Normal);
+ }
+ for info in list {
+ let kind = match info.kind {
+ cargo_metadata::DependencyKind::Normal => Self::Normal,
+ cargo_metadata::DependencyKind::Development => Self::Dev,
+ cargo_metadata::DependencyKind::Build => Self::Build,
+ cargo_metadata::DependencyKind::Unknown => continue,
+ };
+ dep_kinds.push(kind);
+ }
+ dep_kinds.sort_unstable();
+ dep_kinds.dedup();
+ dep_kinds.into_iter()
+ }
+}
+
+/// Information associated with a package's target
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct TargetData {
+ /// Package that provided this target
+ pub package: Package,
+ /// Name as given in the `Cargo.toml` or generated from the file name
+ pub name: String,
+ /// Path to the main source file of the target
+ pub root: AbsPathBuf,
+ /// Kind of target
+ pub kind: TargetKind,
+ /// Is this target a proc-macro
+ pub is_proc_macro: bool,
+ /// Required features of the target without which it won't build
+ pub required_features: Vec<String>,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum TargetKind {
+ Bin,
+ /// Any kind of Cargo lib crate-type (dylib, rlib, proc-macro, ...).
+ Lib,
+ Example,
+ Test,
+ Bench,
+ BuildScript,
+ Other,
+}
+
+impl TargetKind {
+ fn new(kinds: &[String]) -> TargetKind {
+ for kind in kinds {
+ return match kind.as_str() {
+ "bin" => TargetKind::Bin,
+ "test" => TargetKind::Test,
+ "bench" => TargetKind::Bench,
+ "example" => TargetKind::Example,
+ "custom-build" => TargetKind::BuildScript,
+ "proc-macro" => TargetKind::Lib,
+ _ if kind.contains("lib") => TargetKind::Lib,
+ _ => continue,
+ };
+ }
+ TargetKind::Other
+ }
+}
+
++// Deserialize helper for the cargo metadata
+#[derive(Deserialize, Default)]
- .or_else(|| cargo_config_build_target(cargo_toml, config))
- .or_else(|| rustc_discover_host_triple(cargo_toml, config));
+struct PackageMetadata {
+ #[serde(rename = "rust-analyzer")]
+ rust_analyzer: Option<RustAnalyzerPackageMetaData>,
+}
+
+impl CargoWorkspace {
+ pub fn fetch_metadata(
+ cargo_toml: &ManifestPath,
+ current_dir: &AbsPath,
+ config: &CargoConfig,
+ progress: &dyn Fn(String),
+ ) -> Result<cargo_metadata::Metadata> {
+ let target = config
+ .target
+ .clone()
- if config.all_features {
- meta.features(CargoOpt::AllFeatures);
- } else {
- if config.no_default_features {
- // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures`
- // https://github.com/oli-obk/cargo_metadata/issues/79
- meta.features(CargoOpt::NoDefaultFeatures);
++ .or_else(|| cargo_config_build_target(cargo_toml, &config.extra_env))
++ .or_else(|| rustc_discover_host_triple(cargo_toml, &config.extra_env));
+
+ let mut meta = MetadataCommand::new();
+ meta.cargo_path(toolchain::cargo());
+ meta.manifest_path(cargo_toml.to_path_buf());
- if !config.features.is_empty() {
- meta.features(CargoOpt::SomeFeatures(config.features.clone()));
++ match &config.features {
++ CargoFeatures::All => {
++ meta.features(CargoOpt::AllFeatures);
+ }
- fn exec_with_env(
- command: &cargo_metadata::MetadataCommand,
- extra_env: &FxHashMap<String, String>,
- ) -> Result<cargo_metadata::Metadata, cargo_metadata::Error> {
- let mut command = command.cargo_command();
- command.envs(extra_env);
++ CargoFeatures::Selected { features, no_default_features } => {
++ if *no_default_features {
++ // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures`
++ // https://github.com/oli-obk/cargo_metadata/issues/79
++ meta.features(CargoOpt::NoDefaultFeatures);
++ }
++ if !features.is_empty() {
++ meta.features(CargoOpt::SomeFeatures(features.clone()));
++ }
+ }
+ }
+ meta.current_dir(current_dir.as_os_str());
+
+ if let Some(target) = target {
+ meta.other_options(vec![String::from("--filter-platform"), target]);
+ }
+
+ // FIXME: Fetching metadata is a slow process, as it might require
+ // calling crates.io. We should be reporting progress here, but it's
+ // unclear whether cargo itself supports it.
+ progress("metadata".to_string());
+
- }
-
- let meta = exec_with_env(&meta, &config.extra_env)
- .with_context(|| format!("Failed to run `{:?}`", meta.cargo_command()))?;
-
- Ok(meta)
++ (|| -> Result<cargo_metadata::Metadata, cargo_metadata::Error> {
++ let mut command = meta.cargo_command();
++ command.envs(&config.extra_env);
+ let output = command.output()?;
+ if !output.status.success() {
+ return Err(cargo_metadata::Error::CargoMetadata {
+ stderr: String::from_utf8(output.stderr)?,
+ });
+ }
+ let stdout = from_utf8(&output.stdout)?
+ .lines()
+ .find(|line| line.starts_with('{'))
+ .ok_or(cargo_metadata::Error::NoJson)?;
+ cargo_metadata::MetadataCommand::parse(stdout)
- let source = match pkg_by_id.get(&node.id) {
- Some(&src) => src,
- // FIXME: replace this and a similar branch below with `.unwrap`, once
- // https://github.com/rust-lang/cargo/issues/7841
- // is fixed and hits stable (around 1.43-is probably?).
- None => {
- tracing::error!("Node id do not match in cargo metadata, ignoring {}", node.id);
- continue;
- }
- };
++ })()
++ .with_context(|| format!("Failed to run `{:?}`", meta.cargo_command()))
+ }
+
+ pub fn new(mut meta: cargo_metadata::Metadata) -> CargoWorkspace {
+ let mut pkg_by_id = FxHashMap::default();
+ let mut packages = Arena::default();
+ let mut targets = Arena::default();
+
+ let ws_members = &meta.workspace_members;
+
+ meta.packages.sort_by(|a, b| a.id.cmp(&b.id));
+ for meta_pkg in &meta.packages {
+ let cargo_metadata::Package {
+ id,
+ edition,
+ name,
+ manifest_path,
+ version,
+ metadata,
+ repository,
+ ..
+ } = meta_pkg;
+ let meta = from_value::<PackageMetadata>(metadata.clone()).unwrap_or_default();
+ let edition = match edition {
+ cargo_metadata::Edition::E2015 => Edition::Edition2015,
+ cargo_metadata::Edition::E2018 => Edition::Edition2018,
+ cargo_metadata::Edition::E2021 => Edition::Edition2021,
+ _ => {
+ tracing::error!("Unsupported edition `{:?}`", edition);
+ Edition::CURRENT
+ }
+ };
+ // We treat packages without source as "local" packages. That includes all members of
+ // the current workspace, as well as any path dependency outside the workspace.
+ let is_local = meta_pkg.source.is_none();
+ let is_member = ws_members.contains(id);
+
+ let pkg = packages.alloc(PackageData {
+ id: id.repr.clone(),
+ name: name.clone(),
+ version: version.clone(),
+ manifest: AbsPathBuf::assert(PathBuf::from(&manifest_path)).try_into().unwrap(),
+ targets: Vec::new(),
+ is_local,
+ is_member,
+ edition,
+ repository: repository.clone(),
+ dependencies: Vec::new(),
+ features: meta_pkg.features.clone().into_iter().collect(),
+ active_features: Vec::new(),
+ metadata: meta.rust_analyzer.unwrap_or_default(),
+ });
+ let pkg_data = &mut packages[pkg];
+ pkg_by_id.insert(id, pkg);
+ for meta_tgt in &meta_pkg.targets {
+ let is_proc_macro = meta_tgt.kind.as_slice() == ["proc-macro"];
+ let tgt = targets.alloc(TargetData {
+ package: pkg,
+ name: meta_tgt.name.clone(),
+ root: AbsPathBuf::assert(PathBuf::from(&meta_tgt.src_path)),
+ kind: TargetKind::new(meta_tgt.kind.as_slice()),
+ is_proc_macro,
+ required_features: meta_tgt.required_features.clone(),
+ });
+ pkg_data.targets.push(tgt);
+ }
+ }
+ let resolve = meta.resolve.expect("metadata executed with deps");
+ for mut node in resolve.nodes {
- for (dep_node, kind) in node
++ let &source = pkg_by_id.get(&node.id).unwrap();
+ node.deps.sort_by(|a, b| a.pkg.cmp(&b.pkg));
- .flat_map(|dep| DepKind::iter(&dep.dep_kinds).map(move |kind| (dep, kind)))
- {
- let pkg = match pkg_by_id.get(&dep_node.pkg) {
- Some(&pkg) => pkg,
- None => {
- tracing::error!(
- "Dep node id do not match in cargo metadata, ignoring {}",
- dep_node.pkg
- );
- continue;
- }
- };
++ let dependencies = node
+ .deps
+ .iter()
- if &self[dep.pkg].manifest == manifest_path {
- return Some(self[pkg].manifest.clone());
- }
- None
++ .flat_map(|dep| DepKind::iter(&dep.dep_kinds).map(move |kind| (dep, kind)));
++ for (dep_node, kind) in dependencies {
++ let &pkg = pkg_by_id.get(&dep_node.pkg).unwrap();
+ let dep = PackageDependency { name: dep_node.name.clone(), pkg, kind };
+ packages[source].dependencies.push(dep);
+ }
+ packages[source].active_features.extend(node.features);
+ }
+
+ let workspace_root =
+ AbsPathBuf::assert(PathBuf::from(meta.workspace_root.into_os_string()));
+
+ CargoWorkspace { packages, targets, workspace_root }
+ }
+
+ pub fn packages<'a>(&'a self) -> impl Iterator<Item = Package> + ExactSizeIterator + 'a {
+ self.packages.iter().map(|(id, _pkg)| id)
+ }
+
+ pub fn target_by_root(&self, root: &AbsPath) -> Option<Target> {
+ self.packages()
+ .filter(|&pkg| self[pkg].is_member)
+ .find_map(|pkg| self[pkg].targets.iter().find(|&&it| &self[it].root == root))
+ .copied()
+ }
+
+ pub fn workspace_root(&self) -> &AbsPath {
+ &self.workspace_root
+ }
+
+ pub fn package_flag(&self, package: &PackageData) -> String {
+ if self.is_unique(&*package.name) {
+ package.name.clone()
+ } else {
+ format!("{}:{}", package.name, package.version)
+ }
+ }
+
+ pub fn parent_manifests(&self, manifest_path: &ManifestPath) -> Option<Vec<ManifestPath>> {
+ let mut found = false;
+ let parent_manifests = self
+ .packages()
+ .filter_map(|pkg| {
+ if !found && &self[pkg].manifest == manifest_path {
+ found = true
+ }
+ self[pkg].dependencies.iter().find_map(|dep| {
- fn rustc_discover_host_triple(cargo_toml: &ManifestPath, config: &CargoConfig) -> Option<String> {
++ (&self[dep.pkg].manifest == manifest_path).then(|| self[pkg].manifest.clone())
+ })
+ })
+ .collect::<Vec<ManifestPath>>();
+
+ // some packages has this pkg as dep. return their manifests
+ if parent_manifests.len() > 0 {
+ return Some(parent_manifests);
+ }
+
+ // this pkg is inside this cargo workspace, fallback to workspace root
+ if found {
+ return Some(vec![
+ ManifestPath::try_from(self.workspace_root().join("Cargo.toml")).ok()?
+ ]);
+ }
+
+ // not in this workspace
+ None
+ }
+
+ fn is_unique(&self, name: &str) -> bool {
+ self.packages.iter().filter(|(_, v)| v.name == name).count() == 1
+ }
+}
+
- rustc.envs(&config.extra_env);
++fn rustc_discover_host_triple(
++ cargo_toml: &ManifestPath,
++ extra_env: &FxHashMap<String, String>,
++) -> Option<String> {
+ let mut rustc = Command::new(toolchain::rustc());
- fn cargo_config_build_target(cargo_toml: &ManifestPath, config: &CargoConfig) -> Option<String> {
++ rustc.envs(extra_env);
+ rustc.current_dir(cargo_toml.parent()).arg("-vV");
+ tracing::debug!("Discovering host platform by {:?}", rustc);
+ match utf8_stdout(rustc) {
+ Ok(stdout) => {
+ let field = "host: ";
+ let target = stdout.lines().find_map(|l| l.strip_prefix(field));
+ if let Some(target) = target {
+ Some(target.to_string())
+ } else {
+ // If we fail to resolve the host platform, it's not the end of the world.
+ tracing::info!("rustc -vV did not report host platform, got:\n{}", stdout);
+ None
+ }
+ }
+ Err(e) => {
+ tracing::warn!("Failed to discover host platform: {}", e);
+ None
+ }
+ }
+}
+
- cargo_config.envs(&config.extra_env);
++fn cargo_config_build_target(
++ cargo_toml: &ManifestPath,
++ extra_env: &FxHashMap<String, String>,
++) -> Option<String> {
+ let mut cargo_config = Command::new(toolchain::cargo());
++ cargo_config.envs(extra_env);
+ cargo_config
+ .current_dir(cargo_toml.parent())
+ .args(&["-Z", "unstable-options", "config", "get", "build.target"])
+ .env("RUSTC_BOOTSTRAP", "1");
+ // if successful we receive `build.target = "target-triple"`
+ tracing::debug!("Discovering cargo config target by {:?}", cargo_config);
+ match utf8_stdout(cargo_config) {
+ Ok(stdout) => stdout
+ .strip_prefix("build.target = \"")
+ .and_then(|stdout| stdout.strip_suffix('"'))
+ .map(ToOwned::to_owned),
+ Err(_) => None,
+ }
+}
--- /dev/null
- CargoConfig, CargoWorkspace, Package, PackageData, PackageDependency, RustcSource, Target,
- TargetData, TargetKind, UnsetTestCrates,
+//! In rust-analyzer, we maintain a strict separation between pure abstract
+//! semantic project model and a concrete model of a particular build system.
+//!
+//! Pure model is represented by the [`base_db::CrateGraph`] from another crate.
+//!
+//! In this crate, we are concerned with "real world" project models.
+//!
+//! Specifically, here we have a representation for a Cargo project
+//! ([`CargoWorkspace`]) and for manually specified layout ([`ProjectJson`]).
+//!
+//! Roughly, the things we do here are:
+//!
+//! * Project discovery (where's the relevant Cargo.toml for the current dir).
+//! * Custom build steps (`build.rs` code generation and compilation of
+//! procedural macros).
+//! * Lowering of concrete model to a [`base_db::CrateGraph`]
+
+#![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)]
+
+mod manifest_path;
+mod cargo_workspace;
+mod cfg_flag;
+mod project_json;
+mod sysroot;
+mod workspace;
+mod rustc_cfg;
+mod build_scripts;
+
+#[cfg(test)]
+mod tests;
+
+use std::{
+ fs::{self, read_dir, ReadDir},
+ io,
+ process::Command,
+};
+
+use anyhow::{bail, format_err, Context, Result};
+use paths::{AbsPath, AbsPathBuf};
+use rustc_hash::FxHashSet;
+
+pub use crate::{
+ build_scripts::WorkspaceBuildScripts,
+ cargo_workspace::{
++ CargoConfig, CargoFeatures, CargoWorkspace, Package, PackageData, PackageDependency,
++ RustcSource, Target, TargetData, TargetKind, UnsetTestCrates,
+ },
+ manifest_path::ManifestPath,
+ project_json::{ProjectJson, ProjectJsonData},
+ sysroot::Sysroot,
+ workspace::{CfgOverrides, PackageRoot, ProjectWorkspace},
+};
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
+pub enum ProjectManifest {
+ ProjectJson(ManifestPath),
+ CargoToml(ManifestPath),
+}
+
+impl ProjectManifest {
+ pub fn from_manifest_file(path: AbsPathBuf) -> Result<ProjectManifest> {
+ let path = ManifestPath::try_from(path)
+ .map_err(|path| format_err!("bad manifest path: {}", path.display()))?;
+ if path.file_name().unwrap_or_default() == "rust-project.json" {
+ return Ok(ProjectManifest::ProjectJson(path));
+ }
+ if path.file_name().unwrap_or_default() == "Cargo.toml" {
+ return Ok(ProjectManifest::CargoToml(path));
+ }
+ bail!("project root must point to Cargo.toml or rust-project.json: {}", path.display())
+ }
+
+ pub fn discover_single(path: &AbsPath) -> Result<ProjectManifest> {
+ let mut candidates = ProjectManifest::discover(path)?;
+ let res = match candidates.pop() {
+ None => bail!("no projects"),
+ Some(it) => it,
+ };
+
+ if !candidates.is_empty() {
+ bail!("more than one project")
+ }
+ Ok(res)
+ }
+
+ pub fn discover(path: &AbsPath) -> io::Result<Vec<ProjectManifest>> {
+ if let Some(project_json) = find_in_parent_dirs(path, "rust-project.json") {
+ return Ok(vec![ProjectManifest::ProjectJson(project_json)]);
+ }
+ return find_cargo_toml(path)
+ .map(|paths| paths.into_iter().map(ProjectManifest::CargoToml).collect());
+
+ fn find_cargo_toml(path: &AbsPath) -> io::Result<Vec<ManifestPath>> {
+ match find_in_parent_dirs(path, "Cargo.toml") {
+ Some(it) => Ok(vec![it]),
+ None => Ok(find_cargo_toml_in_child_dir(read_dir(path)?)),
+ }
+ }
+
+ fn find_in_parent_dirs(path: &AbsPath, target_file_name: &str) -> Option<ManifestPath> {
+ if path.file_name().unwrap_or_default() == target_file_name {
+ if let Ok(manifest) = ManifestPath::try_from(path.to_path_buf()) {
+ return Some(manifest);
+ }
+ }
+
+ let mut curr = Some(path);
+
+ while let Some(path) = curr {
+ let candidate = path.join(target_file_name);
+ if fs::metadata(&candidate).is_ok() {
+ if let Ok(manifest) = ManifestPath::try_from(candidate) {
+ return Some(manifest);
+ }
+ }
+ curr = path.parent();
+ }
+
+ None
+ }
+
+ fn find_cargo_toml_in_child_dir(entities: ReadDir) -> Vec<ManifestPath> {
+ // Only one level down to avoid cycles the easy way and stop a runaway scan with large projects
+ entities
+ .filter_map(Result::ok)
+ .map(|it| it.path().join("Cargo.toml"))
+ .filter(|it| it.exists())
+ .map(AbsPathBuf::assert)
+ .filter_map(|it| it.try_into().ok())
+ .collect()
+ }
+ }
+
+ pub fn discover_all(paths: &[AbsPathBuf]) -> Vec<ProjectManifest> {
+ let mut res = paths
+ .iter()
+ .filter_map(|it| ProjectManifest::discover(it.as_ref()).ok())
+ .flatten()
+ .collect::<FxHashSet<_>>()
+ .into_iter()
+ .collect::<Vec<_>>();
+ res.sort();
+ res
+ }
+}
+
+fn utf8_stdout(mut cmd: Command) -> Result<String> {
+ let output = cmd.output().with_context(|| format!("{:?} failed", cmd))?;
+ if !output.status.success() {
+ match String::from_utf8(output.stderr) {
+ Ok(stderr) if !stderr.is_empty() => {
+ bail!("{:?} failed, {}\nstderr:\n{}", cmd, output.status, stderr)
+ }
+ _ => bail!("{:?} failed, {}", cmd, output.status),
+ }
+ }
+ let stdout = String::from_utf8(output.stdout)?;
+ Ok(stdout.trim().to_string())
+}
--- /dev/null
+//! `rust-project.json` file format.
+//!
+//! This format is spiritually a serialization of [`base_db::CrateGraph`]. The
+//! idea here is that people who do not use Cargo, can instead teach their build
+//! system to generate `rust-project.json` which can be ingested by
+//! rust-analyzer.
+
+use std::path::PathBuf;
+
+use base_db::{CrateDisplayName, CrateId, CrateName, Dependency, Edition};
+use paths::{AbsPath, AbsPathBuf};
+use rustc_hash::FxHashMap;
+use serde::{de, Deserialize};
+
+use crate::cfg_flag::CfgFlag;
+
+/// Roots and crates that compose this Rust project.
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct ProjectJson {
+ /// e.g. `path/to/sysroot`
+ pub(crate) sysroot: Option<AbsPathBuf>,
+ /// e.g. `path/to/sysroot/lib/rustlib/src/rust`
+ pub(crate) sysroot_src: Option<AbsPathBuf>,
+ project_root: AbsPathBuf,
+ crates: Vec<Crate>,
+}
+
+/// A crate points to the root module of a crate and lists the dependencies of the crate. This is
+/// useful in creating the crate graph.
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct Crate {
+ pub(crate) display_name: Option<CrateDisplayName>,
+ pub(crate) root_module: AbsPathBuf,
+ pub(crate) edition: Edition,
+ pub(crate) version: Option<String>,
+ pub(crate) deps: Vec<Dependency>,
+ pub(crate) cfg: Vec<CfgFlag>,
+ pub(crate) target: Option<String>,
+ pub(crate) env: FxHashMap<String, String>,
+ pub(crate) proc_macro_dylib_path: Option<AbsPathBuf>,
+ pub(crate) is_workspace_member: bool,
+ pub(crate) include: Vec<AbsPathBuf>,
+ pub(crate) exclude: Vec<AbsPathBuf>,
+ pub(crate) is_proc_macro: bool,
+ pub(crate) repository: Option<String>,
+}
+
+impl ProjectJson {
+ /// Create a new ProjectJson instance.
+ ///
+ /// # Arguments
+ ///
+ /// * `base` - The path to the workspace root (i.e. the folder containing `rust-project.json`)
+ /// * `data` - The parsed contents of `rust-project.json`, or project json that's passed via
+ /// configuration.
+ pub fn new(base: &AbsPath, data: ProjectJsonData) -> ProjectJson {
+ ProjectJson {
+ sysroot: data.sysroot.map(|it| base.join(it)),
+ sysroot_src: data.sysroot_src.map(|it| base.join(it)),
+ project_root: base.to_path_buf(),
+ crates: data
+ .crates
+ .into_iter()
+ .map(|crate_data| {
+ let is_workspace_member = crate_data.is_workspace_member.unwrap_or_else(|| {
+ crate_data.root_module.is_relative()
+ && !crate_data.root_module.starts_with("..")
+ || crate_data.root_module.starts_with(base)
+ });
+ let root_module = base.join(crate_data.root_module).normalize();
+ let (include, exclude) = match crate_data.source {
+ Some(src) => {
+ let absolutize = |dirs: Vec<PathBuf>| {
+ dirs.into_iter()
+ .map(|it| base.join(it).normalize())
+ .collect::<Vec<_>>()
+ };
+ (absolutize(src.include_dirs), absolutize(src.exclude_dirs))
+ }
+ None => (vec![root_module.parent().unwrap().to_path_buf()], Vec::new()),
+ };
+
+ Crate {
+ display_name: crate_data
+ .display_name
+ .map(CrateDisplayName::from_canonical_name),
+ root_module,
+ edition: crate_data.edition.into(),
+ version: crate_data.version.as_ref().map(ToString::to_string),
+ deps: crate_data
+ .deps
+ .into_iter()
+ .map(|dep_data| {
+ Dependency::new(dep_data.name, CrateId(dep_data.krate as u32))
+ })
+ .collect::<Vec<_>>(),
+ cfg: crate_data.cfg,
+ target: crate_data.target,
+ env: crate_data.env,
+ proc_macro_dylib_path: crate_data
+ .proc_macro_dylib_path
+ .map(|it| base.join(it)),
+ is_workspace_member,
+ include,
+ exclude,
+ is_proc_macro: crate_data.is_proc_macro,
+ repository: crate_data.repository,
+ }
+ })
+ .collect::<Vec<_>>(),
+ }
+ }
++
+ /// Returns the number of crates in the project.
+ pub fn n_crates(&self) -> usize {
+ self.crates.len()
+ }
++
+ /// Returns an iterator over the crates in the project.
+ pub fn crates(&self) -> impl Iterator<Item = (CrateId, &Crate)> + '_ {
+ self.crates.iter().enumerate().map(|(idx, krate)| (CrateId(idx as u32), krate))
+ }
++
+ /// Returns the path to the project's root folder.
+ pub fn path(&self) -> &AbsPath {
+ &self.project_root
+ }
+}
+
+#[derive(Deserialize, Debug, Clone)]
+pub struct ProjectJsonData {
+ sysroot: Option<PathBuf>,
+ sysroot_src: Option<PathBuf>,
+ crates: Vec<CrateData>,
+}
+
+#[derive(Deserialize, Debug, Clone)]
+struct CrateData {
+ display_name: Option<String>,
+ root_module: PathBuf,
+ edition: EditionData,
+ #[serde(default)]
+ version: Option<semver::Version>,
+ deps: Vec<DepData>,
+ #[serde(default)]
+ cfg: Vec<CfgFlag>,
+ target: Option<String>,
+ #[serde(default)]
+ env: FxHashMap<String, String>,
+ proc_macro_dylib_path: Option<PathBuf>,
+ is_workspace_member: Option<bool>,
+ source: Option<CrateSource>,
+ #[serde(default)]
+ is_proc_macro: bool,
+ #[serde(default)]
+ repository: Option<String>,
+}
+
+#[derive(Deserialize, Debug, Clone)]
+#[serde(rename = "edition")]
+enum EditionData {
+ #[serde(rename = "2015")]
+ Edition2015,
+ #[serde(rename = "2018")]
+ Edition2018,
+ #[serde(rename = "2021")]
+ Edition2021,
+}
+
+impl From<EditionData> for Edition {
+ fn from(data: EditionData) -> Self {
+ match data {
+ EditionData::Edition2015 => Edition::Edition2015,
+ EditionData::Edition2018 => Edition::Edition2018,
+ EditionData::Edition2021 => Edition::Edition2021,
+ }
+ }
+}
+
+#[derive(Deserialize, Debug, Clone)]
+struct DepData {
+ /// Identifies a crate by position in the crates array.
+ #[serde(rename = "crate")]
+ krate: usize,
+ #[serde(deserialize_with = "deserialize_crate_name")]
+ name: CrateName,
+}
+
+#[derive(Deserialize, Debug, Clone)]
+struct CrateSource {
+ include_dirs: Vec<PathBuf>,
+ exclude_dirs: Vec<PathBuf>,
+}
+
+fn deserialize_crate_name<'de, D>(de: D) -> Result<CrateName, D::Error>
+where
+ D: de::Deserializer<'de>,
+{
+ let name = String::deserialize(de)?;
+ CrateName::new(&name).map_err(|err| de::Error::custom(format!("invalid crate name: {:?}", err)))
+}
--- /dev/null
- use crate::{cfg_flag::CfgFlag, utf8_stdout, CargoConfig, ManifestPath};
+//! Runs `rustc --print cfg` to get built-in cfg flags.
+
+use std::process::Command;
+
+use anyhow::Result;
++use rustc_hash::FxHashMap;
+
- config: &CargoConfig,
++use crate::{cfg_flag::CfgFlag, utf8_stdout, ManifestPath};
+
+pub(crate) fn get(
+ cargo_toml: Option<&ManifestPath>,
+ target: Option<&str>,
- match get_rust_cfgs(cargo_toml, target, config) {
++ extra_env: &FxHashMap<String, String>,
+) -> Vec<CfgFlag> {
+ let _p = profile::span("rustc_cfg::get");
+ let mut res = Vec::with_capacity(6 * 2 + 1);
+
+ // Some nightly-only cfgs, which are required for stdlib
+ res.push(CfgFlag::Atom("target_thread_local".into()));
+ for ty in ["8", "16", "32", "64", "cas", "ptr"] {
+ for key in ["target_has_atomic", "target_has_atomic_load_store"] {
+ res.push(CfgFlag::KeyValue { key: key.to_string(), value: ty.into() });
+ }
+ }
+
- config: &CargoConfig,
++ match get_rust_cfgs(cargo_toml, target, extra_env) {
+ Ok(rustc_cfgs) => {
+ tracing::debug!(
+ "rustc cfgs found: {:?}",
+ rustc_cfgs
+ .lines()
+ .map(|it| it.parse::<CfgFlag>().map(|it| it.to_string()))
+ .collect::<Vec<_>>()
+ );
+ res.extend(rustc_cfgs.lines().filter_map(|it| it.parse().ok()));
+ }
+ Err(e) => tracing::error!("failed to get rustc cfgs: {e:?}"),
+ }
+
+ res
+}
+
+fn get_rust_cfgs(
+ cargo_toml: Option<&ManifestPath>,
+ target: Option<&str>,
- cargo_config.envs(&config.extra_env);
++ extra_env: &FxHashMap<String, String>,
+) -> Result<String> {
+ if let Some(cargo_toml) = cargo_toml {
+ let mut cargo_config = Command::new(toolchain::cargo());
- cmd.envs(&config.extra_env);
++ cargo_config.envs(extra_env);
+ cargo_config
+ .current_dir(cargo_toml.parent())
+ .args(&["-Z", "unstable-options", "rustc", "--print", "cfg"])
+ .env("RUSTC_BOOTSTRAP", "1");
+ if let Some(target) = target {
+ cargo_config.args(&["--target", target]);
+ }
+ match utf8_stdout(cargo_config) {
+ Ok(it) => return Ok(it),
+ Err(e) => tracing::debug!("{e:?}: falling back to querying rustc for cfgs"),
+ }
+ }
+ // using unstable cargo features failed, fall back to using plain rustc
+ let mut cmd = Command::new(toolchain::rustc());
++ cmd.envs(extra_env);
+ cmd.args(&["--print", "cfg", "-O"]);
+ if let Some(target) = target {
+ cmd.args(&["--target", target]);
+ }
+ utf8_stdout(cmd)
+}
--- /dev/null
- use crate::{utf8_stdout, CargoConfig, ManifestPath};
+//! Loads "sysroot" crate.
+//!
+//! One confusing point here is that normally sysroot is a bunch of `.rlib`s,
+//! but we can't process `.rlib` and need source code instead. The source code
+//! is typically installed with `rustup component add rust-src` command.
+
+use std::{env, fs, iter, ops, path::PathBuf, process::Command};
+
+use anyhow::{format_err, Result};
+use la_arena::{Arena, Idx};
+use paths::{AbsPath, AbsPathBuf};
++use rustc_hash::FxHashMap;
+
- pub fn discover(dir: &AbsPath, config: &CargoConfig) -> Result<Sysroot> {
++use crate::{utf8_stdout, ManifestPath};
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct Sysroot {
+ root: AbsPathBuf,
+ src_root: AbsPathBuf,
+ crates: Arena<SysrootCrateData>,
+}
+
+pub(crate) type SysrootCrate = Idx<SysrootCrateData>;
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct SysrootCrateData {
+ pub name: String,
+ pub root: ManifestPath,
+ pub deps: Vec<SysrootCrate>,
+}
+
+impl ops::Index<SysrootCrate> for Sysroot {
+ type Output = SysrootCrateData;
+ fn index(&self, index: SysrootCrate) -> &SysrootCrateData {
+ &self.crates[index]
+ }
+}
+
+impl Sysroot {
+ /// Returns sysroot "root" directory, where `bin/`, `etc/`, `lib/`, `libexec/`
+ /// subfolder live, like:
+ /// `$HOME/.rustup/toolchains/nightly-2022-07-23-x86_64-unknown-linux-gnu`
+ pub fn root(&self) -> &AbsPath {
+ &self.root
+ }
+
+ /// Returns the sysroot "source" directory, where stdlib sources are located, like:
+ /// `$HOME/.rustup/toolchains/nightly-2022-07-23-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library`
+ pub fn src_root(&self) -> &AbsPath {
+ &self.src_root
+ }
+
+ pub fn public_deps(&self) -> impl Iterator<Item = (&'static str, SysrootCrate, bool)> + '_ {
+ // core is added as a dependency before std in order to
+ // mimic rustcs dependency order
+ ["core", "alloc", "std"]
+ .into_iter()
+ .zip(iter::repeat(true))
+ .chain(iter::once(("test", false)))
+ .filter_map(move |(name, prelude)| Some((name, self.by_name(name)?, prelude)))
+ }
+
+ pub fn proc_macro(&self) -> Option<SysrootCrate> {
+ self.by_name("proc_macro")
+ }
+
+ pub fn crates<'a>(&'a self) -> impl Iterator<Item = SysrootCrate> + ExactSizeIterator + 'a {
+ self.crates.iter().map(|(id, _data)| id)
+ }
++}
+
- let sysroot_dir = discover_sysroot_dir(dir, config)?;
- let sysroot_src_dir = discover_sysroot_src_dir(&sysroot_dir, dir, config)?;
++impl Sysroot {
++ pub fn discover(dir: &AbsPath, extra_env: &FxHashMap<String, String>) -> Result<Sysroot> {
+ tracing::debug!("Discovering sysroot for {}", dir.display());
- pub fn discover_rustc(cargo_toml: &ManifestPath, config: &CargoConfig) -> Option<ManifestPath> {
++ let sysroot_dir = discover_sysroot_dir(dir, extra_env)?;
++ let sysroot_src_dir =
++ discover_sysroot_src_dir_or_add_component(&sysroot_dir, dir, extra_env)?;
+ let res = Sysroot::load(sysroot_dir, sysroot_src_dir)?;
+ Ok(res)
+ }
+
- discover_sysroot_dir(current_dir, config)
++ pub fn discover_rustc(
++ cargo_toml: &ManifestPath,
++ extra_env: &FxHashMap<String, String>,
++ ) -> Option<ManifestPath> {
+ tracing::debug!("Discovering rustc source for {}", cargo_toml.display());
+ let current_dir = cargo_toml.parent();
- fn discover_sysroot_dir(current_dir: &AbsPath, config: &CargoConfig) -> Result<AbsPathBuf> {
++ discover_sysroot_dir(current_dir, extra_env)
+ .ok()
+ .and_then(|sysroot_dir| get_rustc_src(&sysroot_dir))
+ }
+
++ pub fn with_sysroot_dir(sysroot_dir: AbsPathBuf) -> Result<Sysroot> {
++ let sysroot_src_dir = discover_sysroot_src_dir(&sysroot_dir).ok_or_else(|| {
++ format_err!("can't load standard library from sysroot {}", sysroot_dir.display())
++ })?;
++ let res = Sysroot::load(sysroot_dir, sysroot_src_dir)?;
++ Ok(res)
++ }
++
+ pub fn load(sysroot_dir: AbsPathBuf, sysroot_src_dir: AbsPathBuf) -> Result<Sysroot> {
+ let mut sysroot =
+ Sysroot { root: sysroot_dir, src_root: sysroot_src_dir, crates: Arena::default() };
+
+ for path in SYSROOT_CRATES.trim().lines() {
+ let name = path.split('/').last().unwrap();
+ let root = [format!("{}/src/lib.rs", path), format!("lib{}/lib.rs", path)]
+ .into_iter()
+ .map(|it| sysroot.src_root.join(it))
+ .filter_map(|it| ManifestPath::try_from(it).ok())
+ .find(|it| fs::metadata(it).is_ok());
+
+ if let Some(root) = root {
+ sysroot.crates.alloc(SysrootCrateData {
+ name: name.into(),
+ root,
+ deps: Vec::new(),
+ });
+ }
+ }
+
+ if let Some(std) = sysroot.by_name("std") {
+ for dep in STD_DEPS.trim().lines() {
+ if let Some(dep) = sysroot.by_name(dep) {
+ sysroot.crates[std].deps.push(dep)
+ }
+ }
+ }
+
+ if let Some(alloc) = sysroot.by_name("alloc") {
+ if let Some(core) = sysroot.by_name("core") {
+ sysroot.crates[alloc].deps.push(core);
+ }
+ }
+
+ if let Some(proc_macro) = sysroot.by_name("proc_macro") {
+ if let Some(std) = sysroot.by_name("std") {
+ sysroot.crates[proc_macro].deps.push(std);
+ }
+ }
+
+ if sysroot.by_name("core").is_none() {
+ let var_note = if env::var_os("RUST_SRC_PATH").is_some() {
+ " (`RUST_SRC_PATH` might be incorrect, try unsetting it)"
+ } else {
+ ""
+ };
+ anyhow::bail!(
+ "could not find libcore in sysroot path `{}`{}",
+ sysroot.src_root.as_path().display(),
+ var_note,
+ );
+ }
+
+ Ok(sysroot)
+ }
+
+ fn by_name(&self, name: &str) -> Option<SysrootCrate> {
+ let (id, _data) = self.crates.iter().find(|(_id, data)| data.name == name)?;
+ Some(id)
+ }
+}
+
- rustc.envs(&config.extra_env);
++fn discover_sysroot_dir(
++ current_dir: &AbsPath,
++ extra_env: &FxHashMap<String, String>,
++) -> Result<AbsPathBuf> {
+ let mut rustc = Command::new(toolchain::rustc());
- fn discover_sysroot_src_dir(
- sysroot_path: &AbsPathBuf,
- current_dir: &AbsPath,
- config: &CargoConfig,
- ) -> Result<AbsPathBuf> {
++ rustc.envs(extra_env);
+ rustc.current_dir(current_dir).args(&["--print", "sysroot"]);
+ tracing::debug!("Discovering sysroot by {:?}", rustc);
+ let stdout = utf8_stdout(rustc)?;
+ Ok(AbsPathBuf::assert(PathBuf::from(stdout)))
+}
+
- let path = AbsPathBuf::try_from(path.as_str())
- .map_err(|path| format_err!("RUST_SRC_PATH must be absolute: {}", path.display()))?;
- let core = path.join("core");
- if fs::metadata(&core).is_ok() {
- tracing::debug!("Discovered sysroot by RUST_SRC_PATH: {}", path.display());
- return Ok(path);
++fn discover_sysroot_src_dir(sysroot_path: &AbsPathBuf) -> Option<AbsPathBuf> {
+ if let Ok(path) = env::var("RUST_SRC_PATH") {
- tracing::debug!("RUST_SRC_PATH is set, but is invalid (no core: {:?}), ignoring", core);
++ if let Ok(path) = AbsPathBuf::try_from(path.as_str()) {
++ let core = path.join("core");
++ if fs::metadata(&core).is_ok() {
++ tracing::debug!("Discovered sysroot by RUST_SRC_PATH: {}", path.display());
++ return Some(path);
++ }
++ tracing::debug!("RUST_SRC_PATH is set, but is invalid (no core: {:?}), ignoring", core);
++ } else {
++ tracing::debug!("RUST_SRC_PATH is set, but is invalid, ignoring");
+ }
- rustup.envs(&config.extra_env);
+ }
+
+ get_rust_src(sysroot_path)
++}
++fn discover_sysroot_src_dir_or_add_component(
++ sysroot_path: &AbsPathBuf,
++ current_dir: &AbsPath,
++ extra_env: &FxHashMap<String, String>,
++) -> Result<AbsPathBuf> {
++ discover_sysroot_src_dir(sysroot_path)
+ .or_else(|| {
+ let mut rustup = Command::new(toolchain::rustup());
++ rustup.envs(extra_env);
+ rustup.current_dir(current_dir).args(&["component", "add", "rust-src"]);
+ utf8_stdout(rustup).ok()?;
+ get_rust_src(sysroot_path)
+ })
+ .ok_or_else(|| {
+ format_err!(
+ "\
+can't load standard library from sysroot
+{}
+(discovered via `rustc --print sysroot`)
+try installing the Rust source the same way you installed rustc",
+ sysroot_path.display(),
+ )
+ })
+}
+
+fn get_rustc_src(sysroot_path: &AbsPath) -> Option<ManifestPath> {
+ let rustc_src = sysroot_path.join("lib/rustlib/rustc-src/rust/compiler/rustc/Cargo.toml");
+ let rustc_src = ManifestPath::try_from(rustc_src).ok()?;
+ tracing::debug!("Checking for rustc source code: {}", rustc_src.display());
+ if fs::metadata(&rustc_src).is_ok() {
+ Some(rustc_src)
+ } else {
+ None
+ }
+}
+
+fn get_rust_src(sysroot_path: &AbsPath) -> Option<AbsPathBuf> {
+ let rust_src = sysroot_path.join("lib/rustlib/src/rust/library");
+ tracing::debug!("Checking sysroot: {}", rust_src.display());
+ if fs::metadata(&rust_src).is_ok() {
+ Some(rust_src)
+ } else {
+ None
+ }
+}
+
+const SYSROOT_CRATES: &str = "
+alloc
+core
+panic_abort
+panic_unwind
+proc_macro
+profiler_builtins
+std
+stdarch/crates/std_detect
+term
+test
+unwind";
+
+const STD_DEPS: &str = "
+alloc
+core
+panic_abort
+panic_unwind
+profiler_builtins
+std_detect
+term
+test
+unwind";
--- /dev/null
- CargoConfig, CargoWorkspace, CfgOverrides, ProjectJson, ProjectJsonData, ProjectWorkspace,
- Sysroot, WorkspaceBuildScripts,
+use std::{
+ ops::Deref,
+ path::{Path, PathBuf},
+};
+
+use base_db::{CrateGraph, FileId};
+use cfg::{CfgAtom, CfgDiff};
+use expect_test::{expect, Expect};
+use paths::{AbsPath, AbsPathBuf};
+use serde::de::DeserializeOwned;
+
+use crate::{
- &CargoConfig::default(),
++ CargoWorkspace, CfgOverrides, ProjectJson, ProjectJsonData, ProjectWorkspace, Sysroot,
++ WorkspaceBuildScripts,
+};
+
+fn load_cargo(file: &str) -> CrateGraph {
+ load_cargo_with_overrides(file, CfgOverrides::default())
+}
+
+fn load_cargo_with_overrides(file: &str, cfg_overrides: CfgOverrides) -> CrateGraph {
+ let meta = get_test_json_file(file);
+ let cargo_workspace = CargoWorkspace::new(meta);
+ let project_workspace = ProjectWorkspace::Cargo {
+ cargo: cargo_workspace,
+ build_scripts: WorkspaceBuildScripts::default(),
+ sysroot: None,
+ rustc: None,
+ rustc_cfg: Vec::new(),
+ cfg_overrides,
+ toolchain: None,
+ };
+ to_crate_graph(project_workspace)
+}
+
+fn load_rust_project(file: &str) -> CrateGraph {
+ let data = get_test_json_file(file);
+ let project = rooted_project_json(data);
+ let sysroot = Some(get_fake_sysroot());
+ let project_workspace = ProjectWorkspace::Json { project, sysroot, rustc_cfg: Vec::new() };
+ to_crate_graph(project_workspace)
+}
+
+fn get_test_json_file<T: DeserializeOwned>(file: &str) -> T {
+ let file = get_test_path(file);
+ let data = std::fs::read_to_string(file).unwrap();
+ let mut json = data.parse::<serde_json::Value>().unwrap();
+ fixup_paths(&mut json);
+ return serde_json::from_value(json).unwrap();
+
+ fn fixup_paths(val: &mut serde_json::Value) {
+ match val {
+ serde_json::Value::String(s) => replace_root(s, true),
+ serde_json::Value::Array(vals) => vals.iter_mut().for_each(fixup_paths),
+ serde_json::Value::Object(kvals) => kvals.values_mut().for_each(fixup_paths),
+ serde_json::Value::Null | serde_json::Value::Bool(_) | serde_json::Value::Number(_) => {
+ }
+ }
+ }
+}
+
+fn replace_root(s: &mut String, direction: bool) {
+ if direction {
+ let root = if cfg!(windows) { r#"C:\\ROOT\"# } else { "/ROOT/" };
+ *s = s.replace("$ROOT$", root)
+ } else {
+ let root = if cfg!(windows) { r#"C:\\\\ROOT\\"# } else { "/ROOT/" };
+ *s = s.replace(root, "$ROOT$")
+ }
+}
+
+fn get_test_path(file: &str) -> PathBuf {
+ let base = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
+ base.join("test_data").join(file)
+}
+
+fn get_fake_sysroot() -> Sysroot {
+ let sysroot_path = get_test_path("fake-sysroot");
+ // there's no `libexec/` directory with a `proc-macro-srv` binary in that
+ // fake sysroot, so we give them both the same path:
+ let sysroot_dir = AbsPathBuf::assert(sysroot_path);
+ let sysroot_src_dir = sysroot_dir.clone();
+ Sysroot::load(sysroot_dir, sysroot_src_dir).unwrap()
+}
+
+fn rooted_project_json(data: ProjectJsonData) -> ProjectJson {
+ let mut root = "$ROOT$".to_string();
+ replace_root(&mut root, true);
+ let path = Path::new(&root);
+ let base = AbsPath::assert(path);
+ ProjectJson::new(base, data)
+}
+
+fn to_crate_graph(project_workspace: ProjectWorkspace) -> CrateGraph {
+ project_workspace.to_crate_graph(
+ &mut |_, _| Ok(Vec::new()),
+ &mut {
+ let mut counter = 0;
+ move |_path| {
+ counter += 1;
+ Some(FileId(counter))
+ }
+ },
++ &Default::default(),
+ )
+}
+
+fn check_crate_graph(crate_graph: CrateGraph, expect: Expect) {
+ let mut crate_graph = format!("{:#?}", crate_graph);
+ replace_root(&mut crate_graph, false);
+ expect.assert_eq(&crate_graph);
+}
+
+#[test]
+fn cargo_hello_world_project_model_with_wildcard_overrides() {
+ let cfg_overrides = CfgOverrides::Wildcard(
+ CfgDiff::new(Vec::new(), vec![CfgAtom::Flag("test".into())]).unwrap(),
+ );
+ let crate_graph = load_cargo_with_overrides("hello-world-metadata.json", cfg_overrides);
+ check_crate_graph(
+ crate_graph,
+ expect![[r#"
+ CrateGraph {
+ arena: {
+ CrateId(
+ 0,
+ ): CrateData {
+ root_file_id: FileId(
+ 1,
+ ),
+ edition: Edition2018,
+ version: Some(
+ "0.1.0",
+ ),
+ display_name: Some(
+ CrateDisplayName {
+ crate_name: CrateName(
+ "hello_world",
+ ),
+ canonical_name: "hello-world",
+ },
+ ),
+ cfg_options: CfgOptions(
+ [
+ "debug_assertions",
+ ],
+ ),
+ potential_cfg_options: CfgOptions(
+ [
+ "debug_assertions",
+ ],
+ ),
+ env: Env {
+ entries: {
+ "CARGO_PKG_LICENSE": "",
+ "CARGO_PKG_VERSION_MAJOR": "0",
+ "CARGO_MANIFEST_DIR": "$ROOT$hello-world",
+ "CARGO_PKG_VERSION": "0.1.0",
+ "CARGO_PKG_AUTHORS": "",
+ "CARGO_CRATE_NAME": "hello_world",
+ "CARGO_PKG_LICENSE_FILE": "",
+ "CARGO_PKG_HOMEPAGE": "",
+ "CARGO_PKG_DESCRIPTION": "",
+ "CARGO_PKG_NAME": "hello-world",
+ "CARGO_PKG_VERSION_PATCH": "0",
+ "CARGO": "cargo",
+ "CARGO_PKG_REPOSITORY": "",
+ "CARGO_PKG_VERSION_MINOR": "1",
+ "CARGO_PKG_VERSION_PRE": "",
+ },
+ },
+ dependencies: [
+ Dependency {
+ crate_id: CrateId(
+ 4,
+ ),
+ name: CrateName(
+ "libc",
+ ),
+ prelude: true,
+ },
+ ],
+ proc_macro: Err(
+ "crate has not (yet) been built",
+ ),
+ origin: CratesIo {
+ repo: None,
++ name: Some(
++ "hello-world",
++ ),
+ },
+ is_proc_macro: false,
+ },
+ CrateId(
+ 1,
+ ): CrateData {
+ root_file_id: FileId(
+ 2,
+ ),
+ edition: Edition2018,
+ version: Some(
+ "0.1.0",
+ ),
+ display_name: Some(
+ CrateDisplayName {
+ crate_name: CrateName(
+ "hello_world",
+ ),
+ canonical_name: "hello-world",
+ },
+ ),
+ cfg_options: CfgOptions(
+ [
+ "debug_assertions",
+ ],
+ ),
+ potential_cfg_options: CfgOptions(
+ [
+ "debug_assertions",
+ ],
+ ),
+ env: Env {
+ entries: {
+ "CARGO_PKG_LICENSE": "",
+ "CARGO_PKG_VERSION_MAJOR": "0",
+ "CARGO_MANIFEST_DIR": "$ROOT$hello-world",
+ "CARGO_PKG_VERSION": "0.1.0",
+ "CARGO_PKG_AUTHORS": "",
+ "CARGO_CRATE_NAME": "hello_world",
+ "CARGO_PKG_LICENSE_FILE": "",
+ "CARGO_PKG_HOMEPAGE": "",
+ "CARGO_PKG_DESCRIPTION": "",
+ "CARGO_PKG_NAME": "hello-world",
+ "CARGO_PKG_VERSION_PATCH": "0",
+ "CARGO": "cargo",
+ "CARGO_PKG_REPOSITORY": "",
+ "CARGO_PKG_VERSION_MINOR": "1",
+ "CARGO_PKG_VERSION_PRE": "",
+ },
+ },
+ dependencies: [
+ Dependency {
+ crate_id: CrateId(
+ 0,
+ ),
+ name: CrateName(
+ "hello_world",
+ ),
+ prelude: true,
+ },
+ Dependency {
+ crate_id: CrateId(
+ 4,
+ ),
+ name: CrateName(
+ "libc",
+ ),
+ prelude: true,
+ },
+ ],
+ proc_macro: Err(
+ "crate has not (yet) been built",
+ ),
+ origin: CratesIo {
+ repo: None,
++ name: Some(
++ "hello-world",
++ ),
+ },
+ is_proc_macro: false,
+ },
+ CrateId(
+ 2,
+ ): CrateData {
+ root_file_id: FileId(
+ 3,
+ ),
+ edition: Edition2018,
+ version: Some(
+ "0.1.0",
+ ),
+ display_name: Some(
+ CrateDisplayName {
+ crate_name: CrateName(
+ "an_example",
+ ),
+ canonical_name: "an-example",
+ },
+ ),
+ cfg_options: CfgOptions(
+ [
+ "debug_assertions",
+ ],
+ ),
+ potential_cfg_options: CfgOptions(
+ [
+ "debug_assertions",
+ ],
+ ),
+ env: Env {
+ entries: {
+ "CARGO_PKG_LICENSE": "",
+ "CARGO_PKG_VERSION_MAJOR": "0",
+ "CARGO_MANIFEST_DIR": "$ROOT$hello-world",
+ "CARGO_PKG_VERSION": "0.1.0",
+ "CARGO_PKG_AUTHORS": "",
+ "CARGO_CRATE_NAME": "hello_world",
+ "CARGO_PKG_LICENSE_FILE": "",
+ "CARGO_PKG_HOMEPAGE": "",
+ "CARGO_PKG_DESCRIPTION": "",
+ "CARGO_PKG_NAME": "hello-world",
+ "CARGO_PKG_VERSION_PATCH": "0",
+ "CARGO": "cargo",
+ "CARGO_PKG_REPOSITORY": "",
+ "CARGO_PKG_VERSION_MINOR": "1",
+ "CARGO_PKG_VERSION_PRE": "",
+ },
+ },
+ dependencies: [
+ Dependency {
+ crate_id: CrateId(
+ 0,
+ ),
+ name: CrateName(
+ "hello_world",
+ ),
+ prelude: true,
+ },
+ Dependency {
+ crate_id: CrateId(
+ 4,
+ ),
+ name: CrateName(
+ "libc",
+ ),
+ prelude: true,
+ },
+ ],
+ proc_macro: Err(
+ "crate has not (yet) been built",
+ ),
+ origin: CratesIo {
+ repo: None,
++ name: Some(
++ "hello-world",
++ ),
+ },
+ is_proc_macro: false,
+ },
+ CrateId(
+ 3,
+ ): CrateData {
+ root_file_id: FileId(
+ 4,
+ ),
+ edition: Edition2018,
+ version: Some(
+ "0.1.0",
+ ),
+ display_name: Some(
+ CrateDisplayName {
+ crate_name: CrateName(
+ "it",
+ ),
+ canonical_name: "it",
+ },
+ ),
+ cfg_options: CfgOptions(
+ [
+ "debug_assertions",
+ ],
+ ),
+ potential_cfg_options: CfgOptions(
+ [
+ "debug_assertions",
+ ],
+ ),
+ env: Env {
+ entries: {
+ "CARGO_PKG_LICENSE": "",
+ "CARGO_PKG_VERSION_MAJOR": "0",
+ "CARGO_MANIFEST_DIR": "$ROOT$hello-world",
+ "CARGO_PKG_VERSION": "0.1.0",
+ "CARGO_PKG_AUTHORS": "",
+ "CARGO_CRATE_NAME": "hello_world",
+ "CARGO_PKG_LICENSE_FILE": "",
+ "CARGO_PKG_HOMEPAGE": "",
+ "CARGO_PKG_DESCRIPTION": "",
+ "CARGO_PKG_NAME": "hello-world",
+ "CARGO_PKG_VERSION_PATCH": "0",
+ "CARGO": "cargo",
+ "CARGO_PKG_REPOSITORY": "",
+ "CARGO_PKG_VERSION_MINOR": "1",
+ "CARGO_PKG_VERSION_PRE": "",
+ },
+ },
+ dependencies: [
+ Dependency {
+ crate_id: CrateId(
+ 0,
+ ),
+ name: CrateName(
+ "hello_world",
+ ),
+ prelude: true,
+ },
+ Dependency {
+ crate_id: CrateId(
+ 4,
+ ),
+ name: CrateName(
+ "libc",
+ ),
+ prelude: true,
+ },
+ ],
+ proc_macro: Err(
+ "crate has not (yet) been built",
+ ),
+ origin: CratesIo {
+ repo: None,
++ name: Some(
++ "hello-world",
++ ),
+ },
+ is_proc_macro: false,
+ },
+ CrateId(
+ 4,
+ ): CrateData {
+ root_file_id: FileId(
+ 5,
+ ),
+ edition: Edition2015,
+ version: Some(
+ "0.2.98",
+ ),
+ display_name: Some(
+ CrateDisplayName {
+ crate_name: CrateName(
+ "libc",
+ ),
+ canonical_name: "libc",
+ },
+ ),
+ cfg_options: CfgOptions(
+ [
+ "debug_assertions",
+ "feature=default",
+ "feature=std",
+ ],
+ ),
+ potential_cfg_options: CfgOptions(
+ [
+ "debug_assertions",
+ "feature=align",
+ "feature=const-extern-fn",
+ "feature=default",
+ "feature=extra_traits",
+ "feature=rustc-dep-of-std",
+ "feature=std",
+ "feature=use_std",
+ ],
+ ),
+ env: Env {
+ entries: {
+ "CARGO_PKG_LICENSE": "",
+ "CARGO_PKG_VERSION_MAJOR": "0",
+ "CARGO_MANIFEST_DIR": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.98",
+ "CARGO_PKG_VERSION": "0.2.98",
+ "CARGO_PKG_AUTHORS": "",
+ "CARGO_CRATE_NAME": "libc",
+ "CARGO_PKG_LICENSE_FILE": "",
+ "CARGO_PKG_HOMEPAGE": "",
+ "CARGO_PKG_DESCRIPTION": "",
+ "CARGO_PKG_NAME": "libc",
+ "CARGO_PKG_VERSION_PATCH": "98",
+ "CARGO": "cargo",
+ "CARGO_PKG_REPOSITORY": "",
+ "CARGO_PKG_VERSION_MINOR": "2",
+ "CARGO_PKG_VERSION_PRE": "",
+ },
+ },
+ dependencies: [],
+ proc_macro: Err(
+ "crate has not (yet) been built",
+ ),
+ origin: CratesIo {
+ repo: Some(
+ "https://github.com/rust-lang/libc",
+ ),
++ name: Some(
++ "libc",
++ ),
+ },
+ is_proc_macro: false,
+ },
+ },
+ }"#]],
+ )
+}
+
+#[test]
+fn cargo_hello_world_project_model_with_selective_overrides() {
+ let cfg_overrides = {
+ CfgOverrides::Selective(
+ std::iter::once((
+ "libc".to_owned(),
+ CfgDiff::new(Vec::new(), vec![CfgAtom::Flag("test".into())]).unwrap(),
+ ))
+ .collect(),
+ )
+ };
+ let crate_graph = load_cargo_with_overrides("hello-world-metadata.json", cfg_overrides);
+ check_crate_graph(
+ crate_graph,
+ expect![[r#"
+ CrateGraph {
+ arena: {
+ CrateId(
+ 0,
+ ): CrateData {
+ root_file_id: FileId(
+ 1,
+ ),
+ edition: Edition2018,
+ version: Some(
+ "0.1.0",
+ ),
+ display_name: Some(
+ CrateDisplayName {
+ crate_name: CrateName(
+ "hello_world",
+ ),
+ canonical_name: "hello-world",
+ },
+ ),
+ cfg_options: CfgOptions(
+ [
+ "debug_assertions",
+ "test",
+ ],
+ ),
+ potential_cfg_options: CfgOptions(
+ [
+ "debug_assertions",
+ "test",
+ ],
+ ),
+ env: Env {
+ entries: {
+ "CARGO_PKG_LICENSE": "",
+ "CARGO_PKG_VERSION_MAJOR": "0",
+ "CARGO_MANIFEST_DIR": "$ROOT$hello-world",
+ "CARGO_PKG_VERSION": "0.1.0",
+ "CARGO_PKG_AUTHORS": "",
+ "CARGO_CRATE_NAME": "hello_world",
+ "CARGO_PKG_LICENSE_FILE": "",
+ "CARGO_PKG_HOMEPAGE": "",
+ "CARGO_PKG_DESCRIPTION": "",
+ "CARGO_PKG_NAME": "hello-world",
+ "CARGO_PKG_VERSION_PATCH": "0",
+ "CARGO": "cargo",
+ "CARGO_PKG_REPOSITORY": "",
+ "CARGO_PKG_VERSION_MINOR": "1",
+ "CARGO_PKG_VERSION_PRE": "",
+ },
+ },
+ dependencies: [
+ Dependency {
+ crate_id: CrateId(
+ 4,
+ ),
+ name: CrateName(
+ "libc",
+ ),
+ prelude: true,
+ },
+ ],
+ proc_macro: Err(
+ "crate has not (yet) been built",
+ ),
+ origin: CratesIo {
+ repo: None,
++ name: Some(
++ "hello-world",
++ ),
+ },
+ is_proc_macro: false,
+ },
+ CrateId(
+ 1,
+ ): CrateData {
+ root_file_id: FileId(
+ 2,
+ ),
+ edition: Edition2018,
+ version: Some(
+ "0.1.0",
+ ),
+ display_name: Some(
+ CrateDisplayName {
+ crate_name: CrateName(
+ "hello_world",
+ ),
+ canonical_name: "hello-world",
+ },
+ ),
+ cfg_options: CfgOptions(
+ [
+ "debug_assertions",
+ "test",
+ ],
+ ),
+ potential_cfg_options: CfgOptions(
+ [
+ "debug_assertions",
+ "test",
+ ],
+ ),
+ env: Env {
+ entries: {
+ "CARGO_PKG_LICENSE": "",
+ "CARGO_PKG_VERSION_MAJOR": "0",
+ "CARGO_MANIFEST_DIR": "$ROOT$hello-world",
+ "CARGO_PKG_VERSION": "0.1.0",
+ "CARGO_PKG_AUTHORS": "",
+ "CARGO_CRATE_NAME": "hello_world",
+ "CARGO_PKG_LICENSE_FILE": "",
+ "CARGO_PKG_HOMEPAGE": "",
+ "CARGO_PKG_DESCRIPTION": "",
+ "CARGO_PKG_NAME": "hello-world",
+ "CARGO_PKG_VERSION_PATCH": "0",
+ "CARGO": "cargo",
+ "CARGO_PKG_REPOSITORY": "",
+ "CARGO_PKG_VERSION_MINOR": "1",
+ "CARGO_PKG_VERSION_PRE": "",
+ },
+ },
+ dependencies: [
+ Dependency {
+ crate_id: CrateId(
+ 0,
+ ),
+ name: CrateName(
+ "hello_world",
+ ),
+ prelude: true,
+ },
+ Dependency {
+ crate_id: CrateId(
+ 4,
+ ),
+ name: CrateName(
+ "libc",
+ ),
+ prelude: true,
+ },
+ ],
+ proc_macro: Err(
+ "crate has not (yet) been built",
+ ),
+ origin: CratesIo {
+ repo: None,
++ name: Some(
++ "hello-world",
++ ),
+ },
+ is_proc_macro: false,
+ },
+ CrateId(
+ 2,
+ ): CrateData {
+ root_file_id: FileId(
+ 3,
+ ),
+ edition: Edition2018,
+ version: Some(
+ "0.1.0",
+ ),
+ display_name: Some(
+ CrateDisplayName {
+ crate_name: CrateName(
+ "an_example",
+ ),
+ canonical_name: "an-example",
+ },
+ ),
+ cfg_options: CfgOptions(
+ [
+ "debug_assertions",
+ "test",
+ ],
+ ),
+ potential_cfg_options: CfgOptions(
+ [
+ "debug_assertions",
+ "test",
+ ],
+ ),
+ env: Env {
+ entries: {
+ "CARGO_PKG_LICENSE": "",
+ "CARGO_PKG_VERSION_MAJOR": "0",
+ "CARGO_MANIFEST_DIR": "$ROOT$hello-world",
+ "CARGO_PKG_VERSION": "0.1.0",
+ "CARGO_PKG_AUTHORS": "",
+ "CARGO_CRATE_NAME": "hello_world",
+ "CARGO_PKG_LICENSE_FILE": "",
+ "CARGO_PKG_HOMEPAGE": "",
+ "CARGO_PKG_DESCRIPTION": "",
+ "CARGO_PKG_NAME": "hello-world",
+ "CARGO_PKG_VERSION_PATCH": "0",
+ "CARGO": "cargo",
+ "CARGO_PKG_REPOSITORY": "",
+ "CARGO_PKG_VERSION_MINOR": "1",
+ "CARGO_PKG_VERSION_PRE": "",
+ },
+ },
+ dependencies: [
+ Dependency {
+ crate_id: CrateId(
+ 0,
+ ),
+ name: CrateName(
+ "hello_world",
+ ),
+ prelude: true,
+ },
+ Dependency {
+ crate_id: CrateId(
+ 4,
+ ),
+ name: CrateName(
+ "libc",
+ ),
+ prelude: true,
+ },
+ ],
+ proc_macro: Err(
+ "crate has not (yet) been built",
+ ),
+ origin: CratesIo {
+ repo: None,
++ name: Some(
++ "hello-world",
++ ),
+ },
+ is_proc_macro: false,
+ },
+ CrateId(
+ 3,
+ ): CrateData {
+ root_file_id: FileId(
+ 4,
+ ),
+ edition: Edition2018,
+ version: Some(
+ "0.1.0",
+ ),
+ display_name: Some(
+ CrateDisplayName {
+ crate_name: CrateName(
+ "it",
+ ),
+ canonical_name: "it",
+ },
+ ),
+ cfg_options: CfgOptions(
+ [
+ "debug_assertions",
+ "test",
+ ],
+ ),
+ potential_cfg_options: CfgOptions(
+ [
+ "debug_assertions",
+ "test",
+ ],
+ ),
+ env: Env {
+ entries: {
+ "CARGO_PKG_LICENSE": "",
+ "CARGO_PKG_VERSION_MAJOR": "0",
+ "CARGO_MANIFEST_DIR": "$ROOT$hello-world",
+ "CARGO_PKG_VERSION": "0.1.0",
+ "CARGO_PKG_AUTHORS": "",
+ "CARGO_CRATE_NAME": "hello_world",
+ "CARGO_PKG_LICENSE_FILE": "",
+ "CARGO_PKG_HOMEPAGE": "",
+ "CARGO_PKG_DESCRIPTION": "",
+ "CARGO_PKG_NAME": "hello-world",
+ "CARGO_PKG_VERSION_PATCH": "0",
+ "CARGO": "cargo",
+ "CARGO_PKG_REPOSITORY": "",
+ "CARGO_PKG_VERSION_MINOR": "1",
+ "CARGO_PKG_VERSION_PRE": "",
+ },
+ },
+ dependencies: [
+ Dependency {
+ crate_id: CrateId(
+ 0,
+ ),
+ name: CrateName(
+ "hello_world",
+ ),
+ prelude: true,
+ },
+ Dependency {
+ crate_id: CrateId(
+ 4,
+ ),
+ name: CrateName(
+ "libc",
+ ),
+ prelude: true,
+ },
+ ],
+ proc_macro: Err(
+ "crate has not (yet) been built",
+ ),
+ origin: CratesIo {
+ repo: None,
++ name: Some(
++ "hello-world",
++ ),
+ },
+ is_proc_macro: false,
+ },
+ CrateId(
+ 4,
+ ): CrateData {
+ root_file_id: FileId(
+ 5,
+ ),
+ edition: Edition2015,
+ version: Some(
+ "0.2.98",
+ ),
+ display_name: Some(
+ CrateDisplayName {
+ crate_name: CrateName(
+ "libc",
+ ),
+ canonical_name: "libc",
+ },
+ ),
+ cfg_options: CfgOptions(
+ [
+ "debug_assertions",
+ "feature=default",
+ "feature=std",
+ ],
+ ),
+ potential_cfg_options: CfgOptions(
+ [
+ "debug_assertions",
+ "feature=align",
+ "feature=const-extern-fn",
+ "feature=default",
+ "feature=extra_traits",
+ "feature=rustc-dep-of-std",
+ "feature=std",
+ "feature=use_std",
+ ],
+ ),
+ env: Env {
+ entries: {
+ "CARGO_PKG_LICENSE": "",
+ "CARGO_PKG_VERSION_MAJOR": "0",
+ "CARGO_MANIFEST_DIR": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.98",
+ "CARGO_PKG_VERSION": "0.2.98",
+ "CARGO_PKG_AUTHORS": "",
+ "CARGO_CRATE_NAME": "libc",
+ "CARGO_PKG_LICENSE_FILE": "",
+ "CARGO_PKG_HOMEPAGE": "",
+ "CARGO_PKG_DESCRIPTION": "",
+ "CARGO_PKG_NAME": "libc",
+ "CARGO_PKG_VERSION_PATCH": "98",
+ "CARGO": "cargo",
+ "CARGO_PKG_REPOSITORY": "",
+ "CARGO_PKG_VERSION_MINOR": "2",
+ "CARGO_PKG_VERSION_PRE": "",
+ },
+ },
+ dependencies: [],
+ proc_macro: Err(
+ "crate has not (yet) been built",
+ ),
+ origin: CratesIo {
+ repo: Some(
+ "https://github.com/rust-lang/libc",
+ ),
++ name: Some(
++ "libc",
++ ),
+ },
+ is_proc_macro: false,
+ },
+ },
+ }"#]],
+ )
+}
+
+#[test]
+fn cargo_hello_world_project_model() {
+ let crate_graph = load_cargo("hello-world-metadata.json");
+ check_crate_graph(
+ crate_graph,
+ expect![[r#"
+ CrateGraph {
+ arena: {
+ CrateId(
+ 0,
+ ): CrateData {
+ root_file_id: FileId(
+ 1,
+ ),
+ edition: Edition2018,
+ version: Some(
+ "0.1.0",
+ ),
+ display_name: Some(
+ CrateDisplayName {
+ crate_name: CrateName(
+ "hello_world",
+ ),
+ canonical_name: "hello-world",
+ },
+ ),
+ cfg_options: CfgOptions(
+ [
+ "debug_assertions",
+ "test",
+ ],
+ ),
+ potential_cfg_options: CfgOptions(
+ [
+ "debug_assertions",
+ "test",
+ ],
+ ),
+ env: Env {
+ entries: {
+ "CARGO_PKG_LICENSE": "",
+ "CARGO_PKG_VERSION_MAJOR": "0",
+ "CARGO_MANIFEST_DIR": "$ROOT$hello-world",
+ "CARGO_PKG_VERSION": "0.1.0",
+ "CARGO_PKG_AUTHORS": "",
+ "CARGO_CRATE_NAME": "hello_world",
+ "CARGO_PKG_LICENSE_FILE": "",
+ "CARGO_PKG_HOMEPAGE": "",
+ "CARGO_PKG_DESCRIPTION": "",
+ "CARGO_PKG_NAME": "hello-world",
+ "CARGO_PKG_VERSION_PATCH": "0",
+ "CARGO": "cargo",
+ "CARGO_PKG_REPOSITORY": "",
+ "CARGO_PKG_VERSION_MINOR": "1",
+ "CARGO_PKG_VERSION_PRE": "",
+ },
+ },
+ dependencies: [
+ Dependency {
+ crate_id: CrateId(
+ 4,
+ ),
+ name: CrateName(
+ "libc",
+ ),
+ prelude: true,
+ },
+ ],
+ proc_macro: Err(
+ "crate has not (yet) been built",
+ ),
+ origin: CratesIo {
+ repo: None,
++ name: Some(
++ "hello-world",
++ ),
+ },
+ is_proc_macro: false,
+ },
+ CrateId(
+ 1,
+ ): CrateData {
+ root_file_id: FileId(
+ 2,
+ ),
+ edition: Edition2018,
+ version: Some(
+ "0.1.0",
+ ),
+ display_name: Some(
+ CrateDisplayName {
+ crate_name: CrateName(
+ "hello_world",
+ ),
+ canonical_name: "hello-world",
+ },
+ ),
+ cfg_options: CfgOptions(
+ [
+ "debug_assertions",
+ "test",
+ ],
+ ),
+ potential_cfg_options: CfgOptions(
+ [
+ "debug_assertions",
+ "test",
+ ],
+ ),
+ env: Env {
+ entries: {
+ "CARGO_PKG_LICENSE": "",
+ "CARGO_PKG_VERSION_MAJOR": "0",
+ "CARGO_MANIFEST_DIR": "$ROOT$hello-world",
+ "CARGO_PKG_VERSION": "0.1.0",
+ "CARGO_PKG_AUTHORS": "",
+ "CARGO_CRATE_NAME": "hello_world",
+ "CARGO_PKG_LICENSE_FILE": "",
+ "CARGO_PKG_HOMEPAGE": "",
+ "CARGO_PKG_DESCRIPTION": "",
+ "CARGO_PKG_NAME": "hello-world",
+ "CARGO_PKG_VERSION_PATCH": "0",
+ "CARGO": "cargo",
+ "CARGO_PKG_REPOSITORY": "",
+ "CARGO_PKG_VERSION_MINOR": "1",
+ "CARGO_PKG_VERSION_PRE": "",
+ },
+ },
+ dependencies: [
+ Dependency {
+ crate_id: CrateId(
+ 0,
+ ),
+ name: CrateName(
+ "hello_world",
+ ),
+ prelude: true,
+ },
+ Dependency {
+ crate_id: CrateId(
+ 4,
+ ),
+ name: CrateName(
+ "libc",
+ ),
+ prelude: true,
+ },
+ ],
+ proc_macro: Err(
+ "crate has not (yet) been built",
+ ),
+ origin: CratesIo {
+ repo: None,
++ name: Some(
++ "hello-world",
++ ),
+ },
+ is_proc_macro: false,
+ },
+ CrateId(
+ 2,
+ ): CrateData {
+ root_file_id: FileId(
+ 3,
+ ),
+ edition: Edition2018,
+ version: Some(
+ "0.1.0",
+ ),
+ display_name: Some(
+ CrateDisplayName {
+ crate_name: CrateName(
+ "an_example",
+ ),
+ canonical_name: "an-example",
+ },
+ ),
+ cfg_options: CfgOptions(
+ [
+ "debug_assertions",
+ "test",
+ ],
+ ),
+ potential_cfg_options: CfgOptions(
+ [
+ "debug_assertions",
+ "test",
+ ],
+ ),
+ env: Env {
+ entries: {
+ "CARGO_PKG_LICENSE": "",
+ "CARGO_PKG_VERSION_MAJOR": "0",
+ "CARGO_MANIFEST_DIR": "$ROOT$hello-world",
+ "CARGO_PKG_VERSION": "0.1.0",
+ "CARGO_PKG_AUTHORS": "",
+ "CARGO_CRATE_NAME": "hello_world",
+ "CARGO_PKG_LICENSE_FILE": "",
+ "CARGO_PKG_HOMEPAGE": "",
+ "CARGO_PKG_DESCRIPTION": "",
+ "CARGO_PKG_NAME": "hello-world",
+ "CARGO_PKG_VERSION_PATCH": "0",
+ "CARGO": "cargo",
+ "CARGO_PKG_REPOSITORY": "",
+ "CARGO_PKG_VERSION_MINOR": "1",
+ "CARGO_PKG_VERSION_PRE": "",
+ },
+ },
+ dependencies: [
+ Dependency {
+ crate_id: CrateId(
+ 0,
+ ),
+ name: CrateName(
+ "hello_world",
+ ),
+ prelude: true,
+ },
+ Dependency {
+ crate_id: CrateId(
+ 4,
+ ),
+ name: CrateName(
+ "libc",
+ ),
+ prelude: true,
+ },
+ ],
+ proc_macro: Err(
+ "crate has not (yet) been built",
+ ),
+ origin: CratesIo {
+ repo: None,
++ name: Some(
++ "hello-world",
++ ),
+ },
+ is_proc_macro: false,
+ },
+ CrateId(
+ 3,
+ ): CrateData {
+ root_file_id: FileId(
+ 4,
+ ),
+ edition: Edition2018,
+ version: Some(
+ "0.1.0",
+ ),
+ display_name: Some(
+ CrateDisplayName {
+ crate_name: CrateName(
+ "it",
+ ),
+ canonical_name: "it",
+ },
+ ),
+ cfg_options: CfgOptions(
+ [
+ "debug_assertions",
+ "test",
+ ],
+ ),
+ potential_cfg_options: CfgOptions(
+ [
+ "debug_assertions",
+ "test",
+ ],
+ ),
+ env: Env {
+ entries: {
+ "CARGO_PKG_LICENSE": "",
+ "CARGO_PKG_VERSION_MAJOR": "0",
+ "CARGO_MANIFEST_DIR": "$ROOT$hello-world",
+ "CARGO_PKG_VERSION": "0.1.0",
+ "CARGO_PKG_AUTHORS": "",
+ "CARGO_CRATE_NAME": "hello_world",
+ "CARGO_PKG_LICENSE_FILE": "",
+ "CARGO_PKG_HOMEPAGE": "",
+ "CARGO_PKG_DESCRIPTION": "",
+ "CARGO_PKG_NAME": "hello-world",
+ "CARGO_PKG_VERSION_PATCH": "0",
+ "CARGO": "cargo",
+ "CARGO_PKG_REPOSITORY": "",
+ "CARGO_PKG_VERSION_MINOR": "1",
+ "CARGO_PKG_VERSION_PRE": "",
+ },
+ },
+ dependencies: [
+ Dependency {
+ crate_id: CrateId(
+ 0,
+ ),
+ name: CrateName(
+ "hello_world",
+ ),
+ prelude: true,
+ },
+ Dependency {
+ crate_id: CrateId(
+ 4,
+ ),
+ name: CrateName(
+ "libc",
+ ),
+ prelude: true,
+ },
+ ],
+ proc_macro: Err(
+ "crate has not (yet) been built",
+ ),
+ origin: CratesIo {
+ repo: None,
++ name: Some(
++ "hello-world",
++ ),
+ },
+ is_proc_macro: false,
+ },
+ CrateId(
+ 4,
+ ): CrateData {
+ root_file_id: FileId(
+ 5,
+ ),
+ edition: Edition2015,
+ version: Some(
+ "0.2.98",
+ ),
+ display_name: Some(
+ CrateDisplayName {
+ crate_name: CrateName(
+ "libc",
+ ),
+ canonical_name: "libc",
+ },
+ ),
+ cfg_options: CfgOptions(
+ [
+ "debug_assertions",
+ "feature=default",
+ "feature=std",
+ ],
+ ),
+ potential_cfg_options: CfgOptions(
+ [
+ "debug_assertions",
+ "feature=align",
+ "feature=const-extern-fn",
+ "feature=default",
+ "feature=extra_traits",
+ "feature=rustc-dep-of-std",
+ "feature=std",
+ "feature=use_std",
+ ],
+ ),
+ env: Env {
+ entries: {
+ "CARGO_PKG_LICENSE": "",
+ "CARGO_PKG_VERSION_MAJOR": "0",
+ "CARGO_MANIFEST_DIR": "$ROOT$.cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.98",
+ "CARGO_PKG_VERSION": "0.2.98",
+ "CARGO_PKG_AUTHORS": "",
+ "CARGO_CRATE_NAME": "libc",
+ "CARGO_PKG_LICENSE_FILE": "",
+ "CARGO_PKG_HOMEPAGE": "",
+ "CARGO_PKG_DESCRIPTION": "",
+ "CARGO_PKG_NAME": "libc",
+ "CARGO_PKG_VERSION_PATCH": "98",
+ "CARGO": "cargo",
+ "CARGO_PKG_REPOSITORY": "",
+ "CARGO_PKG_VERSION_MINOR": "2",
+ "CARGO_PKG_VERSION_PRE": "",
+ },
+ },
+ dependencies: [],
+ proc_macro: Err(
+ "crate has not (yet) been built",
+ ),
+ origin: CratesIo {
+ repo: Some(
+ "https://github.com/rust-lang/libc",
+ ),
++ name: Some(
++ "libc",
++ ),
+ },
+ is_proc_macro: false,
+ },
+ },
+ }"#]],
+ )
+}
+
+#[test]
+fn rust_project_hello_world_project_model() {
+ let crate_graph = load_rust_project("hello-world-project.json");
+ check_crate_graph(
+ crate_graph,
+ expect![[r#"
+ CrateGraph {
+ arena: {
+ CrateId(
+ 0,
+ ): CrateData {
+ root_file_id: FileId(
+ 1,
+ ),
+ edition: Edition2018,
+ version: None,
+ display_name: Some(
+ CrateDisplayName {
+ crate_name: CrateName(
+ "alloc",
+ ),
+ canonical_name: "alloc",
+ },
+ ),
+ cfg_options: CfgOptions(
+ [],
+ ),
+ potential_cfg_options: CfgOptions(
+ [],
+ ),
+ env: Env {
+ entries: {},
+ },
+ dependencies: [
+ Dependency {
+ crate_id: CrateId(
+ 1,
+ ),
+ name: CrateName(
+ "core",
+ ),
+ prelude: true,
+ },
+ ],
+ proc_macro: Err(
+ "no proc macro loaded for sysroot crate",
+ ),
+ origin: Lang(
+ Alloc,
+ ),
+ is_proc_macro: false,
+ },
+ CrateId(
+ 1,
+ ): CrateData {
+ root_file_id: FileId(
+ 2,
+ ),
+ edition: Edition2018,
+ version: None,
+ display_name: Some(
+ CrateDisplayName {
+ crate_name: CrateName(
+ "core",
+ ),
+ canonical_name: "core",
+ },
+ ),
+ cfg_options: CfgOptions(
+ [],
+ ),
+ potential_cfg_options: CfgOptions(
+ [],
+ ),
+ env: Env {
+ entries: {},
+ },
+ dependencies: [],
+ proc_macro: Err(
+ "no proc macro loaded for sysroot crate",
+ ),
+ origin: Lang(
+ Core,
+ ),
+ is_proc_macro: false,
+ },
+ CrateId(
+ 2,
+ ): CrateData {
+ root_file_id: FileId(
+ 3,
+ ),
+ edition: Edition2018,
+ version: None,
+ display_name: Some(
+ CrateDisplayName {
+ crate_name: CrateName(
+ "panic_abort",
+ ),
+ canonical_name: "panic_abort",
+ },
+ ),
+ cfg_options: CfgOptions(
+ [],
+ ),
+ potential_cfg_options: CfgOptions(
+ [],
+ ),
+ env: Env {
+ entries: {},
+ },
+ dependencies: [],
+ proc_macro: Err(
+ "no proc macro loaded for sysroot crate",
+ ),
+ origin: Lang(
+ Other,
+ ),
+ is_proc_macro: false,
+ },
+ CrateId(
+ 3,
+ ): CrateData {
+ root_file_id: FileId(
+ 4,
+ ),
+ edition: Edition2018,
+ version: None,
+ display_name: Some(
+ CrateDisplayName {
+ crate_name: CrateName(
+ "panic_unwind",
+ ),
+ canonical_name: "panic_unwind",
+ },
+ ),
+ cfg_options: CfgOptions(
+ [],
+ ),
+ potential_cfg_options: CfgOptions(
+ [],
+ ),
+ env: Env {
+ entries: {},
+ },
+ dependencies: [],
+ proc_macro: Err(
+ "no proc macro loaded for sysroot crate",
+ ),
+ origin: Lang(
+ Other,
+ ),
+ is_proc_macro: false,
+ },
+ CrateId(
+ 4,
+ ): CrateData {
+ root_file_id: FileId(
+ 5,
+ ),
+ edition: Edition2018,
+ version: None,
+ display_name: Some(
+ CrateDisplayName {
+ crate_name: CrateName(
+ "proc_macro",
+ ),
+ canonical_name: "proc_macro",
+ },
+ ),
+ cfg_options: CfgOptions(
+ [],
+ ),
+ potential_cfg_options: CfgOptions(
+ [],
+ ),
+ env: Env {
+ entries: {},
+ },
+ dependencies: [
+ Dependency {
+ crate_id: CrateId(
+ 6,
+ ),
+ name: CrateName(
+ "std",
+ ),
+ prelude: true,
+ },
+ ],
+ proc_macro: Err(
+ "no proc macro loaded for sysroot crate",
+ ),
+ origin: Lang(
+ Other,
+ ),
+ is_proc_macro: false,
+ },
+ CrateId(
+ 5,
+ ): CrateData {
+ root_file_id: FileId(
+ 6,
+ ),
+ edition: Edition2018,
+ version: None,
+ display_name: Some(
+ CrateDisplayName {
+ crate_name: CrateName(
+ "profiler_builtins",
+ ),
+ canonical_name: "profiler_builtins",
+ },
+ ),
+ cfg_options: CfgOptions(
+ [],
+ ),
+ potential_cfg_options: CfgOptions(
+ [],
+ ),
+ env: Env {
+ entries: {},
+ },
+ dependencies: [],
+ proc_macro: Err(
+ "no proc macro loaded for sysroot crate",
+ ),
+ origin: Lang(
+ Other,
+ ),
+ is_proc_macro: false,
+ },
+ CrateId(
+ 6,
+ ): CrateData {
+ root_file_id: FileId(
+ 7,
+ ),
+ edition: Edition2018,
+ version: None,
+ display_name: Some(
+ CrateDisplayName {
+ crate_name: CrateName(
+ "std",
+ ),
+ canonical_name: "std",
+ },
+ ),
+ cfg_options: CfgOptions(
+ [],
+ ),
+ potential_cfg_options: CfgOptions(
+ [],
+ ),
+ env: Env {
+ entries: {},
+ },
+ dependencies: [
+ Dependency {
+ crate_id: CrateId(
+ 0,
+ ),
+ name: CrateName(
+ "alloc",
+ ),
+ prelude: true,
+ },
+ Dependency {
+ crate_id: CrateId(
+ 1,
+ ),
+ name: CrateName(
+ "core",
+ ),
+ prelude: true,
+ },
+ Dependency {
+ crate_id: CrateId(
+ 2,
+ ),
+ name: CrateName(
+ "panic_abort",
+ ),
+ prelude: true,
+ },
+ Dependency {
+ crate_id: CrateId(
+ 3,
+ ),
+ name: CrateName(
+ "panic_unwind",
+ ),
+ prelude: true,
+ },
+ Dependency {
+ crate_id: CrateId(
+ 5,
+ ),
+ name: CrateName(
+ "profiler_builtins",
+ ),
+ prelude: true,
+ },
+ Dependency {
+ crate_id: CrateId(
+ 7,
+ ),
+ name: CrateName(
+ "std_detect",
+ ),
+ prelude: true,
+ },
+ Dependency {
+ crate_id: CrateId(
+ 8,
+ ),
+ name: CrateName(
+ "term",
+ ),
+ prelude: true,
+ },
+ Dependency {
+ crate_id: CrateId(
+ 9,
+ ),
+ name: CrateName(
+ "test",
+ ),
+ prelude: true,
+ },
+ Dependency {
+ crate_id: CrateId(
+ 10,
+ ),
+ name: CrateName(
+ "unwind",
+ ),
+ prelude: true,
+ },
+ ],
+ proc_macro: Err(
+ "no proc macro loaded for sysroot crate",
+ ),
+ origin: Lang(
+ Std,
+ ),
+ is_proc_macro: false,
+ },
+ CrateId(
+ 7,
+ ): CrateData {
+ root_file_id: FileId(
+ 8,
+ ),
+ edition: Edition2018,
+ version: None,
+ display_name: Some(
+ CrateDisplayName {
+ crate_name: CrateName(
+ "std_detect",
+ ),
+ canonical_name: "std_detect",
+ },
+ ),
+ cfg_options: CfgOptions(
+ [],
+ ),
+ potential_cfg_options: CfgOptions(
+ [],
+ ),
+ env: Env {
+ entries: {},
+ },
+ dependencies: [],
+ proc_macro: Err(
+ "no proc macro loaded for sysroot crate",
+ ),
+ origin: Lang(
+ Other,
+ ),
+ is_proc_macro: false,
+ },
+ CrateId(
+ 8,
+ ): CrateData {
+ root_file_id: FileId(
+ 9,
+ ),
+ edition: Edition2018,
+ version: None,
+ display_name: Some(
+ CrateDisplayName {
+ crate_name: CrateName(
+ "term",
+ ),
+ canonical_name: "term",
+ },
+ ),
+ cfg_options: CfgOptions(
+ [],
+ ),
+ potential_cfg_options: CfgOptions(
+ [],
+ ),
+ env: Env {
+ entries: {},
+ },
+ dependencies: [],
+ proc_macro: Err(
+ "no proc macro loaded for sysroot crate",
+ ),
+ origin: Lang(
+ Other,
+ ),
+ is_proc_macro: false,
+ },
+ CrateId(
+ 9,
+ ): CrateData {
+ root_file_id: FileId(
+ 10,
+ ),
+ edition: Edition2018,
+ version: None,
+ display_name: Some(
+ CrateDisplayName {
+ crate_name: CrateName(
+ "test",
+ ),
+ canonical_name: "test",
+ },
+ ),
+ cfg_options: CfgOptions(
+ [],
+ ),
+ potential_cfg_options: CfgOptions(
+ [],
+ ),
+ env: Env {
+ entries: {},
+ },
+ dependencies: [],
+ proc_macro: Err(
+ "no proc macro loaded for sysroot crate",
+ ),
+ origin: Lang(
+ Test,
+ ),
+ is_proc_macro: false,
+ },
+ CrateId(
+ 10,
+ ): CrateData {
+ root_file_id: FileId(
+ 11,
+ ),
+ edition: Edition2018,
+ version: None,
+ display_name: Some(
+ CrateDisplayName {
+ crate_name: CrateName(
+ "unwind",
+ ),
+ canonical_name: "unwind",
+ },
+ ),
+ cfg_options: CfgOptions(
+ [],
+ ),
+ potential_cfg_options: CfgOptions(
+ [],
+ ),
+ env: Env {
+ entries: {},
+ },
+ dependencies: [],
+ proc_macro: Err(
+ "no proc macro loaded for sysroot crate",
+ ),
+ origin: Lang(
+ Other,
+ ),
+ is_proc_macro: false,
+ },
+ CrateId(
+ 11,
+ ): CrateData {
+ root_file_id: FileId(
+ 12,
+ ),
+ edition: Edition2018,
+ version: None,
+ display_name: Some(
+ CrateDisplayName {
+ crate_name: CrateName(
+ "hello_world",
+ ),
+ canonical_name: "hello_world",
+ },
+ ),
+ cfg_options: CfgOptions(
+ [],
+ ),
+ potential_cfg_options: CfgOptions(
+ [],
+ ),
+ env: Env {
+ entries: {},
+ },
+ dependencies: [
+ Dependency {
+ crate_id: CrateId(
+ 1,
+ ),
+ name: CrateName(
+ "core",
+ ),
+ prelude: true,
+ },
+ Dependency {
+ crate_id: CrateId(
+ 0,
+ ),
+ name: CrateName(
+ "alloc",
+ ),
+ prelude: true,
+ },
+ Dependency {
+ crate_id: CrateId(
+ 6,
+ ),
+ name: CrateName(
+ "std",
+ ),
+ prelude: true,
+ },
+ Dependency {
+ crate_id: CrateId(
+ 9,
+ ),
+ name: CrateName(
+ "test",
+ ),
+ prelude: false,
+ },
+ ],
+ proc_macro: Err(
+ "no proc macro dylib present",
+ ),
+ origin: CratesIo {
+ repo: None,
++ name: Some(
++ "hello_world",
++ ),
+ },
+ is_proc_macro: false,
+ },
+ },
+ }"#]],
+ );
+}
+
+#[test]
+fn rust_project_is_proc_macro_has_proc_macro_dep() {
+ let crate_graph = load_rust_project("is-proc-macro-project.json");
+ // Since the project only defines one crate (outside the sysroot crates),
+ // it should be the one with the biggest Id.
+ let crate_id = crate_graph.iter().max().unwrap();
+ let crate_data = &crate_graph[crate_id];
+ // Assert that the project crate with `is_proc_macro` has a dependency
+ // on the proc_macro sysroot crate.
+ crate_data.dependencies.iter().find(|&dep| dep.name.deref() == "proc_macro").unwrap();
+}
--- /dev/null
- utf8_stdout, CargoConfig, CargoWorkspace, ManifestPath, ProjectJson, ProjectManifest, Sysroot,
- TargetKind, WorkspaceBuildScripts,
+//! Handles lowering of build-system specific workspace information (`cargo
+//! metadata` or `rust-project.json`) into representation stored in the salsa
+//! database -- `CrateGraph`.
+
+use std::{collections::VecDeque, fmt, fs, process::Command};
+
+use anyhow::{format_err, Context, Result};
+use base_db::{
+ CrateDisplayName, CrateGraph, CrateId, CrateName, CrateOrigin, Dependency, Edition, Env,
+ FileId, LangCrateOrigin, ProcMacroLoadResult,
+};
+use cfg::{CfgDiff, CfgOptions};
+use paths::{AbsPath, AbsPathBuf};
+use rustc_hash::{FxHashMap, FxHashSet};
+use semver::Version;
+use stdx::{always, hash::NoHashHashMap};
+
+use crate::{
+ build_scripts::BuildScriptOutput,
+ cargo_workspace::{DepKind, PackageData, RustcSource},
+ cfg_flag::CfgFlag,
+ rustc_cfg,
+ sysroot::SysrootCrate,
- ProjectWorkspace::load_inline(project_json, config.target.as_deref(), config)?
++ utf8_stdout, CargoConfig, CargoWorkspace, ManifestPath, Package, ProjectJson, ProjectManifest,
++ Sysroot, TargetKind, WorkspaceBuildScripts,
+};
+
+/// A set of cfg-overrides per crate.
+///
+/// `Wildcard(..)` is useful e.g. disabling `#[cfg(test)]` on all crates,
+/// without having to first obtain a list of all crates.
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub enum CfgOverrides {
+ /// A single global set of overrides matching all crates.
+ Wildcard(CfgDiff),
+ /// A set of overrides matching specific crates.
+ Selective(FxHashMap<String, CfgDiff>),
+}
+
+impl Default for CfgOverrides {
+ fn default() -> Self {
+ Self::Selective(FxHashMap::default())
+ }
+}
+
+impl CfgOverrides {
+ pub fn len(&self) -> usize {
+ match self {
+ CfgOverrides::Wildcard(_) => 1,
+ CfgOverrides::Selective(hash_map) => hash_map.len(),
+ }
+ }
+}
+
+/// `PackageRoot` describes a package root folder.
+/// Which may be an external dependency, or a member of
+/// the current workspace.
+#[derive(Debug, Clone, Eq, PartialEq, Hash)]
+pub struct PackageRoot {
+ /// Is from the local filesystem and may be edited
+ pub is_local: bool,
+ pub include: Vec<AbsPathBuf>,
+ pub exclude: Vec<AbsPathBuf>,
+}
+
+#[derive(Clone, Eq, PartialEq)]
+pub enum ProjectWorkspace {
+ /// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`.
+ Cargo {
+ cargo: CargoWorkspace,
+ build_scripts: WorkspaceBuildScripts,
+ sysroot: Option<Sysroot>,
+ rustc: Option<CargoWorkspace>,
+ /// Holds cfg flags for the current target. We get those by running
+ /// `rustc --print cfg`.
+ ///
+ /// FIXME: make this a per-crate map, as, eg, build.rs might have a
+ /// different target.
+ rustc_cfg: Vec<CfgFlag>,
+ cfg_overrides: CfgOverrides,
+ toolchain: Option<Version>,
+ },
+ /// Project workspace was manually specified using a `rust-project.json` file.
+ Json { project: ProjectJson, sysroot: Option<Sysroot>, rustc_cfg: Vec<CfgFlag> },
+
+ // FIXME: The primary limitation of this approach is that the set of detached files needs to be fixed at the beginning.
+ // That's not the end user experience we should strive for.
+ // Ideally, you should be able to just open a random detached file in existing cargo projects, and get the basic features working.
+ // That needs some changes on the salsa-level though.
+ // In particular, we should split the unified CrateGraph (which currently has maximal durability) into proper crate graph, and a set of ad hoc roots (with minimal durability).
+ // Then, we need to hide the graph behind the queries such that most queries look only at the proper crate graph, and fall back to ad hoc roots only if there's no results.
+ // After this, we should be able to tweak the logic in reload.rs to add newly opened files, which don't belong to any existing crates, to the set of the detached files.
+ // //
+ /// Project with a set of disjoint files, not belonging to any particular workspace.
+ /// Backed by basic sysroot crates for basic completion and highlighting.
+ DetachedFiles { files: Vec<AbsPathBuf>, sysroot: Sysroot, rustc_cfg: Vec<CfgFlag> },
+}
+
+impl fmt::Debug for ProjectWorkspace {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ // Make sure this isn't too verbose.
+ match self {
+ ProjectWorkspace::Cargo {
+ cargo,
+ build_scripts: _,
+ sysroot,
+ rustc,
+ rustc_cfg,
+ cfg_overrides,
+ toolchain,
+ } => f
+ .debug_struct("Cargo")
+ .field("root", &cargo.workspace_root().file_name())
+ .field("n_packages", &cargo.packages().len())
+ .field("sysroot", &sysroot.is_some())
+ .field(
+ "n_rustc_compiler_crates",
+ &rustc.as_ref().map_or(0, |rc| rc.packages().len()),
+ )
+ .field("n_rustc_cfg", &rustc_cfg.len())
+ .field("n_cfg_overrides", &cfg_overrides.len())
+ .field("toolchain", &toolchain)
+ .finish(),
+ ProjectWorkspace::Json { project, sysroot, rustc_cfg } => {
+ let mut debug_struct = f.debug_struct("Json");
+ debug_struct.field("n_crates", &project.n_crates());
+ if let Some(sysroot) = sysroot {
+ debug_struct.field("n_sysroot_crates", &sysroot.crates().len());
+ }
+ debug_struct.field("n_rustc_cfg", &rustc_cfg.len());
+ debug_struct.finish()
+ }
+ ProjectWorkspace::DetachedFiles { files, sysroot, rustc_cfg } => f
+ .debug_struct("DetachedFiles")
+ .field("n_files", &files.len())
+ .field("n_sysroot_crates", &sysroot.crates().len())
+ .field("n_rustc_cfg", &rustc_cfg.len())
+ .finish(),
+ }
+ }
+}
+
+impl ProjectWorkspace {
+ pub fn load(
+ manifest: ProjectManifest,
+ config: &CargoConfig,
+ progress: &dyn Fn(String),
+ ) -> Result<ProjectWorkspace> {
+ let res = match manifest {
+ ProjectManifest::ProjectJson(project_json) => {
+ let file = fs::read_to_string(&project_json).with_context(|| {
+ format!("Failed to read json file {}", project_json.display())
+ })?;
+ let data = serde_json::from_str(&file).with_context(|| {
+ format!("Failed to deserialize json file {}", project_json.display())
+ })?;
+ let project_location = project_json.parent().to_path_buf();
+ let project_json = ProjectJson::new(&project_location, data);
- let sysroot = if config.no_sysroot {
- None
- } else {
- Some(Sysroot::discover(cargo_toml.parent(), config).with_context(|| {
- format!(
++ ProjectWorkspace::load_inline(
++ project_json,
++ config.target.as_deref(),
++ &config.extra_env,
++ )?
+ }
+ ProjectManifest::CargoToml(cargo_toml) => {
+ let cargo_version = utf8_stdout({
+ let mut cmd = Command::new(toolchain::cargo());
+ cmd.envs(&config.extra_env);
+ cmd.arg("--version");
+ cmd
+ })?;
+ let toolchain = cargo_version
+ .get("cargo ".len()..)
+ .and_then(|it| Version::parse(it.split_whitespace().next()?).ok());
+
+ let meta = CargoWorkspace::fetch_metadata(
+ &cargo_toml,
+ cargo_toml.parent(),
+ config,
+ progress,
+ )
+ .with_context(|| {
+ format!(
+ "Failed to read Cargo metadata from Cargo.toml file {}, {:?}",
+ cargo_toml.display(),
+ toolchain
+ )
+ })?;
+ let cargo = CargoWorkspace::new(meta);
+
- })?)
++ let sysroot = match &config.sysroot {
++ Some(RustcSource::Path(path)) => {
++ Some(Sysroot::with_sysroot_dir(path.clone()).with_context(|| {
++ format!(
++ "Failed to find sysroot for Cargo.toml file {}.",
++ cargo_toml.display()
++ )
++ })?)
++ }
++ Some(RustcSource::Discover) => Some(
++ Sysroot::discover(cargo_toml.parent(), &config.extra_env).with_context(
++ || {
++ format!(
+ "Failed to find sysroot for Cargo.toml file {}. Is rust-src installed?",
+ cargo_toml.display()
+ )
- Some(RustcSource::Discover) => Sysroot::discover_rustc(&cargo_toml, config),
++ },
++ )?,
++ ),
++ None => None,
+ };
+
+ let rustc_dir = match &config.rustc_source {
+ Some(RustcSource::Path(path)) => ManifestPath::try_from(path.clone()).ok(),
- let rustc_cfg = rustc_cfg::get(Some(&cargo_toml), config.target.as_deref(), config);
++ Some(RustcSource::Discover) => {
++ Sysroot::discover_rustc(&cargo_toml, &config.extra_env)
++ }
+ None => None,
+ };
+
+ let rustc = match rustc_dir {
+ Some(rustc_dir) => Some({
+ let meta = CargoWorkspace::fetch_metadata(
+ &rustc_dir,
+ cargo_toml.parent(),
+ config,
+ progress,
+ )
+ .with_context(|| {
+ "Failed to read Cargo metadata for Rust sources".to_string()
+ })?;
+ CargoWorkspace::new(meta)
+ }),
+ None => None,
+ };
+
- config: &CargoConfig,
++ let rustc_cfg =
++ rustc_cfg::get(Some(&cargo_toml), config.target.as_deref(), &config.extra_env);
+
+ let cfg_overrides = config.cfg_overrides();
+ ProjectWorkspace::Cargo {
+ cargo,
+ build_scripts: WorkspaceBuildScripts::default(),
+ sysroot,
+ rustc,
+ rustc_cfg,
+ cfg_overrides,
+ toolchain,
+ }
+ }
+ };
+
+ Ok(res)
+ }
+
+ pub fn load_inline(
+ project_json: ProjectJson,
+ target: Option<&str>,
- let rustc_cfg = rustc_cfg::get(None, target, config);
++ extra_env: &FxHashMap<String, String>,
+ ) -> Result<ProjectWorkspace> {
+ let sysroot = match (project_json.sysroot.clone(), project_json.sysroot_src.clone()) {
+ (Some(sysroot), Some(sysroot_src)) => Some(Sysroot::load(sysroot, sysroot_src)?),
+ (Some(sysroot), None) => {
+ // assume sysroot is structured like rustup's and guess `sysroot_src`
+ let sysroot_src =
+ sysroot.join("lib").join("rustlib").join("src").join("rust").join("library");
+
+ Some(Sysroot::load(sysroot, sysroot_src)?)
+ }
+ (None, Some(sysroot_src)) => {
+ // assume sysroot is structured like rustup's and guess `sysroot`
+ let mut sysroot = sysroot_src.clone();
+ for _ in 0..5 {
+ sysroot.pop();
+ }
+ Some(Sysroot::load(sysroot, sysroot_src)?)
+ }
+ (None, None) => None,
+ };
+
- &CargoConfig::default(),
++ let rustc_cfg = rustc_cfg::get(None, target, extra_env);
+ Ok(ProjectWorkspace::Json { project: project_json, sysroot, rustc_cfg })
+ }
+
+ pub fn load_detached_files(detached_files: Vec<AbsPathBuf>) -> Result<ProjectWorkspace> {
+ let sysroot = Sysroot::discover(
+ detached_files
+ .first()
+ .and_then(|it| it.parent())
+ .ok_or_else(|| format_err!("No detached files to load"))?,
- let rustc_cfg = rustc_cfg::get(None, None, &CargoConfig::default());
++ &Default::default(),
+ )?;
- .chain(sysroot.as_ref().into_iter().flat_map(|sysroot| {
- sysroot.crates().map(move |krate| PackageRoot {
- is_local: false,
- include: vec![sysroot[krate].root.parent().to_path_buf()],
- exclude: Vec::new(),
- })
- }))
++ let rustc_cfg = rustc_cfg::get(None, None, &Default::default());
+ Ok(ProjectWorkspace::DetachedFiles { files: detached_files, sysroot, rustc_cfg })
+ }
+
+ pub fn run_build_scripts(
+ &self,
+ config: &CargoConfig,
+ progress: &dyn Fn(String),
+ ) -> Result<WorkspaceBuildScripts> {
+ match self {
+ ProjectWorkspace::Cargo { cargo, toolchain, .. } => {
+ WorkspaceBuildScripts::run(config, cargo, progress, toolchain).with_context(|| {
+ format!("Failed to run build scripts for {}", &cargo.workspace_root().display())
+ })
+ }
+ ProjectWorkspace::Json { .. } | ProjectWorkspace::DetachedFiles { .. } => {
+ Ok(WorkspaceBuildScripts::default())
+ }
+ }
+ }
+
+ pub fn set_build_scripts(&mut self, bs: WorkspaceBuildScripts) {
+ match self {
+ ProjectWorkspace::Cargo { build_scripts, .. } => *build_scripts = bs,
+ _ => {
+ always!(bs == WorkspaceBuildScripts::default());
+ }
+ }
+ }
+
+ /// Returns the roots for the current `ProjectWorkspace`
+ /// The return type contains the path and whether or not
+ /// the root is a member of the current workspace
+ pub fn to_roots(&self) -> Vec<PackageRoot> {
++ let mk_sysroot = |sysroot: Option<&Sysroot>| {
++ sysroot.map(|sysroot| PackageRoot {
++ is_local: false,
++ include: vec![sysroot.src_root().to_path_buf()],
++ exclude: Vec::new(),
++ })
++ };
+ match self {
+ ProjectWorkspace::Json { project, sysroot, rustc_cfg: _ } => project
+ .crates()
+ .map(|(_, krate)| PackageRoot {
+ is_local: krate.is_workspace_member,
+ include: krate.include.clone(),
+ exclude: krate.exclude.clone(),
+ })
+ .collect::<FxHashSet<_>>()
+ .into_iter()
- .chain(sysroot.iter().map(|sysroot| PackageRoot {
- is_local: false,
- include: vec![sysroot.src_root().to_path_buf()],
- exclude: Vec::new(),
- }))
++ .chain(mk_sysroot(sysroot.as_ref()))
+ .collect::<Vec<_>>(),
+ ProjectWorkspace::Cargo {
+ cargo,
+ sysroot,
+ rustc,
+ rustc_cfg: _,
+ cfg_overrides: _,
+ build_scripts,
+ toolchain: _,
+ } => {
+ cargo
+ .packages()
+ .map(|pkg| {
+ let is_local = cargo[pkg].is_local;
+ let pkg_root = cargo[pkg].manifest.parent().to_path_buf();
+
+ let mut include = vec![pkg_root.clone()];
+ let out_dir =
+ build_scripts.get_output(pkg).and_then(|it| it.out_dir.clone());
+ include.extend(out_dir);
+
+ // In case target's path is manually set in Cargo.toml to be
+ // outside the package root, add its parent as an extra include.
+ // An example of this situation would look like this:
+ //
+ // ```toml
+ // [lib]
+ // path = "../../src/lib.rs"
+ // ```
+ let extra_targets = cargo[pkg]
+ .targets
+ .iter()
+ .filter(|&&tgt| cargo[tgt].kind == TargetKind::Lib)
+ .filter_map(|&tgt| cargo[tgt].root.parent())
+ .map(|tgt| tgt.normalize().to_path_buf())
+ .filter(|path| !path.starts_with(&pkg_root));
+ include.extend(extra_targets);
+
+ let mut exclude = vec![pkg_root.join(".git")];
+ if is_local {
+ exclude.push(pkg_root.join("target"));
+ } else {
+ exclude.push(pkg_root.join("tests"));
+ exclude.push(pkg_root.join("examples"));
+ exclude.push(pkg_root.join("benches"));
+ }
+ PackageRoot { is_local, include, exclude }
+ })
- .chain(sysroot.crates().map(|krate| PackageRoot {
- is_local: false,
- include: vec![sysroot[krate].root.parent().to_path_buf()],
- exclude: Vec::new(),
- }))
++ .chain(mk_sysroot(sysroot.as_ref()))
+ .chain(rustc.iter().flat_map(|rustc| {
+ rustc.packages().map(move |krate| PackageRoot {
+ is_local: false,
+ include: vec![rustc[krate].manifest.parent().to_path_buf()],
+ exclude: Vec::new(),
+ })
+ }))
+ .collect()
+ }
+ ProjectWorkspace::DetachedFiles { files, sysroot, .. } => files
+ .iter()
+ .map(|detached_file| PackageRoot {
+ is_local: true,
+ include: vec![detached_file.clone()],
+ exclude: Vec::new(),
+ })
- config: &CargoConfig,
++ .chain(mk_sysroot(Some(sysroot)))
+ .collect(),
+ }
+ }
+
+ pub fn n_packages(&self) -> usize {
+ match self {
+ ProjectWorkspace::Json { project, .. } => project.n_crates(),
+ ProjectWorkspace::Cargo { cargo, sysroot, rustc, .. } => {
+ let rustc_package_len = rustc.as_ref().map_or(0, |it| it.packages().len());
+ let sysroot_package_len = sysroot.as_ref().map_or(0, |it| it.crates().len());
+ cargo.packages().len() + sysroot_package_len + rustc_package_len
+ }
+ ProjectWorkspace::DetachedFiles { sysroot, files, .. } => {
+ sysroot.crates().len() + files.len()
+ }
+ }
+ }
+
+ pub fn to_crate_graph(
+ &self,
+ load_proc_macro: &mut dyn FnMut(&str, &AbsPath) -> ProcMacroLoadResult,
+ load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
- config,
++ extra_env: &FxHashMap<String, String>,
+ ) -> CrateGraph {
+ let _p = profile::span("ProjectWorkspace::to_crate_graph");
+
+ let mut crate_graph = match self {
+ ProjectWorkspace::Json { project, sysroot, rustc_cfg } => project_json_to_crate_graph(
+ rustc_cfg.clone(),
+ load_proc_macro,
+ load,
+ project,
+ sysroot,
- config: &CargoConfig,
++ extra_env,
+ ),
+ ProjectWorkspace::Cargo {
+ cargo,
+ sysroot,
+ rustc,
+ rustc_cfg,
+ cfg_overrides,
+ build_scripts,
+ toolchain: _,
+ } => cargo_to_crate_graph(
+ rustc_cfg.clone(),
+ cfg_overrides,
+ load_proc_macro,
+ load,
+ cargo,
+ build_scripts,
+ sysroot.as_ref(),
+ rustc,
+ ),
+ ProjectWorkspace::DetachedFiles { files, sysroot, rustc_cfg } => {
+ detached_files_to_crate_graph(rustc_cfg.clone(), load, files, sysroot)
+ }
+ };
+ if crate_graph.patch_cfg_if() {
+ tracing::debug!("Patched std to depend on cfg-if")
+ } else {
+ tracing::debug!("Did not patch std to depend on cfg-if")
+ }
+ crate_graph
+ }
+}
+
+fn project_json_to_crate_graph(
+ rustc_cfg: Vec<CfgFlag>,
+ load_proc_macro: &mut dyn FnMut(&str, &AbsPath) -> ProcMacroLoadResult,
+ load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
+ project: &ProjectJson,
+ sysroot: &Option<Sysroot>,
- .or_insert_with(|| rustc_cfg::get(None, Some(target), config)),
++ extra_env: &FxHashMap<String, String>,
+) -> CrateGraph {
+ let mut crate_graph = CrateGraph::default();
+ let sysroot_deps = sysroot
+ .as_ref()
+ .map(|sysroot| sysroot_to_crate_graph(&mut crate_graph, sysroot, rustc_cfg.clone(), load));
+
+ let mut cfg_cache: FxHashMap<&str, Vec<CfgFlag>> = FxHashMap::default();
+ let crates: NoHashHashMap<CrateId, CrateId> = project
+ .crates()
+ .filter_map(|(crate_id, krate)| {
+ let file_path = &krate.root_module;
+ let file_id = load(file_path)?;
+ Some((crate_id, krate, file_id))
+ })
+ .map(|(crate_id, krate, file_id)| {
+ let env = krate.env.clone().into_iter().collect();
+ let proc_macro = match krate.proc_macro_dylib_path.clone() {
+ Some(it) => load_proc_macro(
+ krate.display_name.as_ref().map(|it| it.canonical_name()).unwrap_or(""),
+ &it,
+ ),
+ None => Err("no proc macro dylib present".into()),
+ };
+
+ let target_cfgs = match krate.target.as_deref() {
+ Some(target) => cfg_cache
+ .entry(target)
- CrateOrigin::CratesIo { repo: krate.repository.clone() }
++ .or_insert_with(|| rustc_cfg::get(None, Some(target), extra_env)),
+ None => &rustc_cfg,
+ };
+
+ let mut cfg_options = CfgOptions::default();
+ cfg_options.extend(target_cfgs.iter().chain(krate.cfg.iter()).cloned());
+ (
+ crate_id,
+ crate_graph.add_crate_root(
+ file_id,
+ krate.edition,
+ krate.display_name.clone(),
+ krate.version.clone(),
+ cfg_options.clone(),
+ cfg_options,
+ env,
+ proc_macro,
+ krate.is_proc_macro,
+ if krate.display_name.is_some() {
- CrateOrigin::CratesIo { repo: None }
++ CrateOrigin::CratesIo {
++ repo: krate.repository.clone(),
++ name: krate
++ .display_name
++ .clone()
++ .map(|n| n.canonical_name().to_string()),
++ }
+ } else {
- for (from, kind) in pkg_crates.get(&pkg).into_iter().flatten() {
++ CrateOrigin::CratesIo { repo: None, name: None }
+ },
+ ),
+ )
+ })
+ .collect();
+
+ for (from, krate) in project.crates() {
+ if let Some(&from) = crates.get(&from) {
+ if let Some((public_deps, libproc_macro)) = &sysroot_deps {
+ public_deps.add(from, &mut crate_graph);
+ if krate.is_proc_macro {
+ if let Some(proc_macro) = libproc_macro {
+ add_dep(
+ &mut crate_graph,
+ from,
+ CrateName::new("proc_macro").unwrap(),
+ *proc_macro,
+ );
+ }
+ }
+ }
+
+ for dep in &krate.deps {
+ if let Some(&to) = crates.get(&dep.crate_id) {
+ add_dep(&mut crate_graph, from, dep.name.clone(), to)
+ }
+ }
+ }
+ }
+ crate_graph
+}
+
+fn cargo_to_crate_graph(
+ rustc_cfg: Vec<CfgFlag>,
+ override_cfg: &CfgOverrides,
+ load_proc_macro: &mut dyn FnMut(&str, &AbsPath) -> ProcMacroLoadResult,
+ load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
+ cargo: &CargoWorkspace,
+ build_scripts: &WorkspaceBuildScripts,
+ sysroot: Option<&Sysroot>,
+ rustc: &Option<CargoWorkspace>,
+) -> CrateGraph {
+ let _p = profile::span("cargo_to_crate_graph");
+ let mut crate_graph = CrateGraph::default();
+ let (public_deps, libproc_macro) = match sysroot {
+ Some(sysroot) => sysroot_to_crate_graph(&mut crate_graph, sysroot, rustc_cfg.clone(), load),
+ None => (SysrootPublicDeps::default(), None),
+ };
+
+ let mut cfg_options = CfgOptions::default();
+ cfg_options.extend(rustc_cfg);
+
+ let mut pkg_to_lib_crate = FxHashMap::default();
+
+ cfg_options.insert_atom("debug_assertions".into());
+
+ let mut pkg_crates = FxHashMap::default();
+ // Does any crate signal to rust-analyzer that they need the rustc_private crates?
+ let mut has_private = false;
+ // Next, create crates for each package, target pair
+ for pkg in cargo.packages() {
+ let mut cfg_options = cfg_options.clone();
+
+ let overrides = match override_cfg {
+ CfgOverrides::Wildcard(cfg_diff) => Some(cfg_diff),
+ CfgOverrides::Selective(cfg_overrides) => cfg_overrides.get(&cargo[pkg].name),
+ };
+
+ // Add test cfg for local crates
+ if cargo[pkg].is_local {
+ cfg_options.insert_atom("test".into());
+ }
+
+ if let Some(overrides) = overrides {
+ // FIXME: this is sort of a hack to deal with #![cfg(not(test))] vanishing such as seen
+ // in ed25519_dalek (#7243), and libcore (#9203) (although you only hit that one while
+ // working on rust-lang/rust as that's the only time it appears outside sysroot).
+ //
+ // A more ideal solution might be to reanalyze crates based on where the cursor is and
+ // figure out the set of cfgs that would have to apply to make it active.
+
+ cfg_options.apply_diff(overrides.clone());
+ };
+
+ has_private |= cargo[pkg].metadata.rustc_private;
+ let mut lib_tgt = None;
+ for &tgt in cargo[pkg].targets.iter() {
+ if cargo[tgt].kind != TargetKind::Lib && !cargo[pkg].is_member {
+ // For non-workspace-members, Cargo does not resolve dev-dependencies, so we don't
+ // add any targets except the library target, since those will not work correctly if
+ // they use dev-dependencies.
+ // In fact, they can break quite badly if multiple client workspaces get merged:
+ // https://github.com/rust-lang/rust-analyzer/issues/11300
+ continue;
+ }
+
+ if let Some(file_id) = load(&cargo[tgt].root) {
+ let crate_id = add_target_crate_root(
+ &mut crate_graph,
+ &cargo[pkg],
+ build_scripts.get_output(pkg),
+ cfg_options.clone(),
+ &mut |path| load_proc_macro(&cargo[tgt].name, path),
+ file_id,
+ &cargo[tgt].name,
+ cargo[tgt].is_proc_macro,
+ );
+ if cargo[tgt].kind == TargetKind::Lib {
+ lib_tgt = Some((crate_id, cargo[tgt].name.clone()));
+ pkg_to_lib_crate.insert(pkg, crate_id);
+ }
++ // Even crates that don't set proc-macro = true are allowed to depend on proc_macro
++ // (just none of the APIs work when called outside of a proc macro).
+ if let Some(proc_macro) = libproc_macro {
+ add_dep_with_prelude(
+ &mut crate_graph,
+ crate_id,
+ CrateName::new("proc_macro").unwrap(),
+ proc_macro,
+ cargo[tgt].is_proc_macro,
+ );
+ }
+
+ pkg_crates.entry(pkg).or_insert_with(Vec::new).push((crate_id, cargo[tgt].kind));
+ }
+ }
+
+ // Set deps to the core, std and to the lib target of the current package
- public_deps.add(*from, &mut crate_graph);
++ for &(from, kind) in pkg_crates.get(&pkg).into_iter().flatten() {
+ // Add sysroot deps first so that a lib target named `core` etc. can overwrite them.
- if to != *from && *kind != TargetKind::BuildScript {
++ public_deps.add(from, &mut crate_graph);
+
+ if let Some((to, name)) = lib_tgt.clone() {
- add_dep(&mut crate_graph, *from, name, to);
++ if to != from && kind != TargetKind::BuildScript {
+ // (build script can not depend on its library target)
+
+ // For root projects with dashes in their name,
+ // cargo metadata does not do any normalization,
+ // so we do it ourselves currently
+ let name = CrateName::normalize_dashes(&name);
- for (from, kind) in pkg_crates.get(&pkg).into_iter().flatten() {
- if dep.kind == DepKind::Build && *kind != TargetKind::BuildScript {
++ add_dep(&mut crate_graph, from, name, to);
+ }
+ }
+ }
+ }
+
+ // Now add a dep edge from all targets of upstream to the lib
+ // target of downstream.
+ for pkg in cargo.packages() {
+ for dep in cargo[pkg].dependencies.iter() {
+ let name = CrateName::new(&dep.name).unwrap();
+ if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) {
- if dep.kind != DepKind::Build && *kind == TargetKind::BuildScript {
++ for &(from, kind) in pkg_crates.get(&pkg).into_iter().flatten() {
++ if dep.kind == DepKind::Build && kind != TargetKind::BuildScript {
+ // Only build scripts may depend on build dependencies.
+ continue;
+ }
- add_dep(&mut crate_graph, *from, name.clone(), to)
++ if dep.kind != DepKind::Build && kind == TargetKind::BuildScript {
+ // Build scripts may only depend on build dependencies.
+ continue;
+ }
+
- &mut crate_graph,
++ add_dep(&mut crate_graph, from, name.clone(), to)
+ }
+ }
+ }
+ }
+
+ if has_private {
+ // If the user provided a path to rustc sources, we add all the rustc_private crates
+ // and create dependencies on them for the crates which opt-in to that
+ if let Some(rustc_workspace) = rustc {
+ handle_rustc_crates(
++ &mut crate_graph,
+ rustc_workspace,
+ load,
- display_name,
+ &cfg_options,
+ override_cfg,
+ load_proc_macro,
+ &mut pkg_to_lib_crate,
+ &public_deps,
+ cargo,
+ &pkg_crates,
+ build_scripts,
+ );
+ }
+ }
+ crate_graph
+}
+
+fn detached_files_to_crate_graph(
+ rustc_cfg: Vec<CfgFlag>,
+ load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
+ detached_files: &[AbsPathBuf],
+ sysroot: &Sysroot,
+) -> CrateGraph {
+ let _p = profile::span("detached_files_to_crate_graph");
+ let mut crate_graph = CrateGraph::default();
+ let (public_deps, _libproc_macro) =
+ sysroot_to_crate_graph(&mut crate_graph, sysroot, rustc_cfg.clone(), load);
+
+ let mut cfg_options = CfgOptions::default();
+ cfg_options.extend(rustc_cfg);
+
+ for detached_file in detached_files {
+ let file_id = match load(detached_file) {
+ Some(file_id) => file_id,
+ None => {
+ tracing::error!("Failed to load detached file {:?}", detached_file);
+ continue;
+ }
+ };
+ let display_name = detached_file
+ .file_stem()
+ .and_then(|os_str| os_str.to_str())
+ .map(|file_stem| CrateDisplayName::from_canonical_name(file_stem.to_string()));
+ let detached_file_crate = crate_graph.add_crate_root(
+ file_id,
+ Edition::CURRENT,
- CrateOrigin::CratesIo { repo: None },
++ display_name.clone(),
+ None,
+ cfg_options.clone(),
+ cfg_options.clone(),
+ Env::default(),
+ Ok(Vec::new()),
+ false,
- crate_graph: &mut CrateGraph,
++ CrateOrigin::CratesIo {
++ repo: None,
++ name: display_name.map(|n| n.canonical_name().to_string()),
++ },
+ );
+
+ public_deps.add(detached_file_crate, &mut crate_graph);
+ }
+ crate_graph
+}
+
+fn handle_rustc_crates(
++ crate_graph: &mut CrateGraph,
+ rustc_workspace: &CargoWorkspace,
+ load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
- pkg_to_lib_crate: &mut FxHashMap<la_arena::Idx<crate::PackageData>, CrateId>,
+ cfg_options: &CfgOptions,
+ override_cfg: &CfgOverrides,
+ load_proc_macro: &mut dyn FnMut(&str, &AbsPath) -> ProcMacroLoadResult,
- pkg_crates: &FxHashMap<la_arena::Idx<crate::PackageData>, Vec<(CrateId, TargetKind)>>,
++ pkg_to_lib_crate: &mut FxHashMap<Package, CrateId>,
+ public_deps: &SysrootPublicDeps,
+ cargo: &CargoWorkspace,
- // Don't duplicate packages if they are dependended on a diamond pattern
- // N.B. if this line is omitted, we try to analyse over 4_800_000 crates
++ pkg_crates: &FxHashMap<Package, Vec<(CrateId, TargetKind)>>,
+ build_scripts: &WorkspaceBuildScripts,
+) {
+ let mut rustc_pkg_crates = FxHashMap::default();
+ // The root package of the rustc-dev component is rustc_driver, so we match that
+ let root_pkg =
+ rustc_workspace.packages().find(|package| rustc_workspace[*package].name == "rustc_driver");
+ // The rustc workspace might be incomplete (such as if rustc-dev is not
+ // installed for the current toolchain) and `rustc_source` is set to discover.
+ if let Some(root_pkg) = root_pkg {
+ // Iterate through every crate in the dependency subtree of rustc_driver using BFS
+ let mut queue = VecDeque::new();
+ queue.push_back(root_pkg);
+ while let Some(pkg) = queue.pop_front() {
- CrateOrigin::CratesIo { repo: pkg.repository.clone() },
++ // Don't duplicate packages if they are dependent on a diamond pattern
++ // N.B. if this line is omitted, we try to analyze over 4_800_000 crates
+ // which is not ideal
+ if rustc_pkg_crates.contains_key(&pkg) {
+ continue;
+ }
+ for dep in &rustc_workspace[pkg].dependencies {
+ queue.push_back(dep.pkg);
+ }
+
+ let mut cfg_options = cfg_options.clone();
+
+ let overrides = match override_cfg {
+ CfgOverrides::Wildcard(cfg_diff) => Some(cfg_diff),
+ CfgOverrides::Selective(cfg_overrides) => {
+ cfg_overrides.get(&rustc_workspace[pkg].name)
+ }
+ };
+
+ if let Some(overrides) = overrides {
+ // FIXME: this is sort of a hack to deal with #![cfg(not(test))] vanishing such as seen
+ // in ed25519_dalek (#7243), and libcore (#9203) (although you only hit that one while
+ // working on rust-lang/rust as that's the only time it appears outside sysroot).
+ //
+ // A more ideal solution might be to reanalyze crates based on where the cursor is and
+ // figure out the set of cfgs that would have to apply to make it active.
+
+ cfg_options.apply_diff(overrides.clone());
+ };
+
+ for &tgt in rustc_workspace[pkg].targets.iter() {
+ if rustc_workspace[tgt].kind != TargetKind::Lib {
+ continue;
+ }
+ if let Some(file_id) = load(&rustc_workspace[tgt].root) {
+ let crate_id = add_target_crate_root(
+ crate_graph,
+ &rustc_workspace[pkg],
+ build_scripts.get_output(pkg),
+ cfg_options.clone(),
+ &mut |path| load_proc_macro(&rustc_workspace[tgt].name, path),
+ file_id,
+ &rustc_workspace[tgt].name,
+ rustc_workspace[tgt].is_proc_macro,
+ );
+ pkg_to_lib_crate.insert(pkg, crate_id);
+ // Add dependencies on core / std / alloc for this crate
+ public_deps.add(crate_id, crate_graph);
+ rustc_pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id);
+ }
+ }
+ }
+ }
+ // Now add a dep edge from all targets of upstream to the lib
+ // target of downstream.
+ for pkg in rustc_pkg_crates.keys().copied() {
+ for dep in rustc_workspace[pkg].dependencies.iter() {
+ let name = CrateName::new(&dep.name).unwrap();
+ if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) {
+ for &from in rustc_pkg_crates.get(&pkg).into_iter().flatten() {
+ add_dep(crate_graph, from, name.clone(), to);
+ }
+ }
+ }
+ }
+ // Add a dependency on the rustc_private crates for all targets of each package
+ // which opts in
+ for dep in rustc_workspace.packages() {
+ let name = CrateName::normalize_dashes(&rustc_workspace[dep].name);
+
+ if let Some(&to) = pkg_to_lib_crate.get(&dep) {
+ for pkg in cargo.packages() {
+ let package = &cargo[pkg];
+ if !package.metadata.rustc_private {
+ continue;
+ }
+ for (from, _) in pkg_crates.get(&pkg).into_iter().flatten() {
+ // Avoid creating duplicate dependencies
+ // This avoids the situation where `from` depends on e.g. `arrayvec`, but
+ // `rust_analyzer` thinks that it should use the one from the `rustc_source`
+ // instead of the one from `crates.io`
+ if !crate_graph[*from].dependencies.iter().any(|d| d.name == name) {
+ add_dep(crate_graph, *from, name.clone(), to);
+ }
+ }
+ }
+ }
+ }
+}
+
+fn add_target_crate_root(
+ crate_graph: &mut CrateGraph,
+ pkg: &PackageData,
+ build_data: Option<&BuildScriptOutput>,
+ cfg_options: CfgOptions,
+ load_proc_macro: &mut dyn FnMut(&AbsPath) -> ProcMacroLoadResult,
+ file_id: FileId,
+ cargo_name: &str,
+ is_proc_macro: bool,
+) -> CrateId {
+ let edition = pkg.edition;
+ let mut potential_cfg_options = cfg_options.clone();
+ potential_cfg_options.extend(
+ pkg.features
+ .iter()
+ .map(|feat| CfgFlag::KeyValue { key: "feature".into(), value: feat.0.into() }),
+ );
+ let cfg_options = {
+ let mut opts = cfg_options;
+ for feature in pkg.active_features.iter() {
+ opts.insert_key_value("feature".into(), feature.into());
+ }
+ if let Some(cfgs) = build_data.as_ref().map(|it| &it.cfgs) {
+ opts.extend(cfgs.iter().cloned());
+ }
+ opts
+ };
+
+ let mut env = Env::default();
+ inject_cargo_env(pkg, &mut env);
+
+ if let Some(envs) = build_data.map(|it| &it.envs) {
+ for (k, v) in envs {
+ env.set(k, v.clone());
+ }
+ }
+
+ let proc_macro = match build_data.as_ref().map(|it| it.proc_macro_dylib_path.as_ref()) {
+ Some(Some(it)) => load_proc_macro(it),
+ Some(None) => Err("no proc macro dylib present".into()),
+ None => Err("crate has not (yet) been built".into()),
+ };
+
+ let display_name = CrateDisplayName::from_canonical_name(cargo_name.to_string());
+ crate_graph.add_crate_root(
+ file_id,
+ edition,
+ Some(display_name),
+ Some(pkg.version.to_string()),
+ cfg_options,
+ potential_cfg_options,
+ env,
+ proc_macro,
+ is_proc_macro,
++ CrateOrigin::CratesIo { repo: pkg.repository.clone(), name: Some(pkg.name.clone()) },
+ )
+}
+
+#[derive(Default)]
+struct SysrootPublicDeps {
+ deps: Vec<(CrateName, CrateId, bool)>,
+}
+
+impl SysrootPublicDeps {
+ /// Makes `from` depend on the public sysroot crates.
+ fn add(&self, from: CrateId, crate_graph: &mut CrateGraph) {
+ for (name, krate, prelude) in &self.deps {
+ add_dep_with_prelude(crate_graph, from, name.clone(), *krate, *prelude);
+ }
+ }
+}
+
+fn sysroot_to_crate_graph(
+ crate_graph: &mut CrateGraph,
+ sysroot: &Sysroot,
+ rustc_cfg: Vec<CfgFlag>,
+ load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
+) -> (SysrootPublicDeps, Option<CrateId>) {
+ let _p = profile::span("sysroot_to_crate_graph");
+ let mut cfg_options = CfgOptions::default();
+ cfg_options.extend(rustc_cfg);
+ let sysroot_crates: FxHashMap<SysrootCrate, CrateId> = sysroot
+ .crates()
+ .filter_map(|krate| {
+ let file_id = load(&sysroot[krate].root)?;
+
+ let env = Env::default();
+ let display_name = CrateDisplayName::from_canonical_name(sysroot[krate].name.clone());
+ let crate_id = crate_graph.add_crate_root(
+ file_id,
+ Edition::CURRENT,
+ Some(display_name),
+ None,
+ cfg_options.clone(),
+ cfg_options.clone(),
+ env,
+ Err("no proc macro loaded for sysroot crate".into()),
+ false,
+ CrateOrigin::Lang(LangCrateOrigin::from(&*sysroot[krate].name)),
+ );
+ Some((krate, crate_id))
+ })
+ .collect();
+
+ for from in sysroot.crates() {
+ for &to in sysroot[from].deps.iter() {
+ let name = CrateName::new(&sysroot[to].name).unwrap();
+ if let (Some(&from), Some(&to)) = (sysroot_crates.get(&from), sysroot_crates.get(&to)) {
+ add_dep(crate_graph, from, name, to);
+ }
+ }
+ }
+
+ let public_deps = SysrootPublicDeps {
+ deps: sysroot
+ .public_deps()
+ .map(|(name, idx, prelude)| {
+ (CrateName::new(name).unwrap(), sysroot_crates[&idx], prelude)
+ })
+ .collect::<Vec<_>>(),
+ };
+
+ let libproc_macro = sysroot.proc_macro().and_then(|it| sysroot_crates.get(&it).copied());
+ (public_deps, libproc_macro)
+}
+
+fn add_dep(graph: &mut CrateGraph, from: CrateId, name: CrateName, to: CrateId) {
+ add_dep_inner(graph, from, Dependency::new(name, to))
+}
+
+fn add_dep_with_prelude(
+ graph: &mut CrateGraph,
+ from: CrateId,
+ name: CrateName,
+ to: CrateId,
+ prelude: bool,
+) {
+ add_dep_inner(graph, from, Dependency::with_prelude(name, to, prelude))
+}
+
+fn add_dep_inner(graph: &mut CrateGraph, from: CrateId, dep: Dependency) {
+ if let Err(err) = graph.add_dep(from, dep) {
+ tracing::error!("{}", err)
+ }
+}
+
+/// Recreates the compile-time environment variables that Cargo sets.
+///
+/// Should be synced with
+/// <https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates>
+///
+/// FIXME: ask Cargo to provide this data instead of re-deriving.
+fn inject_cargo_env(package: &PackageData, env: &mut Env) {
+ // FIXME: Missing variables:
+ // CARGO_BIN_NAME, CARGO_BIN_EXE_<name>
+
+ let manifest_dir = package.manifest.parent();
+ env.set("CARGO_MANIFEST_DIR", manifest_dir.as_os_str().to_string_lossy().into_owned());
+
+ // Not always right, but works for common cases.
+ env.set("CARGO", "cargo".into());
+
+ env.set("CARGO_PKG_VERSION", package.version.to_string());
+ env.set("CARGO_PKG_VERSION_MAJOR", package.version.major.to_string());
+ env.set("CARGO_PKG_VERSION_MINOR", package.version.minor.to_string());
+ env.set("CARGO_PKG_VERSION_PATCH", package.version.patch.to_string());
+ env.set("CARGO_PKG_VERSION_PRE", package.version.pre.to_string());
+
+ env.set("CARGO_PKG_AUTHORS", String::new());
+
+ env.set("CARGO_PKG_NAME", package.name.clone());
+ // FIXME: This isn't really correct (a package can have many crates with different names), but
+ // it's better than leaving the variable unset.
+ env.set("CARGO_CRATE_NAME", CrateName::normalize_dashes(&package.name).to_string());
+ env.set("CARGO_PKG_DESCRIPTION", String::new());
+ env.set("CARGO_PKG_HOMEPAGE", String::new());
+ env.set("CARGO_PKG_REPOSITORY", String::new());
+ env.set("CARGO_PKG_LICENSE", String::new());
+
+ env.set("CARGO_PKG_LICENSE_FILE", String::new());
+}
--- /dev/null
- xflags = "0.2.4"
+[package]
+name = "rust-analyzer"
+version = "0.0.0"
+authors = ["rust-analyzer Team"]
+homepage = "https://github.com/rust-analyzer/rust-analyzer"
+description = "A language server for the Rust programming language"
+documentation = "https://rust-analyzer.github.io/manual.html"
+license = "MIT OR Apache-2.0"
+autobins = false
+edition = "2021"
+rust-version = "1.57"
+
+[lib]
+doctest = false
+
+[[bin]]
+name = "rust-analyzer"
+path = "src/bin/main.rs"
+
+[dependencies]
+anyhow = "1.0.57"
+crossbeam-channel = "0.5.5"
+dissimilar = "1.0.4"
+itertools = "0.10.3"
+scip = "0.1.1"
+lsp-types = { version = "0.93.1", features = ["proposed"] }
+parking_lot = "0.12.1"
- lsp-server = { version = "0.6.0", path = "../../lib/lsp-server" }
++xflags = "0.3.0"
+oorandom = "11.1.3"
+rustc-hash = "1.1.0"
+serde = { version = "1.0.137", features = ["derive"] }
+serde_json = { version = "1.0.81", features = ["preserve_order"] }
+threadpool = "1.8.1"
+rayon = "1.5.3"
+num_cpus = "1.13.1"
+mimalloc = { version = "0.1.29", default-features = false, optional = true }
- "sourcegen/in-rust-tree",
++lsp-server = { version = "0.7.0", path = "../../lib/lsp-server" }
+tracing = "0.1.35"
+tracing-subscriber = { version = "0.3.14", default-features = false, features = [
+ "env-filter",
+ "registry",
+ "fmt",
+ "tracing-log",
+] }
+tracing-log = "0.1.3"
+tracing-tree = "0.2.1"
+always-assert = "0.1.2"
+
+stdx = { path = "../stdx", version = "0.0.0" }
+flycheck = { path = "../flycheck", version = "0.0.0" }
+ide = { path = "../ide", version = "0.0.0" }
+ide-db = { path = "../ide-db", version = "0.0.0" }
+profile = { path = "../profile", version = "0.0.0" }
+project-model = { path = "../project-model", version = "0.0.0" }
+syntax = { path = "../syntax", version = "0.0.0" }
+vfs = { path = "../vfs", version = "0.0.0" }
+vfs-notify = { path = "../vfs-notify", version = "0.0.0" }
+cfg = { path = "../cfg", version = "0.0.0" }
+toolchain = { path = "../toolchain", version = "0.0.0" }
+tt = { path = "../tt", version = "0.0.0" }
+proc-macro-api = { path = "../proc-macro-api", version = "0.0.0" }
+
+# This should only be used in CLI
+ide-ssr = { path = "../ide-ssr", version = "0.0.0" }
+hir = { path = "../hir", version = "0.0.0" }
+hir-def = { path = "../hir-def", version = "0.0.0" }
+hir-ty = { path = "../hir-ty", version = "0.0.0" }
+proc-macro-srv = { path = "../proc-macro-srv", version = "0.0.0" }
+
+[target.'cfg(windows)'.dependencies]
+winapi = "0.3.9"
+
+[target.'cfg(not(target_env = "msvc"))'.dependencies]
+jemallocator = { version = "0.5.0", package = "tikv-jemallocator", optional = true }
+
+[dev-dependencies]
+expect-test = "1.4.0"
+jod-thread = "0.1.2"
+xshell = "0.2.2"
+
+test-utils = { path = "../test-utils" }
+sourcegen = { path = "../sourcegen" }
+mbe = { path = "../mbe" }
+
+[features]
+jemalloc = ["jemallocator", "profile/jemalloc"]
+force-always-assert = ["always-assert/force"]
+in-rust-tree = [
+ "proc-macro-srv/sysroot-abi",
+ "ide/in-rust-tree",
+ "syntax/in-rust-tree",
+]
--- /dev/null
- if let Err(err) = try_main() {
+//! Driver for rust-analyzer.
+//!
+//! Based on cli flags, either spawns an LSP server, or runs a batch analysis
+
+#![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)]
+
+mod logger;
+mod rustc_wrapper;
+
+use std::{env, fs, path::Path, process};
+
+use lsp_server::Connection;
+use project_model::ProjectManifest;
+use rust_analyzer::{cli::flags, config::Config, from_json, lsp_ext::supports_utf8, Result};
+use vfs::AbsPathBuf;
+
+#[cfg(all(feature = "mimalloc"))]
+#[global_allocator]
+static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc;
+
+#[cfg(all(feature = "jemalloc", not(target_env = "msvc")))]
+#[global_allocator]
+static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
+
+fn main() {
+ if std::env::var("RA_RUSTC_WRAPPER").is_ok() {
+ let mut args = std::env::args_os();
+ let _me = args.next().unwrap();
+ let rustc = args.next().unwrap();
+ let code = match rustc_wrapper::run_rustc_skipping_cargo_checking(rustc, args.collect()) {
+ Ok(rustc_wrapper::ExitCode(code)) => code.unwrap_or(102),
+ Err(err) => {
+ eprintln!("{}", err);
+ 101
+ }
+ };
+ process::exit(code);
+ }
+
- fn try_main() -> Result<()> {
- let flags = flags::RustAnalyzer::from_env()?;
-
++ let flags = flags::RustAnalyzer::from_env_or_exit();
++ if let Err(err) = try_main(flags) {
+ tracing::error!("Unexpected error: {}", err);
+ eprintln!("{}", err);
+ process::exit(101);
+ }
+}
+
- if cmd.help {
- println!("{}", flags::RustAnalyzer::HELP);
- return Ok(());
- }
++fn try_main(flags: flags::RustAnalyzer) -> Result<()> {
+ #[cfg(debug_assertions)]
+ if flags.wait_dbg || env::var("RA_WAIT_DBG").is_ok() {
+ #[allow(unused_mut)]
+ let mut d = 4;
+ while d == 4 {
+ d = 4;
+ }
+ }
+
+ let mut log_file = flags.log_file.as_deref();
+
+ let env_log_file = env::var("RA_LOG_FILE").ok();
+ if let Some(env_log_file) = env_log_file.as_deref() {
+ log_file = Some(Path::new(env_log_file));
+ }
+
+ setup_logging(log_file)?;
+ let verbosity = flags.verbosity();
+
+ match flags.subcommand {
+ flags::RustAnalyzerCmd::LspServer(cmd) => {
+ if cmd.print_config_schema {
+ println!("{:#}", Config::json_schema());
+ return Ok(());
+ }
+ if cmd.version {
+ println!("rust-analyzer {}", rust_analyzer::version());
+ return Ok(());
+ }
+ with_extra_thread("LspServer", run_server)?;
+ }
+ flags::RustAnalyzerCmd::ProcMacro(flags::ProcMacro) => {
+ with_extra_thread("MacroExpander", || proc_macro_srv::cli::run().map_err(Into::into))?;
+ }
+ flags::RustAnalyzerCmd::Parse(cmd) => cmd.run()?,
+ flags::RustAnalyzerCmd::Symbols(cmd) => cmd.run()?,
+ flags::RustAnalyzerCmd::Highlight(cmd) => cmd.run()?,
+ flags::RustAnalyzerCmd::AnalysisStats(cmd) => cmd.run(verbosity)?,
+ flags::RustAnalyzerCmd::Diagnostics(cmd) => cmd.run()?,
+ flags::RustAnalyzerCmd::Ssr(cmd) => cmd.run()?,
+ flags::RustAnalyzerCmd::Search(cmd) => cmd.run()?,
+ flags::RustAnalyzerCmd::Lsif(cmd) => cmd.run()?,
+ flags::RustAnalyzerCmd::Scip(cmd) => cmd.run()?,
+ }
+ Ok(())
+}
+
+fn setup_logging(log_file: Option<&Path>) -> Result<()> {
+ if cfg!(windows) {
+ // This is required so that windows finds our pdb that is placed right beside the exe.
+ // By default it doesn't look at the folder the exe resides in, only in the current working
+ // directory which we set to the project workspace.
+ // https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/general-environment-variables
+ // https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-syminitialize
+ if let Ok(path) = env::current_exe() {
+ if let Some(path) = path.parent() {
+ env::set_var("_NT_SYMBOL_PATH", path);
+ }
+ }
+ }
+ if env::var("RUST_BACKTRACE").is_err() {
+ env::set_var("RUST_BACKTRACE", "short");
+ }
+
+ let log_file = match log_file {
+ Some(path) => {
+ if let Some(parent) = path.parent() {
+ let _ = fs::create_dir_all(parent);
+ }
+ Some(fs::File::create(path)?)
+ }
+ None => None,
+ };
+ let filter = env::var("RA_LOG").ok();
+ // deliberately enable all `error` logs if the user has not set RA_LOG, as there is usually useful
+ // information in there for debugging
+ logger::Logger::new(log_file, filter.as_deref().or(Some("error"))).install()?;
+
+ profile::init();
+
+ Ok(())
+}
+
+const STACK_SIZE: usize = 1024 * 1024 * 8;
+
+/// Parts of rust-analyzer can use a lot of stack space, and some operating systems only give us
+/// 1 MB by default (eg. Windows), so this spawns a new thread with hopefully sufficient stack
+/// space.
+fn with_extra_thread(
+ thread_name: impl Into<String>,
+ f: impl FnOnce() -> Result<()> + Send + 'static,
+) -> Result<()> {
+ let handle =
+ std::thread::Builder::new().name(thread_name.into()).stack_size(STACK_SIZE).spawn(f)?;
+ match handle.join() {
+ Ok(res) => res,
+ Err(panic) => std::panic::resume_unwind(panic),
+ }
+}
+
+fn run_server() -> Result<()> {
+ tracing::info!("server version {} will start", rust_analyzer::version());
+
+ let (connection, io_threads) = Connection::stdio();
+
+ let (initialize_id, initialize_params) = connection.initialize_start()?;
+ tracing::info!("InitializeParams: {}", initialize_params);
+ let initialize_params =
+ from_json::<lsp_types::InitializeParams>("InitializeParams", &initialize_params)?;
+
+ let root_path = match initialize_params
+ .root_uri
+ .and_then(|it| it.to_file_path().ok())
+ .and_then(|it| AbsPathBuf::try_from(it).ok())
+ {
+ Some(it) => it,
+ None => {
+ let cwd = env::current_dir()?;
+ AbsPathBuf::assert(cwd)
+ }
+ };
+
+ let mut config = Config::new(root_path, initialize_params.capabilities);
+ if let Some(json) = initialize_params.initialization_options {
+ if let Err(e) = config.update(json) {
+ use lsp_types::{
+ notification::{Notification, ShowMessage},
+ MessageType, ShowMessageParams,
+ };
+ let not = lsp_server::Notification::new(
+ ShowMessage::METHOD.to_string(),
+ ShowMessageParams { typ: MessageType::WARNING, message: e.to_string() },
+ );
+ connection.sender.send(lsp_server::Message::Notification(not)).unwrap();
+ }
+ }
+
+ let server_capabilities = rust_analyzer::server_capabilities(&config);
+
+ let initialize_result = lsp_types::InitializeResult {
+ capabilities: server_capabilities,
+ server_info: Some(lsp_types::ServerInfo {
+ name: String::from("rust-analyzer"),
+ version: Some(rust_analyzer::version().to_string()),
+ }),
+ offset_encoding: if supports_utf8(config.caps()) {
+ Some("utf-8".to_string())
+ } else {
+ None
+ },
+ };
+
+ let initialize_result = serde_json::to_value(initialize_result).unwrap();
+
+ connection.initialize_finish(initialize_id, initialize_result)?;
+
+ if let Some(client_info) = initialize_params.client_info {
+ tracing::info!("Client '{}' {}", client_info.name, client_info.version.unwrap_or_default());
+ }
+
+ if config.linked_projects().is_empty() && config.detached_files().is_empty() {
+ let workspace_roots = initialize_params
+ .workspace_folders
+ .map(|workspaces| {
+ workspaces
+ .into_iter()
+ .filter_map(|it| it.uri.to_file_path().ok())
+ .filter_map(|it| AbsPathBuf::try_from(it).ok())
+ .collect::<Vec<_>>()
+ })
+ .filter(|workspaces| !workspaces.is_empty())
+ .unwrap_or_else(|| vec![config.root_path().clone()]);
+
+ let discovered = ProjectManifest::discover_all(&workspace_roots);
+ tracing::info!("discovered projects: {:?}", discovered);
+ if discovered.is_empty() {
+ tracing::error!("failed to find any projects in {:?}", workspace_roots);
+ }
+ config.discovered_projects = Some(discovered);
+ }
+
+ rust_analyzer::main_loop(config, connection)?;
+
+ io_threads.join()?;
+ tracing::info!("server did shut down");
+ Ok(())
+}
--- /dev/null
- use project_model::{self, ManifestPath, TargetKind};
+//! See `CargoTargetSpec`
+
+use std::mem;
+
+use cfg::{CfgAtom, CfgExpr};
+use ide::{FileId, RunnableKind, TestId};
- args.push("test".to_string());
++use project_model::{self, CargoFeatures, ManifestPath, TargetKind};
+use vfs::AbsPathBuf;
+
+use crate::{global_state::GlobalStateSnapshot, Result};
+
+/// Abstract representation of Cargo target.
+///
+/// We use it to cook up the set of cli args we need to pass to Cargo to
+/// build/test/run the target.
+#[derive(Clone)]
+pub(crate) struct CargoTargetSpec {
+ pub(crate) workspace_root: AbsPathBuf,
+ pub(crate) cargo_toml: ManifestPath,
+ pub(crate) package: String,
+ pub(crate) target: String,
+ pub(crate) target_kind: TargetKind,
+ pub(crate) required_features: Vec<String>,
+}
+
+impl CargoTargetSpec {
+ pub(crate) fn runnable_args(
+ snap: &GlobalStateSnapshot,
+ spec: Option<CargoTargetSpec>,
+ kind: &RunnableKind,
+ cfg: &Option<CfgExpr>,
+ ) -> Result<(Vec<String>, Vec<String>)> {
+ let mut args = Vec::new();
+ let mut extra_args = Vec::new();
+
+ match kind {
+ RunnableKind::Test { test_id, attr } => {
- extra_args.push("--exact".to_string());
++ args.push("test".to_owned());
+ extra_args.push(test_id.to_string());
+ if let TestId::Path(_) = test_id {
- extra_args.push("--nocapture".to_string());
++ extra_args.push("--exact".to_owned());
+ }
- extra_args.push("--ignored".to_string());
++ extra_args.push("--nocapture".to_owned());
+ if attr.ignore {
- args.push("test".to_string());
- extra_args.push(path.to_string());
- extra_args.push("--nocapture".to_string());
++ extra_args.push("--ignored".to_owned());
+ }
+ }
+ RunnableKind::TestMod { path } => {
- args.push("bench".to_string());
++ args.push("test".to_owned());
++ extra_args.push(path.clone());
++ extra_args.push("--nocapture".to_owned());
+ }
+ RunnableKind::Bench { test_id } => {
- extra_args.push("--exact".to_string());
++ args.push("bench".to_owned());
+ extra_args.push(test_id.to_string());
+ if let TestId::Path(_) = test_id {
- extra_args.push("--nocapture".to_string());
++ extra_args.push("--exact".to_owned());
+ }
- args.push("test".to_string());
- args.push("--doc".to_string());
++ extra_args.push("--nocapture".to_owned());
+ }
+ RunnableKind::DocTest { test_id } => {
- extra_args.push("--nocapture".to_string());
++ args.push("test".to_owned());
++ args.push("--doc".to_owned());
+ extra_args.push(test_id.to_string());
- args.push(subcommand.to_string());
++ extra_args.push("--nocapture".to_owned());
+ }
+ RunnableKind::Bin => {
+ let subcommand = match spec {
+ Some(CargoTargetSpec { target_kind: TargetKind::Test, .. }) => "test",
+ _ => "run",
+ };
- if cargo_config.all_features {
- args.push("--all-features".to_string());
++ args.push(subcommand.to_owned());
+ }
+ }
+
+ let target_required_features = if let Some(mut spec) = spec {
+ let required_features = mem::take(&mut spec.required_features);
+ spec.push_to(&mut args, kind);
+ required_features
+ } else {
+ Vec::new()
+ };
+
+ let cargo_config = snap.config.cargo();
- for feature in target_required_features {
- args.push("--features".to_string());
- args.push(feature);
- }
- } else {
- let mut features = Vec::new();
- if let Some(cfg) = cfg.as_ref() {
- required_features(cfg, &mut features);
+
- features.extend(cargo_config.features);
- features.extend(target_required_features);
++ match &cargo_config.features {
++ CargoFeatures::All => {
++ args.push("--all-features".to_owned());
++ for feature in target_required_features {
++ args.push("--features".to_owned());
++ args.push(feature);
++ }
+ }
++ CargoFeatures::Selected { features, no_default_features } => {
++ let mut feats = Vec::new();
++ if let Some(cfg) = cfg.as_ref() {
++ required_features(cfg, &mut feats);
++ }
+
- features.dedup();
- for feature in features {
- args.push("--features".to_string());
- args.push(feature);
++ feats.extend(features.iter().cloned());
++ feats.extend(target_required_features);
+
-
++ feats.dedup();
++ for feature in feats {
++ args.push("--features".to_owned());
++ args.push(feature);
++ }
++
++ if *no_default_features {
++ args.push("--no-default-features".to_owned());
++ }
+ }
+ }
- buf.push("--package".to_string());
+ Ok((args, extra_args))
+ }
+
+ pub(crate) fn for_file(
+ global_state_snapshot: &GlobalStateSnapshot,
+ file_id: FileId,
+ ) -> Result<Option<CargoTargetSpec>> {
+ let crate_id = match &*global_state_snapshot.analysis.crate_for(file_id)? {
+ &[crate_id, ..] => crate_id,
+ _ => return Ok(None),
+ };
+ let (cargo_ws, target) = match global_state_snapshot.cargo_target_for_crate_root(crate_id) {
+ Some(it) => it,
+ None => return Ok(None),
+ };
+
+ let target_data = &cargo_ws[target];
+ let package_data = &cargo_ws[target_data.package];
+ let res = CargoTargetSpec {
+ workspace_root: cargo_ws.workspace_root().to_path_buf(),
+ cargo_toml: package_data.manifest.clone(),
+ package: cargo_ws.package_flag(package_data),
+ target: target_data.name.clone(),
+ target_kind: target_data.kind,
+ required_features: target_data.required_features.clone(),
+ };
+
+ Ok(Some(res))
+ }
+
+ pub(crate) fn push_to(self, buf: &mut Vec<String>, kind: &RunnableKind) {
- buf.push("--bin".to_string());
++ buf.push("--package".to_owned());
+ buf.push(self.package);
+
+ // Can't mix --doc with other target flags
+ if let RunnableKind::DocTest { .. } = kind {
+ return;
+ }
+ match self.target_kind {
+ TargetKind::Bin => {
- buf.push("--test".to_string());
++ buf.push("--bin".to_owned());
+ buf.push(self.target);
+ }
+ TargetKind::Test => {
- buf.push("--bench".to_string());
++ buf.push("--test".to_owned());
+ buf.push(self.target);
+ }
+ TargetKind::Bench => {
- buf.push("--example".to_string());
++ buf.push("--bench".to_owned());
+ buf.push(self.target);
+ }
+ TargetKind::Example => {
- buf.push("--lib".to_string());
++ buf.push("--example".to_owned());
+ buf.push(self.target);
+ }
+ TargetKind::Lib => {
++ buf.push("--lib".to_owned());
+ }
+ TargetKind::Other | TargetKind::BuildScript => (),
+ }
+ }
+}
+
+/// Fill minimal features needed
+fn required_features(cfg_expr: &CfgExpr, features: &mut Vec<String>) {
+ match cfg_expr {
+ CfgExpr::Atom(CfgAtom::KeyValue { key, value }) if key == "feature" => {
+ features.push(value.to_string())
+ }
+ CfgExpr::All(preds) => {
+ preds.iter().for_each(|cfg| required_features(cfg, features));
+ }
+ CfgExpr::Any(preds) => {
+ for cfg in preds {
+ let len_features = features.len();
+ required_features(cfg, features);
+ if len_features != features.len() {
+ break;
+ }
+ }
+ }
+ _ => {}
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use cfg::CfgExpr;
+ use mbe::syntax_node_to_token_tree;
+ use syntax::{
+ ast::{self, AstNode},
+ SmolStr,
+ };
+
+ fn check(cfg: &str, expected_features: &[&str]) {
+ let cfg_expr = {
+ let source_file = ast::SourceFile::parse(cfg).ok().unwrap();
+ let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
+ let (tt, _) = syntax_node_to_token_tree(tt.syntax());
+ CfgExpr::parse(&tt)
+ };
+
+ let mut features = vec![];
+ required_features(&cfg_expr, &mut features);
+
+ let expected_features =
+ expected_features.iter().map(|&it| SmolStr::new(it)).collect::<Vec<_>>();
+
+ assert_eq!(features, expected_features);
+ }
+
+ #[test]
+ fn test_cfg_expr_minimal_features_needed() {
+ check(r#"#![cfg(feature = "baz")]"#, &["baz"]);
+ check(r#"#![cfg(all(feature = "baz", feature = "foo"))]"#, &["baz", "foo"]);
+ check(r#"#![cfg(any(feature = "baz", feature = "foo", unix))]"#, &["baz"]);
+ check(r#"#![cfg(foo)]"#, &[]);
+ }
+}
--- /dev/null
- use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace};
+//! Fully type-check project and print various stats, like the number of type
+//! errors.
+
+use std::{
+ env,
+ time::{SystemTime, UNIX_EPOCH},
+};
+
+use hir::{
+ db::{AstDatabase, DefDatabase, HirDatabase},
+ AssocItem, Crate, Function, HasSource, HirDisplay, ModuleDef,
+};
+use hir_def::{
+ body::{BodySourceMap, SyntheticSyntax},
+ expr::ExprId,
+ FunctionId,
+};
+use hir_ty::{TyExt, TypeWalk};
+use ide::{Analysis, AnalysisHost, LineCol, RootDatabase};
+use ide_db::base_db::{
+ salsa::{self, debug::DebugQueryTable, ParallelDatabase},
+ SourceDatabase, SourceDatabaseExt,
+};
+use itertools::Itertools;
+use oorandom::Rand32;
+use profile::{Bytes, StopWatch};
- cargo_config.no_sysroot = self.no_sysroot;
++use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace, RustcSource};
+use rayon::prelude::*;
+use rustc_hash::FxHashSet;
+use stdx::format_to;
+use syntax::{AstNode, SyntaxNode};
+use vfs::{AbsPathBuf, Vfs, VfsPath};
+
+use crate::cli::{
+ flags::{self, OutputFormat},
+ load_cargo::{load_workspace, LoadCargoConfig},
+ print_memory_usage,
+ progress_report::ProgressReport,
+ report_metric, Result, Verbosity,
+};
+
+/// Need to wrap Snapshot to provide `Clone` impl for `map_with`
+struct Snap<DB>(DB);
+impl<DB: ParallelDatabase> Clone for Snap<salsa::Snapshot<DB>> {
+ fn clone(&self) -> Snap<salsa::Snapshot<DB>> {
+ Snap(self.0.snapshot())
+ }
+}
+
+impl flags::AnalysisStats {
+ pub fn run(self, verbosity: Verbosity) -> Result<()> {
+ let mut rng = {
+ let seed = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as u64;
+ Rand32::new(seed)
+ };
+
+ let mut cargo_config = CargoConfig::default();
- load_workspace(workspace, &cargo_config, &load_cargo_config)?;
++ cargo_config.sysroot = match self.no_sysroot {
++ true => None,
++ false => Some(RustcSource::Discover),
++ };
+ let load_cargo_config = LoadCargoConfig {
+ load_out_dirs_from_check: !self.disable_build_scripts,
+ with_proc_macro: !self.disable_proc_macros,
+ prefill_caches: false,
+ };
+ let no_progress = &|_| ();
+
+ let mut db_load_sw = self.stop_watch();
+
+ let path = AbsPathBuf::assert(env::current_dir()?.join(&self.path));
+ let manifest = ProjectManifest::discover_single(&path)?;
+
+ let mut workspace = ProjectWorkspace::load(manifest, &cargo_config, no_progress)?;
+ let metadata_time = db_load_sw.elapsed();
+
+ let build_scripts_time = if self.disable_build_scripts {
+ None
+ } else {
+ let mut build_scripts_sw = self.stop_watch();
+ let bs = workspace.run_build_scripts(&cargo_config, no_progress)?;
+ workspace.set_build_scripts(bs);
+ Some(build_scripts_sw.elapsed())
+ };
+
+ let (host, vfs, _proc_macro) =
++ load_workspace(workspace, &cargo_config.extra_env, &load_cargo_config)?;
+ let db = host.raw_database();
+ eprint!("{:<20} {}", "Database loaded:", db_load_sw.elapsed());
+ eprint!(" (metadata {}", metadata_time);
+ if let Some(build_scripts_time) = build_scripts_time {
+ eprint!("; build {}", build_scripts_time);
+ }
+ eprintln!(")");
+
+ let mut analysis_sw = self.stop_watch();
+ let mut num_crates = 0;
+ let mut visited_modules = FxHashSet::default();
+ let mut visit_queue = Vec::new();
+
+ let mut krates = Crate::all(db);
+ if self.randomize {
+ shuffle(&mut rng, &mut krates);
+ }
+ for krate in krates {
+ let module = krate.root_module(db);
+ let file_id = module.definition_source(db).file_id;
+ let file_id = file_id.original_file(db);
+ let source_root = db.file_source_root(file_id);
+ let source_root = db.source_root(source_root);
+ if !source_root.is_library || self.with_deps {
+ num_crates += 1;
+ visit_queue.push(module);
+ }
+ }
+
+ if self.randomize {
+ shuffle(&mut rng, &mut visit_queue);
+ }
+
+ eprint!(" crates: {}", num_crates);
+ let mut num_decls = 0;
+ let mut funcs = Vec::new();
+ while let Some(module) = visit_queue.pop() {
+ if visited_modules.insert(module) {
+ visit_queue.extend(module.children(db));
+
+ for decl in module.declarations(db) {
+ num_decls += 1;
+ if let ModuleDef::Function(f) = decl {
+ funcs.push(f);
+ }
+ }
+
+ for impl_def in module.impl_defs(db) {
+ for item in impl_def.items(db) {
+ num_decls += 1;
+ if let AssocItem::Function(f) = item {
+ funcs.push(f);
+ }
+ }
+ }
+ }
+ }
+ eprintln!(", mods: {}, decls: {}, fns: {}", visited_modules.len(), num_decls, funcs.len());
+ eprintln!("{:<20} {}", "Item Collection:", analysis_sw.elapsed());
+
+ if self.randomize {
+ shuffle(&mut rng, &mut funcs);
+ }
+
+ if !self.skip_inference {
+ self.run_inference(&host, db, &vfs, &funcs, verbosity);
+ }
+
+ let total_span = analysis_sw.elapsed();
+ eprintln!("{:<20} {}", "Total:", total_span);
+ report_metric("total time", total_span.time.as_millis() as u64, "ms");
+ if let Some(instructions) = total_span.instructions {
+ report_metric("total instructions", instructions, "#instr");
+ }
+ if let Some(memory) = total_span.memory {
+ report_metric("total memory", memory.allocated.megabytes() as u64, "MB");
+ }
+
+ if env::var("RA_COUNT").is_ok() {
+ eprintln!("{}", profile::countme::get_all());
+ }
+
+ if self.source_stats {
+ let mut total_file_size = Bytes::default();
+ for e in ide_db::base_db::ParseQuery.in_db(db).entries::<Vec<_>>() {
+ total_file_size += syntax_len(db.parse(e.key).syntax_node())
+ }
+
+ let mut total_macro_file_size = Bytes::default();
+ for e in hir::db::ParseMacroExpansionQuery.in_db(db).entries::<Vec<_>>() {
+ if let Some((val, _)) = db.parse_macro_expansion(e.key).value {
+ total_macro_file_size += syntax_len(val.syntax_node())
+ }
+ }
+ eprintln!("source files: {}, macro files: {}", total_file_size, total_macro_file_size);
+ }
+
+ if self.memory_usage && verbosity.is_verbose() {
+ print_memory_usage(host, vfs);
+ }
+
+ Ok(())
+ }
+
+ fn run_inference(
+ &self,
+ host: &AnalysisHost,
+ db: &RootDatabase,
+ vfs: &Vfs,
+ funcs: &[Function],
+ verbosity: Verbosity,
+ ) {
+ let mut bar = match verbosity {
+ Verbosity::Quiet | Verbosity::Spammy => ProgressReport::hidden(),
+ _ if self.parallel || self.output.is_some() => ProgressReport::hidden(),
+ _ => ProgressReport::new(funcs.len() as u64),
+ };
+
+ if self.parallel {
+ let mut inference_sw = self.stop_watch();
+ let snap = Snap(db.snapshot());
+ funcs
+ .par_iter()
+ .map_with(snap, |snap, &f| {
+ let f_id = FunctionId::from(f);
+ snap.0.body(f_id.into());
+ snap.0.infer(f_id.into());
+ })
+ .count();
+ eprintln!("{:<20} {}", "Parallel Inference:", inference_sw.elapsed());
+ }
+
+ let mut inference_sw = self.stop_watch();
+ bar.tick();
+ let mut num_exprs = 0;
+ let mut num_exprs_unknown = 0;
+ let mut num_exprs_partially_unknown = 0;
+ let mut num_type_mismatches = 0;
+ let analysis = host.analysis();
+ for f in funcs.iter().copied() {
+ let name = f.name(db);
+ let full_name = f
+ .module(db)
+ .path_to_root(db)
+ .into_iter()
+ .rev()
+ .filter_map(|it| it.name(db))
+ .chain(Some(f.name(db)))
+ .join("::");
+ if let Some(only_name) = self.only.as_deref() {
+ if name.to_string() != only_name && full_name != only_name {
+ continue;
+ }
+ }
+ let mut msg = format!("processing: {}", full_name);
+ if verbosity.is_verbose() {
+ if let Some(src) = f.source(db) {
+ let original_file = src.file_id.original_file(db);
+ let path = vfs.file_path(original_file);
+ let syntax_range = src.value.syntax().text_range();
+ format_to!(msg, " ({} {:?})", path, syntax_range);
+ }
+ }
+ if verbosity.is_spammy() {
+ bar.println(msg.to_string());
+ }
+ bar.set_message(&msg);
+ let f_id = FunctionId::from(f);
+ let (body, sm) = db.body_with_source_map(f_id.into());
+ let inference_result = db.infer(f_id.into());
+ let (previous_exprs, previous_unknown, previous_partially_unknown) =
+ (num_exprs, num_exprs_unknown, num_exprs_partially_unknown);
+ for (expr_id, _) in body.exprs.iter() {
+ let ty = &inference_result[expr_id];
+ num_exprs += 1;
+ let unknown_or_partial = if ty.is_unknown() {
+ num_exprs_unknown += 1;
+ if verbosity.is_spammy() {
+ if let Some((path, start, end)) =
+ expr_syntax_range(db, &analysis, vfs, &sm, expr_id)
+ {
+ bar.println(format!(
+ "{} {}:{}-{}:{}: Unknown type",
+ path,
+ start.line + 1,
+ start.col,
+ end.line + 1,
+ end.col,
+ ));
+ } else {
+ bar.println(format!("{}: Unknown type", name,));
+ }
+ }
+ true
+ } else {
+ let mut is_partially_unknown = false;
+ ty.walk(&mut |ty| {
+ if ty.is_unknown() {
+ is_partially_unknown = true;
+ }
+ });
+ if is_partially_unknown {
+ num_exprs_partially_unknown += 1;
+ }
+ is_partially_unknown
+ };
+ if self.only.is_some() && verbosity.is_spammy() {
+ // in super-verbose mode for just one function, we print every single expression
+ if let Some((_, start, end)) =
+ expr_syntax_range(db, &analysis, vfs, &sm, expr_id)
+ {
+ bar.println(format!(
+ "{}:{}-{}:{}: {}",
+ start.line + 1,
+ start.col,
+ end.line + 1,
+ end.col,
+ ty.display(db)
+ ));
+ } else {
+ bar.println(format!("unknown location: {}", ty.display(db)));
+ }
+ }
+ if unknown_or_partial && self.output == Some(OutputFormat::Csv) {
+ println!(
+ r#"{},type,"{}""#,
+ location_csv(db, &analysis, vfs, &sm, expr_id),
+ ty.display(db)
+ );
+ }
+ if let Some(mismatch) = inference_result.type_mismatch_for_expr(expr_id) {
+ num_type_mismatches += 1;
+ if verbosity.is_verbose() {
+ if let Some((path, start, end)) =
+ expr_syntax_range(db, &analysis, vfs, &sm, expr_id)
+ {
+ bar.println(format!(
+ "{} {}:{}-{}:{}: Expected {}, got {}",
+ path,
+ start.line + 1,
+ start.col,
+ end.line + 1,
+ end.col,
+ mismatch.expected.display(db),
+ mismatch.actual.display(db)
+ ));
+ } else {
+ bar.println(format!(
+ "{}: Expected {}, got {}",
+ name,
+ mismatch.expected.display(db),
+ mismatch.actual.display(db)
+ ));
+ }
+ }
+ if self.output == Some(OutputFormat::Csv) {
+ println!(
+ r#"{},mismatch,"{}","{}""#,
+ location_csv(db, &analysis, vfs, &sm, expr_id),
+ mismatch.expected.display(db),
+ mismatch.actual.display(db)
+ );
+ }
+ }
+ }
+ if verbosity.is_spammy() {
+ bar.println(format!(
+ "In {}: {} exprs, {} unknown, {} partial",
+ full_name,
+ num_exprs - previous_exprs,
+ num_exprs_unknown - previous_unknown,
+ num_exprs_partially_unknown - previous_partially_unknown
+ ));
+ }
+ bar.inc(1);
+ }
+
+ bar.finish_and_clear();
+ eprintln!(
+ " exprs: {}, ??ty: {} ({}%), ?ty: {} ({}%), !ty: {}",
+ num_exprs,
+ num_exprs_unknown,
+ percentage(num_exprs_unknown, num_exprs),
+ num_exprs_partially_unknown,
+ percentage(num_exprs_partially_unknown, num_exprs),
+ num_type_mismatches
+ );
+ report_metric("unknown type", num_exprs_unknown, "#");
+ report_metric("type mismatches", num_type_mismatches, "#");
+
+ eprintln!("{:<20} {}", "Inference:", inference_sw.elapsed());
+ }
+
+ fn stop_watch(&self) -> StopWatch {
+ StopWatch::start().memory(self.memory_usage)
+ }
+}
+
+fn location_csv(
+ db: &RootDatabase,
+ analysis: &Analysis,
+ vfs: &Vfs,
+ sm: &BodySourceMap,
+ expr_id: ExprId,
+) -> String {
+ let src = match sm.expr_syntax(expr_id) {
+ Ok(s) => s,
+ Err(SyntheticSyntax) => return "synthetic,,".to_string(),
+ };
+ let root = db.parse_or_expand(src.file_id).unwrap();
+ let node = src.map(|e| e.to_node(&root).syntax().clone());
+ let original_range = node.as_ref().original_file_range(db);
+ let path = vfs.file_path(original_range.file_id);
+ let line_index = analysis.file_line_index(original_range.file_id).unwrap();
+ let text_range = original_range.range;
+ let (start, end) =
+ (line_index.line_col(text_range.start()), line_index.line_col(text_range.end()));
+ format!("{},{}:{},{}:{}", path, start.line + 1, start.col, end.line + 1, end.col)
+}
+
+fn expr_syntax_range(
+ db: &RootDatabase,
+ analysis: &Analysis,
+ vfs: &Vfs,
+ sm: &BodySourceMap,
+ expr_id: ExprId,
+) -> Option<(VfsPath, LineCol, LineCol)> {
+ let src = sm.expr_syntax(expr_id);
+ if let Ok(src) = src {
+ let root = db.parse_or_expand(src.file_id).unwrap();
+ let node = src.map(|e| e.to_node(&root).syntax().clone());
+ let original_range = node.as_ref().original_file_range(db);
+ let path = vfs.file_path(original_range.file_id);
+ let line_index = analysis.file_line_index(original_range.file_id).unwrap();
+ let text_range = original_range.range;
+ let (start, end) =
+ (line_index.line_col(text_range.start()), line_index.line_col(text_range.end()));
+ Some((path, start, end))
+ } else {
+ None
+ }
+}
+
+fn shuffle<T>(rng: &mut Rand32, slice: &mut [T]) {
+ for i in 0..slice.len() {
+ randomize_first(rng, &mut slice[i..]);
+ }
+
+ fn randomize_first<T>(rng: &mut Rand32, slice: &mut [T]) {
+ assert!(!slice.is_empty());
+ let idx = rng.rand_range(0..slice.len() as u32) as usize;
+ slice.swap(0, idx);
+ }
+}
+
+fn percentage(n: u64, total: u64) -> u64 {
+ (n * 100).checked_div(total).unwrap_or(100)
+}
+
+fn syntax_len(node: SyntaxNode) -> usize {
+ // Macro expanded code doesn't contain whitespace, so erase *all* whitespace
+ // to make macro and non-macro code comparable.
+ node.to_string().replace(|it: char| it.is_ascii_whitespace(), "").len()
+}
--- /dev/null
- /// Print help.
- optional -h, --help
+//! Grammar for the command-line arguments.
+#![allow(unreachable_pub)]
+use std::{path::PathBuf, str::FromStr};
+
+use ide_ssr::{SsrPattern, SsrRule};
+
+use crate::cli::Verbosity;
+
+xflags::xflags! {
+ src "./src/cli/flags.rs"
+
+ /// LSP server for the Rust programming language.
+ ///
+ /// Subcommands and their flags do not provide any stability guarantees and may be removed or
+ /// changed without notice. Top-level flags that are not are marked as [Unstable] provide
+ /// backwards-compatibility and may be relied on.
+ cmd rust-analyzer {
+ /// Verbosity level, can be repeated multiple times.
+ repeated -v, --verbose
+ /// Verbosity level.
+ optional -q, --quiet
+
+ /// Log to the specified file instead of stderr.
+ optional --log-file path: PathBuf
+ /// Flush log records to the file immediately.
+ optional --no-log-buffering
+
+ /// [Unstable] Wait until a debugger is attached to (requires debug build).
+ optional --wait-dbg
+
+ default cmd lsp-server {
+ /// Print version.
+ optional --version
- cmd analysis-stats
+
+ /// Dump a LSP config JSON schema.
+ optional --print-config-schema
+ }
+
+ /// Parse stdin.
+ cmd parse {
+ /// Suppress printing.
+ optional --no-dump
+ }
+
+ /// Parse stdin and print the list of symbols.
+ cmd symbols {}
+
+ /// Highlight stdin as html.
+ cmd highlight {
+ /// Enable rainbow highlighting of identifiers.
+ optional --rainbow
+ }
+
+ /// Batch typecheck project and print summary statistics
- {
++ cmd analysis-stats {
+ /// Directory with Cargo.toml.
+ required path: PathBuf
- cmd diagnostics
++
+ optional --output format: OutputFormat
+
+ /// Randomize order in which crates, modules, and items are processed.
+ optional --randomize
+ /// Run type inference in parallel.
+ optional --parallel
+ /// Collect memory usage statistics.
+ optional --memory-usage
+ /// Print the total length of all source and macro files (whitespace is not counted).
+ optional --source-stats
+
+ /// Only analyze items matching this path.
+ optional -o, --only path: String
+ /// Also analyze all dependencies.
+ optional --with-deps
+ /// Don't load sysroot crates (`std`, `core` & friends).
+ optional --no-sysroot
+
+ /// Don't run build scripts or load `OUT_DIR` values by running `cargo check` before analysis.
+ optional --disable-build-scripts
+ /// Don't use expand proc macros.
+ optional --disable-proc-macros
+ /// Only resolve names, don't run type inference.
+ optional --skip-inference
+ }
+
- {
++ cmd diagnostics {
+ /// Directory with Cargo.toml.
+ required path: PathBuf
- cmd ssr
++
+ /// Don't run build scripts or load `OUT_DIR` values by running `cargo check` before analysis.
+ optional --disable-build-scripts
+ /// Don't use expand proc macros.
+ optional --disable-proc-macros
+ }
+
- {}
++ cmd ssr {
+ /// A structured search replace rule (`$a.foo($b) ==> bar($a, $b)`)
+ repeated rule: SsrRule
- cmd search
++ }
+
- {
++ cmd search {
+ /// A structured search replace pattern (`$a.foo($b)`)
+ repeated pattern: SsrPattern
- cmd lsif
+ /// Prints debug information for any nodes with source exactly equal to snippet.
+ optional --debug snippet: String
+ }
+
+ cmd proc-macro {}
+
- {}
++ cmd lsif {
+ required path: PathBuf
- cmd scip
++ }
+
- {}
++ cmd scip {
+ required path: PathBuf
- pub help: bool,
++ }
+ }
+}
+
+// generated start
+// The following code is generated by `xflags` macro.
+// Run `env UPDATE_XFLAGS=1 cargo build` to regenerate.
+#[derive(Debug)]
+pub struct RustAnalyzer {
+ pub verbose: u32,
+ pub quiet: bool,
+ pub log_file: Option<PathBuf>,
+ pub no_log_buffering: bool,
+ pub wait_dbg: bool,
+ pub subcommand: RustAnalyzerCmd,
+}
+
+#[derive(Debug)]
+pub enum RustAnalyzerCmd {
+ LspServer(LspServer),
+ Parse(Parse),
+ Symbols(Symbols),
+ Highlight(Highlight),
+ AnalysisStats(AnalysisStats),
+ Diagnostics(Diagnostics),
+ Ssr(Ssr),
+ Search(Search),
+ ProcMacro(ProcMacro),
+ Lsif(Lsif),
+ Scip(Scip),
+}
+
+#[derive(Debug)]
+pub struct LspServer {
+ pub version: bool,
- pub const HELP: &'static str = Self::HELP_;
+ pub print_config_schema: bool,
+}
+
+#[derive(Debug)]
+pub struct Parse {
+ pub no_dump: bool,
+}
+
+#[derive(Debug)]
+pub struct Symbols;
+
+#[derive(Debug)]
+pub struct Highlight {
+ pub rainbow: bool,
+}
+
+#[derive(Debug)]
+pub struct AnalysisStats {
+ pub path: PathBuf,
+
+ pub output: Option<OutputFormat>,
+ pub randomize: bool,
+ pub parallel: bool,
+ pub memory_usage: bool,
+ pub source_stats: bool,
+ pub only: Option<String>,
+ pub with_deps: bool,
+ pub no_sysroot: bool,
+ pub disable_build_scripts: bool,
+ pub disable_proc_macros: bool,
+ pub skip_inference: bool,
+}
+
+#[derive(Debug)]
+pub struct Diagnostics {
+ pub path: PathBuf,
+
+ pub disable_build_scripts: bool,
+ pub disable_proc_macros: bool,
+}
+
+#[derive(Debug)]
+pub struct Ssr {
+ pub rule: Vec<SsrRule>,
+}
+
+#[derive(Debug)]
+pub struct Search {
+ pub pattern: Vec<SsrPattern>,
+
+ pub debug: Option<String>,
+}
+
+#[derive(Debug)]
+pub struct ProcMacro;
+
+#[derive(Debug)]
+pub struct Lsif {
+ pub path: PathBuf,
+}
+
+#[derive(Debug)]
+pub struct Scip {
+ pub path: PathBuf,
+}
+
+impl RustAnalyzer {
++ #[allow(dead_code)]
++ pub fn from_env_or_exit() -> Self {
++ Self::from_env_or_exit_()
++ }
+
+ #[allow(dead_code)]
+ pub fn from_env() -> xflags::Result<Self> {
+ Self::from_env_()
+ }
+
+ #[allow(dead_code)]
+ pub fn from_vec(args: Vec<std::ffi::OsString>) -> xflags::Result<Self> {
+ Self::from_vec_(args)
+ }
+}
+// generated end
+
+#[derive(Debug, PartialEq, Eq)]
+pub enum OutputFormat {
+ Csv,
+}
+
+impl RustAnalyzer {
+ pub fn verbosity(&self) -> Verbosity {
+ if self.quiet {
+ return Verbosity::Quiet;
+ }
+ match self.verbose {
+ 0 => Verbosity::Normal,
+ 1 => Verbosity::Verbose,
+ _ => Verbosity::Spammy,
+ }
+ }
+}
+
+impl FromStr for OutputFormat {
+ type Err = String;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match s {
+ "csv" => Ok(Self::Csv),
+ _ => Err(format!("unknown output format `{}`", s)),
+ }
+ }
+}
--- /dev/null
- use ide_db::base_db::CrateGraph;
+//! Loads a Cargo project into a static instance of analysis, without support
+//! for incorporating changes.
+use std::{path::Path, sync::Arc};
+
+use anyhow::Result;
+use crossbeam_channel::{unbounded, Receiver};
+use hir::db::DefDatabase;
+use ide::{AnalysisHost, Change};
- load_workspace(workspace, cargo_config, load_config)
++use ide_db::{base_db::CrateGraph, FxHashMap};
+use proc_macro_api::ProcMacroServer;
+use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace};
+use vfs::{loader::Handle, AbsPath, AbsPathBuf};
+
+use crate::reload::{load_proc_macro, ProjectFolders, SourceRootConfig};
+
+// Note: Since this type is used by external tools that use rust-analyzer as a library
+// what otherwise would be `pub(crate)` has to be `pub` here instead.
+pub struct LoadCargoConfig {
+ pub load_out_dirs_from_check: bool,
+ pub with_proc_macro: bool,
+ pub prefill_caches: bool,
+}
+
+// Note: Since this function is used by external tools that use rust-analyzer as a library
+// what otherwise would be `pub(crate)` has to be `pub` here instead.
+pub fn load_workspace_at(
+ root: &Path,
+ cargo_config: &CargoConfig,
+ load_config: &LoadCargoConfig,
+ progress: &dyn Fn(String),
+) -> Result<(AnalysisHost, vfs::Vfs, Option<ProcMacroServer>)> {
+ let root = AbsPathBuf::assert(std::env::current_dir()?.join(root));
+ let root = ProjectManifest::discover_single(&root)?;
+ let mut workspace = ProjectWorkspace::load(root, cargo_config, progress)?;
+
+ if load_config.load_out_dirs_from_check {
+ let build_scripts = workspace.run_build_scripts(cargo_config, progress)?;
+ workspace.set_build_scripts(build_scripts)
+ }
+
- cargo_config: &CargoConfig,
++ load_workspace(workspace, &cargo_config.extra_env, load_config)
+}
+
+// Note: Since this function is used by external tools that use rust-analyzer as a library
+// what otherwise would be `pub(crate)` has to be `pub` here instead.
+//
+// The reason both, `load_workspace_at` and `load_workspace` are `pub` is that some of
+// these tools need access to `ProjectWorkspace`, too, which `load_workspace_at` hides.
+pub fn load_workspace(
+ ws: ProjectWorkspace,
- let path = AbsPathBuf::assert(std::env::current_exe()?);
- Ok(ProcMacroServer::spawn(path, &["proc-macro"]).unwrap())
++ extra_env: &FxHashMap<String, String>,
+ load_config: &LoadCargoConfig,
+) -> Result<(AnalysisHost, vfs::Vfs, Option<ProcMacroServer>)> {
+ let (sender, receiver) = unbounded();
+ let mut vfs = vfs::Vfs::default();
+ let mut loader = {
+ let loader =
+ vfs_notify::NotifyHandle::spawn(Box::new(move |msg| sender.send(msg).unwrap()));
+ Box::new(loader)
+ };
+
+ let proc_macro_client = if load_config.with_proc_macro {
- Err("proc macro server not started".to_owned())
++ let mut path = AbsPathBuf::assert(std::env::current_exe()?);
++ let mut args = vec!["proc-macro"];
++
++ if let ProjectWorkspace::Cargo { sysroot, .. } | ProjectWorkspace::Json { sysroot, .. } =
++ &ws
++ {
++ if let Some(sysroot) = sysroot.as_ref() {
++ let standalone_server_name =
++ format!("rust-analyzer-proc-macro-srv{}", std::env::consts::EXE_SUFFIX);
++ let server_path = sysroot.root().join("libexec").join(&standalone_server_name);
++ if std::fs::metadata(&server_path).is_ok() {
++ path = server_path;
++ args = vec![];
++ }
++ }
++ }
++
++ ProcMacroServer::spawn(path.clone(), args.clone()).map_err(|e| e.to_string())
+ } else {
- cargo_config,
++ Err("proc macro server disabled".to_owned())
+ };
+
+ let crate_graph = ws.to_crate_graph(
+ &mut |_, path: &AbsPath| {
+ load_proc_macro(proc_macro_client.as_ref().map_err(|e| &**e), path, &[])
+ },
+ &mut |path: &AbsPath| {
+ let contents = loader.load_sync(path);
+ let path = vfs::VfsPath::from(path.to_path_buf());
+ vfs.set_file_contents(path.clone(), contents);
+ vfs.file_id(&path)
+ },
++ extra_env,
+ );
+
+ let project_folders = ProjectFolders::new(&[ws], &[]);
+ loader.set_config(vfs::loader::Config {
+ load: project_folders.load,
+ watch: vec![],
+ version: 0,
+ });
+
+ tracing::debug!("crate graph: {:?}", crate_graph);
+ let host =
+ load_crate_graph(crate_graph, project_folders.source_root_config, &mut vfs, &receiver);
+
+ if load_config.prefill_caches {
+ host.analysis().parallel_prime_caches(1, |_| {})?;
+ }
+ Ok((host, vfs, proc_macro_client.ok()))
+}
+
+fn load_crate_graph(
+ crate_graph: CrateGraph,
+ source_root_config: SourceRootConfig,
+ vfs: &mut vfs::Vfs,
+ receiver: &Receiver<vfs::loader::Message>,
+) -> AnalysisHost {
+ let lru_cap = std::env::var("RA_LRU_CAP").ok().and_then(|it| it.parse::<usize>().ok());
+ let mut host = AnalysisHost::new(lru_cap);
+ let mut analysis_change = Change::new();
+
+ host.raw_database_mut().set_enable_proc_attr_macros(true);
+
+ // wait until Vfs has loaded all roots
+ for task in receiver {
+ match task {
+ vfs::loader::Message::Progress { n_done, n_total, config_version: _ } => {
+ if n_done == n_total {
+ break;
+ }
+ }
+ vfs::loader::Message::Loaded { files } => {
+ for (path, contents) in files {
+ vfs.set_file_contents(path.into(), contents);
+ }
+ }
+ }
+ }
+ let changes = vfs.take_changes();
+ for file in changes {
+ if file.exists() {
+ let contents = vfs.file_contents(file.file_id).to_vec();
+ if let Ok(text) = String::from_utf8(contents) {
+ analysis_change.change_file(file.file_id, Some(Arc::new(text)))
+ }
+ }
+ }
+ let source_roots = source_root_config.partition(vfs);
+ analysis_change.set_roots(source_roots);
+
+ analysis_change.set_crate_graph(crate_graph);
+
+ host.apply_change(analysis_change);
+ host
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use hir::Crate;
+
+ #[test]
+ fn test_loading_rust_analyzer() {
+ let path = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent().unwrap();
+ let cargo_config = CargoConfig::default();
+ let load_cargo_config = LoadCargoConfig {
+ load_out_dirs_from_check: false,
+ with_proc_macro: false,
+ prefill_caches: false,
+ };
+ let (host, _vfs, _proc_macro) =
+ load_workspace_at(path, &cargo_config, &load_cargo_config, &|_| {}).unwrap();
+
+ let n_crates = Crate::all(host.raw_database()).len();
+ // RA has quite a few crates, but the exact count doesn't matter
+ assert!(n_crates > 20);
+ }
+}
--- /dev/null
- load_workspace(workspace, &cargo_config, &load_cargo_config)?;
+//! LSIF (language server index format) generator
+
+use std::collections::HashMap;
+use std::env;
+use std::time::Instant;
+
+use ide::{
+ Analysis, FileId, FileRange, MonikerKind, PackageInformation, RootDatabase, StaticIndex,
+ StaticIndexedFile, TokenId, TokenStaticData,
+};
+use ide_db::LineIndexDatabase;
+
+use ide_db::base_db::salsa::{self, ParallelDatabase};
+use lsp_types::{self, lsif};
+use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace};
+use vfs::{AbsPathBuf, Vfs};
+
+use crate::cli::{
+ flags,
+ load_cargo::{load_workspace, LoadCargoConfig},
+ Result,
+};
+use crate::line_index::{LineEndings, LineIndex, OffsetEncoding};
+use crate::to_proto;
+use crate::version::version;
+
+/// Need to wrap Snapshot to provide `Clone` impl for `map_with`
+struct Snap<DB>(DB);
+impl<DB: ParallelDatabase> Clone for Snap<salsa::Snapshot<DB>> {
+ fn clone(&self) -> Snap<salsa::Snapshot<DB>> {
+ Snap(self.0.snapshot())
+ }
+}
+
+struct LsifManager<'a> {
+ count: i32,
+ token_map: HashMap<TokenId, Id>,
+ range_map: HashMap<FileRange, Id>,
+ file_map: HashMap<FileId, Id>,
+ package_map: HashMap<PackageInformation, Id>,
+ analysis: &'a Analysis,
+ db: &'a RootDatabase,
+ vfs: &'a Vfs,
+}
+
+#[derive(Clone, Copy)]
+struct Id(i32);
+
+impl From<Id> for lsp_types::NumberOrString {
+ fn from(Id(x): Id) -> Self {
+ lsp_types::NumberOrString::Number(x)
+ }
+}
+
+impl LsifManager<'_> {
+ fn new<'a>(analysis: &'a Analysis, db: &'a RootDatabase, vfs: &'a Vfs) -> LsifManager<'a> {
+ LsifManager {
+ count: 0,
+ token_map: HashMap::default(),
+ range_map: HashMap::default(),
+ file_map: HashMap::default(),
+ package_map: HashMap::default(),
+ analysis,
+ db,
+ vfs,
+ }
+ }
+
+ fn add(&mut self, data: lsif::Element) -> Id {
+ let id = Id(self.count);
+ self.emit(&serde_json::to_string(&lsif::Entry { id: id.into(), data }).unwrap());
+ self.count += 1;
+ id
+ }
+
+ fn add_vertex(&mut self, vertex: lsif::Vertex) -> Id {
+ self.add(lsif::Element::Vertex(vertex))
+ }
+
+ fn add_edge(&mut self, edge: lsif::Edge) -> Id {
+ self.add(lsif::Element::Edge(edge))
+ }
+
+ // FIXME: support file in addition to stdout here
+ fn emit(&self, data: &str) {
+ println!("{}", data);
+ }
+
+ fn get_token_id(&mut self, id: TokenId) -> Id {
+ if let Some(x) = self.token_map.get(&id) {
+ return *x;
+ }
+ let result_set_id = self.add_vertex(lsif::Vertex::ResultSet(lsif::ResultSet { key: None }));
+ self.token_map.insert(id, result_set_id);
+ result_set_id
+ }
+
+ fn get_package_id(&mut self, package_information: PackageInformation) -> Id {
+ if let Some(x) = self.package_map.get(&package_information) {
+ return *x;
+ }
+ let pi = package_information.clone();
+ let result_set_id =
+ self.add_vertex(lsif::Vertex::PackageInformation(lsif::PackageInformation {
+ name: pi.name,
+ manager: "cargo".to_string(),
+ uri: None,
+ content: None,
+ repository: Some(lsif::Repository {
+ url: pi.repo,
+ r#type: "git".to_string(),
+ commit_id: None,
+ }),
+ version: Some(pi.version),
+ }));
+ self.package_map.insert(package_information, result_set_id);
+ result_set_id
+ }
+
+ fn get_range_id(&mut self, id: FileRange) -> Id {
+ if let Some(x) = self.range_map.get(&id) {
+ return *x;
+ }
+ let file_id = id.file_id;
+ let doc_id = self.get_file_id(file_id);
+ let line_index = self.db.line_index(file_id);
+ let line_index = LineIndex {
+ index: line_index,
+ encoding: OffsetEncoding::Utf16,
+ endings: LineEndings::Unix,
+ };
+ let range_id = self.add_vertex(lsif::Vertex::Range {
+ range: to_proto::range(&line_index, id.range),
+ tag: None,
+ });
+ self.add_edge(lsif::Edge::Contains(lsif::EdgeDataMultiIn {
+ in_vs: vec![range_id.into()],
+ out_v: doc_id.into(),
+ }));
+ range_id
+ }
+
+ fn get_file_id(&mut self, id: FileId) -> Id {
+ if let Some(x) = self.file_map.get(&id) {
+ return *x;
+ }
+ let path = self.vfs.file_path(id);
+ let path = path.as_path().unwrap();
+ let doc_id = self.add_vertex(lsif::Vertex::Document(lsif::Document {
+ language_id: "rust".to_string(),
+ uri: lsp_types::Url::from_file_path(path).unwrap(),
+ }));
+ self.file_map.insert(id, doc_id);
+ doc_id
+ }
+
+ fn add_token(&mut self, id: TokenId, token: TokenStaticData) {
+ let result_set_id = self.get_token_id(id);
+ if let Some(hover) = token.hover {
+ let hover_id = self.add_vertex(lsif::Vertex::HoverResult {
+ result: lsp_types::Hover {
+ contents: lsp_types::HoverContents::Markup(to_proto::markup_content(
+ hover.markup,
+ ide::HoverDocFormat::Markdown,
+ )),
+ range: None,
+ },
+ });
+ self.add_edge(lsif::Edge::Hover(lsif::EdgeData {
+ in_v: hover_id.into(),
+ out_v: result_set_id.into(),
+ }));
+ }
+ if let Some(moniker) = token.moniker {
+ let package_id = self.get_package_id(moniker.package_information);
+ let moniker_id = self.add_vertex(lsif::Vertex::Moniker(lsp_types::Moniker {
+ scheme: "rust-analyzer".to_string(),
+ identifier: moniker.identifier.to_string(),
+ unique: lsp_types::UniquenessLevel::Scheme,
+ kind: Some(match moniker.kind {
+ MonikerKind::Import => lsp_types::MonikerKind::Import,
+ MonikerKind::Export => lsp_types::MonikerKind::Export,
+ }),
+ }));
+ self.add_edge(lsif::Edge::PackageInformation(lsif::EdgeData {
+ in_v: package_id.into(),
+ out_v: moniker_id.into(),
+ }));
+ self.add_edge(lsif::Edge::Moniker(lsif::EdgeData {
+ in_v: moniker_id.into(),
+ out_v: result_set_id.into(),
+ }));
+ }
+ if let Some(def) = token.definition {
+ let result_id = self.add_vertex(lsif::Vertex::DefinitionResult);
+ let def_vertex = self.get_range_id(def);
+ self.add_edge(lsif::Edge::Item(lsif::Item {
+ document: (*self.file_map.get(&def.file_id).unwrap()).into(),
+ property: None,
+ edge_data: lsif::EdgeDataMultiIn {
+ in_vs: vec![def_vertex.into()],
+ out_v: result_id.into(),
+ },
+ }));
+ self.add_edge(lsif::Edge::Definition(lsif::EdgeData {
+ in_v: result_id.into(),
+ out_v: result_set_id.into(),
+ }));
+ }
+ if !token.references.is_empty() {
+ let result_id = self.add_vertex(lsif::Vertex::ReferenceResult);
+ self.add_edge(lsif::Edge::References(lsif::EdgeData {
+ in_v: result_id.into(),
+ out_v: result_set_id.into(),
+ }));
+ let mut edges = token.references.iter().fold(
+ HashMap::<_, Vec<lsp_types::NumberOrString>>::new(),
+ |mut edges, x| {
+ let entry =
+ edges.entry((x.range.file_id, x.is_definition)).or_insert_with(Vec::new);
+ entry.push((*self.range_map.get(&x.range).unwrap()).into());
+ edges
+ },
+ );
+ for x in token.references {
+ if let Some(vertices) = edges.remove(&(x.range.file_id, x.is_definition)) {
+ self.add_edge(lsif::Edge::Item(lsif::Item {
+ document: (*self.file_map.get(&x.range.file_id).unwrap()).into(),
+ property: Some(if x.is_definition {
+ lsif::ItemKind::Definitions
+ } else {
+ lsif::ItemKind::References
+ }),
+ edge_data: lsif::EdgeDataMultiIn {
+ in_vs: vertices,
+ out_v: result_id.into(),
+ },
+ }));
+ }
+ }
+ }
+ }
+
+ fn add_file(&mut self, file: StaticIndexedFile) {
+ let StaticIndexedFile { file_id, tokens, folds, .. } = file;
+ let doc_id = self.get_file_id(file_id);
+ let text = self.analysis.file_text(file_id).unwrap();
+ let line_index = self.db.line_index(file_id);
+ let line_index = LineIndex {
+ index: line_index,
+ encoding: OffsetEncoding::Utf16,
+ endings: LineEndings::Unix,
+ };
+ let result = folds
+ .into_iter()
+ .map(|it| to_proto::folding_range(&*text, &line_index, false, it))
+ .collect();
+ let folding_id = self.add_vertex(lsif::Vertex::FoldingRangeResult { result });
+ self.add_edge(lsif::Edge::FoldingRange(lsif::EdgeData {
+ in_v: folding_id.into(),
+ out_v: doc_id.into(),
+ }));
+ let tokens_id = tokens
+ .into_iter()
+ .map(|(range, id)| {
+ let range_id = self.add_vertex(lsif::Vertex::Range {
+ range: to_proto::range(&line_index, range),
+ tag: None,
+ });
+ self.range_map.insert(FileRange { file_id, range }, range_id);
+ let result_set_id = self.get_token_id(id);
+ self.add_edge(lsif::Edge::Next(lsif::EdgeData {
+ in_v: result_set_id.into(),
+ out_v: range_id.into(),
+ }));
+ range_id.into()
+ })
+ .collect();
+ self.add_edge(lsif::Edge::Contains(lsif::EdgeDataMultiIn {
+ in_vs: tokens_id,
+ out_v: doc_id.into(),
+ }));
+ }
+}
+
+impl flags::Lsif {
+ pub fn run(self) -> Result<()> {
+ eprintln!("Generating LSIF started...");
+ let now = Instant::now();
+ let cargo_config = CargoConfig::default();
+ let no_progress = &|_| ();
+ let load_cargo_config = LoadCargoConfig {
+ load_out_dirs_from_check: true,
+ with_proc_macro: true,
+ prefill_caches: false,
+ };
+ let path = AbsPathBuf::assert(env::current_dir()?.join(&self.path));
+ let manifest = ProjectManifest::discover_single(&path)?;
+
+ let workspace = ProjectWorkspace::load(manifest, &cargo_config, no_progress)?;
+
+ let (host, vfs, _proc_macro) =
++ load_workspace(workspace, &cargo_config.extra_env, &load_cargo_config)?;
+ let db = host.raw_database();
+ let analysis = host.analysis();
+
+ let si = StaticIndex::compute(&analysis);
+
+ let mut lsif = LsifManager::new(&analysis, db, &vfs);
+ lsif.add_vertex(lsif::Vertex::MetaData(lsif::MetaData {
+ version: String::from("0.5.0"),
+ project_root: lsp_types::Url::from_file_path(path).unwrap(),
+ position_encoding: lsif::Encoding::Utf16,
+ tool_info: Some(lsp_types::lsif::ToolInfo {
+ name: "rust-analyzer".to_string(),
+ args: vec![],
+ version: Some(version().to_string()),
+ }),
+ }));
+ for file in si.files {
+ lsif.add_file(file);
+ }
+ for (id, token) in si.tokens.iter() {
+ lsif.add_token(id, token);
+ }
+ eprintln!("Generating LSIF finished in {:?}", now.elapsed());
+ Ok(())
+ }
+}
--- /dev/null
- let (host, vfs, _) = load_workspace(workspace, &cargo_config, &load_cargo_config)?;
+//! SCIP generator
+
+use std::{
+ collections::{HashMap, HashSet},
+ time::Instant,
+};
+
+use crate::line_index::{LineEndings, LineIndex, OffsetEncoding};
+use hir::Name;
+use ide::{
+ LineCol, MonikerDescriptorKind, MonikerResult, StaticIndex, StaticIndexedFile, TextRange,
+ TokenId,
+};
+use ide_db::LineIndexDatabase;
+use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace};
+use scip::types as scip_types;
+use std::env;
+
+use crate::cli::{
+ flags,
+ load_cargo::{load_workspace, LoadCargoConfig},
+ Result,
+};
+
+impl flags::Scip {
+ pub fn run(self) -> Result<()> {
+ eprintln!("Generating SCIP start...");
+ let now = Instant::now();
+ let cargo_config = CargoConfig::default();
+
+ let no_progress = &|s| (eprintln!("rust-analyzer: Loading {}", s));
+ let load_cargo_config = LoadCargoConfig {
+ load_out_dirs_from_check: true,
+ with_proc_macro: true,
+ prefill_caches: true,
+ };
+ let path = vfs::AbsPathBuf::assert(env::current_dir()?.join(&self.path));
+ let rootpath = path.normalize();
+ let manifest = ProjectManifest::discover_single(&path)?;
+
+ let workspace = ProjectWorkspace::load(manifest, &cargo_config, no_progress)?;
+
++ let (host, vfs, _) =
++ load_workspace(workspace, &cargo_config.extra_env, &load_cargo_config)?;
+ let db = host.raw_database();
+ let analysis = host.analysis();
+
+ let si = StaticIndex::compute(&analysis);
+
+ let mut index = scip_types::Index {
+ metadata: Some(scip_types::Metadata {
+ version: scip_types::ProtocolVersion::UnspecifiedProtocolVersion.into(),
+ tool_info: Some(scip_types::ToolInfo {
+ name: "rust-analyzer".to_owned(),
+ version: "0.1".to_owned(),
+ arguments: vec![],
+ ..Default::default()
+ })
+ .into(),
+ project_root: format!(
+ "file://{}",
+ path.normalize()
+ .as_os_str()
+ .to_str()
+ .ok_or(anyhow::anyhow!("Unable to normalize project_root path"))?
+ .to_string()
+ ),
+ text_document_encoding: scip_types::TextEncoding::UTF8.into(),
+ ..Default::default()
+ })
+ .into(),
+ ..Default::default()
+ };
+
+ let mut symbols_emitted: HashSet<TokenId> = HashSet::default();
+ let mut tokens_to_symbol: HashMap<TokenId, String> = HashMap::new();
+
+ for file in si.files {
+ let mut local_count = 0;
+ let mut new_local_symbol = || {
+ let new_symbol = scip::types::Symbol::new_local(local_count);
+ local_count += 1;
+
+ new_symbol
+ };
+
+ let StaticIndexedFile { file_id, tokens, .. } = file;
+ let relative_path = match get_relative_filepath(&vfs, &rootpath, file_id) {
+ Some(relative_path) => relative_path,
+ None => continue,
+ };
+
+ let line_index = LineIndex {
+ index: db.line_index(file_id),
+ encoding: OffsetEncoding::Utf8,
+ endings: LineEndings::Unix,
+ };
+
+ let mut doc = scip_types::Document {
+ relative_path,
+ language: "rust".to_string(),
+ ..Default::default()
+ };
+
+ tokens.into_iter().for_each(|(range, id)| {
+ let token = si.tokens.get(id).unwrap();
+
+ let mut occurrence = scip_types::Occurrence::default();
+ occurrence.range = text_range_to_scip_range(&line_index, range);
+ occurrence.symbol = match tokens_to_symbol.get(&id) {
+ Some(symbol) => symbol.clone(),
+ None => {
+ let symbol = match &token.moniker {
+ Some(moniker) => moniker_to_symbol(&moniker),
+ None => new_local_symbol(),
+ };
+
+ let symbol = scip::symbol::format_symbol(symbol);
+ tokens_to_symbol.insert(id, symbol.clone());
+ symbol
+ }
+ };
+
+ if let Some(def) = token.definition {
+ if def.range == range {
+ occurrence.symbol_roles |= scip_types::SymbolRole::Definition as i32;
+ }
+
+ if !symbols_emitted.contains(&id) {
+ symbols_emitted.insert(id);
+
+ let mut symbol_info = scip_types::SymbolInformation::default();
+ symbol_info.symbol = occurrence.symbol.clone();
+ if let Some(hover) = &token.hover {
+ if !hover.markup.as_str().is_empty() {
+ symbol_info.documentation = vec![hover.markup.as_str().to_string()];
+ }
+ }
+
+ doc.symbols.push(symbol_info)
+ }
+ }
+
+ doc.occurrences.push(occurrence);
+ });
+
+ if doc.occurrences.is_empty() {
+ continue;
+ }
+
+ index.documents.push(doc);
+ }
+
+ scip::write_message_to_file("index.scip", index)
+ .map_err(|err| anyhow::anyhow!("Failed to write scip to file: {}", err))?;
+
+ eprintln!("Generating SCIP finished {:?}", now.elapsed());
+ Ok(())
+ }
+}
+
+fn get_relative_filepath(
+ vfs: &vfs::Vfs,
+ rootpath: &vfs::AbsPathBuf,
+ file_id: ide::FileId,
+) -> Option<String> {
+ Some(vfs.file_path(file_id).as_path()?.strip_prefix(&rootpath)?.as_ref().to_str()?.to_string())
+}
+
+// SCIP Ranges have a (very large) optimization that ranges if they are on the same line
+// only encode as a vector of [start_line, start_col, end_col].
+//
+// This transforms a line index into the optimized SCIP Range.
+fn text_range_to_scip_range(line_index: &LineIndex, range: TextRange) -> Vec<i32> {
+ let LineCol { line: start_line, col: start_col } = line_index.index.line_col(range.start());
+ let LineCol { line: end_line, col: end_col } = line_index.index.line_col(range.end());
+
+ if start_line == end_line {
+ vec![start_line as i32, start_col as i32, end_col as i32]
+ } else {
+ vec![start_line as i32, start_col as i32, end_line as i32, end_col as i32]
+ }
+}
+
+fn new_descriptor_str(
+ name: &str,
+ suffix: scip_types::descriptor::Suffix,
+) -> scip_types::Descriptor {
+ scip_types::Descriptor {
+ name: name.to_string(),
+ disambiguator: "".to_string(),
+ suffix: suffix.into(),
+ ..Default::default()
+ }
+}
+
+fn new_descriptor(name: Name, suffix: scip_types::descriptor::Suffix) -> scip_types::Descriptor {
+ let mut name = name.to_string();
+ if name.contains("'") {
+ name = format!("`{}`", name);
+ }
+
+ new_descriptor_str(name.as_str(), suffix)
+}
+
+/// Loosely based on `def_to_moniker`
+///
+/// Only returns a Symbol when it's a non-local symbol.
+/// So if the visibility isn't outside of a document, then it will return None
+fn moniker_to_symbol(moniker: &MonikerResult) -> scip_types::Symbol {
+ use scip_types::descriptor::Suffix::*;
+
+ let package_name = moniker.package_information.name.clone();
+ let version = moniker.package_information.version.clone();
+ let descriptors = moniker
+ .identifier
+ .description
+ .iter()
+ .map(|desc| {
+ new_descriptor(
+ desc.name.clone(),
+ match desc.desc {
+ MonikerDescriptorKind::Namespace => Namespace,
+ MonikerDescriptorKind::Type => Type,
+ MonikerDescriptorKind::Term => Term,
+ MonikerDescriptorKind::Method => Method,
+ MonikerDescriptorKind::TypeParameter => TypeParameter,
+ MonikerDescriptorKind::Parameter => Parameter,
+ MonikerDescriptorKind::Macro => Macro,
+ MonikerDescriptorKind::Meta => Meta,
+ },
+ )
+ })
+ .collect();
+
+ scip_types::Symbol {
+ scheme: "rust-analyzer".into(),
+ package: Some(scip_types::Package {
+ manager: "cargo".to_string(),
+ name: package_name,
+ version,
+ ..Default::default()
+ })
+ .into(),
+ descriptors,
+ ..Default::default()
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use hir::Semantics;
+ use ide::{AnalysisHost, FilePosition};
+ use ide_db::defs::IdentClass;
+ use ide_db::{base_db::fixture::ChangeFixture, helpers::pick_best_token};
+ use scip::symbol::format_symbol;
+ use syntax::SyntaxKind::*;
+ use syntax::{AstNode, T};
+
+ fn position(ra_fixture: &str) -> (AnalysisHost, FilePosition) {
+ let mut host = AnalysisHost::default();
+ let change_fixture = ChangeFixture::parse(ra_fixture);
+ host.raw_database_mut().apply_change(change_fixture.change);
+ let (file_id, range_or_offset) =
+ change_fixture.file_position.expect("expected a marker ($0)");
+ let offset = range_or_offset.expect_offset();
+ (host, FilePosition { file_id, offset })
+ }
+
+ /// If expected == "", then assert that there are no symbols (this is basically local symbol)
+ #[track_caller]
+ fn check_symbol(ra_fixture: &str, expected: &str) {
+ let (host, position) = position(ra_fixture);
+
+ let FilePosition { file_id, offset } = position;
+
+ let db = host.raw_database();
+ let sema = &Semantics::new(db);
+ let file = sema.parse(file_id).syntax().clone();
+ let original_token = pick_best_token(file.token_at_offset(offset), |kind| match kind {
+ IDENT
+ | INT_NUMBER
+ | LIFETIME_IDENT
+ | T![self]
+ | T![super]
+ | T![crate]
+ | T![Self]
+ | COMMENT => 2,
+ kind if kind.is_trivia() => 0,
+ _ => 1,
+ })
+ .expect("OK OK");
+
+ let navs = sema
+ .descend_into_macros(original_token.clone())
+ .into_iter()
+ .filter_map(|token| {
+ IdentClass::classify_token(sema, &token).map(IdentClass::definitions).map(|it| {
+ it.into_iter().flat_map(|def| {
+ let module = def.module(db).unwrap();
+ let current_crate = module.krate();
+
+ match MonikerResult::from_def(sema.db, def, current_crate) {
+ Some(moniker_result) => Some(moniker_to_symbol(&moniker_result)),
+ None => None,
+ }
+ })
+ })
+ })
+ .flatten()
+ .collect::<Vec<_>>();
+
+ if expected == "" {
+ assert_eq!(0, navs.len(), "must have no symbols {:?}", navs);
+ return;
+ }
+
+ assert_eq!(1, navs.len(), "must have one symbol {:?}", navs);
+
+ let res = navs.get(0).unwrap();
+ let formatted = format_symbol(res.clone());
+ assert_eq!(formatted, expected);
+ }
+
+ #[test]
+ fn basic() {
+ check_symbol(
+ r#"
+//- /lib.rs crate:main deps:foo
+use foo::example_mod::func;
+fn main() {
+ func$0();
+}
+//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+pub mod example_mod {
+ pub fn func() {}
+}
+"#,
+ "rust-analyzer cargo foo 0.1.0 example_mod/func().",
+ );
+ }
+
+ #[test]
+ fn symbol_for_trait() {
+ check_symbol(
+ r#"
+//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+pub mod module {
+ pub trait MyTrait {
+ pub fn func$0() {}
+ }
+}
+"#,
+ "rust-analyzer cargo foo 0.1.0 module/MyTrait#func().",
+ );
+ }
+
+ #[test]
+ fn symbol_for_trait_constant() {
+ check_symbol(
+ r#"
+ //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+ pub mod module {
+ pub trait MyTrait {
+ const MY_CONST$0: u8;
+ }
+ }
+ "#,
+ "rust-analyzer cargo foo 0.1.0 module/MyTrait#MY_CONST.",
+ );
+ }
+
+ #[test]
+ fn symbol_for_trait_type() {
+ check_symbol(
+ r#"
+ //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+ pub mod module {
+ pub trait MyTrait {
+ type MyType$0;
+ }
+ }
+ "#,
+ // "foo::module::MyTrait::MyType",
+ "rust-analyzer cargo foo 0.1.0 module/MyTrait#[MyType]",
+ );
+ }
+
+ #[test]
+ fn symbol_for_trait_impl_function() {
+ check_symbol(
+ r#"
+ //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+ pub mod module {
+ pub trait MyTrait {
+ pub fn func() {}
+ }
+
+ struct MyStruct {}
+
+ impl MyTrait for MyStruct {
+ pub fn func$0() {}
+ }
+ }
+ "#,
+ // "foo::module::MyStruct::MyTrait::func",
+ "rust-analyzer cargo foo 0.1.0 module/MyStruct#MyTrait#func().",
+ );
+ }
+
+ #[test]
+ fn symbol_for_field() {
+ check_symbol(
+ r#"
+ //- /lib.rs crate:main deps:foo
+ use foo::St;
+ fn main() {
+ let x = St { a$0: 2 };
+ }
+ //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+ pub struct St {
+ pub a: i32,
+ }
+ "#,
+ "rust-analyzer cargo foo 0.1.0 St#a.",
+ );
+ }
+
+ #[test]
+ fn local_symbol_for_local() {
+ check_symbol(
+ r#"
+ //- /lib.rs crate:main deps:foo
+ use foo::module::func;
+ fn main() {
+ func();
+ }
+ //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+ pub mod module {
+ pub fn func() {
+ let x$0 = 2;
+ }
+ }
+ "#,
+ "",
+ );
+ }
+}
--- /dev/null
- use std::{ffi::OsString, fmt, iter, path::PathBuf};
+//! 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.
+
- CargoConfig, ProjectJson, ProjectJsonData, ProjectManifest, RustcSource, UnsetTestCrates,
++use std::{fmt, iter, path::PathBuf};
+
+use flycheck::FlycheckConfig;
+use ide::{
+ AssistConfig, CallableSnippets, CompletionConfig, DiagnosticsConfig, ExprFillDefaultMode,
+ HighlightConfig, HighlightRelatedConfig, HoverConfig, HoverDocFormat, InlayHintsConfig,
+ JoinLinesConfig, Snippet, SnippetScope,
+};
+use ide_db::{
+ imports::insert_use::{ImportGranularity, InsertUseConfig, PrefixKind},
+ SnippetCap,
+};
+use itertools::Itertools;
+use lsp_types::{ClientCapabilities, MarkupKind};
+use project_model::{
- cargo_features: CargoFeatures = "[]",
++ CargoConfig, CargoFeatures, ProjectJson, ProjectJsonData, ProjectManifest, RustcSource,
++ UnsetTestCrates,
+};
+use rustc_hash::{FxHashMap, FxHashSet};
+use serde::{de::DeserializeOwned, Deserialize};
+use vfs::AbsPathBuf;
+
+use crate::{
+ caps::completion_item_edit_resolve,
+ diagnostics::DiagnosticsMapConfig,
+ line_index::OffsetEncoding,
+ lsp_ext::{self, supports_utf8, WorkspaceSymbolSearchKind, WorkspaceSymbolSearchScope},
+};
+
+mod patch_old_style;
+
+// Conventions for configuration keys to preserve maximal extendability without breakage:
+// - Toggles (be it binary true/false or with more options in-between) should almost always suffix as `_enable`
+// This has the benefit of namespaces being extensible, and if the suffix doesn't fit later it can be changed without breakage.
+// - In general be wary of using the namespace of something verbatim, it prevents us from adding subkeys in the future
+// - Don't use abbreviations unless really necessary
+// - foo_command = overrides the subcommand, foo_overrideCommand allows full overwriting, extra args only applies for foo_command
+
+// Defines the server-side configuration of the rust-analyzer. We generate
+// *parts* of VS Code's `package.json` config from this. Run `cargo test` to
+// re-generate that file.
+//
+// However, editor specific config, which the server doesn't know about, should
+// be specified directly in `package.json`.
+//
+// To deprecate an option by replacing it with another name use `new_name | `old_name` so that we keep
+// parsing the old name.
+config_data! {
+ struct ConfigData {
+ /// Placeholder expression to use for missing expressions in assists.
+ assist_expressionFillDefault: ExprFillDefaultDef = "\"todo\"",
+
+ /// Warm up caches on project load.
+ cachePriming_enable: bool = "true",
+ /// How many worker threads to handle priming caches. The default `0` means to pick automatically.
+ cachePriming_numThreads: ParallelCachePrimingNumThreads = "0",
+
+ /// Automatically refresh project info via `cargo metadata` on
+ /// `Cargo.toml` or `.cargo/config.toml` changes.
+ cargo_autoreload: bool = "true",
+ /// Run build scripts (`build.rs`) for more precise code analysis.
+ cargo_buildScripts_enable: bool = "true",
+ /// Override the command rust-analyzer uses to run build scripts and
+ /// build procedural macros. The command is required to output json
+ /// and should therefore include `--message-format=json` or a similar
+ /// option.
+ ///
+ /// By default, a cargo invocation will be constructed for the configured
+ /// targets and features, with the following base command line:
+ ///
+ /// ```bash
+ /// cargo check --quiet --workspace --message-format=json --all-targets
+ /// ```
+ /// .
+ cargo_buildScripts_overrideCommand: Option<Vec<String>> = "null",
+ /// Use `RUSTC_WRAPPER=rust-analyzer` when running build scripts to
+ /// avoid checking unnecessary things.
+ cargo_buildScripts_useRustcWrapper: bool = "true",
+ /// Extra environment variables that will be set when running cargo, rustc
+ /// or other commands within the workspace. Useful for setting RUSTFLAGS.
+ cargo_extraEnv: FxHashMap<String, String> = "{}",
+ /// List of features to activate.
+ ///
+ /// Set this to `"all"` to pass `--all-features` to cargo.
- /// Internal config for debugging, disables loading of sysroot crates.
- cargo_noSysroot: bool = "false",
++ cargo_features: CargoFeaturesDef = "[]",
+ /// Whether to pass `--no-default-features` to cargo.
+ cargo_noDefaultFeatures: bool = "false",
- checkOnSave_features: Option<CargoFeatures> = "null",
++ /// Relative path to the sysroot, or "discover" to try to automatically find it via
++ /// "rustc --print sysroot".
++ ///
++ /// Unsetting this disables sysroot loading.
++ ///
++ /// This option does not take effect until rust-analyzer is restarted.
++ cargo_sysroot: Option<String> = "\"discover\"",
+ /// Compilation target override (target triple).
+ cargo_target: Option<String> = "null",
+ /// Unsets `#[cfg(test)]` for the specified crates.
+ cargo_unsetTest: Vec<String> = "[\"core\"]",
+
+ /// Check all targets and tests (`--all-targets`).
+ checkOnSave_allTargets: bool = "true",
+ /// Cargo command to use for `cargo check`.
+ checkOnSave_command: String = "\"check\"",
+ /// Run specified `cargo check` command for diagnostics on save.
+ checkOnSave_enable: bool = "true",
+ /// Extra arguments for `cargo check`.
+ checkOnSave_extraArgs: Vec<String> = "[]",
+ /// Extra environment variables that will be set when running `cargo check`.
++ /// Extends `#rust-analyzer.cargo.extraEnv#`.
+ checkOnSave_extraEnv: FxHashMap<String, String> = "{}",
+ /// List of features to activate. Defaults to
+ /// `#rust-analyzer.cargo.features#`.
+ ///
+ /// Set to `"all"` to pass `--all-features` to Cargo.
- pub fn proc_macro_srv(&self) -> Option<(AbsPathBuf, Vec<OsString>)> {
++ checkOnSave_features: Option<CargoFeaturesDef> = "null",
+ /// Whether to pass `--no-default-features` to Cargo. Defaults to
+ /// `#rust-analyzer.cargo.noDefaultFeatures#`.
+ checkOnSave_noDefaultFeatures: Option<bool> = "null",
+ /// Override the command rust-analyzer uses instead of `cargo check` for
+ /// diagnostics on save. The command is required to output json and
+ /// should therefor include `--message-format=json` or a similar option.
+ ///
+ /// If you're changing this because you're using some tool wrapping
+ /// Cargo, you might also want to change
+ /// `#rust-analyzer.cargo.buildScripts.overrideCommand#`.
+ ///
+ /// If there are multiple linked projects, this command is invoked for
+ /// each of them, with the working directory being the project root
+ /// (i.e., the folder containing the `Cargo.toml`).
+ ///
+ /// An example command would be:
+ ///
+ /// ```bash
+ /// cargo check --workspace --message-format=json --all-targets
+ /// ```
+ /// .
+ checkOnSave_overrideCommand: Option<Vec<String>> = "null",
+ /// Check for a specific target. Defaults to
+ /// `#rust-analyzer.cargo.target#`.
+ checkOnSave_target: Option<String> = "null",
+
+ /// Toggles the additional completions that automatically add imports when completed.
+ /// Note that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled.
+ completion_autoimport_enable: bool = "true",
+ /// Toggles the additional completions that automatically show method calls and field accesses
+ /// with `self` prefixed to them when inside a method.
+ completion_autoself_enable: bool = "true",
+ /// Whether to add parenthesis and argument snippets when completing function.
+ completion_callable_snippets: CallableCompletionDef = "\"fill_arguments\"",
+ /// Whether to show postfix snippets like `dbg`, `if`, `not`, etc.
+ completion_postfix_enable: bool = "true",
+ /// Enables completions of private items and fields that are defined in the current workspace even if they are not visible at the current position.
+ completion_privateEditable_enable: bool = "false",
+ /// Custom completion snippets.
+ // NOTE: Keep this list in sync with the feature docs of user snippets.
+ completion_snippets_custom: FxHashMap<String, SnippetDef> = r#"{
+ "Arc::new": {
+ "postfix": "arc",
+ "body": "Arc::new(${receiver})",
+ "requires": "std::sync::Arc",
+ "description": "Put the expression into an `Arc`",
+ "scope": "expr"
+ },
+ "Rc::new": {
+ "postfix": "rc",
+ "body": "Rc::new(${receiver})",
+ "requires": "std::rc::Rc",
+ "description": "Put the expression into an `Rc`",
+ "scope": "expr"
+ },
+ "Box::pin": {
+ "postfix": "pinbox",
+ "body": "Box::pin(${receiver})",
+ "requires": "std::boxed::Box",
+ "description": "Put the expression into a pinned `Box`",
+ "scope": "expr"
+ },
+ "Ok": {
+ "postfix": "ok",
+ "body": "Ok(${receiver})",
+ "description": "Wrap the expression in a `Result::Ok`",
+ "scope": "expr"
+ },
+ "Err": {
+ "postfix": "err",
+ "body": "Err(${receiver})",
+ "description": "Wrap the expression in a `Result::Err`",
+ "scope": "expr"
+ },
+ "Some": {
+ "postfix": "some",
+ "body": "Some(${receiver})",
+ "description": "Wrap the expression in an `Option::Some`",
+ "scope": "expr"
+ }
+ }"#,
+
+ /// List of rust-analyzer diagnostics to disable.
+ diagnostics_disabled: FxHashSet<String> = "[]",
+ /// Whether to show native rust-analyzer diagnostics.
+ diagnostics_enable: bool = "true",
+ /// Whether to show experimental rust-analyzer diagnostics that might
+ /// have more false positives than usual.
+ diagnostics_experimental_enable: bool = "false",
+ /// Map of prefixes to be substituted when parsing diagnostic file paths.
+ /// This should be the reverse mapping of what is passed to `rustc` as `--remap-path-prefix`.
+ diagnostics_remapPrefix: FxHashMap<String, String> = "{}",
+ /// List of warnings that should be displayed with hint severity.
+ ///
+ /// The warnings will be indicated by faded text or three dots in code
+ /// and will not show up in the `Problems Panel`.
+ diagnostics_warningsAsHint: Vec<String> = "[]",
+ /// List of warnings that should be displayed with info severity.
+ ///
+ /// The warnings will be indicated by a blue squiggly underline in code
+ /// and a blue icon in the `Problems Panel`.
+ diagnostics_warningsAsInfo: Vec<String> = "[]",
+
+ /// These directories will be ignored by rust-analyzer. They are
+ /// relative to the workspace root, and globs are not supported. You may
+ /// also need to add the folders to Code's `files.watcherExclude`.
+ files_excludeDirs: Vec<PathBuf> = "[]",
+ /// Controls file watching implementation.
+ files_watcher: FilesWatcherDef = "\"client\"",
+ /// Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords.
+ highlightRelated_breakPoints_enable: bool = "true",
+ /// Enables highlighting of all exit points while the cursor is on any `return`, `?`, `fn`, or return type arrow (`->`).
+ highlightRelated_exitPoints_enable: bool = "true",
+ /// Enables highlighting of related references while the cursor is on any identifier.
+ highlightRelated_references_enable: bool = "true",
+ /// Enables highlighting of all break points for a loop or block context while the cursor is on any `async` or `await` keywords.
+ highlightRelated_yieldPoints_enable: bool = "true",
+
+ /// Whether to show `Debug` action. Only applies when
+ /// `#rust-analyzer.hover.actions.enable#` is set.
+ hover_actions_debug_enable: bool = "true",
+ /// Whether to show HoverActions in Rust files.
+ hover_actions_enable: bool = "true",
+ /// Whether to show `Go to Type Definition` action. Only applies when
+ /// `#rust-analyzer.hover.actions.enable#` is set.
+ hover_actions_gotoTypeDef_enable: bool = "true",
+ /// Whether to show `Implementations` action. Only applies when
+ /// `#rust-analyzer.hover.actions.enable#` is set.
+ hover_actions_implementations_enable: bool = "true",
+ /// Whether to show `References` action. Only applies when
+ /// `#rust-analyzer.hover.actions.enable#` is set.
+ hover_actions_references_enable: bool = "false",
+ /// Whether to show `Run` action. Only applies when
+ /// `#rust-analyzer.hover.actions.enable#` is set.
+ hover_actions_run_enable: bool = "true",
+
+ /// Whether to show documentation on hover.
+ hover_documentation_enable: bool = "true",
+ /// Whether to show keyword hover popups. Only applies when
+ /// `#rust-analyzer.hover.documentation.enable#` is set.
+ hover_documentation_keywords_enable: bool = "true",
+ /// Use markdown syntax for links in hover.
+ hover_links_enable: bool = "true",
+
+ /// Whether to enforce the import granularity setting for all files. If set to false rust-analyzer will try to keep import styles consistent per file.
+ imports_granularity_enforce: bool = "false",
+ /// How imports should be grouped into use statements.
+ imports_granularity_group: ImportGranularityDef = "\"crate\"",
+ /// Group inserted imports by the https://rust-analyzer.github.io/manual.html#auto-import[following order]. Groups are separated by newlines.
+ imports_group_enable: bool = "true",
+ /// Whether to allow import insertion to merge new imports into single path glob imports like `use std::fmt::*;`.
+ imports_merge_glob: bool = "true",
+ /// Prefer to unconditionally use imports of the core and alloc crate, over the std crate.
+ imports_prefer_no_std: bool = "false",
+ /// The path structure for newly inserted paths to use.
+ imports_prefix: ImportPrefixDef = "\"plain\"",
+
+ /// Whether to show inlay type hints for binding modes.
+ inlayHints_bindingModeHints_enable: bool = "false",
+ /// Whether to show inlay type hints for method chains.
+ inlayHints_chainingHints_enable: bool = "true",
+ /// Whether to show inlay hints after a closing `}` to indicate what item it belongs to.
+ inlayHints_closingBraceHints_enable: bool = "true",
+ /// Minimum number of lines required before the `}` until the hint is shown (set to 0 or 1
+ /// to always show them).
+ inlayHints_closingBraceHints_minLines: usize = "25",
+ /// Whether to show inlay type hints for return types of closures.
+ inlayHints_closureReturnTypeHints_enable: ClosureReturnTypeHintsDef = "\"never\"",
+ /// Whether to show inlay type hints for elided lifetimes in function signatures.
+ inlayHints_lifetimeElisionHints_enable: LifetimeElisionDef = "\"never\"",
+ /// Whether to prefer using parameter names as the name for elided lifetime hints if possible.
+ inlayHints_lifetimeElisionHints_useParameterNames: bool = "false",
+ /// Maximum length for inlay hints. Set to null to have an unlimited length.
+ inlayHints_maxLength: Option<usize> = "25",
+ /// Whether to show function parameter name inlay hints at the call
+ /// site.
+ inlayHints_parameterHints_enable: bool = "true",
+ /// Whether to show inlay type hints for compiler inserted reborrows.
+ inlayHints_reborrowHints_enable: ReborrowHintsDef = "\"never\"",
+ /// Whether to render leading colons for type hints, and trailing colons for parameter hints.
+ inlayHints_renderColons: bool = "true",
+ /// Whether to show inlay type hints for variables.
+ inlayHints_typeHints_enable: bool = "true",
+ /// Whether to hide inlay type hints for `let` statements that initialize to a closure.
+ /// Only applies to closures with blocks, same as `#rust-analyzer.inlayHints.closureReturnTypeHints.enable#`.
+ inlayHints_typeHints_hideClosureInitialization: bool = "false",
+ /// Whether to hide inlay type hints for constructors.
+ inlayHints_typeHints_hideNamedConstructor: bool = "false",
+
+ /// Join lines merges consecutive declaration and initialization of an assignment.
+ joinLines_joinAssignments: bool = "true",
+ /// Join lines inserts else between consecutive ifs.
+ joinLines_joinElseIf: bool = "true",
+ /// Join lines removes trailing commas.
+ joinLines_removeTrailingComma: bool = "true",
+ /// Join lines unwraps trivial blocks.
+ joinLines_unwrapTrivialBlock: bool = "true",
+
+
+ /// Whether to show `Debug` lens. Only applies when
+ /// `#rust-analyzer.lens.enable#` is set.
+ lens_debug_enable: bool = "true",
+ /// Whether to show CodeLens in Rust files.
+ lens_enable: bool = "true",
+ /// Internal config: use custom client-side commands even when the
+ /// client doesn't set the corresponding capability.
+ lens_forceCustomCommands: bool = "true",
+ /// Whether to show `Implementations` lens. Only applies when
+ /// `#rust-analyzer.lens.enable#` is set.
+ lens_implementations_enable: bool = "true",
+ /// Where to render annotations.
+ lens_location: AnnotationLocation = "\"above_name\"",
+ /// Whether to show `References` lens for Struct, Enum, and Union.
+ /// Only applies when `#rust-analyzer.lens.enable#` is set.
+ lens_references_adt_enable: bool = "false",
+ /// Whether to show `References` lens for Enum Variants.
+ /// Only applies when `#rust-analyzer.lens.enable#` is set.
+ lens_references_enumVariant_enable: bool = "false",
+ /// Whether to show `Method References` lens. Only applies when
+ /// `#rust-analyzer.lens.enable#` is set.
+ lens_references_method_enable: bool = "false",
+ /// Whether to show `References` lens for Trait.
+ /// Only applies when `#rust-analyzer.lens.enable#` is set.
+ lens_references_trait_enable: bool = "false",
+ /// Whether to show `Run` lens. Only applies when
+ /// `#rust-analyzer.lens.enable#` is set.
+ lens_run_enable: bool = "true",
+
+ /// Disable project auto-discovery in favor of explicitly specified set
+ /// of projects.
+ ///
+ /// Elements must be paths pointing to `Cargo.toml`,
+ /// `rust-project.json`, or JSON objects in `rust-project.json` format.
+ linkedProjects: Vec<ManifestOrProjectJson> = "[]",
+
+ /// Number of syntax trees rust-analyzer keeps in memory. Defaults to 128.
+ lru_capacity: Option<usize> = "null",
+
+ /// Whether to show `can't find Cargo.toml` error message.
+ notifications_cargoTomlNotFound: bool = "true",
+
+ /// Expand attribute macros. Requires `#rust-analyzer.procMacro.enable#` to be set.
+ procMacro_attributes_enable: bool = "true",
+ /// Enable support for procedural macros, implies `#rust-analyzer.cargo.buildScripts.enable#`.
+ procMacro_enable: bool = "true",
+ /// These proc-macros will be ignored when trying to expand them.
+ ///
+ /// This config takes a map of crate names with the exported proc-macro names to ignore as values.
+ procMacro_ignored: FxHashMap<Box<str>, Box<[Box<str>]>> = "{}",
+ /// Internal config, path to proc-macro server executable (typically,
+ /// this is rust-analyzer itself, but we override this in tests).
+ procMacro_server: Option<PathBuf> = "null",
+
+ /// Exclude imports from find-all-references.
+ references_excludeImports: bool = "false",
+
+ /// Command to be executed instead of 'cargo' for runnables.
+ runnables_command: Option<String> = "null",
+ /// Additional arguments to be passed to cargo for runnables such as
+ /// tests or binaries. For example, it may be `--release`.
+ runnables_extraArgs: Vec<String> = "[]",
+
+ /// Path to the Cargo.toml of the rust compiler workspace, for usage in rustc_private
+ /// projects, or "discover" to try to automatically find it if the `rustc-dev` component
+ /// is installed.
+ ///
+ /// Any project which uses rust-analyzer with the rustcPrivate
+ /// crates must set `[package.metadata.rust-analyzer] rustc_private=true` to use it.
+ ///
+ /// This option does not take effect until rust-analyzer is restarted.
+ rustc_source: Option<String> = "null",
+
+ /// Additional arguments to `rustfmt`.
+ rustfmt_extraArgs: Vec<String> = "[]",
+ /// Advanced option, fully override the command rust-analyzer uses for
+ /// formatting.
+ rustfmt_overrideCommand: Option<Vec<String>> = "null",
+ /// Enables the use of rustfmt's unstable range formatting command for the
+ /// `textDocument/rangeFormatting` request. The rustfmt option is unstable and only
+ /// available on a nightly build.
+ rustfmt_rangeFormatting_enable: bool = "false",
+
+ /// Inject additional highlighting into doc comments.
+ ///
+ /// When enabled, rust-analyzer will highlight rust source in doc comments as well as intra
+ /// doc links.
+ semanticHighlighting_doc_comment_inject_enable: bool = "true",
+ /// Use semantic tokens for operators.
+ ///
+ /// When disabled, rust-analyzer will emit semantic tokens only for operator tokens when
+ /// they are tagged with modifiers.
+ semanticHighlighting_operator_enable: bool = "true",
+ /// Use specialized semantic tokens for operators.
+ ///
+ /// When enabled, rust-analyzer will emit special token types for operator tokens instead
+ /// of the generic `operator` token type.
+ semanticHighlighting_operator_specialization_enable: bool = "false",
+ /// Use semantic tokens for punctuations.
+ ///
+ /// When disabled, rust-analyzer will emit semantic tokens only for punctuation tokens when
+ /// they are tagged with modifiers or have a special role.
+ semanticHighlighting_punctuation_enable: bool = "false",
+ /// When enabled, rust-analyzer will emit a punctuation semantic token for the `!` of macro
+ /// calls.
+ semanticHighlighting_punctuation_separate_macro_bang: bool = "false",
+ /// Use specialized semantic tokens for punctuations.
+ ///
+ /// When enabled, rust-analyzer will emit special token types for punctuation tokens instead
+ /// of the generic `punctuation` token type.
+ semanticHighlighting_punctuation_specialization_enable: bool = "false",
+ /// Use semantic tokens for strings.
+ ///
+ /// In some editors (e.g. vscode) semantic tokens override other highlighting grammars.
+ /// By disabling semantic tokens for strings, other grammars can be used to highlight
+ /// their contents.
+ semanticHighlighting_strings_enable: bool = "true",
+
+ /// Show full signature of the callable. Only shows parameters if disabled.
+ signatureInfo_detail: SignatureDetail = "\"full\"",
+ /// Show documentation.
+ signatureInfo_documentation_enable: bool = "true",
+
+ /// Whether to insert closing angle brackets when typing an opening angle bracket of a generic argument list.
+ typing_autoClosingAngleBrackets_enable: bool = "false",
+
+ /// Workspace symbol search kind.
+ workspace_symbol_search_kind: WorkspaceSymbolSearchKindDef = "\"only_types\"",
+ /// Limits the number of items returned from a workspace symbol search (Defaults to 128).
+ /// Some clients like vs-code issue new searches on result filtering and don't require all results to be returned in the initial search.
+ /// Other clients requires all results upfront and might require a higher limit.
+ workspace_symbol_search_limit: usize = "128",
+ /// Workspace symbol search scope.
+ workspace_symbol_search_scope: WorkspaceSymbolSearchScopeDef = "\"workspace\"",
+ }
+}
+
+impl Default for ConfigData {
+ fn default() -> Self {
+ ConfigData::from_json(serde_json::Value::Null, &mut Vec::new())
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct Config {
+ pub discovered_projects: Option<Vec<ProjectManifest>>,
+ caps: lsp_types::ClientCapabilities,
+ root_path: AbsPathBuf,
+ data: ConfigData,
+ detached_files: Vec<AbsPathBuf>,
+ snippets: Vec<Snippet>,
+}
+
+type ParallelCachePrimingNumThreads = u8;
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub enum LinkedProject {
+ ProjectManifest(ProjectManifest),
+ InlineJsonProject(ProjectJson),
+}
+
+impl From<ProjectManifest> for LinkedProject {
+ fn from(v: ProjectManifest) -> Self {
+ LinkedProject::ProjectManifest(v)
+ }
+}
+
+impl From<ProjectJson> for LinkedProject {
+ fn from(v: ProjectJson) -> Self {
+ LinkedProject::InlineJsonProject(v)
+ }
+}
+
+pub struct CallInfoConfig {
+ pub params_only: bool,
+ pub docs: bool,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct LensConfig {
+ // runnables
+ pub run: bool,
+ pub debug: bool,
+
+ // implementations
+ pub implementations: bool,
+
+ // references
+ pub method_refs: bool,
+ pub refs_adt: bool, // for Struct, Enum, Union and Trait
+ pub refs_trait: bool, // for Struct, Enum, Union and Trait
+ pub enum_variant_refs: bool,
+
+ // annotations
+ pub location: AnnotationLocation,
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)]
+#[serde(rename_all = "snake_case")]
+pub enum AnnotationLocation {
+ AboveName,
+ AboveWholeItem,
+}
+
+impl From<AnnotationLocation> for ide::AnnotationLocation {
+ fn from(location: AnnotationLocation) -> Self {
+ match location {
+ AnnotationLocation::AboveName => ide::AnnotationLocation::AboveName,
+ AnnotationLocation::AboveWholeItem => ide::AnnotationLocation::AboveWholeItem,
+ }
+ }
+}
+
+impl LensConfig {
+ pub fn any(&self) -> bool {
+ self.run
+ || self.debug
+ || self.implementations
+ || self.method_refs
+ || self.refs_adt
+ || self.refs_trait
+ || self.enum_variant_refs
+ }
+
+ pub fn none(&self) -> bool {
+ !self.any()
+ }
+
+ pub fn runnable(&self) -> bool {
+ self.run || self.debug
+ }
+
+ pub fn references(&self) -> bool {
+ self.method_refs || self.refs_adt || self.refs_trait || self.enum_variant_refs
+ }
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct HoverActionsConfig {
+ pub implementations: bool,
+ pub references: bool,
+ pub run: bool,
+ pub debug: bool,
+ pub goto_type_def: bool,
+}
+
+impl HoverActionsConfig {
+ pub const NO_ACTIONS: Self = Self {
+ implementations: false,
+ references: false,
+ run: false,
+ debug: false,
+ goto_type_def: false,
+ };
+
+ pub fn any(&self) -> bool {
+ self.implementations || self.references || self.runnable() || self.goto_type_def
+ }
+
+ pub fn none(&self) -> bool {
+ !self.any()
+ }
+
+ pub fn runnable(&self) -> bool {
+ self.run || self.debug
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct FilesConfig {
+ pub watcher: FilesWatcher,
+ pub exclude: Vec<AbsPathBuf>,
+}
+
+#[derive(Debug, Clone)]
+pub enum FilesWatcher {
+ Client,
+ Server,
+}
+
+#[derive(Debug, Clone)]
+pub struct NotificationsConfig {
+ pub cargo_toml_not_found: bool,
+}
+
+#[derive(Debug, Clone)]
+pub enum RustfmtConfig {
+ Rustfmt { extra_args: Vec<String>, enable_range_formatting: bool },
+ CustomCommand { command: String, args: Vec<String> },
+}
+
+/// Configuration for runnable items, such as `main` function or tests.
+#[derive(Debug, Clone)]
+pub struct RunnablesConfig {
+ /// Custom command to be executed instead of `cargo` for runnables.
+ pub override_cargo: Option<String>,
+ /// Additional arguments for the `cargo`, e.g. `--release`.
+ pub cargo_extra_args: Vec<String>,
+}
+
+/// Configuration for workspace symbol search requests.
+#[derive(Debug, Clone)]
+pub struct WorkspaceSymbolConfig {
+ /// In what scope should the symbol be searched in.
+ pub search_scope: WorkspaceSymbolSearchScope,
+ /// What kind of symbol is being searched for.
+ pub search_kind: WorkspaceSymbolSearchKind,
+ /// How many items are returned at most.
+ pub search_limit: usize,
+}
+
+pub struct ClientCommandsConfig {
+ pub run_single: bool,
+ pub debug_single: bool,
+ pub show_reference: bool,
+ pub goto_location: bool,
+ pub trigger_parameter_hints: bool,
+}
+
+#[derive(Debug)]
+pub struct ConfigUpdateError {
+ errors: Vec<(String, serde_json::Error)>,
+}
+
+impl fmt::Display for ConfigUpdateError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let errors = self.errors.iter().format_with("\n", |(key, e), f| {
+ f(key)?;
+ f(&": ")?;
+ f(e)
+ });
+ write!(
+ f,
+ "rust-analyzer found {} invalid config value{}:\n{}",
+ self.errors.len(),
+ if self.errors.len() == 1 { "" } else { "s" },
+ errors
+ )
+ }
+}
+
+impl Config {
+ pub fn new(root_path: AbsPathBuf, caps: ClientCapabilities) -> Self {
+ Config {
+ caps,
+ data: ConfigData::default(),
+ detached_files: Vec::new(),
+ discovered_projects: None,
+ root_path,
+ snippets: Default::default(),
+ }
+ }
+
+ pub fn update(&mut self, mut json: serde_json::Value) -> Result<(), ConfigUpdateError> {
+ tracing::info!("updating config from JSON: {:#}", json);
+ if json.is_null() || json.as_object().map_or(false, |it| it.is_empty()) {
+ return Ok(());
+ }
+ let mut errors = Vec::new();
+ self.detached_files =
+ get_field::<Vec<PathBuf>>(&mut json, &mut errors, "detachedFiles", None, "[]")
+ .into_iter()
+ .map(AbsPathBuf::assert)
+ .collect();
+ patch_old_style::patch_json_for_outdated_configs(&mut json);
+ self.data = ConfigData::from_json(json, &mut errors);
+ tracing::debug!("deserialized config data: {:#?}", self.data);
+ self.snippets.clear();
+ for (name, def) in self.data.completion_snippets_custom.iter() {
+ if def.prefix.is_empty() && def.postfix.is_empty() {
+ continue;
+ }
+ let scope = match def.scope {
+ SnippetScopeDef::Expr => SnippetScope::Expr,
+ SnippetScopeDef::Type => SnippetScope::Type,
+ SnippetScopeDef::Item => SnippetScope::Item,
+ };
+ match Snippet::new(
+ &def.prefix,
+ &def.postfix,
+ &def.body,
+ def.description.as_ref().unwrap_or(name),
+ &def.requires,
+ scope,
+ ) {
+ Some(snippet) => self.snippets.push(snippet),
+ None => errors.push((
+ format!("snippet {name} is invalid"),
+ <serde_json::Error as serde::de::Error>::custom(
+ "snippet path is invalid or triggers are missing",
+ ),
+ )),
+ }
+ }
+
+ self.validate(&mut errors);
+
+ if errors.is_empty() {
+ Ok(())
+ } else {
+ Err(ConfigUpdateError { errors })
+ }
+ }
+
+ fn validate(&self, error_sink: &mut Vec<(String, serde_json::Error)>) {
+ use serde::de::Error;
+ if self.data.checkOnSave_command.is_empty() {
+ error_sink.push((
+ "/checkOnSave/command".to_string(),
+ serde_json::Error::custom("expected a non-empty string"),
+ ));
+ }
+ }
+
+ pub fn json_schema() -> serde_json::Value {
+ ConfigData::json_schema()
+ }
+
+ pub fn root_path(&self) -> &AbsPathBuf {
+ &self.root_path
+ }
+
+ pub fn caps(&self) -> &lsp_types::ClientCapabilities {
+ &self.caps
+ }
+
+ pub fn detached_files(&self) -> &[AbsPathBuf] {
+ &self.detached_files
+ }
+}
+
+macro_rules! try_ {
+ ($expr:expr) => {
+ || -> _ { Some($expr) }()
+ };
+}
+macro_rules! try_or {
+ ($expr:expr, $or:expr) => {
+ try_!($expr).unwrap_or($or)
+ };
+}
+
+macro_rules! try_or_def {
+ ($expr:expr) => {
+ try_!($expr).unwrap_or_default()
+ };
+}
+
+impl Config {
+ pub fn linked_projects(&self) -> Vec<LinkedProject> {
+ match self.data.linkedProjects.as_slice() {
+ [] => match self.discovered_projects.as_ref() {
+ Some(discovered_projects) => {
+ let exclude_dirs: Vec<_> = self
+ .data
+ .files_excludeDirs
+ .iter()
+ .map(|p| self.root_path.join(p))
+ .collect();
+ discovered_projects
+ .iter()
+ .filter(|p| {
+ let (ProjectManifest::ProjectJson(path)
+ | ProjectManifest::CargoToml(path)) = p;
+ !exclude_dirs.iter().any(|p| path.starts_with(p))
+ })
+ .cloned()
+ .map(LinkedProject::from)
+ .collect()
+ }
+ None => Vec::new(),
+ },
+ linked_projects => linked_projects
+ .iter()
+ .filter_map(|linked_project| match linked_project {
+ ManifestOrProjectJson::Manifest(it) => {
+ let path = self.root_path.join(it);
+ ProjectManifest::from_manifest_file(path)
+ .map_err(|e| tracing::error!("failed to load linked project: {}", e))
+ .ok()
+ .map(Into::into)
+ }
+ ManifestOrProjectJson::ProjectJson(it) => {
+ Some(ProjectJson::new(&self.root_path, it.clone()).into())
+ }
+ })
+ .collect(),
+ }
+ }
+
+ pub fn did_save_text_document_dynamic_registration(&self) -> bool {
+ let caps = try_or_def!(self.caps.text_document.as_ref()?.synchronization.clone()?);
+ caps.did_save == Some(true) && caps.dynamic_registration == Some(true)
+ }
+
+ pub fn did_change_watched_files_dynamic_registration(&self) -> bool {
+ try_or_def!(
+ self.caps.workspace.as_ref()?.did_change_watched_files.as_ref()?.dynamic_registration?
+ )
+ }
+
+ pub fn prefill_caches(&self) -> bool {
+ self.data.cachePriming_enable
+ }
+
+ pub fn location_link(&self) -> bool {
+ try_or_def!(self.caps.text_document.as_ref()?.definition?.link_support?)
+ }
+
+ pub fn line_folding_only(&self) -> bool {
+ try_or_def!(self.caps.text_document.as_ref()?.folding_range.as_ref()?.line_folding_only?)
+ }
+
+ pub fn hierarchical_symbols(&self) -> bool {
+ try_or_def!(
+ self.caps
+ .text_document
+ .as_ref()?
+ .document_symbol
+ .as_ref()?
+ .hierarchical_document_symbol_support?
+ )
+ }
+
+ pub fn code_action_literals(&self) -> bool {
+ try_!(self
+ .caps
+ .text_document
+ .as_ref()?
+ .code_action
+ .as_ref()?
+ .code_action_literal_support
+ .as_ref()?)
+ .is_some()
+ }
+
+ pub fn work_done_progress(&self) -> bool {
+ try_or_def!(self.caps.window.as_ref()?.work_done_progress?)
+ }
+
+ pub fn will_rename(&self) -> bool {
+ try_or_def!(self.caps.workspace.as_ref()?.file_operations.as_ref()?.will_rename?)
+ }
+
+ pub fn change_annotation_support(&self) -> bool {
+ try_!(self
+ .caps
+ .workspace
+ .as_ref()?
+ .workspace_edit
+ .as_ref()?
+ .change_annotation_support
+ .as_ref()?)
+ .is_some()
+ }
+
+ pub fn code_action_resolve(&self) -> bool {
+ try_or_def!(self
+ .caps
+ .text_document
+ .as_ref()?
+ .code_action
+ .as_ref()?
+ .resolve_support
+ .as_ref()?
+ .properties
+ .as_slice())
+ .iter()
+ .any(|it| it == "edit")
+ }
+
+ pub fn signature_help_label_offsets(&self) -> bool {
+ try_or_def!(
+ self.caps
+ .text_document
+ .as_ref()?
+ .signature_help
+ .as_ref()?
+ .signature_information
+ .as_ref()?
+ .parameter_information
+ .as_ref()?
+ .label_offset_support?
+ )
+ }
+
+ pub fn completion_label_details_support(&self) -> bool {
+ try_!(self
+ .caps
+ .text_document
+ .as_ref()?
+ .completion
+ .as_ref()?
+ .completion_item
+ .as_ref()?
+ .label_details_support
+ .as_ref()?)
+ .is_some()
+ }
+
+ pub fn offset_encoding(&self) -> OffsetEncoding {
+ if supports_utf8(&self.caps) {
+ OffsetEncoding::Utf8
+ } else {
+ OffsetEncoding::Utf16
+ }
+ }
+
+ fn experimental(&self, index: &'static str) -> bool {
+ try_or_def!(self.caps.experimental.as_ref()?.get(index)?.as_bool()?)
+ }
+
+ pub fn code_action_group(&self) -> bool {
+ self.experimental("codeActionGroup")
+ }
+
+ pub fn server_status_notification(&self) -> bool {
+ self.experimental("serverStatusNotification")
+ }
+
+ pub fn publish_diagnostics(&self) -> bool {
+ self.data.diagnostics_enable
+ }
+
+ pub fn diagnostics(&self) -> DiagnosticsConfig {
+ DiagnosticsConfig {
+ proc_attr_macros_enabled: self.expand_proc_attr_macros(),
+ proc_macros_enabled: self.data.procMacro_enable,
+ disable_experimental: !self.data.diagnostics_experimental_enable,
+ disabled: self.data.diagnostics_disabled.clone(),
+ expr_fill_default: match self.data.assist_expressionFillDefault {
+ ExprFillDefaultDef::Todo => ExprFillDefaultMode::Todo,
+ ExprFillDefaultDef::Default => ExprFillDefaultMode::Default,
+ },
+ insert_use: self.insert_use_config(),
+ prefer_no_std: self.data.imports_prefer_no_std,
+ }
+ }
+
+ pub fn diagnostics_map(&self) -> DiagnosticsMapConfig {
+ DiagnosticsMapConfig {
+ remap_prefix: self.data.diagnostics_remapPrefix.clone(),
+ warnings_as_info: self.data.diagnostics_warningsAsInfo.clone(),
+ warnings_as_hint: self.data.diagnostics_warningsAsHint.clone(),
+ }
+ }
+
+ pub fn extra_env(&self) -> &FxHashMap<String, String> {
+ &self.data.cargo_extraEnv
+ }
+
+ pub fn check_on_save_extra_env(&self) -> FxHashMap<String, String> {
+ let mut extra_env = self.data.cargo_extraEnv.clone();
+ extra_env.extend(self.data.checkOnSave_extraEnv.clone());
+ extra_env
+ }
+
+ pub fn lru_capacity(&self) -> Option<usize> {
+ self.data.lru_capacity
+ }
+
- let path = match &self.data.procMacro_server {
- Some(it) => self.root_path.join(it),
- None => AbsPathBuf::assert(std::env::current_exe().ok()?),
- };
- Some((path, vec!["proc-macro".into()]))
++ pub fn proc_macro_srv(&self) -> Option<(AbsPathBuf, /* is path explicitly set */ bool)> {
+ if !self.data.procMacro_enable {
+ return None;
+ }
- no_default_features: self.data.cargo_noDefaultFeatures,
- all_features: matches!(self.data.cargo_features, CargoFeatures::All),
++ Some(match &self.data.procMacro_server {
++ Some(it) => (
++ AbsPathBuf::try_from(it.clone()).unwrap_or_else(|path| self.root_path.join(path)),
++ true,
++ ),
++ None => (AbsPathBuf::assert(std::env::current_exe().ok()?), false),
++ })
+ }
+
+ pub fn dummy_replacements(&self) -> &FxHashMap<Box<str>, Box<[Box<str>]>> {
+ &self.data.procMacro_ignored
+ }
+
+ pub fn expand_proc_attr_macros(&self) -> bool {
+ self.data.procMacro_enable && self.data.procMacro_attributes_enable
+ }
+
+ pub fn files(&self) -> FilesConfig {
+ FilesConfig {
+ watcher: match self.data.files_watcher {
+ FilesWatcherDef::Client if self.did_change_watched_files_dynamic_registration() => {
+ FilesWatcher::Client
+ }
+ _ => FilesWatcher::Server,
+ },
+ exclude: self.data.files_excludeDirs.iter().map(|it| self.root_path.join(it)).collect(),
+ }
+ }
+
+ pub fn notifications(&self) -> NotificationsConfig {
+ NotificationsConfig { cargo_toml_not_found: self.data.notifications_cargoTomlNotFound }
+ }
+
+ pub fn cargo_autoreload(&self) -> bool {
+ self.data.cargo_autoreload
+ }
+
+ pub fn run_build_scripts(&self) -> bool {
+ self.data.cargo_buildScripts_enable || self.data.procMacro_enable
+ }
+
+ pub fn cargo(&self) -> CargoConfig {
+ let rustc_source = self.data.rustc_source.as_ref().map(|rustc_src| {
+ if rustc_src == "discover" {
+ RustcSource::Discover
+ } else {
+ RustcSource::Path(self.root_path.join(rustc_src))
+ }
+ });
++ let sysroot = self.data.cargo_sysroot.as_ref().map(|sysroot| {
++ if sysroot == "discover" {
++ RustcSource::Discover
++ } else {
++ RustcSource::Path(self.root_path.join(sysroot))
++ }
++ });
+
+ CargoConfig {
- CargoFeatures::All => vec![],
- CargoFeatures::Listed(it) => it.clone(),
+ features: match &self.data.cargo_features {
- no_sysroot: self.data.cargo_noSysroot,
++ CargoFeaturesDef::All => CargoFeatures::All,
++ CargoFeaturesDef::Selected(features) => CargoFeatures::Selected {
++ features: features.clone(),
++ no_default_features: self.data.cargo_noDefaultFeatures,
++ },
+ },
+ target: self.data.cargo_target.clone(),
- CargoFeatures::All
++ sysroot,
+ rustc_source,
+ unset_test_crates: UnsetTestCrates::Only(self.data.cargo_unsetTest.clone()),
+ wrap_rustc_in_build_scripts: self.data.cargo_buildScripts_useRustcWrapper,
+ run_build_script_command: self.data.cargo_buildScripts_overrideCommand.clone(),
+ extra_env: self.data.cargo_extraEnv.clone(),
+ }
+ }
+
+ pub fn rustfmt(&self) -> RustfmtConfig {
+ match &self.data.rustfmt_overrideCommand {
+ Some(args) if !args.is_empty() => {
+ let mut args = args.clone();
+ let command = args.remove(0);
+ RustfmtConfig::CustomCommand { command, args }
+ }
+ Some(_) | None => RustfmtConfig::Rustfmt {
+ extra_args: self.data.rustfmt_extraArgs.clone(),
+ enable_range_formatting: self.data.rustfmt_rangeFormatting_enable,
+ },
+ }
+ }
+
+ pub fn flycheck(&self) -> Option<FlycheckConfig> {
+ if !self.data.checkOnSave_enable {
+ return None;
+ }
+ let flycheck_config = match &self.data.checkOnSave_overrideCommand {
+ Some(args) if !args.is_empty() => {
+ let mut args = args.clone();
+ let command = args.remove(0);
+ FlycheckConfig::CustomCommand {
+ command,
+ args,
+ extra_env: self.check_on_save_extra_env(),
+ }
+ }
+ Some(_) | None => FlycheckConfig::CargoCommand {
+ command: self.data.checkOnSave_command.clone(),
+ target_triple: self
+ .data
+ .checkOnSave_target
+ .clone()
+ .or_else(|| self.data.cargo_target.clone()),
+ all_targets: self.data.checkOnSave_allTargets,
+ no_default_features: self
+ .data
+ .checkOnSave_noDefaultFeatures
+ .unwrap_or(self.data.cargo_noDefaultFeatures),
+ all_features: matches!(
+ self.data.checkOnSave_features.as_ref().unwrap_or(&self.data.cargo_features),
- CargoFeatures::All => vec![],
- CargoFeatures::Listed(it) => it,
++ CargoFeaturesDef::All
+ ),
+ features: match self
+ .data
+ .checkOnSave_features
+ .clone()
+ .unwrap_or_else(|| self.data.cargo_features.clone())
+ {
- enum CargoFeatures {
++ CargoFeaturesDef::All => vec![],
++ CargoFeaturesDef::Selected(it) => it,
+ },
+ extra_args: self.data.checkOnSave_extraArgs.clone(),
+ extra_env: self.check_on_save_extra_env(),
+ },
+ };
+ Some(flycheck_config)
+ }
+
+ pub fn runnables(&self) -> RunnablesConfig {
+ RunnablesConfig {
+ override_cargo: self.data.runnables_command.clone(),
+ cargo_extra_args: self.data.runnables_extraArgs.clone(),
+ }
+ }
+
+ pub fn inlay_hints(&self) -> InlayHintsConfig {
+ InlayHintsConfig {
+ render_colons: self.data.inlayHints_renderColons,
+ type_hints: self.data.inlayHints_typeHints_enable,
+ parameter_hints: self.data.inlayHints_parameterHints_enable,
+ chaining_hints: self.data.inlayHints_chainingHints_enable,
+ closure_return_type_hints: match self.data.inlayHints_closureReturnTypeHints_enable {
+ ClosureReturnTypeHintsDef::Always => ide::ClosureReturnTypeHints::Always,
+ ClosureReturnTypeHintsDef::Never => ide::ClosureReturnTypeHints::Never,
+ ClosureReturnTypeHintsDef::WithBlock => ide::ClosureReturnTypeHints::WithBlock,
+ },
+ lifetime_elision_hints: match self.data.inlayHints_lifetimeElisionHints_enable {
+ LifetimeElisionDef::Always => ide::LifetimeElisionHints::Always,
+ LifetimeElisionDef::Never => ide::LifetimeElisionHints::Never,
+ LifetimeElisionDef::SkipTrivial => ide::LifetimeElisionHints::SkipTrivial,
+ },
+ hide_named_constructor_hints: self.data.inlayHints_typeHints_hideNamedConstructor,
+ hide_closure_initialization_hints: self
+ .data
+ .inlayHints_typeHints_hideClosureInitialization,
+ reborrow_hints: match self.data.inlayHints_reborrowHints_enable {
+ ReborrowHintsDef::Always => ide::ReborrowHints::Always,
+ ReborrowHintsDef::Never => ide::ReborrowHints::Never,
+ ReborrowHintsDef::Mutable => ide::ReborrowHints::MutableOnly,
+ },
+ binding_mode_hints: self.data.inlayHints_bindingModeHints_enable,
+ param_names_for_lifetime_elision_hints: self
+ .data
+ .inlayHints_lifetimeElisionHints_useParameterNames,
+ max_length: self.data.inlayHints_maxLength,
+ closing_brace_hints_min_lines: if self.data.inlayHints_closingBraceHints_enable {
+ Some(self.data.inlayHints_closingBraceHints_minLines)
+ } else {
+ None
+ },
+ }
+ }
+
+ fn insert_use_config(&self) -> InsertUseConfig {
+ InsertUseConfig {
+ granularity: match self.data.imports_granularity_group {
+ ImportGranularityDef::Preserve => ImportGranularity::Preserve,
+ ImportGranularityDef::Item => ImportGranularity::Item,
+ ImportGranularityDef::Crate => ImportGranularity::Crate,
+ ImportGranularityDef::Module => ImportGranularity::Module,
+ },
+ enforce_granularity: self.data.imports_granularity_enforce,
+ prefix_kind: match self.data.imports_prefix {
+ ImportPrefixDef::Plain => PrefixKind::Plain,
+ ImportPrefixDef::ByCrate => PrefixKind::ByCrate,
+ ImportPrefixDef::BySelf => PrefixKind::BySelf,
+ },
+ group: self.data.imports_group_enable,
+ skip_glob_imports: !self.data.imports_merge_glob,
+ }
+ }
+
+ pub fn completion(&self) -> CompletionConfig {
+ CompletionConfig {
+ enable_postfix_completions: self.data.completion_postfix_enable,
+ enable_imports_on_the_fly: self.data.completion_autoimport_enable
+ && completion_item_edit_resolve(&self.caps),
+ enable_self_on_the_fly: self.data.completion_autoself_enable,
+ enable_private_editable: self.data.completion_privateEditable_enable,
+ callable: match self.data.completion_callable_snippets {
+ CallableCompletionDef::FillArguments => Some(CallableSnippets::FillArguments),
+ CallableCompletionDef::AddParentheses => Some(CallableSnippets::AddParentheses),
+ CallableCompletionDef::None => None,
+ },
+ insert_use: self.insert_use_config(),
+ prefer_no_std: self.data.imports_prefer_no_std,
+ snippet_cap: SnippetCap::new(try_or_def!(
+ self.caps
+ .text_document
+ .as_ref()?
+ .completion
+ .as_ref()?
+ .completion_item
+ .as_ref()?
+ .snippet_support?
+ )),
+ snippets: self.snippets.clone(),
+ }
+ }
+
+ pub fn find_all_refs_exclude_imports(&self) -> bool {
+ self.data.references_excludeImports
+ }
+
+ pub fn snippet_cap(&self) -> bool {
+ self.experimental("snippetTextEdit")
+ }
+
+ pub fn assist(&self) -> AssistConfig {
+ AssistConfig {
+ snippet_cap: SnippetCap::new(self.experimental("snippetTextEdit")),
+ allowed: None,
+ insert_use: self.insert_use_config(),
+ prefer_no_std: self.data.imports_prefer_no_std,
+ }
+ }
+
+ pub fn join_lines(&self) -> JoinLinesConfig {
+ JoinLinesConfig {
+ join_else_if: self.data.joinLines_joinElseIf,
+ remove_trailing_comma: self.data.joinLines_removeTrailingComma,
+ unwrap_trivial_blocks: self.data.joinLines_unwrapTrivialBlock,
+ join_assignments: self.data.joinLines_joinAssignments,
+ }
+ }
+
+ pub fn call_info(&self) -> CallInfoConfig {
+ CallInfoConfig {
+ params_only: matches!(self.data.signatureInfo_detail, SignatureDetail::Parameters),
+ docs: self.data.signatureInfo_documentation_enable,
+ }
+ }
+
+ pub fn lens(&self) -> LensConfig {
+ LensConfig {
+ run: self.data.lens_enable && self.data.lens_run_enable,
+ debug: self.data.lens_enable && self.data.lens_debug_enable,
+ implementations: self.data.lens_enable && self.data.lens_implementations_enable,
+ method_refs: self.data.lens_enable && self.data.lens_references_method_enable,
+ refs_adt: self.data.lens_enable && self.data.lens_references_adt_enable,
+ refs_trait: self.data.lens_enable && self.data.lens_references_trait_enable,
+ enum_variant_refs: self.data.lens_enable
+ && self.data.lens_references_enumVariant_enable,
+ location: self.data.lens_location,
+ }
+ }
+
+ pub fn hover_actions(&self) -> HoverActionsConfig {
+ let enable = self.experimental("hoverActions") && self.data.hover_actions_enable;
+ HoverActionsConfig {
+ implementations: enable && self.data.hover_actions_implementations_enable,
+ references: enable && self.data.hover_actions_references_enable,
+ run: enable && self.data.hover_actions_run_enable,
+ debug: enable && self.data.hover_actions_debug_enable,
+ goto_type_def: enable && self.data.hover_actions_gotoTypeDef_enable,
+ }
+ }
+
+ pub fn highlighting_config(&self) -> HighlightConfig {
+ HighlightConfig {
+ strings: self.data.semanticHighlighting_strings_enable,
+ punctuation: self.data.semanticHighlighting_punctuation_enable,
+ specialize_punctuation: self
+ .data
+ .semanticHighlighting_punctuation_specialization_enable,
+ macro_bang: self.data.semanticHighlighting_punctuation_separate_macro_bang,
+ operator: self.data.semanticHighlighting_operator_enable,
+ specialize_operator: self.data.semanticHighlighting_operator_specialization_enable,
+ inject_doc_comment: self.data.semanticHighlighting_doc_comment_inject_enable,
+ syntactic_name_ref_highlighting: false,
+ }
+ }
+
+ pub fn hover(&self) -> HoverConfig {
+ HoverConfig {
+ links_in_hover: self.data.hover_links_enable,
+ documentation: self.data.hover_documentation_enable.then(|| {
+ let is_markdown = try_or_def!(self
+ .caps
+ .text_document
+ .as_ref()?
+ .hover
+ .as_ref()?
+ .content_format
+ .as_ref()?
+ .as_slice())
+ .contains(&MarkupKind::Markdown);
+ if is_markdown {
+ HoverDocFormat::Markdown
+ } else {
+ HoverDocFormat::PlainText
+ }
+ }),
+ keywords: self.data.hover_documentation_keywords_enable,
+ }
+ }
+
+ pub fn workspace_symbol(&self) -> WorkspaceSymbolConfig {
+ WorkspaceSymbolConfig {
+ search_scope: match self.data.workspace_symbol_search_scope {
+ WorkspaceSymbolSearchScopeDef::Workspace => WorkspaceSymbolSearchScope::Workspace,
+ WorkspaceSymbolSearchScopeDef::WorkspaceAndDependencies => {
+ WorkspaceSymbolSearchScope::WorkspaceAndDependencies
+ }
+ },
+ search_kind: match self.data.workspace_symbol_search_kind {
+ WorkspaceSymbolSearchKindDef::OnlyTypes => WorkspaceSymbolSearchKind::OnlyTypes,
+ WorkspaceSymbolSearchKindDef::AllSymbols => WorkspaceSymbolSearchKind::AllSymbols,
+ },
+ search_limit: self.data.workspace_symbol_search_limit,
+ }
+ }
+
+ pub fn semantic_tokens_refresh(&self) -> bool {
+ try_or_def!(self.caps.workspace.as_ref()?.semantic_tokens.as_ref()?.refresh_support?)
+ }
+
+ pub fn code_lens_refresh(&self) -> bool {
+ try_or_def!(self.caps.workspace.as_ref()?.code_lens.as_ref()?.refresh_support?)
+ }
+
+ pub fn insert_replace_support(&self) -> bool {
+ try_or_def!(
+ self.caps
+ .text_document
+ .as_ref()?
+ .completion
+ .as_ref()?
+ .completion_item
+ .as_ref()?
+ .insert_replace_support?
+ )
+ }
+
+ pub fn client_commands(&self) -> ClientCommandsConfig {
+ let commands =
+ try_or!(self.caps.experimental.as_ref()?.get("commands")?, &serde_json::Value::Null);
+ let commands: Option<lsp_ext::ClientCommandOptions> =
+ serde_json::from_value(commands.clone()).ok();
+ let force = commands.is_none() && self.data.lens_forceCustomCommands;
+ let commands = commands.map(|it| it.commands).unwrap_or_default();
+
+ let get = |name: &str| commands.iter().any(|it| it == name) || force;
+
+ ClientCommandsConfig {
+ run_single: get("rust-analyzer.runSingle"),
+ debug_single: get("rust-analyzer.debugSingle"),
+ show_reference: get("rust-analyzer.showReferences"),
+ goto_location: get("rust-analyzer.gotoLocation"),
+ trigger_parameter_hints: get("editor.action.triggerParameterHints"),
+ }
+ }
+
+ pub fn highlight_related(&self) -> HighlightRelatedConfig {
+ HighlightRelatedConfig {
+ references: self.data.highlightRelated_references_enable,
+ break_points: self.data.highlightRelated_breakPoints_enable,
+ exit_points: self.data.highlightRelated_exitPoints_enable,
+ yield_points: self.data.highlightRelated_yieldPoints_enable,
+ }
+ }
+
+ pub fn prime_caches_num_threads(&self) -> u8 {
+ match self.data.cachePriming_numThreads {
+ 0 => num_cpus::get_physical().try_into().unwrap_or(u8::MAX),
+ n => n,
+ }
+ }
+
+ pub fn typing_autoclose_angle(&self) -> bool {
+ self.data.typing_autoClosingAngleBrackets_enable
+ }
+}
+// Deserialization definitions
+
+macro_rules! create_bool_or_string_de {
+ ($ident:ident<$bool:literal, $string:literal>) => {
+ fn $ident<'de, D>(d: D) -> Result<(), D::Error>
+ where
+ D: serde::Deserializer<'de>,
+ {
+ struct V;
+ impl<'de> serde::de::Visitor<'de> for V {
+ type Value = ();
+
+ fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str(concat!(
+ stringify!($bool),
+ " or \"",
+ stringify!($string),
+ "\""
+ ))
+ }
+
+ fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
+ where
+ E: serde::de::Error,
+ {
+ match v {
+ $bool => Ok(()),
+ _ => Err(serde::de::Error::invalid_value(
+ serde::de::Unexpected::Bool(v),
+ &self,
+ )),
+ }
+ }
+
+ fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
+ where
+ E: serde::de::Error,
+ {
+ match v {
+ $string => Ok(()),
+ _ => Err(serde::de::Error::invalid_value(
+ serde::de::Unexpected::Str(v),
+ &self,
+ )),
+ }
+ }
+
+ fn visit_enum<A>(self, a: A) -> Result<Self::Value, A::Error>
+ where
+ A: serde::de::EnumAccess<'de>,
+ {
+ use serde::de::VariantAccess;
+ let (variant, va) = a.variant::<&'de str>()?;
+ va.unit_variant()?;
+ match variant {
+ $string => Ok(()),
+ _ => Err(serde::de::Error::invalid_value(
+ serde::de::Unexpected::Str(variant),
+ &self,
+ )),
+ }
+ }
+ }
+ d.deserialize_any(V)
+ }
+ };
+}
+create_bool_or_string_de!(true_or_always<true, "always">);
+create_bool_or_string_de!(false_or_never<false, "never">);
+
+macro_rules! named_unit_variant {
+ ($variant:ident) => {
+ pub(super) fn $variant<'de, D>(deserializer: D) -> Result<(), D::Error>
+ where
+ D: serde::Deserializer<'de>,
+ {
+ struct V;
+ impl<'de> serde::de::Visitor<'de> for V {
+ type Value = ();
+ fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.write_str(concat!("\"", stringify!($variant), "\""))
+ }
+ fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
+ if value == stringify!($variant) {
+ Ok(())
+ } else {
+ Err(E::invalid_value(serde::de::Unexpected::Str(value), &self))
+ }
+ }
+ }
+ deserializer.deserialize_str(V)
+ }
+ };
+}
+
+mod de_unit_v {
+ named_unit_variant!(all);
+ named_unit_variant!(skip_trivial);
+ named_unit_variant!(mutable);
+ named_unit_variant!(with_block);
+}
+
+#[derive(Deserialize, Debug, Clone, Copy)]
+#[serde(rename_all = "snake_case")]
+enum SnippetScopeDef {
+ Expr,
+ Item,
+ Type,
+}
+
+impl Default for SnippetScopeDef {
+ fn default() -> Self {
+ SnippetScopeDef::Expr
+ }
+}
+
+#[derive(Deserialize, Debug, Clone, Default)]
+#[serde(default)]
+struct SnippetDef {
+ #[serde(deserialize_with = "single_or_array")]
+ prefix: Vec<String>,
+ #[serde(deserialize_with = "single_or_array")]
+ postfix: Vec<String>,
+ description: Option<String>,
+ #[serde(deserialize_with = "single_or_array")]
+ body: Vec<String>,
+ #[serde(deserialize_with = "single_or_array")]
+ requires: Vec<String>,
+ scope: SnippetScopeDef,
+}
+
+fn single_or_array<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
+where
+ D: serde::Deserializer<'de>,
+{
+ struct SingleOrVec;
+
+ impl<'de> serde::de::Visitor<'de> for SingleOrVec {
+ type Value = Vec<String>;
+
+ fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ formatter.write_str("string or array of strings")
+ }
+
+ fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
+ where
+ E: serde::de::Error,
+ {
+ Ok(vec![value.to_owned()])
+ }
+
+ fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
+ where
+ A: serde::de::SeqAccess<'de>,
+ {
+ Deserialize::deserialize(serde::de::value::SeqAccessDeserializer::new(seq))
+ }
+ }
+
+ deserializer.deserialize_any(SingleOrVec)
+}
+
+#[derive(Deserialize, Debug, Clone)]
+#[serde(untagged)]
+enum ManifestOrProjectJson {
+ Manifest(PathBuf),
+ ProjectJson(ProjectJsonData),
+}
+
+#[derive(Deserialize, Debug, Clone)]
+#[serde(rename_all = "snake_case")]
+enum ExprFillDefaultDef {
+ Todo,
+ Default,
+}
+
+#[derive(Deserialize, Debug, Clone)]
+#[serde(rename_all = "snake_case")]
+enum ImportGranularityDef {
+ Preserve,
+ Item,
+ Crate,
+ Module,
+}
+
+#[derive(Deserialize, Debug, Copy, Clone)]
+#[serde(rename_all = "snake_case")]
+enum CallableCompletionDef {
+ FillArguments,
+ AddParentheses,
+ None,
+}
+
+#[derive(Deserialize, Debug, Clone)]
+#[serde(untagged)]
- Listed(Vec<String>),
++enum CargoFeaturesDef {
+ #[serde(deserialize_with = "de_unit_v::all")]
+ All,
- "CargoFeatures" => set! {
++ Selected(Vec<String>),
+}
+
+#[derive(Deserialize, Debug, Clone)]
+#[serde(untagged)]
+enum LifetimeElisionDef {
+ #[serde(deserialize_with = "true_or_always")]
+ Always,
+ #[serde(deserialize_with = "false_or_never")]
+ Never,
+ #[serde(deserialize_with = "de_unit_v::skip_trivial")]
+ SkipTrivial,
+}
+
+#[derive(Deserialize, Debug, Clone)]
+#[serde(untagged)]
+enum ClosureReturnTypeHintsDef {
+ #[serde(deserialize_with = "true_or_always")]
+ Always,
+ #[serde(deserialize_with = "false_or_never")]
+ Never,
+ #[serde(deserialize_with = "de_unit_v::with_block")]
+ WithBlock,
+}
+
+#[derive(Deserialize, Debug, Clone)]
+#[serde(untagged)]
+enum ReborrowHintsDef {
+ #[serde(deserialize_with = "true_or_always")]
+ Always,
+ #[serde(deserialize_with = "false_or_never")]
+ Never,
+ #[serde(deserialize_with = "de_unit_v::mutable")]
+ Mutable,
+}
+
+#[derive(Deserialize, Debug, Clone)]
+#[serde(rename_all = "snake_case")]
+enum FilesWatcherDef {
+ Client,
+ Notify,
+ Server,
+}
+
+#[derive(Deserialize, Debug, Clone)]
+#[serde(rename_all = "snake_case")]
+enum ImportPrefixDef {
+ Plain,
+ #[serde(alias = "self")]
+ BySelf,
+ #[serde(alias = "crate")]
+ ByCrate,
+}
+
+#[derive(Deserialize, Debug, Clone)]
+#[serde(rename_all = "snake_case")]
+enum WorkspaceSymbolSearchScopeDef {
+ Workspace,
+ WorkspaceAndDependencies,
+}
+
+#[derive(Deserialize, Debug, Clone)]
+#[serde(rename_all = "snake_case")]
+enum SignatureDetail {
+ Full,
+ Parameters,
+}
+
+#[derive(Deserialize, Debug, Clone)]
+#[serde(rename_all = "snake_case")]
+enum WorkspaceSymbolSearchKindDef {
+ OnlyTypes,
+ AllSymbols,
+}
+
+macro_rules! _config_data {
+ (struct $name:ident {
+ $(
+ $(#[doc=$doc:literal])*
+ $field:ident $(| $alias:ident)*: $ty:ty = $default:expr,
+ )*
+ }) => {
+ #[allow(non_snake_case)]
+ #[derive(Debug, Clone)]
+ struct $name { $($field: $ty,)* }
+ impl $name {
+ fn from_json(mut json: serde_json::Value, error_sink: &mut Vec<(String, serde_json::Error)>) -> $name {
+ $name {$(
+ $field: get_field(
+ &mut json,
+ error_sink,
+ stringify!($field),
+ None$(.or(Some(stringify!($alias))))*,
+ $default,
+ ),
+ )*}
+ }
+
+ fn json_schema() -> serde_json::Value {
+ schema(&[
+ $({
+ let field = stringify!($field);
+ let ty = stringify!($ty);
+
+ (field, ty, &[$($doc),*], $default)
+ },)*
+ ])
+ }
+
+ #[cfg(test)]
+ fn manual() -> String {
+ manual(&[
+ $({
+ let field = stringify!($field);
+ let ty = stringify!($ty);
+
+ (field, ty, &[$($doc),*], $default)
+ },)*
+ ])
+ }
+ }
+
+ #[test]
+ fn fields_are_sorted() {
+ [$(stringify!($field)),*].windows(2).for_each(|w| assert!(w[0] <= w[1], "{} <= {} does not hold", w[0], w[1]));
+ }
+ };
+}
+use _config_data as config_data;
+
+fn get_field<T: DeserializeOwned>(
+ json: &mut serde_json::Value,
+ error_sink: &mut Vec<(String, serde_json::Error)>,
+ field: &'static str,
+ alias: Option<&'static str>,
+ default: &str,
+) -> T {
+ let default = serde_json::from_str(default).unwrap();
+ // XXX: check alias first, to work-around the VS Code where it pre-fills the
+ // defaults instead of sending an empty object.
+ alias
+ .into_iter()
+ .chain(iter::once(field))
+ .find_map(move |field| {
+ let mut pointer = field.replace('_', "/");
+ pointer.insert(0, '/');
+ json.pointer_mut(&pointer).and_then(|it| match serde_json::from_value(it.take()) {
+ Ok(it) => Some(it),
+ Err(e) => {
+ tracing::warn!("Failed to deserialize config field at {}: {:?}", pointer, e);
+ error_sink.push((pointer, e));
+ None
+ }
+ })
+ })
+ .unwrap_or(default)
+}
+
+fn schema(fields: &[(&'static str, &'static str, &[&str], &str)]) -> serde_json::Value {
+ for ((f1, ..), (f2, ..)) in fields.iter().zip(&fields[1..]) {
+ fn key(f: &str) -> &str {
+ f.splitn(2, '_').next().unwrap()
+ }
+ assert!(key(f1) <= key(f2), "wrong field order: {:?} {:?}", f1, f2);
+ }
+
+ let map = fields
+ .iter()
+ .map(|(field, ty, doc, default)| {
+ let name = field.replace('_', ".");
+ let name = format!("rust-analyzer.{}", name);
+ let props = field_props(field, ty, doc, default);
+ (name, props)
+ })
+ .collect::<serde_json::Map<_, _>>();
+ map.into()
+}
+
+fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json::Value {
+ let doc = doc_comment_to_string(doc);
+ let doc = doc.trim_end_matches('\n');
+ assert!(
+ doc.ends_with('.') && doc.starts_with(char::is_uppercase),
+ "bad docs for {}: {:?}",
+ field,
+ doc
+ );
+ let default = default.parse::<serde_json::Value>().unwrap();
+
+ let mut map = serde_json::Map::default();
+ macro_rules! set {
+ ($($key:literal: $value:tt),*$(,)?) => {{$(
+ map.insert($key.into(), serde_json::json!($value));
+ )*}};
+ }
+ set!("markdownDescription": doc);
+ set!("default": default);
+
+ match ty {
+ "bool" => set!("type": "boolean"),
+ "usize" => set!("type": "integer", "minimum": 0),
+ "String" => set!("type": "string"),
+ "Vec<String>" => set! {
+ "type": "array",
+ "items": { "type": "string" },
+ },
+ "Vec<PathBuf>" => set! {
+ "type": "array",
+ "items": { "type": "string" },
+ },
+ "FxHashSet<String>" => set! {
+ "type": "array",
+ "items": { "type": "string" },
+ "uniqueItems": true,
+ },
+ "FxHashMap<Box<str>, Box<[Box<str>]>>" => set! {
+ "type": "object",
+ },
+ "FxHashMap<String, SnippetDef>" => set! {
+ "type": "object",
+ },
+ "FxHashMap<String, String>" => set! {
+ "type": "object",
+ },
+ "Option<usize>" => set! {
+ "type": ["null", "integer"],
+ "minimum": 0,
+ },
+ "Option<String>" => set! {
+ "type": ["null", "string"],
+ },
+ "Option<PathBuf>" => set! {
+ "type": ["null", "string"],
+ },
+ "Option<bool>" => set! {
+ "type": ["null", "boolean"],
+ },
+ "Option<Vec<String>>" => set! {
+ "type": ["null", "array"],
+ "items": { "type": "string" },
+ },
+ "MergeBehaviorDef" => set! {
+ "type": "string",
+ "enum": ["none", "crate", "module"],
+ "enumDescriptions": [
+ "Do not merge imports at all.",
+ "Merge imports from the same crate into a single `use` statement.",
+ "Merge imports from the same module into a single `use` statement."
+ ],
+ },
+ "ExprFillDefaultDef" => set! {
+ "type": "string",
+ "enum": ["todo", "default"],
+ "enumDescriptions": [
+ "Fill missing expressions with the `todo` macro",
+ "Fill missing expressions with reasonable defaults, `new` or `default` constructors."
+ ],
+ },
+ "ImportGranularityDef" => set! {
+ "type": "string",
+ "enum": ["preserve", "crate", "module", "item"],
+ "enumDescriptions": [
+ "Do not change the granularity of any imports and preserve the original structure written by the developer.",
+ "Merge imports from the same crate into a single use statement. Conversely, imports from different crates are split into separate statements.",
+ "Merge imports from the same module into a single use statement. Conversely, imports from different modules are split into separate statements.",
+ "Flatten imports so that each has its own use statement."
+ ],
+ },
+ "ImportPrefixDef" => set! {
+ "type": "string",
+ "enum": [
+ "plain",
+ "self",
+ "crate"
+ ],
+ "enumDescriptions": [
+ "Insert import paths relative to the current module, using up to one `super` prefix if the parent module contains the requested item.",
+ "Insert import paths relative to the current module, using up to one `super` prefix if the parent module contains the requested item. Prefixes `self` in front of the path if it starts with a module.",
+ "Force import paths to be absolute by always starting them with `crate` or the extern crate name they come from."
+ ],
+ },
+ "Vec<ManifestOrProjectJson>" => set! {
+ "type": "array",
+ "items": { "type": ["string", "object"] },
+ },
+ "WorkspaceSymbolSearchScopeDef" => set! {
+ "type": "string",
+ "enum": ["workspace", "workspace_and_dependencies"],
+ "enumDescriptions": [
+ "Search in current workspace only.",
+ "Search in current workspace and dependencies."
+ ],
+ },
+ "WorkspaceSymbolSearchKindDef" => set! {
+ "type": "string",
+ "enum": ["only_types", "all_symbols"],
+ "enumDescriptions": [
+ "Search for types only.",
+ "Search for all symbols kinds."
+ ],
+ },
+ "ParallelCachePrimingNumThreads" => set! {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 255
+ },
+ "LifetimeElisionDef" => set! {
+ "type": "string",
+ "enum": [
+ "always",
+ "never",
+ "skip_trivial"
+ ],
+ "enumDescriptions": [
+ "Always show lifetime elision hints.",
+ "Never show lifetime elision hints.",
+ "Only show lifetime elision hints if a return type is involved."
+ ]
+ },
+ "ClosureReturnTypeHintsDef" => set! {
+ "type": "string",
+ "enum": [
+ "always",
+ "never",
+ "with_block"
+ ],
+ "enumDescriptions": [
+ "Always show type hints for return types of closures.",
+ "Never show type hints for return types of closures.",
+ "Only show type hints for return types of closures with blocks."
+ ]
+ },
+ "ReborrowHintsDef" => set! {
+ "type": "string",
+ "enum": [
+ "always",
+ "never",
+ "mutable"
+ ],
+ "enumDescriptions": [
+ "Always show reborrow hints.",
+ "Never show reborrow hints.",
+ "Only show mutable reborrow hints."
+ ]
+ },
- "Option<CargoFeatures>" => set! {
++ "CargoFeaturesDef" => set! {
+ "anyOf": [
+ {
+ "type": "string",
+ "enum": [
+ "all"
+ ],
+ "enumDescriptions": [
+ "Pass `--all-features` to cargo",
+ ]
+ },
+ {
+ "type": "array",
+ "items": { "type": "string" }
+ }
+ ],
+ },
++ "Option<CargoFeaturesDef>" => set! {
+ "anyOf": [
+ {
+ "type": "string",
+ "enum": [
+ "all"
+ ],
+ "enumDescriptions": [
+ "Pass `--all-features` to cargo",
+ ]
+ },
+ {
+ "type": "array",
+ "items": { "type": "string" }
+ },
+ { "type": "null" }
+ ],
+ },
+ "CallableCompletionDef" => set! {
+ "type": "string",
+ "enum": [
+ "fill_arguments",
+ "add_parentheses",
+ "none",
+ ],
+ "enumDescriptions": [
+ "Add call parentheses and pre-fill arguments.",
+ "Add call parentheses.",
+ "Do no snippet completions for callables."
+ ]
+ },
+ "SignatureDetail" => set! {
+ "type": "string",
+ "enum": ["full", "parameters"],
+ "enumDescriptions": [
+ "Show the entire signature.",
+ "Show only the parameters."
+ ],
+ },
+ "FilesWatcherDef" => set! {
+ "type": "string",
+ "enum": ["client", "server"],
+ "enumDescriptions": [
+ "Use the client (editor) to watch files for changes",
+ "Use server-side file watching",
+ ],
+ },
+ "AnnotationLocation" => set! {
+ "type": "string",
+ "enum": ["above_name", "above_whole_item"],
+ "enumDescriptions": [
+ "Render annotations above the name of the item.",
+ "Render annotations above the whole item, including documentation comments and attributes."
+ ],
+ },
+ _ => panic!("missing entry for {}: {}", ty, default),
+ }
+
+ map.into()
+}
+
+#[cfg(test)]
+fn manual(fields: &[(&'static str, &'static str, &[&str], &str)]) -> String {
+ fields
+ .iter()
+ .map(|(field, _ty, doc, default)| {
+ let name = format!("rust-analyzer.{}", field.replace('_', "."));
+ let doc = doc_comment_to_string(*doc);
+ if default.contains('\n') {
+ format!(
+ r#"[[{}]]{}::
++
+--
+Default:
+----
+{}
+----
+{}
+--
+"#,
+ name, name, default, doc
+ )
+ } else {
+ format!("[[{}]]{} (default: `{}`)::\n+\n--\n{}--\n", name, name, default, doc)
+ }
+ })
+ .collect::<String>()
+}
+
+fn doc_comment_to_string(doc: &[&str]) -> String {
+ doc.iter().map(|it| it.strip_prefix(' ').unwrap_or(it)).map(|it| format!("{}\n", it)).collect()
+}
+
+#[cfg(test)]
+mod tests {
+ use std::fs;
+
+ use test_utils::{ensure_file_contents, project_root};
+
+ use super::*;
+
+ #[test]
+ fn generate_package_json_config() {
+ let s = Config::json_schema();
+ let schema = format!("{:#}", s);
+ let mut schema = schema
+ .trim_start_matches('{')
+ .trim_end_matches('}')
+ .replace(" ", " ")
+ .replace('\n', "\n ")
+ .trim_start_matches('\n')
+ .trim_end()
+ .to_string();
+ schema.push_str(",\n");
+
+ // Transform the asciidoc form link to markdown style.
+ //
+ // https://link[text] => [text](https://link)
+ let url_matches = schema.match_indices("https://");
+ let mut url_offsets = url_matches.map(|(idx, _)| idx).collect::<Vec<usize>>();
+ url_offsets.reverse();
+ for idx in url_offsets {
+ let link = &schema[idx..];
+ // matching on whitespace to ignore normal links
+ if let Some(link_end) = link.find(|c| c == ' ' || c == '[') {
+ if link.chars().nth(link_end) == Some('[') {
+ if let Some(link_text_end) = link.find(']') {
+ let link_text = link[link_end..(link_text_end + 1)].to_string();
+
+ schema.replace_range((idx + link_end)..(idx + link_text_end + 1), "");
+ schema.insert(idx, '(');
+ schema.insert(idx + link_end + 1, ')');
+ schema.insert_str(idx, &link_text);
+ }
+ }
+ }
+ }
+
+ let package_json_path = project_root().join("editors/code/package.json");
+ let mut package_json = fs::read_to_string(&package_json_path).unwrap();
+
+ let start_marker = " \"$generated-start\": {},\n";
+ let end_marker = " \"$generated-end\": {}\n";
+
+ let start = package_json.find(start_marker).unwrap() + start_marker.len();
+ let end = package_json.find(end_marker).unwrap();
+
+ let p = remove_ws(&package_json[start..end]);
+ let s = remove_ws(&schema);
+ if !p.contains(&s) {
+ package_json.replace_range(start..end, &schema);
+ ensure_file_contents(&package_json_path, &package_json)
+ }
+ }
+
+ #[test]
+ fn generate_config_documentation() {
+ let docs_path = project_root().join("docs/user/generated_config.adoc");
+ let expected = ConfigData::manual();
+ ensure_file_contents(&docs_path, &expected);
+ }
+
+ fn remove_ws(text: &str) -> String {
+ text.replace(char::is_whitespace, "")
+ }
+}
--- /dev/null
- let file_id =
- snap.url_to_file_id(¶ms.text_document_position_params.text_document.uri)?;
+//! Conversion lsp_types types to rust-analyzer specific ones.
+use anyhow::format_err;
+use ide::{Annotation, AnnotationKind, AssistKind, LineCol, LineColUtf16};
+use ide_db::base_db::{FileId, FilePosition, FileRange};
+use syntax::{TextRange, TextSize};
+use vfs::AbsPathBuf;
+
+use crate::{
+ from_json,
+ global_state::GlobalStateSnapshot,
+ line_index::{LineIndex, OffsetEncoding},
+ lsp_ext,
+ lsp_utils::invalid_params_error,
+ Result,
+};
+
+pub(crate) fn abs_path(url: &lsp_types::Url) -> Result<AbsPathBuf> {
+ let path = url.to_file_path().map_err(|()| "url is not a file")?;
+ Ok(AbsPathBuf::try_from(path).unwrap())
+}
+
+pub(crate) fn vfs_path(url: &lsp_types::Url) -> Result<vfs::VfsPath> {
+ abs_path(url).map(vfs::VfsPath::from)
+}
+
+pub(crate) fn offset(line_index: &LineIndex, position: lsp_types::Position) -> Result<TextSize> {
+ let line_col = match line_index.encoding {
+ OffsetEncoding::Utf8 => {
+ LineCol { line: position.line as u32, col: position.character as u32 }
+ }
+ OffsetEncoding::Utf16 => {
+ let line_col =
+ LineColUtf16 { line: position.line as u32, col: position.character as u32 };
+ line_index.index.to_utf8(line_col)
+ }
+ };
+ let text_size =
+ line_index.index.offset(line_col).ok_or_else(|| format_err!("Invalid offset"))?;
+ Ok(text_size)
+}
+
+pub(crate) fn text_range(line_index: &LineIndex, range: lsp_types::Range) -> Result<TextRange> {
+ let start = offset(line_index, range.start)?;
+ let end = offset(line_index, range.end)?;
+ let text_range = TextRange::new(start, end);
+ Ok(text_range)
+}
+
+pub(crate) fn file_id(snap: &GlobalStateSnapshot, url: &lsp_types::Url) -> Result<FileId> {
+ snap.url_to_file_id(url)
+}
+
+pub(crate) fn file_position(
+ snap: &GlobalStateSnapshot,
+ tdpp: lsp_types::TextDocumentPositionParams,
+) -> Result<FilePosition> {
+ let file_id = file_id(snap, &tdpp.text_document.uri)?;
+ let line_index = snap.file_line_index(file_id)?;
+ let offset = offset(&line_index, tdpp.position)?;
+ Ok(FilePosition { file_id, offset })
+}
+
+pub(crate) fn file_range(
+ snap: &GlobalStateSnapshot,
+ text_document_identifier: lsp_types::TextDocumentIdentifier,
+ range: lsp_types::Range,
+) -> Result<FileRange> {
+ let file_id = file_id(snap, &text_document_identifier.uri)?;
+ let line_index = snap.file_line_index(file_id)?;
+ let range = text_range(&line_index, range)?;
+ Ok(FileRange { file_id, range })
+}
+
+pub(crate) fn assist_kind(kind: lsp_types::CodeActionKind) -> Option<AssistKind> {
+ let assist_kind = match &kind {
+ k if k == &lsp_types::CodeActionKind::EMPTY => AssistKind::None,
+ k if k == &lsp_types::CodeActionKind::QUICKFIX => AssistKind::QuickFix,
+ k if k == &lsp_types::CodeActionKind::REFACTOR => AssistKind::Refactor,
+ k if k == &lsp_types::CodeActionKind::REFACTOR_EXTRACT => AssistKind::RefactorExtract,
+ k if k == &lsp_types::CodeActionKind::REFACTOR_INLINE => AssistKind::RefactorInline,
+ k if k == &lsp_types::CodeActionKind::REFACTOR_REWRITE => AssistKind::RefactorRewrite,
+ _ => return None,
+ };
+
+ Some(assist_kind)
+}
+
+pub(crate) fn annotation(
+ snap: &GlobalStateSnapshot,
+ code_lens: lsp_types::CodeLens,
+) -> Result<Annotation> {
+ let data =
+ code_lens.data.ok_or_else(|| invalid_params_error("code lens without data".to_string()))?;
+ let resolve = from_json::<lsp_ext::CodeLensResolveData>("CodeLensResolveData", &data)?;
+
+ match resolve {
+ lsp_ext::CodeLensResolveData::Impls(params) => {
- kind: AnnotationKind::HasImpls { file_id, data: None },
++ let pos @ FilePosition { file_id, .. } =
++ file_position(snap, params.text_document_position_params)?;
+ let line_index = snap.file_line_index(file_id)?;
+
+ Ok(Annotation {
+ range: text_range(&line_index, code_lens.range)?,
- let file_id = snap.url_to_file_id(¶ms.text_document.uri)?;
++ kind: AnnotationKind::HasImpls { pos, data: None },
+ })
+ }
+ lsp_ext::CodeLensResolveData::References(params) => {
- kind: AnnotationKind::HasReferences { file_id, data: None },
++ let pos @ FilePosition { file_id, .. } = file_position(snap, params)?;
+ let line_index = snap.file_line_index(file_id)?;
+
+ Ok(Annotation {
+ range: text_range(&line_index, code_lens.range)?,
++ kind: AnnotationKind::HasReferences { pos, data: None },
+ })
+ }
+ }
+}
--- /dev/null
- let changed_files = vfs.take_changes();
+//! The context or environment in which the language server functions. In our
+//! server implementation this is know as the `WorldState`.
+//!
+//! Each tick provides an immutable snapshot of the state as `WorldSnapshot`.
+
+use std::{sync::Arc, time::Instant};
+
+use crossbeam_channel::{unbounded, Receiver, Sender};
+use flycheck::FlycheckHandle;
+use ide::{Analysis, AnalysisHost, Cancellable, Change, FileId};
+use ide_db::base_db::{CrateId, FileLoader, SourceDatabase};
+use lsp_types::{SemanticTokens, Url};
+use parking_lot::{Mutex, RwLock};
+use proc_macro_api::ProcMacroServer;
+use project_model::{CargoWorkspace, ProjectWorkspace, Target, WorkspaceBuildScripts};
+use rustc_hash::FxHashMap;
+use stdx::hash::NoHashHashMap;
+use vfs::AnchoredPathBuf;
+
+use crate::{
+ config::Config,
+ diagnostics::{CheckFixes, DiagnosticCollection},
+ from_proto,
+ line_index::{LineEndings, LineIndex},
+ lsp_ext,
+ main_loop::Task,
+ mem_docs::MemDocs,
+ op_queue::OpQueue,
+ reload::{self, SourceRootConfig},
+ task_pool::TaskPool,
+ to_proto::url_from_abs_path,
+ Result,
+};
+
+// Enforces drop order
+pub(crate) struct Handle<H, C> {
+ pub(crate) handle: H,
+ pub(crate) receiver: C,
+}
+
+pub(crate) type ReqHandler = fn(&mut GlobalState, lsp_server::Response);
+pub(crate) type ReqQueue = lsp_server::ReqQueue<(String, Instant), ReqHandler>;
+
+/// `GlobalState` is the primary mutable state of the language server
+///
+/// The most interesting components are `vfs`, which stores a consistent
+/// snapshot of the file systems, and `analysis_host`, which stores our
+/// incremental salsa database.
+///
+/// Note that this struct has more than one impl in various modules!
+pub(crate) struct GlobalState {
+ sender: Sender<lsp_server::Message>,
+ req_queue: ReqQueue,
+ pub(crate) task_pool: Handle<TaskPool<Task>, Receiver<Task>>,
+ pub(crate) loader: Handle<Box<dyn vfs::loader::Handle>, Receiver<vfs::loader::Message>>,
+ pub(crate) config: Arc<Config>,
+ pub(crate) analysis_host: AnalysisHost,
+ pub(crate) diagnostics: DiagnosticCollection,
+ pub(crate) mem_docs: MemDocs,
+ pub(crate) semantic_tokens_cache: Arc<Mutex<FxHashMap<Url, SemanticTokens>>>,
+ pub(crate) shutdown_requested: bool,
+ pub(crate) proc_macro_changed: bool,
+ pub(crate) last_reported_status: Option<lsp_ext::ServerStatusParams>,
+ pub(crate) source_root_config: SourceRootConfig,
+ pub(crate) proc_macro_clients: Vec<Result<ProcMacroServer, String>>,
+
+ pub(crate) flycheck: Vec<FlycheckHandle>,
+ pub(crate) flycheck_sender: Sender<flycheck::Message>,
+ pub(crate) flycheck_receiver: Receiver<flycheck::Message>,
+
+ pub(crate) vfs: Arc<RwLock<(vfs::Vfs, NoHashHashMap<FileId, LineEndings>)>>,
+ pub(crate) vfs_config_version: u32,
+ pub(crate) vfs_progress_config_version: u32,
+ pub(crate) vfs_progress_n_total: usize,
+ pub(crate) vfs_progress_n_done: usize,
+
+ /// `workspaces` field stores the data we actually use, while the `OpQueue`
+ /// stores the result of the last fetch.
+ ///
+ /// If the fetch (partially) fails, we do not update the current value.
+ ///
+ /// The handling of build data is subtle. We fetch workspace in two phases:
+ ///
+ /// *First*, we run `cargo metadata`, which gives us fast results for
+ /// initial analysis.
+ ///
+ /// *Second*, we run `cargo check` which runs build scripts and compiles
+ /// proc macros.
+ ///
+ /// We need both for the precise analysis, but we want rust-analyzer to be
+ /// at least partially available just after the first phase. That's because
+ /// first phase is much faster, and is much less likely to fail.
+ ///
+ /// This creates a complication -- by the time the second phase completes,
+ /// the results of the fist phase could be invalid. That is, while we run
+ /// `cargo check`, the user edits `Cargo.toml`, we notice this, and the new
+ /// `cargo metadata` completes before `cargo check`.
+ ///
+ /// An additional complication is that we want to avoid needless work. When
+ /// the user just adds comments or whitespace to Cargo.toml, we do not want
+ /// to invalidate any salsa caches.
+ pub(crate) workspaces: Arc<Vec<ProjectWorkspace>>,
+ pub(crate) fetch_workspaces_queue: OpQueue<Vec<anyhow::Result<ProjectWorkspace>>>,
+ pub(crate) fetch_build_data_queue:
+ OpQueue<(Arc<Vec<ProjectWorkspace>>, Vec<anyhow::Result<WorkspaceBuildScripts>>)>,
+
+ pub(crate) prime_caches_queue: OpQueue<()>,
+}
+
+/// An immutable snapshot of the world's state at a point in time.
+pub(crate) struct GlobalStateSnapshot {
+ pub(crate) config: Arc<Config>,
+ pub(crate) analysis: Analysis,
+ pub(crate) check_fixes: CheckFixes,
+ mem_docs: MemDocs,
+ pub(crate) semantic_tokens_cache: Arc<Mutex<FxHashMap<Url, SemanticTokens>>>,
+ vfs: Arc<RwLock<(vfs::Vfs, NoHashHashMap<FileId, LineEndings>)>>,
+ pub(crate) workspaces: Arc<Vec<ProjectWorkspace>>,
+ pub(crate) proc_macros_loaded: bool,
+}
+
+impl std::panic::UnwindSafe for GlobalStateSnapshot {}
+
+impl GlobalState {
+ pub(crate) fn new(sender: Sender<lsp_server::Message>, config: Config) -> GlobalState {
+ let loader = {
+ let (sender, receiver) = unbounded::<vfs::loader::Message>();
+ let handle: vfs_notify::NotifyHandle =
+ vfs::loader::Handle::spawn(Box::new(move |msg| sender.send(msg).unwrap()));
+ let handle = Box::new(handle) as Box<dyn vfs::loader::Handle>;
+ Handle { handle, receiver }
+ };
+
+ let task_pool = {
+ let (sender, receiver) = unbounded();
+ let handle = TaskPool::new(sender);
+ Handle { handle, receiver }
+ };
+
+ let analysis_host = AnalysisHost::new(config.lru_capacity());
+ let (flycheck_sender, flycheck_receiver) = unbounded();
+ let mut this = GlobalState {
+ sender,
+ req_queue: ReqQueue::default(),
+ task_pool,
+ loader,
+ config: Arc::new(config.clone()),
+ analysis_host,
+ diagnostics: Default::default(),
+ mem_docs: MemDocs::default(),
+ semantic_tokens_cache: Arc::new(Default::default()),
+ shutdown_requested: false,
+ proc_macro_changed: false,
+ last_reported_status: None,
+ source_root_config: SourceRootConfig::default(),
+ proc_macro_clients: vec![],
+
+ flycheck: Vec::new(),
+ flycheck_sender,
+ flycheck_receiver,
+
+ vfs: Arc::new(RwLock::new((vfs::Vfs::default(), NoHashHashMap::default()))),
+ vfs_config_version: 0,
+ vfs_progress_config_version: 0,
+ vfs_progress_n_total: 0,
+ vfs_progress_n_done: 0,
+
+ workspaces: Arc::new(Vec::new()),
+ fetch_workspaces_queue: OpQueue::default(),
+ prime_caches_queue: OpQueue::default(),
+
+ fetch_build_data_queue: OpQueue::default(),
+ };
+ // Apply any required database inputs from the config.
+ this.update_configuration(config);
+ this
+ }
+
+ pub(crate) fn process_changes(&mut self) -> bool {
+ let _p = profile::span("GlobalState::process_changes");
+ // A file was added or deleted
+ let mut has_structure_changes = false;
+ let mut workspace_structure_change = None;
+
+ let (change, changed_files) = {
+ let mut change = Change::new();
+ let (vfs, line_endings_map) = &mut *self.vfs.write();
++ let mut changed_files = vfs.take_changes();
+ if changed_files.is_empty() {
+ return false;
+ }
+
++ // important: this needs to be a stable sort, the order between changes is relevant
++ // for the same file ids
++ changed_files.sort_by_key(|file| file.file_id);
++ // We need to fix up the changed events a bit, if we have a create or modify for a file
++ // id that is followed by a delete we actually no longer observe the file text from the
++ // create or modify which may cause problems later on
++ changed_files.dedup_by(|a, b| {
++ use vfs::ChangeKind::*;
++
++ if a.file_id != b.file_id {
++ return false;
++ }
++
++ match (a.change_kind, b.change_kind) {
++ // duplicate can be merged
++ (Create, Create) | (Modify, Modify) | (Delete, Delete) => true,
++ // just leave the create, modify is irrelevant
++ (Create, Modify) => {
++ std::mem::swap(a, b);
++ true
++ }
++ // modify becomes irrelevant if the file is deleted
++ (Modify, Delete) => true,
++ // we should fully remove this occurrence,
++ // but leaving just a delete works as well
++ (Create, Delete) => true,
++ // this is equivalent to a modify
++ (Delete, Create) => {
++ a.change_kind = Modify;
++ true
++ }
++ // can't really occur
++ (Modify, Create) => false,
++ (Delete, Modify) => false,
++ }
++ });
++
+ for file in &changed_files {
+ if let Some(path) = vfs.file_path(file.file_id).as_path() {
+ let path = path.to_path_buf();
+ if reload::should_refresh_for_change(&path, file.change_kind) {
+ workspace_structure_change = Some(path);
+ }
+ if file.is_created_or_deleted() {
+ has_structure_changes = true;
+ }
+ }
+
+ // Clear native diagnostics when their file gets deleted
+ if !file.exists() {
+ self.diagnostics.clear_native_for(file.file_id);
+ }
+
+ let text = if file.exists() {
+ let bytes = vfs.file_contents(file.file_id).to_vec();
+ String::from_utf8(bytes).ok().and_then(|text| {
+ let (text, line_endings) = LineEndings::normalize(text);
+ line_endings_map.insert(file.file_id, line_endings);
+ Some(Arc::new(text))
+ })
+ } else {
+ None
+ };
+ change.change_file(file.file_id, text);
+ }
+ if has_structure_changes {
+ let roots = self.source_root_config.partition(vfs);
+ change.set_roots(roots);
+ }
+ (change, changed_files)
+ };
+
+ self.analysis_host.apply_change(change);
+
+ {
+ let raw_database = self.analysis_host.raw_database();
+ // FIXME: ideally we should only trigger a workspace fetch for non-library changes
+ // but somethings going wrong with the source root business when we add a new local
+ // crate see https://github.com/rust-lang/rust-analyzer/issues/13029
+ if let Some(path) = workspace_structure_change {
+ self.fetch_workspaces_queue
+ .request_op(format!("workspace vfs file change: {}", path.display()));
+ }
+ self.proc_macro_changed =
+ changed_files.iter().filter(|file| !file.is_created_or_deleted()).any(|file| {
+ let crates = raw_database.relevant_crates(file.file_id);
+ let crate_graph = raw_database.crate_graph();
+
+ crates.iter().any(|&krate| crate_graph[krate].is_proc_macro)
+ });
+ }
+
+ true
+ }
+
+ pub(crate) fn snapshot(&self) -> GlobalStateSnapshot {
+ GlobalStateSnapshot {
+ config: Arc::clone(&self.config),
+ workspaces: Arc::clone(&self.workspaces),
+ analysis: self.analysis_host.analysis(),
+ vfs: Arc::clone(&self.vfs),
+ check_fixes: Arc::clone(&self.diagnostics.check_fixes),
+ mem_docs: self.mem_docs.clone(),
+ semantic_tokens_cache: Arc::clone(&self.semantic_tokens_cache),
+ proc_macros_loaded: !self.fetch_build_data_queue.last_op_result().0.is_empty(),
+ }
+ }
+
+ pub(crate) fn send_request<R: lsp_types::request::Request>(
+ &mut self,
+ params: R::Params,
+ handler: ReqHandler,
+ ) {
+ let request = self.req_queue.outgoing.register(R::METHOD.to_string(), params, handler);
+ self.send(request.into());
+ }
+
+ pub(crate) fn complete_request(&mut self, response: lsp_server::Response) {
+ let handler = self
+ .req_queue
+ .outgoing
+ .complete(response.id.clone())
+ .expect("received response for unknown request");
+ handler(self, response)
+ }
+
+ pub(crate) fn send_notification<N: lsp_types::notification::Notification>(
+ &mut self,
+ params: N::Params,
+ ) {
+ let not = lsp_server::Notification::new(N::METHOD.to_string(), params);
+ self.send(not.into());
+ }
+
+ pub(crate) fn register_request(
+ &mut self,
+ request: &lsp_server::Request,
+ request_received: Instant,
+ ) {
+ self.req_queue
+ .incoming
+ .register(request.id.clone(), (request.method.clone(), request_received));
+ }
+
+ pub(crate) fn respond(&mut self, response: lsp_server::Response) {
+ if let Some((method, start)) = self.req_queue.incoming.complete(response.id.clone()) {
+ if let Some(err) = &response.error {
+ if err.message.starts_with("server panicked") {
+ self.poke_rust_analyzer_developer(format!("{}, check the log", err.message))
+ }
+ }
+
+ let duration = start.elapsed();
+ tracing::debug!("handled {} - ({}) in {:0.2?}", method, response.id, duration);
+ self.send(response.into());
+ }
+ }
+
+ pub(crate) fn cancel(&mut self, request_id: lsp_server::RequestId) {
+ if let Some(response) = self.req_queue.incoming.cancel(request_id) {
+ self.send(response.into());
+ }
+ }
+
++ pub(crate) fn is_completed(&self, request: &lsp_server::Request) -> bool {
++ self.req_queue.incoming.is_completed(&request.id)
++ }
++
+ fn send(&mut self, message: lsp_server::Message) {
+ self.sender.send(message).unwrap()
+ }
+}
+
+impl Drop for GlobalState {
+ fn drop(&mut self) {
+ self.analysis_host.request_cancellation();
+ }
+}
+
+impl GlobalStateSnapshot {
+ pub(crate) fn url_to_file_id(&self, url: &Url) -> Result<FileId> {
+ url_to_file_id(&self.vfs.read().0, url)
+ }
+
+ pub(crate) fn file_id_to_url(&self, id: FileId) -> Url {
+ file_id_to_url(&self.vfs.read().0, id)
+ }
+
+ pub(crate) fn file_line_index(&self, file_id: FileId) -> Cancellable<LineIndex> {
+ let endings = self.vfs.read().1[&file_id];
+ let index = self.analysis.file_line_index(file_id)?;
+ let res = LineIndex { index, endings, encoding: self.config.offset_encoding() };
+ Ok(res)
+ }
+
+ pub(crate) fn url_file_version(&self, url: &Url) -> Option<i32> {
+ let path = from_proto::vfs_path(url).ok()?;
+ Some(self.mem_docs.get(&path)?.version)
+ }
+
+ pub(crate) fn anchored_path(&self, path: &AnchoredPathBuf) -> Url {
+ let mut base = self.vfs.read().0.file_path(path.anchor);
+ base.pop();
+ let path = base.join(&path.path).unwrap();
+ let path = path.as_path().unwrap();
+ url_from_abs_path(path)
+ }
+
+ pub(crate) fn cargo_target_for_crate_root(
+ &self,
+ crate_id: CrateId,
+ ) -> Option<(&CargoWorkspace, Target)> {
+ let file_id = self.analysis.crate_root(crate_id).ok()?;
+ let path = self.vfs.read().0.file_path(file_id);
+ let path = path.as_path()?;
+ self.workspaces.iter().find_map(|ws| match ws {
+ ProjectWorkspace::Cargo { cargo, .. } => {
+ cargo.target_by_root(path).map(|it| (cargo, it))
+ }
+ ProjectWorkspace::Json { .. } => None,
+ ProjectWorkspace::DetachedFiles { .. } => None,
+ })
+ }
+}
+
+pub(crate) fn file_id_to_url(vfs: &vfs::Vfs, id: FileId) -> Url {
+ let path = vfs.file_path(id);
+ let path = path.as_path().unwrap();
+ url_from_abs_path(path)
+}
+
+pub(crate) fn url_to_file_id(vfs: &vfs::Vfs, url: &Url) -> Result<FileId> {
+ let path = from_proto::vfs_path(url)?;
+ let res = vfs.file_id(&path).ok_or_else(|| format!("file not found: {}", path))?;
+ Ok(res)
+}
--- /dev/null
- Task::Retry(req) => self.on_request(req),
+//! The main loop of `rust-analyzer` responsible for dispatching LSP
+//! requests/replies and notifications back to the client.
+use std::{
+ fmt,
+ ops::Deref,
+ sync::Arc,
+ time::{Duration, Instant},
+};
+
+use always_assert::always;
+use crossbeam_channel::{select, Receiver};
+use flycheck::FlycheckHandle;
+use ide_db::base_db::{SourceDatabase, SourceDatabaseExt, VfsPath};
+use itertools::Itertools;
+use lsp_server::{Connection, Notification, Request};
+use lsp_types::notification::Notification as _;
+use vfs::{ChangeKind, FileId};
+
+use crate::{
+ config::Config,
+ dispatch::{NotificationDispatcher, RequestDispatcher},
+ from_proto,
+ global_state::{file_id_to_url, url_to_file_id, GlobalState},
+ handlers, lsp_ext,
+ lsp_utils::{apply_document_changes, notification_is, Progress},
+ mem_docs::DocumentData,
+ reload::{self, BuildDataProgress, ProjectWorkspaceProgress},
+ Result,
+};
+
+pub fn main_loop(config: Config, connection: Connection) -> Result<()> {
+ tracing::info!("initial config: {:#?}", config);
+
+ // Windows scheduler implements priority boosts: if thread waits for an
+ // event (like a condvar), and event fires, priority of the thread is
+ // temporary bumped. This optimization backfires in our case: each time the
+ // `main_loop` schedules a task to run on a threadpool, the worker threads
+ // gets a higher priority, and (on a machine with fewer cores) displaces the
+ // main loop! We work-around this by marking the main loop as a
+ // higher-priority thread.
+ //
+ // https://docs.microsoft.com/en-us/windows/win32/procthread/scheduling-priorities
+ // https://docs.microsoft.com/en-us/windows/win32/procthread/priority-boosts
+ // https://github.com/rust-lang/rust-analyzer/issues/2835
+ #[cfg(windows)]
+ unsafe {
+ use winapi::um::processthreadsapi::*;
+ let thread = GetCurrentThread();
+ let thread_priority_above_normal = 1;
+ SetThreadPriority(thread, thread_priority_above_normal);
+ }
+
+ GlobalState::new(connection.sender, config).run(connection.receiver)
+}
+
+enum Event {
+ Lsp(lsp_server::Message),
+ Task(Task),
+ Vfs(vfs::loader::Message),
+ Flycheck(flycheck::Message),
+}
+
+#[derive(Debug)]
+pub(crate) enum Task {
+ Response(lsp_server::Response),
+ Retry(lsp_server::Request),
+ Diagnostics(Vec<(FileId, Vec<lsp_types::Diagnostic>)>),
+ PrimeCaches(PrimeCachesProgress),
+ FetchWorkspace(ProjectWorkspaceProgress),
+ FetchBuildData(BuildDataProgress),
+}
+
+#[derive(Debug)]
+pub(crate) enum PrimeCachesProgress {
+ Begin,
+ Report(ide::ParallelPrimeCachesProgress),
+ End { cancelled: bool },
+}
+
+impl fmt::Debug for Event {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let debug_verbose_not = |not: &Notification, f: &mut fmt::Formatter<'_>| {
+ f.debug_struct("Notification").field("method", ¬.method).finish()
+ };
+
+ match self {
+ Event::Lsp(lsp_server::Message::Notification(not)) => {
+ if notification_is::<lsp_types::notification::DidOpenTextDocument>(not)
+ || notification_is::<lsp_types::notification::DidChangeTextDocument>(not)
+ {
+ return debug_verbose_not(not, f);
+ }
+ }
+ Event::Task(Task::Response(resp)) => {
+ return f
+ .debug_struct("Response")
+ .field("id", &resp.id)
+ .field("error", &resp.error)
+ .finish();
+ }
+ _ => (),
+ }
+ match self {
+ Event::Lsp(it) => fmt::Debug::fmt(it, f),
+ Event::Task(it) => fmt::Debug::fmt(it, f),
+ Event::Vfs(it) => fmt::Debug::fmt(it, f),
+ Event::Flycheck(it) => fmt::Debug::fmt(it, f),
+ }
+ }
+}
+
+impl GlobalState {
+ fn run(mut self, inbox: Receiver<lsp_server::Message>) -> Result<()> {
+ if self.config.linked_projects().is_empty()
+ && self.config.detached_files().is_empty()
+ && self.config.notifications().cargo_toml_not_found
+ {
+ self.show_and_log_error("rust-analyzer failed to discover workspace".to_string(), None);
+ };
+
+ if self.config.did_save_text_document_dynamic_registration() {
+ let save_registration_options = lsp_types::TextDocumentSaveRegistrationOptions {
+ include_text: Some(false),
+ text_document_registration_options: lsp_types::TextDocumentRegistrationOptions {
+ document_selector: Some(vec![
+ lsp_types::DocumentFilter {
+ language: None,
+ scheme: None,
+ pattern: Some("**/*.rs".into()),
+ },
+ lsp_types::DocumentFilter {
+ language: None,
+ scheme: None,
+ pattern: Some("**/Cargo.toml".into()),
+ },
+ lsp_types::DocumentFilter {
+ language: None,
+ scheme: None,
+ pattern: Some("**/Cargo.lock".into()),
+ },
+ ]),
+ },
+ };
+
+ let registration = lsp_types::Registration {
+ id: "textDocument/didSave".to_string(),
+ method: "textDocument/didSave".to_string(),
+ register_options: Some(serde_json::to_value(save_registration_options).unwrap()),
+ };
+ self.send_request::<lsp_types::request::RegisterCapability>(
+ lsp_types::RegistrationParams { registrations: vec![registration] },
+ |_, _| (),
+ );
+ }
+
+ self.fetch_workspaces_queue.request_op("startup".to_string());
+ if let Some(cause) = self.fetch_workspaces_queue.should_start_op() {
+ self.fetch_workspaces(cause);
+ }
+
+ while let Some(event) = self.next_event(&inbox) {
+ if let Event::Lsp(lsp_server::Message::Notification(not)) = &event {
+ if not.method == lsp_types::notification::Exit::METHOD {
+ return Ok(());
+ }
+ }
+ self.handle_event(event)?
+ }
+
+ Err("client exited without proper shutdown sequence".into())
+ }
+
+ fn next_event(&self, inbox: &Receiver<lsp_server::Message>) -> Option<Event> {
+ select! {
+ recv(inbox) -> msg =>
+ msg.ok().map(Event::Lsp),
+
+ recv(self.task_pool.receiver) -> task =>
+ Some(Event::Task(task.unwrap())),
+
+ recv(self.loader.receiver) -> task =>
+ Some(Event::Vfs(task.unwrap())),
+
+ recv(self.flycheck_receiver) -> task =>
+ Some(Event::Flycheck(task.unwrap())),
+ }
+ }
+
+ fn handle_event(&mut self, event: Event) -> Result<()> {
+ let loop_start = Instant::now();
+ // NOTE: don't count blocking select! call as a loop-turn time
+ let _p = profile::span("GlobalState::handle_event");
+
+ tracing::debug!("handle_event({:?})", event);
+ let task_queue_len = self.task_pool.handle.len();
+ if task_queue_len > 0 {
+ tracing::info!("task queue len: {}", task_queue_len);
+ }
+
+ let was_quiescent = self.is_quiescent();
+ match event {
+ Event::Lsp(msg) => match msg {
+ lsp_server::Message::Request(req) => self.on_new_request(loop_start, req),
+ lsp_server::Message::Notification(not) => {
+ self.on_notification(not)?;
+ }
+ lsp_server::Message::Response(resp) => self.complete_request(resp),
+ },
+ Event::Task(task) => {
+ let _p = profile::span("GlobalState::handle_event/task");
+ let mut prime_caches_progress = Vec::new();
+
+ self.handle_task(&mut prime_caches_progress, task);
+ // Coalesce multiple task events into one loop turn
+ while let Ok(task) = self.task_pool.receiver.try_recv() {
+ self.handle_task(&mut prime_caches_progress, task);
+ }
+
+ for progress in prime_caches_progress {
+ let (state, message, fraction);
+ match progress {
+ PrimeCachesProgress::Begin => {
+ state = Progress::Begin;
+ message = None;
+ fraction = 0.0;
+ }
+ PrimeCachesProgress::Report(report) => {
+ state = Progress::Report;
+
+ message = match &report.crates_currently_indexing[..] {
+ [crate_name] => Some(format!(
+ "{}/{} ({})",
+ report.crates_done, report.crates_total, crate_name
+ )),
+ [crate_name, rest @ ..] => Some(format!(
+ "{}/{} ({} + {} more)",
+ report.crates_done,
+ report.crates_total,
+ crate_name,
+ rest.len()
+ )),
+ _ => None,
+ };
+
+ fraction = Progress::fraction(report.crates_done, report.crates_total);
+ }
+ PrimeCachesProgress::End { cancelled } => {
+ state = Progress::End;
+ message = None;
+ fraction = 1.0;
+
+ self.prime_caches_queue.op_completed(());
+ if cancelled {
+ self.prime_caches_queue
+ .request_op("restart after cancellation".to_string());
+ }
+ }
+ };
+
+ self.report_progress("Indexing", state, message, Some(fraction));
+ }
+ }
+ Event::Vfs(message) => {
+ let _p = profile::span("GlobalState::handle_event/vfs");
+ self.handle_vfs_msg(message);
+ // Coalesce many VFS event into a single loop turn
+ while let Ok(message) = self.loader.receiver.try_recv() {
+ self.handle_vfs_msg(message);
+ }
+ }
+ Event::Flycheck(message) => {
+ let _p = profile::span("GlobalState::handle_event/flycheck");
+ self.handle_flycheck_msg(message);
+ // Coalesce many flycheck updates into a single loop turn
+ while let Ok(message) = self.flycheck_receiver.try_recv() {
+ self.handle_flycheck_msg(message);
+ }
+ }
+ }
+
+ let state_changed = self.process_changes();
+ let memdocs_added_or_removed = self.mem_docs.take_changes();
+
+ if self.is_quiescent() {
+ let became_quiescent = !(was_quiescent
+ || self.fetch_workspaces_queue.op_requested()
+ || self.fetch_build_data_queue.op_requested());
+
+ if became_quiescent {
+ // Project has loaded properly, kick off initial flycheck
+ self.flycheck.iter().for_each(FlycheckHandle::restart);
+ if self.config.prefill_caches() {
+ self.prime_caches_queue.request_op("became quiescent".to_string());
+ }
+ }
+
+ if !was_quiescent || state_changed {
+ // Refresh semantic tokens if the client supports it.
+ if self.config.semantic_tokens_refresh() {
+ self.semantic_tokens_cache.lock().clear();
+ self.send_request::<lsp_types::request::SemanticTokensRefresh>((), |_, _| ());
+ }
+
+ // Refresh code lens if the client supports it.
+ if self.config.code_lens_refresh() {
+ self.send_request::<lsp_types::request::CodeLensRefresh>((), |_, _| ());
+ }
+ }
+
+ if !was_quiescent || state_changed || memdocs_added_or_removed {
+ if self.config.publish_diagnostics() {
+ self.update_diagnostics()
+ }
+ }
+ }
+
+ if let Some(diagnostic_changes) = self.diagnostics.take_changes() {
+ for file_id in diagnostic_changes {
+ let db = self.analysis_host.raw_database();
+ let source_root = db.file_source_root(file_id);
+ if db.source_root(source_root).is_library {
+ // Only publish diagnostics for files in the workspace, not from crates.io deps
+ // or the sysroot.
+ // While theoretically these should never have errors, we have quite a few false
+ // positives particularly in the stdlib, and those diagnostics would stay around
+ // forever if we emitted them here.
+ continue;
+ }
+
+ let uri = file_id_to_url(&self.vfs.read().0, file_id);
+ let mut diagnostics =
+ self.diagnostics.diagnostics_for(file_id).cloned().collect::<Vec<_>>();
+
+ // VSCode assumes diagnostic messages to be non-empty strings, so we need to patch
+ // empty diagnostics. Neither the docs of VSCode nor the LSP spec say whether
+ // diagnostic messages are actually allowed to be empty or not and patching this
+ // in the VSCode client does not work as the assertion happens in the protocol
+ // conversion. So this hack is here to stay, and will be considered a hack
+ // until the LSP decides to state that empty messages are allowed.
+
+ // See https://github.com/rust-lang/rust-analyzer/issues/11404
+ // See https://github.com/rust-lang/rust-analyzer/issues/13130
+ let patch_empty = |message: &mut String| {
+ if message.is_empty() {
+ *message = " ".to_string();
+ }
+ };
+
+ for d in &mut diagnostics {
+ patch_empty(&mut d.message);
+ if let Some(dri) = &mut d.related_information {
+ for dri in dri {
+ patch_empty(&mut dri.message);
+ }
+ }
+ }
+
+ let version = from_proto::vfs_path(&uri)
+ .map(|path| self.mem_docs.get(&path).map(|it| it.version))
+ .unwrap_or_default();
+
+ self.send_notification::<lsp_types::notification::PublishDiagnostics>(
+ lsp_types::PublishDiagnosticsParams { uri, diagnostics, version },
+ );
+ }
+ }
+
+ if self.config.cargo_autoreload() {
+ if let Some(cause) = self.fetch_workspaces_queue.should_start_op() {
+ self.fetch_workspaces(cause);
+ }
+ }
+
+ if !self.fetch_workspaces_queue.op_in_progress() {
+ if let Some(cause) = self.fetch_build_data_queue.should_start_op() {
+ self.fetch_build_data(cause);
+ }
+ }
+
+ if let Some(cause) = self.prime_caches_queue.should_start_op() {
+ tracing::debug!(%cause, "will prime caches");
+ let num_worker_threads = self.config.prime_caches_num_threads();
+
+ self.task_pool.handle.spawn_with_sender({
+ let analysis = self.snapshot().analysis;
+ move |sender| {
+ sender.send(Task::PrimeCaches(PrimeCachesProgress::Begin)).unwrap();
+ let res = analysis.parallel_prime_caches(num_worker_threads, |progress| {
+ let report = PrimeCachesProgress::Report(progress);
+ sender.send(Task::PrimeCaches(report)).unwrap();
+ });
+ sender
+ .send(Task::PrimeCaches(PrimeCachesProgress::End {
+ cancelled: res.is_err(),
+ }))
+ .unwrap();
+ }
+ });
+ }
+
+ let status = self.current_status();
+ if self.last_reported_status.as_ref() != Some(&status) {
+ self.last_reported_status = Some(status.clone());
+
+ if let (lsp_ext::Health::Error, Some(message)) = (status.health, &status.message) {
+ self.show_message(lsp_types::MessageType::ERROR, message.clone());
+ }
+
+ if self.config.server_status_notification() {
+ self.send_notification::<lsp_ext::ServerStatusNotification>(status);
+ }
+ }
+
+ let loop_duration = loop_start.elapsed();
+ if loop_duration > Duration::from_millis(100) && was_quiescent {
+ tracing::warn!("overly long loop turn: {:?}", loop_duration);
+ self.poke_rust_analyzer_developer(format!(
+ "overly long loop turn: {:?}",
+ loop_duration
+ ));
+ }
+ Ok(())
+ }
+
+ fn handle_task(&mut self, prime_caches_progress: &mut Vec<PrimeCachesProgress>, task: Task) {
+ match task {
+ Task::Response(response) => self.respond(response),
++ // Only retry requests that haven't been cancelled. Otherwise we do unnecessary work.
++ Task::Retry(req) if !self.is_completed(&req) => self.on_request(req),
++ Task::Retry(_) => (),
+ Task::Diagnostics(diagnostics_per_file) => {
+ for (file_id, diagnostics) in diagnostics_per_file {
+ self.diagnostics.set_native_diagnostics(file_id, diagnostics)
+ }
+ }
+ Task::PrimeCaches(progress) => match progress {
+ PrimeCachesProgress::Begin => prime_caches_progress.push(progress),
+ PrimeCachesProgress::Report(_) => {
+ match prime_caches_progress.last_mut() {
+ Some(last @ PrimeCachesProgress::Report(_)) => {
+ // Coalesce subsequent update events.
+ *last = progress;
+ }
+ _ => prime_caches_progress.push(progress),
+ }
+ }
+ PrimeCachesProgress::End { .. } => prime_caches_progress.push(progress),
+ },
+ Task::FetchWorkspace(progress) => {
+ let (state, msg) = match progress {
+ ProjectWorkspaceProgress::Begin => (Progress::Begin, None),
+ ProjectWorkspaceProgress::Report(msg) => (Progress::Report, Some(msg)),
+ ProjectWorkspaceProgress::End(workspaces) => {
+ self.fetch_workspaces_queue.op_completed(workspaces);
+
+ let old = Arc::clone(&self.workspaces);
+ self.switch_workspaces("fetched workspace".to_string());
+ let workspaces_updated = !Arc::ptr_eq(&old, &self.workspaces);
+
+ if self.config.run_build_scripts() && workspaces_updated {
+ self.fetch_build_data_queue.request_op(format!("workspace updated"));
+ }
+
+ (Progress::End, None)
+ }
+ };
+
+ self.report_progress("Fetching", state, msg, None);
+ }
+ Task::FetchBuildData(progress) => {
+ let (state, msg) = match progress {
+ BuildDataProgress::Begin => (Some(Progress::Begin), None),
+ BuildDataProgress::Report(msg) => (Some(Progress::Report), Some(msg)),
+ BuildDataProgress::End(build_data_result) => {
+ self.fetch_build_data_queue.op_completed(build_data_result);
+
+ self.switch_workspaces("fetched build data".to_string());
+
+ (Some(Progress::End), None)
+ }
+ };
+
+ if let Some(state) = state {
+ self.report_progress("Loading", state, msg, None);
+ }
+ }
+ }
+ }
+
+ fn handle_vfs_msg(&mut self, message: vfs::loader::Message) {
+ match message {
+ vfs::loader::Message::Loaded { files } => {
+ let vfs = &mut self.vfs.write().0;
+ for (path, contents) in files {
+ let path = VfsPath::from(path);
+ if !self.mem_docs.contains(&path) {
+ vfs.set_file_contents(path, contents);
+ }
+ }
+ }
+ vfs::loader::Message::Progress { n_total, n_done, config_version } => {
+ always!(config_version <= self.vfs_config_version);
+
+ self.vfs_progress_config_version = config_version;
+ self.vfs_progress_n_total = n_total;
+ self.vfs_progress_n_done = n_done;
+
+ let state = if n_done == 0 {
+ Progress::Begin
+ } else if n_done < n_total {
+ Progress::Report
+ } else {
+ assert_eq!(n_done, n_total);
+ Progress::End
+ };
+ self.report_progress(
+ "Roots Scanned",
+ state,
+ Some(format!("{}/{}", n_done, n_total)),
+ Some(Progress::fraction(n_done, n_total)),
+ )
+ }
+ }
+ }
+
+ fn handle_flycheck_msg(&mut self, message: flycheck::Message) {
+ match message {
+ flycheck::Message::AddDiagnostic { id, workspace_root, diagnostic } => {
+ let snap = self.snapshot();
+ let diagnostics = crate::diagnostics::to_proto::map_rust_diagnostic_to_lsp(
+ &self.config.diagnostics_map(),
+ &diagnostic,
+ &workspace_root,
+ &snap,
+ );
+ for diag in diagnostics {
+ match url_to_file_id(&self.vfs.read().0, &diag.url) {
+ Ok(file_id) => self.diagnostics.add_check_diagnostic(
+ id,
+ file_id,
+ diag.diagnostic,
+ diag.fix,
+ ),
+ Err(err) => {
+ tracing::error!("File with cargo diagnostic not found in VFS: {}", err);
+ }
+ };
+ }
+ }
+
+ flycheck::Message::Progress { id, progress } => {
+ let (state, message) = match progress {
+ flycheck::Progress::DidStart => {
+ self.diagnostics.clear_check(id);
+ (Progress::Begin, None)
+ }
+ flycheck::Progress::DidCheckCrate(target) => (Progress::Report, Some(target)),
+ flycheck::Progress::DidCancel => (Progress::End, None),
+ flycheck::Progress::DidFailToRestart(err) => {
+ self.show_and_log_error(
+ "cargo check failed".to_string(),
+ Some(err.to_string()),
+ );
+ return;
+ }
+ flycheck::Progress::DidFinish(result) => {
+ if let Err(err) = result {
+ self.show_and_log_error(
+ "cargo check failed".to_string(),
+ Some(err.to_string()),
+ );
+ }
+ (Progress::End, None)
+ }
+ };
+
+ // When we're running multiple flychecks, we have to include a disambiguator in
+ // the title, or the editor complains. Note that this is a user-facing string.
+ let title = if self.flycheck.len() == 1 {
+ match self.config.flycheck() {
+ Some(config) => format!("{}", config),
+ None => "cargo check".to_string(),
+ }
+ } else {
+ format!("cargo check (#{})", id + 1)
+ };
+ self.report_progress(&title, state, message, None);
+ }
+ }
+ }
+
+ /// Registers and handles a request. This should only be called once per incoming request.
+ fn on_new_request(&mut self, request_received: Instant, req: Request) {
+ self.register_request(&req, request_received);
+ self.on_request(req);
+ }
+
+ /// Handles a request.
+ fn on_request(&mut self, req: Request) {
+ if self.shutdown_requested {
+ self.respond(lsp_server::Response::new_err(
+ req.id,
+ lsp_server::ErrorCode::InvalidRequest as i32,
+ "Shutdown already requested.".to_owned(),
+ ));
+ return;
+ }
+
+ // Avoid flashing a bunch of unresolved references during initial load.
+ if self.workspaces.is_empty() && !self.is_quiescent() {
+ self.respond(lsp_server::Response::new_err(
+ req.id,
+ lsp_server::ErrorCode::ContentModified as i32,
+ "waiting for cargo metadata or cargo check".to_owned(),
+ ));
+ return;
+ }
+
+ RequestDispatcher { req: Some(req), global_state: self }
+ .on_sync_mut::<lsp_types::request::Shutdown>(|s, ()| {
+ s.shutdown_requested = true;
+ Ok(())
+ })
+ .on_sync_mut::<lsp_ext::ReloadWorkspace>(handlers::handle_workspace_reload)
+ .on_sync_mut::<lsp_ext::MemoryUsage>(handlers::handle_memory_usage)
+ .on_sync_mut::<lsp_ext::ShuffleCrateGraph>(handlers::handle_shuffle_crate_graph)
+ .on_sync_mut::<lsp_ext::CancelFlycheck>(handlers::handle_cancel_flycheck)
+ .on_sync::<lsp_ext::JoinLines>(handlers::handle_join_lines)
+ .on_sync::<lsp_ext::OnEnter>(handlers::handle_on_enter)
+ .on_sync::<lsp_types::request::SelectionRangeRequest>(handlers::handle_selection_range)
+ .on_sync::<lsp_ext::MatchingBrace>(handlers::handle_matching_brace)
+ .on::<lsp_ext::AnalyzerStatus>(handlers::handle_analyzer_status)
+ .on::<lsp_ext::SyntaxTree>(handlers::handle_syntax_tree)
+ .on::<lsp_ext::ViewHir>(handlers::handle_view_hir)
+ .on::<lsp_ext::ViewFileText>(handlers::handle_view_file_text)
+ .on::<lsp_ext::ViewCrateGraph>(handlers::handle_view_crate_graph)
+ .on::<lsp_ext::ViewItemTree>(handlers::handle_view_item_tree)
+ .on::<lsp_ext::ExpandMacro>(handlers::handle_expand_macro)
+ .on::<lsp_ext::ParentModule>(handlers::handle_parent_module)
+ .on::<lsp_ext::Runnables>(handlers::handle_runnables)
+ .on::<lsp_ext::RelatedTests>(handlers::handle_related_tests)
+ .on::<lsp_ext::CodeActionRequest>(handlers::handle_code_action)
+ .on::<lsp_ext::CodeActionResolveRequest>(handlers::handle_code_action_resolve)
+ .on::<lsp_ext::HoverRequest>(handlers::handle_hover)
+ .on::<lsp_ext::ExternalDocs>(handlers::handle_open_docs)
+ .on::<lsp_ext::OpenCargoToml>(handlers::handle_open_cargo_toml)
+ .on::<lsp_ext::MoveItem>(handlers::handle_move_item)
+ .on::<lsp_ext::WorkspaceSymbol>(handlers::handle_workspace_symbol)
+ .on::<lsp_ext::OnTypeFormatting>(handlers::handle_on_type_formatting)
+ .on::<lsp_types::request::DocumentSymbolRequest>(handlers::handle_document_symbol)
+ .on::<lsp_types::request::GotoDefinition>(handlers::handle_goto_definition)
+ .on::<lsp_types::request::GotoDeclaration>(handlers::handle_goto_declaration)
+ .on::<lsp_types::request::GotoImplementation>(handlers::handle_goto_implementation)
+ .on::<lsp_types::request::GotoTypeDefinition>(handlers::handle_goto_type_definition)
+ .on::<lsp_types::request::InlayHintRequest>(handlers::handle_inlay_hints)
+ .on::<lsp_types::request::InlayHintResolveRequest>(handlers::handle_inlay_hints_resolve)
+ .on::<lsp_types::request::Completion>(handlers::handle_completion)
+ .on::<lsp_types::request::ResolveCompletionItem>(handlers::handle_completion_resolve)
+ .on::<lsp_types::request::CodeLensRequest>(handlers::handle_code_lens)
+ .on::<lsp_types::request::CodeLensResolve>(handlers::handle_code_lens_resolve)
+ .on::<lsp_types::request::FoldingRangeRequest>(handlers::handle_folding_range)
+ .on::<lsp_types::request::SignatureHelpRequest>(handlers::handle_signature_help)
+ .on::<lsp_types::request::PrepareRenameRequest>(handlers::handle_prepare_rename)
+ .on::<lsp_types::request::Rename>(handlers::handle_rename)
+ .on::<lsp_types::request::References>(handlers::handle_references)
+ .on::<lsp_types::request::Formatting>(handlers::handle_formatting)
+ .on::<lsp_types::request::RangeFormatting>(handlers::handle_range_formatting)
+ .on::<lsp_types::request::DocumentHighlightRequest>(handlers::handle_document_highlight)
+ .on::<lsp_types::request::CallHierarchyPrepare>(handlers::handle_call_hierarchy_prepare)
+ .on::<lsp_types::request::CallHierarchyIncomingCalls>(
+ handlers::handle_call_hierarchy_incoming,
+ )
+ .on::<lsp_types::request::CallHierarchyOutgoingCalls>(
+ handlers::handle_call_hierarchy_outgoing,
+ )
+ .on::<lsp_types::request::SemanticTokensFullRequest>(
+ handlers::handle_semantic_tokens_full,
+ )
+ .on::<lsp_types::request::SemanticTokensFullDeltaRequest>(
+ handlers::handle_semantic_tokens_full_delta,
+ )
+ .on::<lsp_types::request::SemanticTokensRangeRequest>(
+ handlers::handle_semantic_tokens_range,
+ )
+ .on::<lsp_types::request::WillRenameFiles>(handlers::handle_will_rename_files)
+ .on::<lsp_ext::Ssr>(handlers::handle_ssr)
+ .finish();
+ }
+
+ /// Handles an incoming notification.
+ fn on_notification(&mut self, not: Notification) -> Result<()> {
+ NotificationDispatcher { not: Some(not), global_state: self }
+ .on::<lsp_types::notification::Cancel>(|this, params| {
+ let id: lsp_server::RequestId = match params.id {
+ lsp_types::NumberOrString::Number(id) => id.into(),
+ lsp_types::NumberOrString::String(id) => id.into(),
+ };
+ this.cancel(id);
+ Ok(())
+ })?
+ .on::<lsp_types::notification::WorkDoneProgressCancel>(|_this, _params| {
+ // Just ignore this. It is OK to continue sending progress
+ // notifications for this token, as the client can't know when
+ // we accepted notification.
+ Ok(())
+ })?
+ .on::<lsp_types::notification::DidOpenTextDocument>(|this, params| {
+ if let Ok(path) = from_proto::vfs_path(¶ms.text_document.uri) {
+ let already_exists = this
+ .mem_docs
+ .insert(path.clone(), DocumentData::new(params.text_document.version))
+ .is_err();
+ if already_exists {
+ tracing::error!("duplicate DidOpenTextDocument: {}", path)
+ }
+ this.vfs
+ .write()
+ .0
+ .set_file_contents(path, Some(params.text_document.text.into_bytes()));
+ }
+ Ok(())
+ })?
+ .on::<lsp_types::notification::DidChangeTextDocument>(|this, params| {
+ if let Ok(path) = from_proto::vfs_path(¶ms.text_document.uri) {
+ match this.mem_docs.get_mut(&path) {
+ Some(doc) => {
+ // The version passed in DidChangeTextDocument is the version after all edits are applied
+ // so we should apply it before the vfs is notified.
+ doc.version = params.text_document.version;
+ }
+ None => {
+ tracing::error!("unexpected DidChangeTextDocument: {}", path);
+ return Ok(());
+ }
+ };
+
+ let vfs = &mut this.vfs.write().0;
+ let file_id = vfs.file_id(&path).unwrap();
+ let mut text = String::from_utf8(vfs.file_contents(file_id).to_vec()).unwrap();
+ apply_document_changes(&mut text, params.content_changes);
+
+ vfs.set_file_contents(path, Some(text.into_bytes()));
+ }
+ Ok(())
+ })?
+ .on::<lsp_types::notification::DidCloseTextDocument>(|this, params| {
+ if let Ok(path) = from_proto::vfs_path(¶ms.text_document.uri) {
+ if this.mem_docs.remove(&path).is_err() {
+ tracing::error!("orphan DidCloseTextDocument: {}", path);
+ }
+
+ this.semantic_tokens_cache.lock().remove(¶ms.text_document.uri);
+
+ if let Some(path) = path.as_path() {
+ this.loader.handle.invalidate(path.to_path_buf());
+ }
+ }
+ Ok(())
+ })?
+ .on::<lsp_types::notification::DidSaveTextDocument>(|this, params| {
+ let mut updated = false;
+ if let Ok(vfs_path) = from_proto::vfs_path(¶ms.text_document.uri) {
+ let (vfs, _) = &*this.vfs.read();
+
+ // Trigger flychecks for all workspaces that depend on the saved file
+ if let Some(file_id) = vfs.file_id(&vfs_path) {
+ let analysis = this.analysis_host.analysis();
+ // Crates containing or depending on the saved file
+ let crate_ids: Vec<_> = analysis
+ .crate_for(file_id)?
+ .into_iter()
+ .flat_map(|id| {
+ this.analysis_host
+ .raw_database()
+ .crate_graph()
+ .transitive_rev_deps(id)
+ })
+ .sorted()
+ .unique()
+ .collect();
+
+ let crate_root_paths: Vec<_> = crate_ids
+ .iter()
+ .filter_map(|&crate_id| {
+ analysis
+ .crate_root(crate_id)
+ .map(|file_id| {
+ vfs.file_path(file_id).as_path().map(ToOwned::to_owned)
+ })
+ .transpose()
+ })
+ .collect::<ide::Cancellable<_>>()?;
+ let crate_root_paths: Vec<_> =
+ crate_root_paths.iter().map(Deref::deref).collect();
+
+ // Find all workspaces that have at least one target containing the saved file
+ let workspace_ids =
+ this.workspaces.iter().enumerate().filter(|(_, ws)| match ws {
+ project_model::ProjectWorkspace::Cargo { cargo, .. } => {
+ cargo.packages().any(|pkg| {
+ cargo[pkg].targets.iter().any(|&it| {
+ crate_root_paths.contains(&cargo[it].root.as_path())
+ })
+ })
+ }
+ project_model::ProjectWorkspace::Json { project, .. } => project
+ .crates()
+ .any(|(c, _)| crate_ids.iter().any(|&crate_id| crate_id == c)),
+ project_model::ProjectWorkspace::DetachedFiles { .. } => false,
+ });
+
+ // Find and trigger corresponding flychecks
+ for flycheck in &this.flycheck {
+ for (id, _) in workspace_ids.clone() {
+ if id == flycheck.id() {
+ updated = true;
+ flycheck.restart();
+ continue;
+ }
+ }
+ }
+ }
+
+ // Re-fetch workspaces if a workspace related file has changed
+ if let Some(abs_path) = vfs_path.as_path() {
+ if reload::should_refresh_for_change(&abs_path, ChangeKind::Modify) {
+ this.fetch_workspaces_queue
+ .request_op(format!("DidSaveTextDocument {}", abs_path.display()));
+ }
+ }
+ }
+
+ // No specific flycheck was triggered, so let's trigger all of them.
+ if !updated {
+ for flycheck in &this.flycheck {
+ flycheck.restart();
+ }
+ }
+ Ok(())
+ })?
+ .on::<lsp_types::notification::DidChangeConfiguration>(|this, _params| {
+ // As stated in https://github.com/microsoft/language-server-protocol/issues/676,
+ // this notification's parameters should be ignored and the actual config queried separately.
+ this.send_request::<lsp_types::request::WorkspaceConfiguration>(
+ lsp_types::ConfigurationParams {
+ items: vec![lsp_types::ConfigurationItem {
+ scope_uri: None,
+ section: Some("rust-analyzer".to_string()),
+ }],
+ },
+ |this, resp| {
+ tracing::debug!("config update response: '{:?}", resp);
+ let lsp_server::Response { error, result, .. } = resp;
+
+ match (error, result) {
+ (Some(err), _) => {
+ tracing::error!("failed to fetch the server settings: {:?}", err)
+ }
+ (None, Some(mut configs)) => {
+ if let Some(json) = configs.get_mut(0) {
+ // Note that json can be null according to the spec if the client can't
+ // provide a configuration. This is handled in Config::update below.
+ let mut config = Config::clone(&*this.config);
+ if let Err(error) = config.update(json.take()) {
+ this.show_message(
+ lsp_types::MessageType::WARNING,
+ error.to_string(),
+ );
+ }
+ this.update_configuration(config);
+ }
+ }
+ (None, None) => tracing::error!(
+ "received empty server settings response from the client"
+ ),
+ }
+ },
+ );
+
+ Ok(())
+ })?
+ .on::<lsp_types::notification::DidChangeWatchedFiles>(|this, params| {
+ for change in params.changes {
+ if let Ok(path) = from_proto::abs_path(&change.uri) {
+ this.loader.handle.invalidate(path);
+ }
+ }
+ Ok(())
+ })?
+ .finish();
+ Ok(())
+ }
+
+ fn update_diagnostics(&mut self) {
+ let subscriptions = self
+ .mem_docs
+ .iter()
+ .map(|path| self.vfs.read().0.file_id(path).unwrap())
+ .collect::<Vec<_>>();
+
+ tracing::trace!("updating notifications for {:?}", subscriptions);
+
+ let snapshot = self.snapshot();
+ self.task_pool.handle.spawn(move || {
+ let diagnostics = subscriptions
+ .into_iter()
+ .filter_map(|file_id| {
+ handlers::publish_diagnostics(&snapshot, file_id)
+ .ok()
+ .map(|diags| (file_id, diags))
+ })
+ .collect::<Vec<_>>();
+ Task::Diagnostics(diagnostics)
+ })
+ }
+}
--- /dev/null
- &cargo_config,
+//! Project loading & configuration updates.
+//!
+//! This is quite tricky. The main problem is time and changes -- there's no
+//! fixed "project" rust-analyzer is working with, "current project" is itself
+//! mutable state. For example, when the user edits `Cargo.toml` by adding a new
+//! dependency, project model changes. What's more, switching project model is
+//! not instantaneous -- it takes time to run `cargo metadata` and (for proc
+//! macros) `cargo check`.
+//!
+//! The main guiding principle here is, as elsewhere in rust-analyzer,
+//! robustness. We try not to assume that the project model exists or is
+//! correct. Instead, we try to provide a best-effort service. Even if the
+//! project is currently loading and we don't have a full project model, we
+//! still want to respond to various requests.
+use std::{mem, sync::Arc};
+
+use flycheck::{FlycheckConfig, FlycheckHandle};
+use hir::db::DefDatabase;
+use ide::Change;
+use ide_db::base_db::{
+ CrateGraph, Env, ProcMacro, ProcMacroExpander, ProcMacroExpansionError, ProcMacroKind,
+ ProcMacroLoadResult, SourceRoot, VfsPath,
+};
+use proc_macro_api::{MacroDylib, ProcMacroServer};
+use project_model::{ProjectWorkspace, WorkspaceBuildScripts};
+use syntax::SmolStr;
+use vfs::{file_set::FileSetConfig, AbsPath, AbsPathBuf, ChangeKind};
+
+use crate::{
+ config::{Config, FilesWatcher, LinkedProject},
+ global_state::GlobalState,
+ lsp_ext,
+ main_loop::Task,
+ op_queue::Cause,
+};
+
+#[derive(Debug)]
+pub(crate) enum ProjectWorkspaceProgress {
+ Begin,
+ Report(String),
+ End(Vec<anyhow::Result<ProjectWorkspace>>),
+}
+
+#[derive(Debug)]
+pub(crate) enum BuildDataProgress {
+ Begin,
+ Report(String),
+ End((Arc<Vec<ProjectWorkspace>>, Vec<anyhow::Result<WorkspaceBuildScripts>>)),
+}
+
+impl GlobalState {
+ pub(crate) fn is_quiescent(&self) -> bool {
+ !(self.fetch_workspaces_queue.op_in_progress()
+ || self.fetch_build_data_queue.op_in_progress()
+ || self.vfs_progress_config_version < self.vfs_config_version
+ || self.vfs_progress_n_done < self.vfs_progress_n_total)
+ }
+
+ pub(crate) fn update_configuration(&mut self, config: Config) {
+ let _p = profile::span("GlobalState::update_configuration");
+ let old_config = mem::replace(&mut self.config, Arc::new(config));
+ if self.config.lru_capacity() != old_config.lru_capacity() {
+ self.analysis_host.update_lru_capacity(self.config.lru_capacity());
+ }
+ if self.config.linked_projects() != old_config.linked_projects() {
+ self.fetch_workspaces_queue.request_op("linked projects changed".to_string())
+ } else if self.config.flycheck() != old_config.flycheck() {
+ self.reload_flycheck();
+ }
+
+ if self.analysis_host.raw_database().enable_proc_attr_macros()
+ != self.config.expand_proc_attr_macros()
+ {
+ self.analysis_host
+ .raw_database_mut()
+ .set_enable_proc_attr_macros(self.config.expand_proc_attr_macros());
+ }
+ }
+
+ pub(crate) fn current_status(&self) -> lsp_ext::ServerStatusParams {
+ let mut status = lsp_ext::ServerStatusParams {
+ health: lsp_ext::Health::Ok,
+ quiescent: self.is_quiescent(),
+ message: None,
+ };
+
+ if self.proc_macro_changed {
+ status.health = lsp_ext::Health::Warning;
+ status.message =
+ Some("Reload required due to source changes of a procedural macro.".into())
+ }
+ if let Err(_) = self.fetch_build_data_error() {
+ status.health = lsp_ext::Health::Warning;
+ status.message =
+ Some("Failed to run build scripts of some packages, check the logs.".to_string());
+ }
+ if !self.config.cargo_autoreload()
+ && self.is_quiescent()
+ && self.fetch_workspaces_queue.op_requested()
+ {
+ status.health = lsp_ext::Health::Warning;
+ status.message = Some("Workspace reload required".to_string())
+ }
+
+ if let Err(error) = self.fetch_workspace_error() {
+ status.health = lsp_ext::Health::Error;
+ status.message = Some(error)
+ }
+ status
+ }
+
+ pub(crate) fn fetch_workspaces(&mut self, cause: Cause) {
+ tracing::info!(%cause, "will fetch workspaces");
+
+ self.task_pool.handle.spawn_with_sender({
+ let linked_projects = self.config.linked_projects();
+ let detached_files = self.config.detached_files().to_vec();
+ let cargo_config = self.config.cargo();
+
+ move |sender| {
+ let progress = {
+ let sender = sender.clone();
+ move |msg| {
+ sender
+ .send(Task::FetchWorkspace(ProjectWorkspaceProgress::Report(msg)))
+ .unwrap()
+ }
+ };
+
+ sender.send(Task::FetchWorkspace(ProjectWorkspaceProgress::Begin)).unwrap();
+
+ let mut workspaces = linked_projects
+ .iter()
+ .map(|project| match project {
+ LinkedProject::ProjectManifest(manifest) => {
+ project_model::ProjectWorkspace::load(
+ manifest.clone(),
+ &cargo_config,
+ &progress,
+ )
+ }
+ LinkedProject::InlineJsonProject(it) => {
+ project_model::ProjectWorkspace::load_inline(
+ it.clone(),
+ cargo_config.target.as_deref(),
- if let Some((path, args)) = self.config.proc_macro_srv() {
++ &cargo_config.extra_env,
+ )
+ }
+ })
+ .collect::<Vec<_>>();
+
+ if !detached_files.is_empty() {
+ workspaces
+ .push(project_model::ProjectWorkspace::load_detached_files(detached_files));
+ }
+
+ tracing::info!("did fetch workspaces {:?}", workspaces);
+ sender
+ .send(Task::FetchWorkspace(ProjectWorkspaceProgress::End(workspaces)))
+ .unwrap();
+ }
+ });
+ }
+
+ pub(crate) fn fetch_build_data(&mut self, cause: Cause) {
+ tracing::info!(%cause, "will fetch build data");
+ let workspaces = Arc::clone(&self.workspaces);
+ let config = self.config.cargo();
+ self.task_pool.handle.spawn_with_sender(move |sender| {
+ sender.send(Task::FetchBuildData(BuildDataProgress::Begin)).unwrap();
+
+ let progress = {
+ let sender = sender.clone();
+ move |msg| {
+ sender.send(Task::FetchBuildData(BuildDataProgress::Report(msg))).unwrap()
+ }
+ };
+ let mut res = Vec::new();
+ for ws in workspaces.iter() {
+ res.push(ws.run_build_scripts(&config, &progress));
+ }
+ sender.send(Task::FetchBuildData(BuildDataProgress::End((workspaces, res)))).unwrap();
+ });
+ }
+
+ pub(crate) fn switch_workspaces(&mut self, cause: Cause) {
+ let _p = profile::span("GlobalState::switch_workspaces");
+ tracing::info!(%cause, "will switch workspaces");
+
+ if let Err(error_message) = self.fetch_workspace_error() {
+ self.show_and_log_error(error_message, None);
+ if !self.workspaces.is_empty() {
+ // It only makes sense to switch to a partially broken workspace
+ // if we don't have any workspace at all yet.
+ return;
+ }
+ }
+
+ if let Err(error) = self.fetch_build_data_error() {
+ self.show_and_log_error("failed to run build scripts".to_string(), Some(error));
+ }
+
+ let workspaces = self
+ .fetch_workspaces_queue
+ .last_op_result()
+ .iter()
+ .filter_map(|res| res.as_ref().ok().cloned())
+ .collect::<Vec<_>>();
+
+ fn eq_ignore_build_data<'a>(
+ left: &'a ProjectWorkspace,
+ right: &'a ProjectWorkspace,
+ ) -> bool {
+ let key = |p: &'a ProjectWorkspace| match p {
+ ProjectWorkspace::Cargo {
+ cargo,
+ sysroot,
+ rustc,
+ rustc_cfg,
+ cfg_overrides,
+
+ build_scripts: _,
+ toolchain: _,
+ } => Some((cargo, sysroot, rustc, rustc_cfg, cfg_overrides)),
+ _ => None,
+ };
+ match (key(left), key(right)) {
+ (Some(lk), Some(rk)) => lk == rk,
+ _ => left == right,
+ }
+ }
+
+ let same_workspaces = workspaces.len() == self.workspaces.len()
+ && workspaces
+ .iter()
+ .zip(self.workspaces.iter())
+ .all(|(l, r)| eq_ignore_build_data(l, r));
+
+ if same_workspaces {
+ let (workspaces, build_scripts) = self.fetch_build_data_queue.last_op_result();
+ if Arc::ptr_eq(workspaces, &self.workspaces) {
+ tracing::debug!("set build scripts to workspaces");
+
+ let workspaces = workspaces
+ .iter()
+ .cloned()
+ .zip(build_scripts)
+ .map(|(mut ws, bs)| {
+ ws.set_build_scripts(bs.as_ref().ok().cloned().unwrap_or_default());
+ ws
+ })
+ .collect::<Vec<_>>();
+
+ // Workspaces are the same, but we've updated build data.
+ self.workspaces = Arc::new(workspaces);
+ } else {
+ tracing::info!("build scripts do not match the version of the active workspace");
+ // Current build scripts do not match the version of the active
+ // workspace, so there's nothing for us to update.
+ return;
+ }
+ } else {
+ tracing::debug!("abandon build scripts for workspaces");
+
+ // Here, we completely changed the workspace (Cargo.toml edit), so
+ // we don't care about build-script results, they are stale.
+ self.workspaces = Arc::new(workspaces)
+ }
+
+ if let FilesWatcher::Client = self.config.files().watcher {
+ let registration_options = lsp_types::DidChangeWatchedFilesRegistrationOptions {
+ watchers: self
+ .workspaces
+ .iter()
+ .flat_map(|ws| ws.to_roots())
+ .filter(|it| it.is_local)
+ .flat_map(|root| {
+ root.include.into_iter().flat_map(|it| {
+ [
+ format!("{}/**/*.rs", it.display()),
+ format!("{}/**/Cargo.toml", it.display()),
+ format!("{}/**/Cargo.lock", it.display()),
+ ]
+ })
+ })
+ .map(|glob_pattern| lsp_types::FileSystemWatcher { glob_pattern, kind: None })
+ .collect(),
+ };
+ let registration = lsp_types::Registration {
+ id: "workspace/didChangeWatchedFiles".to_string(),
+ method: "workspace/didChangeWatchedFiles".to_string(),
+ register_options: Some(serde_json::to_value(registration_options).unwrap()),
+ };
+ self.send_request::<lsp_types::request::RegisterCapability>(
+ lsp_types::RegistrationParams { registrations: vec![registration] },
+ |_, _| (),
+ );
+ }
+
+ let mut change = Change::new();
+
+ let files_config = self.config.files();
+ let project_folders = ProjectFolders::new(&self.workspaces, &files_config.exclude);
+
+ let standalone_server_name =
+ format!("rust-analyzer-proc-macro-srv{}", std::env::consts::EXE_SUFFIX);
+
+ if self.proc_macro_clients.is_empty() {
- let mut args = args.clone();
- let mut path = path.clone();
-
- if let ProjectWorkspace::Cargo { sysroot, .. }
- | ProjectWorkspace::Json { sysroot, .. } = ws
- {
- tracing::debug!("Found a cargo workspace...");
- if let Some(sysroot) = sysroot.as_ref() {
- tracing::debug!("Found a cargo workspace with a sysroot...");
- let server_path =
- sysroot.root().join("libexec").join(&standalone_server_name);
- if std::fs::metadata(&server_path).is_ok() {
- tracing::debug!(
- "And the server exists at {}",
- server_path.display()
- );
- path = server_path;
- args = vec![];
- } else {
- tracing::debug!(
- "And the server does not exist at {}",
- server_path.display()
- );
++ if let Some((path, path_manually_set)) = self.config.proc_macro_srv() {
+ tracing::info!("Spawning proc-macro servers");
+ self.proc_macro_clients = self
+ .workspaces
+ .iter()
+ .map(|ws| {
- }
++ let (path, args) = if path_manually_set {
++ tracing::debug!(
++ "Pro-macro server path explicitly set: {}",
++ path.display()
++ );
++ (path.clone(), vec![])
++ } else {
++ let mut sysroot_server = None;
++ if let ProjectWorkspace::Cargo { sysroot, .. }
++ | ProjectWorkspace::Json { sysroot, .. } = ws
++ {
++ if let Some(sysroot) = sysroot.as_ref() {
++ let server_path = sysroot
++ .root()
++ .join("libexec")
++ .join(&standalone_server_name);
++ if std::fs::metadata(&server_path).is_ok() {
++ tracing::debug!(
++ "Sysroot proc-macro server exists at {}",
++ server_path.display()
++ );
++ sysroot_server = Some(server_path);
++ } else {
++ tracing::debug!(
++ "Sysroot proc-macro server does not exist at {}",
++ server_path.display()
++ );
++ }
+ }
+ }
- ProcMacroServer::spawn(path.clone(), args.clone()).map_err(|err| {
++ sysroot_server.map_or_else(
++ || (path.clone(), vec!["proc-macro".to_owned()]),
++ |path| (path, vec![]),
++ )
++ };
+
+ tracing::info!(?args, "Using proc-macro server at {}", path.display(),);
- &self.config.cargo(),
++ ProcMacroServer::spawn(path.clone(), args).map_err(|err| {
+ let error = format!(
+ "Failed to run proc-macro server from path {}, error: {:?}",
+ path.display(),
+ err
+ );
+ tracing::error!(error);
+ error
+ })
+ })
+ .collect()
+ };
+ }
+
+ let watch = match files_config.watcher {
+ FilesWatcher::Client => vec![],
+ FilesWatcher::Server => project_folders.watch,
+ };
+ self.vfs_config_version += 1;
+ self.loader.handle.set_config(vfs::loader::Config {
+ load: project_folders.load,
+ watch,
+ version: self.vfs_config_version,
+ });
+
+ // Create crate graph from all the workspaces
+ let crate_graph = {
+ let dummy_replacements = self.config.dummy_replacements();
+
+ let vfs = &mut self.vfs.write().0;
+ let loader = &mut self.loader;
+ let mem_docs = &self.mem_docs;
+ let mut load = move |path: &AbsPath| {
+ let _p = profile::span("GlobalState::load");
+ let vfs_path = vfs::VfsPath::from(path.to_path_buf());
+ if !mem_docs.contains(&vfs_path) {
+ let contents = loader.handle.load_sync(path);
+ vfs.set_file_contents(vfs_path.clone(), contents);
+ }
+ let res = vfs.file_id(&vfs_path);
+ if res.is_none() {
+ tracing::warn!("failed to load {}", path.display())
+ }
+ res
+ };
+
+ let mut crate_graph = CrateGraph::default();
+ for (idx, ws) in self.workspaces.iter().enumerate() {
+ let proc_macro_client = match self.proc_macro_clients.get(idx) {
+ Some(res) => res.as_ref().map_err(|e| &**e),
+ None => Err("Proc macros are disabled"),
+ };
+ let mut load_proc_macro = move |crate_name: &str, path: &AbsPath| {
+ load_proc_macro(
+ proc_macro_client,
+ path,
+ dummy_replacements.get(crate_name).map(|v| &**v).unwrap_or_default(),
+ )
+ };
+ crate_graph.extend(ws.to_crate_graph(
+ &mut load_proc_macro,
+ &mut load,
++ &self.config.cargo().extra_env,
+ ));
+ }
+ crate_graph
+ };
+ change.set_crate_graph(crate_graph);
+
+ self.source_root_config = project_folders.source_root_config;
+
+ self.analysis_host.apply_change(change);
+ self.process_changes();
+ self.reload_flycheck();
+ tracing::info!("did switch workspaces");
+ }
+
+ fn fetch_workspace_error(&self) -> Result<(), String> {
+ let mut buf = String::new();
+
+ for ws in self.fetch_workspaces_queue.last_op_result() {
+ if let Err(err) = ws {
+ stdx::format_to!(buf, "rust-analyzer failed to load workspace: {:#}\n", err);
+ }
+ }
+
+ if buf.is_empty() {
+ return Ok(());
+ }
+
+ Err(buf)
+ }
+
+ fn fetch_build_data_error(&self) -> Result<(), String> {
+ let mut buf = String::new();
+
+ for ws in &self.fetch_build_data_queue.last_op_result().1 {
+ match ws {
+ Ok(data) => match data.error() {
+ Some(stderr) => stdx::format_to!(buf, "{:#}\n", stderr),
+ _ => (),
+ },
+ // io errors
+ Err(err) => stdx::format_to!(buf, "{:#}\n", err),
+ }
+ }
+
+ if buf.is_empty() {
+ Ok(())
+ } else {
+ Err(buf)
+ }
+ }
+
+ fn reload_flycheck(&mut self) {
+ let _p = profile::span("GlobalState::reload_flycheck");
+ let config = match self.config.flycheck() {
+ Some(it) => it,
+ None => {
+ self.flycheck = Vec::new();
+ self.diagnostics.clear_check_all();
+ return;
+ }
+ };
+
+ let sender = self.flycheck_sender.clone();
+ self.flycheck = self
+ .workspaces
+ .iter()
+ .enumerate()
+ .filter_map(|(id, w)| match w {
+ ProjectWorkspace::Cargo { cargo, .. } => Some((id, cargo.workspace_root())),
+ ProjectWorkspace::Json { project, .. } => {
+ // Enable flychecks for json projects if a custom flycheck command was supplied
+ // in the workspace configuration.
+ match config {
+ FlycheckConfig::CustomCommand { .. } => Some((id, project.path())),
+ _ => None,
+ }
+ }
+ ProjectWorkspace::DetachedFiles { .. } => None,
+ })
+ .map(|(id, root)| {
+ let sender = sender.clone();
+ FlycheckHandle::spawn(
+ id,
+ Box::new(move |msg| sender.send(msg).unwrap()),
+ config.clone(),
+ root.to_path_buf(),
+ )
+ })
+ .collect();
+ }
+}
+
+#[derive(Default)]
+pub(crate) struct ProjectFolders {
+ pub(crate) load: Vec<vfs::loader::Entry>,
+ pub(crate) watch: Vec<usize>,
+ pub(crate) source_root_config: SourceRootConfig,
+}
+
+impl ProjectFolders {
+ pub(crate) fn new(
+ workspaces: &[ProjectWorkspace],
+ global_excludes: &[AbsPathBuf],
+ ) -> ProjectFolders {
+ let mut res = ProjectFolders::default();
+ let mut fsc = FileSetConfig::builder();
+ let mut local_filesets = vec![];
+
+ for root in workspaces.iter().flat_map(|ws| ws.to_roots()) {
+ let file_set_roots: Vec<VfsPath> =
+ root.include.iter().cloned().map(VfsPath::from).collect();
+
+ let entry = {
+ let mut dirs = vfs::loader::Directories::default();
+ dirs.extensions.push("rs".into());
+ dirs.include.extend(root.include);
+ dirs.exclude.extend(root.exclude);
+ for excl in global_excludes {
+ if dirs
+ .include
+ .iter()
+ .any(|incl| incl.starts_with(excl) || excl.starts_with(incl))
+ {
+ dirs.exclude.push(excl.clone());
+ }
+ }
+
+ vfs::loader::Entry::Directories(dirs)
+ };
+
+ if root.is_local {
+ res.watch.push(res.load.len());
+ }
+ res.load.push(entry);
+
+ if root.is_local {
+ local_filesets.push(fsc.len());
+ }
+ fsc.add_file_set(file_set_roots)
+ }
+
+ let fsc = fsc.build();
+ res.source_root_config = SourceRootConfig { fsc, local_filesets };
+
+ res
+ }
+}
+
+#[derive(Default, Debug)]
+pub(crate) struct SourceRootConfig {
+ pub(crate) fsc: FileSetConfig,
+ pub(crate) local_filesets: Vec<usize>,
+}
+
+impl SourceRootConfig {
+ pub(crate) fn partition(&self, vfs: &vfs::Vfs) -> Vec<SourceRoot> {
+ let _p = profile::span("SourceRootConfig::partition");
+ self.fsc
+ .partition(vfs)
+ .into_iter()
+ .enumerate()
+ .map(|(idx, file_set)| {
+ let is_local = self.local_filesets.contains(&idx);
+ if is_local {
+ SourceRoot::new_local(file_set)
+ } else {
+ SourceRoot::new_library(file_set)
+ }
+ })
+ .collect()
+ }
+}
+
+/// Load the proc-macros for the given lib path, replacing all expanders whose names are in `dummy_replace`
+/// with an identity dummy expander.
+pub(crate) fn load_proc_macro(
+ server: Result<&ProcMacroServer, &str>,
+ path: &AbsPath,
+ dummy_replace: &[Box<str>],
+) -> ProcMacroLoadResult {
+ let res: Result<Vec<_>, String> = (|| {
+ let dylib = MacroDylib::new(path.to_path_buf())
+ .map_err(|io| format!("Proc-macro dylib loading failed: {io}"))?;
+ let server = server.map_err(ToOwned::to_owned)?;
+ let vec = server.load_dylib(dylib).map_err(|e| format!("{e}"))?;
+ if vec.is_empty() {
+ return Err("proc macro library returned no proc macros".to_string());
+ }
+ Ok(vec
+ .into_iter()
+ .map(|expander| expander_to_proc_macro(expander, dummy_replace))
+ .collect())
+ })();
+ return match res {
+ Ok(proc_macros) => {
+ tracing::info!(
+ "Loaded proc-macros for {}: {:?}",
+ path.display(),
+ proc_macros.iter().map(|it| it.name.clone()).collect::<Vec<_>>()
+ );
+ Ok(proc_macros)
+ }
+ Err(e) => {
+ tracing::warn!("proc-macro loading for {} failed: {e}", path.display());
+ Err(e)
+ }
+ };
+
+ fn expander_to_proc_macro(
+ expander: proc_macro_api::ProcMacro,
+ dummy_replace: &[Box<str>],
+ ) -> ProcMacro {
+ let name = SmolStr::from(expander.name());
+ let kind = match expander.kind() {
+ proc_macro_api::ProcMacroKind::CustomDerive => ProcMacroKind::CustomDerive,
+ proc_macro_api::ProcMacroKind::FuncLike => ProcMacroKind::FuncLike,
+ proc_macro_api::ProcMacroKind::Attr => ProcMacroKind::Attr,
+ };
+ let expander: Arc<dyn ProcMacroExpander> =
+ if dummy_replace.iter().any(|replace| &**replace == name) {
+ match kind {
+ ProcMacroKind::Attr => Arc::new(IdentityExpander),
+ _ => Arc::new(EmptyExpander),
+ }
+ } else {
+ Arc::new(Expander(expander))
+ };
+ ProcMacro { name, kind, expander }
+ }
+
+ #[derive(Debug)]
+ struct Expander(proc_macro_api::ProcMacro);
+
+ impl ProcMacroExpander for Expander {
+ fn expand(
+ &self,
+ subtree: &tt::Subtree,
+ attrs: Option<&tt::Subtree>,
+ env: &Env,
+ ) -> Result<tt::Subtree, ProcMacroExpansionError> {
+ let env = env.iter().map(|(k, v)| (k.to_string(), v.to_string())).collect();
+ match self.0.expand(subtree, attrs, env) {
+ Ok(Ok(subtree)) => Ok(subtree),
+ Ok(Err(err)) => Err(ProcMacroExpansionError::Panic(err.0)),
+ Err(err) => Err(ProcMacroExpansionError::System(err.to_string())),
+ }
+ }
+ }
+
+ /// Dummy identity expander, used for attribute proc-macros that are deliberately ignored by the user.
+ #[derive(Debug)]
+ struct IdentityExpander;
+
+ impl ProcMacroExpander for IdentityExpander {
+ fn expand(
+ &self,
+ subtree: &tt::Subtree,
+ _: Option<&tt::Subtree>,
+ _: &Env,
+ ) -> Result<tt::Subtree, ProcMacroExpansionError> {
+ Ok(subtree.clone())
+ }
+ }
+
+ /// Empty expander, used for proc-macros that are deliberately ignored by the user.
+ #[derive(Debug)]
+ struct EmptyExpander;
+
+ impl ProcMacroExpander for EmptyExpander {
+ fn expand(
+ &self,
+ _: &tt::Subtree,
+ _: Option<&tt::Subtree>,
+ _: &Env,
+ ) -> Result<tt::Subtree, ProcMacroExpansionError> {
+ Ok(tt::Subtree::default())
+ }
+ }
+}
+
+pub(crate) fn should_refresh_for_change(path: &AbsPath, change_kind: ChangeKind) -> bool {
+ const IMPLICIT_TARGET_FILES: &[&str] = &["build.rs", "src/main.rs", "src/lib.rs"];
+ const IMPLICIT_TARGET_DIRS: &[&str] = &["src/bin", "examples", "tests", "benches"];
+
+ let file_name = match path.file_name().unwrap_or_default().to_str() {
+ Some(it) => it,
+ None => return false,
+ };
+
+ if let "Cargo.toml" | "Cargo.lock" = file_name {
+ return true;
+ }
+ if change_kind == ChangeKind::Modify {
+ return false;
+ }
+
+ // .cargo/config{.toml}
+ if path.extension().unwrap_or_default() != "rs" {
+ let is_cargo_config = matches!(file_name, "config.toml" | "config")
+ && path.parent().map(|parent| parent.as_ref().ends_with(".cargo")).unwrap_or(false);
+ return is_cargo_config;
+ }
+
+ if IMPLICIT_TARGET_FILES.iter().any(|it| path.as_ref().ends_with(it)) {
+ return true;
+ }
+ let parent = match path.parent() {
+ Some(it) => it,
+ None => return false,
+ };
+ if IMPLICIT_TARGET_DIRS.iter().any(|it| parent.as_ref().ends_with(it)) {
+ return true;
+ }
+ if file_name == "main.rs" {
+ let grand_parent = match parent.parent() {
+ Some(it) => it,
+ None => return false,
+ };
+ if IMPLICIT_TARGET_DIRS.iter().any(|it| grand_parent.as_ref().ends_with(it)) {
+ return true;
+ }
+ }
+ false
+}
--- /dev/null
- AnnotationKind::HasImpls { file_id, data } => {
+//! Conversion of rust-analyzer specific types to lsp_types equivalents.
+use std::{
+ iter::once,
+ path,
+ sync::atomic::{AtomicU32, Ordering},
+};
+
+use ide::{
+ Annotation, AnnotationKind, Assist, AssistKind, Cancellable, CompletionItem,
+ CompletionItemKind, CompletionRelevance, Documentation, FileId, FileRange, FileSystemEdit,
+ Fold, FoldKind, Highlight, HlMod, HlOperator, HlPunct, HlRange, HlTag, Indel, InlayHint,
+ InlayHintLabel, InlayKind, Markup, NavigationTarget, ReferenceCategory, RenameError, Runnable,
+ Severity, SignatureHelp, SourceChange, StructureNodeKind, SymbolKind, TextEdit, TextRange,
+ TextSize,
+};
+use itertools::Itertools;
+use serde_json::to_value;
+use vfs::AbsPath;
+
+use crate::{
+ cargo_target_spec::CargoTargetSpec,
+ config::{CallInfoConfig, Config},
+ global_state::GlobalStateSnapshot,
+ line_index::{LineEndings, LineIndex, OffsetEncoding},
+ lsp_ext,
+ lsp_utils::invalid_params_error,
+ semantic_tokens, Result,
+};
+
+pub(crate) fn position(line_index: &LineIndex, offset: TextSize) -> lsp_types::Position {
+ let line_col = line_index.index.line_col(offset);
+ match line_index.encoding {
+ OffsetEncoding::Utf8 => lsp_types::Position::new(line_col.line, line_col.col),
+ OffsetEncoding::Utf16 => {
+ let line_col = line_index.index.to_utf16(line_col);
+ lsp_types::Position::new(line_col.line, line_col.col)
+ }
+ }
+}
+
+pub(crate) fn range(line_index: &LineIndex, range: TextRange) -> lsp_types::Range {
+ let start = position(line_index, range.start());
+ let end = position(line_index, range.end());
+ lsp_types::Range::new(start, end)
+}
+
+pub(crate) fn symbol_kind(symbol_kind: SymbolKind) -> lsp_types::SymbolKind {
+ match symbol_kind {
+ SymbolKind::Function => lsp_types::SymbolKind::FUNCTION,
+ SymbolKind::Struct => lsp_types::SymbolKind::STRUCT,
+ SymbolKind::Enum => lsp_types::SymbolKind::ENUM,
+ SymbolKind::Variant => lsp_types::SymbolKind::ENUM_MEMBER,
+ SymbolKind::Trait => lsp_types::SymbolKind::INTERFACE,
+ SymbolKind::Macro
+ | SymbolKind::BuiltinAttr
+ | SymbolKind::Attribute
+ | SymbolKind::Derive
+ | SymbolKind::DeriveHelper => lsp_types::SymbolKind::FUNCTION,
+ SymbolKind::Module | SymbolKind::ToolModule => lsp_types::SymbolKind::MODULE,
+ SymbolKind::TypeAlias | SymbolKind::TypeParam | SymbolKind::SelfType => {
+ lsp_types::SymbolKind::TYPE_PARAMETER
+ }
+ SymbolKind::Field => lsp_types::SymbolKind::FIELD,
+ SymbolKind::Static => lsp_types::SymbolKind::CONSTANT,
+ SymbolKind::Const => lsp_types::SymbolKind::CONSTANT,
+ SymbolKind::ConstParam => lsp_types::SymbolKind::CONSTANT,
+ SymbolKind::Impl => lsp_types::SymbolKind::OBJECT,
+ SymbolKind::Local
+ | SymbolKind::SelfParam
+ | SymbolKind::LifetimeParam
+ | SymbolKind::ValueParam
+ | SymbolKind::Label => lsp_types::SymbolKind::VARIABLE,
+ SymbolKind::Union => lsp_types::SymbolKind::STRUCT,
+ }
+}
+
+pub(crate) fn structure_node_kind(kind: StructureNodeKind) -> lsp_types::SymbolKind {
+ match kind {
+ StructureNodeKind::SymbolKind(symbol) => symbol_kind(symbol),
+ StructureNodeKind::Region => lsp_types::SymbolKind::NAMESPACE,
+ }
+}
+
+pub(crate) fn document_highlight_kind(
+ category: ReferenceCategory,
+) -> Option<lsp_types::DocumentHighlightKind> {
+ match category {
+ ReferenceCategory::Read => Some(lsp_types::DocumentHighlightKind::READ),
+ ReferenceCategory::Write => Some(lsp_types::DocumentHighlightKind::WRITE),
+ ReferenceCategory::Import => None,
+ }
+}
+
+pub(crate) fn diagnostic_severity(severity: Severity) -> lsp_types::DiagnosticSeverity {
+ match severity {
+ Severity::Error => lsp_types::DiagnosticSeverity::ERROR,
+ Severity::WeakWarning => lsp_types::DiagnosticSeverity::HINT,
+ }
+}
+
+pub(crate) fn documentation(documentation: Documentation) -> lsp_types::Documentation {
+ let value = crate::markdown::format_docs(documentation.as_str());
+ let markup_content = lsp_types::MarkupContent { kind: lsp_types::MarkupKind::Markdown, value };
+ lsp_types::Documentation::MarkupContent(markup_content)
+}
+
+pub(crate) fn completion_item_kind(
+ completion_item_kind: CompletionItemKind,
+) -> lsp_types::CompletionItemKind {
+ match completion_item_kind {
+ CompletionItemKind::Binding => lsp_types::CompletionItemKind::VARIABLE,
+ CompletionItemKind::BuiltinType => lsp_types::CompletionItemKind::STRUCT,
+ CompletionItemKind::InferredType => lsp_types::CompletionItemKind::SNIPPET,
+ CompletionItemKind::Keyword => lsp_types::CompletionItemKind::KEYWORD,
+ CompletionItemKind::Method => lsp_types::CompletionItemKind::METHOD,
+ CompletionItemKind::Snippet => lsp_types::CompletionItemKind::SNIPPET,
+ CompletionItemKind::UnresolvedReference => lsp_types::CompletionItemKind::REFERENCE,
+ CompletionItemKind::SymbolKind(symbol) => match symbol {
+ SymbolKind::Attribute => lsp_types::CompletionItemKind::FUNCTION,
+ SymbolKind::Const => lsp_types::CompletionItemKind::CONSTANT,
+ SymbolKind::ConstParam => lsp_types::CompletionItemKind::TYPE_PARAMETER,
+ SymbolKind::Derive => lsp_types::CompletionItemKind::FUNCTION,
+ SymbolKind::DeriveHelper => lsp_types::CompletionItemKind::FUNCTION,
+ SymbolKind::Enum => lsp_types::CompletionItemKind::ENUM,
+ SymbolKind::Field => lsp_types::CompletionItemKind::FIELD,
+ SymbolKind::Function => lsp_types::CompletionItemKind::FUNCTION,
+ SymbolKind::Impl => lsp_types::CompletionItemKind::TEXT,
+ SymbolKind::Label => lsp_types::CompletionItemKind::VARIABLE,
+ SymbolKind::LifetimeParam => lsp_types::CompletionItemKind::TYPE_PARAMETER,
+ SymbolKind::Local => lsp_types::CompletionItemKind::VARIABLE,
+ SymbolKind::Macro => lsp_types::CompletionItemKind::FUNCTION,
+ SymbolKind::Module => lsp_types::CompletionItemKind::MODULE,
+ SymbolKind::SelfParam => lsp_types::CompletionItemKind::VALUE,
+ SymbolKind::SelfType => lsp_types::CompletionItemKind::TYPE_PARAMETER,
+ SymbolKind::Static => lsp_types::CompletionItemKind::VALUE,
+ SymbolKind::Struct => lsp_types::CompletionItemKind::STRUCT,
+ SymbolKind::Trait => lsp_types::CompletionItemKind::INTERFACE,
+ SymbolKind::TypeAlias => lsp_types::CompletionItemKind::STRUCT,
+ SymbolKind::TypeParam => lsp_types::CompletionItemKind::TYPE_PARAMETER,
+ SymbolKind::Union => lsp_types::CompletionItemKind::STRUCT,
+ SymbolKind::ValueParam => lsp_types::CompletionItemKind::VALUE,
+ SymbolKind::Variant => lsp_types::CompletionItemKind::ENUM_MEMBER,
+ SymbolKind::BuiltinAttr => lsp_types::CompletionItemKind::FUNCTION,
+ SymbolKind::ToolModule => lsp_types::CompletionItemKind::MODULE,
+ },
+ }
+}
+
+pub(crate) fn text_edit(line_index: &LineIndex, indel: Indel) -> lsp_types::TextEdit {
+ let range = range(line_index, indel.delete);
+ let new_text = match line_index.endings {
+ LineEndings::Unix => indel.insert,
+ LineEndings::Dos => indel.insert.replace('\n', "\r\n"),
+ };
+ lsp_types::TextEdit { range, new_text }
+}
+
+pub(crate) fn completion_text_edit(
+ line_index: &LineIndex,
+ insert_replace_support: Option<lsp_types::Position>,
+ indel: Indel,
+) -> lsp_types::CompletionTextEdit {
+ let text_edit = text_edit(line_index, indel);
+ match insert_replace_support {
+ Some(cursor_pos) => lsp_types::InsertReplaceEdit {
+ new_text: text_edit.new_text,
+ insert: lsp_types::Range { start: text_edit.range.start, end: cursor_pos },
+ replace: text_edit.range,
+ }
+ .into(),
+ None => text_edit.into(),
+ }
+}
+
+pub(crate) fn snippet_text_edit(
+ line_index: &LineIndex,
+ is_snippet: bool,
+ indel: Indel,
+) -> lsp_ext::SnippetTextEdit {
+ let text_edit = text_edit(line_index, indel);
+ let insert_text_format =
+ if is_snippet { Some(lsp_types::InsertTextFormat::SNIPPET) } else { None };
+ lsp_ext::SnippetTextEdit {
+ range: text_edit.range,
+ new_text: text_edit.new_text,
+ insert_text_format,
+ annotation_id: None,
+ }
+}
+
+pub(crate) fn text_edit_vec(
+ line_index: &LineIndex,
+ text_edit: TextEdit,
+) -> Vec<lsp_types::TextEdit> {
+ text_edit.into_iter().map(|indel| self::text_edit(line_index, indel)).collect()
+}
+
+pub(crate) fn snippet_text_edit_vec(
+ line_index: &LineIndex,
+ is_snippet: bool,
+ text_edit: TextEdit,
+) -> Vec<lsp_ext::SnippetTextEdit> {
+ text_edit
+ .into_iter()
+ .map(|indel| self::snippet_text_edit(line_index, is_snippet, indel))
+ .collect()
+}
+
+pub(crate) fn completion_items(
+ config: &Config,
+ line_index: &LineIndex,
+ tdpp: lsp_types::TextDocumentPositionParams,
+ items: Vec<CompletionItem>,
+) -> Vec<lsp_types::CompletionItem> {
+ let max_relevance = items.iter().map(|it| it.relevance().score()).max().unwrap_or_default();
+ let mut res = Vec::with_capacity(items.len());
+ for item in items {
+ completion_item(&mut res, config, line_index, &tdpp, max_relevance, item)
+ }
+ res
+}
+
+fn completion_item(
+ acc: &mut Vec<lsp_types::CompletionItem>,
+ config: &Config,
+ line_index: &LineIndex,
+ tdpp: &lsp_types::TextDocumentPositionParams,
+ max_relevance: u32,
+ item: CompletionItem,
+) {
+ let insert_replace_support = config.insert_replace_support().then(|| tdpp.position);
+ let mut additional_text_edits = Vec::new();
+
+ // LSP does not allow arbitrary edits in completion, so we have to do a
+ // non-trivial mapping here.
+ let text_edit = {
+ let mut text_edit = None;
+ let source_range = item.source_range();
+ for indel in item.text_edit().iter() {
+ if indel.delete.contains_range(source_range) {
+ text_edit = Some(if indel.delete == source_range {
+ self::completion_text_edit(line_index, insert_replace_support, indel.clone())
+ } else {
+ assert!(source_range.end() == indel.delete.end());
+ let range1 = TextRange::new(indel.delete.start(), source_range.start());
+ let range2 = source_range;
+ let indel1 = Indel::replace(range1, String::new());
+ let indel2 = Indel::replace(range2, indel.insert.clone());
+ additional_text_edits.push(self::text_edit(line_index, indel1));
+ self::completion_text_edit(line_index, insert_replace_support, indel2)
+ })
+ } else {
+ assert!(source_range.intersect(indel.delete).is_none());
+ let text_edit = self::text_edit(line_index, indel.clone());
+ additional_text_edits.push(text_edit);
+ }
+ }
+ text_edit.unwrap()
+ };
+
+ let insert_text_format = item.is_snippet().then(|| lsp_types::InsertTextFormat::SNIPPET);
+ let tags = item.deprecated().then(|| vec![lsp_types::CompletionItemTag::DEPRECATED]);
+ let command = if item.trigger_call_info() && config.client_commands().trigger_parameter_hints {
+ Some(command::trigger_parameter_hints())
+ } else {
+ None
+ };
+
+ let mut lsp_item = lsp_types::CompletionItem {
+ label: item.label().to_string(),
+ detail: item.detail().map(|it| it.to_string()),
+ filter_text: Some(item.lookup().to_string()),
+ kind: Some(completion_item_kind(item.kind())),
+ text_edit: Some(text_edit),
+ additional_text_edits: Some(additional_text_edits),
+ documentation: item.documentation().map(documentation),
+ deprecated: Some(item.deprecated()),
+ tags,
+ command,
+ insert_text_format,
+ ..Default::default()
+ };
+
+ if config.completion_label_details_support() {
+ lsp_item.label_details = Some(lsp_types::CompletionItemLabelDetails {
+ detail: None,
+ description: lsp_item.detail.clone(),
+ });
+ }
+
+ set_score(&mut lsp_item, max_relevance, item.relevance());
+
+ if config.completion().enable_imports_on_the_fly {
+ if let imports @ [_, ..] = item.imports_to_add() {
+ let imports: Vec<_> = imports
+ .iter()
+ .filter_map(|import_edit| {
+ let import_path = &import_edit.import_path;
+ let import_name = import_path.segments().last()?;
+ Some(lsp_ext::CompletionImport {
+ full_import_path: import_path.to_string(),
+ imported_name: import_name.to_string(),
+ })
+ })
+ .collect();
+ if !imports.is_empty() {
+ let data = lsp_ext::CompletionResolveData { position: tdpp.clone(), imports };
+ lsp_item.data = Some(to_value(data).unwrap());
+ }
+ }
+ }
+
+ if let Some((mutability, offset, relevance)) = item.ref_match() {
+ let mut lsp_item_with_ref = lsp_item.clone();
+ set_score(&mut lsp_item_with_ref, max_relevance, relevance);
+ lsp_item_with_ref.label =
+ format!("&{}{}", mutability.as_keyword_for_ref(), lsp_item_with_ref.label);
+ lsp_item_with_ref.additional_text_edits.get_or_insert_with(Default::default).push(
+ self::text_edit(
+ line_index,
+ Indel::insert(offset, format!("&{}", mutability.as_keyword_for_ref())),
+ ),
+ );
+
+ acc.push(lsp_item_with_ref);
+ };
+
+ acc.push(lsp_item);
+
+ fn set_score(
+ res: &mut lsp_types::CompletionItem,
+ max_relevance: u32,
+ relevance: CompletionRelevance,
+ ) {
+ if relevance.is_relevant() && relevance.score() == max_relevance {
+ res.preselect = Some(true);
+ }
+ // The relevance needs to be inverted to come up with a sort score
+ // because the client will sort ascending.
+ let sort_score = relevance.score() ^ 0xFF_FF_FF_FF;
+ // Zero pad the string to ensure values can be properly sorted
+ // by the client. Hex format is used because it is easier to
+ // visually compare very large values, which the sort text
+ // tends to be since it is the opposite of the score.
+ res.sort_text = Some(format!("{:08x}", sort_score));
+ }
+}
+
+pub(crate) fn signature_help(
+ call_info: SignatureHelp,
+ config: CallInfoConfig,
+ label_offsets: bool,
+) -> lsp_types::SignatureHelp {
+ let (label, parameters) = match (config.params_only, label_offsets) {
+ (concise, false) => {
+ let params = call_info
+ .parameter_labels()
+ .map(|label| lsp_types::ParameterInformation {
+ label: lsp_types::ParameterLabel::Simple(label.to_string()),
+ documentation: None,
+ })
+ .collect::<Vec<_>>();
+ let label =
+ if concise { call_info.parameter_labels().join(", ") } else { call_info.signature };
+ (label, params)
+ }
+ (false, true) => {
+ let params = call_info
+ .parameter_ranges()
+ .iter()
+ .map(|it| {
+ let start = call_info.signature[..it.start().into()].chars().count() as u32;
+ let end = call_info.signature[..it.end().into()].chars().count() as u32;
+ [start, end]
+ })
+ .map(|label_offsets| lsp_types::ParameterInformation {
+ label: lsp_types::ParameterLabel::LabelOffsets(label_offsets),
+ documentation: None,
+ })
+ .collect::<Vec<_>>();
+ (call_info.signature, params)
+ }
+ (true, true) => {
+ let mut params = Vec::new();
+ let mut label = String::new();
+ let mut first = true;
+ for param in call_info.parameter_labels() {
+ if !first {
+ label.push_str(", ");
+ }
+ first = false;
+ let start = label.chars().count() as u32;
+ label.push_str(param);
+ let end = label.chars().count() as u32;
+ params.push(lsp_types::ParameterInformation {
+ label: lsp_types::ParameterLabel::LabelOffsets([start, end]),
+ documentation: None,
+ });
+ }
+
+ (label, params)
+ }
+ };
+
+ let documentation = call_info.doc.filter(|_| config.docs).map(|doc| {
+ lsp_types::Documentation::MarkupContent(lsp_types::MarkupContent {
+ kind: lsp_types::MarkupKind::Markdown,
+ value: doc,
+ })
+ });
+
+ let active_parameter = call_info.active_parameter.map(|it| it as u32);
+
+ let signature = lsp_types::SignatureInformation {
+ label,
+ documentation,
+ parameters: Some(parameters),
+ active_parameter,
+ };
+ lsp_types::SignatureHelp {
+ signatures: vec![signature],
+ active_signature: Some(0),
+ active_parameter,
+ }
+}
+
+pub(crate) fn inlay_hint(
+ snap: &GlobalStateSnapshot,
+ line_index: &LineIndex,
+ render_colons: bool,
+ mut inlay_hint: InlayHint,
+) -> Result<lsp_types::InlayHint> {
+ match inlay_hint.kind {
+ InlayKind::ParameterHint if render_colons => inlay_hint.label.append_str(":"),
+ InlayKind::TypeHint if render_colons => inlay_hint.label.prepend_str(": "),
+ InlayKind::ClosureReturnTypeHint => inlay_hint.label.prepend_str(" -> "),
+ _ => {}
+ }
+
+ Ok(lsp_types::InlayHint {
+ position: match inlay_hint.kind {
+ // before annotated thing
+ InlayKind::ParameterHint
+ | InlayKind::ImplicitReborrowHint
+ | InlayKind::BindingModeHint => position(line_index, inlay_hint.range.start()),
+ // after annotated thing
+ InlayKind::ClosureReturnTypeHint
+ | InlayKind::TypeHint
+ | InlayKind::ChainingHint
+ | InlayKind::GenericParamListHint
+ | InlayKind::LifetimeHint
+ | InlayKind::ClosingBraceHint => position(line_index, inlay_hint.range.end()),
+ },
+ padding_left: Some(match inlay_hint.kind {
+ InlayKind::TypeHint => !render_colons,
+ InlayKind::ChainingHint | InlayKind::ClosingBraceHint => true,
+ InlayKind::BindingModeHint
+ | InlayKind::ClosureReturnTypeHint
+ | InlayKind::GenericParamListHint
+ | InlayKind::ImplicitReborrowHint
+ | InlayKind::LifetimeHint
+ | InlayKind::ParameterHint => false,
+ }),
+ padding_right: Some(match inlay_hint.kind {
+ InlayKind::ChainingHint
+ | InlayKind::ClosureReturnTypeHint
+ | InlayKind::GenericParamListHint
+ | InlayKind::ImplicitReborrowHint
+ | InlayKind::TypeHint
+ | InlayKind::ClosingBraceHint => false,
+ InlayKind::BindingModeHint => inlay_hint.label.as_simple_str() != Some("&"),
+ InlayKind::ParameterHint | InlayKind::LifetimeHint => true,
+ }),
+ kind: match inlay_hint.kind {
+ InlayKind::ParameterHint => Some(lsp_types::InlayHintKind::PARAMETER),
+ InlayKind::ClosureReturnTypeHint | InlayKind::TypeHint | InlayKind::ChainingHint => {
+ Some(lsp_types::InlayHintKind::TYPE)
+ }
+ InlayKind::BindingModeHint
+ | InlayKind::GenericParamListHint
+ | InlayKind::LifetimeHint
+ | InlayKind::ImplicitReborrowHint
+ | InlayKind::ClosingBraceHint => None,
+ },
+ text_edits: None,
+ data: (|| match inlay_hint.tooltip {
+ Some(ide::InlayTooltip::HoverOffset(file_id, offset)) => {
+ let uri = url(snap, file_id);
+ let line_index = snap.file_line_index(file_id).ok()?;
+
+ let text_document = lsp_types::TextDocumentIdentifier { uri };
+ to_value(lsp_ext::InlayHintResolveData {
+ text_document,
+ position: lsp_ext::PositionOrRange::Position(position(&line_index, offset)),
+ })
+ .ok()
+ }
+ Some(ide::InlayTooltip::HoverRanged(file_id, text_range)) => {
+ let uri = url(snap, file_id);
+ let text_document = lsp_types::TextDocumentIdentifier { uri };
+ let line_index = snap.file_line_index(file_id).ok()?;
+ to_value(lsp_ext::InlayHintResolveData {
+ text_document,
+ position: lsp_ext::PositionOrRange::Range(range(&line_index, text_range)),
+ })
+ .ok()
+ }
+ _ => None,
+ })(),
+ tooltip: Some(match inlay_hint.tooltip {
+ Some(ide::InlayTooltip::String(s)) => lsp_types::InlayHintTooltip::String(s),
+ _ => lsp_types::InlayHintTooltip::String(inlay_hint.label.to_string()),
+ }),
+ label: inlay_hint_label(snap, inlay_hint.label)?,
+ })
+}
+
+fn inlay_hint_label(
+ snap: &GlobalStateSnapshot,
+ label: InlayHintLabel,
+) -> Result<lsp_types::InlayHintLabel> {
+ Ok(match label.as_simple_str() {
+ Some(s) => lsp_types::InlayHintLabel::String(s.into()),
+ None => lsp_types::InlayHintLabel::LabelParts(
+ label
+ .parts
+ .into_iter()
+ .map(|part| {
+ Ok(lsp_types::InlayHintLabelPart {
+ value: part.text,
+ tooltip: None,
+ location: part
+ .linked_location
+ .map(|range| location(snap, range))
+ .transpose()?,
+ command: None,
+ })
+ })
+ .collect::<Result<Vec<_>>>()?,
+ ),
+ })
+}
+
+static TOKEN_RESULT_COUNTER: AtomicU32 = AtomicU32::new(1);
+
+pub(crate) fn semantic_tokens(
+ text: &str,
+ line_index: &LineIndex,
+ highlights: Vec<HlRange>,
+) -> lsp_types::SemanticTokens {
+ let id = TOKEN_RESULT_COUNTER.fetch_add(1, Ordering::SeqCst).to_string();
+ let mut builder = semantic_tokens::SemanticTokensBuilder::new(id);
+
+ for highlight_range in highlights {
+ if highlight_range.highlight.is_empty() {
+ continue;
+ }
+
+ let (ty, mods) = semantic_token_type_and_modifiers(highlight_range.highlight);
+ let token_index = semantic_tokens::type_index(ty);
+ let modifier_bitset = mods.0;
+
+ for mut text_range in line_index.index.lines(highlight_range.range) {
+ if text[text_range].ends_with('\n') {
+ text_range =
+ TextRange::new(text_range.start(), text_range.end() - TextSize::of('\n'));
+ }
+ let range = range(line_index, text_range);
+ builder.push(range, token_index, modifier_bitset);
+ }
+ }
+
+ builder.build()
+}
+
+pub(crate) fn semantic_token_delta(
+ previous: &lsp_types::SemanticTokens,
+ current: &lsp_types::SemanticTokens,
+) -> lsp_types::SemanticTokensDelta {
+ let result_id = current.result_id.clone();
+ let edits = semantic_tokens::diff_tokens(&previous.data, ¤t.data);
+ lsp_types::SemanticTokensDelta { result_id, edits }
+}
+
+fn semantic_token_type_and_modifiers(
+ highlight: Highlight,
+) -> (lsp_types::SemanticTokenType, semantic_tokens::ModifierSet) {
+ let mut mods = semantic_tokens::ModifierSet::default();
+ let type_ = match highlight.tag {
+ HlTag::Symbol(symbol) => match symbol {
+ SymbolKind::Attribute => semantic_tokens::DECORATOR,
+ SymbolKind::Derive => semantic_tokens::DERIVE,
+ SymbolKind::DeriveHelper => semantic_tokens::DERIVE_HELPER,
+ SymbolKind::Module => semantic_tokens::NAMESPACE,
+ SymbolKind::Impl => semantic_tokens::TYPE_ALIAS,
+ SymbolKind::Field => semantic_tokens::PROPERTY,
+ SymbolKind::TypeParam => semantic_tokens::TYPE_PARAMETER,
+ SymbolKind::ConstParam => semantic_tokens::CONST_PARAMETER,
+ SymbolKind::LifetimeParam => semantic_tokens::LIFETIME,
+ SymbolKind::Label => semantic_tokens::LABEL,
+ SymbolKind::ValueParam => semantic_tokens::PARAMETER,
+ SymbolKind::SelfParam => semantic_tokens::SELF_KEYWORD,
+ SymbolKind::SelfType => semantic_tokens::SELF_TYPE_KEYWORD,
+ SymbolKind::Local => semantic_tokens::VARIABLE,
+ SymbolKind::Function => {
+ if highlight.mods.contains(HlMod::Associated) {
+ semantic_tokens::METHOD
+ } else {
+ semantic_tokens::FUNCTION
+ }
+ }
+ SymbolKind::Const => {
+ mods |= semantic_tokens::CONSTANT;
+ mods |= semantic_tokens::STATIC;
+ semantic_tokens::VARIABLE
+ }
+ SymbolKind::Static => {
+ mods |= semantic_tokens::STATIC;
+ semantic_tokens::VARIABLE
+ }
+ SymbolKind::Struct => semantic_tokens::STRUCT,
+ SymbolKind::Enum => semantic_tokens::ENUM,
+ SymbolKind::Variant => semantic_tokens::ENUM_MEMBER,
+ SymbolKind::Union => semantic_tokens::UNION,
+ SymbolKind::TypeAlias => semantic_tokens::TYPE_ALIAS,
+ SymbolKind::Trait => semantic_tokens::INTERFACE,
+ SymbolKind::Macro => semantic_tokens::MACRO,
+ SymbolKind::BuiltinAttr => semantic_tokens::BUILTIN_ATTRIBUTE,
+ SymbolKind::ToolModule => semantic_tokens::TOOL_MODULE,
+ },
+ HlTag::AttributeBracket => semantic_tokens::ATTRIBUTE_BRACKET,
+ HlTag::BoolLiteral => semantic_tokens::BOOLEAN,
+ HlTag::BuiltinType => semantic_tokens::BUILTIN_TYPE,
+ HlTag::ByteLiteral | HlTag::NumericLiteral => semantic_tokens::NUMBER,
+ HlTag::CharLiteral => semantic_tokens::CHAR,
+ HlTag::Comment => semantic_tokens::COMMENT,
+ HlTag::EscapeSequence => semantic_tokens::ESCAPE_SEQUENCE,
+ HlTag::FormatSpecifier => semantic_tokens::FORMAT_SPECIFIER,
+ HlTag::Keyword => semantic_tokens::KEYWORD,
+ HlTag::None => semantic_tokens::GENERIC,
+ HlTag::Operator(op) => match op {
+ HlOperator::Bitwise => semantic_tokens::BITWISE,
+ HlOperator::Arithmetic => semantic_tokens::ARITHMETIC,
+ HlOperator::Logical => semantic_tokens::LOGICAL,
+ HlOperator::Comparison => semantic_tokens::COMPARISON,
+ HlOperator::Other => semantic_tokens::OPERATOR,
+ },
+ HlTag::StringLiteral => semantic_tokens::STRING,
+ HlTag::UnresolvedReference => semantic_tokens::UNRESOLVED_REFERENCE,
+ HlTag::Punctuation(punct) => match punct {
+ HlPunct::Bracket => semantic_tokens::BRACKET,
+ HlPunct::Brace => semantic_tokens::BRACE,
+ HlPunct::Parenthesis => semantic_tokens::PARENTHESIS,
+ HlPunct::Angle => semantic_tokens::ANGLE,
+ HlPunct::Comma => semantic_tokens::COMMA,
+ HlPunct::Dot => semantic_tokens::DOT,
+ HlPunct::Colon => semantic_tokens::COLON,
+ HlPunct::Semi => semantic_tokens::SEMICOLON,
+ HlPunct::Other => semantic_tokens::PUNCTUATION,
+ HlPunct::MacroBang => semantic_tokens::MACRO_BANG,
+ },
+ };
+
+ for modifier in highlight.mods.iter() {
+ let modifier = match modifier {
+ HlMod::Associated => continue,
+ HlMod::Async => semantic_tokens::ASYNC,
+ HlMod::Attribute => semantic_tokens::ATTRIBUTE_MODIFIER,
+ HlMod::Callable => semantic_tokens::CALLABLE,
+ HlMod::Consuming => semantic_tokens::CONSUMING,
+ HlMod::ControlFlow => semantic_tokens::CONTROL_FLOW,
+ HlMod::CrateRoot => semantic_tokens::CRATE_ROOT,
+ HlMod::DefaultLibrary => semantic_tokens::DEFAULT_LIBRARY,
+ HlMod::Definition => semantic_tokens::DECLARATION,
+ HlMod::Documentation => semantic_tokens::DOCUMENTATION,
+ HlMod::Injected => semantic_tokens::INJECTED,
+ HlMod::IntraDocLink => semantic_tokens::INTRA_DOC_LINK,
+ HlMod::Library => semantic_tokens::LIBRARY,
+ HlMod::Mutable => semantic_tokens::MUTABLE,
+ HlMod::Public => semantic_tokens::PUBLIC,
+ HlMod::Reference => semantic_tokens::REFERENCE,
+ HlMod::Static => semantic_tokens::STATIC,
+ HlMod::Trait => semantic_tokens::TRAIT_MODIFIER,
+ HlMod::Unsafe => semantic_tokens::UNSAFE,
+ };
+ mods |= modifier;
+ }
+
+ (type_, mods)
+}
+
+pub(crate) fn folding_range(
+ text: &str,
+ line_index: &LineIndex,
+ line_folding_only: bool,
+ fold: Fold,
+) -> lsp_types::FoldingRange {
+ let kind = match fold.kind {
+ FoldKind::Comment => Some(lsp_types::FoldingRangeKind::Comment),
+ FoldKind::Imports => Some(lsp_types::FoldingRangeKind::Imports),
+ FoldKind::Region => Some(lsp_types::FoldingRangeKind::Region),
+ FoldKind::Mods
+ | FoldKind::Block
+ | FoldKind::ArgList
+ | FoldKind::Consts
+ | FoldKind::Statics
+ | FoldKind::WhereClause
+ | FoldKind::ReturnType
+ | FoldKind::Array
+ | FoldKind::MatchArm => None,
+ };
+
+ let range = range(line_index, fold.range);
+
+ if line_folding_only {
+ // Clients with line_folding_only == true (such as VSCode) will fold the whole end line
+ // even if it contains text not in the folding range. To prevent that we exclude
+ // range.end.line from the folding region if there is more text after range.end
+ // on the same line.
+ let has_more_text_on_end_line = text[TextRange::new(fold.range.end(), TextSize::of(text))]
+ .chars()
+ .take_while(|it| *it != '\n')
+ .any(|it| !it.is_whitespace());
+
+ let end_line = if has_more_text_on_end_line {
+ range.end.line.saturating_sub(1)
+ } else {
+ range.end.line
+ };
+
+ lsp_types::FoldingRange {
+ start_line: range.start.line,
+ start_character: None,
+ end_line,
+ end_character: None,
+ kind,
+ }
+ } else {
+ lsp_types::FoldingRange {
+ start_line: range.start.line,
+ start_character: Some(range.start.character),
+ end_line: range.end.line,
+ end_character: Some(range.end.character),
+ kind,
+ }
+ }
+}
+
+pub(crate) fn url(snap: &GlobalStateSnapshot, file_id: FileId) -> lsp_types::Url {
+ snap.file_id_to_url(file_id)
+}
+
+/// Returns a `Url` object from a given path, will lowercase drive letters if present.
+/// This will only happen when processing windows paths.
+///
+/// When processing non-windows path, this is essentially the same as `Url::from_file_path`.
+pub(crate) fn url_from_abs_path(path: &AbsPath) -> lsp_types::Url {
+ let url = lsp_types::Url::from_file_path(path).unwrap();
+ match path.as_ref().components().next() {
+ Some(path::Component::Prefix(prefix))
+ if matches!(prefix.kind(), path::Prefix::Disk(_) | path::Prefix::VerbatimDisk(_)) =>
+ {
+ // Need to lowercase driver letter
+ }
+ _ => return url,
+ }
+
+ let driver_letter_range = {
+ let (scheme, drive_letter, _rest) = match url.as_str().splitn(3, ':').collect_tuple() {
+ Some(it) => it,
+ None => return url,
+ };
+ let start = scheme.len() + ':'.len_utf8();
+ start..(start + drive_letter.len())
+ };
+
+ // Note: lowercasing the `path` itself doesn't help, the `Url::parse`
+ // machinery *also* canonicalizes the drive letter. So, just massage the
+ // string in place.
+ let mut url: String = url.into();
+ url[driver_letter_range].make_ascii_lowercase();
+ lsp_types::Url::parse(&url).unwrap()
+}
+
+pub(crate) fn optional_versioned_text_document_identifier(
+ snap: &GlobalStateSnapshot,
+ file_id: FileId,
+) -> lsp_types::OptionalVersionedTextDocumentIdentifier {
+ let url = url(snap, file_id);
+ let version = snap.url_file_version(&url);
+ lsp_types::OptionalVersionedTextDocumentIdentifier { uri: url, version }
+}
+
+pub(crate) fn location(
+ snap: &GlobalStateSnapshot,
+ frange: FileRange,
+) -> Result<lsp_types::Location> {
+ let url = url(snap, frange.file_id);
+ let line_index = snap.file_line_index(frange.file_id)?;
+ let range = range(&line_index, frange.range);
+ let loc = lsp_types::Location::new(url, range);
+ Ok(loc)
+}
+
+/// Prefer using `location_link`, if the client has the cap.
+pub(crate) fn location_from_nav(
+ snap: &GlobalStateSnapshot,
+ nav: NavigationTarget,
+) -> Result<lsp_types::Location> {
+ let url = url(snap, nav.file_id);
+ let line_index = snap.file_line_index(nav.file_id)?;
+ let range = range(&line_index, nav.full_range);
+ let loc = lsp_types::Location::new(url, range);
+ Ok(loc)
+}
+
+pub(crate) fn location_link(
+ snap: &GlobalStateSnapshot,
+ src: Option<FileRange>,
+ target: NavigationTarget,
+) -> Result<lsp_types::LocationLink> {
+ let origin_selection_range = match src {
+ Some(src) => {
+ let line_index = snap.file_line_index(src.file_id)?;
+ let range = range(&line_index, src.range);
+ Some(range)
+ }
+ None => None,
+ };
+ let (target_uri, target_range, target_selection_range) = location_info(snap, target)?;
+ let res = lsp_types::LocationLink {
+ origin_selection_range,
+ target_uri,
+ target_range,
+ target_selection_range,
+ };
+ Ok(res)
+}
+
+fn location_info(
+ snap: &GlobalStateSnapshot,
+ target: NavigationTarget,
+) -> Result<(lsp_types::Url, lsp_types::Range, lsp_types::Range)> {
+ let line_index = snap.file_line_index(target.file_id)?;
+
+ let target_uri = url(snap, target.file_id);
+ let target_range = range(&line_index, target.full_range);
+ let target_selection_range =
+ target.focus_range.map(|it| range(&line_index, it)).unwrap_or(target_range);
+ Ok((target_uri, target_range, target_selection_range))
+}
+
+pub(crate) fn goto_definition_response(
+ snap: &GlobalStateSnapshot,
+ src: Option<FileRange>,
+ targets: Vec<NavigationTarget>,
+) -> Result<lsp_types::GotoDefinitionResponse> {
+ if snap.config.location_link() {
+ let links = targets
+ .into_iter()
+ .map(|nav| location_link(snap, src, nav))
+ .collect::<Result<Vec<_>>>()?;
+ Ok(links.into())
+ } else {
+ let locations = targets
+ .into_iter()
+ .map(|nav| {
+ location(snap, FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() })
+ })
+ .collect::<Result<Vec<_>>>()?;
+ Ok(locations.into())
+ }
+}
+
+fn outside_workspace_annotation_id() -> String {
+ String::from("OutsideWorkspace")
+}
+
+pub(crate) fn snippet_text_document_edit(
+ snap: &GlobalStateSnapshot,
+ is_snippet: bool,
+ file_id: FileId,
+ edit: TextEdit,
+) -> Result<lsp_ext::SnippetTextDocumentEdit> {
+ let text_document = optional_versioned_text_document_identifier(snap, file_id);
+ let line_index = snap.file_line_index(file_id)?;
+ let mut edits: Vec<_> =
+ edit.into_iter().map(|it| snippet_text_edit(&line_index, is_snippet, it)).collect();
+
+ if snap.analysis.is_library_file(file_id)? && snap.config.change_annotation_support() {
+ for edit in &mut edits {
+ edit.annotation_id = Some(outside_workspace_annotation_id())
+ }
+ }
+ Ok(lsp_ext::SnippetTextDocumentEdit { text_document, edits })
+}
+
+pub(crate) fn snippet_text_document_ops(
+ snap: &GlobalStateSnapshot,
+ file_system_edit: FileSystemEdit,
+) -> Cancellable<Vec<lsp_ext::SnippetDocumentChangeOperation>> {
+ let mut ops = Vec::new();
+ match file_system_edit {
+ FileSystemEdit::CreateFile { dst, initial_contents } => {
+ let uri = snap.anchored_path(&dst);
+ let create_file = lsp_types::ResourceOp::Create(lsp_types::CreateFile {
+ uri: uri.clone(),
+ options: None,
+ annotation_id: None,
+ });
+ ops.push(lsp_ext::SnippetDocumentChangeOperation::Op(create_file));
+ if !initial_contents.is_empty() {
+ let text_document =
+ lsp_types::OptionalVersionedTextDocumentIdentifier { uri, version: None };
+ let text_edit = lsp_ext::SnippetTextEdit {
+ range: lsp_types::Range::default(),
+ new_text: initial_contents,
+ insert_text_format: Some(lsp_types::InsertTextFormat::PLAIN_TEXT),
+ annotation_id: None,
+ };
+ let edit_file =
+ lsp_ext::SnippetTextDocumentEdit { text_document, edits: vec![text_edit] };
+ ops.push(lsp_ext::SnippetDocumentChangeOperation::Edit(edit_file));
+ }
+ }
+ FileSystemEdit::MoveFile { src, dst } => {
+ let old_uri = snap.file_id_to_url(src);
+ let new_uri = snap.anchored_path(&dst);
+ let mut rename_file =
+ lsp_types::RenameFile { old_uri, new_uri, options: None, annotation_id: None };
+ if snap.analysis.is_library_file(src).ok() == Some(true)
+ && snap.config.change_annotation_support()
+ {
+ rename_file.annotation_id = Some(outside_workspace_annotation_id())
+ }
+ ops.push(lsp_ext::SnippetDocumentChangeOperation::Op(lsp_types::ResourceOp::Rename(
+ rename_file,
+ )))
+ }
+ FileSystemEdit::MoveDir { src, src_id, dst } => {
+ let old_uri = snap.anchored_path(&src);
+ let new_uri = snap.anchored_path(&dst);
+ let mut rename_file =
+ lsp_types::RenameFile { old_uri, new_uri, options: None, annotation_id: None };
+ if snap.analysis.is_library_file(src_id).ok() == Some(true)
+ && snap.config.change_annotation_support()
+ {
+ rename_file.annotation_id = Some(outside_workspace_annotation_id())
+ }
+ ops.push(lsp_ext::SnippetDocumentChangeOperation::Op(lsp_types::ResourceOp::Rename(
+ rename_file,
+ )))
+ }
+ }
+ Ok(ops)
+}
+
+pub(crate) fn snippet_workspace_edit(
+ snap: &GlobalStateSnapshot,
+ source_change: SourceChange,
+) -> Result<lsp_ext::SnippetWorkspaceEdit> {
+ let mut document_changes: Vec<lsp_ext::SnippetDocumentChangeOperation> = Vec::new();
+
+ for op in source_change.file_system_edits {
+ let ops = snippet_text_document_ops(snap, op)?;
+ document_changes.extend_from_slice(&ops);
+ }
+ for (file_id, edit) in source_change.source_file_edits {
+ let edit = snippet_text_document_edit(snap, source_change.is_snippet, file_id, edit)?;
+ document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Edit(edit));
+ }
+ let mut workspace_edit = lsp_ext::SnippetWorkspaceEdit {
+ changes: None,
+ document_changes: Some(document_changes),
+ change_annotations: None,
+ };
+ if snap.config.change_annotation_support() {
+ workspace_edit.change_annotations = Some(
+ once((
+ outside_workspace_annotation_id(),
+ lsp_types::ChangeAnnotation {
+ label: String::from("Edit outside of the workspace"),
+ needs_confirmation: Some(true),
+ description: Some(String::from(
+ "This edit lies outside of the workspace and may affect dependencies",
+ )),
+ },
+ ))
+ .collect(),
+ )
+ }
+ Ok(workspace_edit)
+}
+
+pub(crate) fn workspace_edit(
+ snap: &GlobalStateSnapshot,
+ source_change: SourceChange,
+) -> Result<lsp_types::WorkspaceEdit> {
+ assert!(!source_change.is_snippet);
+ snippet_workspace_edit(snap, source_change).map(|it| it.into())
+}
+
+impl From<lsp_ext::SnippetWorkspaceEdit> for lsp_types::WorkspaceEdit {
+ fn from(snippet_workspace_edit: lsp_ext::SnippetWorkspaceEdit) -> lsp_types::WorkspaceEdit {
+ lsp_types::WorkspaceEdit {
+ changes: None,
+ document_changes: snippet_workspace_edit.document_changes.map(|changes| {
+ lsp_types::DocumentChanges::Operations(
+ changes
+ .into_iter()
+ .map(|change| match change {
+ lsp_ext::SnippetDocumentChangeOperation::Op(op) => {
+ lsp_types::DocumentChangeOperation::Op(op)
+ }
+ lsp_ext::SnippetDocumentChangeOperation::Edit(edit) => {
+ lsp_types::DocumentChangeOperation::Edit(
+ lsp_types::TextDocumentEdit {
+ text_document: edit.text_document,
+ edits: edit.edits.into_iter().map(From::from).collect(),
+ },
+ )
+ }
+ })
+ .collect(),
+ )
+ }),
+ change_annotations: snippet_workspace_edit.change_annotations,
+ }
+ }
+}
+
+impl From<lsp_ext::SnippetTextEdit>
+ for lsp_types::OneOf<lsp_types::TextEdit, lsp_types::AnnotatedTextEdit>
+{
+ fn from(
+ lsp_ext::SnippetTextEdit { annotation_id, insert_text_format:_, new_text, range }: lsp_ext::SnippetTextEdit,
+ ) -> Self {
+ match annotation_id {
+ Some(annotation_id) => lsp_types::OneOf::Right(lsp_types::AnnotatedTextEdit {
+ text_edit: lsp_types::TextEdit { range, new_text },
+ annotation_id,
+ }),
+ None => lsp_types::OneOf::Left(lsp_types::TextEdit { range, new_text }),
+ }
+ }
+}
+
+pub(crate) fn call_hierarchy_item(
+ snap: &GlobalStateSnapshot,
+ target: NavigationTarget,
+) -> Result<lsp_types::CallHierarchyItem> {
+ let name = target.name.to_string();
+ let detail = target.description.clone();
+ let kind = target.kind.map(symbol_kind).unwrap_or(lsp_types::SymbolKind::FUNCTION);
+ let (uri, range, selection_range) = location_info(snap, target)?;
+ Ok(lsp_types::CallHierarchyItem {
+ name,
+ kind,
+ tags: None,
+ detail,
+ uri,
+ range,
+ selection_range,
+ data: None,
+ })
+}
+
+pub(crate) fn code_action_kind(kind: AssistKind) -> lsp_types::CodeActionKind {
+ match kind {
+ AssistKind::None | AssistKind::Generate => lsp_types::CodeActionKind::EMPTY,
+ AssistKind::QuickFix => lsp_types::CodeActionKind::QUICKFIX,
+ AssistKind::Refactor => lsp_types::CodeActionKind::REFACTOR,
+ AssistKind::RefactorExtract => lsp_types::CodeActionKind::REFACTOR_EXTRACT,
+ AssistKind::RefactorInline => lsp_types::CodeActionKind::REFACTOR_INLINE,
+ AssistKind::RefactorRewrite => lsp_types::CodeActionKind::REFACTOR_REWRITE,
+ }
+}
+
+pub(crate) fn code_action(
+ snap: &GlobalStateSnapshot,
+ assist: Assist,
+ resolve_data: Option<(usize, lsp_types::CodeActionParams)>,
+) -> Result<lsp_ext::CodeAction> {
+ let mut res = lsp_ext::CodeAction {
+ title: assist.label.to_string(),
+ group: assist.group.filter(|_| snap.config.code_action_group()).map(|gr| gr.0),
+ kind: Some(code_action_kind(assist.id.1)),
+ edit: None,
+ is_preferred: None,
+ data: None,
+ command: None,
+ };
+
+ if assist.trigger_signature_help && snap.config.client_commands().trigger_parameter_hints {
+ res.command = Some(command::trigger_parameter_hints());
+ }
+
+ match (assist.source_change, resolve_data) {
+ (Some(it), _) => res.edit = Some(snippet_workspace_edit(snap, it)?),
+ (None, Some((index, code_action_params))) => {
+ res.data = Some(lsp_ext::CodeActionData {
+ id: format!("{}:{}:{}", assist.id.0, assist.id.1.name(), index),
+ code_action_params,
+ });
+ }
+ (None, None) => {
+ stdx::never!("assist should always be resolved if client can't do lazy resolving")
+ }
+ };
+ Ok(res)
+}
+
+pub(crate) fn runnable(
+ snap: &GlobalStateSnapshot,
+ runnable: Runnable,
+) -> Result<lsp_ext::Runnable> {
+ let config = snap.config.runnables();
+ let spec = CargoTargetSpec::for_file(snap, runnable.nav.file_id)?;
+ let workspace_root = spec.as_ref().map(|it| it.workspace_root.clone());
+ let target = spec.as_ref().map(|s| s.target.clone());
+ let (cargo_args, executable_args) =
+ CargoTargetSpec::runnable_args(snap, spec, &runnable.kind, &runnable.cfg)?;
+ let label = runnable.label(target);
+ let location = location_link(snap, None, runnable.nav)?;
+
+ Ok(lsp_ext::Runnable {
+ label,
+ location: Some(location),
+ kind: lsp_ext::RunnableKind::Cargo,
+ args: lsp_ext::CargoRunnable {
+ workspace_root: workspace_root.map(|it| it.into()),
+ override_cargo: config.override_cargo,
+ cargo_args,
+ cargo_extra_args: config.cargo_extra_args,
+ executable_args,
+ expect_test: None,
+ },
+ })
+}
+
+pub(crate) fn code_lens(
+ acc: &mut Vec<lsp_types::CodeLens>,
+ snap: &GlobalStateSnapshot,
+ annotation: Annotation,
+) -> Result<()> {
+ let client_commands_config = snap.config.client_commands();
+ match annotation.kind {
+ AnnotationKind::Runnable(run) => {
+ let line_index = snap.file_line_index(run.nav.file_id)?;
+ let annotation_range = range(&line_index, annotation.range);
+
+ let title = run.title();
+ let can_debug = match run.kind {
+ ide::RunnableKind::DocTest { .. } => false,
+ ide::RunnableKind::TestMod { .. }
+ | ide::RunnableKind::Test { .. }
+ | ide::RunnableKind::Bench { .. }
+ | ide::RunnableKind::Bin => true,
+ };
+ let r = runnable(snap, run)?;
+
+ let lens_config = snap.config.lens();
+ if lens_config.run && client_commands_config.run_single {
+ let command = command::run_single(&r, &title);
+ acc.push(lsp_types::CodeLens {
+ range: annotation_range,
+ command: Some(command),
+ data: None,
+ })
+ }
+ if lens_config.debug && can_debug && client_commands_config.debug_single {
+ let command = command::debug_single(&r);
+ acc.push(lsp_types::CodeLens {
+ range: annotation_range,
+ command: Some(command),
+ data: None,
+ })
+ }
+ }
- let line_index = snap.file_line_index(file_id)?;
++ AnnotationKind::HasImpls { pos: file_range, data } => {
+ if !client_commands_config.show_reference {
+ return Ok(());
+ }
- let url = url(snap, file_id);
++ let line_index = snap.file_line_index(file_range.file_id)?;
+ let annotation_range = range(&line_index, annotation.range);
- AnnotationKind::HasReferences { file_id, data } => {
++ let url = url(snap, file_range.file_id);
+
+ let id = lsp_types::TextDocumentIdentifier { uri: url.clone() };
+
+ let doc_pos = lsp_types::TextDocumentPositionParams::new(id, annotation_range.start);
+
+ let goto_params = lsp_types::request::GotoImplementationParams {
+ text_document_position_params: doc_pos,
+ work_done_progress_params: Default::default(),
+ partial_result_params: Default::default(),
+ };
+
+ let command = data.map(|ranges| {
+ let locations: Vec<lsp_types::Location> = ranges
+ .into_iter()
+ .filter_map(|target| {
+ location(
+ snap,
+ FileRange { file_id: target.file_id, range: target.full_range },
+ )
+ .ok()
+ })
+ .collect();
+
+ command::show_references(
+ implementation_title(locations.len()),
+ &url,
+ annotation_range.start,
+ locations,
+ )
+ });
+
+ acc.push(lsp_types::CodeLens {
+ range: annotation_range,
+ command,
+ data: Some(to_value(lsp_ext::CodeLensResolveData::Impls(goto_params)).unwrap()),
+ })
+ }
- let line_index = snap.file_line_index(file_id)?;
++ AnnotationKind::HasReferences { pos: file_range, data } => {
+ if !client_commands_config.show_reference {
+ return Ok(());
+ }
- let url = url(snap, file_id);
++ let line_index = snap.file_line_index(file_range.file_id)?;
+ let annotation_range = range(&line_index, annotation.range);
++ let url = url(snap, file_range.file_id);
+
+ let id = lsp_types::TextDocumentIdentifier { uri: url.clone() };
+
+ let doc_pos = lsp_types::TextDocumentPositionParams::new(id, annotation_range.start);
+
+ let command = data.map(|ranges| {
+ let locations: Vec<lsp_types::Location> =
+ ranges.into_iter().filter_map(|range| location(snap, range).ok()).collect();
+
+ command::show_references(
+ reference_title(locations.len()),
+ &url,
+ annotation_range.start,
+ locations,
+ )
+ });
+
+ acc.push(lsp_types::CodeLens {
+ range: annotation_range,
+ command,
+ data: Some(to_value(lsp_ext::CodeLensResolveData::References(doc_pos)).unwrap()),
+ })
+ }
+ }
+ Ok(())
+}
+
+pub(crate) mod command {
+ use ide::{FileRange, NavigationTarget};
+ use serde_json::to_value;
+
+ use crate::{
+ global_state::GlobalStateSnapshot,
+ lsp_ext,
+ to_proto::{location, location_link},
+ };
+
+ pub(crate) fn show_references(
+ title: String,
+ uri: &lsp_types::Url,
+ position: lsp_types::Position,
+ locations: Vec<lsp_types::Location>,
+ ) -> lsp_types::Command {
+ // We cannot use the 'editor.action.showReferences' command directly
+ // because that command requires vscode types which we convert in the handler
+ // on the client side.
+
+ lsp_types::Command {
+ title,
+ command: "rust-analyzer.showReferences".into(),
+ arguments: Some(vec![
+ to_value(uri).unwrap(),
+ to_value(position).unwrap(),
+ to_value(locations).unwrap(),
+ ]),
+ }
+ }
+
+ pub(crate) fn run_single(runnable: &lsp_ext::Runnable, title: &str) -> lsp_types::Command {
+ lsp_types::Command {
+ title: title.to_string(),
+ command: "rust-analyzer.runSingle".into(),
+ arguments: Some(vec![to_value(runnable).unwrap()]),
+ }
+ }
+
+ pub(crate) fn debug_single(runnable: &lsp_ext::Runnable) -> lsp_types::Command {
+ lsp_types::Command {
+ title: "Debug".into(),
+ command: "rust-analyzer.debugSingle".into(),
+ arguments: Some(vec![to_value(runnable).unwrap()]),
+ }
+ }
+
+ pub(crate) fn goto_location(
+ snap: &GlobalStateSnapshot,
+ nav: &NavigationTarget,
+ ) -> Option<lsp_types::Command> {
+ let value = if snap.config.location_link() {
+ let link = location_link(snap, None, nav.clone()).ok()?;
+ to_value(link).ok()?
+ } else {
+ let range = FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() };
+ let location = location(snap, range).ok()?;
+ to_value(location).ok()?
+ };
+
+ Some(lsp_types::Command {
+ title: nav.name.to_string(),
+ command: "rust-analyzer.gotoLocation".into(),
+ arguments: Some(vec![value]),
+ })
+ }
+
+ pub(crate) fn trigger_parameter_hints() -> lsp_types::Command {
+ lsp_types::Command {
+ title: "triggerParameterHints".into(),
+ command: "editor.action.triggerParameterHints".into(),
+ arguments: None,
+ }
+ }
+}
+
+pub(crate) fn implementation_title(count: usize) -> String {
+ if count == 1 {
+ "1 implementation".into()
+ } else {
+ format!("{} implementations", count)
+ }
+}
+
+pub(crate) fn reference_title(count: usize) -> String {
+ if count == 1 {
+ "1 reference".into()
+ } else {
+ format!("{} references", count)
+ }
+}
+
+pub(crate) fn markup_content(
+ markup: Markup,
+ kind: ide::HoverDocFormat,
+) -> lsp_types::MarkupContent {
+ let kind = match kind {
+ ide::HoverDocFormat::Markdown => lsp_types::MarkupKind::Markdown,
+ ide::HoverDocFormat::PlainText => lsp_types::MarkupKind::PlainText,
+ };
+ let value = crate::markdown::format_docs(markup.as_str());
+ lsp_types::MarkupContent { kind, value }
+}
+
+pub(crate) fn rename_error(err: RenameError) -> crate::LspError {
+ // This is wrong, but we don't have a better alternative I suppose?
+ // https://github.com/microsoft/language-server-protocol/issues/1341
+ invalid_params_error(err.to_string())
+}
+
+#[cfg(test)]
+mod tests {
+ use std::sync::Arc;
+
+ use ide::Analysis;
+
+ use super::*;
+
+ #[test]
+ fn conv_fold_line_folding_only_fixup() {
+ let text = r#"mod a;
+mod b;
+mod c;
+
+fn main() {
+ if cond {
+ a::do_a();
+ } else {
+ b::do_b();
+ }
+}"#;
+
+ let (analysis, file_id) = Analysis::from_single_file(text.to_string());
+ let folds = analysis.folding_ranges(file_id).unwrap();
+ assert_eq!(folds.len(), 4);
+
+ let line_index = LineIndex {
+ index: Arc::new(ide::LineIndex::new(text)),
+ endings: LineEndings::Unix,
+ encoding: OffsetEncoding::Utf16,
+ };
+ let converted: Vec<lsp_types::FoldingRange> =
+ folds.into_iter().map(|it| folding_range(text, &line_index, true, it)).collect();
+
+ let expected_lines = [(0, 2), (4, 10), (5, 6), (7, 9)];
+ assert_eq!(converted.len(), expected_lines.len());
+ for (folding_range, (start_line, end_line)) in converted.iter().zip(expected_lines.iter()) {
+ assert_eq!(folding_range.start_line, *start_line);
+ assert_eq!(folding_range.start_character, None);
+ assert_eq!(folding_range.end_line, *end_line);
+ assert_eq!(folding_range.end_character, None);
+ }
+ }
+
+ // `Url` is not able to parse windows paths on unix machines.
+ #[test]
+ #[cfg(target_os = "windows")]
+ fn test_lowercase_drive_letter() {
+ use std::path::Path;
+
+ let url = url_from_abs_path(Path::new("C:\\Test").try_into().unwrap());
+ assert_eq!(url.to_string(), "file:///c:/Test");
+
+ let url = url_from_abs_path(Path::new(r#"\\localhost\C$\my_dir"#).try_into().unwrap());
+ assert_eq!(url.to_string(), "file://localhost/C$/my_dir");
+ }
+}
--- /dev/null
- use expect_test::expect;
+//! The most high-level integrated tests for rust-analyzer.
+//!
+//! This tests run a full LSP event loop, spawn cargo and process stdlib from
+//! sysroot. For this reason, the tests here are very slow, and should be
+//! avoided unless absolutely necessary.
+//!
+//! In particular, it's fine *not* to test that client & server agree on
+//! specific JSON shapes here -- there's little value in such tests, as we can't
+//! be sure without a real client anyway.
+
+#![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)]
+
+#[cfg(not(feature = "in-rust-tree"))]
+mod sourcegen;
+mod support;
+mod testdir;
+mod tidy;
+
+use std::{collections::HashMap, path::PathBuf, time::Instant};
+
- "cargo": { "noSysroot": false }
+use lsp_types::{
+ notification::DidOpenTextDocument,
+ request::{
+ CodeActionRequest, Completion, Formatting, GotoTypeDefinition, HoverRequest,
+ WillRenameFiles, WorkspaceSymbol,
+ },
+ CodeActionContext, CodeActionParams, CompletionParams, DidOpenTextDocumentParams,
+ DocumentFormattingParams, FileRename, FormattingOptions, GotoDefinitionParams, HoverParams,
+ PartialResultParams, Position, Range, RenameFilesParams, TextDocumentItem,
+ TextDocumentPositionParams, WorkDoneProgressParams,
+};
+use rust_analyzer::lsp_ext::{OnEnter, Runnables, RunnablesParams};
+use serde_json::json;
+use test_utils::skip_slow_tests;
+
+use crate::{
+ support::{project, Project},
+ testdir::TestDir,
+};
+
+const PROFILE: &str = "";
+// const PROFILE: &'static str = "*@3>100";
+
+#[test]
+fn completes_items_from_standard_library() {
+ if skip_slow_tests() {
+ return;
+ }
+
+ let server = Project::with_fixture(
+ r#"
+//- /Cargo.toml
+[package]
+name = "foo"
+version = "0.0.0"
+
+//- /src/lib.rs
+use std::collections::Spam;
+"#,
+ )
+ .with_config(serde_json::json!({
- "cargo": { "noSysroot": false }
++ "cargo": { "sysroot": "discover" }
+ }))
+ .server()
+ .wait_until_workspace_is_loaded();
+
+ let res = server.send_request::<Completion>(CompletionParams {
+ text_document_position: TextDocumentPositionParams::new(
+ server.doc_id("src/lib.rs"),
+ Position::new(0, 23),
+ ),
+ context: None,
+ partial_result_params: PartialResultParams::default(),
+ work_done_progress_params: WorkDoneProgressParams::default(),
+ });
+ assert!(res.to_string().contains("HashMap"));
+}
+
+#[test]
+fn test_runnables_project() {
+ if skip_slow_tests() {
+ return;
+ }
+
+ let server = Project::with_fixture(
+ r#"
+//- /foo/Cargo.toml
+[package]
+name = "foo"
+version = "0.0.0"
+
+//- /foo/src/lib.rs
+pub fn foo() {}
+
+//- /foo/tests/spam.rs
+#[test]
+fn test_eggs() {}
+
+//- /bar/Cargo.toml
+[package]
+name = "bar"
+version = "0.0.0"
+
+//- /bar/src/main.rs
+fn main() {}
+"#,
+ )
+ .root("foo")
+ .root("bar")
+ .server()
+ .wait_until_workspace_is_loaded();
+
+ server.request::<Runnables>(
+ RunnablesParams { text_document: server.doc_id("foo/tests/spam.rs"), position: None },
+ json!([
+ {
+ "args": {
+ "cargoArgs": ["test", "--package", "foo", "--test", "spam"],
+ "executableArgs": ["test_eggs", "--exact", "--nocapture"],
+ "cargoExtraArgs": [],
+ "overrideCargo": null,
+ "workspaceRoot": server.path().join("foo")
+ },
+ "kind": "cargo",
+ "label": "test test_eggs",
+ "location": {
+ "targetRange": {
+ "end": { "character": 17, "line": 1 },
+ "start": { "character": 0, "line": 0 }
+ },
+ "targetSelectionRange": {
+ "end": { "character": 12, "line": 1 },
+ "start": { "character": 3, "line": 1 }
+ },
+ "targetUri": "file:///[..]/tests/spam.rs"
+ }
+ },
+ {
+ "args": {
+ "overrideCargo": null,
+ "workspaceRoot": server.path().join("foo"),
+ "cargoArgs": [
+ "test",
+ "--package",
+ "foo",
+ "--test",
+ "spam"
+ ],
+ "cargoExtraArgs": [],
+ "executableArgs": [
+ "",
+ "--nocapture"
+ ]
+ },
+ "kind": "cargo",
+ "label": "test-mod ",
+ "location": {
+ "targetUri": "file:///[..]/tests/spam.rs",
+ "targetRange": {
+ "start": {
+ "line": 0,
+ "character": 0
+ },
+ "end": {
+ "line": 3,
+ "character": 0
+ }
+ },
+ "targetSelectionRange": {
+ "start": {
+ "line": 0,
+ "character": 0
+ },
+ "end": {
+ "line": 3,
+ "character": 0
+ }
+ }
+ },
+ },
+ {
+ "args": {
+ "cargoArgs": ["check", "--package", "foo", "--all-targets"],
+ "executableArgs": [],
+ "cargoExtraArgs": [],
+ "overrideCargo": null,
+ "workspaceRoot": server.path().join("foo")
+ },
+ "kind": "cargo",
+ "label": "cargo check -p foo --all-targets"
+ },
+ {
+ "args": {
+ "cargoArgs": ["test", "--package", "foo", "--all-targets"],
+ "executableArgs": [],
+ "cargoExtraArgs": [],
+ "overrideCargo": null,
+ "workspaceRoot": server.path().join("foo")
+ },
+ "kind": "cargo",
+ "label": "cargo test -p foo --all-targets"
+ }
+ ]),
+ );
+}
+
+// Each package in these workspaces should be run from its own root
+#[test]
+fn test_path_dependency_runnables() {
+ if skip_slow_tests() {
+ return;
+ }
+
+ let server = Project::with_fixture(
+ r#"
+//- /consumer/Cargo.toml
+[package]
+name = "consumer"
+version = "0.1.0"
+[dependencies]
+dependency = { path = "../dependency" }
+
+//- /consumer/src/lib.rs
+#[cfg(test)]
+mod tests {
+ #[test]
+ fn consumer() {}
+}
+
+//- /dependency/Cargo.toml
+[package]
+name = "dependency"
+version = "0.1.0"
+[dev-dependencies]
+devdependency = { path = "../devdependency" }
+
+//- /dependency/src/lib.rs
+#[cfg(test)]
+mod tests {
+ #[test]
+ fn dependency() {}
+}
+
+//- /devdependency/Cargo.toml
+[package]
+name = "devdependency"
+version = "0.1.0"
+
+//- /devdependency/src/lib.rs
+#[cfg(test)]
+mod tests {
+ #[test]
+ fn devdependency() {}
+}
+ "#,
+ )
+ .root("consumer")
+ .root("dependency")
+ .root("devdependency")
+ .server()
+ .wait_until_workspace_is_loaded();
+
+ for runnable in ["consumer", "dependency", "devdependency"] {
+ server.request::<Runnables>(
+ RunnablesParams {
+ text_document: server.doc_id(&format!("{}/src/lib.rs", runnable)),
+ position: None,
+ },
+ json!([
+ "{...}",
+ {
+ "label": "cargo test -p [..] --all-targets",
+ "kind": "cargo",
+ "args": {
+ "overrideCargo": null,
+ "workspaceRoot": server.path().join(runnable),
+ "cargoArgs": [
+ "test",
+ "--package",
+ runnable,
+ "--all-targets"
+ ],
+ "cargoExtraArgs": [],
+ "executableArgs": []
+ },
+ },
+ "{...}",
+ "{...}"
+ ]),
+ );
+ }
+}
+
+#[test]
+fn test_format_document() {
+ if skip_slow_tests() {
+ return;
+ }
+
+ let server = project(
+ r#"
+//- /Cargo.toml
+[package]
+name = "foo"
+version = "0.0.0"
+
+//- /src/lib.rs
+mod bar;
+
+fn main() {
+}
+
+pub use std::collections::HashMap;
+"#,
+ )
+ .wait_until_workspace_is_loaded();
+
+ server.request::<Formatting>(
+ DocumentFormattingParams {
+ text_document: server.doc_id("src/lib.rs"),
+ options: FormattingOptions {
+ tab_size: 4,
+ insert_spaces: false,
+ insert_final_newline: None,
+ trim_final_newlines: None,
+ trim_trailing_whitespace: None,
+ properties: HashMap::new(),
+ },
+ work_done_progress_params: WorkDoneProgressParams::default(),
+ },
+ json!([
+ {
+ "newText": "",
+ "range": {
+ "end": { "character": 0, "line": 3 },
+ "start": { "character": 11, "line": 2 }
+ }
+ }
+ ]),
+ );
+}
+
+#[test]
+fn test_format_document_2018() {
+ if skip_slow_tests() {
+ return;
+ }
+
+ let server = project(
+ r#"
+//- /Cargo.toml
+[package]
+name = "foo"
+version = "0.0.0"
+edition = "2018"
+
+//- /src/lib.rs
+mod bar;
+
+async fn test() {
+}
+
+fn main() {
+}
+
+pub use std::collections::HashMap;
+"#,
+ )
+ .wait_until_workspace_is_loaded();
+
+ server.request::<Formatting>(
+ DocumentFormattingParams {
+ text_document: server.doc_id("src/lib.rs"),
+ options: FormattingOptions {
+ tab_size: 4,
+ insert_spaces: false,
+ properties: HashMap::new(),
+ insert_final_newline: None,
+ trim_final_newlines: None,
+ trim_trailing_whitespace: None,
+ },
+ work_done_progress_params: WorkDoneProgressParams::default(),
+ },
+ json!([
+ {
+ "newText": "",
+ "range": {
+ "end": { "character": 0, "line": 3 },
+ "start": { "character": 17, "line": 2 }
+ }
+ },
+ {
+ "newText": "",
+ "range": {
+ "end": { "character": 0, "line": 6 },
+ "start": { "character": 11, "line": 5 }
+ }
+ }
+ ]),
+ );
+}
+
+#[test]
+fn test_format_document_unchanged() {
+ if skip_slow_tests() {
+ return;
+ }
+
+ let server = project(
+ r#"
+//- /Cargo.toml
+[package]
+name = "foo"
+version = "0.0.0"
+
+//- /src/lib.rs
+fn main() {}
+"#,
+ )
+ .wait_until_workspace_is_loaded();
+
+ server.request::<Formatting>(
+ DocumentFormattingParams {
+ text_document: server.doc_id("src/lib.rs"),
+ options: FormattingOptions {
+ tab_size: 4,
+ insert_spaces: false,
+ insert_final_newline: None,
+ trim_final_newlines: None,
+ trim_trailing_whitespace: None,
+ properties: HashMap::new(),
+ },
+ work_done_progress_params: WorkDoneProgressParams::default(),
+ },
+ json!(null),
+ );
+}
+
+#[test]
+fn test_missing_module_code_action() {
+ if skip_slow_tests() {
+ return;
+ }
+
+ let server = project(
+ r#"
+//- /Cargo.toml
+[package]
+name = "foo"
+version = "0.0.0"
+
+//- /src/lib.rs
+mod bar;
+
+fn main() {}
+"#,
+ )
+ .wait_until_workspace_is_loaded();
+
+ server.request::<CodeActionRequest>(
+ CodeActionParams {
+ text_document: server.doc_id("src/lib.rs"),
+ range: Range::new(Position::new(0, 4), Position::new(0, 7)),
+ context: CodeActionContext::default(),
+ partial_result_params: PartialResultParams::default(),
+ work_done_progress_params: WorkDoneProgressParams::default(),
+ },
+ json!([
+ {
+ "title": "Create module at `bar.rs`",
+ "kind": "quickfix",
+ "edit": {
+ "documentChanges": [
+ {
+ "kind": "create",
+ "uri": "file://[..]/src/bar.rs"
+ }
+ ]
+ }
+ },
+ {
+ "title": "Create module at `bar/mod.rs`",
+ "kind": "quickfix",
+ "edit": {
+ "documentChanges": [
+ {
+ "kind": "create",
+ "uri": "file://[..]src/bar/mod.rs"
+ }
+ ]
+ }
+ }
+ ]),
+ );
+
+ server.request::<CodeActionRequest>(
+ CodeActionParams {
+ text_document: server.doc_id("src/lib.rs"),
+ range: Range::new(Position::new(2, 8), Position::new(2, 8)),
+ context: CodeActionContext::default(),
+ partial_result_params: PartialResultParams::default(),
+ work_done_progress_params: WorkDoneProgressParams::default(),
+ },
+ json!([]),
+ );
+}
+
+#[test]
+fn test_missing_module_code_action_in_json_project() {
+ if skip_slow_tests() {
+ // return;
+ }
+
+ let tmp_dir = TestDir::new();
+
+ let path = tmp_dir.path();
+
+ let project = json!({
+ "roots": [path],
+ "crates": [ {
+ "root_module": path.join("src/lib.rs"),
+ "deps": [],
+ "edition": "2015",
+ "cfg": [ "cfg_atom_1", "feature=\"cfg_1\""],
+ } ]
+ });
+
+ let code = format!(
+ r#"
+//- /rust-project.json
+{PROJECT}
+
+//- /src/lib.rs
+mod bar;
+
+fn main() {{}}
+"#,
+ PROJECT = project,
+ );
+
+ let server =
+ Project::with_fixture(&code).tmp_dir(tmp_dir).server().wait_until_workspace_is_loaded();
+
+ server.request::<CodeActionRequest>(
+ CodeActionParams {
+ text_document: server.doc_id("src/lib.rs"),
+ range: Range::new(Position::new(0, 4), Position::new(0, 7)),
+ context: CodeActionContext::default(),
+ partial_result_params: PartialResultParams::default(),
+ work_done_progress_params: WorkDoneProgressParams::default(),
+ },
+ json!([
+ {
+ "title": "Create module at `bar.rs`",
+ "kind": "quickfix",
+ "edit": {
+ "documentChanges": [
+ {
+ "kind": "create",
+ "uri": "file://[..]/src/bar.rs"
+ }
+ ]
+ }
+ },
+ {
+ "title": "Create module at `bar/mod.rs`",
+ "kind": "quickfix",
+ "edit": {
+ "documentChanges": [
+ {
+ "kind": "create",
+ "uri": "file://[..]src/bar/mod.rs"
+ }
+ ]
+ }
+ }
+ ]),
+ );
+
+ server.request::<CodeActionRequest>(
+ CodeActionParams {
+ text_document: server.doc_id("src/lib.rs"),
+ range: Range::new(Position::new(2, 8), Position::new(2, 8)),
+ context: CodeActionContext::default(),
+ partial_result_params: PartialResultParams::default(),
+ work_done_progress_params: WorkDoneProgressParams::default(),
+ },
+ json!([]),
+ );
+}
+
+#[test]
+fn diagnostics_dont_block_typing() {
+ if skip_slow_tests() {
+ return;
+ }
+
+ let librs: String = (0..10).map(|i| format!("mod m{};", i)).collect();
+ let libs: String = (0..10).map(|i| format!("//- /src/m{}.rs\nfn foo() {{}}\n\n", i)).collect();
+ let server = Project::with_fixture(&format!(
+ r#"
+//- /Cargo.toml
+[package]
+name = "foo"
+version = "0.0.0"
+
+//- /src/lib.rs
+{}
+
+{}
+
+fn main() {{}}
+"#,
+ librs, libs
+ ))
+ .with_config(serde_json::json!({
- "noSysroot": true,
++ "cargo": { "sysroot": "discover" }
+ }))
+ .server()
+ .wait_until_workspace_is_loaded();
+
+ for i in 0..10 {
+ server.notification::<DidOpenTextDocument>(DidOpenTextDocumentParams {
+ text_document: TextDocumentItem {
+ uri: server.doc_id(&format!("src/m{}.rs", i)).uri,
+ language_id: "rust".to_string(),
+ version: 0,
+ text: "/// Docs\nfn foo() {}".to_string(),
+ },
+ });
+ }
+ let start = Instant::now();
+ server.request::<OnEnter>(
+ TextDocumentPositionParams {
+ text_document: server.doc_id("src/m0.rs"),
+ position: Position { line: 0, character: 5 },
+ },
+ json!([{
+ "insertTextFormat": 2,
+ "newText": "\n/// $0",
+ "range": {
+ "end": { "character": 5, "line": 0 },
+ "start": { "character": 5, "line": 0 }
+ }
+ }]),
+ );
+ let elapsed = start.elapsed();
+ assert!(elapsed.as_millis() < 2000, "typing enter took {:?}", elapsed);
+}
+
+#[test]
+fn preserves_dos_line_endings() {
+ if skip_slow_tests() {
+ return;
+ }
+
+ let server = Project::with_fixture(
+ "
+//- /Cargo.toml
+[package]
+name = \"foo\"
+version = \"0.0.0\"
+
+//- /src/main.rs
+/// Some Docs\r\nfn main() {}
+",
+ )
+ .server()
+ .wait_until_workspace_is_loaded();
+
+ server.request::<OnEnter>(
+ TextDocumentPositionParams {
+ text_document: server.doc_id("src/main.rs"),
+ position: Position { line: 0, character: 8 },
+ },
+ json!([{
+ "insertTextFormat": 2,
+ "newText": "\r\n/// $0",
+ "range": {
+ "end": { "line": 0, "character": 8 },
+ "start": { "line": 0, "character": 8 }
+ }
+ }]),
+ );
+}
+
+#[test]
+fn out_dirs_check() {
+ if skip_slow_tests() {
+ // return;
+ }
+
+ let server = Project::with_fixture(
+ r###"
+//- /Cargo.toml
+[package]
+name = "foo"
+version = "0.0.0"
+
+//- /build.rs
+use std::{env, fs, path::Path};
+
+fn main() {
+ let out_dir = env::var_os("OUT_DIR").unwrap();
+ let dest_path = Path::new(&out_dir).join("hello.rs");
+ fs::write(
+ &dest_path,
+ r#"pub fn message() -> &'static str { "Hello, World!" }"#,
+ )
+ .unwrap();
+ println!("cargo:rustc-cfg=atom_cfg");
+ println!("cargo:rustc-cfg=featlike=\"set\"");
+ println!("cargo:rerun-if-changed=build.rs");
+}
+//- /src/main.rs
+#[rustc_builtin_macro] macro_rules! include {}
+#[rustc_builtin_macro] macro_rules! include_str {}
+#[rustc_builtin_macro] macro_rules! concat {}
+#[rustc_builtin_macro] macro_rules! env {}
+
+include!(concat!(env!("OUT_DIR"), "/hello.rs"));
+
+#[cfg(atom_cfg)]
+struct A;
+#[cfg(bad_atom_cfg)]
+struct A;
+#[cfg(featlike = "set")]
+struct B;
+#[cfg(featlike = "not_set")]
+struct B;
+
+fn main() {
+ let va = A;
+ let vb = B;
+ let should_be_str = message();
+ let another_str = include_str!("main.rs");
+}
+"###,
+ )
+ .with_config(serde_json::json!({
+ "cargo": {
+ "buildScripts": {
+ "enable": true
+ },
- "noSysroot": true,
++ "sysroot": null,
+ }
+ }))
+ .server()
+ .wait_until_workspace_is_loaded();
+
+ let res = server.send_request::<HoverRequest>(HoverParams {
+ text_document_position_params: TextDocumentPositionParams::new(
+ server.doc_id("src/main.rs"),
+ Position::new(19, 10),
+ ),
+ work_done_progress_params: Default::default(),
+ });
+ assert!(res.to_string().contains("&str"));
+
+ let res = server.send_request::<HoverRequest>(HoverParams {
+ text_document_position_params: TextDocumentPositionParams::new(
+ server.doc_id("src/main.rs"),
+ Position::new(20, 10),
+ ),
+ work_done_progress_params: Default::default(),
+ });
+ assert!(res.to_string().contains("&str"));
+
+ server.request::<GotoTypeDefinition>(
+ GotoDefinitionParams {
+ text_document_position_params: TextDocumentPositionParams::new(
+ server.doc_id("src/main.rs"),
+ Position::new(17, 9),
+ ),
+ work_done_progress_params: Default::default(),
+ partial_result_params: Default::default(),
+ },
+ json!([{
+ "originSelectionRange": {
+ "end": { "character": 10, "line": 17 },
+ "start": { "character": 8, "line": 17 }
+ },
+ "targetRange": {
+ "end": { "character": 9, "line": 8 },
+ "start": { "character": 0, "line": 7 }
+ },
+ "targetSelectionRange": {
+ "end": { "character": 8, "line": 8 },
+ "start": { "character": 7, "line": 8 }
+ },
+ "targetUri": "file:///[..]src/main.rs"
+ }]),
+ );
+
+ server.request::<GotoTypeDefinition>(
+ GotoDefinitionParams {
+ text_document_position_params: TextDocumentPositionParams::new(
+ server.doc_id("src/main.rs"),
+ Position::new(18, 9),
+ ),
+ work_done_progress_params: Default::default(),
+ partial_result_params: Default::default(),
+ },
+ json!([{
+ "originSelectionRange": {
+ "end": { "character": 10, "line": 18 },
+ "start": { "character": 8, "line": 18 }
+ },
+ "targetRange": {
+ "end": { "character": 9, "line": 12 },
+ "start": { "character": 0, "line":11 }
+ },
+ "targetSelectionRange": {
+ "end": { "character": 8, "line": 12 },
+ "start": { "character": 7, "line": 12 }
+ },
+ "targetUri": "file:///[..]src/main.rs"
+ }]),
+ );
+}
+
+#[test]
++// FIXME: Re-enable once we can run proc-macro tests on rust-lang/rust-analyzer again
++#[cfg(any())]
+fn resolve_proc_macro() {
++ use expect_test::expect;
+ if skip_slow_tests() {
+ return;
+ }
+
+ let server = Project::with_fixture(
+ r###"
+//- /foo/Cargo.toml
+[package]
+name = "foo"
+version = "0.0.0"
+edition = "2021"
+[dependencies]
+bar = {path = "../bar"}
+
+//- /foo/src/main.rs
+use bar::Bar;
+
+#[rustc_builtin_macro]
+macro derive($item:item) {}
+trait Bar {
+ fn bar();
+}
+#[derive(Bar)]
+struct Foo {}
+fn main() {
+ Foo::bar();
+}
+
+//- /bar/Cargo.toml
+[package]
+name = "bar"
+version = "0.0.0"
+edition = "2021"
+
+[lib]
+proc-macro = true
+
+//- /bar/src/lib.rs
+extern crate proc_macro;
+use proc_macro::{Delimiter, Group, Ident, Span, TokenStream, TokenTree};
+macro_rules! t {
+ ($n:literal) => {
+ TokenTree::from(Ident::new($n, Span::call_site()))
+ };
+ ({}) => {
+ TokenTree::from(Group::new(Delimiter::Brace, TokenStream::new()))
+ };
+ (()) => {
+ TokenTree::from(Group::new(Delimiter::Parenthesis, TokenStream::new()))
+ };
+}
+#[proc_macro_derive(Bar)]
+pub fn foo(_input: TokenStream) -> TokenStream {
+ // We hard code the output here for preventing to use any deps
+ let mut res = TokenStream::new();
+
+ // ill behaved proc-macro will use the stdout
+ // we should ignore it
+ println!("I am bad guy");
+
+ // impl Bar for Foo { fn bar() {} }
+ let mut tokens = vec![t!("impl"), t!("Bar"), t!("for"), t!("Foo")];
+ let mut fn_stream = TokenStream::new();
+ fn_stream.extend(vec![t!("fn"), t!("bar"), t!(()), t!({})]);
+ tokens.push(Group::new(Delimiter::Brace, fn_stream).into());
+ res.extend(tokens);
+ res
+}
+
+"###,
+ )
+ .with_config(serde_json::json!({
+ "cargo": {
+ "buildScripts": {
+ "enable": true
+ },
++ "sysroot": null,
+ },
+ "procMacro": {
+ "enable": true,
+ "server": PathBuf::from(env!("CARGO_BIN_EXE_rust-analyzer")),
+ }
+ }))
+ .root("foo")
+ .root("bar")
+ .server()
+ .wait_until_workspace_is_loaded();
+
+ let res = server.send_request::<HoverRequest>(HoverParams {
+ text_document_position_params: TextDocumentPositionParams::new(
+ server.doc_id("foo/src/main.rs"),
+ Position::new(10, 9),
+ ),
+ work_done_progress_params: Default::default(),
+ });
+ let value = res.get("contents").unwrap().get("value").unwrap().as_str().unwrap();
+
+ expect![[r#"
+
+ ```rust
+ foo::Foo
+ ```
+
+ ```rust
+ fn bar()
+ ```"#]]
+ .assert_eq(value);
+}
+
+#[test]
+fn test_will_rename_files_same_level() {
+ if skip_slow_tests() {
+ return;
+ }
+
+ let tmp_dir = TestDir::new();
+ let tmp_dir_path = tmp_dir.path().to_owned();
+ let tmp_dir_str = tmp_dir_path.to_str().unwrap();
+ let base_path = PathBuf::from(format!("file://{}", tmp_dir_str));
+
+ let code = r#"
+//- /Cargo.toml
+[package]
+name = "foo"
+version = "0.0.0"
+
+//- /src/lib.rs
+mod old_file;
+mod from_mod;
+mod to_mod;
+mod old_folder;
+fn main() {}
+
+//- /src/old_file.rs
+
+//- /src/old_folder/mod.rs
+
+//- /src/from_mod/mod.rs
+
+//- /src/to_mod/foo.rs
+
+"#;
+ let server =
+ Project::with_fixture(code).tmp_dir(tmp_dir).server().wait_until_workspace_is_loaded();
+
+ //rename same level file
+ server.request::<WillRenameFiles>(
+ RenameFilesParams {
+ files: vec![FileRename {
+ old_uri: base_path.join("src/old_file.rs").to_str().unwrap().to_string(),
+ new_uri: base_path.join("src/new_file.rs").to_str().unwrap().to_string(),
+ }],
+ },
+ json!({
+ "documentChanges": [
+ {
+ "textDocument": {
+ "uri": format!("file://{}", tmp_dir_path.join("src").join("lib.rs").to_str().unwrap().to_string().replace("C:\\", "/c:/").replace('\\', "/")),
+ "version": null
+ },
+ "edits": [
+ {
+ "range": {
+ "start": {
+ "line": 0,
+ "character": 4
+ },
+ "end": {
+ "line": 0,
+ "character": 12
+ }
+ },
+ "newText": "new_file"
+ }
+ ]
+ }
+ ]
+ }),
+ );
+
+ //rename file from mod.rs to foo.rs
+ server.request::<WillRenameFiles>(
+ RenameFilesParams {
+ files: vec![FileRename {
+ old_uri: base_path.join("src/from_mod/mod.rs").to_str().unwrap().to_string(),
+ new_uri: base_path.join("src/from_mod/foo.rs").to_str().unwrap().to_string(),
+ }],
+ },
+ json!(null),
+ );
+
+ //rename file from foo.rs to mod.rs
+ server.request::<WillRenameFiles>(
+ RenameFilesParams {
+ files: vec![FileRename {
+ old_uri: base_path.join("src/to_mod/foo.rs").to_str().unwrap().to_string(),
+ new_uri: base_path.join("src/to_mod/mod.rs").to_str().unwrap().to_string(),
+ }],
+ },
+ json!(null),
+ );
+
+ //rename same level file
+ server.request::<WillRenameFiles>(
+ RenameFilesParams {
+ files: vec![FileRename {
+ old_uri: base_path.join("src/old_folder").to_str().unwrap().to_string(),
+ new_uri: base_path.join("src/new_folder").to_str().unwrap().to_string(),
+ }],
+ },
+ json!({
+ "documentChanges": [
+ {
+ "textDocument": {
+ "uri": format!("file://{}", tmp_dir_path.join("src").join("lib.rs").to_str().unwrap().to_string().replace("C:\\", "/c:/").replace('\\', "/")),
+ "version": null
+ },
+ "edits": [
+ {
+ "range": {
+ "start": {
+ "line": 3,
+ "character": 4
+ },
+ "end": {
+ "line": 3,
+ "character": 14
+ }
+ },
+ "newText": "new_folder"
+ }
+ ]
+ }
+ ]
+ }),
+ );
+}
+
+#[test]
+fn test_exclude_config_works() {
+ if skip_slow_tests() {
+ return;
+ }
+
+ let server = Project::with_fixture(
+ r#"
+//- /foo/Cargo.toml
+[package]
+name = "foo"
+version = "0.0.0"
+
+//- /foo/src/lib.rs
+pub fn foo() {}
+
+//- /bar/Cargo.toml
+[package]
+name = "bar"
+version = "0.0.0"
+
+//- /bar/src/lib.rs
+pub fn bar() {}
+"#,
+ )
+ .root("foo")
+ .root("bar")
+ .with_config(json!({
+ "files": {
+ "excludeDirs": ["foo", "bar"]
+ }
+ }))
+ .server()
+ .wait_until_workspace_is_loaded();
+
+ server.request::<WorkspaceSymbol>(Default::default(), json!([]));
+}
--- /dev/null
- "noSysroot": true,
+use std::{
+ cell::{Cell, RefCell},
+ fs,
+ path::{Path, PathBuf},
+ sync::Once,
+ time::Duration,
+};
+
+use crossbeam_channel::{after, select, Receiver};
+use lsp_server::{Connection, Message, Notification, Request};
+use lsp_types::{notification::Exit, request::Shutdown, TextDocumentIdentifier, Url};
+use project_model::ProjectManifest;
+use rust_analyzer::{config::Config, lsp_ext, main_loop};
+use serde::Serialize;
+use serde_json::{json, to_string_pretty, Value};
+use test_utils::Fixture;
+use vfs::AbsPathBuf;
+
+use crate::testdir::TestDir;
+
+pub(crate) struct Project<'a> {
+ fixture: &'a str,
+ tmp_dir: Option<TestDir>,
+ roots: Vec<PathBuf>,
+ config: serde_json::Value,
+}
+
+impl<'a> Project<'a> {
+ pub(crate) fn with_fixture(fixture: &str) -> Project<'_> {
+ Project {
+ fixture,
+ tmp_dir: None,
+ roots: vec![],
+ config: serde_json::json!({
+ "cargo": {
+ // Loading standard library is costly, let's ignore it by default
++ "sysroot": null,
+ // Can't use test binary as rustc wrapper.
+ "buildScripts": {
+ "useRustcWrapper": false
+ },
+ }
+ }),
+ }
+ }
+
+ pub(crate) fn tmp_dir(mut self, tmp_dir: TestDir) -> Project<'a> {
+ self.tmp_dir = Some(tmp_dir);
+ self
+ }
+
+ pub(crate) fn root(mut self, path: &str) -> Project<'a> {
+ self.roots.push(path.into());
+ self
+ }
+
+ pub(crate) fn with_config(mut self, config: serde_json::Value) -> Project<'a> {
+ fn merge(dst: &mut serde_json::Value, src: serde_json::Value) {
+ match (dst, src) {
+ (Value::Object(dst), Value::Object(src)) => {
+ for (k, v) in src {
+ merge(dst.entry(k).or_insert(v.clone()), v)
+ }
+ }
+ (dst, src) => *dst = src,
+ }
+ }
+ merge(&mut self.config, config);
+ self
+ }
+
+ pub(crate) fn server(self) -> Server {
+ let tmp_dir = self.tmp_dir.unwrap_or_else(TestDir::new);
+ static INIT: Once = Once::new();
+ INIT.call_once(|| {
+ tracing_subscriber::fmt()
+ .with_test_writer()
+ .with_env_filter(tracing_subscriber::EnvFilter::from_env("RA_LOG"))
+ .init();
+ profile::init_from(crate::PROFILE);
+ });
+
+ let (mini_core, proc_macros, fixtures) = Fixture::parse(self.fixture);
+ assert!(proc_macros.is_empty());
+ assert!(mini_core.is_none());
+ for entry in fixtures {
+ let path = tmp_dir.path().join(&entry.path['/'.len_utf8()..]);
+ fs::create_dir_all(path.parent().unwrap()).unwrap();
+ fs::write(path.as_path(), entry.text.as_bytes()).unwrap();
+ }
+
+ let tmp_dir_path = AbsPathBuf::assert(tmp_dir.path().to_path_buf());
+ let mut roots =
+ self.roots.into_iter().map(|root| tmp_dir_path.join(root)).collect::<Vec<_>>();
+ if roots.is_empty() {
+ roots.push(tmp_dir_path.clone());
+ }
+ let discovered_projects = roots
+ .into_iter()
+ .map(|it| ProjectManifest::discover_single(&it).unwrap())
+ .collect::<Vec<_>>();
+
+ let mut config = Config::new(
+ tmp_dir_path,
+ lsp_types::ClientCapabilities {
+ workspace: Some(lsp_types::WorkspaceClientCapabilities {
+ did_change_watched_files: Some(
+ lsp_types::DidChangeWatchedFilesClientCapabilities {
+ dynamic_registration: Some(true),
+ },
+ ),
+ ..Default::default()
+ }),
+ text_document: Some(lsp_types::TextDocumentClientCapabilities {
+ definition: Some(lsp_types::GotoCapability {
+ link_support: Some(true),
+ ..Default::default()
+ }),
+ code_action: Some(lsp_types::CodeActionClientCapabilities {
+ code_action_literal_support: Some(
+ lsp_types::CodeActionLiteralSupport::default(),
+ ),
+ ..Default::default()
+ }),
+ hover: Some(lsp_types::HoverClientCapabilities {
+ content_format: Some(vec![lsp_types::MarkupKind::Markdown]),
+ ..Default::default()
+ }),
+ ..Default::default()
+ }),
+ window: Some(lsp_types::WindowClientCapabilities {
+ work_done_progress: Some(false),
+ ..Default::default()
+ }),
+ experimental: Some(json!({
+ "serverStatusNotification": true,
+ })),
+ ..Default::default()
+ },
+ );
+ config.discovered_projects = Some(discovered_projects);
+ config.update(self.config).expect("invalid config");
+
+ Server::new(tmp_dir, config)
+ }
+}
+
+pub(crate) fn project(fixture: &str) -> Server {
+ Project::with_fixture(fixture).server()
+}
+
+pub(crate) struct Server {
+ req_id: Cell<i32>,
+ messages: RefCell<Vec<Message>>,
+ _thread: jod_thread::JoinHandle<()>,
+ client: Connection,
+ /// XXX: remove the tempdir last
+ dir: TestDir,
+}
+
+impl Server {
+ fn new(dir: TestDir, config: Config) -> Server {
+ let (connection, client) = Connection::memory();
+
+ let _thread = jod_thread::Builder::new()
+ .name("test server".to_string())
+ .spawn(move || main_loop(config, connection).unwrap())
+ .expect("failed to spawn a thread");
+
+ Server { req_id: Cell::new(1), dir, messages: Default::default(), client, _thread }
+ }
+
+ pub(crate) fn doc_id(&self, rel_path: &str) -> TextDocumentIdentifier {
+ let path = self.dir.path().join(rel_path);
+ TextDocumentIdentifier { uri: Url::from_file_path(path).unwrap() }
+ }
+
+ pub(crate) fn notification<N>(&self, params: N::Params)
+ where
+ N: lsp_types::notification::Notification,
+ N::Params: Serialize,
+ {
+ let r = Notification::new(N::METHOD.to_string(), params);
+ self.send_notification(r)
+ }
+
+ #[track_caller]
+ pub(crate) fn request<R>(&self, params: R::Params, expected_resp: Value)
+ where
+ R: lsp_types::request::Request,
+ R::Params: Serialize,
+ {
+ let actual = self.send_request::<R>(params);
+ if let Some((expected_part, actual_part)) = find_mismatch(&expected_resp, &actual) {
+ panic!(
+ "JSON mismatch\nExpected:\n{}\nWas:\n{}\nExpected part:\n{}\nActual part:\n{}\n",
+ to_string_pretty(&expected_resp).unwrap(),
+ to_string_pretty(&actual).unwrap(),
+ to_string_pretty(expected_part).unwrap(),
+ to_string_pretty(actual_part).unwrap(),
+ );
+ }
+ }
+
+ pub(crate) fn send_request<R>(&self, params: R::Params) -> Value
+ where
+ R: lsp_types::request::Request,
+ R::Params: Serialize,
+ {
+ let id = self.req_id.get();
+ self.req_id.set(id.wrapping_add(1));
+
+ let r = Request::new(id.into(), R::METHOD.to_string(), params);
+ self.send_request_(r)
+ }
+ fn send_request_(&self, r: Request) -> Value {
+ let id = r.id.clone();
+ self.client.sender.send(r.clone().into()).unwrap();
+ while let Some(msg) = self.recv().unwrap_or_else(|Timeout| panic!("timeout: {:?}", r)) {
+ match msg {
+ Message::Request(req) => {
+ if req.method == "client/registerCapability" {
+ let params = req.params.to_string();
+ if ["workspace/didChangeWatchedFiles", "textDocument/didSave"]
+ .into_iter()
+ .any(|it| params.contains(it))
+ {
+ continue;
+ }
+ }
+ panic!("unexpected request: {:?}", req)
+ }
+ Message::Notification(_) => (),
+ Message::Response(res) => {
+ assert_eq!(res.id, id);
+ if let Some(err) = res.error {
+ panic!("error response: {:#?}", err);
+ }
+ return res.result.unwrap();
+ }
+ }
+ }
+ panic!("no response for {:?}", r);
+ }
+ pub(crate) fn wait_until_workspace_is_loaded(self) -> Server {
+ self.wait_for_message_cond(1, &|msg: &Message| match msg {
+ Message::Notification(n) if n.method == "experimental/serverStatus" => {
+ let status = n
+ .clone()
+ .extract::<lsp_ext::ServerStatusParams>("experimental/serverStatus")
+ .unwrap();
+ status.quiescent
+ }
+ _ => false,
+ })
+ .unwrap_or_else(|Timeout| panic!("timeout while waiting for ws to load"));
+ self
+ }
+ fn wait_for_message_cond(
+ &self,
+ n: usize,
+ cond: &dyn Fn(&Message) -> bool,
+ ) -> Result<(), Timeout> {
+ let mut total = 0;
+ for msg in self.messages.borrow().iter() {
+ if cond(msg) {
+ total += 1
+ }
+ }
+ while total < n {
+ let msg = self.recv()?.expect("no response");
+ if cond(&msg) {
+ total += 1;
+ }
+ }
+ Ok(())
+ }
+ fn recv(&self) -> Result<Option<Message>, Timeout> {
+ let msg = recv_timeout(&self.client.receiver)?;
+ let msg = msg.map(|msg| {
+ self.messages.borrow_mut().push(msg.clone());
+ msg
+ });
+ Ok(msg)
+ }
+ fn send_notification(&self, not: Notification) {
+ self.client.sender.send(Message::Notification(not)).unwrap();
+ }
+
+ pub(crate) fn path(&self) -> &Path {
+ self.dir.path()
+ }
+}
+
+impl Drop for Server {
+ fn drop(&mut self) {
+ self.request::<Shutdown>((), Value::Null);
+ self.notification::<Exit>(());
+ }
+}
+
+struct Timeout;
+
+fn recv_timeout(receiver: &Receiver<Message>) -> Result<Option<Message>, Timeout> {
+ let timeout =
+ if cfg!(target_os = "macos") { Duration::from_secs(300) } else { Duration::from_secs(120) };
+ select! {
+ recv(receiver) -> msg => Ok(msg.ok()),
+ recv(after(timeout)) -> _ => Err(Timeout),
+ }
+}
+
+// Comparison functionality borrowed from cargo:
+
+/// Compares JSON object for approximate equality.
+/// You can use `[..]` wildcard in strings (useful for OS dependent things such
+/// as paths). You can use a `"{...}"` string literal as a wildcard for
+/// arbitrary nested JSON. Arrays are sorted before comparison.
+fn find_mismatch<'a>(expected: &'a Value, actual: &'a Value) -> Option<(&'a Value, &'a Value)> {
+ match (expected, actual) {
+ (Value::Number(l), Value::Number(r)) if l == r => None,
+ (Value::Bool(l), Value::Bool(r)) if l == r => None,
+ (Value::String(l), Value::String(r)) if lines_match(l, r) => None,
+ (Value::Array(l), Value::Array(r)) => {
+ if l.len() != r.len() {
+ return Some((expected, actual));
+ }
+
+ let mut l = l.iter().collect::<Vec<_>>();
+ let mut r = r.iter().collect::<Vec<_>>();
+
+ l.retain(|l| match r.iter().position(|r| find_mismatch(l, r).is_none()) {
+ Some(i) => {
+ r.remove(i);
+ false
+ }
+ None => true,
+ });
+
+ if !l.is_empty() {
+ assert!(!r.is_empty());
+ Some((l[0], r[0]))
+ } else {
+ assert_eq!(r.len(), 0);
+ None
+ }
+ }
+ (Value::Object(l), Value::Object(r)) => {
+ fn sorted_values(obj: &serde_json::Map<String, Value>) -> Vec<&Value> {
+ let mut entries = obj.iter().collect::<Vec<_>>();
+ entries.sort_by_key(|it| it.0);
+ entries.into_iter().map(|(_k, v)| v).collect::<Vec<_>>()
+ }
+
+ let same_keys = l.len() == r.len() && l.keys().all(|k| r.contains_key(k));
+ if !same_keys {
+ return Some((expected, actual));
+ }
+
+ let l = sorted_values(l);
+ let r = sorted_values(r);
+
+ l.into_iter().zip(r).find_map(|(l, r)| find_mismatch(l, r))
+ }
+ (Value::Null, Value::Null) => None,
+ // magic string literal "{...}" acts as wildcard for any sub-JSON
+ (Value::String(l), _) if l == "{...}" => None,
+ _ => Some((expected, actual)),
+ }
+}
+
+/// Compare a line with an expected pattern.
+/// - Use `[..]` as a wildcard to match 0 or more characters on the same line
+/// (similar to `.*` in a regex).
+fn lines_match(expected: &str, actual: &str) -> bool {
+ // Let's not deal with / vs \ (windows...)
+ // First replace backslash-escaped backslashes with forward slashes
+ // which can occur in, for example, JSON output
+ let expected = expected.replace(r"\\", "/").replace('\\', "/");
+ let mut actual: &str = &actual.replace(r"\\", "/").replace('\\', "/");
+ for (i, part) in expected.split("[..]").enumerate() {
+ match actual.find(part) {
+ Some(j) => {
+ if i == 0 && j != 0 {
+ return false;
+ }
+ actual = &actual[j + part.len()..];
+ }
+ None => return false,
+ }
+ }
+ actual.is_empty() || expected.ends_with("[..]")
+}
+
+#[test]
+fn lines_match_works() {
+ assert!(lines_match("a b", "a b"));
+ assert!(lines_match("a[..]b", "a b"));
+ assert!(lines_match("a[..]", "a b"));
+ assert!(lines_match("[..]", "a b"));
+ assert!(lines_match("[..]b", "a b"));
+
+ assert!(!lines_match("[..]b", "c"));
+ assert!(!lines_match("b", "c"));
+ assert!(!lines_match("b", "cb"));
+}
--- /dev/null
-
- [features]
- in-rust-tree = []
+[package]
+name = "sourcegen"
+version = "0.0.0"
+description = "TBD"
+license = "MIT OR Apache-2.0"
+edition = "2021"
+rust-version = "1.57"
+
+[lib]
+doctest = false
+
+[dependencies]
+xshell = "0.2.2"
--- /dev/null
+//! Various extension methods to ast Nodes, which are hard to code-generate.
+//! Extensions for various expressions live in a sibling `expr_extensions` module.
+//!
+//! These methods should only do simple, shallow tasks related to the syntax of the node itself.
+
+use std::{borrow::Cow, fmt, iter::successors};
+
+use itertools::Itertools;
+use parser::SyntaxKind;
+use rowan::{GreenNodeData, GreenTokenData};
+
+use crate::{
+ ast::{self, support, AstNode, AstToken, HasAttrs, HasGenericParams, HasName, SyntaxNode},
+ NodeOrToken, SmolStr, SyntaxElement, SyntaxToken, TokenText, T,
+};
+
+impl ast::Lifetime {
+ pub fn text(&self) -> TokenText<'_> {
+ text_of_first_token(self.syntax())
+ }
+}
+
+impl ast::Name {
+ pub fn text(&self) -> TokenText<'_> {
+ text_of_first_token(self.syntax())
+ }
+}
+
+impl ast::NameRef {
+ pub fn text(&self) -> TokenText<'_> {
+ text_of_first_token(self.syntax())
+ }
+
+ pub fn as_tuple_field(&self) -> Option<usize> {
+ self.text().parse().ok()
+ }
+
+ pub fn token_kind(&self) -> SyntaxKind {
+ self.syntax().first_token().map_or(SyntaxKind::ERROR, |it| it.kind())
+ }
+}
+
+fn text_of_first_token(node: &SyntaxNode) -> TokenText<'_> {
+ fn first_token(green_ref: &GreenNodeData) -> &GreenTokenData {
+ green_ref.children().next().and_then(NodeOrToken::into_token).unwrap()
+ }
+
+ match node.green() {
+ Cow::Borrowed(green_ref) => TokenText::borrowed(first_token(green_ref).text()),
+ Cow::Owned(green) => TokenText::owned(first_token(&green).to_owned()),
+ }
+}
+
+impl ast::HasModuleItem for ast::StmtList {}
+
+impl ast::BlockExpr {
+ // FIXME: remove all these methods, they belong to ast::StmtList
+ pub fn statements(&self) -> impl Iterator<Item = ast::Stmt> {
+ self.stmt_list().into_iter().flat_map(|it| it.statements())
+ }
+ pub fn tail_expr(&self) -> Option<ast::Expr> {
+ self.stmt_list()?.tail_expr()
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub enum Macro {
+ MacroRules(ast::MacroRules),
+ MacroDef(ast::MacroDef),
+}
+
+impl From<ast::MacroRules> for Macro {
+ fn from(it: ast::MacroRules) -> Self {
+ Macro::MacroRules(it)
+ }
+}
+
+impl From<ast::MacroDef> for Macro {
+ fn from(it: ast::MacroDef) -> Self {
+ Macro::MacroDef(it)
+ }
+}
+
+impl AstNode for Macro {
+ fn can_cast(kind: SyntaxKind) -> bool {
+ matches!(kind, SyntaxKind::MACRO_RULES | SyntaxKind::MACRO_DEF)
+ }
+ fn cast(syntax: SyntaxNode) -> Option<Self> {
+ let res = match syntax.kind() {
+ SyntaxKind::MACRO_RULES => Macro::MacroRules(ast::MacroRules { syntax }),
+ SyntaxKind::MACRO_DEF => Macro::MacroDef(ast::MacroDef { syntax }),
+ _ => return None,
+ };
+ Some(res)
+ }
+ fn syntax(&self) -> &SyntaxNode {
+ match self {
+ Macro::MacroRules(it) => it.syntax(),
+ Macro::MacroDef(it) => it.syntax(),
+ }
+ }
+}
+
+impl HasName for Macro {
+ fn name(&self) -> Option<ast::Name> {
+ match self {
+ Macro::MacroRules(mac) => mac.name(),
+ Macro::MacroDef(mac) => mac.name(),
+ }
+ }
+}
+
+impl HasAttrs for Macro {}
+
+impl From<ast::AssocItem> for ast::Item {
+ fn from(assoc: ast::AssocItem) -> Self {
+ match assoc {
+ ast::AssocItem::Const(it) => ast::Item::Const(it),
+ ast::AssocItem::Fn(it) => ast::Item::Fn(it),
+ ast::AssocItem::MacroCall(it) => ast::Item::MacroCall(it),
+ ast::AssocItem::TypeAlias(it) => ast::Item::TypeAlias(it),
+ }
+ }
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum AttrKind {
+ Inner,
+ Outer,
+}
+
+impl AttrKind {
+ /// Returns `true` if the attr_kind is [`Inner`](Self::Inner).
+ pub fn is_inner(&self) -> bool {
+ matches!(self, Self::Inner)
+ }
+
+ /// Returns `true` if the attr_kind is [`Outer`](Self::Outer).
+ pub fn is_outer(&self) -> bool {
+ matches!(self, Self::Outer)
+ }
+}
+
+impl ast::Attr {
+ pub fn as_simple_atom(&self) -> Option<SmolStr> {
+ let meta = self.meta()?;
+ if meta.eq_token().is_some() || meta.token_tree().is_some() {
+ return None;
+ }
+ self.simple_name()
+ }
+
+ pub fn as_simple_call(&self) -> Option<(SmolStr, ast::TokenTree)> {
+ let tt = self.meta()?.token_tree()?;
+ Some((self.simple_name()?, tt))
+ }
+
+ pub fn simple_name(&self) -> Option<SmolStr> {
+ let path = self.meta()?.path()?;
+ match (path.segment(), path.qualifier()) {
+ (Some(segment), None) => Some(segment.syntax().first_token()?.text().into()),
+ _ => None,
+ }
+ }
+
+ pub fn kind(&self) -> AttrKind {
+ match self.excl_token() {
+ Some(_) => AttrKind::Inner,
+ None => AttrKind::Outer,
+ }
+ }
+
+ pub fn path(&self) -> Option<ast::Path> {
+ self.meta()?.path()
+ }
+
+ pub fn expr(&self) -> Option<ast::Expr> {
+ self.meta()?.expr()
+ }
+
+ pub fn token_tree(&self) -> Option<ast::TokenTree> {
+ self.meta()?.token_tree()
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum PathSegmentKind {
+ Name(ast::NameRef),
+ Type { type_ref: Option<ast::Type>, trait_ref: Option<ast::PathType> },
+ SelfTypeKw,
+ SelfKw,
+ SuperKw,
+ CrateKw,
+}
+
+impl ast::PathSegment {
+ pub fn parent_path(&self) -> ast::Path {
+ self.syntax()
+ .parent()
+ .and_then(ast::Path::cast)
+ .expect("segments are always nested in paths")
+ }
+
+ pub fn crate_token(&self) -> Option<SyntaxToken> {
+ self.name_ref().and_then(|it| it.crate_token())
+ }
+
+ pub fn self_token(&self) -> Option<SyntaxToken> {
+ self.name_ref().and_then(|it| it.self_token())
+ }
+
+ pub fn self_type_token(&self) -> Option<SyntaxToken> {
+ self.name_ref().and_then(|it| it.Self_token())
+ }
+
+ pub fn super_token(&self) -> Option<SyntaxToken> {
+ self.name_ref().and_then(|it| it.super_token())
+ }
+
+ pub fn kind(&self) -> Option<PathSegmentKind> {
+ let res = if let Some(name_ref) = self.name_ref() {
+ match name_ref.token_kind() {
+ T![Self] => PathSegmentKind::SelfTypeKw,
+ T![self] => PathSegmentKind::SelfKw,
+ T![super] => PathSegmentKind::SuperKw,
+ T![crate] => PathSegmentKind::CrateKw,
+ _ => PathSegmentKind::Name(name_ref),
+ }
+ } else {
+ match self.syntax().first_child_or_token()?.kind() {
+ T![<] => {
+ // <T> or <T as Trait>
+ // T is any TypeRef, Trait has to be a PathType
+ let mut type_refs =
+ self.syntax().children().filter(|node| ast::Type::can_cast(node.kind()));
+ let type_ref = type_refs.next().and_then(ast::Type::cast);
+ let trait_ref = type_refs.next().and_then(ast::PathType::cast);
+ PathSegmentKind::Type { type_ref, trait_ref }
+ }
+ _ => return None,
+ }
+ };
+ Some(res)
+ }
+}
+
+impl ast::Path {
+ pub fn parent_path(&self) -> Option<ast::Path> {
+ self.syntax().parent().and_then(ast::Path::cast)
+ }
+
+ pub fn as_single_segment(&self) -> Option<ast::PathSegment> {
+ match self.qualifier() {
+ Some(_) => None,
+ None => self.segment(),
+ }
+ }
+
+ pub fn as_single_name_ref(&self) -> Option<ast::NameRef> {
+ match self.qualifier() {
+ Some(_) => None,
+ None => self.segment()?.name_ref(),
+ }
+ }
+
+ pub fn first_qualifier_or_self(&self) -> ast::Path {
+ successors(Some(self.clone()), ast::Path::qualifier).last().unwrap()
+ }
+
+ pub fn first_segment(&self) -> Option<ast::PathSegment> {
+ self.first_qualifier_or_self().segment()
+ }
+
+ pub fn segments(&self) -> impl Iterator<Item = ast::PathSegment> + Clone {
+ successors(self.first_segment(), |p| {
+ p.parent_path().parent_path().and_then(|p| p.segment())
+ })
+ }
+
+ pub fn qualifiers(&self) -> impl Iterator<Item = ast::Path> + Clone {
+ successors(self.qualifier(), |p| p.qualifier())
+ }
+
+ pub fn top_path(&self) -> ast::Path {
+ let mut this = self.clone();
+ while let Some(path) = this.parent_path() {
+ this = path;
+ }
+ this
+ }
+}
+
+impl ast::Use {
+ pub fn is_simple_glob(&self) -> bool {
+ self.use_tree().map_or(false, |use_tree| {
+ use_tree.use_tree_list().is_none() && use_tree.star_token().is_some()
+ })
+ }
+}
+
+impl ast::UseTree {
+ pub fn is_simple_path(&self) -> bool {
+ self.use_tree_list().is_none() && self.star_token().is_none()
+ }
+}
+
+impl ast::UseTreeList {
+ pub fn parent_use_tree(&self) -> ast::UseTree {
+ self.syntax()
+ .parent()
+ .and_then(ast::UseTree::cast)
+ .expect("UseTreeLists are always nested in UseTrees")
+ }
+
+ pub fn has_inner_comment(&self) -> bool {
+ self.syntax()
+ .children_with_tokens()
+ .filter_map(|it| it.into_token())
+ .find_map(ast::Comment::cast)
+ .is_some()
+ }
+}
+
+impl ast::Impl {
+ pub fn self_ty(&self) -> Option<ast::Type> {
+ match self.target() {
+ (Some(t), None) | (_, Some(t)) => Some(t),
+ _ => None,
+ }
+ }
+
+ pub fn trait_(&self) -> Option<ast::Type> {
+ match self.target() {
+ (Some(t), Some(_)) => Some(t),
+ _ => None,
+ }
+ }
+
+ fn target(&self) -> (Option<ast::Type>, Option<ast::Type>) {
+ let mut types = support::children(self.syntax());
+ let first = types.next();
+ let second = types.next();
+ (first, second)
+ }
+
+ pub fn for_trait_name_ref(name_ref: &ast::NameRef) -> Option<ast::Impl> {
+ let this = name_ref.syntax().ancestors().find_map(ast::Impl::cast)?;
+ if this.trait_()?.syntax().text_range().start() == name_ref.syntax().text_range().start() {
+ Some(this)
+ } else {
+ None
+ }
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum StructKind {
+ Record(ast::RecordFieldList),
+ Tuple(ast::TupleFieldList),
+ Unit,
+}
+
+impl StructKind {
+ fn from_node<N: AstNode>(node: &N) -> StructKind {
+ if let Some(nfdl) = support::child::<ast::RecordFieldList>(node.syntax()) {
+ StructKind::Record(nfdl)
+ } else if let Some(pfl) = support::child::<ast::TupleFieldList>(node.syntax()) {
+ StructKind::Tuple(pfl)
+ } else {
+ StructKind::Unit
+ }
+ }
+}
+
+impl ast::Struct {
+ pub fn kind(&self) -> StructKind {
+ StructKind::from_node(self)
+ }
+}
+
+impl ast::RecordExprField {
+ pub fn for_field_name(field_name: &ast::NameRef) -> Option<ast::RecordExprField> {
+ let candidate = Self::for_name_ref(field_name)?;
+ if candidate.field_name().as_ref() == Some(field_name) {
+ Some(candidate)
+ } else {
+ None
+ }
+ }
+
+ pub fn for_name_ref(name_ref: &ast::NameRef) -> Option<ast::RecordExprField> {
+ let syn = name_ref.syntax();
+ syn.parent()
+ .and_then(ast::RecordExprField::cast)
+ .or_else(|| syn.ancestors().nth(4).and_then(ast::RecordExprField::cast))
+ }
+
+ /// Deals with field init shorthand
+ pub fn field_name(&self) -> Option<ast::NameRef> {
+ if let Some(name_ref) = self.name_ref() {
+ return Some(name_ref);
+ }
+ if let ast::Expr::PathExpr(expr) = self.expr()? {
+ let path = expr.path()?;
+ let segment = path.segment()?;
+ let name_ref = segment.name_ref()?;
+ if path.qualifier().is_none() {
+ return Some(name_ref);
+ }
+ }
+ None
+ }
+}
+
+#[derive(Debug, Clone)]
+pub enum NameLike {
+ NameRef(ast::NameRef),
+ Name(ast::Name),
+ Lifetime(ast::Lifetime),
+}
+
+impl NameLike {
+ pub fn as_name_ref(&self) -> Option<&ast::NameRef> {
+ match self {
+ NameLike::NameRef(name_ref) => Some(name_ref),
+ _ => None,
+ }
+ }
+ pub fn as_lifetime(&self) -> Option<&ast::Lifetime> {
+ match self {
+ NameLike::Lifetime(lifetime) => Some(lifetime),
+ _ => None,
+ }
+ }
+ pub fn text(&self) -> TokenText<'_> {
+ match self {
+ NameLike::NameRef(name_ref) => name_ref.text(),
+ NameLike::Name(name) => name.text(),
+ NameLike::Lifetime(lifetime) => lifetime.text(),
+ }
+ }
+}
+
+impl ast::AstNode for NameLike {
+ fn can_cast(kind: SyntaxKind) -> bool {
+ matches!(kind, SyntaxKind::NAME | SyntaxKind::NAME_REF | SyntaxKind::LIFETIME)
+ }
+ fn cast(syntax: SyntaxNode) -> Option<Self> {
+ let res = match syntax.kind() {
+ SyntaxKind::NAME => NameLike::Name(ast::Name { syntax }),
+ SyntaxKind::NAME_REF => NameLike::NameRef(ast::NameRef { syntax }),
+ SyntaxKind::LIFETIME => NameLike::Lifetime(ast::Lifetime { syntax }),
+ _ => return None,
+ };
+ Some(res)
+ }
+ fn syntax(&self) -> &SyntaxNode {
+ match self {
+ NameLike::NameRef(it) => it.syntax(),
+ NameLike::Name(it) => it.syntax(),
+ NameLike::Lifetime(it) => it.syntax(),
+ }
+ }
+}
+
+const _: () = {
+ use ast::{Lifetime, Name, NameRef};
+ stdx::impl_from!(NameRef, Name, Lifetime for NameLike);
+};
+
+#[derive(Debug, Clone, PartialEq)]
+pub enum NameOrNameRef {
+ Name(ast::Name),
+ NameRef(ast::NameRef),
+}
+
+impl fmt::Display for NameOrNameRef {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ NameOrNameRef::Name(it) => fmt::Display::fmt(it, f),
+ NameOrNameRef::NameRef(it) => fmt::Display::fmt(it, f),
+ }
+ }
+}
+
+impl NameOrNameRef {
+ pub fn text(&self) -> TokenText<'_> {
+ match self {
+ NameOrNameRef::Name(name) => name.text(),
+ NameOrNameRef::NameRef(name_ref) => name_ref.text(),
+ }
+ }
+}
+
+impl ast::RecordPatField {
+ pub fn for_field_name_ref(field_name: &ast::NameRef) -> Option<ast::RecordPatField> {
+ let candidate = field_name.syntax().parent().and_then(ast::RecordPatField::cast)?;
+ match candidate.field_name()? {
+ NameOrNameRef::NameRef(name_ref) if name_ref == *field_name => Some(candidate),
+ _ => None,
+ }
+ }
+
+ pub fn for_field_name(field_name: &ast::Name) -> Option<ast::RecordPatField> {
+ let candidate =
+ field_name.syntax().ancestors().nth(2).and_then(ast::RecordPatField::cast)?;
+ match candidate.field_name()? {
+ NameOrNameRef::Name(name) if name == *field_name => Some(candidate),
+ _ => None,
+ }
+ }
+
+ pub fn parent_record_pat(&self) -> ast::RecordPat {
+ self.syntax().ancestors().find_map(ast::RecordPat::cast).unwrap()
+ }
+
+ /// Deals with field init shorthand
+ pub fn field_name(&self) -> Option<NameOrNameRef> {
+ if let Some(name_ref) = self.name_ref() {
+ return Some(NameOrNameRef::NameRef(name_ref));
+ }
+ match self.pat() {
+ Some(ast::Pat::IdentPat(pat)) => {
+ let name = pat.name()?;
+ Some(NameOrNameRef::Name(name))
+ }
+ Some(ast::Pat::BoxPat(pat)) => match pat.pat() {
+ Some(ast::Pat::IdentPat(pat)) => {
+ let name = pat.name()?;
+ Some(NameOrNameRef::Name(name))
+ }
+ _ => None,
+ },
+ _ => None,
+ }
+ }
+}
+
+impl ast::Variant {
+ pub fn parent_enum(&self) -> ast::Enum {
+ self.syntax()
+ .parent()
+ .and_then(|it| it.parent())
+ .and_then(ast::Enum::cast)
+ .expect("EnumVariants are always nested in Enums")
+ }
+ pub fn kind(&self) -> StructKind {
+ StructKind::from_node(self)
+ }
+}
+
+impl ast::Item {
+ pub fn generic_param_list(&self) -> Option<ast::GenericParamList> {
+ ast::AnyHasGenericParams::cast(self.syntax().clone())?.generic_param_list()
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum FieldKind {
+ Name(ast::NameRef),
+ Index(SyntaxToken),
+}
+
+impl ast::FieldExpr {
+ pub fn index_token(&self) -> Option<SyntaxToken> {
+ self.syntax
+ .children_with_tokens()
+ // FIXME: Accepting floats here to reject them in validation later
+ .find(|c| c.kind() == SyntaxKind::INT_NUMBER || c.kind() == SyntaxKind::FLOAT_NUMBER)
+ .as_ref()
+ .and_then(SyntaxElement::as_token)
+ .cloned()
+ }
+
+ pub fn field_access(&self) -> Option<FieldKind> {
+ match self.name_ref() {
+ Some(nr) => Some(FieldKind::Name(nr)),
+ None => self.index_token().map(FieldKind::Index),
+ }
+ }
+}
+
+pub struct SlicePatComponents {
+ pub prefix: Vec<ast::Pat>,
+ pub slice: Option<ast::Pat>,
+ pub suffix: Vec<ast::Pat>,
+}
+
+impl ast::SlicePat {
+ pub fn components(&self) -> SlicePatComponents {
+ let mut args = self.pats().peekable();
+ let prefix = args
+ .peeking_take_while(|p| match p {
+ ast::Pat::RestPat(_) => false,
+ ast::Pat::IdentPat(bp) => !matches!(bp.pat(), Some(ast::Pat::RestPat(_))),
+ ast::Pat::RefPat(rp) => match rp.pat() {
+ Some(ast::Pat::RestPat(_)) => false,
+ Some(ast::Pat::IdentPat(bp)) => !matches!(bp.pat(), Some(ast::Pat::RestPat(_))),
+ _ => true,
+ },
+ _ => true,
+ })
+ .collect();
+ let slice = args.next();
+ let suffix = args.collect();
+
+ SlicePatComponents { prefix, slice, suffix }
+ }
+}
+
+impl ast::IdentPat {
+ pub fn is_simple_ident(&self) -> bool {
+ self.at_token().is_none()
+ && self.mut_token().is_none()
+ && self.ref_token().is_none()
+ && self.pat().is_none()
+ }
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
+pub enum SelfParamKind {
+ /// self
+ Owned,
+ /// &self
+ Ref,
+ /// &mut self
+ MutRef,
+}
+
+impl ast::SelfParam {
+ pub fn kind(&self) -> SelfParamKind {
+ if self.amp_token().is_some() {
+ if self.mut_token().is_some() {
+ SelfParamKind::MutRef
+ } else {
+ SelfParamKind::Ref
+ }
+ } else {
+ SelfParamKind::Owned
+ }
+ }
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Hash)]
+pub enum TypeBoundKind {
+ /// Trait
+ PathType(ast::PathType),
+ /// for<'a> ...
+ ForType(ast::ForType),
+ /// 'a
+ Lifetime(ast::Lifetime),
+}
+
+impl ast::TypeBound {
+ pub fn kind(&self) -> TypeBoundKind {
+ if let Some(path_type) = support::children(self.syntax()).next() {
+ TypeBoundKind::PathType(path_type)
+ } else if let Some(for_type) = support::children(self.syntax()).next() {
+ TypeBoundKind::ForType(for_type)
+ } else if let Some(lifetime) = self.lifetime() {
+ TypeBoundKind::Lifetime(lifetime)
+ } else {
+ unreachable!()
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+pub enum TypeOrConstParam {
+ Type(ast::TypeParam),
+ Const(ast::ConstParam),
+}
+
+impl TypeOrConstParam {
+ pub fn name(&self) -> Option<ast::Name> {
+ match self {
+ TypeOrConstParam::Type(x) => x.name(),
+ TypeOrConstParam::Const(x) => x.name(),
+ }
+ }
+}
+
+pub enum VisibilityKind {
+ In(ast::Path),
+ PubCrate,
+ PubSuper,
+ PubSelf,
+ Pub,
+}
+
+impl ast::Visibility {
+ pub fn kind(&self) -> VisibilityKind {
+ match self.path() {
+ Some(path) => {
+ if let Some(segment) =
+ path.as_single_segment().filter(|it| it.coloncolon_token().is_none())
+ {
+ if segment.crate_token().is_some() {
+ return VisibilityKind::PubCrate;
+ } else if segment.super_token().is_some() {
+ return VisibilityKind::PubSuper;
+ } else if segment.self_token().is_some() {
+ return VisibilityKind::PubSelf;
+ }
+ }
+ VisibilityKind::In(path)
+ }
+ None => VisibilityKind::Pub,
+ }
+ }
+}
+
+impl ast::LifetimeParam {
+ pub fn lifetime_bounds(&self) -> impl Iterator<Item = SyntaxToken> {
+ self.syntax()
+ .children_with_tokens()
+ .filter_map(|it| it.into_token())
+ .skip_while(|x| x.kind() != T![:])
+ .filter(|it| it.kind() == T![lifetime_ident])
+ }
+}
+
+impl ast::Module {
+ /// Returns the parent ast::Module, this is different than the semantic parent in that this only
+ /// considers parent declarations in the AST
+ pub fn parent(&self) -> Option<ast::Module> {
+ self.syntax().ancestors().nth(2).and_then(ast::Module::cast)
+ }
+}
+
+impl ast::RangePat {
+ pub fn start(&self) -> Option<ast::Pat> {
+ self.syntax()
+ .children_with_tokens()
+ .take_while(|it| !(it.kind() == T![..] || it.kind() == T![..=]))
+ .filter_map(|it| it.into_node())
+ .find_map(ast::Pat::cast)
+ }
+
+ pub fn end(&self) -> Option<ast::Pat> {
+ self.syntax()
+ .children_with_tokens()
+ .skip_while(|it| !(it.kind() == T![..] || it.kind() == T![..=]))
+ .filter_map(|it| it.into_node())
+ .find_map(ast::Pat::cast)
+ }
+}
+
+impl ast::TokenTree {
+ pub fn token_trees_and_tokens(
+ &self,
+ ) -> impl Iterator<Item = NodeOrToken<ast::TokenTree, SyntaxToken>> {
+ self.syntax().children_with_tokens().filter_map(|not| match not {
+ NodeOrToken::Node(node) => ast::TokenTree::cast(node).map(NodeOrToken::Node),
+ NodeOrToken::Token(t) => Some(NodeOrToken::Token(t)),
+ })
+ }
+
+ pub fn left_delimiter_token(&self) -> Option<SyntaxToken> {
+ self.syntax()
+ .first_child_or_token()?
+ .into_token()
+ .filter(|it| matches!(it.kind(), T!['{'] | T!['('] | T!['[']))
+ }
+
+ pub fn right_delimiter_token(&self) -> Option<SyntaxToken> {
+ self.syntax()
+ .last_child_or_token()?
+ .into_token()
+ .filter(|it| matches!(it.kind(), T!['}'] | T![')'] | T![']']))
+ }
+
+ pub fn parent_meta(&self) -> Option<ast::Meta> {
+ self.syntax().parent().and_then(ast::Meta::cast)
+ }
+}
+
+impl ast::Meta {
+ pub fn parent_attr(&self) -> Option<ast::Attr> {
+ self.syntax().parent().and_then(ast::Attr::cast)
+ }
+}
+
+impl ast::GenericArgList {
+ pub fn lifetime_args(&self) -> impl Iterator<Item = ast::LifetimeArg> {
+ self.generic_args().filter_map(|arg| match arg {
+ ast::GenericArg::LifetimeArg(it) => Some(it),
+ _ => None,
+ })
+ }
+}
+
+impl ast::GenericParamList {
+ pub fn lifetime_params(&self) -> impl Iterator<Item = ast::LifetimeParam> {
+ self.generic_params().filter_map(|param| match param {
+ ast::GenericParam::LifetimeParam(it) => Some(it),
+ ast::GenericParam::TypeParam(_) | ast::GenericParam::ConstParam(_) => None,
+ })
+ }
+ pub fn type_or_const_params(&self) -> impl Iterator<Item = ast::TypeOrConstParam> {
+ self.generic_params().filter_map(|param| match param {
+ ast::GenericParam::TypeParam(it) => Some(ast::TypeOrConstParam::Type(it)),
+ ast::GenericParam::LifetimeParam(_) => None,
+ ast::GenericParam::ConstParam(it) => Some(ast::TypeOrConstParam::Const(it)),
+ })
+ }
+}
+
+impl ast::ForExpr {
+ pub fn iterable(&self) -> Option<ast::Expr> {
+ // If the iterable is a BlockExpr, check if the body is missing.
+ // If it is assume the iterable is the expression that is missing instead.
+ let mut exprs = support::children(self.syntax());
+ let first = exprs.next();
+ match first {
+ Some(ast::Expr::BlockExpr(_)) => exprs.next().and(first),
+ first => first,
+ }
+ }
+}
+
+impl ast::HasLoopBody for ast::ForExpr {
+ fn loop_body(&self) -> Option<ast::BlockExpr> {
+ let mut exprs = support::children(self.syntax());
+ let first = exprs.next();
+ let second = exprs.next();
+ second.or(first)
+ }
+}
+
+impl ast::WhileExpr {
+ pub fn condition(&self) -> Option<ast::Expr> {
+ // If the condition is a BlockExpr, check if the body is missing.
+ // If it is assume the condition is the expression that is missing instead.
+ let mut exprs = support::children(self.syntax());
+ let first = exprs.next();
+ match first {
+ Some(ast::Expr::BlockExpr(_)) => exprs.next().and(first),
+ first => first,
+ }
+ }
+}
+
+impl ast::HasLoopBody for ast::WhileExpr {
+ fn loop_body(&self) -> Option<ast::BlockExpr> {
+ let mut exprs = support::children(self.syntax());
+ let first = exprs.next();
+ let second = exprs.next();
+ second.or(first)
+ }
+}
+
+impl ast::HasAttrs for ast::AnyHasDocComments {}
+
+impl From<ast::Adt> for ast::Item {
+ fn from(it: ast::Adt) -> Self {
+ match it {
+ ast::Adt::Enum(it) => ast::Item::Enum(it),
+ ast::Adt::Struct(it) => ast::Item::Struct(it),
+ ast::Adt::Union(it) => ast::Item::Union(it),
+ }
+ }
+}
+
+impl ast::IfExpr {
+ pub fn condition(&self) -> Option<ast::Expr> {
+ support::child(&self.syntax)
+ }
+}
+
+impl ast::MatchGuard {
+ pub fn condition(&self) -> Option<ast::Expr> {
+ support::child(&self.syntax)
+ }
+}
++
++impl From<ast::Item> for ast::AnyHasAttrs {
++ fn from(node: ast::Item) -> Self {
++ Self::new(node)
++ }
++}
++
++impl From<ast::AssocItem> for ast::AnyHasAttrs {
++ fn from(node: ast::AssocItem) -> Self {
++ Self::new(node)
++ }
++}
++
++impl From<ast::Variant> for ast::AnyHasAttrs {
++ fn from(node: ast::Variant) -> Self {
++ Self::new(node)
++ }
++}
++
++impl From<ast::RecordField> for ast::AnyHasAttrs {
++ fn from(node: ast::RecordField) -> Self {
++ Self::new(node)
++ }
++}
++
++impl From<ast::TupleField> for ast::AnyHasAttrs {
++ fn from(node: ast::TupleField) -> Self {
++ Self::new(node)
++ }
++}
--- /dev/null
+//! This is a fixture we use for tests that need lang items.
+//!
+//! We want to include the minimal subset of core for each test, so this file
+//! supports "conditional compilation". Tests use the following syntax to include minicore:
+//!
+//! //- minicore: flag1, flag2
+//!
+//! We then strip all the code marked with other flags.
+//!
+//! Available flags:
+//! sized:
+//! unsize: sized
+//! coerce_unsized: unsize
+//! slice:
+//! range:
+//! deref: sized
+//! deref_mut: deref
+//! index: sized
+//! fn:
+//! try:
+//! pin:
+//! future: pin
+//! option:
+//! result:
+//! iterator: option
+//! iterators: iterator, fn
+//! default: sized
+//! hash:
+//! clone: sized
+//! copy: clone
+//! from: sized
+//! eq: sized
+//! ord: eq, option
+//! derive:
+//! fmt: result
+//! bool_impl: option, fn
+//! add:
+//! as_ref: sized
+//! drop:
++//! generator: pin
+
+pub mod marker {
+ // region:sized
+ #[lang = "sized"]
+ #[fundamental]
+ #[rustc_specialization_trait]
+ pub trait Sized {}
+ // endregion:sized
+
+ // region:unsize
+ #[lang = "unsize"]
+ pub trait Unsize<T: ?Sized> {}
+ // endregion:unsize
+
+ // region:copy
+ #[lang = "copy"]
+ pub trait Copy: Clone {}
+ // region:derive
+ #[rustc_builtin_macro]
+ pub macro Copy($item:item) {}
+ // endregion:derive
+
+ mod copy_impls {
+ use super::Copy;
+
+ macro_rules! impl_copy {
+ ($($t:ty)*) => {
+ $(
+ impl Copy for $t {}
+ )*
+ }
+ }
+
+ impl_copy! {
+ usize u8 u16 u32 u64 u128
+ isize i8 i16 i32 i64 i128
+ f32 f64
+ bool char
+ }
+
+ impl<T: ?Sized> Copy for *const T {}
+ impl<T: ?Sized> Copy for *mut T {}
+ impl<T: ?Sized> Copy for &T {}
+ }
+ // endregion:copy
+}
+
+// region:default
+pub mod default {
+ pub trait Default: Sized {
+ fn default() -> Self;
+ }
+ // region:derive
+ #[rustc_builtin_macro]
+ pub macro Default($item:item) {}
+ // endregion:derive
+}
+// endregion:default
+
+// region:hash
+pub mod hash {
+ pub trait Hasher {}
+
+ pub trait Hash {
+ fn hash<H: Hasher>(&self, state: &mut H);
+ }
+}
+// endregion:hash
+
+// region:clone
+pub mod clone {
+ #[lang = "clone"]
+ pub trait Clone: Sized {
+ fn clone(&self) -> Self;
+ }
+ // region:derive
+ #[rustc_builtin_macro]
+ pub macro Clone($item:item) {}
+ // endregion:derive
+}
+// endregion:clone
+
+pub mod convert {
+ // region:from
+ pub trait From<T>: Sized {
+ fn from(_: T) -> Self;
+ }
+ pub trait Into<T>: Sized {
+ fn into(self) -> T;
+ }
+
+ impl<T, U> Into<U> for T
+ where
+ U: From<T>,
+ {
+ fn into(self) -> U {
+ U::from(self)
+ }
+ }
+
+ impl<T> From<T> for T {
+ fn from(t: T) -> T {
+ t
+ }
+ }
+ // endregion:from
+
+ // region:as_ref
+ pub trait AsRef<T: ?Sized> {
+ fn as_ref(&self) -> &T;
+ }
+ // endregion:as_ref
+}
+
+pub mod ops {
+ // region:coerce_unsized
+ mod unsize {
+ use crate::marker::Unsize;
+
+ #[lang = "coerce_unsized"]
+ pub trait CoerceUnsized<T: ?Sized> {}
+
+ impl<'a, T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<&'a mut U> for &'a mut T {}
+ impl<'a, 'b: 'a, T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<&'a U> for &'b mut T {}
+ impl<'a, T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<*mut U> for &'a mut T {}
+ impl<'a, T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<*const U> for &'a mut T {}
+
+ impl<'a, 'b: 'a, T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<&'a U> for &'b T {}
+ impl<'a, T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<*const U> for &'a T {}
+
+ impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<*mut U> for *mut T {}
+ impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<*const U> for *mut T {}
+ impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<*const U> for *const T {}
+ }
+ pub use self::unsize::CoerceUnsized;
+ // endregion:coerce_unsized
+
+ // region:deref
+ mod deref {
+ #[lang = "deref"]
+ pub trait Deref {
+ #[lang = "deref_target"]
+ type Target: ?Sized;
+ fn deref(&self) -> &Self::Target;
+ }
++
++ impl<T: ?Sized> Deref for &T {
++ type Target = T;
++ fn deref(&self) -> &T {
++ loop {}
++ }
++ }
++ impl<T: ?Sized> Deref for &mut T {
++ type Target = T;
++ fn deref(&self) -> &T {
++ loop {}
++ }
++ }
+ // region:deref_mut
+ #[lang = "deref_mut"]
+ pub trait DerefMut: Deref {
+ fn deref_mut(&mut self) -> &mut Self::Target;
+ }
+ // endregion:deref_mut
+ }
+ pub use self::deref::{
+ Deref,
+ DerefMut, // :deref_mut
+ };
+ // endregion:deref
+
+ // region:drop
+ #[lang = "drop"]
+ pub trait Drop {
+ fn drop(&mut self);
+ }
+ // endregion:drop
+
+ // region:index
+ mod index {
+ #[lang = "index"]
+ pub trait Index<Idx: ?Sized> {
+ type Output: ?Sized;
+ fn index(&self, index: Idx) -> &Self::Output;
+ }
+ #[lang = "index_mut"]
+ pub trait IndexMut<Idx: ?Sized>: Index<Idx> {
+ fn index_mut(&mut self, index: Idx) -> &mut Self::Output;
+ }
+
+ // region:slice
+ impl<T, I> Index<I> for [T]
+ where
+ I: SliceIndex<[T]>,
+ {
+ type Output = I::Output;
+ fn index(&self, index: I) -> &I::Output {
+ loop {}
+ }
+ }
+ impl<T, I> IndexMut<I> for [T]
+ where
+ I: SliceIndex<[T]>,
+ {
+ fn index_mut(&mut self, index: I) -> &mut I::Output {
+ loop {}
+ }
+ }
+
+ pub unsafe trait SliceIndex<T: ?Sized> {
+ type Output: ?Sized;
+ }
+ unsafe impl<T> SliceIndex<[T]> for usize {
+ type Output = T;
+ }
+ // endregion:slice
+ }
+ pub use self::index::{Index, IndexMut};
+ // endregion:index
+
+ // region:drop
+ pub mod mem {
+ pub fn drop<T>(_x: T) {}
+ }
+ // endregion:drop
+
+ // region:range
+ mod range {
+ #[lang = "RangeFull"]
+ pub struct RangeFull;
+
+ #[lang = "Range"]
+ pub struct Range<Idx> {
+ pub start: Idx,
+ pub end: Idx,
+ }
+
+ #[lang = "RangeFrom"]
+ pub struct RangeFrom<Idx> {
+ pub start: Idx,
+ }
+
+ #[lang = "RangeTo"]
+ pub struct RangeTo<Idx> {
+ pub end: Idx,
+ }
+
+ #[lang = "RangeInclusive"]
+ pub struct RangeInclusive<Idx> {
+ pub(crate) start: Idx,
+ pub(crate) end: Idx,
+ pub(crate) exhausted: bool,
+ }
+
+ #[lang = "RangeToInclusive"]
+ pub struct RangeToInclusive<Idx> {
+ pub end: Idx,
+ }
+ }
+ pub use self::range::{Range, RangeFrom, RangeFull, RangeTo};
+ pub use self::range::{RangeInclusive, RangeToInclusive};
+ // endregion:range
+
+ // region:fn
+ mod function {
+ #[lang = "fn"]
+ #[fundamental]
+ pub trait Fn<Args>: FnMut<Args> {}
+
+ #[lang = "fn_mut"]
+ #[fundamental]
+ pub trait FnMut<Args>: FnOnce<Args> {}
+
+ #[lang = "fn_once"]
+ #[fundamental]
+ pub trait FnOnce<Args> {
+ #[lang = "fn_once_output"]
+ type Output;
+ }
+ }
+ pub use self::function::{Fn, FnMut, FnOnce};
+ // endregion:fn
+ // region:try
+ mod try_ {
+ pub enum ControlFlow<B, C = ()> {
+ Continue(C),
+ Break(B),
+ }
+ pub trait FromResidual<R = Self::Residual> {
+ #[lang = "from_residual"]
+ fn from_residual(residual: R) -> Self;
+ }
+ #[lang = "try"]
+ pub trait Try: FromResidual<Self::Residual> {
+ type Output;
+ type Residual;
+ #[lang = "from_output"]
+ fn from_output(output: Self::Output) -> Self;
+ #[lang = "branch"]
+ fn branch(self) -> ControlFlow<Self::Residual, Self::Output>;
+ }
+
+ impl<B, C> Try for ControlFlow<B, C> {
+ type Output = C;
+ type Residual = ControlFlow<B, convert::Infallible>;
+ fn from_output(output: Self::Output) -> Self {}
+ fn branch(self) -> ControlFlow<Self::Residual, Self::Output> {}
+ }
+
+ impl<B, C> FromResidual for ControlFlow<B, C> {
+ fn from_residual(residual: ControlFlow<B, convert::Infallible>) -> Self {}
+ }
+ }
+ pub use self::try_::{ControlFlow, FromResidual, Try};
+ // endregion:try
+
+ // region:add
+ #[lang = "add"]
+ pub trait Add<Rhs = Self> {
+ type Output;
+ fn add(self, rhs: Rhs) -> Self::Output;
+ }
+ // endregion:add
++
++ // region:generator
++ mod generator {
++ use crate::pin::Pin;
++
++ #[lang = "generator"]
++ pub trait Generator<R = ()> {
++ type Yield;
++ #[lang = "generator_return"]
++ type Return;
++ fn resume(self: Pin<&mut Self>, arg: R) -> GeneratorState<Self::Yield, Self::Return>;
++ }
++
++ #[lang = "generator_state"]
++ pub enum GeneratorState<Y, R> {
++ Yielded(Y),
++ Complete(R),
++ }
++ }
++ pub use self::generator::{Generator, GeneratorState};
++ // endregion:generator
+}
+
+// region:eq
+pub mod cmp {
+ #[lang = "eq"]
+ pub trait PartialEq<Rhs: ?Sized = Self> {
+ fn eq(&self, other: &Rhs) -> bool;
+ fn ne(&self, other: &Rhs) -> bool {
+ !self.eq(other)
+ }
+ }
+
+ pub trait Eq: PartialEq<Self> {}
+
+ // region:derive
+ #[rustc_builtin_macro]
+ pub macro PartialEq($item:item) {}
+ #[rustc_builtin_macro]
+ pub macro Eq($item:item) {}
+ // endregion:derive
+
+ // region:ord
+ #[lang = "partial_ord"]
+ pub trait PartialOrd<Rhs: ?Sized = Self>: PartialEq<Rhs> {
+ fn partial_cmp(&self, other: &Rhs) -> Option<Ordering>;
+ }
+
+ pub trait Ord: Eq + PartialOrd<Self> {
+ fn cmp(&self, other: &Self) -> Ordering;
+ }
+
+ pub enum Ordering {
+ Less = -1,
+ Equal = 0,
+ Greater = 1,
+ }
+
+ // region:derive
+ #[rustc_builtin_macro]
+ pub macro PartialOrd($item:item) {}
+ #[rustc_builtin_macro]
+ pub macro Ord($item:item) {}
+ // endregion:derive
+
+ // endregion:ord
+}
+// endregion:eq
+
+// region:fmt
+pub mod fmt {
+ pub struct Error;
+ pub type Result = Result<(), Error>;
+ pub struct Formatter<'a>;
+ pub trait Debug {
+ fn fmt(&self, f: &mut Formatter<'_>) -> Result;
+ }
+}
+// endregion:fmt
+
+// region:slice
+pub mod slice {
+ #[lang = "slice"]
+ impl<T> [T] {
+ pub fn len(&self) -> usize {
+ loop {}
+ }
+ }
+}
+// endregion:slice
+
+// region:option
+pub mod option {
+ pub enum Option<T> {
+ #[lang = "None"]
+ None,
+ #[lang = "Some"]
+ Some(T),
+ }
+
+ impl<T> Option<T> {
+ pub const fn unwrap(self) -> T {
+ match self {
+ Some(val) => val,
+ None => panic!("called `Option::unwrap()` on a `None` value"),
+ }
+ }
+ }
+}
+// endregion:option
+
+// region:result
+pub mod result {
+ pub enum Result<T, E> {
+ #[lang = "Ok"]
+ Ok(T),
+ #[lang = "Err"]
+ Err(E),
+ }
+}
+// endregion:result
+
+// region:pin
+pub mod pin {
+ #[lang = "pin"]
+ #[fundamental]
+ pub struct Pin<P> {
+ pointer: P,
+ }
++ impl<P> Pin<P> {
++ pub fn new(pointer: P) -> Pin<P> {
++ loop {}
++ }
++ }
++ // region:deref
++ impl<P: crate::ops::Deref> crate::ops::Deref for Pin<P> {
++ type Target = P::Target;
++ fn deref(&self) -> &P::Target {
++ loop {}
++ }
++ }
++ // endregion:deref
+}
+// endregion:pin
+
+// region:future
+pub mod future {
+ use crate::{
+ pin::Pin,
+ task::{Context, Poll},
+ };
+
+ #[lang = "future_trait"]
+ pub trait Future {
+ type Output;
+ #[lang = "poll"]
+ fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
+ }
+
+ pub trait IntoFuture {
+ type Output;
+ type IntoFuture: Future<Output = Self::Output>;
+ #[lang = "into_future"]
+ fn into_future(self) -> Self::IntoFuture;
+ }
+
+ impl<F: Future> IntoFuture for F {
+ type Output = F::Output;
+ type IntoFuture = F;
+ fn into_future(self) -> F {
+ self
+ }
+ }
+}
+pub mod task {
+ pub enum Poll<T> {
+ #[lang = "Ready"]
+ Ready(T),
+ #[lang = "Pending"]
+ Pending,
+ }
+
+ pub struct Context<'a> {
+ waker: &'a (),
+ }
+}
+// endregion:future
+
+// region:iterator
+pub mod iter {
+ // region:iterators
+ mod adapters {
+ pub struct Take<I> {
+ iter: I,
+ n: usize,
+ }
+ impl<I> Iterator for Take<I>
+ where
+ I: Iterator,
+ {
+ type Item = <I as Iterator>::Item;
+
+ fn next(&mut self) -> Option<<I as Iterator>::Item> {
+ loop {}
+ }
+ }
+
+ pub struct FilterMap<I, F> {
+ iter: I,
+ f: F,
+ }
+ impl<B, I: Iterator, F> Iterator for FilterMap<I, F>
+ where
+ F: FnMut(I::Item) -> Option<B>,
+ {
+ type Item = B;
+
+ #[inline]
+ fn next(&mut self) -> Option<B> {
+ loop {}
+ }
+ }
+ }
+ pub use self::adapters::{Take, FilterMap};
+
+ mod sources {
+ mod repeat {
+ pub fn repeat<T>(elt: T) -> Repeat<T> {
+ loop {}
+ }
+
+ pub struct Repeat<A> {
+ element: A,
+ }
+
+ impl<A> Iterator for Repeat<A> {
+ type Item = A;
+
+ fn next(&mut self) -> Option<A> {
+ loop {}
+ }
+ }
+ }
+ pub use self::repeat::{repeat, Repeat};
+ }
+ pub use self::sources::{repeat, Repeat};
+ // endregion:iterators
+
+ mod traits {
+ mod iterator {
+ use super::super::Take;
+
+ pub trait Iterator {
+ type Item;
+ #[lang = "next"]
+ fn next(&mut self) -> Option<Self::Item>;
+ fn nth(&mut self, n: usize) -> Option<Self::Item> {
+ loop {}
+ }
+ fn by_ref(&mut self) -> &mut Self
+ where
+ Self: Sized,
+ {
+ self
+ }
+ // region:iterators
+ fn take(self, n: usize) -> crate::iter::Take<Self> {
+ loop {}
+ }
+ fn filter_map<B, F>(self, f: F) -> crate::iter::FilterMap<Self, F>
+ where
+ Self: Sized,
+ F: FnMut(Self::Item) -> Option<B>,
+ {
+ loop {}
+ }
+ // endregion:iterators
+ }
+ impl<I: Iterator + ?Sized> Iterator for &mut I {
+ type Item = I::Item;
+ fn next(&mut self) -> Option<I::Item> {
+ (**self).next()
+ }
+ }
+ }
+ pub use self::iterator::Iterator;
+
+ mod collect {
+ pub trait IntoIterator {
+ type Item;
+ type IntoIter: Iterator<Item = Self::Item>;
+ #[lang = "into_iter"]
+ fn into_iter(self) -> Self::IntoIter;
+ }
+ impl<I: Iterator> IntoIterator for I {
+ type Item = I::Item;
+ type IntoIter = I;
+ fn into_iter(self) -> I {
+ self
+ }
+ }
+ }
+ pub use self::collect::IntoIterator;
+ }
+ pub use self::traits::{IntoIterator, Iterator};
+}
+// endregion:iterator
+
+// region:derive
+mod macros {
+ pub(crate) mod builtin {
+ #[rustc_builtin_macro]
+ pub macro derive($item:item) {
+ /* compiler built-in */
+ }
+ }
+}
+// endregion:derive
+
+// region:bool_impl
+#[lang = "bool"]
+impl bool {
+ pub fn then<T, F: FnOnce() -> T>(self, f: F) -> Option<T> {
+ if self {
+ Some(f())
+ } else {
+ None
+ }
+ }
+}
+// endregion:bool_impl
+
+pub mod prelude {
+ pub mod v1 {
+ pub use crate::{
+ clone::Clone, // :clone
+ cmp::{Eq, PartialEq}, // :eq
+ cmp::{Ord, PartialOrd}, // :ord
+ convert::AsRef, // :as_ref
+ convert::{From, Into}, // :from
+ default::Default, // :default
+ iter::{IntoIterator, Iterator}, // :iterator
+ macros::builtin::derive, // :derive
+ marker::Copy, // :copy
+ marker::Sized, // :sized
+ mem::drop, // :drop
+ ops::Drop, // :drop
+ ops::{Fn, FnMut, FnOnce}, // :fn
+ option::Option::{self, None, Some}, // :option
+ result::Result::{self, Err, Ok}, // :result
+ };
+ }
+
+ pub mod rust_2015 {
+ pub use super::v1::*;
+ }
+
+ pub mod rust_2018 {
+ pub use super::v1::*;
+ }
+
+ pub mod rust_2021 {
+ pub use super::v1::*;
+ }
+}
+
+#[prelude_import]
+#[allow(unused)]
+use prelude::v1::*;
--- /dev/null
- To speed things up, sometimes I open a temporary hello-world project which has `"rust-analyzer.cargo.noSysroot": true` in `.code/settings.json`.
+# Contributing Quick Start
+
+rust-analyzer is an ordinary Rust project, which is organized as a Cargo workspace, builds on stable and doesn't depend on C libraries.
+So, just
+
+```
+$ cargo test
+```
+
+should be enough to get you started!
+
+To learn more about how rust-analyzer works, see [./architecture.md](./architecture.md).
+It also explains the high-level layout of the source code.
+Do skim through that document.
+
+We also publish rustdoc docs to pages: https://rust-lang.github.io/rust-analyzer/ide/.
+Note though, that the internal documentation is very incomplete.
+
+Various organizational and process issues are discussed in this document.
+
+# Getting in Touch
+
+rust-analyzer is a part of the [RLS-2.0 working
+group](https://github.com/rust-lang/compiler-team/tree/6a769c13656c0a6959ebc09e7b1f7c09b86fb9c0/working-groups/rls-2.0).
+Discussion happens in this Zulip stream:
+
+https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Frust-analyzer
+
+# Issue Labels
+
+* [good-first-issue](https://github.com/rust-lang/rust-analyzer/labels/good%20first%20issue)
+ are good issues to get into the project.
+* [E-has-instructions](https://github.com/rust-lang/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-has-instructions)
+ issues have links to the code in question and tests.
+* [Broken Window](https://github.com/rust-lang/rust-analyzer/issues?q=is:issue+is:open+label:%22Broken+Window%22)
+ are issues which are not necessarily critical by themselves, but which should be fixed ASAP regardless, to avoid accumulation of technical debt.
+* [E-easy](https://github.com/rust-lang/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-easy),
+ [E-medium](https://github.com/rust-lang/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-medium),
+ [E-hard](https://github.com/rust-lang/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-hard),
+ [E-unknown](https://github.com/rust-lang/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-unknown),
+ labels are *estimates* for how hard would be to write a fix. Each triaged issue should have one of these labels.
+* [S-actionable](https://github.com/rust-lang/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AS-actionable) and
+ [S-unactionable](https://github.com/rust-lang/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AS-unactionable)
+ specify if there are concrete steps to resolve or advance an issue. Roughly, actionable issues need only work to be fixed,
+ while unactionable ones are blocked either on user feedback (providing a reproducible example), or on larger architectural
+ work or decisions. This classification is descriptive, not prescriptive, and might be wrong: Any unactionable issue might have a simple fix that we missed.
+ Each triaged issue should have one of these labels.
+* [fun](https://github.com/rust-lang/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3Afun)
+ is for cool, but probably hard stuff.
+* [Design](https://github.com/rust-lang/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%Design)
+ is for moderate/large scale architecture discussion.
+ Also a kind of fun.
+ These issues should generally include a link to a Zulip discussion thread.
+
+# Code Style & Review Process
+
+Do see [./style.md](./style.md).
+
+# Cookbook
+
+## CI
+
+We use GitHub Actions for CI.
+Most of the things, including formatting, are checked by `cargo test`.
+If `cargo test` passes locally, that's a good sign that CI will be green as well.
+The only exception is that some long-running tests are skipped locally by default.
+Use `env RUN_SLOW_TESTS=1 cargo test` to run the full suite.
+
+We use bors to enforce the [not rocket science](https://graydon2.dreamwidth.org/1597.html) rule.
+
+## Launching rust-analyzer
+
+Debugging the language server can be tricky.
+LSP is rather chatty, so driving it from the command line is not really feasible, driving it via VS Code requires interacting with two processes.
+
+For this reason, the best way to see how rust-analyzer works is to **find a relevant test and execute it**.
+VS Code & Emacs include an action for running a single test.
+
+Launching a VS Code instance with a locally built language server is also possible.
+There's **"Run Extension (Debug Build)"** launch configuration for this in VS Code.
+
+In general, I use one of the following workflows for fixing bugs and implementing features:
+
+If the problem concerns only internal parts of rust-analyzer (i.e. I don't need to touch the `rust-analyzer` crate or TypeScript code), there is a unit-test for it.
+So, I use **rust-analyzer: Run** action in VS Code to run this single test, and then just do printf-driven development/debugging.
+As a sanity check after I'm done, I use `cargo xtask install --server` and **Reload Window** action in VS Code to verify that the thing works as I expect.
+
+If the problem concerns only the VS Code extension, I use **Run Installed Extension** launch configuration from `launch.json`.
+Notably, this uses the usual `rust-analyzer` binary from `PATH`.
+For this, it is important to have the following in your `settings.json` file:
+```json
+{
+ "rust-analyzer.server.path": "rust-analyzer"
+}
+```
+After I am done with the fix, I use `cargo xtask install --client` to try the new extension for real.
+
+If I need to fix something in the `rust-analyzer` crate, I feel sad because it's on the boundary between the two processes, and working there is slow.
+I usually just `cargo xtask install --server` and poke changes from my live environment.
+Note that this uses `--release`, which is usually faster overall, because loading stdlib into debug version of rust-analyzer takes a lot of time.
++To speed things up, sometimes I open a temporary hello-world project which has `"rust-analyzer.cargo.sysroot": null` in `.code/settings.json`.
+This flag causes rust-analyzer to skip loading the sysroot, which greatly reduces the amount of things rust-analyzer needs to do, and makes printf's more useful.
+Note that you should only use the `eprint!` family of macros for debugging: stdout is used for LSP communication, and `print!` would break it.
+
+If I need to fix something simultaneously in the server and in the client, I feel even more sad.
+I don't have a specific workflow for this case.
+
+Additionally, I use `cargo run --release -p rust-analyzer -- analysis-stats path/to/some/rust/crate` to run a batch analysis.
+This is primarily useful for performance optimizations, or for bug minimization.
+
+## TypeScript Tests
+
+If you change files under `editors/code` and would like to run the tests and linter, install npm and run:
+
+```bash
+cd editors/code
+npm ci
+npm run lint
+```
+## How to ...
+
+* ... add an assist? [#7535](https://github.com/rust-lang/rust-analyzer/pull/7535)
+* ... add a new protocol extension? [#4569](https://github.com/rust-lang/rust-analyzer/pull/4569)
+* ... add a new configuration option? [#7451](https://github.com/rust-lang/rust-analyzer/pull/7451)
+* ... add a new completion? [#6964](https://github.com/rust-lang/rust-analyzer/pull/6964)
+* ... allow new syntax in the parser? [#7338](https://github.com/rust-lang/rust-analyzer/pull/7338)
+
+## Logging
+
+Logging is done by both rust-analyzer and VS Code, so it might be tricky to figure out where logs go.
+
+Inside rust-analyzer, we use the [`tracing`](https://docs.rs/tracing/) crate for logging,
+and [`tracing-subscriber`](https://docs.rs/tracing-subscriber) for logging frontend.
+By default, log goes to stderr, but the stderr itself is processed by VS Code.
+`--log-file <PATH>` CLI argument allows logging to file.
+Setting the `RA_LOG_FILE=<PATH>` environment variable will also log to file, it will also override `--log-file`.
+
+To see stderr in the running VS Code instance, go to the "Output" tab of the panel and select `rust-analyzer`.
+This shows `eprintln!` as well.
+Note that `stdout` is used for the actual protocol, so `println!` will break things.
+
+To log all communication between the server and the client, there are two choices:
+
+* You can log on the server side, by running something like
+ ```
+ env RA_LOG=lsp_server=debug code .
+ ```
+* You can log on the client side, by enabling `"rust-analyzer.trace.server": "verbose"` workspace setting.
+ These logs are shown in a separate tab in the output and could be used with LSP inspector.
+ Kudos to [@DJMcNab](https://github.com/DJMcNab) for setting this awesome infra up!
+
+
+There are also several VS Code commands which might be of interest:
+
+* `rust-analyzer: Status` shows some memory-usage statistics.
+
+* `rust-analyzer: Syntax Tree` shows syntax tree of the current file/selection.
+
+* `rust-analyzer: View Hir` shows the HIR expressions within the function containing the cursor.
+
+ You can hover over syntax nodes in the opened text file to see the appropriate
+ rust code that it refers to and the rust editor will also highlight the proper
+ text range.
+
+ If you trigger Go to Definition in the inspected Rust source file,
+ the syntax tree read-only editor should scroll to and select the
+ appropriate syntax node token.
+
+ ![demo](https://user-images.githubusercontent.com/36276403/78225773-6636a480-74d3-11ea-9d9f-1c9d42da03b0.png)
+
+## Profiling
+
+We have a built-in hierarchical profiler, you can enable it by using `RA_PROFILE` env-var:
+
+```
+RA_PROFILE=* // dump everything
+RA_PROFILE=foo|bar|baz // enabled only selected entries
+RA_PROFILE=*@3>10 // dump everything, up to depth 3, if it takes more than 10 ms
+```
+
+In particular, I have `export RA_PROFILE='*>10'` in my shell profile.
+
+We also have a "counting" profiler which counts number of instances of popular structs.
+It is enabled by `RA_COUNT=1`.
+
+To measure time for from-scratch analysis, use something like this:
+
+```
+$ cargo run --release -p rust-analyzer -- analysis-stats ../chalk/
+```
+
+For measuring time of incremental analysis, use either of these:
+
+```
+$ cargo run --release -p rust-analyzer -- analysis-bench ../chalk/ --highlight ../chalk/chalk-engine/src/logic.rs
+$ cargo run --release -p rust-analyzer -- analysis-bench ../chalk/ --complete ../chalk/chalk-engine/src/logic.rs:94:0
+```
+
+Look for `fn benchmark_xxx` tests for a quick way to reproduce performance problems.
+
+## Release Process
+
+Release process is handled by `release`, `dist` and `promote` xtasks, `release` being the main one.
+
+`release` assumes that you have checkouts of `rust-analyzer`, `rust-analyzer.github.io`, and `rust-lang/rust` in the same directory:
+
+```
+./rust-analyzer
+./rust-analyzer.github.io
+./rust-rust-analyzer # Note the name!
+```
+
+The remote for `rust-analyzer` must be called `upstream` (I use `origin` to point to my fork).
+In addition, for `xtask promote` (see below), `rust-rust-analyzer` must have a `rust-analyzer` remote pointing to this repository on GitHub.
+
+`release` calls the GitHub API calls to scrape pull request comments and categorize them in the changelog.
+This step uses the `curl` and `jq` applications, which need to be available in `PATH`.
+Finally, you need to obtain a GitHub personal access token and set the `GITHUB_TOKEN` environment variable.
+
+Release steps:
+
+1. Set the `GITHUB_TOKEN` environment variable.
+2. Inside rust-analyzer, run `cargo xtask release`. This will:
+ * checkout the `release` branch
+ * reset it to `upstream/nightly`
+ * push it to `upstream`. This triggers GitHub Actions which:
+ * runs `cargo xtask dist` to package binaries and VS Code extension
+ * makes a GitHub release
+ * publishes the VS Code extension to the marketplace
+ * call the GitHub API for PR details
+ * create a new changelog in `rust-analyzer.github.io`
+3. While the release is in progress, fill in the changelog
+4. Commit & push the changelog
+5. Tweet
+6. Inside `rust-analyzer`, run `cargo xtask promote` -- this will create a PR to rust-lang/rust updating rust-analyzer's subtree.
+ Self-approve the PR.
+
+If the GitHub Actions release fails because of a transient problem like a timeout, you can re-run the job from the Actions console.
+If it fails because of something that needs to be fixed, remove the release tag (if needed), fix the problem, then start over.
+Make sure to remove the new changelog post created when running `cargo xtask release` a second time.
+
+We release "nightly" every night automatically and promote the latest nightly to "stable" manually, every week.
+
+We don't do "patch" releases, unless something truly egregious comes up.
+To do a patch release, cherry-pick the fix on top of the current `release` branch and push the branch.
+There's no need to write a changelog for a patch release, it's OK to include the notes about the fix into the next weekly one.
+Note: we tag releases by dates, releasing a patch release on the same day should work (by overwriting a tag), but I am not 100% sure.
+
+## Permissions
+
+There are three sets of people with extra permissions:
+
+* rust-analyzer GitHub organization [**admins**](https://github.com/orgs/rust-analyzer/people?query=role:owner) (which include current t-compiler leads).
+ Admins have full access to the org.
+* [**review**](https://github.com/orgs/rust-analyzer/teams/review) team in the organization.
+ Reviewers have `r+` access to all of organization's repositories and publish rights on crates.io.
+ They also have direct commit access, but all changes should via bors queue.
+ It's ok to self-approve if you think you know what you are doing!
+ bors should automatically sync the permissions.
+ Feel free to request a review or assign any PR to a reviewer with the relevant expertise to bring the work to their attention.
+ Don't feel pressured to review assigned PRs though.
+ If you don't feel like reviewing for whatever reason, someone else will pick the review up!
+* [**triage**](https://github.com/orgs/rust-analyzer/teams/triage) team in the organization.
+ This team can label and close issues.
+
+Note that at the time being you need to be a member of the org yourself to view the links.
--- /dev/null
- [`AnalysisHost`]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/ide_api/src/lib.rs#L265-L284
- [`Analysis`]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/ide_api/src/lib.rs#L291-L478
+# Guide to rust-analyzer
+
+## About the guide
+
+This guide describes the current state of rust-analyzer as of 2019-01-20 (git
+tag [guide-2019-01]). Its purpose is to document various problems and
+architectural solutions related to the problem of building IDE-first compiler
+for Rust. There is a video version of this guide as well:
+https://youtu.be/ANKBNiSWyfc.
+
+[guide-2019-01]: https://github.com/rust-lang/rust-analyzer/tree/guide-2019-01
+
+## The big picture
+
+On the highest possible level, rust-analyzer is a stateful component. A client may
+apply changes to the analyzer (new contents of `foo.rs` file is "fn main() {}")
+and it may ask semantic questions about the current state (what is the
+definition of the identifier with offset 92 in file `bar.rs`?). Two important
+properties hold:
+
+* Analyzer does not do any I/O. It starts in an empty state and all input data is
+ provided via `apply_change` API.
+
+* Only queries about the current state are supported. One can, of course,
+ simulate undo and redo by keeping a log of changes and inverse changes respectively.
+
+## IDE API
+
+To see the bigger picture of how the IDE features work, let's take a look at the [`AnalysisHost`] and
+[`Analysis`] pair of types. `AnalysisHost` has three methods:
+
+* `default()` for creating an empty analysis instance
+* `apply_change(&mut self)` to make changes (this is how you get from an empty
+ state to something interesting)
+* `analysis(&self)` to get an instance of `Analysis`
+
+`Analysis` has a ton of methods for IDEs, like `goto_definition`, or
+`completions`. Both inputs and outputs of `Analysis`' methods are formulated in
+terms of files and offsets, and **not** in terms of Rust concepts like structs,
+traits, etc. The "typed" API with Rust specific types is slightly lower in the
+stack, we'll talk about it later.
+
++[`AnalysisHost`]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/ra_ide_api/src/lib.rs#L265-L284
++[`Analysis`]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/ra_ide_api/src/lib.rs#L291-L478
+
+The reason for this separation of `Analysis` and `AnalysisHost` is that we want to apply
+changes "uniquely", but we might also want to fork an `Analysis` and send it to
+another thread for background processing. That is, there is only a single
+`AnalysisHost`, but there may be several (equivalent) `Analysis`.
+
+Note that all of the `Analysis` API return `Cancellable<T>`. This is required to
+be responsive in an IDE setting. Sometimes a long-running query is being computed
+and the user types something in the editor and asks for completion. In this
+case, we cancel the long-running computation (so it returns `Err(Cancelled)`),
+apply the change and execute request for completion. We never use stale data to
+answer requests. Under the cover, `AnalysisHost` "remembers" all outstanding
+`Analysis` instances. The `AnalysisHost::apply_change` method cancels all
+`Analysis`es, blocks until all of them are `Dropped` and then applies changes
+in-place. This may be familiar to Rustaceans who use read-write locks for interior
+mutability.
+
+Next, let's talk about what the inputs to the `Analysis` are, precisely.
+
+## Inputs
+
+rust-analyzer never does any I/O itself, all inputs get passed explicitly via
+the `AnalysisHost::apply_change` method, which accepts a single argument, a
+`Change`. [`Change`] is a builder for a single change
+"transaction", so it suffices to study its methods to understand all of the
+input data.
+
+[`Change`]: https://github.com/rust-lang/rust-analyzer/blob/master/crates/base_db/src/change.rs#L14-L89
+
+The `(add|change|remove)_file` methods control the set of the input files, where
+each file has an integer id (`FileId`, picked by the client), text (`String`)
+and a filesystem path. Paths are tricky; they'll be explained below, in source roots
+section, together with the `add_root` method. The `add_library` method allows us to add a
+group of files which are assumed to rarely change. It's mostly an optimization
+and does not change the fundamental picture.
+
+The `set_crate_graph` method allows us to control how the input files are partitioned
+into compilation units -- crates. It also controls (in theory, not implemented
+yet) `cfg` flags. `CrateGraph` is a directed acyclic graph of crates. Each crate
+has a root `FileId`, a set of active `cfg` flags and a set of dependencies. Each
+dependency is a pair of a crate and a name. It is possible to have two crates
+with the same root `FileId` but different `cfg`-flags/dependencies. This model
+is lower than Cargo's model of packages: each Cargo package consists of several
+targets, each of which is a separate crate (or several crates, if you try
+different feature combinations).
+
+Procedural macros should become inputs as well, but currently they are not
+supported. Procedural macro will be a black box `Box<dyn Fn(TokenStream) -> TokenStream>`
+function, and will be inserted into the crate graph just like dependencies.
+
+Soon we'll talk how we build an LSP server on top of `Analysis`, but first,
+let's deal with that paths issue.
+
+## Source roots (a.k.a. "Filesystems are horrible")
+
+This is a non-essential section, feel free to skip.
+
+The previous section said that the filesystem path is an attribute of a file,
+but this is not the whole truth. Making it an absolute `PathBuf` will be bad for
+several reasons. First, filesystems are full of (platform-dependent) edge cases:
+
+* It's hard (requires a syscall) to decide if two paths are equivalent.
+* Some filesystems are case-sensitive (e.g. macOS).
+* Paths are not necessarily UTF-8.
+* Symlinks can form cycles.
+
+Second, this might hurt the reproducibility and hermeticity of builds. In theory,
+moving a project from `/foo/bar/my-project` to `/spam/eggs/my-project` should
+not change a bit in the output. However, if the absolute path is a part of the
+input, it is at least in theory observable, and *could* affect the output.
+
+Yet another problem is that we really *really* want to avoid doing I/O, but with
+Rust the set of "input" files is not necessarily known up-front. In theory, you
+can have `#[path="/dev/random"] mod foo;`.
+
+To solve (or explicitly refuse to solve) these problems rust-analyzer uses the
+concept of a "source root". Roughly speaking, source roots are the contents of a
+directory on a file systems, like `/home/matklad/projects/rustraytracer/**.rs`.
+
+More precisely, all files (`FileId`s) are partitioned into disjoint
+`SourceRoot`s. Each file has a relative UTF-8 path within the `SourceRoot`.
+`SourceRoot` has an identity (integer ID). Crucially, the root path of the
+source root itself is unknown to the analyzer: A client is supposed to maintain a
+mapping between `SourceRoot` IDs (which are assigned by the client) and actual
+`PathBuf`s. `SourceRoot`s give a sane tree model of the file system to the
+analyzer.
+
+Note that `mod`, `#[path]` and `include!()` can only reference files from the
+same source root. It is of course possible to explicitly add extra files to
+the source root, even `/dev/random`.
+
+## Language Server Protocol
+
+Now let's see how the `Analysis` API is exposed via the JSON RPC based language server protocol. The
+hard part here is managing changes (which can come either from the file system
+or from the editor) and concurrency (we want to spawn background jobs for things
+like syntax highlighting). We use the event loop pattern to manage the zoo, and
+the loop is the [`main_loop_inner`] function. The [`main_loop`] does a one-time
+initialization and tearing down of the resources.
+
+[`main_loop`]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/ra_lsp_server/src/main_loop.rs#L51-L110
+[`main_loop_inner`]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/ra_lsp_server/src/main_loop.rs#L156-L258
+
+
+Let's walk through a typical analyzer session!
+
+First, we need to figure out what to analyze. To do this, we run `cargo
+metadata` to learn about Cargo packages for current workspace and dependencies,
+and we run `rustc --print sysroot` and scan the "sysroot" (the directory containing the current Rust toolchain's files) to learn about crates like
+`std`. Currently we load this configuration once at the start of the server, but
+it should be possible to dynamically reconfigure it later without restart.
+
+[main_loop.rs#L62-L70](https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/ra_lsp_server/src/main_loop.rs#L62-L70)
+
+The [`ProjectModel`] we get after this step is very Cargo and sysroot specific,
+it needs to be lowered to get the input in the form of `Change`. This
+happens in [`ServerWorldState::new`] method. Specifically
+
+* Create a `SourceRoot` for each Cargo package and sysroot.
+* Schedule a filesystem scan of the roots.
+* Create an analyzer's `Crate` for each Cargo **target** and sysroot crate.
+* Setup dependencies between the crates.
+
+[`ProjectModel`]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/ra_lsp_server/src/project_model.rs#L16-L20
+[`ServerWorldState::new`]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/ra_lsp_server/src/server_world.rs#L38-L160
+
+The results of the scan (which may take a while) will be processed in the body
+of the main loop, just like any other change. Here's where we handle:
+
+* [File system changes](https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/ra_lsp_server/src/main_loop.rs#L194)
+* [Changes from the editor](https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/ra_lsp_server/src/main_loop.rs#L377)
+
+After a single loop's turn, we group the changes into one `Change` and
+[apply] it. This always happens on the main thread and blocks the loop.
+
+[apply]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/ra_lsp_server/src/server_world.rs#L216
+
+To handle requests, like ["goto definition"], we create an instance of the
+`Analysis` and [`schedule`] the task (which consumes `Analysis`) on the
+threadpool. [The task] calls the corresponding `Analysis` method, while
+massaging the types into the LSP representation. Keep in mind that if we are
+executing "goto definition" on the threadpool and a new change comes in, the
+task will be canceled as soon as the main loop calls `apply_change` on the
+`AnalysisHost`.
+
+["goto definition"]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/ra_lsp_server/src/server_world.rs#L216
+[`schedule`]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/ra_lsp_server/src/main_loop.rs#L426-L455
+[The task]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/ra_lsp_server/src/main_loop/handlers.rs#L205-L223
+
+This concludes the overview of the analyzer's programing *interface*. Next, let's
+dig into the implementation!
+
+## Salsa
+
+The most straightforward way to implement an "apply change, get analysis, repeat"
+API would be to maintain the input state and to compute all possible analysis
+information from scratch after every change. This works, but scales poorly with
+the size of the project. To make this fast, we need to take advantage of the
+fact that most of the changes are small, and that analysis results are unlikely
+to change significantly between invocations.
+
+To do this we use [salsa]: a framework for incremental on-demand computation.
+You can skip the rest of the section if you are familiar with `rustc`'s red-green
+algorithm (which is used for incremental compilation).
+
+[salsa]: https://github.com/salsa-rs/salsa
+
+It's better to refer to salsa's docs to learn about it. Here's a small excerpt:
+
+The key idea of salsa is that you define your program as a set of queries. Every
+query is used like a function `K -> V` that maps from some key of type `K` to a value
+of type `V`. Queries come in two basic varieties:
+
+* **Inputs**: the base inputs to your system. You can change these whenever you
+ like.
+
+* **Functions**: pure functions (no side effects) that transform your inputs
+ into other values. The results of queries are memoized to avoid recomputing
+ them a lot. When you make changes to the inputs, we'll figure out (fairly
+ intelligently) when we can re-use these memoized values and when we have to
+ recompute them.
+
+For further discussion, its important to understand one bit of "fairly
+intelligently". Suppose we have two functions, `f1` and `f2`, and one input,
+`z`. We call `f1(X)` which in turn calls `f2(Y)` which inspects `i(Z)`. `i(Z)`
+returns some value `V1`, `f2` uses that and returns `R1`, `f1` uses that and
+returns `O`. Now, let's change `i` at `Z` to `V2` from `V1` and try to compute
+`f1(X)` again. Because `f1(X)` (transitively) depends on `i(Z)`, we can't just
+reuse its value as is. However, if `f2(Y)` is *still* equal to `R1` (despite
+`i`'s change), we, in fact, *can* reuse `O` as result of `f1(X)`. And that's how
+salsa works: it recomputes results in *reverse* order, starting from inputs and
+progressing towards outputs, stopping as soon as it sees an intermediate value
+that hasn't changed. If this sounds confusing to you, don't worry: it is
+confusing. This illustration by @killercup might help:
+
+<img alt="step 1" src="https://user-images.githubusercontent.com/1711539/51460907-c5484780-1d6d-11e9-9cd2-d6f62bd746e0.png" width="50%">
+
+<img alt="step 2" src="https://user-images.githubusercontent.com/1711539/51460915-c9746500-1d6d-11e9-9a77-27d33a0c51b5.png" width="50%">
+
+<img alt="step 3" src="https://user-images.githubusercontent.com/1711539/51460920-cda08280-1d6d-11e9-8d96-a782aa57a4d4.png" width="50%">
+
+<img alt="step 4" src="https://user-images.githubusercontent.com/1711539/51460927-d1340980-1d6d-11e9-851e-13c149d5c406.png" width="50%">
+
+## Salsa Input Queries
+
+All analyzer information is stored in a salsa database. `Analysis` and
+`AnalysisHost` types are newtype wrappers for [`RootDatabase`] -- a salsa
+database.
+
+[`RootDatabase`]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/ide_api/src/db.rs#L88-L134
+
+Salsa input queries are defined in [`FilesDatabase`] (which is a part of
+`RootDatabase`). They closely mirror the familiar `Change` structure:
+indeed, what `apply_change` does is it sets the values of input queries.
+
+[`FilesDatabase`]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/base_db/src/input.rs#L150-L174
+
+## From text to semantic model
+
+The bulk of the rust-analyzer is transforming input text into a semantic model of
+Rust code: a web of entities like modules, structs, functions and traits.
+
+An important fact to realize is that (unlike most other languages like C# or
+Java) there is not a one-to-one mapping between the source code and the semantic model. A
+single function definition in the source code might result in several semantic
+functions: for example, the same source file might get included as a module in
+several crates or a single crate might be present in the compilation DAG
+several times, with different sets of `cfg`s enabled. The IDE-specific task of
+mapping source code into a semantic model is inherently imprecise for
+this reason and gets handled by the [`source_binder`].
+
+[`source_binder`]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/hir/src/source_binder.rs
+
+The semantic interface is declared in the [`code_model_api`] module. Each entity is
+identified by an integer ID and has a bunch of methods which take a salsa database
+as an argument and returns other entities (which are also IDs). Internally, these
+methods invoke various queries on the database to build the model on demand.
+Here's [the list of queries].
+
+[`code_model_api`]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/hir/src/code_model_api.rs
+[the list of queries]: https://github.com/rust-lang/rust-analyzer/blob/7e84440e25e19529e4ff8a66e521d1b06349c6ec/crates/hir/src/db.rs#L20-L106
+
+The first step of building the model is parsing the source code.
+
+## Syntax trees
+
+An important property of the Rust language is that each file can be parsed in
+isolation. Unlike, say, `C++`, an `include` can't change the meaning of the
+syntax. For this reason, rust-analyzer can build a syntax tree for each "source
+file", which could then be reused by several semantic models if this file
+happens to be a part of several crates.
+
+The representation of syntax trees that rust-analyzer uses is similar to that of `Roslyn`
+and Swift's new [libsyntax]. Swift's docs give an excellent overview of the
+approach, so I skip this part here and instead outline the main characteristics
+of the syntax trees:
+
+* Syntax trees are fully lossless. Converting **any** text to a syntax tree and
+ back is a total identity function. All whitespace and comments are explicitly
+ represented in the tree.
+
+* Syntax nodes have generic `(next|previous)_sibling`, `parent`,
+ `(first|last)_child` functions. You can get from any one node to any other
+ node in the file using only these functions.
+
+* Syntax nodes know their range (start offset and length) in the file.
+
+* Syntax nodes share the ownership of their syntax tree: if you keep a reference
+ to a single function, the whole enclosing file is alive.
+
+* Syntax trees are immutable and the cost of replacing the subtree is
+ proportional to the depth of the subtree. Read Swift's docs to learn how
+ immutable + parent pointers + cheap modification is possible.
+
+* Syntax trees are build on best-effort basis. All accessor methods return
+ `Option`s. The tree for `fn foo` will contain a function declaration with
+ `None` for parameter list and body.
+
+* Syntax trees do not know the file they are built from, they only know about
+ the text.
+
+The implementation is based on the generic [rowan] crate on top of which a
+[rust-specific] AST is generated.
+
+[libsyntax]: https://github.com/apple/swift/tree/5e2c815edfd758f9b1309ce07bfc01c4bc20ec23/lib/Syntax
+[rowan]: https://github.com/rust-analyzer/rowan/tree/100a36dc820eb393b74abe0d20ddf99077b61f88
+[rust-specific]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/ra_syntax/src/ast/generated.rs
+
+The next step in constructing the semantic model is ...
+
+## Building a Module Tree
+
+The algorithm for building a tree of modules is to start with a crate root
+(remember, each `Crate` from a `CrateGraph` has a `FileId`), collect all `mod`
+declarations and recursively process child modules. This is handled by the
+[`module_tree_query`], with two slight variations.
+
+[`module_tree_query`]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/hir/src/module_tree.rs#L116-L123
+
+First, rust-analyzer builds a module tree for all crates in a source root
+simultaneously. The main reason for this is historical (`module_tree` predates
+`CrateGraph`), but this approach also enables accounting for files which are not
+part of any crate. That is, if you create a file but do not include it as a
+submodule anywhere, you still get semantic completion, and you get a warning
+about a free-floating module (the actual warning is not implemented yet).
+
+The second difference is that `module_tree_query` does not *directly* depend on
+the "parse" query (which is confusingly called `source_file`). Why would calling
+the parse directly be bad? Suppose the user changes the file slightly, by adding
+an insignificant whitespace. Adding whitespace changes the parse tree (because
+it includes whitespace), and that means recomputing the whole module tree.
+
+We deal with this problem by introducing an intermediate [`submodules_query`].
+This query processes the syntax tree and extracts a set of declared submodule
+names. Now, changing the whitespace results in `submodules_query` being
+re-executed for a *single* module, but because the result of this query stays
+the same, we don't have to re-execute [`module_tree_query`]. In fact, we only
+need to re-execute it when we add/remove new files or when we change mod
+declarations.
+
+[`submodules_query`]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/hir/src/module_tree.rs#L41
+
+We store the resulting modules in a `Vec`-based indexed arena. The indices in
+the arena becomes module IDs. And this brings us to the next topic:
+assigning IDs in the general case.
+
+## Location Interner pattern
+
+One way to assign IDs is how we've dealt with modules: Collect all items into a
+single array in some specific order and use the index in the array as an ID. The
+main drawback of this approach is that these IDs are not stable: Adding a new item can
+shift the IDs of all other items. This works for modules, because adding a module is
+a comparatively rare operation, but would be less convenient for, for example,
+functions.
+
+Another solution here is positional IDs: We can identify a function as "the
+function with name `foo` in a ModuleId(92) module". Such locations are stable:
+adding a new function to the module (unless it is also named `foo`) does not
+change the location. However, such "ID" types ceases to be a `Copy`able integer and in
+general can become pretty large if we account for nesting (for example: "third parameter of
+the `foo` function of the `bar` `impl` in the `baz` module").
+
+[`LocationInterner`] allows us to combine the benefits of positional and numeric
+IDs. It is a bidirectional append-only map between locations and consecutive
+integers which can "intern" a location and return an integer ID back. The salsa
+database we use includes a couple of [interners]. How to "garbage collect"
+unused locations is an open question.
+
+[`LocationInterner`]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/base_db/src/loc2id.rs#L65-L71
+[interners]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/hir/src/db.rs#L22-L23
+
+For example, we use `LocationInterner` to assign IDs to definitions of functions,
+structs, enums, etc. The location, [`DefLoc`] contains two bits of information:
+
+* the ID of the module which contains the definition,
+* the ID of the specific item in the modules source code.
+
+We "could" use a text offset for the location of a particular item, but that would play
+badly with salsa: offsets change after edits. So, as a rule of thumb, we avoid
+using offsets, text ranges or syntax trees as keys and values for queries. What
+we do instead is we store "index" of the item among all of the items of a file
+(so, a positional based ID, but localized to a single file).
+
+[`DefLoc`]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/hir/src/ids.rs#L127-L139
+
+One thing we've glossed over for the time being is support for macros. We have
+only proof of concept handling of macros at the moment, but they are extremely
+interesting from an "assigning IDs" perspective.
+
+## Macros and recursive locations
+
+The tricky bit about macros is that they effectively create new source files.
+While we can use `FileId`s to refer to original files, we can't just assign them
+willy-nilly to the pseudo files of macro expansion. Instead, we use a special
+ID, [`HirFileId`] to refer to either a usual file or a macro-generated file:
+
+```rust
+enum HirFileId {
+ FileId(FileId),
+ Macro(MacroCallId),
+}
+```
+
+`MacroCallId` is an interned ID that specifies a particular macro invocation.
+Its `MacroCallLoc` contains:
+
+* `ModuleId` of the containing module
+* `HirFileId` of the containing file or pseudo file
+* an index of this particular macro invocation in this file (positional id
+ again).
+
+Note how `HirFileId` is defined in terms of `MacroCallLoc` which is defined in
+terms of `HirFileId`! This does not recur infinitely though: any chain of
+`HirFileId`s bottoms out in `HirFileId::FileId`, that is, some source file
+actually written by the user.
+
+[`HirFileId`]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/hir/src/ids.rs#L18-L125
+
+Now that we understand how to identify a definition, in a source or in a
+macro-generated file, we can discuss name resolution a bit.
+
+## Name resolution
+
+Name resolution faces the same problem as the module tree: if we look at the
+syntax tree directly, we'll have to recompute name resolution after every
+modification. The solution to the problem is the same: We [lower] the source code of
+each module into a position-independent representation which does not change if
+we modify bodies of the items. After that we [loop] resolving all imports until
+we've reached a fixed point.
+
+[lower]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/hir/src/nameres/lower.rs#L113-L117
+[loop]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/hir/src/nameres.rs#L186-L196
+
+And, given all our preparation with IDs and a position-independent representation,
+it is satisfying to [test] that typing inside function body does not invalidate
+name resolution results.
+
+[test]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/hir/src/nameres/tests.rs#L376
+
+An interesting fact about name resolution is that it "erases" all of the
+intermediate paths from the imports: in the end, we know which items are defined
+and which items are imported in each module, but, if the import was `use
+foo::bar::baz`, we deliberately forget what modules `foo` and `bar` resolve to.
+
+To serve "goto definition" requests on intermediate segments we need this info
+in the IDE, however. Luckily, we need it only for a tiny fraction of imports, so we just ask
+the module explicitly, "What does the path `foo::bar` resolve to?". This is a
+general pattern: we try to compute the minimal possible amount of information
+during analysis while allowing IDE to ask for additional specific bits.
+
+Name resolution is also a good place to introduce another salsa pattern used
+throughout the analyzer:
+
+## Source Map pattern
+
+Due to an obscure edge case in completion, IDE needs to know the syntax node of
+a use statement which imported the given completion candidate. We can't just
+store the syntax node as a part of name resolution: this will break
+incrementality, due to the fact that syntax changes after every file
+modification.
+
+We solve this problem during the lowering step of name resolution. The lowering
+query actually produces a *pair* of outputs: `LoweredModule` and [`SourceMap`].
+The `LoweredModule` module contains [imports], but in a position-independent form.
+The `SourceMap` contains a mapping from position-independent imports to
+(position-dependent) syntax nodes.
+
+The result of this basic lowering query changes after every modification. But
+there's an intermediate [projection query] which returns only the first
+position-independent part of the lowering. The result of this query is stable.
+Naturally, name resolution [uses] this stable projection query.
+
+[imports]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/hir/src/nameres/lower.rs#L52-L59
+[`SourceMap`]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/hir/src/nameres/lower.rs#L52-L59
+[projection query]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/hir/src/nameres/lower.rs#L97-L103
+[uses]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/hir/src/query_definitions.rs#L49
+
+## Type inference
+
+First of all, implementation of type inference in rust-analyzer was spearheaded
+by [@flodiebold]. [#327] was an awesome Christmas present, thank you, Florian!
+
+Type inference runs on per-function granularity and uses the patterns we've
+discussed previously.
+
+First, we [lower the AST] of a function body into a position-independent
+representation. In this representation, each expression is assigned a
+[positional ID]. Alongside the lowered expression, [a source map] is produced,
+which maps between expression ids and original syntax. This lowering step also
+deals with "incomplete" source trees by replacing missing expressions by an
+explicit `Missing` expression.
+
+Given the lowered body of the function, we can now run [type inference] and
+construct a mapping from `ExprId`s to types.
+
+[@flodiebold]: https://github.com/flodiebold
+[#327]: https://github.com/rust-lang/rust-analyzer/pull/327
+[lower the AST]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/hir/src/expr.rs
+[positional ID]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/hir/src/expr.rs#L13-L15
+[a source map]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/hir/src/expr.rs#L41-L44
+[type inference]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/hir/src/ty.rs#L1208-L1223
+
+## Tying it all together: completion
+
+To conclude the overview of the rust-analyzer, let's trace the request for
+(type-inference powered!) code completion!
+
+We start by [receiving a message] from the language client. We decode the
+message as a request for completion and [schedule it on the threadpool]. This is
+the place where we [catch] canceled errors if, immediately after completion, the
+client sends some modification.
+
+In [the handler], we deserialize LSP requests into rust-analyzer specific data
+types (by converting a file url into a numeric `FileId`), [ask analysis for
+completion] and serialize results into the LSP.
+
+The [completion implementation] is finally the place where we start doing the actual
+work. The first step is to collect the `CompletionContext` -- a struct which
+describes the cursor position in terms of Rust syntax and semantics. For
+example, `function_syntax: Option<&'a ast::FnDef>` stores a reference to
+the enclosing function *syntax*, while `function: Option<hir::Function>` is the
+`Def` for this function.
+
+To construct the context, we first do an ["IntelliJ Trick"]: we insert a dummy
+identifier at the cursor's position and parse this modified file, to get a
+reasonably looking syntax tree. Then we do a bunch of "classification" routines
+to figure out the context. For example, we [find an ancestor `fn` node] and we get a
+[semantic model] for it (using the lossy `source_binder` infrastructure).
+
+The second step is to run a [series of independent completion routines]. Let's
+take a closer look at [`complete_dot`], which completes fields and methods in
+`foo.bar|`. First we extract a semantic function and a syntactic receiver
+expression out of the `Context`. Then we run type-inference for this single
+function and map our syntactic expression to `ExprId`. Using the ID, we figure
+out the type of the receiver expression. Then we add all fields & methods from
+the type to completion.
+
+[receiving a message]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/ra_lsp_server/src/main_loop.rs#L203
+[schedule it on the threadpool]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/ra_lsp_server/src/main_loop.rs#L428
+[catch]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/ra_lsp_server/src/main_loop.rs#L436-L442
+[the handler]: https://salsa.zulipchat.com/#narrow/stream/181542-rfcs.2Fsalsa-query-group/topic/design.20next.20steps
+[ask analysis for completion]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/ide_api/src/lib.rs#L439-L444
+[completion implementation]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/ide_api/src/completion.rs#L46-L62
+[`CompletionContext`]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/ide_api/src/completion/completion_context.rs#L14-L37
+["IntelliJ Trick"]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/ide_api/src/completion/completion_context.rs#L72-L75
+[find an ancestor `fn` node]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/ide_api/src/completion/completion_context.rs#L116-L120
+[semantic model]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/ide_api/src/completion/completion_context.rs#L123
+[series of independent completion routines]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/ide_api/src/completion.rs#L52-L59
+[`complete_dot`]: https://github.com/rust-lang/rust-analyzer/blob/guide-2019-01/crates/ide_api/src/completion/complete_dot.rs#L6-L22
--- /dev/null
- [[rust-analyzer.cargo.noSysroot]]rust-analyzer.cargo.noSysroot (default: `false`)::
+[[rust-analyzer.assist.expressionFillDefault]]rust-analyzer.assist.expressionFillDefault (default: `"todo"`)::
++
+--
+Placeholder expression to use for missing expressions in assists.
+--
+[[rust-analyzer.cachePriming.enable]]rust-analyzer.cachePriming.enable (default: `true`)::
++
+--
+Warm up caches on project load.
+--
+[[rust-analyzer.cachePriming.numThreads]]rust-analyzer.cachePriming.numThreads (default: `0`)::
++
+--
+How many worker threads to handle priming caches. The default `0` means to pick automatically.
+--
+[[rust-analyzer.cargo.autoreload]]rust-analyzer.cargo.autoreload (default: `true`)::
++
+--
+Automatically refresh project info via `cargo metadata` on
+`Cargo.toml` or `.cargo/config.toml` changes.
+--
+[[rust-analyzer.cargo.buildScripts.enable]]rust-analyzer.cargo.buildScripts.enable (default: `true`)::
++
+--
+Run build scripts (`build.rs`) for more precise code analysis.
+--
+[[rust-analyzer.cargo.buildScripts.overrideCommand]]rust-analyzer.cargo.buildScripts.overrideCommand (default: `null`)::
++
+--
+Override the command rust-analyzer uses to run build scripts and
+build procedural macros. The command is required to output json
+and should therefore include `--message-format=json` or a similar
+option.
+
+By default, a cargo invocation will be constructed for the configured
+targets and features, with the following base command line:
+
+```bash
+cargo check --quiet --workspace --message-format=json --all-targets
+```
+.
+--
+[[rust-analyzer.cargo.buildScripts.useRustcWrapper]]rust-analyzer.cargo.buildScripts.useRustcWrapper (default: `true`)::
++
+--
+Use `RUSTC_WRAPPER=rust-analyzer` when running build scripts to
+avoid checking unnecessary things.
+--
+[[rust-analyzer.cargo.extraEnv]]rust-analyzer.cargo.extraEnv (default: `{}`)::
++
+--
+Extra environment variables that will be set when running cargo, rustc
+or other commands within the workspace. Useful for setting RUSTFLAGS.
+--
+[[rust-analyzer.cargo.features]]rust-analyzer.cargo.features (default: `[]`)::
++
+--
+List of features to activate.
+
+Set this to `"all"` to pass `--all-features` to cargo.
+--
+[[rust-analyzer.cargo.noDefaultFeatures]]rust-analyzer.cargo.noDefaultFeatures (default: `false`)::
++
+--
+Whether to pass `--no-default-features` to cargo.
+--
- Internal config for debugging, disables loading of sysroot crates.
++[[rust-analyzer.cargo.sysroot]]rust-analyzer.cargo.sysroot (default: `"discover"`)::
++
+--
++Relative path to the sysroot, or "discover" to try to automatically find it via
++"rustc --print sysroot".
++
++Unsetting this disables sysroot loading.
++
++This option does not take effect until rust-analyzer is restarted.
+--
+[[rust-analyzer.cargo.target]]rust-analyzer.cargo.target (default: `null`)::
++
+--
+Compilation target override (target triple).
+--
+[[rust-analyzer.cargo.unsetTest]]rust-analyzer.cargo.unsetTest (default: `["core"]`)::
++
+--
+Unsets `#[cfg(test)]` for the specified crates.
+--
+[[rust-analyzer.checkOnSave.allTargets]]rust-analyzer.checkOnSave.allTargets (default: `true`)::
++
+--
+Check all targets and tests (`--all-targets`).
+--
+[[rust-analyzer.checkOnSave.command]]rust-analyzer.checkOnSave.command (default: `"check"`)::
++
+--
+Cargo command to use for `cargo check`.
+--
+[[rust-analyzer.checkOnSave.enable]]rust-analyzer.checkOnSave.enable (default: `true`)::
++
+--
+Run specified `cargo check` command for diagnostics on save.
+--
+[[rust-analyzer.checkOnSave.extraArgs]]rust-analyzer.checkOnSave.extraArgs (default: `[]`)::
++
+--
+Extra arguments for `cargo check`.
+--
+[[rust-analyzer.checkOnSave.extraEnv]]rust-analyzer.checkOnSave.extraEnv (default: `{}`)::
++
+--
+Extra environment variables that will be set when running `cargo check`.
++Extends `#rust-analyzer.cargo.extraEnv#`.
+--
+[[rust-analyzer.checkOnSave.features]]rust-analyzer.checkOnSave.features (default: `null`)::
++
+--
+List of features to activate. Defaults to
+`#rust-analyzer.cargo.features#`.
+
+Set to `"all"` to pass `--all-features` to Cargo.
+--
+[[rust-analyzer.checkOnSave.noDefaultFeatures]]rust-analyzer.checkOnSave.noDefaultFeatures (default: `null`)::
++
+--
+Whether to pass `--no-default-features` to Cargo. Defaults to
+`#rust-analyzer.cargo.noDefaultFeatures#`.
+--
+[[rust-analyzer.checkOnSave.overrideCommand]]rust-analyzer.checkOnSave.overrideCommand (default: `null`)::
++
+--
+Override the command rust-analyzer uses instead of `cargo check` for
+diagnostics on save. The command is required to output json and
+should therefor include `--message-format=json` or a similar option.
+
+If you're changing this because you're using some tool wrapping
+Cargo, you might also want to change
+`#rust-analyzer.cargo.buildScripts.overrideCommand#`.
+
+If there are multiple linked projects, this command is invoked for
+each of them, with the working directory being the project root
+(i.e., the folder containing the `Cargo.toml`).
+
+An example command would be:
+
+```bash
+cargo check --workspace --message-format=json --all-targets
+```
+.
+--
+[[rust-analyzer.checkOnSave.target]]rust-analyzer.checkOnSave.target (default: `null`)::
++
+--
+Check for a specific target. Defaults to
+`#rust-analyzer.cargo.target#`.
+--
+[[rust-analyzer.completion.autoimport.enable]]rust-analyzer.completion.autoimport.enable (default: `true`)::
++
+--
+Toggles the additional completions that automatically add imports when completed.
+Note that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled.
+--
+[[rust-analyzer.completion.autoself.enable]]rust-analyzer.completion.autoself.enable (default: `true`)::
++
+--
+Toggles the additional completions that automatically show method calls and field accesses
+with `self` prefixed to them when inside a method.
+--
+[[rust-analyzer.completion.callable.snippets]]rust-analyzer.completion.callable.snippets (default: `"fill_arguments"`)::
++
+--
+Whether to add parenthesis and argument snippets when completing function.
+--
+[[rust-analyzer.completion.postfix.enable]]rust-analyzer.completion.postfix.enable (default: `true`)::
++
+--
+Whether to show postfix snippets like `dbg`, `if`, `not`, etc.
+--
+[[rust-analyzer.completion.privateEditable.enable]]rust-analyzer.completion.privateEditable.enable (default: `false`)::
++
+--
+Enables completions of private items and fields that are defined in the current workspace even if they are not visible at the current position.
+--
+[[rust-analyzer.completion.snippets.custom]]rust-analyzer.completion.snippets.custom::
++
+--
+Default:
+----
+{
+ "Arc::new": {
+ "postfix": "arc",
+ "body": "Arc::new(${receiver})",
+ "requires": "std::sync::Arc",
+ "description": "Put the expression into an `Arc`",
+ "scope": "expr"
+ },
+ "Rc::new": {
+ "postfix": "rc",
+ "body": "Rc::new(${receiver})",
+ "requires": "std::rc::Rc",
+ "description": "Put the expression into an `Rc`",
+ "scope": "expr"
+ },
+ "Box::pin": {
+ "postfix": "pinbox",
+ "body": "Box::pin(${receiver})",
+ "requires": "std::boxed::Box",
+ "description": "Put the expression into a pinned `Box`",
+ "scope": "expr"
+ },
+ "Ok": {
+ "postfix": "ok",
+ "body": "Ok(${receiver})",
+ "description": "Wrap the expression in a `Result::Ok`",
+ "scope": "expr"
+ },
+ "Err": {
+ "postfix": "err",
+ "body": "Err(${receiver})",
+ "description": "Wrap the expression in a `Result::Err`",
+ "scope": "expr"
+ },
+ "Some": {
+ "postfix": "some",
+ "body": "Some(${receiver})",
+ "description": "Wrap the expression in an `Option::Some`",
+ "scope": "expr"
+ }
+ }
+----
+Custom completion snippets.
+
+--
+[[rust-analyzer.diagnostics.disabled]]rust-analyzer.diagnostics.disabled (default: `[]`)::
++
+--
+List of rust-analyzer diagnostics to disable.
+--
+[[rust-analyzer.diagnostics.enable]]rust-analyzer.diagnostics.enable (default: `true`)::
++
+--
+Whether to show native rust-analyzer diagnostics.
+--
+[[rust-analyzer.diagnostics.experimental.enable]]rust-analyzer.diagnostics.experimental.enable (default: `false`)::
++
+--
+Whether to show experimental rust-analyzer diagnostics that might
+have more false positives than usual.
+--
+[[rust-analyzer.diagnostics.remapPrefix]]rust-analyzer.diagnostics.remapPrefix (default: `{}`)::
++
+--
+Map of prefixes to be substituted when parsing diagnostic file paths.
+This should be the reverse mapping of what is passed to `rustc` as `--remap-path-prefix`.
+--
+[[rust-analyzer.diagnostics.warningsAsHint]]rust-analyzer.diagnostics.warningsAsHint (default: `[]`)::
++
+--
+List of warnings that should be displayed with hint severity.
+
+The warnings will be indicated by faded text or three dots in code
+and will not show up in the `Problems Panel`.
+--
+[[rust-analyzer.diagnostics.warningsAsInfo]]rust-analyzer.diagnostics.warningsAsInfo (default: `[]`)::
++
+--
+List of warnings that should be displayed with info severity.
+
+The warnings will be indicated by a blue squiggly underline in code
+and a blue icon in the `Problems Panel`.
+--
+[[rust-analyzer.files.excludeDirs]]rust-analyzer.files.excludeDirs (default: `[]`)::
++
+--
+These directories will be ignored by rust-analyzer. They are
+relative to the workspace root, and globs are not supported. You may
+also need to add the folders to Code's `files.watcherExclude`.
+--
+[[rust-analyzer.files.watcher]]rust-analyzer.files.watcher (default: `"client"`)::
++
+--
+Controls file watching implementation.
+--
+[[rust-analyzer.highlightRelated.breakPoints.enable]]rust-analyzer.highlightRelated.breakPoints.enable (default: `true`)::
++
+--
+Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords.
+--
+[[rust-analyzer.highlightRelated.exitPoints.enable]]rust-analyzer.highlightRelated.exitPoints.enable (default: `true`)::
++
+--
+Enables highlighting of all exit points while the cursor is on any `return`, `?`, `fn`, or return type arrow (`->`).
+--
+[[rust-analyzer.highlightRelated.references.enable]]rust-analyzer.highlightRelated.references.enable (default: `true`)::
++
+--
+Enables highlighting of related references while the cursor is on any identifier.
+--
+[[rust-analyzer.highlightRelated.yieldPoints.enable]]rust-analyzer.highlightRelated.yieldPoints.enable (default: `true`)::
++
+--
+Enables highlighting of all break points for a loop or block context while the cursor is on any `async` or `await` keywords.
+--
+[[rust-analyzer.hover.actions.debug.enable]]rust-analyzer.hover.actions.debug.enable (default: `true`)::
++
+--
+Whether to show `Debug` action. Only applies when
+`#rust-analyzer.hover.actions.enable#` is set.
+--
+[[rust-analyzer.hover.actions.enable]]rust-analyzer.hover.actions.enable (default: `true`)::
++
+--
+Whether to show HoverActions in Rust files.
+--
+[[rust-analyzer.hover.actions.gotoTypeDef.enable]]rust-analyzer.hover.actions.gotoTypeDef.enable (default: `true`)::
++
+--
+Whether to show `Go to Type Definition` action. Only applies when
+`#rust-analyzer.hover.actions.enable#` is set.
+--
+[[rust-analyzer.hover.actions.implementations.enable]]rust-analyzer.hover.actions.implementations.enable (default: `true`)::
++
+--
+Whether to show `Implementations` action. Only applies when
+`#rust-analyzer.hover.actions.enable#` is set.
+--
+[[rust-analyzer.hover.actions.references.enable]]rust-analyzer.hover.actions.references.enable (default: `false`)::
++
+--
+Whether to show `References` action. Only applies when
+`#rust-analyzer.hover.actions.enable#` is set.
+--
+[[rust-analyzer.hover.actions.run.enable]]rust-analyzer.hover.actions.run.enable (default: `true`)::
++
+--
+Whether to show `Run` action. Only applies when
+`#rust-analyzer.hover.actions.enable#` is set.
+--
+[[rust-analyzer.hover.documentation.enable]]rust-analyzer.hover.documentation.enable (default: `true`)::
++
+--
+Whether to show documentation on hover.
+--
+[[rust-analyzer.hover.documentation.keywords.enable]]rust-analyzer.hover.documentation.keywords.enable (default: `true`)::
++
+--
+Whether to show keyword hover popups. Only applies when
+`#rust-analyzer.hover.documentation.enable#` is set.
+--
+[[rust-analyzer.hover.links.enable]]rust-analyzer.hover.links.enable (default: `true`)::
++
+--
+Use markdown syntax for links in hover.
+--
+[[rust-analyzer.imports.granularity.enforce]]rust-analyzer.imports.granularity.enforce (default: `false`)::
++
+--
+Whether to enforce the import granularity setting for all files. If set to false rust-analyzer will try to keep import styles consistent per file.
+--
+[[rust-analyzer.imports.granularity.group]]rust-analyzer.imports.granularity.group (default: `"crate"`)::
++
+--
+How imports should be grouped into use statements.
+--
+[[rust-analyzer.imports.group.enable]]rust-analyzer.imports.group.enable (default: `true`)::
++
+--
+Group inserted imports by the https://rust-analyzer.github.io/manual.html#auto-import[following order]. Groups are separated by newlines.
+--
+[[rust-analyzer.imports.merge.glob]]rust-analyzer.imports.merge.glob (default: `true`)::
++
+--
+Whether to allow import insertion to merge new imports into single path glob imports like `use std::fmt::*;`.
+--
+[[rust-analyzer.imports.prefer.no.std]]rust-analyzer.imports.prefer.no.std (default: `false`)::
++
+--
+Prefer to unconditionally use imports of the core and alloc crate, over the std crate.
+--
+[[rust-analyzer.imports.prefix]]rust-analyzer.imports.prefix (default: `"plain"`)::
++
+--
+The path structure for newly inserted paths to use.
+--
+[[rust-analyzer.inlayHints.bindingModeHints.enable]]rust-analyzer.inlayHints.bindingModeHints.enable (default: `false`)::
++
+--
+Whether to show inlay type hints for binding modes.
+--
+[[rust-analyzer.inlayHints.chainingHints.enable]]rust-analyzer.inlayHints.chainingHints.enable (default: `true`)::
++
+--
+Whether to show inlay type hints for method chains.
+--
+[[rust-analyzer.inlayHints.closingBraceHints.enable]]rust-analyzer.inlayHints.closingBraceHints.enable (default: `true`)::
++
+--
+Whether to show inlay hints after a closing `}` to indicate what item it belongs to.
+--
+[[rust-analyzer.inlayHints.closingBraceHints.minLines]]rust-analyzer.inlayHints.closingBraceHints.minLines (default: `25`)::
++
+--
+Minimum number of lines required before the `}` until the hint is shown (set to 0 or 1
+to always show them).
+--
+[[rust-analyzer.inlayHints.closureReturnTypeHints.enable]]rust-analyzer.inlayHints.closureReturnTypeHints.enable (default: `"never"`)::
++
+--
+Whether to show inlay type hints for return types of closures.
+--
+[[rust-analyzer.inlayHints.lifetimeElisionHints.enable]]rust-analyzer.inlayHints.lifetimeElisionHints.enable (default: `"never"`)::
++
+--
+Whether to show inlay type hints for elided lifetimes in function signatures.
+--
+[[rust-analyzer.inlayHints.lifetimeElisionHints.useParameterNames]]rust-analyzer.inlayHints.lifetimeElisionHints.useParameterNames (default: `false`)::
++
+--
+Whether to prefer using parameter names as the name for elided lifetime hints if possible.
+--
+[[rust-analyzer.inlayHints.maxLength]]rust-analyzer.inlayHints.maxLength (default: `25`)::
++
+--
+Maximum length for inlay hints. Set to null to have an unlimited length.
+--
+[[rust-analyzer.inlayHints.parameterHints.enable]]rust-analyzer.inlayHints.parameterHints.enable (default: `true`)::
++
+--
+Whether to show function parameter name inlay hints at the call
+site.
+--
+[[rust-analyzer.inlayHints.reborrowHints.enable]]rust-analyzer.inlayHints.reborrowHints.enable (default: `"never"`)::
++
+--
+Whether to show inlay type hints for compiler inserted reborrows.
+--
+[[rust-analyzer.inlayHints.renderColons]]rust-analyzer.inlayHints.renderColons (default: `true`)::
++
+--
+Whether to render leading colons for type hints, and trailing colons for parameter hints.
+--
+[[rust-analyzer.inlayHints.typeHints.enable]]rust-analyzer.inlayHints.typeHints.enable (default: `true`)::
++
+--
+Whether to show inlay type hints for variables.
+--
+[[rust-analyzer.inlayHints.typeHints.hideClosureInitialization]]rust-analyzer.inlayHints.typeHints.hideClosureInitialization (default: `false`)::
++
+--
+Whether to hide inlay type hints for `let` statements that initialize to a closure.
+Only applies to closures with blocks, same as `#rust-analyzer.inlayHints.closureReturnTypeHints.enable#`.
+--
+[[rust-analyzer.inlayHints.typeHints.hideNamedConstructor]]rust-analyzer.inlayHints.typeHints.hideNamedConstructor (default: `false`)::
++
+--
+Whether to hide inlay type hints for constructors.
+--
+[[rust-analyzer.joinLines.joinAssignments]]rust-analyzer.joinLines.joinAssignments (default: `true`)::
++
+--
+Join lines merges consecutive declaration and initialization of an assignment.
+--
+[[rust-analyzer.joinLines.joinElseIf]]rust-analyzer.joinLines.joinElseIf (default: `true`)::
++
+--
+Join lines inserts else between consecutive ifs.
+--
+[[rust-analyzer.joinLines.removeTrailingComma]]rust-analyzer.joinLines.removeTrailingComma (default: `true`)::
++
+--
+Join lines removes trailing commas.
+--
+[[rust-analyzer.joinLines.unwrapTrivialBlock]]rust-analyzer.joinLines.unwrapTrivialBlock (default: `true`)::
++
+--
+Join lines unwraps trivial blocks.
+--
+[[rust-analyzer.lens.debug.enable]]rust-analyzer.lens.debug.enable (default: `true`)::
++
+--
+Whether to show `Debug` lens. Only applies when
+`#rust-analyzer.lens.enable#` is set.
+--
+[[rust-analyzer.lens.enable]]rust-analyzer.lens.enable (default: `true`)::
++
+--
+Whether to show CodeLens in Rust files.
+--
+[[rust-analyzer.lens.forceCustomCommands]]rust-analyzer.lens.forceCustomCommands (default: `true`)::
++
+--
+Internal config: use custom client-side commands even when the
+client doesn't set the corresponding capability.
+--
+[[rust-analyzer.lens.implementations.enable]]rust-analyzer.lens.implementations.enable (default: `true`)::
++
+--
+Whether to show `Implementations` lens. Only applies when
+`#rust-analyzer.lens.enable#` is set.
+--
+[[rust-analyzer.lens.location]]rust-analyzer.lens.location (default: `"above_name"`)::
++
+--
+Where to render annotations.
+--
+[[rust-analyzer.lens.references.adt.enable]]rust-analyzer.lens.references.adt.enable (default: `false`)::
++
+--
+Whether to show `References` lens for Struct, Enum, and Union.
+Only applies when `#rust-analyzer.lens.enable#` is set.
+--
+[[rust-analyzer.lens.references.enumVariant.enable]]rust-analyzer.lens.references.enumVariant.enable (default: `false`)::
++
+--
+Whether to show `References` lens for Enum Variants.
+Only applies when `#rust-analyzer.lens.enable#` is set.
+--
+[[rust-analyzer.lens.references.method.enable]]rust-analyzer.lens.references.method.enable (default: `false`)::
++
+--
+Whether to show `Method References` lens. Only applies when
+`#rust-analyzer.lens.enable#` is set.
+--
+[[rust-analyzer.lens.references.trait.enable]]rust-analyzer.lens.references.trait.enable (default: `false`)::
++
+--
+Whether to show `References` lens for Trait.
+Only applies when `#rust-analyzer.lens.enable#` is set.
+--
+[[rust-analyzer.lens.run.enable]]rust-analyzer.lens.run.enable (default: `true`)::
++
+--
+Whether to show `Run` lens. Only applies when
+`#rust-analyzer.lens.enable#` is set.
+--
+[[rust-analyzer.linkedProjects]]rust-analyzer.linkedProjects (default: `[]`)::
++
+--
+Disable project auto-discovery in favor of explicitly specified set
+of projects.
+
+Elements must be paths pointing to `Cargo.toml`,
+`rust-project.json`, or JSON objects in `rust-project.json` format.
+--
+[[rust-analyzer.lru.capacity]]rust-analyzer.lru.capacity (default: `null`)::
++
+--
+Number of syntax trees rust-analyzer keeps in memory. Defaults to 128.
+--
+[[rust-analyzer.notifications.cargoTomlNotFound]]rust-analyzer.notifications.cargoTomlNotFound (default: `true`)::
++
+--
+Whether to show `can't find Cargo.toml` error message.
+--
+[[rust-analyzer.procMacro.attributes.enable]]rust-analyzer.procMacro.attributes.enable (default: `true`)::
++
+--
+Expand attribute macros. Requires `#rust-analyzer.procMacro.enable#` to be set.
+--
+[[rust-analyzer.procMacro.enable]]rust-analyzer.procMacro.enable (default: `true`)::
++
+--
+Enable support for procedural macros, implies `#rust-analyzer.cargo.buildScripts.enable#`.
+--
+[[rust-analyzer.procMacro.ignored]]rust-analyzer.procMacro.ignored (default: `{}`)::
++
+--
+These proc-macros will be ignored when trying to expand them.
+
+This config takes a map of crate names with the exported proc-macro names to ignore as values.
+--
+[[rust-analyzer.procMacro.server]]rust-analyzer.procMacro.server (default: `null`)::
++
+--
+Internal config, path to proc-macro server executable (typically,
+this is rust-analyzer itself, but we override this in tests).
+--
+[[rust-analyzer.references.excludeImports]]rust-analyzer.references.excludeImports (default: `false`)::
++
+--
+Exclude imports from find-all-references.
+--
+[[rust-analyzer.runnables.command]]rust-analyzer.runnables.command (default: `null`)::
++
+--
+Command to be executed instead of 'cargo' for runnables.
+--
+[[rust-analyzer.runnables.extraArgs]]rust-analyzer.runnables.extraArgs (default: `[]`)::
++
+--
+Additional arguments to be passed to cargo for runnables such as
+tests or binaries. For example, it may be `--release`.
+--
+[[rust-analyzer.rustc.source]]rust-analyzer.rustc.source (default: `null`)::
++
+--
+Path to the Cargo.toml of the rust compiler workspace, for usage in rustc_private
+projects, or "discover" to try to automatically find it if the `rustc-dev` component
+is installed.
+
+Any project which uses rust-analyzer with the rustcPrivate
+crates must set `[package.metadata.rust-analyzer] rustc_private=true` to use it.
+
+This option does not take effect until rust-analyzer is restarted.
+--
+[[rust-analyzer.rustfmt.extraArgs]]rust-analyzer.rustfmt.extraArgs (default: `[]`)::
++
+--
+Additional arguments to `rustfmt`.
+--
+[[rust-analyzer.rustfmt.overrideCommand]]rust-analyzer.rustfmt.overrideCommand (default: `null`)::
++
+--
+Advanced option, fully override the command rust-analyzer uses for
+formatting.
+--
+[[rust-analyzer.rustfmt.rangeFormatting.enable]]rust-analyzer.rustfmt.rangeFormatting.enable (default: `false`)::
++
+--
+Enables the use of rustfmt's unstable range formatting command for the
+`textDocument/rangeFormatting` request. The rustfmt option is unstable and only
+available on a nightly build.
+--
+[[rust-analyzer.semanticHighlighting.doc.comment.inject.enable]]rust-analyzer.semanticHighlighting.doc.comment.inject.enable (default: `true`)::
++
+--
+Inject additional highlighting into doc comments.
+
+When enabled, rust-analyzer will highlight rust source in doc comments as well as intra
+doc links.
+--
+[[rust-analyzer.semanticHighlighting.operator.enable]]rust-analyzer.semanticHighlighting.operator.enable (default: `true`)::
++
+--
+Use semantic tokens for operators.
+
+When disabled, rust-analyzer will emit semantic tokens only for operator tokens when
+they are tagged with modifiers.
+--
+[[rust-analyzer.semanticHighlighting.operator.specialization.enable]]rust-analyzer.semanticHighlighting.operator.specialization.enable (default: `false`)::
++
+--
+Use specialized semantic tokens for operators.
+
+When enabled, rust-analyzer will emit special token types for operator tokens instead
+of the generic `operator` token type.
+--
+[[rust-analyzer.semanticHighlighting.punctuation.enable]]rust-analyzer.semanticHighlighting.punctuation.enable (default: `false`)::
++
+--
+Use semantic tokens for punctuations.
+
+When disabled, rust-analyzer will emit semantic tokens only for punctuation tokens when
+they are tagged with modifiers or have a special role.
+--
+[[rust-analyzer.semanticHighlighting.punctuation.separate.macro.bang]]rust-analyzer.semanticHighlighting.punctuation.separate.macro.bang (default: `false`)::
++
+--
+When enabled, rust-analyzer will emit a punctuation semantic token for the `!` of macro
+calls.
+--
+[[rust-analyzer.semanticHighlighting.punctuation.specialization.enable]]rust-analyzer.semanticHighlighting.punctuation.specialization.enable (default: `false`)::
++
+--
+Use specialized semantic tokens for punctuations.
+
+When enabled, rust-analyzer will emit special token types for punctuation tokens instead
+of the generic `punctuation` token type.
+--
+[[rust-analyzer.semanticHighlighting.strings.enable]]rust-analyzer.semanticHighlighting.strings.enable (default: `true`)::
++
+--
+Use semantic tokens for strings.
+
+In some editors (e.g. vscode) semantic tokens override other highlighting grammars.
+By disabling semantic tokens for strings, other grammars can be used to highlight
+their contents.
+--
+[[rust-analyzer.signatureInfo.detail]]rust-analyzer.signatureInfo.detail (default: `"full"`)::
++
+--
+Show full signature of the callable. Only shows parameters if disabled.
+--
+[[rust-analyzer.signatureInfo.documentation.enable]]rust-analyzer.signatureInfo.documentation.enable (default: `true`)::
++
+--
+Show documentation.
+--
+[[rust-analyzer.typing.autoClosingAngleBrackets.enable]]rust-analyzer.typing.autoClosingAngleBrackets.enable (default: `false`)::
++
+--
+Whether to insert closing angle brackets when typing an opening angle bracket of a generic argument list.
+--
+[[rust-analyzer.workspace.symbol.search.kind]]rust-analyzer.workspace.symbol.search.kind (default: `"only_types"`)::
++
+--
+Workspace symbol search kind.
+--
+[[rust-analyzer.workspace.symbol.search.limit]]rust-analyzer.workspace.symbol.search.limit (default: `128`)::
++
+--
+Limits the number of items returned from a workspace symbol search (Defaults to 128).
+Some clients like vs-code issue new searches on result filtering and don't require all results to be returned in the initial search.
+Other clients requires all results upfront and might require a higher limit.
+--
+[[rust-analyzer.workspace.symbol.search.scope]]rust-analyzer.workspace.symbol.search.scope (default: `"workspace"`)::
++
+--
+Workspace symbol search scope.
+--
--- /dev/null
- "rust-analyzer.cargo.noSysroot": {
- "markdownDescription": "Internal config for debugging, disables loading of sysroot crates.",
- "default": false,
- "type": "boolean"
+{
+ "name": "rust-analyzer",
+ "displayName": "rust-analyzer",
+ "description": "Rust language support for Visual Studio Code",
+ "private": true,
+ "icon": "icon.png",
+ "version": "0.5.0-dev",
+ "releaseTag": null,
+ "publisher": "rust-lang",
+ "repository": {
+ "url": "https://github.com/rust-lang/rust-analyzer.git",
+ "type": "git"
+ },
+ "homepage": "https://rust-analyzer.github.io/",
+ "license": "MIT OR Apache-2.0",
+ "keywords": [
+ "rust"
+ ],
+ "categories": [
+ "Programming Languages"
+ ],
+ "engines": {
+ "vscode": "^1.66.0"
+ },
+ "enabledApiProposals": [],
+ "scripts": {
+ "vscode:prepublish": "npm run build-base -- --minify",
+ "package": "vsce package -o rust-analyzer.vsix",
+ "build-base": "esbuild ./src/main.ts --bundle --outfile=out/main.js --external:vscode --format=cjs --platform=node --target=node16",
+ "build": "npm run build-base -- --sourcemap",
+ "watch": "npm run build-base -- --sourcemap --watch",
+ "lint": "prettier --check . && eslint -c .eslintrc.js --ext ts ./src ./tests",
+ "fix": "prettier --write . && eslint -c .eslintrc.js --ext ts ./src ./tests --fix",
+ "pretest": "tsc && npm run build",
+ "test": "cross-env TEST_VARIABLE=test node ./out/tests/runTests.js"
+ },
+ "dependencies": {
+ "d3": "^7.6.1",
+ "d3-graphviz": "^4.1.1",
+ "vscode-languageclient": "^8.0.0-next.14"
+ },
+ "devDependencies": {
+ "@types/node": "~16.11.7",
+ "@types/vscode": "~1.66.0",
+ "@typescript-eslint/eslint-plugin": "^5.30.5",
+ "@typescript-eslint/parser": "^5.30.5",
+ "@vscode/test-electron": "^2.1.5",
+ "cross-env": "^7.0.3",
+ "esbuild": "^0.14.48",
+ "eslint": "^8.19.0",
+ "eslint-config-prettier": "^8.5.0",
+ "ovsx": "^0.5.1",
+ "prettier": "^2.7.1",
+ "tslib": "^2.4.0",
+ "typescript": "^4.7.4",
+ "vsce": "^2.9.2"
+ },
+ "activationEvents": [
+ "onLanguage:rust",
+ "onCommand:rust-analyzer.analyzerStatus",
+ "onCommand:rust-analyzer.memoryUsage",
+ "onCommand:rust-analyzer.reloadWorkspace",
+ "workspaceContains:*/Cargo.toml",
+ "workspaceContains:*/rust-project.json"
+ ],
+ "main": "./out/main",
+ "contributes": {
+ "taskDefinitions": [
+ {
+ "type": "cargo",
+ "required": [
+ "command"
+ ],
+ "properties": {
+ "label": {
+ "type": "string"
+ },
+ "command": {
+ "type": "string"
+ },
+ "args": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "env": {
+ "type": "object",
+ "patternProperties": {
+ ".+": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ ],
+ "commands": [
+ {
+ "command": "rust-analyzer.syntaxTree",
+ "title": "Show Syntax Tree",
+ "category": "rust-analyzer"
+ },
+ {
+ "command": "rust-analyzer.viewHir",
+ "title": "View Hir",
+ "category": "rust-analyzer"
+ },
+ {
+ "command": "rust-analyzer.viewFileText",
+ "title": "View File Text (as seen by the server)",
+ "category": "rust-analyzer"
+ },
+ {
+ "command": "rust-analyzer.viewItemTree",
+ "title": "Debug ItemTree",
+ "category": "rust-analyzer"
+ },
+ {
+ "command": "rust-analyzer.viewCrateGraph",
+ "title": "View Crate Graph",
+ "category": "rust-analyzer"
+ },
+ {
+ "command": "rust-analyzer.viewFullCrateGraph",
+ "title": "View Crate Graph (Full)",
+ "category": "rust-analyzer"
+ },
+ {
+ "command": "rust-analyzer.expandMacro",
+ "title": "Expand macro recursively",
+ "category": "rust-analyzer"
+ },
+ {
+ "command": "rust-analyzer.matchingBrace",
+ "title": "Find matching brace",
+ "category": "rust-analyzer"
+ },
+ {
+ "command": "rust-analyzer.parentModule",
+ "title": "Locate parent module",
+ "category": "rust-analyzer"
+ },
+ {
+ "command": "rust-analyzer.joinLines",
+ "title": "Join lines",
+ "category": "rust-analyzer"
+ },
+ {
+ "command": "rust-analyzer.run",
+ "title": "Run",
+ "category": "rust-analyzer"
+ },
+ {
+ "command": "rust-analyzer.copyRunCommandLine",
+ "title": "Copy Run Command Line",
+ "category": "rust-analyzer"
+ },
+ {
+ "command": "rust-analyzer.debug",
+ "title": "Debug",
+ "category": "rust-analyzer"
+ },
+ {
+ "command": "rust-analyzer.newDebugConfig",
+ "title": "Generate launch configuration",
+ "category": "rust-analyzer"
+ },
+ {
+ "command": "rust-analyzer.analyzerStatus",
+ "title": "Status",
+ "category": "rust-analyzer"
+ },
+ {
+ "command": "rust-analyzer.memoryUsage",
+ "title": "Memory Usage (Clears Database)",
+ "category": "rust-analyzer"
+ },
+ {
+ "command": "rust-analyzer.shuffleCrateGraph",
+ "title": "Shuffle Crate Graph",
+ "category": "rust-analyzer"
+ },
+ {
+ "command": "rust-analyzer.reloadWorkspace",
+ "title": "Reload workspace",
+ "category": "rust-analyzer"
+ },
+ {
+ "command": "rust-analyzer.reload",
+ "title": "Restart server",
+ "category": "rust-analyzer"
+ },
+ {
+ "command": "rust-analyzer.onEnter",
+ "title": "Enhanced enter key",
+ "category": "rust-analyzer"
+ },
+ {
+ "command": "rust-analyzer.ssr",
+ "title": "Structural Search Replace",
+ "category": "rust-analyzer"
+ },
+ {
+ "command": "rust-analyzer.serverVersion",
+ "title": "Show RA Version",
+ "category": "rust-analyzer"
+ },
+ {
+ "command": "rust-analyzer.openDocs",
+ "title": "Open docs under cursor",
+ "category": "rust-analyzer"
+ },
+ {
+ "command": "rust-analyzer.openCargoToml",
+ "title": "Open Cargo.toml",
+ "category": "rust-analyzer"
+ },
+ {
+ "command": "rust-analyzer.peekTests",
+ "title": "Peek related tests",
+ "category": "rust-analyzer"
+ },
+ {
+ "command": "rust-analyzer.moveItemUp",
+ "title": "Move item up",
+ "category": "rust-analyzer"
+ },
+ {
+ "command": "rust-analyzer.moveItemDown",
+ "title": "Move item down",
+ "category": "rust-analyzer"
+ },
+ {
+ "command": "rust-analyzer.cancelFlycheck",
+ "title": "Cancel running flychecks",
+ "category": "rust-analyzer"
+ }
+ ],
+ "keybindings": [
+ {
+ "command": "rust-analyzer.parentModule",
+ "key": "ctrl+shift+u",
+ "when": "editorTextFocus && editorLangId == rust"
+ },
+ {
+ "command": "rust-analyzer.matchingBrace",
+ "key": "ctrl+shift+m",
+ "when": "editorTextFocus && editorLangId == rust"
+ },
+ {
+ "command": "rust-analyzer.joinLines",
+ "key": "ctrl+shift+j",
+ "when": "editorTextFocus && editorLangId == rust"
+ }
+ ],
+ "configuration": {
+ "type": "object",
+ "title": "rust-analyzer",
+ "properties": {
+ "rust-analyzer.cargoRunner": {
+ "type": [
+ "null",
+ "string"
+ ],
+ "default": null,
+ "description": "Custom cargo runner extension ID."
+ },
+ "rust-analyzer.runnableEnv": {
+ "anyOf": [
+ {
+ "type": "null"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "mask": {
+ "type": "string",
+ "description": "Runnable name mask"
+ },
+ "env": {
+ "type": "object",
+ "description": "Variables in form of { \"key\": \"value\"}"
+ }
+ }
+ }
+ },
+ {
+ "type": "object",
+ "description": "Variables in form of { \"key\": \"value\"}"
+ }
+ ],
+ "default": null,
+ "markdownDescription": "Environment variables passed to the runnable launched using `Test` or `Debug` lens or `rust-analyzer.run` command."
+ },
+ "rust-analyzer.server.path": {
+ "type": [
+ "null",
+ "string"
+ ],
+ "scope": "machine-overridable",
+ "default": null,
+ "markdownDescription": "Path to rust-analyzer executable (points to bundled binary by default)."
+ },
+ "rust-analyzer.server.extraEnv": {
+ "type": [
+ "null",
+ "object"
+ ],
+ "additionalProperties": {
+ "type": [
+ "string",
+ "number"
+ ]
+ },
+ "default": null,
+ "markdownDescription": "Extra environment variables that will be passed to the rust-analyzer executable. Useful for passing e.g. `RA_LOG` for debugging."
+ },
+ "rust-analyzer.trace.server": {
+ "type": "string",
+ "scope": "window",
+ "enum": [
+ "off",
+ "messages",
+ "verbose"
+ ],
+ "enumDescriptions": [
+ "No traces",
+ "Error only",
+ "Full log"
+ ],
+ "default": "off",
+ "description": "Trace requests to the rust-analyzer (this is usually overly verbose and not recommended for regular users)."
+ },
+ "rust-analyzer.trace.extension": {
+ "description": "Enable logging of VS Code extensions itself.",
+ "type": "boolean",
+ "default": false
+ },
+ "rust-analyzer.debug.engine": {
+ "type": "string",
+ "enum": [
+ "auto",
+ "vadimcn.vscode-lldb",
+ "ms-vscode.cpptools"
+ ],
+ "default": "auto",
+ "description": "Preferred debug engine.",
+ "markdownEnumDescriptions": [
+ "First try to use [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb), if it's not installed try to use [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools).",
+ "Use [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb)",
+ "Use [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools)"
+ ]
+ },
+ "rust-analyzer.debug.sourceFileMap": {
+ "type": [
+ "object",
+ "string"
+ ],
+ "const": "auto",
+ "description": "Optional source file mappings passed to the debug engine.",
+ "default": {
+ "/rustc/<id>": "${env:USERPROFILE}/.rustup/toolchains/<toolchain-id>/lib/rustlib/src/rust"
+ }
+ },
+ "rust-analyzer.debug.openDebugPane": {
+ "markdownDescription": "Whether to open up the `Debug Panel` on debugging start.",
+ "type": "boolean",
+ "default": false
+ },
+ "rust-analyzer.debug.engineSettings": {
+ "type": "object",
+ "default": {},
+ "markdownDescription": "Optional settings passed to the debug engine. Example: `{ \"lldb\": { \"terminal\":\"external\"} }`"
+ },
+ "rust-analyzer.restartServerOnConfigChange": {
+ "markdownDescription": "Whether to restart the server automatically when certain settings that require a restart are changed.",
+ "default": false,
+ "type": "boolean"
+ },
+ "rust-analyzer.typing.continueCommentsOnNewline": {
+ "markdownDescription": "Whether to prefix newlines after comments with the corresponding comment prefix.",
+ "default": true,
+ "type": "boolean"
+ },
+ "$generated-start": {},
+ "rust-analyzer.assist.expressionFillDefault": {
+ "markdownDescription": "Placeholder expression to use for missing expressions in assists.",
+ "default": "todo",
+ "type": "string",
+ "enum": [
+ "todo",
+ "default"
+ ],
+ "enumDescriptions": [
+ "Fill missing expressions with the `todo` macro",
+ "Fill missing expressions with reasonable defaults, `new` or `default` constructors."
+ ]
+ },
+ "rust-analyzer.cachePriming.enable": {
+ "markdownDescription": "Warm up caches on project load.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.cachePriming.numThreads": {
+ "markdownDescription": "How many worker threads to handle priming caches. The default `0` means to pick automatically.",
+ "default": 0,
+ "type": "number",
+ "minimum": 0,
+ "maximum": 255
+ },
+ "rust-analyzer.cargo.autoreload": {
+ "markdownDescription": "Automatically refresh project info via `cargo metadata` on\n`Cargo.toml` or `.cargo/config.toml` changes.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.cargo.buildScripts.enable": {
+ "markdownDescription": "Run build scripts (`build.rs`) for more precise code analysis.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.cargo.buildScripts.overrideCommand": {
+ "markdownDescription": "Override the command rust-analyzer uses to run build scripts and\nbuild procedural macros. The command is required to output json\nand should therefore include `--message-format=json` or a similar\noption.\n\nBy default, a cargo invocation will be constructed for the configured\ntargets and features, with the following base command line:\n\n```bash\ncargo check --quiet --workspace --message-format=json --all-targets\n```\n.",
+ "default": null,
+ "type": [
+ "null",
+ "array"
+ ],
+ "items": {
+ "type": "string"
+ }
+ },
+ "rust-analyzer.cargo.buildScripts.useRustcWrapper": {
+ "markdownDescription": "Use `RUSTC_WRAPPER=rust-analyzer` when running build scripts to\navoid checking unnecessary things.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.cargo.extraEnv": {
+ "markdownDescription": "Extra environment variables that will be set when running cargo, rustc\nor other commands within the workspace. Useful for setting RUSTFLAGS.",
+ "default": {},
+ "type": "object"
+ },
+ "rust-analyzer.cargo.features": {
+ "markdownDescription": "List of features to activate.\n\nSet this to `\"all\"` to pass `--all-features` to cargo.",
+ "default": [],
+ "anyOf": [
+ {
+ "type": "string",
+ "enum": [
+ "all"
+ ],
+ "enumDescriptions": [
+ "Pass `--all-features` to cargo"
+ ]
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ ]
+ },
+ "rust-analyzer.cargo.noDefaultFeatures": {
+ "markdownDescription": "Whether to pass `--no-default-features` to cargo.",
+ "default": false,
+ "type": "boolean"
+ },
- "markdownDescription": "Extra environment variables that will be set when running `cargo check`.",
++ "rust-analyzer.cargo.sysroot": {
++ "markdownDescription": "Relative path to the sysroot, or \"discover\" to try to automatically find it via\n\"rustc --print sysroot\".\n\nUnsetting this disables sysroot loading.\n\nThis option does not take effect until rust-analyzer is restarted.",
++ "default": "discover",
++ "type": [
++ "null",
++ "string"
++ ]
+ },
+ "rust-analyzer.cargo.target": {
+ "markdownDescription": "Compilation target override (target triple).",
+ "default": null,
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "rust-analyzer.cargo.unsetTest": {
+ "markdownDescription": "Unsets `#[cfg(test)]` for the specified crates.",
+ "default": [
+ "core"
+ ],
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "rust-analyzer.checkOnSave.allTargets": {
+ "markdownDescription": "Check all targets and tests (`--all-targets`).",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.checkOnSave.command": {
+ "markdownDescription": "Cargo command to use for `cargo check`.",
+ "default": "check",
+ "type": "string"
+ },
+ "rust-analyzer.checkOnSave.enable": {
+ "markdownDescription": "Run specified `cargo check` command for diagnostics on save.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.checkOnSave.extraArgs": {
+ "markdownDescription": "Extra arguments for `cargo check`.",
+ "default": [],
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "rust-analyzer.checkOnSave.extraEnv": {
++ "markdownDescription": "Extra environment variables that will be set when running `cargo check`.\nExtends `#rust-analyzer.cargo.extraEnv#`.",
+ "default": {},
+ "type": "object"
+ },
+ "rust-analyzer.checkOnSave.features": {
+ "markdownDescription": "List of features to activate. Defaults to\n`#rust-analyzer.cargo.features#`.\n\nSet to `\"all\"` to pass `--all-features` to Cargo.",
+ "default": null,
+ "anyOf": [
+ {
+ "type": "string",
+ "enum": [
+ "all"
+ ],
+ "enumDescriptions": [
+ "Pass `--all-features` to cargo"
+ ]
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "rust-analyzer.checkOnSave.noDefaultFeatures": {
+ "markdownDescription": "Whether to pass `--no-default-features` to Cargo. Defaults to\n`#rust-analyzer.cargo.noDefaultFeatures#`.",
+ "default": null,
+ "type": [
+ "null",
+ "boolean"
+ ]
+ },
+ "rust-analyzer.checkOnSave.overrideCommand": {
+ "markdownDescription": "Override the command rust-analyzer uses instead of `cargo check` for\ndiagnostics on save. The command is required to output json and\nshould therefor include `--message-format=json` or a similar option.\n\nIf you're changing this because you're using some tool wrapping\nCargo, you might also want to change\n`#rust-analyzer.cargo.buildScripts.overrideCommand#`.\n\nIf there are multiple linked projects, this command is invoked for\neach of them, with the working directory being the project root\n(i.e., the folder containing the `Cargo.toml`).\n\nAn example command would be:\n\n```bash\ncargo check --workspace --message-format=json --all-targets\n```\n.",
+ "default": null,
+ "type": [
+ "null",
+ "array"
+ ],
+ "items": {
+ "type": "string"
+ }
+ },
+ "rust-analyzer.checkOnSave.target": {
+ "markdownDescription": "Check for a specific target. Defaults to\n`#rust-analyzer.cargo.target#`.",
+ "default": null,
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "rust-analyzer.completion.autoimport.enable": {
+ "markdownDescription": "Toggles the additional completions that automatically add imports when completed.\nNote that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.completion.autoself.enable": {
+ "markdownDescription": "Toggles the additional completions that automatically show method calls and field accesses\nwith `self` prefixed to them when inside a method.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.completion.callable.snippets": {
+ "markdownDescription": "Whether to add parenthesis and argument snippets when completing function.",
+ "default": "fill_arguments",
+ "type": "string",
+ "enum": [
+ "fill_arguments",
+ "add_parentheses",
+ "none"
+ ],
+ "enumDescriptions": [
+ "Add call parentheses and pre-fill arguments.",
+ "Add call parentheses.",
+ "Do no snippet completions for callables."
+ ]
+ },
+ "rust-analyzer.completion.postfix.enable": {
+ "markdownDescription": "Whether to show postfix snippets like `dbg`, `if`, `not`, etc.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.completion.privateEditable.enable": {
+ "markdownDescription": "Enables completions of private items and fields that are defined in the current workspace even if they are not visible at the current position.",
+ "default": false,
+ "type": "boolean"
+ },
+ "rust-analyzer.completion.snippets.custom": {
+ "markdownDescription": "Custom completion snippets.",
+ "default": {
+ "Arc::new": {
+ "postfix": "arc",
+ "body": "Arc::new(${receiver})",
+ "requires": "std::sync::Arc",
+ "description": "Put the expression into an `Arc`",
+ "scope": "expr"
+ },
+ "Rc::new": {
+ "postfix": "rc",
+ "body": "Rc::new(${receiver})",
+ "requires": "std::rc::Rc",
+ "description": "Put the expression into an `Rc`",
+ "scope": "expr"
+ },
+ "Box::pin": {
+ "postfix": "pinbox",
+ "body": "Box::pin(${receiver})",
+ "requires": "std::boxed::Box",
+ "description": "Put the expression into a pinned `Box`",
+ "scope": "expr"
+ },
+ "Ok": {
+ "postfix": "ok",
+ "body": "Ok(${receiver})",
+ "description": "Wrap the expression in a `Result::Ok`",
+ "scope": "expr"
+ },
+ "Err": {
+ "postfix": "err",
+ "body": "Err(${receiver})",
+ "description": "Wrap the expression in a `Result::Err`",
+ "scope": "expr"
+ },
+ "Some": {
+ "postfix": "some",
+ "body": "Some(${receiver})",
+ "description": "Wrap the expression in an `Option::Some`",
+ "scope": "expr"
+ }
+ },
+ "type": "object"
+ },
+ "rust-analyzer.diagnostics.disabled": {
+ "markdownDescription": "List of rust-analyzer diagnostics to disable.",
+ "default": [],
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "uniqueItems": true
+ },
+ "rust-analyzer.diagnostics.enable": {
+ "markdownDescription": "Whether to show native rust-analyzer diagnostics.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.diagnostics.experimental.enable": {
+ "markdownDescription": "Whether to show experimental rust-analyzer diagnostics that might\nhave more false positives than usual.",
+ "default": false,
+ "type": "boolean"
+ },
+ "rust-analyzer.diagnostics.remapPrefix": {
+ "markdownDescription": "Map of prefixes to be substituted when parsing diagnostic file paths.\nThis should be the reverse mapping of what is passed to `rustc` as `--remap-path-prefix`.",
+ "default": {},
+ "type": "object"
+ },
+ "rust-analyzer.diagnostics.warningsAsHint": {
+ "markdownDescription": "List of warnings that should be displayed with hint severity.\n\nThe warnings will be indicated by faded text or three dots in code\nand will not show up in the `Problems Panel`.",
+ "default": [],
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "rust-analyzer.diagnostics.warningsAsInfo": {
+ "markdownDescription": "List of warnings that should be displayed with info severity.\n\nThe warnings will be indicated by a blue squiggly underline in code\nand a blue icon in the `Problems Panel`.",
+ "default": [],
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "rust-analyzer.files.excludeDirs": {
+ "markdownDescription": "These directories will be ignored by rust-analyzer. They are\nrelative to the workspace root, and globs are not supported. You may\nalso need to add the folders to Code's `files.watcherExclude`.",
+ "default": [],
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "rust-analyzer.files.watcher": {
+ "markdownDescription": "Controls file watching implementation.",
+ "default": "client",
+ "type": "string",
+ "enum": [
+ "client",
+ "server"
+ ],
+ "enumDescriptions": [
+ "Use the client (editor) to watch files for changes",
+ "Use server-side file watching"
+ ]
+ },
+ "rust-analyzer.highlightRelated.breakPoints.enable": {
+ "markdownDescription": "Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.highlightRelated.exitPoints.enable": {
+ "markdownDescription": "Enables highlighting of all exit points while the cursor is on any `return`, `?`, `fn`, or return type arrow (`->`).",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.highlightRelated.references.enable": {
+ "markdownDescription": "Enables highlighting of related references while the cursor is on any identifier.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.highlightRelated.yieldPoints.enable": {
+ "markdownDescription": "Enables highlighting of all break points for a loop or block context while the cursor is on any `async` or `await` keywords.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.hover.actions.debug.enable": {
+ "markdownDescription": "Whether to show `Debug` action. Only applies when\n`#rust-analyzer.hover.actions.enable#` is set.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.hover.actions.enable": {
+ "markdownDescription": "Whether to show HoverActions in Rust files.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.hover.actions.gotoTypeDef.enable": {
+ "markdownDescription": "Whether to show `Go to Type Definition` action. Only applies when\n`#rust-analyzer.hover.actions.enable#` is set.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.hover.actions.implementations.enable": {
+ "markdownDescription": "Whether to show `Implementations` action. Only applies when\n`#rust-analyzer.hover.actions.enable#` is set.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.hover.actions.references.enable": {
+ "markdownDescription": "Whether to show `References` action. Only applies when\n`#rust-analyzer.hover.actions.enable#` is set.",
+ "default": false,
+ "type": "boolean"
+ },
+ "rust-analyzer.hover.actions.run.enable": {
+ "markdownDescription": "Whether to show `Run` action. Only applies when\n`#rust-analyzer.hover.actions.enable#` is set.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.hover.documentation.enable": {
+ "markdownDescription": "Whether to show documentation on hover.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.hover.documentation.keywords.enable": {
+ "markdownDescription": "Whether to show keyword hover popups. Only applies when\n`#rust-analyzer.hover.documentation.enable#` is set.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.hover.links.enable": {
+ "markdownDescription": "Use markdown syntax for links in hover.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.imports.granularity.enforce": {
+ "markdownDescription": "Whether to enforce the import granularity setting for all files. If set to false rust-analyzer will try to keep import styles consistent per file.",
+ "default": false,
+ "type": "boolean"
+ },
+ "rust-analyzer.imports.granularity.group": {
+ "markdownDescription": "How imports should be grouped into use statements.",
+ "default": "crate",
+ "type": "string",
+ "enum": [
+ "preserve",
+ "crate",
+ "module",
+ "item"
+ ],
+ "enumDescriptions": [
+ "Do not change the granularity of any imports and preserve the original structure written by the developer.",
+ "Merge imports from the same crate into a single use statement. Conversely, imports from different crates are split into separate statements.",
+ "Merge imports from the same module into a single use statement. Conversely, imports from different modules are split into separate statements.",
+ "Flatten imports so that each has its own use statement."
+ ]
+ },
+ "rust-analyzer.imports.group.enable": {
+ "markdownDescription": "Group inserted imports by the [following order](https://rust-analyzer.github.io/manual.html#auto-import). Groups are separated by newlines.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.imports.merge.glob": {
+ "markdownDescription": "Whether to allow import insertion to merge new imports into single path glob imports like `use std::fmt::*;`.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.imports.prefer.no.std": {
+ "markdownDescription": "Prefer to unconditionally use imports of the core and alloc crate, over the std crate.",
+ "default": false,
+ "type": "boolean"
+ },
+ "rust-analyzer.imports.prefix": {
+ "markdownDescription": "The path structure for newly inserted paths to use.",
+ "default": "plain",
+ "type": "string",
+ "enum": [
+ "plain",
+ "self",
+ "crate"
+ ],
+ "enumDescriptions": [
+ "Insert import paths relative to the current module, using up to one `super` prefix if the parent module contains the requested item.",
+ "Insert import paths relative to the current module, using up to one `super` prefix if the parent module contains the requested item. Prefixes `self` in front of the path if it starts with a module.",
+ "Force import paths to be absolute by always starting them with `crate` or the extern crate name they come from."
+ ]
+ },
+ "rust-analyzer.inlayHints.bindingModeHints.enable": {
+ "markdownDescription": "Whether to show inlay type hints for binding modes.",
+ "default": false,
+ "type": "boolean"
+ },
+ "rust-analyzer.inlayHints.chainingHints.enable": {
+ "markdownDescription": "Whether to show inlay type hints for method chains.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.inlayHints.closingBraceHints.enable": {
+ "markdownDescription": "Whether to show inlay hints after a closing `}` to indicate what item it belongs to.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.inlayHints.closingBraceHints.minLines": {
+ "markdownDescription": "Minimum number of lines required before the `}` until the hint is shown (set to 0 or 1\nto always show them).",
+ "default": 25,
+ "type": "integer",
+ "minimum": 0
+ },
+ "rust-analyzer.inlayHints.closureReturnTypeHints.enable": {
+ "markdownDescription": "Whether to show inlay type hints for return types of closures.",
+ "default": "never",
+ "type": "string",
+ "enum": [
+ "always",
+ "never",
+ "with_block"
+ ],
+ "enumDescriptions": [
+ "Always show type hints for return types of closures.",
+ "Never show type hints for return types of closures.",
+ "Only show type hints for return types of closures with blocks."
+ ]
+ },
+ "rust-analyzer.inlayHints.lifetimeElisionHints.enable": {
+ "markdownDescription": "Whether to show inlay type hints for elided lifetimes in function signatures.",
+ "default": "never",
+ "type": "string",
+ "enum": [
+ "always",
+ "never",
+ "skip_trivial"
+ ],
+ "enumDescriptions": [
+ "Always show lifetime elision hints.",
+ "Never show lifetime elision hints.",
+ "Only show lifetime elision hints if a return type is involved."
+ ]
+ },
+ "rust-analyzer.inlayHints.lifetimeElisionHints.useParameterNames": {
+ "markdownDescription": "Whether to prefer using parameter names as the name for elided lifetime hints if possible.",
+ "default": false,
+ "type": "boolean"
+ },
+ "rust-analyzer.inlayHints.maxLength": {
+ "markdownDescription": "Maximum length for inlay hints. Set to null to have an unlimited length.",
+ "default": 25,
+ "type": [
+ "null",
+ "integer"
+ ],
+ "minimum": 0
+ },
+ "rust-analyzer.inlayHints.parameterHints.enable": {
+ "markdownDescription": "Whether to show function parameter name inlay hints at the call\nsite.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.inlayHints.reborrowHints.enable": {
+ "markdownDescription": "Whether to show inlay type hints for compiler inserted reborrows.",
+ "default": "never",
+ "type": "string",
+ "enum": [
+ "always",
+ "never",
+ "mutable"
+ ],
+ "enumDescriptions": [
+ "Always show reborrow hints.",
+ "Never show reborrow hints.",
+ "Only show mutable reborrow hints."
+ ]
+ },
+ "rust-analyzer.inlayHints.renderColons": {
+ "markdownDescription": "Whether to render leading colons for type hints, and trailing colons for parameter hints.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.inlayHints.typeHints.enable": {
+ "markdownDescription": "Whether to show inlay type hints for variables.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.inlayHints.typeHints.hideClosureInitialization": {
+ "markdownDescription": "Whether to hide inlay type hints for `let` statements that initialize to a closure.\nOnly applies to closures with blocks, same as `#rust-analyzer.inlayHints.closureReturnTypeHints.enable#`.",
+ "default": false,
+ "type": "boolean"
+ },
+ "rust-analyzer.inlayHints.typeHints.hideNamedConstructor": {
+ "markdownDescription": "Whether to hide inlay type hints for constructors.",
+ "default": false,
+ "type": "boolean"
+ },
+ "rust-analyzer.joinLines.joinAssignments": {
+ "markdownDescription": "Join lines merges consecutive declaration and initialization of an assignment.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.joinLines.joinElseIf": {
+ "markdownDescription": "Join lines inserts else between consecutive ifs.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.joinLines.removeTrailingComma": {
+ "markdownDescription": "Join lines removes trailing commas.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.joinLines.unwrapTrivialBlock": {
+ "markdownDescription": "Join lines unwraps trivial blocks.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.lens.debug.enable": {
+ "markdownDescription": "Whether to show `Debug` lens. Only applies when\n`#rust-analyzer.lens.enable#` is set.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.lens.enable": {
+ "markdownDescription": "Whether to show CodeLens in Rust files.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.lens.forceCustomCommands": {
+ "markdownDescription": "Internal config: use custom client-side commands even when the\nclient doesn't set the corresponding capability.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.lens.implementations.enable": {
+ "markdownDescription": "Whether to show `Implementations` lens. Only applies when\n`#rust-analyzer.lens.enable#` is set.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.lens.location": {
+ "markdownDescription": "Where to render annotations.",
+ "default": "above_name",
+ "type": "string",
+ "enum": [
+ "above_name",
+ "above_whole_item"
+ ],
+ "enumDescriptions": [
+ "Render annotations above the name of the item.",
+ "Render annotations above the whole item, including documentation comments and attributes."
+ ]
+ },
+ "rust-analyzer.lens.references.adt.enable": {
+ "markdownDescription": "Whether to show `References` lens for Struct, Enum, and Union.\nOnly applies when `#rust-analyzer.lens.enable#` is set.",
+ "default": false,
+ "type": "boolean"
+ },
+ "rust-analyzer.lens.references.enumVariant.enable": {
+ "markdownDescription": "Whether to show `References` lens for Enum Variants.\nOnly applies when `#rust-analyzer.lens.enable#` is set.",
+ "default": false,
+ "type": "boolean"
+ },
+ "rust-analyzer.lens.references.method.enable": {
+ "markdownDescription": "Whether to show `Method References` lens. Only applies when\n`#rust-analyzer.lens.enable#` is set.",
+ "default": false,
+ "type": "boolean"
+ },
+ "rust-analyzer.lens.references.trait.enable": {
+ "markdownDescription": "Whether to show `References` lens for Trait.\nOnly applies when `#rust-analyzer.lens.enable#` is set.",
+ "default": false,
+ "type": "boolean"
+ },
+ "rust-analyzer.lens.run.enable": {
+ "markdownDescription": "Whether to show `Run` lens. Only applies when\n`#rust-analyzer.lens.enable#` is set.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.linkedProjects": {
+ "markdownDescription": "Disable project auto-discovery in favor of explicitly specified set\nof projects.\n\nElements must be paths pointing to `Cargo.toml`,\n`rust-project.json`, or JSON objects in `rust-project.json` format.",
+ "default": [],
+ "type": "array",
+ "items": {
+ "type": [
+ "string",
+ "object"
+ ]
+ }
+ },
+ "rust-analyzer.lru.capacity": {
+ "markdownDescription": "Number of syntax trees rust-analyzer keeps in memory. Defaults to 128.",
+ "default": null,
+ "type": [
+ "null",
+ "integer"
+ ],
+ "minimum": 0
+ },
+ "rust-analyzer.notifications.cargoTomlNotFound": {
+ "markdownDescription": "Whether to show `can't find Cargo.toml` error message.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.procMacro.attributes.enable": {
+ "markdownDescription": "Expand attribute macros. Requires `#rust-analyzer.procMacro.enable#` to be set.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.procMacro.enable": {
+ "markdownDescription": "Enable support for procedural macros, implies `#rust-analyzer.cargo.buildScripts.enable#`.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.procMacro.ignored": {
+ "markdownDescription": "These proc-macros will be ignored when trying to expand them.\n\nThis config takes a map of crate names with the exported proc-macro names to ignore as values.",
+ "default": {},
+ "type": "object"
+ },
+ "rust-analyzer.procMacro.server": {
+ "markdownDescription": "Internal config, path to proc-macro server executable (typically,\nthis is rust-analyzer itself, but we override this in tests).",
+ "default": null,
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "rust-analyzer.references.excludeImports": {
+ "markdownDescription": "Exclude imports from find-all-references.",
+ "default": false,
+ "type": "boolean"
+ },
+ "rust-analyzer.runnables.command": {
+ "markdownDescription": "Command to be executed instead of 'cargo' for runnables.",
+ "default": null,
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "rust-analyzer.runnables.extraArgs": {
+ "markdownDescription": "Additional arguments to be passed to cargo for runnables such as\ntests or binaries. For example, it may be `--release`.",
+ "default": [],
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "rust-analyzer.rustc.source": {
+ "markdownDescription": "Path to the Cargo.toml of the rust compiler workspace, for usage in rustc_private\nprojects, or \"discover\" to try to automatically find it if the `rustc-dev` component\nis installed.\n\nAny project which uses rust-analyzer with the rustcPrivate\ncrates must set `[package.metadata.rust-analyzer] rustc_private=true` to use it.\n\nThis option does not take effect until rust-analyzer is restarted.",
+ "default": null,
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "rust-analyzer.rustfmt.extraArgs": {
+ "markdownDescription": "Additional arguments to `rustfmt`.",
+ "default": [],
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "rust-analyzer.rustfmt.overrideCommand": {
+ "markdownDescription": "Advanced option, fully override the command rust-analyzer uses for\nformatting.",
+ "default": null,
+ "type": [
+ "null",
+ "array"
+ ],
+ "items": {
+ "type": "string"
+ }
+ },
+ "rust-analyzer.rustfmt.rangeFormatting.enable": {
+ "markdownDescription": "Enables the use of rustfmt's unstable range formatting command for the\n`textDocument/rangeFormatting` request. The rustfmt option is unstable and only\navailable on a nightly build.",
+ "default": false,
+ "type": "boolean"
+ },
+ "rust-analyzer.semanticHighlighting.doc.comment.inject.enable": {
+ "markdownDescription": "Inject additional highlighting into doc comments.\n\nWhen enabled, rust-analyzer will highlight rust source in doc comments as well as intra\ndoc links.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.semanticHighlighting.operator.enable": {
+ "markdownDescription": "Use semantic tokens for operators.\n\nWhen disabled, rust-analyzer will emit semantic tokens only for operator tokens when\nthey are tagged with modifiers.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.semanticHighlighting.operator.specialization.enable": {
+ "markdownDescription": "Use specialized semantic tokens for operators.\n\nWhen enabled, rust-analyzer will emit special token types for operator tokens instead\nof the generic `operator` token type.",
+ "default": false,
+ "type": "boolean"
+ },
+ "rust-analyzer.semanticHighlighting.punctuation.enable": {
+ "markdownDescription": "Use semantic tokens for punctuations.\n\nWhen disabled, rust-analyzer will emit semantic tokens only for punctuation tokens when\nthey are tagged with modifiers or have a special role.",
+ "default": false,
+ "type": "boolean"
+ },
+ "rust-analyzer.semanticHighlighting.punctuation.separate.macro.bang": {
+ "markdownDescription": "When enabled, rust-analyzer will emit a punctuation semantic token for the `!` of macro\ncalls.",
+ "default": false,
+ "type": "boolean"
+ },
+ "rust-analyzer.semanticHighlighting.punctuation.specialization.enable": {
+ "markdownDescription": "Use specialized semantic tokens for punctuations.\n\nWhen enabled, rust-analyzer will emit special token types for punctuation tokens instead\nof the generic `punctuation` token type.",
+ "default": false,
+ "type": "boolean"
+ },
+ "rust-analyzer.semanticHighlighting.strings.enable": {
+ "markdownDescription": "Use semantic tokens for strings.\n\nIn some editors (e.g. vscode) semantic tokens override other highlighting grammars.\nBy disabling semantic tokens for strings, other grammars can be used to highlight\ntheir contents.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.signatureInfo.detail": {
+ "markdownDescription": "Show full signature of the callable. Only shows parameters if disabled.",
+ "default": "full",
+ "type": "string",
+ "enum": [
+ "full",
+ "parameters"
+ ],
+ "enumDescriptions": [
+ "Show the entire signature.",
+ "Show only the parameters."
+ ]
+ },
+ "rust-analyzer.signatureInfo.documentation.enable": {
+ "markdownDescription": "Show documentation.",
+ "default": true,
+ "type": "boolean"
+ },
+ "rust-analyzer.typing.autoClosingAngleBrackets.enable": {
+ "markdownDescription": "Whether to insert closing angle brackets when typing an opening angle bracket of a generic argument list.",
+ "default": false,
+ "type": "boolean"
+ },
+ "rust-analyzer.workspace.symbol.search.kind": {
+ "markdownDescription": "Workspace symbol search kind.",
+ "default": "only_types",
+ "type": "string",
+ "enum": [
+ "only_types",
+ "all_symbols"
+ ],
+ "enumDescriptions": [
+ "Search for types only.",
+ "Search for all symbols kinds."
+ ]
+ },
+ "rust-analyzer.workspace.symbol.search.limit": {
+ "markdownDescription": "Limits the number of items returned from a workspace symbol search (Defaults to 128).\nSome clients like vs-code issue new searches on result filtering and don't require all results to be returned in the initial search.\nOther clients requires all results upfront and might require a higher limit.",
+ "default": 128,
+ "type": "integer",
+ "minimum": 0
+ },
+ "rust-analyzer.workspace.symbol.search.scope": {
+ "markdownDescription": "Workspace symbol search scope.",
+ "default": "workspace",
+ "type": "string",
+ "enum": [
+ "workspace",
+ "workspace_and_dependencies"
+ ],
+ "enumDescriptions": [
+ "Search in current workspace only.",
+ "Search in current workspace and dependencies."
+ ]
+ },
+ "$generated-end": {}
+ }
+ },
+ "problemPatterns": [
+ {
+ "name": "rustc",
+ "patterns": [
+ {
+ "regexp": "^(warning|warn|error)(?:\\[(.*?)\\])?: (.*)$",
+ "severity": 1,
+ "code": 2,
+ "message": 3
+ },
+ {
+ "regexp": "^[\\s->=]*(.*?):(\\d*):(\\d*)\\s*$",
+ "file": 1,
+ "line": 2,
+ "column": 3
+ }
+ ]
+ },
+ {
+ "name": "rustc-json",
+ "patterns": [
+ {
+ "regexp": "^.*\"message\":{\"message\":\"([^\"]*).*?\"file_name\":\"([^\"]+).*?\"line_start\":(\\d+).*?\"line_end\":(\\d+).*?\"column_start\":(\\d+).*?\"column_end\":(\\d+).*}$",
+ "message": 1,
+ "file": 2,
+ "line": 3,
+ "endLine": 4,
+ "column": 5,
+ "endColumn": 6
+ }
+ ]
+ }
+ ],
+ "languages": [
+ {
+ "id": "ra_syntax_tree",
+ "extensions": [
+ ".rast"
+ ]
+ },
+ {
+ "id": "rust",
+ "extensions": [
+ ".rs"
+ ],
+ "aliases": [
+ "Rust",
+ "rs"
+ ],
+ "configuration": "language-configuration.json"
+ }
+ ],
+ "grammars": [
+ {
+ "language": "ra_syntax_tree",
+ "scopeName": "source.ra_syntax_tree",
+ "path": "ra_syntax_tree.tmGrammar.json"
+ }
+ ],
+ "problemMatchers": [
+ {
+ "name": "rustc",
+ "owner": "rustc",
+ "source": "rustc",
+ "fileLocation": [
+ "autoDetect",
+ "${workspaceRoot}"
+ ],
+ "pattern": "$rustc"
+ },
+ {
+ "name": "rustc-json",
+ "owner": "rustc",
+ "source": "rustc",
+ "fileLocation": [
+ "autoDetect",
+ "${workspaceRoot}"
+ ],
+ "pattern": "$rustc-json"
+ },
+ {
+ "name": "rustc-watch",
+ "owner": "rustc",
+ "source": "rustc",
+ "fileLocation": [
+ "autoDetect",
+ "${workspaceRoot}"
+ ],
+ "background": {
+ "beginsPattern": "^\\[Running\\b",
+ "endsPattern": "^\\[Finished running\\b"
+ },
+ "pattern": "$rustc"
+ }
+ ],
+ "colors": [
+ {
+ "id": "rust_analyzer.syntaxTreeBorder",
+ "description": "Color of the border displayed in the Rust source code for the selected syntax node (see \"Show Syntax Tree\" command)",
+ "defaults": {
+ "dark": "#ffffff",
+ "light": "#b700ff",
+ "highContrast": "#b700ff"
+ }
+ }
+ ],
+ "semanticTokenTypes": [
+ {
+ "id": "angle",
+ "description": "Style for < or >",
+ "superType": "punctuation"
+ },
+ {
+ "id": "arithmetic",
+ "description": "Style for arithmetic operators",
+ "superType": "operator"
+ },
+ {
+ "id": "attribute",
+ "description": "Style for attributes"
+ },
+ {
+ "id": "attributeBracket",
+ "description": "Style for attribute invocation brackets, that is the `#[` and `]` tokens",
+ "superType": "punctuation"
+ },
+ {
+ "id": "bitwise",
+ "description": "Style for bitwise operators",
+ "superType": "operator"
+ },
+ {
+ "id": "boolean",
+ "description": "Style for boolean literals",
+ "superType": "keyword"
+ },
+ {
+ "id": "brace",
+ "description": "Style for { or }",
+ "superType": "punctuation"
+ },
+ {
+ "id": "bracket",
+ "description": "Style for [ or ]",
+ "superType": "punctuation"
+ },
+ {
+ "id": "builtinAttribute",
+ "description": "Style for builtin attributes",
+ "superType": "attribute"
+ },
+ {
+ "id": "builtinType",
+ "description": "Style for builtin types",
+ "superType": "type"
+ },
+ {
+ "id": "character",
+ "description": "Style for character literals",
+ "superType": "string"
+ },
+ {
+ "id": "colon",
+ "description": "Style for :",
+ "superType": "punctuation"
+ },
+ {
+ "id": "comma",
+ "description": "Style for ,",
+ "superType": "punctuation"
+ },
+ {
+ "id": "comparison",
+ "description": "Style for comparison operators",
+ "superType": "operator"
+ },
+ {
+ "id": "constParameter",
+ "description": "Style for const generics"
+ },
+ {
+ "id": "derive",
+ "description": "Style for derives",
+ "superType": "attribute"
+ },
+ {
+ "id": "dot",
+ "description": "Style for .",
+ "superType": "punctuation"
+ },
+ {
+ "id": "escapeSequence",
+ "description": "Style for char escapes in strings"
+ },
+ {
+ "id": "formatSpecifier",
+ "description": "Style for {} placeholders in format strings"
+ },
+ {
+ "id": "label",
+ "description": "Style for labels"
+ },
+ {
+ "id": "lifetime",
+ "description": "Style for lifetimes"
+ },
+ {
+ "id": "logical",
+ "description": "Style for logic operators",
+ "superType": "operator"
+ },
+ {
+ "id": "macroBang",
+ "description": "Style for the ! token of macro calls",
+ "superType": "punctuation"
+ },
+ {
+ "id": "operator",
+ "description": "Style for operators",
+ "superType": "punctuation"
+ },
+ {
+ "id": "parenthesis",
+ "description": "Style for ( or )",
+ "superType": "punctuation"
+ },
+ {
+ "id": "punctuation",
+ "description": "Style for generic punctuation"
+ },
+ {
+ "id": "selfKeyword",
+ "description": "Style for the self keyword",
+ "superType": "keyword"
+ },
+ {
+ "id": "selfTypeKeyword",
+ "description": "Style for the self type keyword",
+ "superType": "keyword"
+ },
+ {
+ "id": "semicolon",
+ "description": "Style for ;",
+ "superType": "punctuation"
+ },
+ {
+ "id": "typeAlias",
+ "description": "Style for type aliases",
+ "superType": "type"
+ },
+ {
+ "id": "union",
+ "description": "Style for C-style untagged unions",
+ "superType": "type"
+ },
+ {
+ "id": "unresolvedReference",
+ "description": "Style for names which can not be resolved due to compilation errors"
+ }
+ ],
+ "semanticTokenModifiers": [
+ {
+ "id": "async",
+ "description": "Style for async functions and the `async` and `await` keywords"
+ },
+ {
+ "id": "attribute",
+ "description": "Style for elements within attributes"
+ },
+ {
+ "id": "callable",
+ "description": "Style for locals whose types implements one of the `Fn*` traits"
+ },
+ {
+ "id": "constant",
+ "description": "Style for compile-time constants"
+ },
+ {
+ "id": "consuming",
+ "description": "Style for locals that are being consumed when use in a function call"
+ },
+ {
+ "id": "controlFlow",
+ "description": "Style for control-flow related tokens, this includes the `?` operator"
+ },
+ {
+ "id": "crateRoot",
+ "description": "Style for names resolving to a crate root"
+ },
+ {
+ "id": "injected",
+ "description": "Style for doc-string injected highlighting like rust source blocks in documentation"
+ },
+ {
+ "id": "intraDocLink",
+ "description": "Style for intra doc links in doc-strings"
+ },
+ {
+ "id": "library",
+ "description": "Style for items that are defined outside of the current crate"
+ },
+ {
+ "id": "mutable",
+ "description": "Style for mutable locals and statics as well as functions taking `&mut self`"
+ },
+ {
+ "id": "public",
+ "description": "Style for items that are from the current crate and are `pub`"
+ },
+ {
+ "id": "reference",
+ "description": "Style for locals behind a reference and functions taking `self` by reference"
+ },
+ {
+ "id": "trait",
+ "description": "Style for associated trait items"
+ },
+ {
+ "id": "unsafe",
+ "description": "Style for unsafe operations, like unsafe function calls, as well as the `unsafe` token"
+ }
+ ],
+ "semanticTokenScopes": [
+ {
+ "language": "rust",
+ "scopes": {
+ "attribute": [
+ "meta.attribute.rust"
+ ],
+ "boolean": [
+ "constant.language.boolean.rust"
+ ],
+ "builtinType": [
+ "support.type.primitive.rust"
+ ],
+ "constParameter": [
+ "constant.other.caps.rust"
+ ],
+ "enum": [
+ "entity.name.type.enum.rust"
+ ],
+ "formatSpecifier": [
+ "punctuation.section.embedded.rust"
+ ],
+ "function": [
+ "entity.name.function.rust"
+ ],
+ "interface": [
+ "entity.name.type.trait.rust"
+ ],
+ "keyword": [
+ "keyword.other.rust"
+ ],
+ "keyword.controlFlow": [
+ "keyword.control.rust"
+ ],
+ "lifetime": [
+ "storage.modifier.lifetime.rust"
+ ],
+ "macroBang": [
+ "entity.name.function.macro.rust"
+ ],
+ "method": [
+ "entity.name.function.rust"
+ ],
+ "struct": [
+ "entity.name.type.struct.rust"
+ ],
+ "typeAlias": [
+ "entity.name.type.declaration.rust"
+ ],
+ "union": [
+ "entity.name.type.union.rust"
+ ],
+ "variable": [
+ "variable.other.rust"
+ ],
+ "variable.constant": [
+ "variable.other.constant.rust"
+ ],
+ "*.mutable": [
+ "markup.underline"
+ ]
+ }
+ }
+ ],
+ "menus": {
+ "commandPalette": [
+ {
+ "command": "rust-analyzer.syntaxTree",
+ "when": "inRustProject"
+ },
+ {
+ "command": "rust-analyzer.viewHir",
+ "when": "inRustProject"
+ },
+ {
+ "command": "rust-analyzer.viewFileText",
+ "when": "inRustProject"
+ },
+ {
+ "command": "rust-analyzer.expandMacro",
+ "when": "inRustProject"
+ },
+ {
+ "command": "rust-analyzer.matchingBrace",
+ "when": "inRustProject"
+ },
+ {
+ "command": "rust-analyzer.parentModule",
+ "when": "inRustProject"
+ },
+ {
+ "command": "rust-analyzer.joinLines",
+ "when": "inRustProject"
+ },
+ {
+ "command": "rust-analyzer.run",
+ "when": "inRustProject"
+ },
+ {
+ "command": "rust-analyzer.debug",
+ "when": "inRustProject"
+ },
+ {
+ "command": "rust-analyzer.newDebugConfig",
+ "when": "inRustProject"
+ },
+ {
+ "command": "rust-analyzer.analyzerStatus",
+ "when": "inRustProject"
+ },
+ {
+ "command": "rust-analyzer.memoryUsage",
+ "when": "inRustProject"
+ },
+ {
+ "command": "rust-analyzer.reloadWorkspace",
+ "when": "inRustProject"
+ },
+ {
+ "command": "rust-analyzer.reload",
+ "when": "inRustProject"
+ },
+ {
+ "command": "rust-analyzer.onEnter",
+ "when": "inRustProject"
+ },
+ {
+ "command": "rust-analyzer.ssr",
+ "when": "inRustProject"
+ },
+ {
+ "command": "rust-analyzer.serverVersion",
+ "when": "inRustProject"
+ },
+ {
+ "command": "rust-analyzer.openDocs",
+ "when": "inRustProject"
+ },
+ {
+ "command": "rust-analyzer.openCargoToml",
+ "when": "inRustProject"
+ }
+ ],
+ "editor/context": [
+ {
+ "command": "rust-analyzer.peekTests",
+ "when": "inRustProject",
+ "group": "navigation@1000"
+ }
+ ]
+ }
+ }
+}
--- /dev/null
- version = "0.6.0"
+[package]
+name = "lsp-server"
- serde_json = "1.0.81"
- serde = { version = "1.0.137", features = ["derive"] }
- crossbeam-channel = "0.5.5"
++version = "0.7.0"
+description = "Generic LSP server scaffold."
+license = "MIT OR Apache-2.0"
+repository = "https://github.com/rust-lang/rust-analyzer/tree/master/lib/lsp-server"
+edition = "2021"
+
+[dependencies]
+log = "0.4.17"
- lsp-types = "0.93.0"
++serde_json = "1.0.85"
++serde = { version = "1.0.144", features = ["derive"] }
++crossbeam-channel = "0.5.6"
+
+[dev-dependencies]
++lsp-types = "0.93.1"
--- /dev/null
- #[allow(unused)]
+use std::{
+ fmt,
+ io::{self, BufRead, Write},
+};
+
+use serde::{de::DeserializeOwned, Deserialize, Serialize};
+
+use crate::error::ExtractError;
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+#[serde(untagged)]
+pub enum Message {
+ Request(Request),
+ Response(Response),
+ Notification(Notification),
+}
+
+impl From<Request> for Message {
+ fn from(request: Request) -> Message {
+ Message::Request(request)
+ }
+}
+
+impl From<Response> for Message {
+ fn from(response: Response) -> Message {
+ Message::Response(response)
+ }
+}
+
+impl From<Notification> for Message {
+ fn from(notification: Notification) -> Message {
+ Message::Notification(notification)
+ }
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+#[serde(transparent)]
+pub struct RequestId(IdRepr);
+
+#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+#[serde(untagged)]
+enum IdRepr {
+ I32(i32),
+ String(String),
+}
+
+impl From<i32> for RequestId {
+ fn from(id: i32) -> RequestId {
+ RequestId(IdRepr::I32(id))
+ }
+}
+
+impl From<String> for RequestId {
+ fn from(id: String) -> RequestId {
+ RequestId(IdRepr::String(id))
+ }
+}
+
+impl fmt::Display for RequestId {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match &self.0 {
+ IdRepr::I32(it) => fmt::Display::fmt(it, f),
+ // Use debug here, to make it clear that `92` and `"92"` are
+ // different, and to reduce WTF factor if the sever uses `" "` as an
+ // ID.
+ IdRepr::String(it) => fmt::Debug::fmt(it, f),
+ }
+ }
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct Request {
+ pub id: RequestId,
+ pub method: String,
+ #[serde(default = "serde_json::Value::default")]
+ #[serde(skip_serializing_if = "serde_json::Value::is_null")]
+ pub params: serde_json::Value,
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct Response {
+ // JSON RPC allows this to be null if it was impossible
+ // to decode the request's id. Ignore this special case
+ // and just die horribly.
+ pub id: RequestId,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub result: Option<serde_json::Value>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub error: Option<ResponseError>,
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct ResponseError {
+ pub code: i32,
+ pub message: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub data: Option<serde_json::Value>,
+}
+
+#[derive(Clone, Copy, Debug)]
++#[non_exhaustive]
+pub enum ErrorCode {
+ // Defined by JSON RPC:
+ ParseError = -32700,
+ InvalidRequest = -32600,
+ MethodNotFound = -32601,
+ InvalidParams = -32602,
+ InternalError = -32603,
+ ServerErrorStart = -32099,
+ ServerErrorEnd = -32000,
+
+ /// Error code indicating that a server received a notification or
+ /// request before the server has received the `initialize` request.
+ ServerNotInitialized = -32002,
+ UnknownErrorCode = -32001,
+
+ // Defined by the protocol:
+ /// The client has canceled a request and a server has detected
+ /// the cancel.
+ RequestCanceled = -32800,
+
+ /// The server detected that the content of a document got
+ /// modified outside normal conditions. A server should
+ /// NOT send this error code if it detects a content change
+ /// in it unprocessed messages. The result even computed
+ /// on an older state might still be useful for the client.
+ ///
+ /// If a client decides that a result is not of any use anymore
+ /// the client should cancel the request.
+ ContentModified = -32801,
+
+ /// The server cancelled the request. This error code should
+ /// only be used for requests that explicitly support being
+ /// server cancellable.
+ ///
+ /// @since 3.17.0
+ ServerCancelled = -32802,
++
++ /// A request failed but it was syntactically correct, e.g the
++ /// method name was known and the parameters were valid. The error
++ /// message should contain human readable information about why
++ /// the request failed.
++ ///
++ /// @since 3.17.0
++ RequestFailed = -32803,
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct Notification {
+ pub method: String,
+ #[serde(default = "serde_json::Value::default")]
+ #[serde(skip_serializing_if = "serde_json::Value::is_null")]
+ pub params: serde_json::Value,
+}
+
+impl Message {
+ pub fn read(r: &mut impl BufRead) -> io::Result<Option<Message>> {
+ Message::_read(r)
+ }
+ fn _read(r: &mut dyn BufRead) -> io::Result<Option<Message>> {
+ let text = match read_msg_text(r)? {
+ None => return Ok(None),
+ Some(text) => text,
+ };
+ let msg = serde_json::from_str(&text)?;
+ Ok(Some(msg))
+ }
+ pub fn write(self, w: &mut impl Write) -> io::Result<()> {
+ self._write(w)
+ }
+ fn _write(self, w: &mut dyn Write) -> io::Result<()> {
+ #[derive(Serialize)]
+ struct JsonRpc {
+ jsonrpc: &'static str,
+ #[serde(flatten)]
+ msg: Message,
+ }
+ let text = serde_json::to_string(&JsonRpc { jsonrpc: "2.0", msg: self })?;
+ write_msg_text(w, &text)
+ }
+}
+
+impl Response {
+ pub fn new_ok<R: Serialize>(id: RequestId, result: R) -> Response {
+ Response { id, result: Some(serde_json::to_value(result).unwrap()), error: None }
+ }
+ pub fn new_err(id: RequestId, code: i32, message: String) -> Response {
+ let error = ResponseError { code, message, data: None };
+ Response { id, result: None, error: Some(error) }
+ }
+}
+
+impl Request {
+ pub fn new<P: Serialize>(id: RequestId, method: String, params: P) -> Request {
+ Request { id, method, params: serde_json::to_value(params).unwrap() }
+ }
+ pub fn extract<P: DeserializeOwned>(
+ self,
+ method: &str,
+ ) -> Result<(RequestId, P), ExtractError<Request>> {
+ if self.method != method {
+ return Err(ExtractError::MethodMismatch(self));
+ }
+ match serde_json::from_value(self.params) {
+ Ok(params) => Ok((self.id, params)),
+ Err(error) => Err(ExtractError::JsonError { method: self.method, error }),
+ }
+ }
+
+ pub(crate) fn is_shutdown(&self) -> bool {
+ self.method == "shutdown"
+ }
+ pub(crate) fn is_initialize(&self) -> bool {
+ self.method == "initialize"
+ }
+}
+
+impl Notification {
+ pub fn new(method: String, params: impl Serialize) -> Notification {
+ Notification { method, params: serde_json::to_value(params).unwrap() }
+ }
+ pub fn extract<P: DeserializeOwned>(
+ self,
+ method: &str,
+ ) -> Result<P, ExtractError<Notification>> {
+ if self.method != method {
+ return Err(ExtractError::MethodMismatch(self));
+ }
+ match serde_json::from_value(self.params) {
+ Ok(params) => Ok(params),
+ Err(error) => Err(ExtractError::JsonError { method: self.method, error }),
+ }
+ }
+ pub(crate) fn is_exit(&self) -> bool {
+ self.method == "exit"
+ }
+ pub(crate) fn is_initialized(&self) -> bool {
+ self.method == "initialized"
+ }
+}
+
+fn read_msg_text(inp: &mut dyn BufRead) -> io::Result<Option<String>> {
+ fn invalid_data(error: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> io::Error {
+ io::Error::new(io::ErrorKind::InvalidData, error)
+ }
+ macro_rules! invalid_data {
+ ($($tt:tt)*) => (invalid_data(format!($($tt)*)))
+ }
+
+ let mut size = None;
+ let mut buf = String::new();
+ loop {
+ buf.clear();
+ if inp.read_line(&mut buf)? == 0 {
+ return Ok(None);
+ }
+ if !buf.ends_with("\r\n") {
+ return Err(invalid_data!("malformed header: {:?}", buf));
+ }
+ let buf = &buf[..buf.len() - 2];
+ if buf.is_empty() {
+ break;
+ }
+ let mut parts = buf.splitn(2, ": ");
+ let header_name = parts.next().unwrap();
+ let header_value =
+ parts.next().ok_or_else(|| invalid_data!("malformed header: {:?}", buf))?;
+ if header_name == "Content-Length" {
+ size = Some(header_value.parse::<usize>().map_err(invalid_data)?);
+ }
+ }
+ let size: usize = size.ok_or_else(|| invalid_data!("no Content-Length"))?;
+ let mut buf = buf.into_bytes();
+ buf.resize(size, 0);
+ inp.read_exact(&mut buf)?;
+ let buf = String::from_utf8(buf).map_err(invalid_data)?;
+ log::debug!("< {}", buf);
+ Ok(Some(buf))
+}
+
+fn write_msg_text(out: &mut dyn Write, msg: &str) -> io::Result<()> {
+ log::debug!("> {}", msg);
+ write!(out, "Content-Length: {}\r\n\r\n", msg.len())?;
+ out.write_all(msg.as_bytes())?;
+ out.flush()?;
+ Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{Message, Notification, Request, RequestId};
+
+ #[test]
+ fn shutdown_with_explicit_null() {
+ let text = "{\"jsonrpc\": \"2.0\",\"id\": 3,\"method\": \"shutdown\", \"params\": null }";
+ let msg: Message = serde_json::from_str(text).unwrap();
+
+ assert!(
+ matches!(msg, Message::Request(req) if req.id == 3.into() && req.method == "shutdown")
+ );
+ }
+
+ #[test]
+ fn shutdown_with_no_params() {
+ let text = "{\"jsonrpc\": \"2.0\",\"id\": 3,\"method\": \"shutdown\"}";
+ let msg: Message = serde_json::from_str(text).unwrap();
+
+ assert!(
+ matches!(msg, Message::Request(req) if req.id == 3.into() && req.method == "shutdown")
+ );
+ }
+
+ #[test]
+ fn notification_with_explicit_null() {
+ let text = "{\"jsonrpc\": \"2.0\",\"method\": \"exit\", \"params\": null }";
+ let msg: Message = serde_json::from_str(text).unwrap();
+
+ assert!(matches!(msg, Message::Notification(not) if not.method == "exit"));
+ }
+
+ #[test]
+ fn notification_with_no_params() {
+ let text = "{\"jsonrpc\": \"2.0\",\"method\": \"exit\"}";
+ let msg: Message = serde_json::from_str(text).unwrap();
+
+ assert!(matches!(msg, Message::Notification(not) if not.method == "exit"));
+ }
+
+ #[test]
+ fn serialize_request_with_null_params() {
+ let msg = Message::Request(Request {
+ id: RequestId::from(3),
+ method: "shutdown".into(),
+ params: serde_json::Value::Null,
+ });
+ let serialized = serde_json::to_string(&msg).unwrap();
+
+ assert_eq!("{\"id\":3,\"method\":\"shutdown\"}", serialized);
+ }
+
+ #[test]
+ fn serialize_notification_with_null_params() {
+ let msg = Message::Notification(Notification {
+ method: "exit".into(),
+ params: serde_json::Value::Null,
+ });
+ let serialized = serde_json::to_string(&msg).unwrap();
+
+ assert_eq!("{\"method\":\"exit\"}", serialized);
+ }
+}
--- /dev/null
+use std::collections::HashMap;
+
+use serde::Serialize;
+
+use crate::{ErrorCode, Request, RequestId, Response, ResponseError};
+
+/// Manages the set of pending requests, both incoming and outgoing.
+#[derive(Debug)]
+pub struct ReqQueue<I, O> {
+ pub incoming: Incoming<I>,
+ pub outgoing: Outgoing<O>,
+}
+
+impl<I, O> Default for ReqQueue<I, O> {
+ fn default() -> ReqQueue<I, O> {
+ ReqQueue {
+ incoming: Incoming { pending: HashMap::default() },
+ outgoing: Outgoing { next_id: 0, pending: HashMap::default() },
+ }
+ }
+}
+
+#[derive(Debug)]
+pub struct Incoming<I> {
+ pending: HashMap<RequestId, I>,
+}
+
+#[derive(Debug)]
+pub struct Outgoing<O> {
+ next_id: i32,
+ pending: HashMap<RequestId, O>,
+}
+
+impl<I> Incoming<I> {
+ pub fn register(&mut self, id: RequestId, data: I) {
+ self.pending.insert(id, data);
+ }
++
+ pub fn cancel(&mut self, id: RequestId) -> Option<Response> {
+ let _data = self.complete(id.clone())?;
+ let error = ResponseError {
+ code: ErrorCode::RequestCanceled as i32,
+ message: "canceled by client".to_string(),
+ data: None,
+ };
+ Some(Response { id, result: None, error: Some(error) })
+ }
++
+ pub fn complete(&mut self, id: RequestId) -> Option<I> {
+ self.pending.remove(&id)
+ }
++
++ pub fn is_completed(&self, id: &RequestId) -> bool {
++ !self.pending.contains_key(id)
++ }
+}
+
+impl<O> Outgoing<O> {
+ pub fn register<P: Serialize>(&mut self, method: String, params: P, data: O) -> Request {
+ let id = RequestId::from(self.next_id);
+ self.pending.insert(id.clone(), data);
+ self.next_id += 1;
+ Request::new(id, method, params)
+ }
++
+ pub fn complete(&mut self, id: RequestId) -> Option<O> {
+ self.pending.remove(&id)
+ }
+}
--- /dev/null
- xflags = "0.2.4"
+[package]
+name = "xtask"
+version = "0.1.0"
+publish = false
+license = "MIT OR Apache-2.0"
+edition = "2021"
+rust-version = "1.57"
+
+[dependencies]
+anyhow = "1.0.57"
+flate2 = "1.0.24"
+write-json = "0.1.2"
+xshell = "0.2.2"
++xflags = "0.3.0"
+# Avoid adding more dependencies to this crate
--- /dev/null
- default cmd help {
- /// Print help information.
- optional -h, --help
- }
+#![allow(unreachable_pub)]
+
+use crate::install::{ClientOpt, Malloc, ServerOpt};
+
+xflags::xflags! {
+ src "./src/flags.rs"
+
+ /// Run custom build command.
+ cmd xtask {
- cmd bb
+
+ /// Install rust-analyzer server or editor plugin.
+ cmd install {
+ /// Install only VS Code plugin.
+ optional --client
+ /// One of 'code', 'code-exploration', 'code-insiders', 'codium', or 'code-oss'.
+ optional --code-bin name: String
+
+ /// Install only the language server.
+ optional --server
+ /// Use mimalloc allocator for server
+ optional --mimalloc
+ /// Use jemalloc allocator for server
+ optional --jemalloc
+ }
+
+ cmd fuzz-tests {}
+
+ cmd release {
+ optional --dry-run
+ }
+ cmd promote {
+ optional --dry-run
+ }
+ cmd dist {
+ optional --client-patch-version version: String
+ }
+ cmd metrics {
+ optional --dry-run
+ }
+ /// Builds a benchmark version of rust-analyzer and puts it into `./target`.
- {}
++ cmd bb {
+ required suffix: String
- Help(Help),
++ }
+ }
+}
+
+// generated start
+// The following code is generated by `xflags` macro.
+// Run `env UPDATE_XFLAGS=1 cargo build` to regenerate.
+#[derive(Debug)]
+pub struct Xtask {
+ pub subcommand: XtaskCmd,
+}
+
+#[derive(Debug)]
+pub enum XtaskCmd {
- #[derive(Debug)]
- pub struct Help {
- pub help: bool,
- }
-
+ Install(Install),
+ FuzzTests(FuzzTests),
+ Release(Release),
+ Promote(Promote),
+ Dist(Dist),
+ Metrics(Metrics),
+ Bb(Bb),
+}
+
- pub const HELP: &'static str = Self::HELP_;
+#[derive(Debug)]
+pub struct Install {
+ pub client: bool,
+ pub code_bin: Option<String>,
+ pub server: bool,
+ pub mimalloc: bool,
+ pub jemalloc: bool,
+}
+
+#[derive(Debug)]
+pub struct FuzzTests;
+
+#[derive(Debug)]
+pub struct Release {
+ pub dry_run: bool,
+}
+
+#[derive(Debug)]
+pub struct Promote {
+ pub dry_run: bool,
+}
+
+#[derive(Debug)]
+pub struct Dist {
+ pub client_patch_version: Option<String>,
+}
+
+#[derive(Debug)]
+pub struct Metrics {
+ pub dry_run: bool,
+}
+
+#[derive(Debug)]
+pub struct Bb {
+ pub suffix: String,
+}
+
+impl Xtask {
++ #[allow(dead_code)]
++ pub fn from_env_or_exit() -> Self {
++ Self::from_env_or_exit_()
++ }
+
+ #[allow(dead_code)]
+ pub fn from_env() -> xflags::Result<Self> {
+ Self::from_env_()
+ }
+
+ #[allow(dead_code)]
+ pub fn from_vec(args: Vec<std::ffi::OsString>) -> xflags::Result<Self> {
+ Self::from_vec_(args)
+ }
+}
+// generated end
+
+impl Install {
+ pub(crate) fn server(&self) -> Option<ServerOpt> {
+ if self.client && !self.server {
+ return None;
+ }
+ let malloc = if self.mimalloc {
+ Malloc::Mimalloc
+ } else if self.jemalloc {
+ Malloc::Jemalloc
+ } else {
+ Malloc::System
+ };
+ Some(ServerOpt { malloc })
+ }
+ pub(crate) fn client(&self) -> Option<ClientOpt> {
+ if !self.client && self.server {
+ return None;
+ }
+ Some(ClientOpt { code_bin: self.code_bin.clone() })
+ }
+}
--- /dev/null
- let flags = flags::Xtask::from_env()?;
+//! See <https://github.com/matklad/cargo-xtask/>.
+//!
+//! This binary defines various auxiliary build commands, which are not
+//! expressible with just `cargo`. Notably, it provides tests via `cargo test -p xtask`
+//! 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`.
+
+#![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)]
+
+mod flags;
+
+mod install;
+mod release;
+mod dist;
+mod metrics;
+
+use anyhow::bail;
+use std::{
+ env,
+ path::{Path, PathBuf},
+};
+use xshell::{cmd, Shell};
+
+fn main() -> anyhow::Result<()> {
++ let flags = flags::Xtask::from_env_or_exit();
++
+ let sh = &Shell::new()?;
+ sh.change_dir(project_root());
+
- flags::XtaskCmd::Help(_) => {
- println!("{}", flags::Xtask::HELP);
- Ok(())
- }
+ match flags.subcommand {
+ flags::XtaskCmd::Install(cmd) => cmd.run(sh),
+ flags::XtaskCmd::FuzzTests(_) => run_fuzzer(sh),
+ flags::XtaskCmd::Release(cmd) => cmd.run(sh),
+ flags::XtaskCmd::Promote(cmd) => cmd.run(sh),
+ flags::XtaskCmd::Dist(cmd) => cmd.run(sh),
+ flags::XtaskCmd::Metrics(cmd) => cmd.run(sh),
+ flags::XtaskCmd::Bb(cmd) => {
+ {
+ let _d = sh.push_dir("./crates/rust-analyzer");
+ cmd!(sh, "cargo build --release --features jemalloc").run()?;
+ }
+ sh.copy_file(
+ "./target/release/rust-analyzer",
+ format!("./target/rust-analyzer-{}", cmd.suffix),
+ )?;
+ Ok(())
+ }
+ }
+}
+
+fn project_root() -> PathBuf {
+ Path::new(
+ &env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| env!("CARGO_MANIFEST_DIR").to_owned()),
+ )
+ .ancestors()
+ .nth(1)
+ .unwrap()
+ .to_path_buf()
+}
+
+fn run_fuzzer(sh: &Shell) -> anyhow::Result<()> {
+ let _d = sh.push_dir("./crates/syntax");
+ let _e = sh.push_env("RUSTUP_TOOLCHAIN", "nightly");
+ if cmd!(sh, "cargo fuzz --help").read().is_err() {
+ cmd!(sh, "cargo install cargo-fuzz").run()?;
+ };
+
+ // Expecting nightly rustc
+ let out = cmd!(sh, "rustc --version").read()?;
+ if !out.contains("nightly") {
+ bail!("fuzz tests require nightly rustc")
+ }
+
+ cmd!(sh, "cargo fuzz run parser").run()?;
+ Ok(())
+}
+
+fn date_iso(sh: &Shell) -> anyhow::Result<String> {
+ let res = cmd!(sh, "date -u +%Y-%m-%d").read()?;
+ Ok(res)
+}
+
+fn is_release_tag(tag: &str) -> bool {
+ tag.len() == "2020-02-24".len() && tag.starts_with(|c: char| c.is_ascii_digit())
+}