Merge remote-tracking branch 'upstream/master' into 503-hover-doc-links

Hasn't fixed tests yet.
This commit is contained in:
Zac Pullar-Strecker 2020-07-31 14:12:44 +12:00
commit f05d7b41a7
655 changed files with 34763 additions and 36934 deletions

4
.gitattributes vendored
View file

@ -1,2 +1,6 @@
* text=auto eol=lf * text=auto eol=lf
crates/ra_syntax/test_data/** -text eof=LF crates/ra_syntax/test_data/** -text eof=LF
# Older git versions try to fix line endings on images, this prevents it.
*.png binary
*.jpg binary
*.ico binary

View file

@ -16,19 +16,19 @@ env:
RUSTUP_MAX_RETRIES: 10 RUSTUP_MAX_RETRIES: 10
jobs: jobs:
rust-audit: # rust-audit:
name: Audit Rust vulnerabilities # name: Audit Rust vulnerabilities
runs-on: ubuntu-latest # runs-on: ubuntu-latest
steps: # steps:
- name: Checkout repository # - name: Checkout repository
uses: actions/checkout@v2 # uses: actions/checkout@v2
- uses: actions-rs/install@v0.1 # - uses: actions-rs/install@v0.1
with: # with:
crate: cargo-audit # crate: cargo-audit
use-tool-cache: true # use-tool-cache: true
- run: cargo audit # - run: cargo audit
rust: rust:
name: Rust name: Rust
@ -61,29 +61,22 @@ jobs:
override: true override: true
components: rustfmt, rust-src components: rustfmt, rust-src
- if: matrix.os == 'ubuntu-latest' - name: Cache cargo directories
run: sudo chown -R $(whoami):$(id -ng) ~/.cargo/ uses: actions/cache@v2
- name: Cache cargo registry
uses: actions/cache@v1
with: with:
path: ~/.cargo/registry path: |
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} ~/.cargo/registry
~/.cargo/git
- name: Cache cargo index key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
uses: actions/cache@v1
with:
path: ~/.cargo/git
key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo target dir - name: Cache cargo target dir
uses: actions/cache@v1 uses: actions/cache@v2
with: with:
path: target path: target
key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
- name: Compile - name: Compile
run: cargo test --no-run run: cargo test --no-run --locked
- name: Test - name: Test
run: cargo test run: cargo test
@ -95,6 +88,34 @@ jobs:
if: matrix.os == 'windows-latest' if: matrix.os == 'windows-latest'
run: Remove-Item ./target/debug/xtask.exe, ./target/debug/deps/xtask.exe run: Remove-Item ./target/debug/xtask.exe, ./target/debug/deps/xtask.exe
# Weird target to catch non-portable code
rust-power:
name: Rust Power
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
target: 'powerpc-unknown-linux-gnu'
- name: Cache cargo directories
uses: actions/cache@v2
with:
path: |
~/.cargo/registry
~/.cargo/git
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Check
run: cargo check --target=powerpc-unknown-linux-gnu --all-targets
typescript: typescript:
name: TypeScript name: TypeScript
strategy: strategy:
@ -116,9 +137,9 @@ jobs:
- run: npm ci - run: npm ci
working-directory: ./editors/code working-directory: ./editors/code
- run: npm audit || { sleep 10 && npm audit; } || { sleep 30 && npm audit; } # - run: npm audit || { sleep 10 && npm audit; } || { sleep 30 && npm audit; }
if: runner.os == 'Linux' # if: runner.os == 'Linux'
working-directory: ./editors/code # working-directory: ./editors/code
- run: npm run lint - run: npm run lint
working-directory: ./editors/code working-directory: ./editors/code

32
.github/workflows/metrics.yaml vendored Normal file
View file

@ -0,0 +1,32 @@
name: metrics
on:
push:
branches:
- master
env:
CARGO_INCREMENTAL: 0
CARGO_NET_RETRY: 10
RUSTFLAGS: -D warnings
RUSTUP_MAX_RETRIES: 10
jobs:
metrics:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
components: rust-src
- name: Collect metrics
run: cargo xtask metrics
env:
METRICS_TOKEN: ${{ secrets.METRICS_TOKEN }}

552
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -24,9 +24,16 @@ opt-level = 0
opt-level = 0 opt-level = 0
[profile.release.package.salsa-macros] [profile.release.package.salsa-macros]
opt-level = 0 opt-level = 0
[profile.release.package.tracing-attributes]
opt-level = 0
[profile.release.package.xtask] [profile.release.package.xtask]
opt-level = 0 opt-level = 0
# Gzipping the artifacts is up to 10 times faster with optimizations (`cargo xtask dist`).
# `miniz_oxide` is the direct dependency of `flate2` which does all the heavy lifting
[profile.dev.package.miniz_oxide]
opt-level = 3
[patch.'crates-io'] [patch.'crates-io']
# rowan = { path = "../rowan" } # rowan = { path = "../rowan" }

View file

@ -2,11 +2,8 @@
<img src="https://user-images.githubusercontent.com/1711539/72443316-5a79f280-37ae-11ea-858f-035209ece2dd.png" alt="rust-analyzer logo"> <img src="https://user-images.githubusercontent.com/1711539/72443316-5a79f280-37ae-11ea-858f-035209ece2dd.png" alt="rust-analyzer logo">
</p> </p>
rust-analyzer is an **experimental** modular compiler frontend for the Rust rust-analyzer is an **experimental** modular compiler frontend for the Rust language.
language. It is a part of a larger rls-2.0 effort to create excellent IDE It is a part of a larger rls-2.0 effort to create excellent IDE support for Rust.
support for Rust. If you want to get involved, check the rls-2.0 working group:
https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Fwg-rls-2.2E0
Work on rust-analyzer is sponsored by Work on rust-analyzer is sponsored by
@ -25,8 +22,8 @@ If you want to **contribute** to rust-analyzer or are just curious about how
things work under the hood, check the [./docs/dev](./docs/dev) folder. things work under the hood, check the [./docs/dev](./docs/dev) folder.
If you want to **use** rust-analyzer's language server with your editor of If you want to **use** rust-analyzer's language server with your editor of
choice, check [the manual](https://rust-analyzer.github.io/manual.html) folder. It also contains some tips & tricks to help choice, check [the manual](https://rust-analyzer.github.io/manual.html) folder.
you be more productive when using rust-analyzer. It also contains some tips & tricks to help you be more productive when using rust-analyzer.
## Communication ## Communication
@ -40,8 +37,9 @@ https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Frls-2.2E0
## Quick Links ## Quick Links
* API docs: https://rust-analyzer.github.io/rust-analyzer/ra_ide/
* Website: https://rust-analyzer.github.io/ * Website: https://rust-analyzer.github.io/
* Metrics: https://rust-analyzer.github.io/metrics/
* API docs: https://rust-analyzer.github.io/rust-analyzer/ra_ide/
## License ## License

14
crates/expect/Cargo.toml Normal file
View file

@ -0,0 +1,14 @@
[package]
name = "expect"
version = "0.1.0"
authors = ["rust-analyzer developers"]
edition = "2018"
license = "MIT OR Apache-2.0"
[lib]
doctest = false
[dependencies]
once_cell = "1"
difference = "2"
stdx = { path = "../stdx" }

356
crates/expect/src/lib.rs Normal file
View file

@ -0,0 +1,356 @@
//! Snapshot testing library, see
//! https://github.com/rust-analyzer/rust-analyzer/pull/5101
use std::{
collections::HashMap,
env, fmt, fs, mem,
ops::Range,
panic,
path::{Path, PathBuf},
sync::Mutex,
};
use difference::Changeset;
use once_cell::sync::Lazy;
use stdx::{lines_with_ends, trim_indent};
const HELP: &str = "
You can update all `expect![[]]` tests by running:
env UPDATE_EXPECT=1 cargo test
To update a single test, place the cursor on `expect` token and use `run` feature of rust-analyzer.
";
fn update_expect() -> bool {
env::var("UPDATE_EXPECT").is_ok()
}
/// expect![[r#"inline snapshot"#]]
#[macro_export]
macro_rules! expect {
[[$data:literal]] => {$crate::Expect {
position: $crate::Position {
file: file!(),
line: line!(),
column: column!(),
},
data: $data,
}};
[[]] => { $crate::expect![[""]] };
}
/// expect_file!["/crates/foo/test_data/bar.html"]
#[macro_export]
macro_rules! expect_file {
[$path:expr] => {$crate::ExpectFile {
path: std::path::PathBuf::from($path)
}};
}
#[derive(Debug)]
pub struct Expect {
pub position: Position,
pub data: &'static str,
}
#[derive(Debug)]
pub struct ExpectFile {
pub path: PathBuf,
}
#[derive(Debug)]
pub struct Position {
pub file: &'static str,
pub line: u32,
pub column: u32,
}
impl fmt::Display for Position {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}:{}", self.file, self.line, self.column)
}
}
impl Expect {
pub fn assert_eq(&self, actual: &str) {
let trimmed = self.trimmed();
if &trimmed == actual {
return;
}
Runtime::fail_expect(self, &trimmed, actual);
}
pub fn assert_debug_eq(&self, actual: &impl fmt::Debug) {
let actual = format!("{:#?}\n", actual);
self.assert_eq(&actual)
}
fn trimmed(&self) -> String {
if !self.data.contains('\n') {
return self.data.to_string();
}
trim_indent(self.data)
}
fn locate(&self, file: &str) -> Location {
let mut target_line = None;
let mut line_start = 0;
for (i, line) in lines_with_ends(file).enumerate() {
if i == self.position.line as usize - 1 {
let pat = "expect![[";
let offset = line.find(pat).unwrap();
let literal_start = line_start + offset + pat.len();
let indent = line.chars().take_while(|&it| it == ' ').count();
target_line = Some((literal_start, indent));
break;
}
line_start += line.len();
}
let (literal_start, line_indent) = target_line.unwrap();
let literal_length =
file[literal_start..].find("]]").expect("Couldn't find matching `]]` for `expect![[`.");
let literal_range = literal_start..literal_start + literal_length;
Location { line_indent, literal_range }
}
}
impl ExpectFile {
pub fn assert_eq(&self, actual: &str) {
let expected = self.read();
if actual == expected {
return;
}
Runtime::fail_file(self, &expected, actual);
}
pub fn assert_debug_eq(&self, actual: &impl fmt::Debug) {
let actual = format!("{:#?}\n", actual);
self.assert_eq(&actual)
}
fn read(&self) -> String {
fs::read_to_string(self.abs_path()).unwrap_or_default().replace("\r\n", "\n")
}
fn write(&self, contents: &str) {
fs::write(self.abs_path(), contents).unwrap()
}
fn abs_path(&self) -> PathBuf {
WORKSPACE_ROOT.join(&self.path)
}
}
#[derive(Default)]
struct Runtime {
help_printed: bool,
per_file: HashMap<&'static str, FileRuntime>,
}
static RT: Lazy<Mutex<Runtime>> = Lazy::new(Default::default);
impl Runtime {
fn fail_expect(expect: &Expect, expected: &str, actual: &str) {
let mut rt = RT.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
if update_expect() {
println!("\x1b[1m\x1b[92mupdating\x1b[0m: {}", expect.position);
rt.per_file
.entry(expect.position.file)
.or_insert_with(|| FileRuntime::new(expect))
.update(expect, actual);
return;
}
rt.panic(expect.position.to_string(), expected, actual);
}
fn fail_file(expect: &ExpectFile, expected: &str, actual: &str) {
let mut rt = RT.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
if update_expect() {
println!("\x1b[1m\x1b[92mupdating\x1b[0m: {}", expect.path.display());
expect.write(actual);
return;
}
rt.panic(expect.path.display().to_string(), expected, actual);
}
fn panic(&mut self, position: String, expected: &str, actual: &str) {
let print_help = !mem::replace(&mut self.help_printed, true);
let help = if print_help { HELP } else { "" };
let diff = Changeset::new(actual, expected, "\n");
println!(
"\n
\x1b[1m\x1b[91merror\x1b[97m: expect test failed\x1b[0m
\x1b[1m\x1b[34m-->\x1b[0m {}
{}
\x1b[1mExpect\x1b[0m:
----
{}
----
\x1b[1mActual\x1b[0m:
----
{}
----
\x1b[1mDiff\x1b[0m:
----
{}
----
",
position, help, expected, actual, diff
);
// Use resume_unwind instead of panic!() to prevent a backtrace, which is unnecessary noise.
panic::resume_unwind(Box::new(()));
}
}
struct FileRuntime {
path: PathBuf,
original_text: String,
patchwork: Patchwork,
}
impl FileRuntime {
fn new(expect: &Expect) -> FileRuntime {
let path = WORKSPACE_ROOT.join(expect.position.file);
let original_text = fs::read_to_string(&path).unwrap();
let patchwork = Patchwork::new(original_text.clone());
FileRuntime { path, original_text, patchwork }
}
fn update(&mut self, expect: &Expect, actual: &str) {
let loc = expect.locate(&self.original_text);
let patch = format_patch(loc.line_indent.clone(), actual);
self.patchwork.patch(loc.literal_range, &patch);
fs::write(&self.path, &self.patchwork.text).unwrap()
}
}
#[derive(Debug)]
struct Location {
line_indent: usize,
literal_range: Range<usize>,
}
#[derive(Debug)]
struct Patchwork {
text: String,
indels: Vec<(Range<usize>, usize)>,
}
impl Patchwork {
fn new(text: String) -> Patchwork {
Patchwork { text, indels: Vec::new() }
}
fn patch(&mut self, mut range: Range<usize>, patch: &str) {
self.indels.push((range.clone(), patch.len()));
self.indels.sort_by_key(|(delete, _insert)| delete.start);
let (delete, insert) = self
.indels
.iter()
.take_while(|(delete, _)| delete.start < range.start)
.map(|(delete, insert)| (delete.end - delete.start, insert))
.fold((0usize, 0usize), |(x1, y1), (x2, y2)| (x1 + x2, y1 + y2));
for pos in &mut [&mut range.start, &mut range.end] {
**pos -= delete;
**pos += insert;
}
self.text.replace_range(range, &patch);
}
}
fn format_patch(line_indent: usize, patch: &str) -> String {
let mut max_hashes = 0;
let mut cur_hashes = 0;
for byte in patch.bytes() {
if byte != b'#' {
cur_hashes = 0;
continue;
}
cur_hashes += 1;
max_hashes = max_hashes.max(cur_hashes);
}
let hashes = &"#".repeat(max_hashes + 1);
let indent = &" ".repeat(line_indent);
let is_multiline = patch.contains('\n');
let mut buf = String::new();
buf.push('r');
buf.push_str(hashes);
buf.push('"');
if is_multiline {
buf.push('\n');
}
let mut final_newline = false;
for line in lines_with_ends(patch) {
if is_multiline && !line.trim().is_empty() {
buf.push_str(indent);
buf.push_str(" ");
}
buf.push_str(line);
final_newline = line.ends_with('\n');
}
if final_newline {
buf.push_str(indent);
}
buf.push('"');
buf.push_str(hashes);
buf
}
static WORKSPACE_ROOT: Lazy<PathBuf> = Lazy::new(|| {
let my_manifest =
env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| env!("CARGO_MANIFEST_DIR").to_owned());
// Heuristic, see https://github.com/rust-lang/cargo/issues/3946
Path::new(&my_manifest)
.ancestors()
.filter(|it| it.join("Cargo.toml").exists())
.last()
.unwrap()
.to_path_buf()
});
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_format_patch() {
let patch = format_patch(0, "hello\nworld\n");
expect![[r##"
r#"
hello
world
"#"##]]
.assert_eq(&patch);
let patch = format_patch(4, "single line");
expect![[r##"r#"single line"#"##]].assert_eq(&patch);
}
#[test]
fn test_patchwork() {
let mut patchwork = Patchwork::new("one two three".to_string());
patchwork.patch(4..7, "zwei");
patchwork.patch(0..3, "один");
patchwork.patch(8..13, "3");
expect![[r#"
Patchwork {
text: "один zwei 3",
indels: [
(
0..3,
8,
),
(
4..7,
4,
),
(
8..13,
1,
),
],
}
"#]]
.assert_debug_eq(&patchwork);
}
}

View file

@ -3,6 +3,7 @@ edition = "2018"
name = "flycheck" name = "flycheck"
version = "0.1.0" version = "0.1.0"
authors = ["rust-analyzer developers"] authors = ["rust-analyzer developers"]
license = "MIT OR Apache-2.0"
[lib] [lib]
doctest = false doctest = false
@ -10,7 +11,7 @@ doctest = false
[dependencies] [dependencies]
crossbeam-channel = "0.4.0" crossbeam-channel = "0.4.0"
log = "0.4.8" log = "0.4.8"
cargo_metadata = "0.10.0" cargo_metadata = "0.11.1"
serde_json = "1.0.48" serde_json = "1.0.48"
jod-thread = "0.1.1" jod-thread = "0.1.1"
ra_toolchain = { path = "../ra_toolchain" } ra_toolchain = { path = "../ra_toolchain" }

View file

@ -14,14 +14,17 @@ use std::{
use crossbeam_channel::{never, select, unbounded, Receiver, Sender}; use crossbeam_channel::{never, select, unbounded, Receiver, Sender};
pub use cargo_metadata::diagnostic::{ pub use cargo_metadata::diagnostic::{
Applicability, Diagnostic, DiagnosticLevel, DiagnosticSpan, DiagnosticSpanMacroExpansion, Applicability, Diagnostic, DiagnosticCode, DiagnosticLevel, DiagnosticSpan,
DiagnosticSpanMacroExpansion,
}; };
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub enum FlycheckConfig { pub enum FlycheckConfig {
CargoCommand { CargoCommand {
command: String, command: String,
target_triple: Option<String>,
all_targets: bool, all_targets: bool,
no_default_features: bool,
all_features: bool, all_features: bool,
features: Vec<String>, features: Vec<String>,
extra_args: Vec<String>, extra_args: Vec<String>,
@ -132,6 +135,7 @@ impl FlycheckActor {
self.cancel_check_process(); self.cancel_check_process();
let mut command = self.check_command(); let mut command = self.check_command();
log::info!("restart flycheck {:?}", command);
command.stdout(Stdio::piped()).stderr(Stdio::null()).stdin(Stdio::null()); command.stdout(Stdio::piped()).stderr(Stdio::null()).stdin(Stdio::null());
if let Ok(child) = command.spawn().map(JodChild) { if let Ok(child) = command.spawn().map(JodChild) {
self.cargo_handle = Some(CargoHandle::spawn(child)); self.cargo_handle = Some(CargoHandle::spawn(child));
@ -176,6 +180,8 @@ impl FlycheckActor {
let mut cmd = match &self.config { let mut cmd = match &self.config {
FlycheckConfig::CargoCommand { FlycheckConfig::CargoCommand {
command, command,
target_triple,
no_default_features,
all_targets, all_targets,
all_features, all_features,
extra_args, extra_args,
@ -185,15 +191,24 @@ impl FlycheckActor {
cmd.arg(command); cmd.arg(command);
cmd.args(&["--workspace", "--message-format=json", "--manifest-path"]) cmd.args(&["--workspace", "--message-format=json", "--manifest-path"])
.arg(self.workspace_root.join("Cargo.toml")); .arg(self.workspace_root.join("Cargo.toml"));
if let Some(target) = target_triple {
cmd.args(&["--target", target.as_str()]);
}
if *all_targets { if *all_targets {
cmd.arg("--all-targets"); cmd.arg("--all-targets");
} }
if *all_features { if *all_features {
cmd.arg("--all-features"); cmd.arg("--all-features");
} else if !features.is_empty() { } else {
if *no_default_features {
cmd.arg("--no-default-features");
}
if !features.is_empty() {
cmd.arg("--features"); cmd.arg("--features");
cmd.arg(features.join(" ")); cmd.arg(features.join(" "));
} }
}
cmd.args(extra_args); cmd.args(extra_args);
cmd cmd
} }

View file

@ -3,6 +3,7 @@ name = "paths"
version = "0.1.0" version = "0.1.0"
authors = ["rust-analyzer developers"] authors = ["rust-analyzer developers"]
edition = "2018" edition = "2018"
license = "MIT OR Apache-2.0"
[lib] [lib]
doctest = false doctest = false

View file

@ -3,6 +3,7 @@ edition = "2018"
name = "ra_arena" name = "ra_arena"
version = "0.1.0" version = "0.1.0"
authors = ["rust-analyzer developers"] authors = ["rust-analyzer developers"]
license = "MIT OR Apache-2.0"
[lib] [lib]
doctest = false doctest = false

View file

@ -3,6 +3,7 @@ edition = "2018"
name = "ra_assists" name = "ra_assists"
version = "0.1.0" version = "0.1.0"
authors = ["rust-analyzer developers"] authors = ["rust-analyzer developers"]
license = "MIT OR Apache-2.0"
[lib] [lib]
doctest = false doctest = false

View file

@ -4,9 +4,12 @@
//! module, and we use to statically check that we only produce snippet //! module, and we use to statically check that we only produce snippet
//! assists if we are allowed to. //! assists if we are allowed to.
use crate::AssistKind;
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct AssistConfig { pub struct AssistConfig {
pub snippet_cap: Option<SnippetCap>, pub snippet_cap: Option<SnippetCap>,
pub allowed: Option<Vec<AssistKind>>,
} }
impl AssistConfig { impl AssistConfig {
@ -22,6 +25,6 @@ pub struct SnippetCap {
impl Default for AssistConfig { impl Default for AssistConfig {
fn default() -> Self { fn default() -> Self {
AssistConfig { snippet_cap: Some(SnippetCap { _private: () }) } AssistConfig { snippet_cap: Some(SnippetCap { _private: () }), allowed: None }
} }
} }

View file

@ -19,7 +19,7 @@ use ra_text_edit::TextEditBuilder;
use crate::{ use crate::{
assist_config::{AssistConfig, SnippetCap}, assist_config::{AssistConfig, SnippetCap},
Assist, AssistId, GroupLabel, ResolvedAssist, Assist, AssistId, AssistKind, GroupLabel, ResolvedAssist,
}; };
/// `AssistContext` allows to apply an assist or check if it could be applied. /// `AssistContext` allows to apply an assist or check if it could be applied.
@ -55,7 +55,6 @@ use crate::{
pub(crate) struct AssistContext<'a> { pub(crate) struct AssistContext<'a> {
pub(crate) config: &'a AssistConfig, pub(crate) config: &'a AssistConfig,
pub(crate) sema: Semantics<'a, RootDatabase>, pub(crate) sema: Semantics<'a, RootDatabase>,
pub(crate) db: &'a RootDatabase,
pub(crate) frange: FileRange, pub(crate) frange: FileRange,
source_file: SourceFile, source_file: SourceFile,
} }
@ -67,8 +66,11 @@ impl<'a> AssistContext<'a> {
frange: FileRange, frange: FileRange,
) -> AssistContext<'a> { ) -> AssistContext<'a> {
let source_file = sema.parse(frange.file_id); let source_file = sema.parse(frange.file_id);
let db = sema.db; AssistContext { config, sema, frange, source_file }
AssistContext { config, sema, db, frange, source_file } }
pub(crate) fn db(&self) -> &RootDatabase {
self.sema.db
} }
// NB, this ignores active selection. // NB, this ignores active selection.
@ -101,14 +103,26 @@ pub(crate) struct Assists {
resolve: bool, resolve: bool,
file: FileId, file: FileId,
buf: Vec<(Assist, Option<SourceChange>)>, buf: Vec<(Assist, Option<SourceChange>)>,
allowed: Option<Vec<AssistKind>>,
} }
impl Assists { impl Assists {
pub(crate) fn new_resolved(ctx: &AssistContext) -> Assists { pub(crate) fn new_resolved(ctx: &AssistContext) -> Assists {
Assists { resolve: true, file: ctx.frange.file_id, buf: Vec::new() } Assists {
resolve: true,
file: ctx.frange.file_id,
buf: Vec::new(),
allowed: ctx.config.allowed.clone(),
} }
}
pub(crate) fn new_unresolved(ctx: &AssistContext) -> Assists { pub(crate) fn new_unresolved(ctx: &AssistContext) -> Assists {
Assists { resolve: false, file: ctx.frange.file_id, buf: Vec::new() } Assists {
resolve: false,
file: ctx.frange.file_id,
buf: Vec::new(),
allowed: ctx.config.allowed.clone(),
}
} }
pub(crate) fn finish_unresolved(self) -> Vec<Assist> { pub(crate) fn finish_unresolved(self) -> Vec<Assist> {
@ -137,9 +151,13 @@ impl Assists {
target: TextRange, target: TextRange,
f: impl FnOnce(&mut AssistBuilder), f: impl FnOnce(&mut AssistBuilder),
) -> Option<()> { ) -> Option<()> {
if !self.is_allowed(&id) {
return None;
}
let label = Assist::new(id, label.into(), None, target); let label = Assist::new(id, label.into(), None, target);
self.add_impl(label, f) self.add_impl(label, f)
} }
pub(crate) fn add_group( pub(crate) fn add_group(
&mut self, &mut self,
group: &GroupLabel, group: &GroupLabel,
@ -148,9 +166,14 @@ impl Assists {
target: TextRange, target: TextRange,
f: impl FnOnce(&mut AssistBuilder), f: impl FnOnce(&mut AssistBuilder),
) -> Option<()> { ) -> Option<()> {
if !self.is_allowed(&id) {
return None;
}
let label = Assist::new(id, label.into(), Some(group.clone()), target); let label = Assist::new(id, label.into(), Some(group.clone()), target);
self.add_impl(label, f) self.add_impl(label, f)
} }
fn add_impl(&mut self, label: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> { fn add_impl(&mut self, label: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> {
let source_change = if self.resolve { let source_change = if self.resolve {
let mut builder = AssistBuilder::new(self.file); let mut builder = AssistBuilder::new(self.file);
@ -168,13 +191,20 @@ impl Assists {
self.buf.sort_by_key(|(label, _edit)| label.target.len()); self.buf.sort_by_key(|(label, _edit)| label.target.len());
self.buf self.buf
} }
fn is_allowed(&self, id: &AssistId) -> bool {
match &self.allowed {
Some(allowed) => allowed.iter().any(|kind| kind.contains(id.1)),
None => true,
}
}
} }
pub(crate) struct AssistBuilder { pub(crate) struct AssistBuilder {
edit: TextEditBuilder, edit: TextEditBuilder,
file_id: FileId, file_id: FileId,
is_snippet: bool, is_snippet: bool,
edits: Vec<SourceFileEdit>, change: SourceChange,
} }
impl AssistBuilder { impl AssistBuilder {
@ -183,7 +213,7 @@ impl AssistBuilder {
edit: TextEditBuilder::default(), edit: TextEditBuilder::default(),
file_id, file_id,
is_snippet: false, is_snippet: false,
edits: Vec::new(), change: SourceChange::default(),
} }
} }
@ -195,8 +225,8 @@ impl AssistBuilder {
let edit = mem::take(&mut self.edit).finish(); let edit = mem::take(&mut self.edit).finish();
if !edit.is_empty() { if !edit.is_empty() {
let new_edit = SourceFileEdit { file_id: self.file_id, edit }; let new_edit = SourceFileEdit { file_id: self.file_id, edit };
assert!(!self.edits.iter().any(|it| it.file_id == new_edit.file_id)); assert!(!self.change.source_file_edits.iter().any(|it| it.file_id == new_edit.file_id));
self.edits.push(new_edit); self.change.source_file_edits.push(new_edit);
} }
} }
@ -263,10 +293,10 @@ impl AssistBuilder {
fn finish(mut self) -> SourceChange { fn finish(mut self) -> SourceChange {
self.commit(); self.commit();
let mut res: SourceChange = mem::take(&mut self.edits).into(); let mut change = mem::take(&mut self.change);
if self.is_snippet { if self.is_snippet {
res.is_snippet = true; change.is_snippet = true;
} }
res change
} }
} }

View file

@ -2,7 +2,6 @@
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use hir::{HirDisplay, PathResolution, SemanticsScope}; use hir::{HirDisplay, PathResolution, SemanticsScope};
use ra_ide_db::RootDatabase;
use ra_syntax::{ use ra_syntax::{
algo::SyntaxRewriter, algo::SyntaxRewriter,
ast::{self, AstNode}, ast::{self, AstNode},
@ -32,17 +31,17 @@ impl<'a> AstTransform<'a> for NullTransformer {
} }
pub struct SubstituteTypeParams<'a> { pub struct SubstituteTypeParams<'a> {
source_scope: &'a SemanticsScope<'a, RootDatabase>, source_scope: &'a SemanticsScope<'a>,
substs: FxHashMap<hir::TypeParam, ast::TypeRef>, substs: FxHashMap<hir::TypeParam, ast::TypeRef>,
previous: Box<dyn AstTransform<'a> + 'a>, previous: Box<dyn AstTransform<'a> + 'a>,
} }
impl<'a> SubstituteTypeParams<'a> { impl<'a> SubstituteTypeParams<'a> {
pub fn for_trait_impl( pub fn for_trait_impl(
source_scope: &'a SemanticsScope<'a, RootDatabase>, source_scope: &'a SemanticsScope<'a>,
// FIXME: there's implicit invariant that `trait_` and `source_scope` match... // FIXME: there's implicit invariant that `trait_` and `source_scope` match...
trait_: hir::Trait, trait_: hir::Trait,
impl_def: ast::ImplDef, impl_def: ast::Impl,
) -> SubstituteTypeParams<'a> { ) -> SubstituteTypeParams<'a> {
let substs = get_syntactic_substs(impl_def).unwrap_or_default(); let substs = get_syntactic_substs(impl_def).unwrap_or_default();
let generic_def: hir::GenericDef = trait_.into(); let generic_def: hir::GenericDef = trait_.into();
@ -81,7 +80,7 @@ impl<'a> SubstituteTypeParams<'a> {
// FIXME: It would probably be nicer if we could get this via HIR (i.e. get the // FIXME: It would probably be nicer if we could get this via HIR (i.e. get the
// trait ref, and then go from the types in the substs back to the syntax) // trait ref, and then go from the types in the substs back to the syntax)
fn get_syntactic_substs(impl_def: ast::ImplDef) -> Option<Vec<ast::TypeRef>> { fn get_syntactic_substs(impl_def: ast::Impl) -> Option<Vec<ast::TypeRef>> {
let target_trait = impl_def.target_trait()?; let target_trait = impl_def.target_trait()?;
let path_type = match target_trait { let path_type = match target_trait {
ast::TypeRef::PathType(path) => path, ast::TypeRef::PathType(path) => path,
@ -126,16 +125,13 @@ impl<'a> AstTransform<'a> for SubstituteTypeParams<'a> {
} }
pub struct QualifyPaths<'a> { pub struct QualifyPaths<'a> {
target_scope: &'a SemanticsScope<'a, RootDatabase>, target_scope: &'a SemanticsScope<'a>,
source_scope: &'a SemanticsScope<'a, RootDatabase>, source_scope: &'a SemanticsScope<'a>,
previous: Box<dyn AstTransform<'a> + 'a>, previous: Box<dyn AstTransform<'a> + 'a>,
} }
impl<'a> QualifyPaths<'a> { impl<'a> QualifyPaths<'a> {
pub fn new( pub fn new(target_scope: &'a SemanticsScope<'a>, source_scope: &'a SemanticsScope<'a>) -> Self {
target_scope: &'a SemanticsScope<'a, RootDatabase>,
source_scope: &'a SemanticsScope<'a, RootDatabase>,
) -> Self {
Self { target_scope, source_scope, previous: Box::new(NullTransformer) } Self { target_scope, source_scope, previous: Box::new(NullTransformer) }
} }
@ -156,7 +152,7 @@ impl<'a> QualifyPaths<'a> {
let resolution = self.source_scope.resolve_hir_path(&hir_path?)?; let resolution = self.source_scope.resolve_hir_path(&hir_path?)?;
match resolution { match resolution {
PathResolution::Def(def) => { PathResolution::Def(def) => {
let found_path = from.find_use_path(self.source_scope.db, def)?; let found_path = from.find_use_path(self.source_scope.db.upcast(), def)?;
let mut path = path_to_ast(found_path); let mut path = path_to_ast(found_path);
let type_args = p let type_args = p

View file

@ -8,7 +8,7 @@ use stdx::SepBy;
use crate::{ use crate::{
assist_context::{AssistContext, Assists}, assist_context::{AssistContext, Assists},
AssistId, AssistId, AssistKind,
}; };
// Assist: add_custom_impl // Assist: add_custom_impl
@ -29,8 +29,8 @@ use crate::{
// } // }
// ``` // ```
pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let input = ctx.find_node_at_offset::<ast::AttrInput>()?; let attr = ctx.find_node_at_offset::<ast::Attr>()?;
let attr = input.syntax().parent().and_then(ast::Attr::cast)?; let input = attr.token_tree()?;
let attr_name = attr let attr_name = attr
.syntax() .syntax()
@ -52,7 +52,7 @@ pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<
format!("Add custom impl `{}` for `{}`", trait_token.text().as_str(), annotated_name); format!("Add custom impl `{}` for `{}`", trait_token.text().as_str(), annotated_name);
let target = attr.syntax().text_range(); let target = attr.syntax().text_range();
acc.add(AssistId("add_custom_impl"), label, target, |builder| { acc.add(AssistId("add_custom_impl", AssistKind::Refactor), label, target, |builder| {
let new_attr_input = input let new_attr_input = input
.syntax() .syntax()
.descendants_with_tokens() .descendants_with_tokens()

View file

@ -1,10 +1,10 @@
use hir::HirDisplay; use hir::HirDisplay;
use ra_syntax::{ use ra_syntax::{
ast::{self, AstNode, LetStmt, NameOwner, TypeAscriptionOwner}, ast::{self, AstNode, LetStmt, NameOwner},
TextRange, TextRange,
}; };
use crate::{AssistContext, AssistId, Assists}; use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: add_explicit_type // Assist: add_explicit_type
// //
@ -22,11 +22,11 @@ use crate::{AssistContext, AssistId, Assists};
// } // }
// ``` // ```
pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let stmt = ctx.find_node_at_offset::<LetStmt>()?; let let_stmt = ctx.find_node_at_offset::<LetStmt>()?;
let module = ctx.sema.scope(stmt.syntax()).module()?; let module = ctx.sema.scope(let_stmt.syntax()).module()?;
let expr = stmt.initializer()?; let expr = let_stmt.initializer()?;
// Must be a binding // Must be a binding
let pat = match stmt.pat()? { let pat = match let_stmt.pat()? {
ast::Pat::BindPat(bind_pat) => bind_pat, ast::Pat::BindPat(bind_pat) => bind_pat,
_ => return None, _ => return None,
}; };
@ -34,8 +34,8 @@ pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext) -> Optio
// The binding must have a name // The binding must have a name
let name = pat.name()?; let name = pat.name()?;
let name_range = name.syntax().text_range(); let name_range = name.syntax().text_range();
let stmt_range = stmt.syntax().text_range(); let stmt_range = let_stmt.syntax().text_range();
let eq_range = stmt.eq_token()?.text_range(); let eq_range = let_stmt.eq_token()?.text_range();
// Assist should only be applicable if cursor is between 'let' and '=' // Assist should only be applicable if cursor is between 'let' and '='
let let_range = TextRange::new(stmt_range.start(), eq_range.start()); let let_range = TextRange::new(stmt_range.start(), eq_range.start());
let cursor_in_range = let_range.contains_range(ctx.frange.range); let cursor_in_range = let_range.contains_range(ctx.frange.range);
@ -44,7 +44,7 @@ pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext) -> Optio
} }
// Assist not applicable if the type has already been specified // Assist not applicable if the type has already been specified
// and it has no placeholders // and it has no placeholders
let ascribed_ty = stmt.ascribed_type(); let ascribed_ty = let_stmt.ty();
if let Some(ty) = &ascribed_ty { if let Some(ty) = &ascribed_ty {
if ty.syntax().descendants().find_map(ast::PlaceholderType::cast).is_none() { if ty.syntax().descendants().find_map(ast::PlaceholderType::cast).is_none() {
return None; return None;
@ -57,9 +57,9 @@ pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext) -> Optio
return None; return None;
} }
let inferred_type = ty.display_source_code(ctx.db, module.into()).ok()?; let inferred_type = ty.display_source_code(ctx.db(), module.into()).ok()?;
acc.add( acc.add(
AssistId("add_explicit_type"), AssistId("add_explicit_type", AssistKind::RefactorRewrite),
format!("Insert explicit type `{}`", inferred_type), format!("Insert explicit type `{}`", inferred_type),
pat_range, pat_range,
|builder| match ascribed_ty { |builder| match ascribed_ty {

View file

@ -1,98 +0,0 @@
use ra_syntax::ast::{self, AstNode, NameOwner, TypeParamsOwner};
use stdx::{format_to, SepBy};
use crate::{AssistContext, AssistId, Assists};
// Assist: add_impl
//
// Adds a new inherent impl for a type.
//
// ```
// struct Ctx<T: Clone> {
// data: T,<|>
// }
// ```
// ->
// ```
// struct Ctx<T: Clone> {
// data: T,
// }
//
// impl<T: Clone> Ctx<T> {
// $0
// }
// ```
pub(crate) fn add_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?;
let name = nominal.name()?;
let target = nominal.syntax().text_range();
acc.add(AssistId("add_impl"), format!("Implement {}", name.text().as_str()), target, |edit| {
let type_params = nominal.type_param_list();
let start_offset = nominal.syntax().text_range().end();
let mut buf = String::new();
buf.push_str("\n\nimpl");
if let Some(type_params) = &type_params {
format_to!(buf, "{}", type_params.syntax());
}
buf.push_str(" ");
buf.push_str(name.text().as_str());
if let Some(type_params) = type_params {
let lifetime_params = type_params
.lifetime_params()
.filter_map(|it| it.lifetime_token())
.map(|it| it.text().clone());
let type_params =
type_params.type_params().filter_map(|it| it.name()).map(|it| it.text().clone());
let generic_params = lifetime_params.chain(type_params).sep_by(", ");
format_to!(buf, "<{}>", generic_params)
}
match ctx.config.snippet_cap {
Some(cap) => {
buf.push_str(" {\n $0\n}");
edit.insert_snippet(cap, start_offset, buf);
}
None => {
buf.push_str(" {\n}");
edit.insert(start_offset, buf);
}
}
})
}
#[cfg(test)]
mod tests {
use crate::tests::{check_assist, check_assist_target};
use super::*;
#[test]
fn test_add_impl() {
check_assist(add_impl, "struct Foo {<|>}\n", "struct Foo {}\n\nimpl Foo {\n $0\n}\n");
check_assist(
add_impl,
"struct Foo<T: Clone> {<|>}",
"struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n $0\n}",
);
check_assist(
add_impl,
"struct Foo<'a, T: Foo<'a>> {<|>}",
"struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n $0\n}",
);
}
#[test]
fn add_impl_target() {
check_assist_target(
add_impl,
"
struct SomeThingIrrelevant;
/// Has a lifetime parameter
struct Foo<'a, T: Foo<'a>> {<|>}
struct EvenMoreIrrelevant;
",
"/// Has a lifetime parameter
struct Foo<'a, T: Foo<'a>> {}",
);
}
}

View file

@ -12,7 +12,7 @@ use crate::{
assist_context::{AssistContext, Assists}, assist_context::{AssistContext, Assists},
ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams},
utils::{get_missing_assoc_items, render_snippet, resolve_target_trait, Cursor}, utils::{get_missing_assoc_items, render_snippet, resolve_target_trait, Cursor},
AssistId, AssistId, AssistKind,
}; };
#[derive(PartialEq)] #[derive(PartialEq)]
@ -111,16 +111,17 @@ fn add_missing_impl_members_inner(
label: &'static str, label: &'static str,
) -> Option<()> { ) -> Option<()> {
let _p = ra_prof::profile("add_missing_impl_members_inner"); let _p = ra_prof::profile("add_missing_impl_members_inner");
let impl_def = ctx.find_node_at_offset::<ast::ImplDef>()?; let impl_def = ctx.find_node_at_offset::<ast::Impl>()?;
let impl_item_list = impl_def.item_list()?; let impl_item_list = impl_def.assoc_item_list()?;
let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?; let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?;
let def_name = |item: &ast::AssocItem| -> Option<SmolStr> { let def_name = |item: &ast::AssocItem| -> Option<SmolStr> {
match item { match item {
ast::AssocItem::FnDef(def) => def.name(), ast::AssocItem::Fn(def) => def.name(),
ast::AssocItem::TypeAliasDef(def) => def.name(), ast::AssocItem::TypeAlias(def) => def.name(),
ast::AssocItem::ConstDef(def) => def.name(), ast::AssocItem::Const(def) => def.name(),
ast::AssocItem::MacroCall(_) => None,
} }
.map(|it| it.text().clone()) .map(|it| it.text().clone())
}; };
@ -128,13 +129,13 @@ fn add_missing_impl_members_inner(
let missing_items = get_missing_assoc_items(&ctx.sema, &impl_def) let missing_items = get_missing_assoc_items(&ctx.sema, &impl_def)
.iter() .iter()
.map(|i| match i { .map(|i| match i {
hir::AssocItem::Function(i) => ast::AssocItem::FnDef(i.source(ctx.db).value), hir::AssocItem::Function(i) => ast::AssocItem::Fn(i.source(ctx.db()).value),
hir::AssocItem::TypeAlias(i) => ast::AssocItem::TypeAliasDef(i.source(ctx.db).value), hir::AssocItem::TypeAlias(i) => ast::AssocItem::TypeAlias(i.source(ctx.db()).value),
hir::AssocItem::Const(i) => ast::AssocItem::ConstDef(i.source(ctx.db).value), hir::AssocItem::Const(i) => ast::AssocItem::Const(i.source(ctx.db()).value),
}) })
.filter(|t| def_name(&t).is_some()) .filter(|t| def_name(&t).is_some())
.filter(|t| match t { .filter(|t| match t {
ast::AssocItem::FnDef(def) => match mode { ast::AssocItem::Fn(def) => match mode {
AddMissingImplMembersMode::DefaultMethodsOnly => def.body().is_some(), AddMissingImplMembersMode::DefaultMethodsOnly => def.body().is_some(),
AddMissingImplMembersMode::NoDefaultMethods => def.body().is_none(), AddMissingImplMembersMode::NoDefaultMethods => def.body().is_none(),
}, },
@ -147,7 +148,7 @@ fn add_missing_impl_members_inner(
} }
let target = impl_def.syntax().text_range(); let target = impl_def.syntax().text_range();
acc.add(AssistId(assist_id), label, target, |builder| { acc.add(AssistId(assist_id, AssistKind::QuickFix), label, target, |builder| {
let n_existing_items = impl_item_list.assoc_items().count(); let n_existing_items = impl_item_list.assoc_items().count();
let source_scope = ctx.sema.scope_for_def(trait_); let source_scope = ctx.sema.scope_for_def(trait_);
let target_scope = ctx.sema.scope(impl_item_list.syntax()); let target_scope = ctx.sema.scope(impl_item_list.syntax());
@ -157,7 +158,8 @@ fn add_missing_impl_members_inner(
.into_iter() .into_iter()
.map(|it| ast_transform::apply(&*ast_transform, it)) .map(|it| ast_transform::apply(&*ast_transform, it))
.map(|it| match it { .map(|it| match it {
ast::AssocItem::FnDef(def) => ast::AssocItem::FnDef(add_body(def)), ast::AssocItem::Fn(def) => ast::AssocItem::Fn(add_body(def)),
ast::AssocItem::TypeAlias(def) => ast::AssocItem::TypeAlias(def.remove_bounds()),
_ => it, _ => it,
}) })
.map(|it| edit::remove_attrs_and_docs(&it)); .map(|it| edit::remove_attrs_and_docs(&it));
@ -170,7 +172,7 @@ fn add_missing_impl_members_inner(
Some(cap) => { Some(cap) => {
let mut cursor = Cursor::Before(first_new_item.syntax()); let mut cursor = Cursor::Before(first_new_item.syntax());
let placeholder; let placeholder;
if let ast::AssocItem::FnDef(func) = &first_new_item { if let ast::AssocItem::Fn(func) = &first_new_item {
if let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast) { if let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast) {
if m.syntax().text() == "todo!()" { if m.syntax().text() == "todo!()" {
placeholder = m; placeholder = m;
@ -188,7 +190,7 @@ fn add_missing_impl_members_inner(
}) })
} }
fn add_body(fn_def: ast::FnDef) -> ast::FnDef { fn add_body(fn_def: ast::Fn) -> ast::Fn {
if fn_def.body().is_some() { if fn_def.body().is_some() {
return fn_def; return fn_def;
} }
@ -681,6 +683,28 @@ impl Foo<T> for S<T> {
fn bar(&self, this: &T, that: &Self) { fn bar(&self, this: &T, that: &Self) {
${0:todo!()} ${0:todo!()}
} }
}"#,
)
}
#[test]
fn test_assoc_type_bounds_are_removed() {
check_assist(
add_missing_impl_members,
r#"
trait Tr {
type Ty: Copy + 'static;
}
impl Tr for ()<|> {
}"#,
r#"
trait Tr {
type Ty: Copy + 'static;
}
impl Tr for () {
$0type Ty;
}"#, }"#,
) )
} }

View file

@ -4,7 +4,7 @@ use test_utils::mark;
use crate::{ use crate::{
assist_context::{AssistContext, Assists}, assist_context::{AssistContext, Assists},
AssistId, AssistId, AssistKind,
}; };
// Assist: add_turbo_fish // Assist: add_turbo_fish
@ -25,7 +25,14 @@ use crate::{
// } // }
// ``` // ```
pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let ident = ctx.find_token_at_offset(SyntaxKind::IDENT)?; let ident = ctx.find_token_at_offset(SyntaxKind::IDENT).or_else(|| {
let arg_list = ctx.find_node_at_offset::<ast::ArgList>()?;
if arg_list.args().count() > 0 {
return None;
}
mark::hit!(add_turbo_fish_after_call);
arg_list.l_paren_token()?.prev_token().filter(|it| it.kind() == SyntaxKind::IDENT)
})?;
let next_token = ident.next_token()?; let next_token = ident.next_token()?;
if next_token.kind() == T![::] { if next_token.kind() == T![::] {
mark::hit!(add_turbo_fish_one_fish_is_enough); mark::hit!(add_turbo_fish_one_fish_is_enough);
@ -45,12 +52,15 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext) -> Option<(
mark::hit!(add_turbo_fish_non_generic); mark::hit!(add_turbo_fish_non_generic);
return None; return None;
} }
acc.add(AssistId("add_turbo_fish"), "Add `::<>`", ident.text_range(), |builder| { acc.add(
match ctx.config.snippet_cap { AssistId("add_turbo_fish", AssistKind::RefactorRewrite),
"Add `::<>`",
ident.text_range(),
|builder| match ctx.config.snippet_cap {
Some(cap) => builder.insert_snippet(cap, ident.text_range().end(), "::<${0:_}>"), Some(cap) => builder.insert_snippet(cap, ident.text_range().end(), "::<${0:_}>"),
None => builder.insert(ident.text_range().end(), "::<_>"), None => builder.insert(ident.text_range().end(), "::<_>"),
} },
}) )
} }
#[cfg(test)] #[cfg(test)]
@ -79,6 +89,26 @@ fn main() {
); );
} }
#[test]
fn add_turbo_fish_after_call() {
mark::check!(add_turbo_fish_after_call);
check_assist(
add_turbo_fish,
r#"
fn make<T>() -> T {}
fn main() {
make()<|>;
}
"#,
r#"
fn make<T>() -> T {}
fn main() {
make::<${0:_}>();
}
"#,
);
}
#[test] #[test]
fn add_turbo_fish_method() { fn add_turbo_fish_method() {
check_assist( check_assist(

View file

@ -1,6 +1,6 @@
use ra_syntax::ast::{self, AstNode}; use ra_syntax::ast::{self, AstNode};
use crate::{utils::invert_boolean_expression, AssistContext, AssistId, Assists}; use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKind, Assists};
// Assist: apply_demorgan // Assist: apply_demorgan
// //
@ -39,11 +39,16 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<(
let rhs_range = rhs.syntax().text_range(); let rhs_range = rhs.syntax().text_range();
let not_rhs = invert_boolean_expression(rhs); let not_rhs = invert_boolean_expression(rhs);
acc.add(AssistId("apply_demorgan"), "Apply De Morgan's law", op_range, |edit| { acc.add(
AssistId("apply_demorgan", AssistKind::RefactorRewrite),
"Apply De Morgan's law",
op_range,
|edit| {
edit.replace(op_range, opposite_op); edit.replace(op_range, opposite_op);
edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text())); edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text()));
edit.replace(rhs_range, format!("{})", not_rhs.syntax().text())); edit.replace(rhs_range, format!("{})", not_rhs.syntax().text()));
}) },
)
} }
// Return the opposite text for a given logical operator, if it makes sense // Return the opposite text for a given logical operator, if it makes sense

View file

@ -5,7 +5,7 @@ use hir::{
AsAssocItem, AssocItemContainer, ModPath, Module, ModuleDef, PathResolution, Semantics, Trait, AsAssocItem, AssocItemContainer, ModPath, Module, ModuleDef, PathResolution, Semantics, Trait,
Type, Type,
}; };
use ra_ide_db::{imports_locator::ImportsLocator, RootDatabase}; use ra_ide_db::{imports_locator, RootDatabase};
use ra_prof::profile; use ra_prof::profile;
use ra_syntax::{ use ra_syntax::{
ast::{self, AstNode}, ast::{self, AstNode},
@ -13,7 +13,9 @@ use ra_syntax::{
}; };
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use crate::{utils::insert_use_statement, AssistContext, AssistId, Assists, GroupLabel}; use crate::{
utils::insert_use_statement, AssistContext, AssistId, AssistKind, Assists, GroupLabel,
};
// Assist: auto_import // Assist: auto_import
// //
@ -35,8 +37,8 @@ use crate::{utils::insert_use_statement, AssistContext, AssistId, Assists, Group
// # pub mod std { pub mod collections { pub struct HashMap { } } } // # pub mod std { pub mod collections { pub struct HashMap { } } }
// ``` // ```
pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let auto_import_assets = AutoImportAssets::new(&ctx)?; let auto_import_assets = AutoImportAssets::new(ctx)?;
let proposed_imports = auto_import_assets.search_for_imports(ctx.db); let proposed_imports = auto_import_assets.search_for_imports(ctx);
if proposed_imports.is_empty() { if proposed_imports.is_empty() {
return None; return None;
} }
@ -46,7 +48,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
for import in proposed_imports { for import in proposed_imports {
acc.add_group( acc.add_group(
&group, &group,
AssistId("auto_import"), AssistId("auto_import", AssistKind::QuickFix),
format!("Import `{}`", &import), format!("Import `{}`", &import),
range, range,
|builder| { |builder| {
@ -90,7 +92,7 @@ impl AutoImportAssets {
fn for_regular_path(path_under_caret: ast::Path, ctx: &AssistContext) -> Option<Self> { fn for_regular_path(path_under_caret: ast::Path, ctx: &AssistContext) -> Option<Self> {
let syntax_under_caret = path_under_caret.syntax().to_owned(); let syntax_under_caret = path_under_caret.syntax().to_owned();
if syntax_under_caret.ancestors().find_map(ast::UseItem::cast).is_some() { if syntax_under_caret.ancestors().find_map(ast::Use::cast).is_some() {
return None; return None;
} }
@ -127,11 +129,11 @@ impl AutoImportAssets {
GroupLabel(name) GroupLabel(name)
} }
fn search_for_imports(&self, db: &RootDatabase) -> BTreeSet<ModPath> { fn search_for_imports(&self, ctx: &AssistContext) -> BTreeSet<ModPath> {
let _p = profile("auto_import::search_for_imports"); let _p = profile("auto_import::search_for_imports");
let db = ctx.db();
let current_crate = self.module_with_name_to_import.krate(); let current_crate = self.module_with_name_to_import.krate();
ImportsLocator::new(db, current_crate) imports_locator::find_imports(&ctx.sema, current_crate, &self.get_search_query())
.find_imports(&self.get_search_query())
.into_iter() .into_iter()
.filter_map(|candidate| match &self.import_candidate { .filter_map(|candidate| match &self.import_candidate {
ImportCandidate::TraitAssocItem(assoc_item_type, _) => { ImportCandidate::TraitAssocItem(assoc_item_type, _) => {
@ -810,6 +812,146 @@ fn main() {
); );
} }
#[test]
fn trait_method_cross_crate() {
check_assist(
auto_import,
r"
//- /main.rs crate:main deps:dep
fn main() {
let test_struct = dep::test_mod::TestStruct {};
test_struct.test_meth<|>od()
}
//- /dep.rs crate:dep
pub mod test_mod {
pub trait TestTrait {
fn test_method(&self);
}
pub struct TestStruct {}
impl TestTrait for TestStruct {
fn test_method(&self) {}
}
}
",
r"
use dep::test_mod::TestTrait;
fn main() {
let test_struct = dep::test_mod::TestStruct {};
test_struct.test_method()
}
",
);
}
#[test]
fn assoc_fn_cross_crate() {
check_assist(
auto_import,
r"
//- /main.rs crate:main deps:dep
fn main() {
dep::test_mod::TestStruct::test_func<|>tion
}
//- /dep.rs crate:dep
pub mod test_mod {
pub trait TestTrait {
fn test_function();
}
pub struct TestStruct {}
impl TestTrait for TestStruct {
fn test_function() {}
}
}
",
r"
use dep::test_mod::TestTrait;
fn main() {
dep::test_mod::TestStruct::test_function
}
",
);
}
#[test]
fn assoc_const_cross_crate() {
check_assist(
auto_import,
r"
//- /main.rs crate:main deps:dep
fn main() {
dep::test_mod::TestStruct::CONST<|>
}
//- /dep.rs crate:dep
pub mod test_mod {
pub trait TestTrait {
const CONST: bool;
}
pub struct TestStruct {}
impl TestTrait for TestStruct {
const CONST: bool = true;
}
}
",
r"
use dep::test_mod::TestTrait;
fn main() {
dep::test_mod::TestStruct::CONST
}
",
);
}
#[test]
fn assoc_fn_as_method_cross_crate() {
check_assist_not_applicable(
auto_import,
r"
//- /main.rs crate:main deps:dep
fn main() {
let test_struct = dep::test_mod::TestStruct {};
test_struct.test_func<|>tion()
}
//- /dep.rs crate:dep
pub mod test_mod {
pub trait TestTrait {
fn test_function();
}
pub struct TestStruct {}
impl TestTrait for TestStruct {
fn test_function() {}
}
}
",
);
}
#[test]
fn private_trait_cross_crate() {
check_assist_not_applicable(
auto_import,
r"
//- /main.rs crate:main deps:dep
fn main() {
let test_struct = dep::test_mod::TestStruct {};
test_struct.test_meth<|>od()
}
//- /dep.rs crate:dep
pub mod test_mod {
trait TestTrait {
fn test_method(&self);
}
pub struct TestStruct {}
impl TestTrait for TestStruct {
fn test_method(&self) {}
}
}
",
);
}
#[test] #[test]
fn not_applicable_for_imported_trait_for_method() { fn not_applicable_for_imported_trait_for_method() {
check_assist_not_applicable( check_assist_not_applicable(

View file

@ -3,7 +3,7 @@ use ra_syntax::{
AstNode, SyntaxNode, AstNode, SyntaxNode,
}; };
use crate::{AssistContext, AssistId, Assists}; use crate::{AssistContext, AssistId, AssistKind, Assists};
use test_utils::mark; use test_utils::mark;
// Assist: change_return_type_to_result // Assist: change_return_type_to_result
@ -20,9 +20,9 @@ use test_utils::mark;
pub(crate) fn change_return_type_to_result(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { pub(crate) fn change_return_type_to_result(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let ret_type = ctx.find_node_at_offset::<ast::RetType>()?; let ret_type = ctx.find_node_at_offset::<ast::RetType>()?;
// FIXME: extend to lambdas as well // FIXME: extend to lambdas as well
let fn_def = ret_type.syntax().parent().and_then(ast::FnDef::cast)?; let fn_def = ret_type.syntax().parent().and_then(ast::Fn::cast)?;
let type_ref = &ret_type.type_ref()?; let type_ref = &ret_type.ty()?;
let ret_type_str = type_ref.syntax().text().to_string(); let ret_type_str = type_ref.syntax().text().to_string();
let first_part_ret_type = ret_type_str.splitn(2, '<').next(); let first_part_ret_type = ret_type_str.splitn(2, '<').next();
if let Some(ret_type_first_part) = first_part_ret_type { if let Some(ret_type_first_part) = first_part_ret_type {
@ -35,8 +35,8 @@ pub(crate) fn change_return_type_to_result(acc: &mut Assists, ctx: &AssistContex
let block_expr = &fn_def.body()?; let block_expr = &fn_def.body()?;
acc.add( acc.add(
AssistId("change_return_type_to_result"), AssistId("change_return_type_to_result", AssistKind::RefactorRewrite),
"Change return type to Result", "Wrap return type in Result",
type_ref.syntax().text_range(), type_ref.syntax().text_range(),
|builder| { |builder| {
let mut tail_return_expr_collector = TailReturnCollector::new(); let mut tail_return_expr_collector = TailReturnCollector::new();
@ -240,7 +240,7 @@ fn get_tail_expr_from_block(expr: &Expr) -> Option<Vec<NodeType>> {
Expr::ParenExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), Expr::ParenExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
Expr::PathExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), Expr::PathExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
Expr::Label(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), Expr::Label(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
Expr::RecordLit(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), Expr::RecordExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
Expr::IndexExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), Expr::IndexExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
Expr::MethodCallExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), Expr::MethodCallExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
Expr::AwaitExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), Expr::AwaitExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),

View file

@ -1,12 +1,12 @@
use ra_syntax::{ use ra_syntax::{
ast::{self, NameOwner, VisibilityOwner}, ast::{self, NameOwner, VisibilityOwner},
AstNode, AstNode,
SyntaxKind::{CONST_DEF, ENUM_DEF, FN_DEF, MODULE, STRUCT_DEF, TRAIT_DEF, VISIBILITY}, SyntaxKind::{CONST, ENUM, FN, MODULE, STATIC, STRUCT, TRAIT, VISIBILITY},
T, T,
}; };
use test_utils::mark; use test_utils::mark;
use crate::{utils::vis_offset, AssistContext, AssistId, Assists}; use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists};
// Assist: change_visibility // Assist: change_visibility
// //
@ -28,12 +28,15 @@ pub(crate) fn change_visibility(acc: &mut Assists, ctx: &AssistContext) -> Optio
fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let item_keyword = ctx.token_at_offset().find(|leaf| { let item_keyword = ctx.token_at_offset().find(|leaf| {
matches!(leaf.kind(), T![const] | T![fn] | T![mod] | T![struct] | T![enum] | T![trait]) matches!(
leaf.kind(),
T![const] | T![static] | T![fn] | T![mod] | T![struct] | T![enum] | T![trait]
)
}); });
let (offset, target) = if let Some(keyword) = item_keyword { let (offset, target) = if let Some(keyword) = item_keyword {
let parent = keyword.parent(); let parent = keyword.parent();
let def_kws = vec![CONST_DEF, FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF]; let def_kws = vec![CONST, STATIC, FN, MODULE, STRUCT, ENUM, TRAIT];
// Parent is not a definition, can't add visibility // Parent is not a definition, can't add visibility
if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) { if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) {
return None; return None;
@ -44,7 +47,7 @@ fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
} }
(vis_offset(&parent), keyword.text_range()) (vis_offset(&parent), keyword.text_range())
} else if let Some(field_name) = ctx.find_node_at_offset::<ast::Name>() { } else if let Some(field_name) = ctx.find_node_at_offset::<ast::Name>() {
let field = field_name.syntax().ancestors().find_map(ast::RecordFieldDef::cast)?; let field = field_name.syntax().ancestors().find_map(ast::RecordField::cast)?;
if field.name()? != field_name { if field.name()? != field_name {
mark::hit!(change_visibility_field_false_positive); mark::hit!(change_visibility_field_false_positive);
return None; return None;
@ -53,7 +56,7 @@ fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
return None; return None;
} }
(vis_offset(field.syntax()), field_name.syntax().text_range()) (vis_offset(field.syntax()), field_name.syntax().text_range())
} else if let Some(field) = ctx.find_node_at_offset::<ast::TupleFieldDef>() { } else if let Some(field) = ctx.find_node_at_offset::<ast::TupleField>() {
if field.visibility().is_some() { if field.visibility().is_some() {
return None; return None;
} }
@ -62,16 +65,21 @@ fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
return None; return None;
}; };
acc.add(AssistId("change_visibility"), "Change visibility to pub(crate)", target, |edit| { acc.add(
AssistId("change_visibility", AssistKind::RefactorRewrite),
"Change visibility to pub(crate)",
target,
|edit| {
edit.insert(offset, "pub(crate) "); edit.insert(offset, "pub(crate) ");
}) },
)
} }
fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> { fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> {
if vis.syntax().text() == "pub" { if vis.syntax().text() == "pub" {
let target = vis.syntax().text_range(); let target = vis.syntax().text_range();
return acc.add( return acc.add(
AssistId("change_visibility"), AssistId("change_visibility", AssistKind::RefactorRewrite),
"Change Visibility to pub(crate)", "Change Visibility to pub(crate)",
target, target,
|edit| { |edit| {
@ -82,7 +90,7 @@ fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> {
if vis.syntax().text() == "pub(crate)" { if vis.syntax().text() == "pub(crate)" {
let target = vis.syntax().text_range(); let target = vis.syntax().text_range();
return acc.add( return acc.add(
AssistId("change_visibility"), AssistId("change_visibility", AssistKind::RefactorRewrite),
"Change visibility to pub", "Change visibility to pub",
target, target,
|edit| { |edit| {
@ -146,6 +154,11 @@ mod tests {
check_assist(change_visibility, "<|>const FOO = 3u8;", "pub(crate) const FOO = 3u8;"); check_assist(change_visibility, "<|>const FOO = 3u8;", "pub(crate) const FOO = 3u8;");
} }
#[test]
fn change_visibility_static() {
check_assist(change_visibility, "<|>static FOO = 3u8;", "pub(crate) static FOO = 3u8;");
}
#[test] #[test]
fn change_visibility_handles_comment_attrs() { fn change_visibility_handles_comment_attrs() {
check_assist( check_assist(

View file

@ -8,14 +8,14 @@ use ra_syntax::{
make, make,
}, },
AstNode, AstNode,
SyntaxKind::{FN_DEF, LOOP_EXPR, L_CURLY, R_CURLY, WHILE_EXPR, WHITESPACE}, SyntaxKind::{FN, LOOP_EXPR, L_CURLY, R_CURLY, WHILE_EXPR, WHITESPACE},
SyntaxNode, SyntaxNode,
}; };
use crate::{ use crate::{
assist_context::{AssistContext, Assists}, assist_context::{AssistContext, Assists},
utils::invert_boolean_expression, utils::invert_boolean_expression,
AssistId, AssistId, AssistKind,
}; };
// Assist: convert_to_guarded_return // Assist: convert_to_guarded_return
@ -88,7 +88,7 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext)
let early_expression: ast::Expr = match parent_container.kind() { let early_expression: ast::Expr = match parent_container.kind() {
WHILE_EXPR | LOOP_EXPR => make::expr_continue(), WHILE_EXPR | LOOP_EXPR => make::expr_continue(),
FN_DEF => make::expr_return(), FN => make::expr_return(),
_ => return None, _ => return None,
}; };
@ -99,7 +99,11 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext)
then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?; then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?;
let target = if_expr.syntax().text_range(); let target = if_expr.syntax().text_range();
acc.add(AssistId("convert_to_guarded_return"), "Convert to guarded return", target, |edit| { acc.add(
AssistId("convert_to_guarded_return", AssistKind::RefactorRewrite),
"Convert to guarded return",
target,
|edit| {
let if_indent_level = IndentLevel::from_node(&if_expr.syntax()); let if_indent_level = IndentLevel::from_node(&if_expr.syntax());
let new_block = match if_let_pat { let new_block = match if_let_pat {
None => { None => {
@ -108,7 +112,8 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext)
let then_branch = let then_branch =
make::block_expr(once(make::expr_stmt(early_expression).into()), None); make::block_expr(once(make::expr_stmt(early_expression).into()), None);
let cond = invert_boolean_expression(cond_expr); let cond = invert_boolean_expression(cond_expr);
make::expr_if(make::condition(cond, None), then_branch).indent(if_indent_level) make::expr_if(make::condition(cond, None), then_branch)
.indent(if_indent_level)
}; };
replace(new_expr.syntax(), &then_block, &parent_block, &if_expr) replace(new_expr.syntax(), &then_block, &parent_block, &if_expr)
} }
@ -178,7 +183,8 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext)
&mut then_statements, &mut then_statements,
) )
} }
}) },
)
} }
#[cfg(test)] #[cfg(test)]

View file

@ -10,7 +10,8 @@ use ra_syntax::{
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use crate::{ use crate::{
assist_context::AssistBuilder, utils::insert_use_statement, AssistContext, AssistId, Assists, assist_context::AssistBuilder, utils::insert_use_statement, AssistContext, AssistId,
AssistKind, Assists,
}; };
// Assist: extract_struct_from_enum_variant // Assist: extract_struct_from_enum_variant
@ -30,30 +31,30 @@ pub(crate) fn extract_struct_from_enum_variant(
acc: &mut Assists, acc: &mut Assists,
ctx: &AssistContext, ctx: &AssistContext,
) -> Option<()> { ) -> Option<()> {
let variant = ctx.find_node_at_offset::<ast::EnumVariant>()?; let variant = ctx.find_node_at_offset::<ast::Variant>()?;
let field_list = match variant.kind() { let field_list = match variant.kind() {
ast::StructKind::Tuple(field_list) => field_list, ast::StructKind::Tuple(field_list) => field_list,
_ => return None, _ => return None,
}; };
let variant_name = variant.name()?.to_string(); let variant_name = variant.name()?.to_string();
let variant_hir = ctx.sema.to_def(&variant)?; let variant_hir = ctx.sema.to_def(&variant)?;
if existing_struct_def(ctx.db, &variant_name, &variant_hir) { if existing_struct_def(ctx.db(), &variant_name, &variant_hir) {
return None; return None;
} }
let enum_ast = variant.parent_enum(); let enum_ast = variant.parent_enum();
let visibility = enum_ast.visibility(); let visibility = enum_ast.visibility();
let enum_hir = ctx.sema.to_def(&enum_ast)?; let enum_hir = ctx.sema.to_def(&enum_ast)?;
let variant_hir_name = variant_hir.name(ctx.db); let variant_hir_name = variant_hir.name(ctx.db());
let enum_module_def = ModuleDef::from(enum_hir); let enum_module_def = ModuleDef::from(enum_hir);
let current_module = enum_hir.module(ctx.db); let current_module = enum_hir.module(ctx.db());
let target = variant.syntax().text_range(); let target = variant.syntax().text_range();
acc.add( acc.add(
AssistId("extract_struct_from_enum_variant"), AssistId("extract_struct_from_enum_variant", AssistKind::RefactorRewrite),
"Extract struct from enum variant", "Extract struct from enum variant",
target, target,
|builder| { |builder| {
let definition = Definition::ModuleDef(ModuleDef::EnumVariant(variant_hir)); let definition = Definition::ModuleDef(ModuleDef::EnumVariant(variant_hir));
let res = definition.find_usages(&ctx.db, None); let res = definition.find_usages(&ctx.sema, None);
let start_offset = variant.parent_enum().syntax().text_range().start(); let start_offset = variant.parent_enum().syntax().text_range().start();
let mut visited_modules_set = FxHashSet::default(); let mut visited_modules_set = FxHashSet::default();
visited_modules_set.insert(current_module); visited_modules_set.insert(current_module);
@ -101,7 +102,7 @@ fn insert_import(
enum_module_def: &ModuleDef, enum_module_def: &ModuleDef,
variant_hir_name: &Name, variant_hir_name: &Name,
) -> Option<()> { ) -> Option<()> {
let db = ctx.db; let db = ctx.db();
let mod_path = module.find_use_path(db, enum_module_def.clone()); let mod_path = module.find_use_path(db, enum_module_def.clone());
if let Some(mut mod_path) = mod_path { if let Some(mut mod_path) = mod_path {
mod_path.segments.pop(); mod_path.segments.pop();

View file

@ -2,14 +2,13 @@ use ra_syntax::{
ast::{self, AstNode}, ast::{self, AstNode},
SyntaxKind::{ SyntaxKind::{
BLOCK_EXPR, BREAK_EXPR, COMMENT, LAMBDA_EXPR, LOOP_EXPR, MATCH_ARM, PATH_EXPR, RETURN_EXPR, BLOCK_EXPR, BREAK_EXPR, COMMENT, LAMBDA_EXPR, LOOP_EXPR, MATCH_ARM, PATH_EXPR, RETURN_EXPR,
WHITESPACE,
}, },
SyntaxNode, SyntaxNode,
}; };
use stdx::format_to; use stdx::format_to;
use test_utils::mark; use test_utils::mark;
use crate::{AssistContext, AssistId, Assists}; use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: extract_variable // Assist: extract_variable
// //
@ -36,15 +35,17 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext) -> Option
mark::hit!(extract_var_in_comment_is_not_applicable); mark::hit!(extract_var_in_comment_is_not_applicable);
return None; return None;
} }
let expr = node.ancestors().find_map(valid_target_expr)?; let to_extract = node.ancestors().find_map(valid_target_expr)?;
let (anchor_stmt, wrap_in_block) = anchor_stmt(expr.clone())?; let anchor = Anchor::from(&to_extract)?;
let indent = anchor_stmt.prev_sibling_or_token()?.as_token()?.clone(); let indent = anchor.syntax().prev_sibling_or_token()?.as_token()?.clone();
if indent.kind() != WHITESPACE { let target = to_extract.syntax().text_range();
return None; acc.add(
} AssistId("extract_variable", AssistKind::RefactorExtract),
let target = expr.syntax().text_range(); "Extract into variable",
acc.add(AssistId("extract_variable"), "Extract into variable", target, move |edit| { target,
let field_shorthand = match expr.syntax().parent().and_then(ast::RecordField::cast) { move |edit| {
let field_shorthand =
match to_extract.syntax().parent().and_then(ast::RecordExprField::cast) {
Some(field) => field.name_ref(), Some(field) => field.name_ref(),
None => None, None => None,
}; };
@ -56,32 +57,26 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext) -> Option
None => "var_name".to_string(), None => "var_name".to_string(),
}; };
let expr_range = match &field_shorthand { let expr_range = match &field_shorthand {
Some(it) => it.syntax().text_range().cover(expr.syntax().text_range()), Some(it) => it.syntax().text_range().cover(to_extract.syntax().text_range()),
None => expr.syntax().text_range(), None => to_extract.syntax().text_range(),
}; };
if wrap_in_block { if let Anchor::WrapInBlock(_) = anchor {
format_to!(buf, "{{ let {} = ", var_name); format_to!(buf, "{{ let {} = ", var_name);
} else { } else {
format_to!(buf, "let {} = ", var_name); format_to!(buf, "let {} = ", var_name);
}; };
format_to!(buf, "{}", expr.syntax()); format_to!(buf, "{}", to_extract.syntax());
let full_stmt = ast::ExprStmt::cast(anchor_stmt.clone()); if let Anchor::Replace(stmt) = anchor {
let is_full_stmt = if let Some(expr_stmt) = &full_stmt {
Some(expr.syntax().clone()) == expr_stmt.expr().map(|e| e.syntax().clone())
} else {
false
};
if is_full_stmt {
mark::hit!(test_extract_var_expr_stmt); mark::hit!(test_extract_var_expr_stmt);
if full_stmt.unwrap().semicolon_token().is_none() { if stmt.semicolon_token().is_none() {
buf.push_str(";"); buf.push_str(";");
} }
match ctx.config.snippet_cap { match ctx.config.snippet_cap {
Some(cap) => { Some(cap) => {
let snip = let snip = buf
buf.replace(&format!("let {}", var_name), &format!("let $0{}", var_name)); .replace(&format!("let {}", var_name), &format!("let $0{}", var_name));
edit.replace_snippet(cap, expr_range, snip) edit.replace_snippet(cap, expr_range, snip)
} }
None => edit.replace(expr_range, buf), None => edit.replace(expr_range, buf),
@ -103,7 +98,7 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext) -> Option
} }
edit.replace(expr_range, var_name.clone()); edit.replace(expr_range, var_name.clone());
let offset = anchor_stmt.text_range().start(); let offset = anchor.syntax().text_range().start();
match ctx.config.snippet_cap { match ctx.config.snippet_cap {
Some(cap) => { Some(cap) => {
let snip = let snip =
@ -113,10 +108,11 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext) -> Option
None => edit.insert(offset, buf), None => edit.insert(offset, buf),
} }
if wrap_in_block { if let Anchor::WrapInBlock(_) = anchor {
edit.insert(anchor_stmt.text_range().end(), " }"); edit.insert(anchor.syntax().text_range().end(), " }");
} }
}) },
)
} }
/// Check whether the node is a valid expression which can be extracted to a variable. /// Check whether the node is a valid expression which can be extracted to a variable.
@ -133,34 +129,50 @@ fn valid_target_expr(node: SyntaxNode) -> Option<ast::Expr> {
} }
} }
/// Returns the syntax node which will follow the freshly extractd var enum Anchor {
/// and a boolean indicating whether we have to wrap it within a { } block Before(SyntaxNode),
/// to produce correct code. Replace(ast::ExprStmt),
/// It can be a statement, the last in a block expression or a wanna be block WrapInBlock(SyntaxNode),
/// expression like a lambda or match arm. }
fn anchor_stmt(expr: ast::Expr) -> Option<(SyntaxNode, bool)> {
expr.syntax().ancestors().find_map(|node| { impl Anchor {
if let Some(expr) = node.parent().and_then(ast::BlockExpr::cast).and_then(|it| it.expr()) { fn from(to_extract: &ast::Expr) -> Option<Anchor> {
to_extract.syntax().ancestors().find_map(|node| {
if let Some(expr) =
node.parent().and_then(ast::BlockExpr::cast).and_then(|it| it.expr())
{
if expr.syntax() == &node { if expr.syntax() == &node {
mark::hit!(test_extract_var_last_expr); mark::hit!(test_extract_var_last_expr);
return Some((node, false)); return Some(Anchor::Before(node));
} }
} }
if let Some(parent) = node.parent() { if let Some(parent) = node.parent() {
if parent.kind() == MATCH_ARM || parent.kind() == LAMBDA_EXPR { if parent.kind() == MATCH_ARM || parent.kind() == LAMBDA_EXPR {
return Some((node, true)); return Some(Anchor::WrapInBlock(node));
} }
} }
if ast::Stmt::cast(node.clone()).is_some() { if let Some(stmt) = ast::Stmt::cast(node.clone()) {
return Some((node, false)); if let ast::Stmt::ExprStmt(stmt) = stmt {
if stmt.expr().as_ref() == Some(to_extract) {
return Some(Anchor::Replace(stmt));
}
}
return Some(Anchor::Before(node));
} }
None None
}) })
} }
fn syntax(&self) -> &SyntaxNode {
match self {
Anchor::Before(it) | Anchor::WrapInBlock(it) => it,
Anchor::Replace(stmt) => stmt.syntax(),
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use test_utils::mark; use test_utils::mark;

View file

@ -8,7 +8,7 @@ use test_utils::mark;
use crate::{ use crate::{
utils::{render_snippet, Cursor, FamousDefs}, utils::{render_snippet, Cursor, FamousDefs},
AssistContext, AssistId, Assists, AssistContext, AssistId, AssistKind, Assists,
}; };
// Assist: fill_match_arms // Assist: fill_match_arms
@ -51,11 +51,11 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
let module = ctx.sema.scope(expr.syntax()).module()?; let module = ctx.sema.scope(expr.syntax()).module()?;
let missing_arms: Vec<MatchArm> = if let Some(enum_def) = resolve_enum_def(&ctx.sema, &expr) { let missing_arms: Vec<MatchArm> = if let Some(enum_def) = resolve_enum_def(&ctx.sema, &expr) {
let variants = enum_def.variants(ctx.db); let variants = enum_def.variants(ctx.db());
let mut variants = variants let mut variants = variants
.into_iter() .into_iter()
.filter_map(|variant| build_pat(ctx.db, module, variant)) .filter_map(|variant| build_pat(ctx.db(), module, variant))
.filter(|variant_pat| is_variant_missing(&mut arms, variant_pat)) .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat))
.map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block())) .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block()))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@ -84,11 +84,11 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
// where each tuple represents a proposed match arm. // where each tuple represents a proposed match arm.
enum_defs enum_defs
.into_iter() .into_iter()
.map(|enum_def| enum_def.variants(ctx.db)) .map(|enum_def| enum_def.variants(ctx.db()))
.multi_cartesian_product() .multi_cartesian_product()
.map(|variants| { .map(|variants| {
let patterns = let patterns =
variants.into_iter().filter_map(|variant| build_pat(ctx.db, module, variant)); variants.into_iter().filter_map(|variant| build_pat(ctx.db(), module, variant));
ast::Pat::from(make::tuple_pat(patterns)) ast::Pat::from(make::tuple_pat(patterns))
}) })
.filter(|variant_pat| is_variant_missing(&mut arms, variant_pat)) .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat))
@ -103,7 +103,11 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
} }
let target = match_expr.syntax().text_range(); let target = match_expr.syntax().text_range();
acc.add(AssistId("fill_match_arms"), "Fill match arms", target, |builder| { acc.add(
AssistId("fill_match_arms", AssistKind::QuickFix),
"Fill match arms",
target,
|builder| {
let new_arm_list = match_arm_list.remove_placeholder(); let new_arm_list = match_arm_list.remove_placeholder();
let n_old_arms = new_arm_list.arms().count(); let n_old_arms = new_arm_list.arms().count();
let new_arm_list = new_arm_list.append_arms(missing_arms); let new_arm_list = new_arm_list.append_arms(missing_arms);
@ -111,16 +115,25 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
let old_range = match_arm_list.syntax().text_range(); let old_range = match_arm_list.syntax().text_range();
match (first_new_arm, ctx.config.snippet_cap) { match (first_new_arm, ctx.config.snippet_cap) {
(Some(first_new_arm), Some(cap)) => { (Some(first_new_arm), Some(cap)) => {
let snippet = render_snippet( let extend_lifetime;
cap, let cursor = match first_new_arm
new_arm_list.syntax(), .syntax()
Cursor::Before(first_new_arm.syntax()), .descendants()
); .find_map(ast::PlaceholderPat::cast)
{
Some(it) => {
extend_lifetime = it.syntax().clone();
Cursor::Replace(&extend_lifetime)
}
None => Cursor::Before(first_new_arm.syntax()),
};
let snippet = render_snippet(cap, new_arm_list.syntax(), cursor);
builder.replace_snippet(cap, old_range, snippet); builder.replace_snippet(cap, old_range, snippet);
} }
_ => builder.replace(old_range, new_arm_list.to_string()), _ => builder.replace(old_range, new_arm_list.to_string()),
} }
}) },
)
} }
fn is_variant_missing(existing_arms: &mut Vec<MatchArm>, var: &Pat) -> bool { fn is_variant_missing(existing_arms: &mut Vec<MatchArm>, var: &Pat) -> bool {
@ -286,11 +299,7 @@ mod tests {
check_assist( check_assist(
fill_match_arms, fill_match_arms,
r#" r#"
enum A { enum A { As, Bs, Cs(Option<i32>) }
As,
Bs,
Cs(Option<i32>),
}
fn main() { fn main() {
match A::As<|> { match A::As<|> {
A::Cs(_) | A::Bs => {} A::Cs(_) | A::Bs => {}
@ -298,11 +307,7 @@ mod tests {
} }
"#, "#,
r#" r#"
enum A { enum A { As, Bs, Cs(Option<i32>) }
As,
Bs,
Cs(Option<i32>),
}
fn main() { fn main() {
match A::As { match A::As {
A::Cs(_) | A::Bs => {} A::Cs(_) | A::Bs => {}
@ -318,17 +323,8 @@ mod tests {
check_assist( check_assist(
fill_match_arms, fill_match_arms,
r#" r#"
enum A { enum A { As, Bs, Cs, Ds(String), Es(B) }
As, enum B { Xs, Ys }
Bs,
Cs,
Ds(String),
Es(B),
}
enum B {
Xs,
Ys,
}
fn main() { fn main() {
match A::As<|> { match A::As<|> {
A::Bs if 0 < 1 => {} A::Bs if 0 < 1 => {}
@ -338,17 +334,8 @@ mod tests {
} }
"#, "#,
r#" r#"
enum A { enum A { As, Bs, Cs, Ds(String), Es(B) }
As, enum B { Xs, Ys }
Bs,
Cs,
Ds(String),
Es(B),
}
enum B {
Xs,
Ys,
}
fn main() { fn main() {
match A::As { match A::As {
A::Bs if 0 < 1 => {} A::Bs if 0 < 1 => {}
@ -367,11 +354,7 @@ mod tests {
check_assist( check_assist(
fill_match_arms, fill_match_arms,
r#" r#"
enum A { enum A { As, Bs, Cs(Option<i32>) }
As,
Bs,
Cs(Option<i32>),
}
fn main() { fn main() {
match A::As<|> { match A::As<|> {
A::As(_) => {} A::As(_) => {}
@ -380,16 +363,12 @@ mod tests {
} }
"#, "#,
r#" r#"
enum A { enum A { As, Bs, Cs(Option<i32>) }
As,
Bs,
Cs(Option<i32>),
}
fn main() { fn main() {
match A::As { match A::As {
A::As(_) => {} A::As(_) => {}
a @ A::Bs(_) => {} a @ A::Bs(_) => {}
$0A::Cs(_) => {} A::Cs(${0:_}) => {}
} }
} }
"#, "#,
@ -401,13 +380,7 @@ mod tests {
check_assist( check_assist(
fill_match_arms, fill_match_arms,
r#" r#"
enum A { enum A { As, Bs, Cs(String), Ds(String, String), Es { x: usize, y: usize } }
As,
Bs,
Cs(String),
Ds(String, String),
Es { x: usize, y: usize }
}
fn main() { fn main() {
let a = A::As; let a = A::As;
@ -415,13 +388,7 @@ mod tests {
} }
"#, "#,
r#" r#"
enum A { enum A { As, Bs, Cs(String), Ds(String, String), Es { x: usize, y: usize } }
As,
Bs,
Cs(String),
Ds(String, String),
Es { x: usize, y: usize }
}
fn main() { fn main() {
let a = A::As; let a = A::As;
@ -773,7 +740,7 @@ fn foo(opt: Option<i32>) {
r#" r#"
fn foo(opt: Option<i32>) { fn foo(opt: Option<i32>) {
match opt { match opt {
$0Some(_) => {} Some(${0:_}) => {}
None => {} None => {}
} }
} }

View file

@ -2,7 +2,8 @@ use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution};
use ra_db::FileId; use ra_db::FileId;
use ra_syntax::{ast, AstNode, TextRange, TextSize}; use ra_syntax::{ast, AstNode, TextRange, TextSize};
use crate::{utils::vis_offset, AssistContext, AssistId, Assists}; use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists};
use ast::VisibilityOwner;
// FIXME: this really should be a fix for diagnostic, rather than an assist. // FIXME: this really should be a fix for diagnostic, rather than an assist.
@ -41,14 +42,15 @@ fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext) -> O
}; };
let current_module = ctx.sema.scope(&path.syntax()).module()?; let current_module = ctx.sema.scope(&path.syntax()).module()?;
let target_module = def.module(ctx.db)?; let target_module = def.module(ctx.db())?;
let vis = target_module.visibility_of(ctx.db, &def)?; let vis = target_module.visibility_of(ctx.db(), &def)?;
if vis.is_visible_from(ctx.db, current_module.into()) { if vis.is_visible_from(ctx.db(), current_module.into()) {
return None; return None;
}; };
let (offset, target, target_file, target_name) = target_data_for_def(ctx.db, def)?; let (offset, current_visibility, target, target_file, target_name) =
target_data_for_def(ctx.db(), def)?;
let missing_visibility = let missing_visibility =
if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" }; if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" };
@ -58,54 +60,78 @@ fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext) -> O
Some(name) => format!("Change visibility of {} to {}", name, missing_visibility), Some(name) => format!("Change visibility of {} to {}", name, missing_visibility),
}; };
acc.add(AssistId("fix_visibility"), assist_label, target, |builder| { acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |builder| {
builder.edit_file(target_file); builder.edit_file(target_file);
match ctx.config.snippet_cap { match ctx.config.snippet_cap {
Some(cap) => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)), Some(cap) => match current_visibility {
Some(current_visibility) => builder.replace_snippet(
cap,
current_visibility.syntax().text_range(),
format!("$0{}", missing_visibility),
),
None => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
},
None => match current_visibility {
Some(current_visibility) => {
builder.replace(current_visibility.syntax().text_range(), missing_visibility)
}
None => builder.insert(offset, format!("{} ", missing_visibility)), None => builder.insert(offset, format!("{} ", missing_visibility)),
},
} }
}) })
} }
fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let record_field: ast::RecordField = ctx.find_node_at_offset()?; let record_field: ast::RecordExprField = ctx.find_node_at_offset()?;
let (record_field_def, _) = ctx.sema.resolve_record_field(&record_field)?; let (record_field_def, _) = ctx.sema.resolve_record_field(&record_field)?;
let current_module = ctx.sema.scope(record_field.syntax()).module()?; let current_module = ctx.sema.scope(record_field.syntax()).module()?;
let visibility = record_field_def.visibility(ctx.db); let visibility = record_field_def.visibility(ctx.db());
if visibility.is_visible_from(ctx.db, current_module.into()) { if visibility.is_visible_from(ctx.db(), current_module.into()) {
return None; return None;
} }
let parent = record_field_def.parent_def(ctx.db); let parent = record_field_def.parent_def(ctx.db());
let parent_name = parent.name(ctx.db); let parent_name = parent.name(ctx.db());
let target_module = parent.module(ctx.db); let target_module = parent.module(ctx.db());
let in_file_source = record_field_def.source(ctx.db); let in_file_source = record_field_def.source(ctx.db());
let (offset, target) = match in_file_source.value { let (offset, current_visibility, target) = match in_file_source.value {
hir::FieldSource::Named(it) => { hir::FieldSource::Named(it) => {
let s = it.syntax(); let s = it.syntax();
(vis_offset(s), s.text_range()) (vis_offset(s), it.visibility(), s.text_range())
} }
hir::FieldSource::Pos(it) => { hir::FieldSource::Pos(it) => {
let s = it.syntax(); let s = it.syntax();
(vis_offset(s), s.text_range()) (vis_offset(s), it.visibility(), s.text_range())
} }
}; };
let missing_visibility = let missing_visibility =
if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" }; if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" };
let target_file = in_file_source.file_id.original_file(ctx.db); let target_file = in_file_source.file_id.original_file(ctx.db());
let target_name = record_field_def.name(ctx.db); let target_name = record_field_def.name(ctx.db());
let assist_label = let assist_label =
format!("Change visibility of {}.{} to {}", parent_name, target_name, missing_visibility); format!("Change visibility of {}.{} to {}", parent_name, target_name, missing_visibility);
acc.add(AssistId("fix_visibility"), assist_label, target, |builder| { acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |builder| {
builder.edit_file(target_file); builder.edit_file(target_file);
match ctx.config.snippet_cap { match ctx.config.snippet_cap {
Some(cap) => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)), Some(cap) => match current_visibility {
Some(current_visibility) => builder.replace_snippet(
cap,
dbg!(current_visibility.syntax()).text_range(),
format!("$0{}", missing_visibility),
),
None => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
},
None => match current_visibility {
Some(current_visibility) => {
builder.replace(current_visibility.syntax().text_range(), missing_visibility)
}
None => builder.insert(offset, format!("{} ", missing_visibility)), None => builder.insert(offset, format!("{} ", missing_visibility)),
},
} }
}) })
} }
@ -113,24 +139,30 @@ fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext) ->
fn target_data_for_def( fn target_data_for_def(
db: &dyn HirDatabase, db: &dyn HirDatabase,
def: hir::ModuleDef, def: hir::ModuleDef,
) -> Option<(TextSize, TextRange, FileId, Option<hir::Name>)> { ) -> Option<(TextSize, Option<ast::Visibility>, TextRange, FileId, Option<hir::Name>)> {
fn offset_target_and_file_id<S, Ast>( fn offset_target_and_file_id<S, Ast>(
db: &dyn HirDatabase, db: &dyn HirDatabase,
x: S, x: S,
) -> (TextSize, TextRange, FileId) ) -> (TextSize, Option<ast::Visibility>, TextRange, FileId)
where where
S: HasSource<Ast = Ast>, S: HasSource<Ast = Ast>,
Ast: AstNode, Ast: AstNode + ast::VisibilityOwner,
{ {
let source = x.source(db); let source = x.source(db);
let in_file_syntax = source.syntax(); let in_file_syntax = source.syntax();
let file_id = in_file_syntax.file_id; let file_id = in_file_syntax.file_id;
let syntax = in_file_syntax.value; let syntax = in_file_syntax.value;
(vis_offset(syntax), syntax.text_range(), file_id.original_file(db.upcast())) let current_visibility = source.value.visibility();
(
vis_offset(syntax),
current_visibility,
syntax.text_range(),
file_id.original_file(db.upcast()),
)
} }
let target_name; let target_name;
let (offset, target, target_file) = match def { let (offset, current_visibility, target, target_file) = match def {
hir::ModuleDef::Function(f) => { hir::ModuleDef::Function(f) => {
target_name = Some(f.name(db)); target_name = Some(f.name(db));
offset_target_and_file_id(db, f) offset_target_and_file_id(db, f)
@ -164,13 +196,13 @@ fn target_data_for_def(
let in_file_source = m.declaration_source(db)?; let in_file_source = m.declaration_source(db)?;
let file_id = in_file_source.file_id.original_file(db.upcast()); let file_id = in_file_source.file_id.original_file(db.upcast());
let syntax = in_file_source.value.syntax(); let syntax = in_file_source.value.syntax();
(vis_offset(syntax), syntax.text_range(), file_id) (vis_offset(syntax), in_file_source.value.visibility(), syntax.text_range(), file_id)
} }
// Enum variants can't be private, we can't modify builtin types // Enum variants can't be private, we can't modify builtin types
hir::ModuleDef::EnumVariant(_) | hir::ModuleDef::BuiltinType(_) => return None, hir::ModuleDef::EnumVariant(_) | hir::ModuleDef::BuiltinType(_) => return None,
}; };
Some((offset, target, target_file, target_name)) Some((offset, current_visibility, target, target_file, target_name))
} }
#[cfg(test)] #[cfg(test)]
@ -522,6 +554,34 @@ struct Bar;
) )
} }
#[test]
fn replaces_pub_crate_with_pub() {
check_assist(
fix_visibility,
r"
//- /main.rs crate:a deps:foo
foo::Bar<|>
//- /lib.rs crate:foo
pub(crate) struct Bar;
",
r"$0pub struct Bar;
",
);
check_assist(
fix_visibility,
r"
//- /main.rs crate:a deps:foo
fn main() {
foo::Foo { <|>bar: () };
}
//- /lib.rs crate:foo
pub struct Foo { pub(crate) bar: () }
",
r"pub struct Foo { $0pub bar: () }
",
);
}
#[test] #[test]
#[ignore] #[ignore]
// FIXME handle reexports properly // FIXME handle reexports properly

View file

@ -1,6 +1,6 @@
use ra_syntax::ast::{AstNode, BinExpr, BinOp}; use ra_syntax::ast::{AstNode, BinExpr, BinOp};
use crate::{AssistContext, AssistId, Assists}; use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: flip_binexpr // Assist: flip_binexpr
// //
@ -33,13 +33,18 @@ pub(crate) fn flip_binexpr(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
return None; return None;
} }
acc.add(AssistId("flip_binexpr"), "Flip binary expression", op_range, |edit| { acc.add(
AssistId("flip_binexpr", AssistKind::RefactorRewrite),
"Flip binary expression",
op_range,
|edit| {
if let FlipAction::FlipAndReplaceOp(new_op) = action { if let FlipAction::FlipAndReplaceOp(new_op) = action {
edit.replace(op_range, new_op); edit.replace(op_range, new_op);
} }
edit.replace(lhs.text_range(), rhs.text()); edit.replace(lhs.text_range(), rhs.text());
edit.replace(rhs.text_range(), lhs.text()); edit.replace(rhs.text_range(), lhs.text());
}) },
)
} }
enum FlipAction { enum FlipAction {

View file

@ -1,6 +1,6 @@
use ra_syntax::{algo::non_trivia_sibling, Direction, T}; use ra_syntax::{algo::non_trivia_sibling, Direction, T};
use crate::{AssistContext, AssistId, Assists}; use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: flip_comma // Assist: flip_comma
// //
@ -28,10 +28,15 @@ pub(crate) fn flip_comma(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
return None; return None;
} }
acc.add(AssistId("flip_comma"), "Flip comma", comma.text_range(), |edit| { acc.add(
AssistId("flip_comma", AssistKind::RefactorRewrite),
"Flip comma",
comma.text_range(),
|edit| {
edit.replace(prev.text_range(), next.to_string()); edit.replace(prev.text_range(), next.to_string());
edit.replace(next.text_range(), prev.to_string()); edit.replace(next.text_range(), prev.to_string());
}) },
)
} }
#[cfg(test)] #[cfg(test)]

View file

@ -4,7 +4,7 @@ use ra_syntax::{
Direction, T, Direction, T,
}; };
use crate::{AssistContext, AssistId, Assists}; use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: flip_trait_bound // Assist: flip_trait_bound
// //
@ -33,10 +33,15 @@ pub(crate) fn flip_trait_bound(acc: &mut Assists, ctx: &AssistContext) -> Option
); );
let target = plus.text_range(); let target = plus.text_range();
acc.add(AssistId("flip_trait_bound"), "Flip trait bounds", target, |edit| { acc.add(
AssistId("flip_trait_bound", AssistKind::RefactorRewrite),
"Flip trait bounds",
target,
|edit| {
edit.replace(before.text_range(), after.to_string()); edit.replace(before.text_range(), after.to_string());
edit.replace(after.text_range(), before.to_string()); edit.replace(after.text_range(), before.to_string());
}) },
)
} }
#[cfg(test)] #[cfg(test)]

View file

@ -4,9 +4,9 @@ use ra_syntax::{
TextSize, TextSize,
}; };
use crate::{AssistContext, AssistId, Assists}; use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: add_derive // Assist: generate_derive
// //
// Adds a new `#[derive()]` clause to a struct or enum. // Adds a new `#[derive()]` clause to a struct or enum.
// //
@ -24,12 +24,16 @@ use crate::{AssistContext, AssistId, Assists};
// y: u32, // y: u32,
// } // }
// ``` // ```
pub(crate) fn add_derive(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { pub(crate) fn generate_derive(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let cap = ctx.config.snippet_cap?; let cap = ctx.config.snippet_cap?;
let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?; let nominal = ctx.find_node_at_offset::<ast::AdtDef>()?;
let node_start = derive_insertion_offset(&nominal)?; let node_start = derive_insertion_offset(&nominal)?;
let target = nominal.syntax().text_range(); let target = nominal.syntax().text_range();
acc.add(AssistId("add_derive"), "Add `#[derive]`", target, |builder| { acc.add(
AssistId("generate_derive", AssistKind::Generate),
"Add `#[derive]`",
target,
|builder| {
let derive_attr = nominal let derive_attr = nominal
.attrs() .attrs()
.filter_map(|x| x.as_simple_call()) .filter_map(|x| x.as_simple_call())
@ -49,11 +53,12 @@ pub(crate) fn add_derive(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
) )
} }
}; };
}) },
)
} }
// Insert `derive` after doc comments. // Insert `derive` after doc comments.
fn derive_insertion_offset(nominal: &ast::NominalDef) -> Option<TextSize> { fn derive_insertion_offset(nominal: &ast::AdtDef) -> Option<TextSize> {
let non_ws_child = nominal let non_ws_child = nominal
.syntax() .syntax()
.children_with_tokens() .children_with_tokens()
@ -70,12 +75,12 @@ mod tests {
#[test] #[test]
fn add_derive_new() { fn add_derive_new() {
check_assist( check_assist(
add_derive, generate_derive,
"struct Foo { a: i32, <|>}", "struct Foo { a: i32, <|>}",
"#[derive($0)]\nstruct Foo { a: i32, }", "#[derive($0)]\nstruct Foo { a: i32, }",
); );
check_assist( check_assist(
add_derive, generate_derive,
"struct Foo { <|> a: i32, }", "struct Foo { <|> a: i32, }",
"#[derive($0)]\nstruct Foo { a: i32, }", "#[derive($0)]\nstruct Foo { a: i32, }",
); );
@ -84,7 +89,7 @@ mod tests {
#[test] #[test]
fn add_derive_existing() { fn add_derive_existing() {
check_assist( check_assist(
add_derive, generate_derive,
"#[derive(Clone)]\nstruct Foo { a: i32<|>, }", "#[derive(Clone)]\nstruct Foo { a: i32<|>, }",
"#[derive(Clone$0)]\nstruct Foo { a: i32, }", "#[derive(Clone$0)]\nstruct Foo { a: i32, }",
); );
@ -93,7 +98,7 @@ mod tests {
#[test] #[test]
fn add_derive_new_with_doc_comment() { fn add_derive_new_with_doc_comment() {
check_assist( check_assist(
add_derive, generate_derive,
" "
/// `Foo` is a pretty important struct. /// `Foo` is a pretty important struct.
/// It does stuff. /// It does stuff.
@ -111,7 +116,7 @@ struct Foo { a: i32, }
#[test] #[test]
fn add_derive_target() { fn add_derive_target() {
check_assist_target( check_assist_target(
add_derive, generate_derive,
" "
struct SomeThingIrrelevant; struct SomeThingIrrelevant;
/// `Foo` is a pretty important struct. /// `Foo` is a pretty important struct.

View file

@ -2,9 +2,9 @@ use ra_ide_db::RootDatabase;
use ra_syntax::ast::{self, AstNode, NameOwner}; use ra_syntax::ast::{self, AstNode, NameOwner};
use test_utils::mark; use test_utils::mark;
use crate::{utils::FamousDefs, AssistContext, AssistId, Assists}; use crate::{utils::FamousDefs, AssistContext, AssistId, AssistKind, Assists};
// Assist: add_from_impl_for_enum // Assist: generate_from_impl_for_enum
// //
// Adds a From impl for an enum variant with one tuple field. // Adds a From impl for an enum variant with one tuple field.
// //
@ -21,8 +21,8 @@ use crate::{utils::FamousDefs, AssistContext, AssistId, Assists};
// } // }
// } // }
// ``` // ```
pub(crate) fn add_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { pub(crate) fn generate_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let variant = ctx.find_node_at_offset::<ast::EnumVariant>()?; let variant = ctx.find_node_at_offset::<ast::Variant>()?;
let variant_name = variant.name()?; let variant_name = variant.name()?;
let enum_name = variant.parent_enum().name()?; let enum_name = variant.parent_enum().name()?;
let field_list = match variant.kind() { let field_list = match variant.kind() {
@ -32,7 +32,7 @@ pub(crate) fn add_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext) ->
if field_list.fields().count() != 1 { if field_list.fields().count() != 1 {
return None; return None;
} }
let field_type = field_list.fields().next()?.type_ref()?; let field_type = field_list.fields().next()?.ty()?;
let path = match field_type { let path = match field_type {
ast::TypeRef::PathType(it) => it, ast::TypeRef::PathType(it) => it,
_ => return None, _ => return None,
@ -45,8 +45,8 @@ pub(crate) fn add_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext) ->
let target = variant.syntax().text_range(); let target = variant.syntax().text_range();
acc.add( acc.add(
AssistId("add_from_impl_for_enum"), AssistId("generate_from_impl_for_enum", AssistKind::Generate),
"Add From impl for this enum variant", "Generate `From` impl for this enum variant",
target, target,
|edit| { |edit| {
let start_offset = variant.parent_enum().syntax().text_range().end(); let start_offset = variant.parent_enum().syntax().text_range().end();
@ -69,7 +69,7 @@ impl From<{0}> for {1} {{
fn existing_from_impl( fn existing_from_impl(
sema: &'_ hir::Semantics<'_, RootDatabase>, sema: &'_ hir::Semantics<'_, RootDatabase>,
variant: &ast::EnumVariant, variant: &ast::Variant,
) -> Option<()> { ) -> Option<()> {
let variant = sema.to_def(variant)?; let variant = sema.to_def(variant)?;
let enum_ = variant.parent_enum(sema.db); let enum_ = variant.parent_enum(sema.db);
@ -97,9 +97,9 @@ mod tests {
use super::*; use super::*;
#[test] #[test]
fn test_add_from_impl_for_enum() { fn test_generate_from_impl_for_enum() {
check_assist( check_assist(
add_from_impl_for_enum, generate_from_impl_for_enum,
"enum A { <|>One(u32) }", "enum A { <|>One(u32) }",
r#"enum A { One(u32) } r#"enum A { One(u32) }
@ -112,9 +112,9 @@ impl From<u32> for A {
} }
#[test] #[test]
fn test_add_from_impl_for_enum_complicated_path() { fn test_generate_from_impl_for_enum_complicated_path() {
check_assist( check_assist(
add_from_impl_for_enum, generate_from_impl_for_enum,
r#"enum A { <|>One(foo::bar::baz::Boo) }"#, r#"enum A { <|>One(foo::bar::baz::Boo) }"#,
r#"enum A { One(foo::bar::baz::Boo) } r#"enum A { One(foo::bar::baz::Boo) }
@ -129,7 +129,7 @@ impl From<foo::bar::baz::Boo> for A {
fn check_not_applicable(ra_fixture: &str) { fn check_not_applicable(ra_fixture: &str) {
let fixture = let fixture =
format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE); format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
check_assist_not_applicable(add_from_impl_for_enum, &fixture) check_assist_not_applicable(generate_from_impl_for_enum, &fixture)
} }
#[test] #[test]
@ -166,7 +166,7 @@ impl From<u32> for A {
#[test] #[test]
fn test_add_from_impl_different_variant_impl_exists() { fn test_add_from_impl_different_variant_impl_exists() {
check_assist( check_assist(
add_from_impl_for_enum, generate_from_impl_for_enum,
r#"enum A { <|>One(u32), Two(String), } r#"enum A { <|>One(u32), Two(String), }
impl From<String> for A { impl From<String> for A {

View file

@ -13,10 +13,10 @@ use rustc_hash::{FxHashMap, FxHashSet};
use crate::{ use crate::{
assist_config::SnippetCap, assist_config::SnippetCap,
utils::{render_snippet, Cursor}, utils::{render_snippet, Cursor},
AssistContext, AssistId, Assists, AssistContext, AssistId, AssistKind, Assists,
}; };
// Assist: add_function // Assist: generate_function
// //
// Adds a stub function with a signature matching the function under the cursor. // Adds a stub function with a signature matching the function under the cursor.
// //
@ -41,7 +41,7 @@ use crate::{
// } // }
// //
// ``` // ```
pub(crate) fn add_function(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { pub(crate) fn generate_function(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let path_expr: ast::PathExpr = ctx.find_node_at_offset()?; let path_expr: ast::PathExpr = ctx.find_node_at_offset()?;
let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?; let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
let path = path_expr.path()?; let path = path_expr.path()?;
@ -62,7 +62,11 @@ pub(crate) fn add_function(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?; let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?;
let target = call.syntax().text_range(); let target = call.syntax().text_range();
acc.add(AssistId("add_function"), "Add function", target, |builder| { acc.add(
AssistId("generate_function", AssistKind::Generate),
format!("Generate `{}` function", function_builder.fn_name),
target,
|builder| {
let function_template = function_builder.render(); let function_template = function_builder.render();
builder.edit_file(function_template.file); builder.edit_file(function_template.file);
let new_fn = function_template.to_string(ctx.config.snippet_cap); let new_fn = function_template.to_string(ctx.config.snippet_cap);
@ -70,14 +74,15 @@ pub(crate) fn add_function(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
Some(cap) => builder.insert_snippet(cap, function_template.insert_offset, new_fn), Some(cap) => builder.insert_snippet(cap, function_template.insert_offset, new_fn),
None => builder.insert(function_template.insert_offset, new_fn), None => builder.insert(function_template.insert_offset, new_fn),
} }
}) },
)
} }
struct FunctionTemplate { struct FunctionTemplate {
insert_offset: TextSize, insert_offset: TextSize,
placeholder_expr: ast::MacroCall, placeholder_expr: ast::MacroCall,
leading_ws: String, leading_ws: String,
fn_def: ast::FnDef, fn_def: ast::Fn,
trailing_ws: String, trailing_ws: String,
file: FileId, file: FileId,
} }
@ -99,7 +104,7 @@ impl FunctionTemplate {
struct FunctionBuilder { struct FunctionBuilder {
target: GeneratedFunctionTarget, target: GeneratedFunctionTarget,
fn_name: ast::Name, fn_name: ast::Name,
type_params: Option<ast::TypeParamList>, type_params: Option<ast::GenericParamList>,
params: ast::ParamList, params: ast::ParamList,
file: FileId, file: FileId,
needs_pub: bool, needs_pub: bool,
@ -117,7 +122,7 @@ impl FunctionBuilder {
let mut file = ctx.frange.file_id; let mut file = ctx.frange.file_id;
let target = match &target_module { let target = match &target_module {
Some(target_module) => { Some(target_module) => {
let module_source = target_module.definition_source(ctx.db); let module_source = target_module.definition_source(ctx.db());
let (in_file, target) = next_space_for_fn_in_module(ctx.sema.db, &module_source)?; let (in_file, target) = next_space_for_fn_in_module(ctx.sema.db, &module_source)?;
file = in_file; file = in_file;
target target
@ -195,7 +200,7 @@ fn fn_args(
ctx: &AssistContext, ctx: &AssistContext,
target_module: hir::Module, target_module: hir::Module,
call: &ast::CallExpr, call: &ast::CallExpr,
) -> Option<(Option<ast::TypeParamList>, ast::ParamList)> { ) -> Option<(Option<ast::GenericParamList>, ast::ParamList)> {
let mut arg_names = Vec::new(); let mut arg_names = Vec::new();
let mut arg_types = Vec::new(); let mut arg_types = Vec::new();
for arg in call.arg_list()?.args() { for arg in call.arg_list()?.args() {
@ -269,7 +274,7 @@ fn fn_arg_type(
return None; return None;
} }
if let Ok(rendered) = ty.display_source_code(ctx.db, target_module.into()) { if let Ok(rendered) = ty.display_source_code(ctx.db(), target_module.into()) {
Some(rendered) Some(rendered)
} else { } else {
None None
@ -333,7 +338,7 @@ mod tests {
#[test] #[test]
fn add_function_with_no_args() { fn add_function_with_no_args() {
check_assist( check_assist(
add_function, generate_function,
r" r"
fn foo() { fn foo() {
bar<|>(); bar<|>();
@ -356,7 +361,7 @@ fn bar() {
// This ensures that the function is correctly generated // This ensures that the function is correctly generated
// in the next outer mod or file // in the next outer mod or file
check_assist( check_assist(
add_function, generate_function,
r" r"
impl Foo { impl Foo {
fn foo() { fn foo() {
@ -382,7 +387,7 @@ fn bar() {
fn add_function_directly_after_current_block() { fn add_function_directly_after_current_block() {
// The new fn should not be created at the end of the file or module // The new fn should not be created at the end of the file or module
check_assist( check_assist(
add_function, generate_function,
r" r"
fn foo1() { fn foo1() {
bar<|>(); bar<|>();
@ -407,7 +412,7 @@ fn foo2() {}
#[test] #[test]
fn add_function_with_no_args_in_same_module() { fn add_function_with_no_args_in_same_module() {
check_assist( check_assist(
add_function, generate_function,
r" r"
mod baz { mod baz {
fn foo() { fn foo() {
@ -432,7 +437,7 @@ mod baz {
#[test] #[test]
fn add_function_with_function_call_arg() { fn add_function_with_function_call_arg() {
check_assist( check_assist(
add_function, generate_function,
r" r"
struct Baz; struct Baz;
fn baz() -> Baz { todo!() } fn baz() -> Baz { todo!() }
@ -457,7 +462,7 @@ fn bar(baz: Baz) {
#[test] #[test]
fn add_function_with_method_call_arg() { fn add_function_with_method_call_arg() {
check_assist( check_assist(
add_function, generate_function,
r" r"
struct Baz; struct Baz;
impl Baz { impl Baz {
@ -490,7 +495,7 @@ fn bar(baz: Baz) {
#[test] #[test]
fn add_function_with_string_literal_arg() { fn add_function_with_string_literal_arg() {
check_assist( check_assist(
add_function, generate_function,
r#" r#"
fn foo() { fn foo() {
<|>bar("bar") <|>bar("bar")
@ -511,7 +516,7 @@ fn bar(arg: &str) {
#[test] #[test]
fn add_function_with_char_literal_arg() { fn add_function_with_char_literal_arg() {
check_assist( check_assist(
add_function, generate_function,
r#" r#"
fn foo() { fn foo() {
<|>bar('x') <|>bar('x')
@ -532,7 +537,7 @@ fn bar(arg: char) {
#[test] #[test]
fn add_function_with_int_literal_arg() { fn add_function_with_int_literal_arg() {
check_assist( check_assist(
add_function, generate_function,
r" r"
fn foo() { fn foo() {
<|>bar(42) <|>bar(42)
@ -553,7 +558,7 @@ fn bar(arg: i32) {
#[test] #[test]
fn add_function_with_cast_int_literal_arg() { fn add_function_with_cast_int_literal_arg() {
check_assist( check_assist(
add_function, generate_function,
r" r"
fn foo() { fn foo() {
<|>bar(42 as u8) <|>bar(42 as u8)
@ -576,7 +581,7 @@ fn bar(arg: u8) {
// Ensures that the name of the cast type isn't used // Ensures that the name of the cast type isn't used
// in the generated function signature. // in the generated function signature.
check_assist( check_assist(
add_function, generate_function,
r" r"
fn foo() { fn foo() {
let x = 42; let x = 42;
@ -599,7 +604,7 @@ fn bar(x: u8) {
#[test] #[test]
fn add_function_with_variable_arg() { fn add_function_with_variable_arg() {
check_assist( check_assist(
add_function, generate_function,
r" r"
fn foo() { fn foo() {
let worble = (); let worble = ();
@ -622,7 +627,7 @@ fn bar(worble: ()) {
#[test] #[test]
fn add_function_with_impl_trait_arg() { fn add_function_with_impl_trait_arg() {
check_assist( check_assist(
add_function, generate_function,
r" r"
trait Foo {} trait Foo {}
fn foo() -> impl Foo { fn foo() -> impl Foo {
@ -651,7 +656,7 @@ fn bar(foo: impl Foo) {
#[test] #[test]
fn borrowed_arg() { fn borrowed_arg() {
check_assist( check_assist(
add_function, generate_function,
r" r"
struct Baz; struct Baz;
fn baz() -> Baz { todo!() } fn baz() -> Baz { todo!() }
@ -678,7 +683,7 @@ fn bar(baz: &Baz) {
#[test] #[test]
fn add_function_with_qualified_path_arg() { fn add_function_with_qualified_path_arg() {
check_assist( check_assist(
add_function, generate_function,
r" r"
mod Baz { mod Baz {
pub struct Bof; pub struct Bof;
@ -709,7 +714,7 @@ fn bar(baz: Baz::Bof) {
// FIXME fix printing the generics of a `Ty` to make this test pass // FIXME fix printing the generics of a `Ty` to make this test pass
fn add_function_with_generic_arg() { fn add_function_with_generic_arg() {
check_assist( check_assist(
add_function, generate_function,
r" r"
fn foo<T>(t: T) { fn foo<T>(t: T) {
<|>bar(t) <|>bar(t)
@ -732,7 +737,7 @@ fn bar<T>(t: T) {
// FIXME Fix function type printing to make this test pass // FIXME Fix function type printing to make this test pass
fn add_function_with_fn_arg() { fn add_function_with_fn_arg() {
check_assist( check_assist(
add_function, generate_function,
r" r"
struct Baz; struct Baz;
impl Baz { impl Baz {
@ -763,7 +768,7 @@ fn bar(arg: fn() -> Baz) {
// FIXME Fix closure type printing to make this test pass // FIXME Fix closure type printing to make this test pass
fn add_function_with_closure_arg() { fn add_function_with_closure_arg() {
check_assist( check_assist(
add_function, generate_function,
r" r"
fn foo() { fn foo() {
let closure = |x: i64| x - 1; let closure = |x: i64| x - 1;
@ -786,7 +791,7 @@ fn bar(closure: impl Fn(i64) -> i64) {
#[test] #[test]
fn unresolveable_types_default_to_unit() { fn unresolveable_types_default_to_unit() {
check_assist( check_assist(
add_function, generate_function,
r" r"
fn foo() { fn foo() {
<|>bar(baz) <|>bar(baz)
@ -807,7 +812,7 @@ fn bar(baz: ()) {
#[test] #[test]
fn arg_names_dont_overlap() { fn arg_names_dont_overlap() {
check_assist( check_assist(
add_function, generate_function,
r" r"
struct Baz; struct Baz;
fn baz() -> Baz { Baz } fn baz() -> Baz { Baz }
@ -832,7 +837,7 @@ fn bar(baz_1: Baz, baz_2: Baz) {
#[test] #[test]
fn arg_name_counters_start_at_1_per_name() { fn arg_name_counters_start_at_1_per_name() {
check_assist( check_assist(
add_function, generate_function,
r#" r#"
struct Baz; struct Baz;
fn baz() -> Baz { Baz } fn baz() -> Baz { Baz }
@ -857,7 +862,7 @@ fn bar(baz_1: Baz, baz_2: Baz, arg_1: &str, arg_2: &str) {
#[test] #[test]
fn add_function_in_module() { fn add_function_in_module() {
check_assist( check_assist(
add_function, generate_function,
r" r"
mod bar {} mod bar {}
@ -885,7 +890,7 @@ fn foo() {
// See https://github.com/rust-analyzer/rust-analyzer/issues/1165 // See https://github.com/rust-analyzer/rust-analyzer/issues/1165
fn qualified_path_uses_correct_scope() { fn qualified_path_uses_correct_scope() {
check_assist( check_assist(
add_function, generate_function,
" "
mod foo { mod foo {
pub struct Foo; pub struct Foo;
@ -916,7 +921,7 @@ fn baz(foo: foo::Foo) {
#[test] #[test]
fn add_function_in_module_containing_other_items() { fn add_function_in_module_containing_other_items() {
check_assist( check_assist(
add_function, generate_function,
r" r"
mod bar { mod bar {
fn something_else() {} fn something_else() {}
@ -945,7 +950,7 @@ fn foo() {
#[test] #[test]
fn add_function_in_nested_module() { fn add_function_in_nested_module() {
check_assist( check_assist(
add_function, generate_function,
r" r"
mod bar { mod bar {
mod baz {} mod baz {}
@ -974,7 +979,7 @@ fn foo() {
#[test] #[test]
fn add_function_in_another_file() { fn add_function_in_another_file() {
check_assist( check_assist(
add_function, generate_function,
r" r"
//- /main.rs //- /main.rs
mod foo; mod foo;
@ -996,7 +1001,7 @@ pub(crate) fn bar() {
#[test] #[test]
fn add_function_not_applicable_if_function_already_exists() { fn add_function_not_applicable_if_function_already_exists() {
check_assist_not_applicable( check_assist_not_applicable(
add_function, generate_function,
r" r"
fn foo() { fn foo() {
bar<|>(); bar<|>();
@ -1013,7 +1018,7 @@ fn bar() {}
// bar is resolved, but baz isn't. // bar is resolved, but baz isn't.
// The assist is only active if the cursor is on an unresolved path, // The assist is only active if the cursor is on an unresolved path,
// but the assist should only be offered if the path is a function call. // but the assist should only be offered if the path is a function call.
add_function, generate_function,
r" r"
fn foo() { fn foo() {
bar(b<|>az); bar(b<|>az);
@ -1028,7 +1033,7 @@ fn bar(baz: ()) {}
#[ignore] #[ignore]
fn create_method_with_no_args() { fn create_method_with_no_args() {
check_assist( check_assist(
add_function, generate_function,
r" r"
struct Foo; struct Foo;
impl Foo { impl Foo {

View file

@ -0,0 +1,109 @@
use ra_syntax::ast::{self, AstNode, GenericParamsOwner, NameOwner};
use stdx::{format_to, SepBy};
use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: generate_impl
//
// Adds a new inherent impl for a type.
//
// ```
// struct Ctx<T: Clone> {
// data: T,<|>
// }
// ```
// ->
// ```
// struct Ctx<T: Clone> {
// data: T,
// }
//
// impl<T: Clone> Ctx<T> {
// $0
// }
// ```
pub(crate) fn generate_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let nominal = ctx.find_node_at_offset::<ast::AdtDef>()?;
let name = nominal.name()?;
let target = nominal.syntax().text_range();
acc.add(
AssistId("generate_impl", AssistKind::Generate),
format!("Generate impl for `{}`", name),
target,
|edit| {
let type_params = nominal.generic_param_list();
let start_offset = nominal.syntax().text_range().end();
let mut buf = String::new();
buf.push_str("\n\nimpl");
if let Some(type_params) = &type_params {
format_to!(buf, "{}", type_params.syntax());
}
buf.push_str(" ");
buf.push_str(name.text().as_str());
if let Some(type_params) = type_params {
let lifetime_params = type_params
.lifetime_params()
.filter_map(|it| it.lifetime_token())
.map(|it| it.text().clone());
let type_params = type_params
.type_params()
.filter_map(|it| it.name())
.map(|it| it.text().clone());
let generic_params = lifetime_params.chain(type_params).sep_by(", ");
format_to!(buf, "<{}>", generic_params)
}
match ctx.config.snippet_cap {
Some(cap) => {
buf.push_str(" {\n $0\n}");
edit.insert_snippet(cap, start_offset, buf);
}
None => {
buf.push_str(" {\n}");
edit.insert(start_offset, buf);
}
}
},
)
}
#[cfg(test)]
mod tests {
use crate::tests::{check_assist, check_assist_target};
use super::*;
#[test]
fn test_add_impl() {
check_assist(
generate_impl,
"struct Foo {<|>}\n",
"struct Foo {}\n\nimpl Foo {\n $0\n}\n",
);
check_assist(
generate_impl,
"struct Foo<T: Clone> {<|>}",
"struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n $0\n}",
);
check_assist(
generate_impl,
"struct Foo<'a, T: Foo<'a>> {<|>}",
"struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n $0\n}",
);
}
#[test]
fn add_impl_target() {
check_assist_target(
generate_impl,
"
struct SomeThingIrrelevant;
/// Has a lifetime parameter
struct Foo<'a, T: Foo<'a>> {<|>}
struct EvenMoreIrrelevant;
",
"/// Has a lifetime parameter
struct Foo<'a, T: Foo<'a>> {}",
);
}
}

View file

@ -1,15 +1,13 @@
use hir::Adt; use hir::Adt;
use ra_syntax::{ use ra_syntax::{
ast::{ ast::{self, AstNode, GenericParamsOwner, NameOwner, StructKind, VisibilityOwner},
self, AstNode, NameOwner, StructKind, TypeAscriptionOwner, TypeParamsOwner, VisibilityOwner,
},
T, T,
}; };
use stdx::{format_to, SepBy}; use stdx::{format_to, SepBy};
use crate::{AssistContext, AssistId, Assists}; use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: add_new // Assist: generate_new
// //
// Adds a new inherent impl for a type. // Adds a new inherent impl for a type.
// //
@ -29,8 +27,8 @@ use crate::{AssistContext, AssistId, Assists};
// } // }
// //
// ``` // ```
pub(crate) fn add_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let strukt = ctx.find_node_at_offset::<ast::StructDef>()?; let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
// We want to only apply this to non-union structs with named fields // We want to only apply this to non-union structs with named fields
let field_list = match strukt.kind() { let field_list = match strukt.kind() {
@ -42,7 +40,7 @@ pub(crate) fn add_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let impl_def = find_struct_impl(&ctx, &strukt)?; let impl_def = find_struct_impl(&ctx, &strukt)?;
let target = strukt.syntax().text_range(); let target = strukt.syntax().text_range();
acc.add(AssistId("add_new"), "Add default constructor", target, |builder| { acc.add(AssistId("generate_new", AssistKind::Generate), "Generate `new`", target, |builder| {
let mut buf = String::with_capacity(512); let mut buf = String::with_capacity(512);
if impl_def.is_some() { if impl_def.is_some() {
@ -53,9 +51,7 @@ pub(crate) fn add_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let params = field_list let params = field_list
.fields() .fields()
.filter_map(|f| { .filter_map(|f| Some(format!("{}: {}", f.name()?.syntax(), f.ty()?.syntax())))
Some(format!("{}: {}", f.name()?.syntax(), f.ascribed_type()?.syntax()))
})
.sep_by(", "); .sep_by(", ");
let fields = field_list.fields().filter_map(|f| f.name()).sep_by(", "); let fields = field_list.fields().filter_map(|f| f.name()).sep_by(", ");
@ -90,8 +86,8 @@ pub(crate) fn add_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
// Generates the surrounding `impl Type { <code> }` including type and lifetime // Generates the surrounding `impl Type { <code> }` including type and lifetime
// parameters // parameters
fn generate_impl_text(strukt: &ast::StructDef, code: &str) -> String { fn generate_impl_text(strukt: &ast::Struct, code: &str) -> String {
let type_params = strukt.type_param_list(); let type_params = strukt.generic_param_list();
let mut buf = String::with_capacity(code.len()); let mut buf = String::with_capacity(code.len());
buf.push_str("\n\nimpl"); buf.push_str("\n\nimpl");
if let Some(type_params) = &type_params { if let Some(type_params) = &type_params {
@ -121,15 +117,15 @@ fn generate_impl_text(strukt: &ast::StructDef, code: &str) -> String {
// //
// FIXME: change the new fn checking to a more semantic approach when that's more // FIXME: change the new fn checking to a more semantic approach when that's more
// viable (e.g. we process proc macros, etc) // viable (e.g. we process proc macros, etc)
fn find_struct_impl(ctx: &AssistContext, strukt: &ast::StructDef) -> Option<Option<ast::ImplDef>> { fn find_struct_impl(ctx: &AssistContext, strukt: &ast::Struct) -> Option<Option<ast::Impl>> {
let db = ctx.db; let db = ctx.db();
let module = strukt.syntax().ancestors().find(|node| { let module = strukt.syntax().ancestors().find(|node| {
ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind()) ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind())
})?; })?;
let struct_def = ctx.sema.to_def(strukt)?; let struct_def = ctx.sema.to_def(strukt)?;
let block = module.descendants().filter_map(ast::ImplDef::cast).find_map(|impl_blk| { let block = module.descendants().filter_map(ast::Impl::cast).find_map(|impl_blk| {
let blk = ctx.sema.to_def(&impl_blk)?; let blk = ctx.sema.to_def(&impl_blk)?;
// FIXME: handle e.g. `struct S<T>; impl<U> S<U> {}` // FIXME: handle e.g. `struct S<T>; impl<U> S<U> {}`
@ -157,10 +153,10 @@ fn find_struct_impl(ctx: &AssistContext, strukt: &ast::StructDef) -> Option<Opti
Some(block) Some(block)
} }
fn has_new_fn(imp: &ast::ImplDef) -> bool { fn has_new_fn(imp: &ast::Impl) -> bool {
if let Some(il) = imp.item_list() { if let Some(il) = imp.assoc_item_list() {
for item in il.assoc_items() { for item in il.assoc_items() {
if let ast::AssocItem::FnDef(f) = item { if let ast::AssocItem::Fn(f) = item {
if let Some(name) = f.name() { if let Some(name) = f.name() {
if name.text().eq_ignore_ascii_case("new") { if name.text().eq_ignore_ascii_case("new") {
return true; return true;
@ -181,10 +177,10 @@ mod tests {
#[test] #[test]
#[rustfmt::skip] #[rustfmt::skip]
fn test_add_new() { fn test_generate_new() {
// Check output of generation // Check output of generation
check_assist( check_assist(
add_new, generate_new,
"struct Foo {<|>}", "struct Foo {<|>}",
"struct Foo {} "struct Foo {}
@ -194,7 +190,7 @@ impl Foo {
", ",
); );
check_assist( check_assist(
add_new, generate_new,
"struct Foo<T: Clone> {<|>}", "struct Foo<T: Clone> {<|>}",
"struct Foo<T: Clone> {} "struct Foo<T: Clone> {}
@ -204,7 +200,7 @@ impl<T: Clone> Foo<T> {
", ",
); );
check_assist( check_assist(
add_new, generate_new,
"struct Foo<'a, T: Foo<'a>> {<|>}", "struct Foo<'a, T: Foo<'a>> {<|>}",
"struct Foo<'a, T: Foo<'a>> {} "struct Foo<'a, T: Foo<'a>> {}
@ -214,7 +210,7 @@ impl<'a, T: Foo<'a>> Foo<'a, T> {
", ",
); );
check_assist( check_assist(
add_new, generate_new,
"struct Foo { baz: String <|>}", "struct Foo { baz: String <|>}",
"struct Foo { baz: String } "struct Foo { baz: String }
@ -224,7 +220,7 @@ impl Foo {
", ",
); );
check_assist( check_assist(
add_new, generate_new,
"struct Foo { baz: String, qux: Vec<i32> <|>}", "struct Foo { baz: String, qux: Vec<i32> <|>}",
"struct Foo { baz: String, qux: Vec<i32> } "struct Foo { baz: String, qux: Vec<i32> }
@ -236,7 +232,7 @@ impl Foo {
// Check that visibility modifiers don't get brought in for fields // Check that visibility modifiers don't get brought in for fields
check_assist( check_assist(
add_new, generate_new,
"struct Foo { pub baz: String, pub qux: Vec<i32> <|>}", "struct Foo { pub baz: String, pub qux: Vec<i32> <|>}",
"struct Foo { pub baz: String, pub qux: Vec<i32> } "struct Foo { pub baz: String, pub qux: Vec<i32> }
@ -248,7 +244,7 @@ impl Foo {
// Check that it reuses existing impls // Check that it reuses existing impls
check_assist( check_assist(
add_new, generate_new,
"struct Foo {<|>} "struct Foo {<|>}
impl Foo {} impl Foo {}
@ -261,7 +257,7 @@ impl Foo {
", ",
); );
check_assist( check_assist(
add_new, generate_new,
"struct Foo {<|>} "struct Foo {<|>}
impl Foo { impl Foo {
@ -279,7 +275,7 @@ impl Foo {
); );
check_assist( check_assist(
add_new, generate_new,
"struct Foo {<|>} "struct Foo {<|>}
impl Foo { impl Foo {
@ -304,7 +300,7 @@ impl Foo {
// Check visibility of new fn based on struct // Check visibility of new fn based on struct
check_assist( check_assist(
add_new, generate_new,
"pub struct Foo {<|>}", "pub struct Foo {<|>}",
"pub struct Foo {} "pub struct Foo {}
@ -314,7 +310,7 @@ impl Foo {
", ",
); );
check_assist( check_assist(
add_new, generate_new,
"pub(crate) struct Foo {<|>}", "pub(crate) struct Foo {<|>}",
"pub(crate) struct Foo {} "pub(crate) struct Foo {}
@ -326,9 +322,9 @@ impl Foo {
} }
#[test] #[test]
fn add_new_not_applicable_if_fn_exists() { fn generate_new_not_applicable_if_fn_exists() {
check_assist_not_applicable( check_assist_not_applicable(
add_new, generate_new,
" "
struct Foo {<|>} struct Foo {<|>}
@ -340,7 +336,7 @@ impl Foo {
); );
check_assist_not_applicable( check_assist_not_applicable(
add_new, generate_new,
" "
struct Foo {<|>} struct Foo {<|>}
@ -353,9 +349,9 @@ impl Foo {
} }
#[test] #[test]
fn add_new_target() { fn generate_new_target() {
check_assist_target( check_assist_target(
add_new, generate_new,
" "
struct SomeThingIrrelevant; struct SomeThingIrrelevant;
/// Has a lifetime parameter /// Has a lifetime parameter
@ -370,7 +366,7 @@ struct Foo<'a, T: Foo<'a>> {}",
#[test] #[test]
fn test_unrelated_new() { fn test_unrelated_new() {
check_assist( check_assist(
add_new, generate_new,
r##" r##"
pub struct AstId<N: AstNode> { pub struct AstId<N: AstNode> {
file_id: HirFileId, file_id: HirFileId,

View file

@ -7,7 +7,7 @@ use test_utils::mark;
use crate::{ use crate::{
assist_context::{AssistContext, Assists}, assist_context::{AssistContext, Assists},
AssistId, AssistId, AssistKind,
}; };
// Assist: inline_local_variable // Assist: inline_local_variable
@ -44,7 +44,7 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> O
let def = ctx.sema.to_def(&bind_pat)?; let def = ctx.sema.to_def(&bind_pat)?;
let def = Definition::Local(def); let def = Definition::Local(def);
let refs = def.find_usages(ctx.db, None); let refs = def.find_usages(&ctx.sema, None);
if refs.is_empty() { if refs.is_empty() {
mark::hit!(test_not_applicable_if_variable_unused); mark::hit!(test_not_applicable_if_variable_unused);
return None; return None;
@ -110,13 +110,19 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> O
let init_in_paren = format!("({})", &init_str); let init_in_paren = format!("({})", &init_str);
let target = bind_pat.syntax().text_range(); let target = bind_pat.syntax().text_range();
acc.add(AssistId("inline_local_variable"), "Inline variable", target, move |builder| { acc.add(
AssistId("inline_local_variable", AssistKind::RefactorInline),
"Inline variable",
target,
move |builder| {
builder.delete(delete_range); builder.delete(delete_range);
for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) { for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) {
let replacement = if should_wrap { init_in_paren.clone() } else { init_str.clone() }; let replacement =
if should_wrap { init_in_paren.clone() } else { init_str.clone() };
builder.replace(desc.file_range.range, replacement) builder.replace(desc.file_range.range, replacement)
} }
}) },
)
} }
#[cfg(test)] #[cfg(test)]

View file

@ -1,10 +1,10 @@
use ra_syntax::{ use ra_syntax::{
ast::{self, NameOwner, TypeAscriptionOwner, TypeParamsOwner}, ast::{self, GenericParamsOwner, NameOwner},
AstNode, SyntaxKind, TextRange, TextSize, AstNode, SyntaxKind, TextRange, TextSize,
}; };
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use crate::{assist_context::AssistBuilder, AssistContext, AssistId, Assists}; use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists};
static ASSIST_NAME: &str = "introduce_named_lifetime"; static ASSIST_NAME: &str = "introduce_named_lifetime";
static ASSIST_LABEL: &str = "Introduce named lifetime"; static ASSIST_LABEL: &str = "Introduce named lifetime";
@ -38,9 +38,9 @@ pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext) -
let lifetime_token = ctx let lifetime_token = ctx
.find_token_at_offset(SyntaxKind::LIFETIME) .find_token_at_offset(SyntaxKind::LIFETIME)
.filter(|lifetime| lifetime.text() == "'_")?; .filter(|lifetime| lifetime.text() == "'_")?;
if let Some(fn_def) = lifetime_token.ancestors().find_map(ast::FnDef::cast) { if let Some(fn_def) = lifetime_token.ancestors().find_map(ast::Fn::cast) {
generate_fn_def_assist(acc, &fn_def, lifetime_token.text_range()) generate_fn_def_assist(acc, &fn_def, lifetime_token.text_range())
} else if let Some(impl_def) = lifetime_token.ancestors().find_map(ast::ImplDef::cast) { } else if let Some(impl_def) = lifetime_token.ancestors().find_map(ast::Impl::cast) {
generate_impl_def_assist(acc, &impl_def, lifetime_token.text_range()) generate_impl_def_assist(acc, &impl_def, lifetime_token.text_range())
} else { } else {
None None
@ -50,11 +50,11 @@ pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext) -
/// Generate the assist for the fn def case /// Generate the assist for the fn def case
fn generate_fn_def_assist( fn generate_fn_def_assist(
acc: &mut Assists, acc: &mut Assists,
fn_def: &ast::FnDef, fn_def: &ast::Fn,
lifetime_loc: TextRange, lifetime_loc: TextRange,
) -> Option<()> { ) -> Option<()> {
let param_list: ast::ParamList = fn_def.param_list()?; let param_list: ast::ParamList = fn_def.param_list()?;
let new_lifetime_param = generate_unique_lifetime_param_name(&fn_def.type_param_list())?; let new_lifetime_param = generate_unique_lifetime_param_name(&fn_def.generic_param_list())?;
let end_of_fn_ident = fn_def.name()?.ident_token()?.text_range().end(); let end_of_fn_ident = fn_def.name()?.ident_token()?.text_range().end();
let self_param = let self_param =
// use the self if it's a reference and has no explicit lifetime // use the self if it's a reference and has no explicit lifetime
@ -67,7 +67,7 @@ fn generate_fn_def_assist(
// otherwise, if there's a single reference parameter without a named liftime, use that // otherwise, if there's a single reference parameter without a named liftime, use that
let fn_params_without_lifetime: Vec<_> = param_list let fn_params_without_lifetime: Vec<_> = param_list
.params() .params()
.filter_map(|param| match param.ascribed_type() { .filter_map(|param| match param.ty() {
Some(ast::TypeRef::ReferenceType(ascribed_type)) Some(ast::TypeRef::ReferenceType(ascribed_type))
if ascribed_type.lifetime_token() == None => if ascribed_type.lifetime_token() == None =>
{ {
@ -83,7 +83,7 @@ fn generate_fn_def_assist(
_ => return None, _ => return None,
} }
}; };
acc.add(AssistId(ASSIST_NAME), ASSIST_LABEL, lifetime_loc, |builder| { acc.add(AssistId(ASSIST_NAME, AssistKind::Refactor), ASSIST_LABEL, lifetime_loc, |builder| {
add_lifetime_param(fn_def, builder, end_of_fn_ident, new_lifetime_param); add_lifetime_param(fn_def, builder, end_of_fn_ident, new_lifetime_param);
builder.replace(lifetime_loc, format!("'{}", new_lifetime_param)); builder.replace(lifetime_loc, format!("'{}", new_lifetime_param));
loc_needing_lifetime.map(|loc| builder.insert(loc, format!("'{} ", new_lifetime_param))); loc_needing_lifetime.map(|loc| builder.insert(loc, format!("'{} ", new_lifetime_param)));
@ -93,12 +93,12 @@ fn generate_fn_def_assist(
/// Generate the assist for the impl def case /// Generate the assist for the impl def case
fn generate_impl_def_assist( fn generate_impl_def_assist(
acc: &mut Assists, acc: &mut Assists,
impl_def: &ast::ImplDef, impl_def: &ast::Impl,
lifetime_loc: TextRange, lifetime_loc: TextRange,
) -> Option<()> { ) -> Option<()> {
let new_lifetime_param = generate_unique_lifetime_param_name(&impl_def.type_param_list())?; let new_lifetime_param = generate_unique_lifetime_param_name(&impl_def.generic_param_list())?;
let end_of_impl_kw = impl_def.impl_token()?.text_range().end(); let end_of_impl_kw = impl_def.impl_token()?.text_range().end();
acc.add(AssistId(ASSIST_NAME), ASSIST_LABEL, lifetime_loc, |builder| { acc.add(AssistId(ASSIST_NAME, AssistKind::Refactor), ASSIST_LABEL, lifetime_loc, |builder| {
add_lifetime_param(impl_def, builder, end_of_impl_kw, new_lifetime_param); add_lifetime_param(impl_def, builder, end_of_impl_kw, new_lifetime_param);
builder.replace(lifetime_loc, format!("'{}", new_lifetime_param)); builder.replace(lifetime_loc, format!("'{}", new_lifetime_param));
}) })
@ -107,7 +107,7 @@ fn generate_impl_def_assist(
/// Given a type parameter list, generate a unique lifetime parameter name /// Given a type parameter list, generate a unique lifetime parameter name
/// which is not in the list /// which is not in the list
fn generate_unique_lifetime_param_name( fn generate_unique_lifetime_param_name(
existing_type_param_list: &Option<ast::TypeParamList>, existing_type_param_list: &Option<ast::GenericParamList>,
) -> Option<char> { ) -> Option<char> {
match existing_type_param_list { match existing_type_param_list {
Some(type_params) => { Some(type_params) => {
@ -123,13 +123,13 @@ fn generate_unique_lifetime_param_name(
/// Add the lifetime param to `builder`. If there are type parameters in `type_params_owner`, add it to the end. Otherwise /// Add the lifetime param to `builder`. If there are type parameters in `type_params_owner`, add it to the end. Otherwise
/// add new type params brackets with the lifetime parameter at `new_type_params_loc`. /// add new type params brackets with the lifetime parameter at `new_type_params_loc`.
fn add_lifetime_param<TypeParamsOwner: ast::TypeParamsOwner>( fn add_lifetime_param<TypeParamsOwner: ast::GenericParamsOwner>(
type_params_owner: &TypeParamsOwner, type_params_owner: &TypeParamsOwner,
builder: &mut AssistBuilder, builder: &mut AssistBuilder,
new_type_params_loc: TextSize, new_type_params_loc: TextSize,
new_lifetime_param: char, new_lifetime_param: char,
) { ) {
match type_params_owner.type_param_list() { match type_params_owner.generic_param_list() {
// add the new lifetime parameter to an existing type param list // add the new lifetime parameter to an existing type param list
Some(type_params) => { Some(type_params) => {
builder.insert( builder.insert(

View file

@ -6,7 +6,7 @@ use ra_syntax::{
use crate::{ use crate::{
assist_context::{AssistContext, Assists}, assist_context::{AssistContext, Assists},
utils::invert_boolean_expression, utils::invert_boolean_expression,
AssistId, AssistId, AssistKind,
}; };
// Assist: invert_if // Assist: invert_if
@ -54,7 +54,7 @@ pub(crate) fn invert_if(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let else_node = else_block.syntax(); let else_node = else_block.syntax();
let else_range = else_node.text_range(); let else_range = else_node.text_range();
let then_range = then_node.text_range(); let then_range = then_node.text_range();
acc.add(AssistId("invert_if"), "Invert if", if_range, |edit| { acc.add(AssistId("invert_if", AssistKind::RefactorRewrite), "Invert if", if_range, |edit| {
edit.replace(cond_range, flip_cond.syntax().text()); edit.replace(cond_range, flip_cond.syntax().text());
edit.replace(else_range, then_node.text()); edit.replace(else_range, then_node.text());
edit.replace(then_range, else_node.text()); edit.replace(then_range, else_node.text());

View file

@ -8,7 +8,7 @@ use ra_syntax::{
use crate::{ use crate::{
assist_context::{AssistContext, Assists}, assist_context::{AssistContext, Assists},
AssistId, AssistId, AssistKind,
}; };
// Assist: merge_imports // Assist: merge_imports
@ -28,7 +28,7 @@ pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<()
let mut rewriter = SyntaxRewriter::default(); let mut rewriter = SyntaxRewriter::default();
let mut offset = ctx.offset(); let mut offset = ctx.offset();
if let Some(use_item) = tree.syntax().parent().and_then(ast::UseItem::cast) { if let Some(use_item) = tree.syntax().parent().and_then(ast::Use::cast) {
let (merged, to_delete) = next_prev() let (merged, to_delete) = next_prev()
.filter_map(|dir| neighbor(&use_item, dir)) .filter_map(|dir| neighbor(&use_item, dir))
.filter_map(|it| Some((it.clone(), it.use_tree()?))) .filter_map(|it| Some((it.clone(), it.use_tree()?)))
@ -56,9 +56,14 @@ pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<()
}; };
let target = tree.syntax().text_range(); let target = tree.syntax().text_range();
acc.add(AssistId("merge_imports"), "Merge imports", target, |builder| { acc.add(
AssistId("merge_imports", AssistKind::RefactorRewrite),
"Merge imports",
target,
|builder| {
builder.rewrite(rewriter); builder.rewrite(rewriter);
}) },
)
} }
fn next_prev() -> impl Iterator<Item = Direction> { fn next_prev() -> impl Iterator<Item = Direction> {

View file

@ -6,7 +6,7 @@ use ra_syntax::{
Direction, Direction,
}; };
use crate::{AssistContext, AssistId, Assists, TextRange}; use crate::{AssistContext, AssistId, AssistKind, Assists, TextRange};
// Assist: merge_match_arms // Assist: merge_match_arms
// //
@ -59,7 +59,11 @@ pub(crate) fn merge_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option
return None; return None;
} }
acc.add(AssistId("merge_match_arms"), "Merge match arms", current_text_range, |edit| { acc.add(
AssistId("merge_match_arms", AssistKind::RefactorRewrite),
"Merge match arms",
current_text_range,
|edit| {
let pats = if arms_to_merge.iter().any(contains_placeholder) { let pats = if arms_to_merge.iter().any(contains_placeholder) {
"_".into() "_".into()
} else { } else {
@ -77,7 +81,8 @@ pub(crate) fn merge_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option
let end = arms_to_merge.last().unwrap().syntax().text_range().end(); let end = arms_to_merge.last().unwrap().syntax().text_range().end();
edit.replace(TextRange::new(start, end), arm); edit.replace(TextRange::new(start, end), arm);
}) },
)
} }
fn contains_placeholder(a: &ast::MatchArm) -> bool { fn contains_placeholder(a: &ast::MatchArm) -> bool {

View file

@ -5,7 +5,7 @@ use ra_syntax::{
T, T,
}; };
use crate::{AssistContext, AssistId, Assists}; use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: move_bounds_to_where_clause // Assist: move_bounds_to_where_clause
// //
@ -23,7 +23,7 @@ use crate::{AssistContext, AssistId, Assists};
// } // }
// ``` // ```
pub(crate) fn move_bounds_to_where_clause(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { pub(crate) fn move_bounds_to_where_clause(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let type_param_list = ctx.find_node_at_offset::<ast::TypeParamList>()?; let type_param_list = ctx.find_node_at_offset::<ast::GenericParamList>()?;
let mut type_params = type_param_list.type_params(); let mut type_params = type_param_list.type_params();
if type_params.all(|p| p.type_bound_list().is_none()) { if type_params.all(|p| p.type_bound_list().is_none()) {
@ -37,20 +37,24 @@ pub(crate) fn move_bounds_to_where_clause(acc: &mut Assists, ctx: &AssistContext
let anchor = match_ast! { let anchor = match_ast! {
match parent { match parent {
ast::FnDef(it) => it.body()?.syntax().clone().into(), ast::Fn(it) => it.body()?.syntax().clone().into(),
ast::TraitDef(it) => it.item_list()?.syntax().clone().into(), ast::Trait(it) => it.assoc_item_list()?.syntax().clone().into(),
ast::ImplDef(it) => it.item_list()?.syntax().clone().into(), ast::Impl(it) => it.assoc_item_list()?.syntax().clone().into(),
ast::EnumDef(it) => it.variant_list()?.syntax().clone().into(), ast::Enum(it) => it.variant_list()?.syntax().clone().into(),
ast::StructDef(it) => { ast::Struct(it) => {
it.syntax().children_with_tokens() it.syntax().children_with_tokens()
.find(|it| it.kind() == RECORD_FIELD_DEF_LIST || it.kind() == T![;])? .find(|it| it.kind() == RECORD_FIELD_LIST || it.kind() == T![;])?
}, },
_ => return None _ => return None
} }
}; };
let target = type_param_list.syntax().text_range(); let target = type_param_list.syntax().text_range();
acc.add(AssistId("move_bounds_to_where_clause"), "Move to where clause", target, |edit| { acc.add(
AssistId("move_bounds_to_where_clause", AssistKind::RefactorRewrite),
"Move to where clause",
target,
|edit| {
let new_params = type_param_list let new_params = type_param_list
.type_params() .type_params()
.filter(|it| it.type_bound_list().is_some()) .filter(|it| it.type_bound_list().is_some())
@ -68,11 +72,14 @@ pub(crate) fn move_bounds_to_where_clause(acc: &mut Assists, ctx: &AssistContext
}; };
let to_insert = match anchor.prev_sibling_or_token() { let to_insert = match anchor.prev_sibling_or_token() {
Some(ref elem) if elem.kind() == WHITESPACE => format!("{} ", where_clause.syntax()), Some(ref elem) if elem.kind() == WHITESPACE => {
format!("{} ", where_clause.syntax())
}
_ => format!(" {}", where_clause.syntax()), _ => format!(" {}", where_clause.syntax()),
}; };
edit.insert(anchor.text_range().start(), to_insert); edit.insert(anchor.text_range().start(), to_insert);
}) },
)
} }
fn build_predicate(param: ast::TypeParam) -> Option<ast::WherePred> { fn build_predicate(param: ast::TypeParam) -> Option<ast::WherePred> {

View file

@ -3,7 +3,7 @@ use ra_syntax::{
SyntaxKind::WHITESPACE, SyntaxKind::WHITESPACE,
}; };
use crate::{AssistContext, AssistId, Assists}; use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: move_guard_to_arm_body // Assist: move_guard_to_arm_body
// //
@ -40,7 +40,11 @@ pub(crate) fn move_guard_to_arm_body(acc: &mut Assists, ctx: &AssistContext) ->
let buf = format!("if {} {{ {} }}", guard_conditions.syntax().text(), arm_expr.syntax().text()); let buf = format!("if {} {{ {} }}", guard_conditions.syntax().text(), arm_expr.syntax().text());
let target = guard.syntax().text_range(); let target = guard.syntax().text_range();
acc.add(AssistId("move_guard_to_arm_body"), "Move guard to arm body", target, |edit| { acc.add(
AssistId("move_guard_to_arm_body", AssistKind::RefactorRewrite),
"Move guard to arm body",
target,
|edit| {
match space_before_guard { match space_before_guard {
Some(element) if element.kind() == WHITESPACE => { Some(element) if element.kind() == WHITESPACE => {
edit.delete(element.text_range()); edit.delete(element.text_range());
@ -50,7 +54,8 @@ pub(crate) fn move_guard_to_arm_body(acc: &mut Assists, ctx: &AssistContext) ->
edit.delete(guard.syntax().text_range()); edit.delete(guard.syntax().text_range());
edit.replace_node_and_indent(arm_expr.syntax(), buf); edit.replace_node_and_indent(arm_expr.syntax(), buf);
}) },
)
} }
// Assist: move_arm_cond_to_match_guard // Assist: move_arm_cond_to_match_guard
@ -100,7 +105,7 @@ pub(crate) fn move_arm_cond_to_match_guard(acc: &mut Assists, ctx: &AssistContex
let target = if_expr.syntax().text_range(); let target = if_expr.syntax().text_range();
acc.add( acc.add(
AssistId("move_arm_cond_to_match_guard"), AssistId("move_arm_cond_to_match_guard", AssistKind::RefactorRewrite),
"Move condition to match guard", "Move condition to match guard",
target, target,
|edit| { |edit| {

View file

@ -1,11 +1,14 @@
use std::borrow::Cow;
use ra_syntax::{ use ra_syntax::{
ast::{self, HasStringValue}, ast::{self, HasQuotes, HasStringValue},
AstToken, AstToken,
SyntaxKind::{RAW_STRING, STRING}, SyntaxKind::{RAW_STRING, STRING},
TextSize, TextRange, TextSize,
}; };
use test_utils::mark;
use crate::{AssistContext, AssistId, Assists}; use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: make_raw_string // Assist: make_raw_string
// //
@ -26,14 +29,24 @@ pub(crate) fn make_raw_string(acc: &mut Assists, ctx: &AssistContext) -> Option<
let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?; let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?;
let value = token.value()?; let value = token.value()?;
let target = token.syntax().text_range(); let target = token.syntax().text_range();
acc.add(AssistId("make_raw_string"), "Rewrite as raw string", target, |edit| { acc.add(
let max_hash_streak = count_hashes(&value); AssistId("make_raw_string", AssistKind::RefactorRewrite),
let mut hashes = String::with_capacity(max_hash_streak + 1); "Rewrite as raw string",
for _ in 0..hashes.capacity() { target,
hashes.push('#'); |edit| {
let hashes = "#".repeat(required_hashes(&value).max(1));
if matches!(value, Cow::Borrowed(_)) {
// Avoid replacing the whole string to better position the cursor.
edit.insert(token.syntax().text_range().start(), format!("r{}", hashes));
edit.insert(token.syntax().text_range().end(), format!("{}", hashes));
} else {
edit.replace(
token.syntax().text_range(),
format!("r{}\"{}\"{}", hashes, value, hashes),
);
} }
edit.replace(token.syntax().text_range(), format!("r{}\"{}\"{}", hashes, value, hashes)); },
}) )
} }
// Assist: make_usual_string // Assist: make_usual_string
@ -55,11 +68,24 @@ pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext) -> Optio
let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?; let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?;
let value = token.value()?; let value = token.value()?;
let target = token.syntax().text_range(); let target = token.syntax().text_range();
acc.add(AssistId("make_usual_string"), "Rewrite as regular string", target, |edit| { acc.add(
AssistId("make_usual_string", AssistKind::RefactorRewrite),
"Rewrite as regular string",
target,
|edit| {
// parse inside string to escape `"` // parse inside string to escape `"`
let escaped = value.escape_default().to_string(); let escaped = value.escape_default().to_string();
if let Some(offsets) = token.quote_offsets() {
if token.text()[offsets.contents - token.syntax().text_range().start()] == escaped {
edit.replace(offsets.quotes.0, "\"");
edit.replace(offsets.quotes.1, "\"");
return;
}
}
edit.replace(token.syntax().text_range(), format!("\"{}\"", escaped)); edit.replace(token.syntax().text_range(), format!("\"{}\"", escaped));
}) },
)
} }
// Assist: add_hash // Assist: add_hash
@ -80,7 +106,7 @@ pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext) -> Optio
pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let token = ctx.find_token_at_offset(RAW_STRING)?; let token = ctx.find_token_at_offset(RAW_STRING)?;
let target = token.text_range(); let target = token.text_range();
acc.add(AssistId("add_hash"), "Add # to raw string", target, |edit| { acc.add(AssistId("add_hash", AssistKind::Refactor), "Add #", target, |edit| {
edit.insert(token.text_range().start() + TextSize::of('r'), "#"); edit.insert(token.text_range().start() + TextSize::of('r'), "#");
edit.insert(token.text_range().end(), "#"); edit.insert(token.text_range().end(), "#");
}) })
@ -102,44 +128,58 @@ pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
// } // }
// ``` // ```
pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let token = ctx.find_token_at_offset(RAW_STRING)?; let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?;
let text = token.text().as_str(); let text = token.text().as_str();
if text.starts_with("r\"") { if !text.starts_with("r#") && text.ends_with('#') {
// no hash to remove
return None; return None;
} }
let target = token.text_range();
acc.add(AssistId("remove_hash"), "Remove hash from raw string", target, |edit| { let existing_hashes = text.chars().skip(1).take_while(|&it| it == '#').count();
let result = &text[2..text.len() - 1];
let result = if result.starts_with('\"') { let text_range = token.syntax().text_range();
// FIXME: this logic is wrong, not only the last has has to handled specially let internal_text = &text[token.text_range_between_quotes()? - text_range.start()];
// no more hash, escape
let internal_str = &result[1..result.len() - 1]; if existing_hashes == required_hashes(internal_text) {
format!("\"{}\"", internal_str.escape_default().to_string()) mark::hit!(cant_remove_required_hash);
} else { return None;
result.to_owned() }
};
edit.replace(token.text_range(), format!("r{}", result)); acc.add(AssistId("remove_hash", AssistKind::RefactorRewrite), "Remove #", text_range, |edit| {
edit.delete(TextRange::at(text_range.start() + TextSize::of('r'), TextSize::of('#')));
edit.delete(TextRange::new(text_range.end() - TextSize::of('#'), text_range.end()));
}) })
} }
fn count_hashes(s: &str) -> usize { fn required_hashes(s: &str) -> usize {
let mut max_hash_streak = 0usize; let mut res = 0usize;
for idx in s.match_indices("\"#").map(|(i, _)| i) { for idx in s.match_indices('"').map(|(i, _)| i) {
let (_, sub) = s.split_at(idx + 1); let (_, sub) = s.split_at(idx + 1);
let nb_hash = sub.chars().take_while(|c| *c == '#').count(); let n_hashes = sub.chars().take_while(|c| *c == '#').count();
if nb_hash > max_hash_streak { res = res.max(n_hashes + 1)
max_hash_streak = nb_hash;
} }
res
} }
max_hash_streak
#[test]
fn test_required_hashes() {
assert_eq!(0, required_hashes("abc"));
assert_eq!(0, required_hashes("###"));
assert_eq!(1, required_hashes("\""));
assert_eq!(2, required_hashes("\"#abc"));
assert_eq!(0, required_hashes("#abc"));
assert_eq!(3, required_hashes("#ab\"##c"));
assert_eq!(5, required_hashes("#ab\"##\"####c"));
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use test_utils::mark;
use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
use super::*;
#[test] #[test]
fn make_raw_string_target() { fn make_raw_string_target() {
check_assist_target( check_assist_target(
@ -341,33 +381,21 @@ string"###;
fn remove_hash_works() { fn remove_hash_works() {
check_assist( check_assist(
remove_hash, remove_hash,
r##" r##"fn f() { let s = <|>r#"random string"#; }"##,
fn f() { r#"fn f() { let s = r"random string"; }"#,
let s = <|>r#"random string"#;
}
"##,
r#"
fn f() {
let s = r"random string";
}
"#,
) )
} }
#[test] #[test]
fn remove_hash_with_quote_works() { fn cant_remove_required_hash() {
check_assist( mark::check!(cant_remove_required_hash);
check_assist_not_applicable(
remove_hash, remove_hash,
r##" r##"
fn f() { fn f() {
let s = <|>r#"random"str"ing"#; let s = <|>r#"random"str"ing"#;
} }
"##, "##,
r#"
fn f() {
let s = r"random\"str\"ing";
}
"#,
) )
} }
@ -389,27 +417,13 @@ string"###;
} }
#[test] #[test]
fn remove_hash_not_works() { fn remove_hash_doesnt_work() {
check_assist_not_applicable( check_assist_not_applicable(remove_hash, r#"fn f() { let s = <|>"random string"; }"#);
remove_hash,
r#"
fn f() {
let s = <|>"random string";
}
"#,
);
} }
#[test] #[test]
fn remove_hash_no_hash_not_works() { fn remove_hash_no_hash_doesnt_work() {
check_assist_not_applicable( check_assist_not_applicable(remove_hash, r#"fn f() { let s = <|>r"random string"; }"#);
remove_hash,
r#"
fn f() {
let s = <|>r"random string";
}
"#,
);
} }
#[test] #[test]
@ -487,14 +501,4 @@ string"###;
"#, "#,
); );
} }
#[test]
fn count_hashes_test() {
assert_eq!(0, count_hashes("abc"));
assert_eq!(0, count_hashes("###"));
assert_eq!(1, count_hashes("\"#abc"));
assert_eq!(0, count_hashes("#abc"));
assert_eq!(2, count_hashes("#ab\"##c"));
assert_eq!(4, count_hashes("#ab\"##\"####c"));
}
} }

View file

@ -1,9 +1,9 @@
use ra_syntax::{ use ra_syntax::{
ast::{self, AstNode}, ast::{self, AstNode},
TextSize, T, TextRange, TextSize, T,
}; };
use crate::{AssistContext, AssistId, Assists}; use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: remove_dbg // Assist: remove_dbg
// //
@ -27,19 +27,33 @@ pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
return None; return None;
} }
let macro_range = macro_call.syntax().text_range(); let is_leaf = macro_call.syntax().next_sibling().is_none();
let macro_content = { let macro_end = if macro_call.semicolon_token().is_some() {
let macro_args = macro_call.token_tree()?.syntax().clone(); macro_call.syntax().text_range().end() - TextSize::of(';')
} else {
macro_call.syntax().text_range().end()
};
let text = macro_args.text(); // macro_range determines what will be deleted and replaced with macro_content
let without_parens = TextSize::of('(')..text.len() - TextSize::of(')'); let macro_range = TextRange::new(macro_call.syntax().text_range().start(), macro_end);
text.slice(without_parens).to_string() let paste_instead_of_dbg = {
let text = macro_call.token_tree()?.syntax().text();
// leafiness determines if we should include the parenthesis or not
let slice_index: TextRange = if is_leaf {
// leaf means - we can extract the contents of the dbg! in text
TextRange::new(TextSize::of('('), text.len() - TextSize::of(')'))
} else {
// not leaf - means we should keep the parens
TextRange::up_to(text.len())
};
text.slice(slice_index).to_string()
}; };
let target = macro_call.syntax().text_range(); let target = macro_call.syntax().text_range();
acc.add(AssistId("remove_dbg"), "Remove dbg!()", target, |builder| { acc.add(AssistId("remove_dbg", AssistKind::Refactor), "Remove dbg!()", target, |builder| {
builder.replace(macro_range, macro_content); builder.replace(macro_range, paste_instead_of_dbg);
}) })
} }
@ -99,6 +113,7 @@ fn foo(n: usize) {
", ",
); );
} }
#[test] #[test]
fn test_remove_dbg_with_brackets_and_braces() { fn test_remove_dbg_with_brackets_and_braces() {
check_assist(remove_dbg, "dbg![<|>1 + 1]", "1 + 1"); check_assist(remove_dbg, "dbg![<|>1 + 1]", "1 + 1");
@ -113,7 +128,7 @@ fn foo(n: usize) {
} }
#[test] #[test]
fn remove_dbg_target() { fn test_remove_dbg_target() {
check_assist_target( check_assist_target(
remove_dbg, remove_dbg,
" "
@ -126,4 +141,65 @@ fn foo(n: usize) {
"dbg!(n.checked_sub(4))", "dbg!(n.checked_sub(4))",
); );
} }
#[test]
fn test_remove_dbg_keep_semicolon() {
// https://github.com/rust-analyzer/rust-analyzer/issues/5129#issuecomment-651399779
// not quite though
// adding a comment at the end of the line makes
// the ast::MacroCall to include the semicolon at the end
check_assist(
remove_dbg,
r#"let res = <|>dbg!(1 * 20); // needless comment"#,
r#"let res = 1 * 20; // needless comment"#,
);
}
#[test]
fn test_remove_dbg_keep_expression() {
check_assist(
remove_dbg,
r#"let res = <|>dbg!(a + b).foo();"#,
r#"let res = (a + b).foo();"#,
);
}
#[test]
fn test_remove_dbg_from_inside_fn() {
check_assist_target(
remove_dbg,
r#"
fn square(x: u32) -> u32 {
x * x
}
fn main() {
let x = square(dbg<|>!(5 + 10));
println!("{}", x);
}"#,
"dbg!(5 + 10)",
);
check_assist(
remove_dbg,
r#"
fn square(x: u32) -> u32 {
x * x
}
fn main() {
let x = square(dbg<|>!(5 + 10));
println!("{}", x);
}"#,
r#"
fn square(x: u32) -> u32 {
x * x
}
fn main() {
let x = square(5 + 10);
println!("{}", x);
}"#,
);
}
} }

View file

@ -1,6 +1,6 @@
use ra_syntax::{SyntaxKind, TextRange, T}; use ra_syntax::{SyntaxKind, TextRange, T};
use crate::{AssistContext, AssistId, Assists}; use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: remove_mut // Assist: remove_mut
// //
@ -26,7 +26,12 @@ pub(crate) fn remove_mut(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
}; };
let target = mut_token.text_range(); let target = mut_token.text_range();
acc.add(AssistId("remove_mut"), "Remove `mut` keyword", target, |builder| { acc.add(
AssistId("remove_mut", AssistKind::Refactor),
"Remove `mut` keyword",
target,
|builder| {
builder.delete(TextRange::new(delete_from, delete_to)); builder.delete(TextRange::new(delete_from, delete_to));
}) },
)
} }

View file

@ -5,7 +5,7 @@ use hir::{Adt, ModuleDef, PathResolution, Semantics, Struct};
use ra_ide_db::RootDatabase; use ra_ide_db::RootDatabase;
use ra_syntax::{algo, ast, match_ast, AstNode, SyntaxKind, SyntaxKind::*, SyntaxNode}; use ra_syntax::{algo, ast, match_ast, AstNode, SyntaxKind, SyntaxKind::*, SyntaxNode};
use crate::{AssistContext, AssistId, Assists}; use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: reorder_fields // Assist: reorder_fields
// //
@ -23,7 +23,7 @@ use crate::{AssistContext, AssistId, Assists};
// ``` // ```
// //
pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
reorder::<ast::RecordLit>(acc, ctx).or_else(|| reorder::<ast::RecordPat>(acc, ctx)) reorder::<ast::RecordExpr>(acc, ctx).or_else(|| reorder::<ast::RecordPat>(acc, ctx))
} }
fn reorder<R: AstNode>(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { fn reorder<R: AstNode>(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
@ -42,16 +42,21 @@ fn reorder<R: AstNode>(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
} }
let target = record.syntax().text_range(); let target = record.syntax().text_range();
acc.add(AssistId("reorder_fields"), "Reorder record fields", target, |edit| { acc.add(
AssistId("reorder_fields", AssistKind::RefactorRewrite),
"Reorder record fields",
target,
|edit| {
for (old, new) in fields.iter().zip(&sorted_fields) { for (old, new) in fields.iter().zip(&sorted_fields) {
algo::diff(old, new).into_text_edit(edit.text_edit_builder()); algo::diff(old, new).into_text_edit(edit.text_edit_builder());
} }
}) },
)
} }
fn get_fields_kind(node: &SyntaxNode) -> Vec<SyntaxKind> { fn get_fields_kind(node: &SyntaxNode) -> Vec<SyntaxKind> {
match node.kind() { match node.kind() {
RECORD_LIT => vec![RECORD_FIELD], RECORD_EXPR => vec![RECORD_EXPR_FIELD],
RECORD_PAT => vec![RECORD_FIELD_PAT, BIND_PAT], RECORD_PAT => vec![RECORD_FIELD_PAT, BIND_PAT],
_ => vec![], _ => vec![],
} }
@ -60,7 +65,7 @@ fn get_fields_kind(node: &SyntaxNode) -> Vec<SyntaxKind> {
fn get_field_name(node: &SyntaxNode) -> String { fn get_field_name(node: &SyntaxNode) -> String {
let res = match_ast! { let res = match_ast! {
match node { match node {
ast::RecordField(field) => field.field_name().map(|it| it.to_string()), ast::RecordExprField(field) => field.field_name().map(|it| it.to_string()),
ast::RecordFieldPat(field) => field.field_name().map(|it| it.to_string()), ast::RecordFieldPat(field) => field.field_name().map(|it| it.to_string()),
_ => None, _ => None,
} }
@ -90,10 +95,10 @@ fn struct_definition(path: &ast::Path, sema: &Semantics<RootDatabase>) -> Option
fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> { fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> {
Some( Some(
struct_definition(path, &ctx.sema)? struct_definition(path, &ctx.sema)?
.fields(ctx.db) .fields(ctx.db())
.iter() .iter()
.enumerate() .enumerate()
.map(|(idx, field)| (field.name(ctx.db).to_string(), idx)) .map(|(idx, field)| (field.name(ctx.db()).to_string(), idx))
.collect(), .collect(),
) )
} }

View file

@ -8,7 +8,7 @@ use ra_syntax::{
AstNode, AstNode,
}; };
use crate::{utils::TryEnum, AssistContext, AssistId, Assists}; use crate::{utils::TryEnum, AssistContext, AssistId, AssistKind, Assists};
// Assist: replace_if_let_with_match // Assist: replace_if_let_with_match
// //
@ -48,7 +48,11 @@ pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext)
}; };
let target = if_expr.syntax().text_range(); let target = if_expr.syntax().text_range();
acc.add(AssistId("replace_if_let_with_match"), "Replace with match", target, move |edit| { acc.add(
AssistId("replace_if_let_with_match", AssistKind::RefactorRewrite),
"Replace with match",
target,
move |edit| {
let match_expr = { let match_expr = {
let then_arm = { let then_arm = {
let then_block = then_block.reset_indent().indent(IndentLevel(1)); let then_block = then_block.reset_indent().indent(IndentLevel(1));
@ -65,12 +69,14 @@ pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext)
let else_expr = unwrap_trivial_block(else_block); let else_expr = unwrap_trivial_block(else_block);
make::match_arm(vec![pattern], else_expr) make::match_arm(vec![pattern], else_expr)
}; };
let match_expr = make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm])); let match_expr =
make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm]));
match_expr.indent(IndentLevel::from_node(if_expr.syntax())) match_expr.indent(IndentLevel::from_node(if_expr.syntax()))
}; };
edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr); edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr);
}) },
)
} }
#[cfg(test)] #[cfg(test)]

View file

@ -9,7 +9,7 @@ use ra_syntax::{
AstNode, T, AstNode, T,
}; };
use crate::{utils::TryEnum, AssistContext, AssistId, Assists}; use crate::{utils::TryEnum, AssistContext, AssistId, AssistKind, Assists};
// Assist: replace_let_with_if_let // Assist: replace_let_with_if_let
// //
@ -44,7 +44,11 @@ pub(crate) fn replace_let_with_if_let(acc: &mut Assists, ctx: &AssistContext) ->
let happy_variant = TryEnum::from_ty(&ctx.sema, &ty).map(|it| it.happy_case()); let happy_variant = TryEnum::from_ty(&ctx.sema, &ty).map(|it| it.happy_case());
let target = let_kw.text_range(); let target = let_kw.text_range();
acc.add(AssistId("replace_let_with_if_let"), "Replace with if-let", target, |edit| { acc.add(
AssistId("replace_let_with_if_let", AssistKind::RefactorRewrite),
"Replace with if-let",
target,
|edit| {
let with_placeholder: ast::Pat = match happy_variant { let with_placeholder: ast::Pat = match happy_variant {
None => make::placeholder_pat().into(), None => make::placeholder_pat().into(),
Some(var_name) => make::tuple_struct_pat( Some(var_name) => make::tuple_struct_pat(
@ -53,15 +57,18 @@ pub(crate) fn replace_let_with_if_let(acc: &mut Assists, ctx: &AssistContext) ->
) )
.into(), .into(),
}; };
let block = make::block_expr(None, None).indent(IndentLevel::from_node(let_stmt.syntax())); let block =
make::block_expr(None, None).indent(IndentLevel::from_node(let_stmt.syntax()));
let if_ = make::expr_if(make::condition(init, Some(with_placeholder)), block); let if_ = make::expr_if(make::condition(init, Some(with_placeholder)), block);
let stmt = make::expr_stmt(if_); let stmt = make::expr_stmt(if_);
let placeholder = stmt.syntax().descendants().find_map(ast::PlaceholderPat::cast).unwrap(); let placeholder =
stmt.syntax().descendants().find_map(ast::PlaceholderPat::cast).unwrap();
let stmt = stmt.replace_descendant(placeholder.into(), original_pat); let stmt = stmt.replace_descendant(placeholder.into(), original_pat);
edit.replace_ast(ast::Stmt::from(let_stmt), ast::Stmt::from(stmt)); edit.replace_ast(ast::Stmt::from(let_stmt), ast::Stmt::from(stmt));
}) },
)
} }
#[cfg(test)] #[cfg(test)]

View file

@ -3,7 +3,7 @@ use ra_syntax::{algo::SyntaxRewriter, ast, match_ast, AstNode, SmolStr, SyntaxNo
use crate::{ use crate::{
utils::{find_insert_use_container, insert_use_statement}, utils::{find_insert_use_container, insert_use_statement},
AssistContext, AssistId, Assists, AssistContext, AssistId, AssistKind, Assists,
}; };
// Assist: replace_qualified_name_with_use // Assist: replace_qualified_name_with_use
@ -25,7 +25,7 @@ pub(crate) fn replace_qualified_name_with_use(
) -> Option<()> { ) -> Option<()> {
let path: ast::Path = ctx.find_node_at_offset()?; let path: ast::Path = ctx.find_node_at_offset()?;
// We don't want to mess with use statements // We don't want to mess with use statements
if path.syntax().ancestors().find_map(ast::UseItem::cast).is_some() { if path.syntax().ancestors().find_map(ast::Use::cast).is_some() {
return None; return None;
} }
@ -37,7 +37,7 @@ pub(crate) fn replace_qualified_name_with_use(
let target = path.syntax().text_range(); let target = path.syntax().text_range();
acc.add( acc.add(
AssistId("replace_qualified_name_with_use"), AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite),
"Replace qualified path with use", "Replace qualified path with use",
target, target,
|builder| { |builder| {
@ -85,7 +85,7 @@ fn shorten_paths(rewriter: &mut SyntaxRewriter<'static>, node: SyntaxNode, path:
match child { match child {
// Don't modify `use` items, as this can break the `use` item when injecting a new // Don't modify `use` items, as this can break the `use` item when injecting a new
// import into the use tree. // import into the use tree.
ast::UseItem(_it) => continue, ast::Use(_it) => continue,
// Don't descend into submodules, they don't have the same `use` items in scope. // Don't descend into submodules, they don't have the same `use` items in scope.
ast::Module(_it) => continue, ast::Module(_it) => continue,
@ -106,7 +106,7 @@ fn maybe_replace_path(
path: ast::Path, path: ast::Path,
target: ast::Path, target: ast::Path,
) -> Option<()> { ) -> Option<()> {
if !path_eq(path.clone(), target.clone()) { if !path_eq(path.clone(), target) {
return None; return None;
} }

View file

@ -11,7 +11,7 @@ use ra_syntax::{
use crate::{ use crate::{
utils::{render_snippet, Cursor, TryEnum}, utils::{render_snippet, Cursor, TryEnum},
AssistContext, AssistId, Assists, AssistContext, AssistId, AssistKind, Assists,
}; };
// Assist: replace_unwrap_with_match // Assist: replace_unwrap_with_match
@ -46,7 +46,11 @@ pub(crate) fn replace_unwrap_with_match(acc: &mut Assists, ctx: &AssistContext)
let ty = ctx.sema.type_of_expr(&caller)?; let ty = ctx.sema.type_of_expr(&caller)?;
let happy_variant = TryEnum::from_ty(&ctx.sema, &ty)?.happy_case(); let happy_variant = TryEnum::from_ty(&ctx.sema, &ty)?.happy_case();
let target = method_call.syntax().text_range(); let target = method_call.syntax().text_range();
acc.add(AssistId("replace_unwrap_with_match"), "Replace unwrap with match", target, |builder| { acc.add(
AssistId("replace_unwrap_with_match", AssistKind::RefactorRewrite),
"Replace unwrap with match",
target,
|builder| {
let ok_path = make::path_unqualified(make::path_segment(make::name_ref(happy_variant))); let ok_path = make::path_unqualified(make::path_segment(make::name_ref(happy_variant)));
let it = make::bind_pat(make::name("a")).into(); let it = make::bind_pat(make::name("a")).into();
let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into(); let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into();
@ -55,7 +59,8 @@ pub(crate) fn replace_unwrap_with_match(acc: &mut Assists, ctx: &AssistContext)
let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path)); let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path));
let unreachable_call = make::expr_unreachable(); let unreachable_call = make::expr_unreachable();
let err_arm = make::match_arm(iter::once(make::placeholder_pat().into()), unreachable_call); let err_arm =
make::match_arm(iter::once(make::placeholder_pat().into()), unreachable_call);
let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]); let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]);
let match_expr = make::expr_match(caller.clone(), match_arm_list) let match_expr = make::expr_match(caller.clone(), match_arm_list)
@ -76,7 +81,8 @@ pub(crate) fn replace_unwrap_with_match(acc: &mut Assists, ctx: &AssistContext)
} }
None => builder.replace(range, match_expr.to_string()), None => builder.replace(range, match_expr.to_string()),
} }
}) },
)
} }
#[cfg(test)] #[cfg(test)]

View file

@ -2,7 +2,7 @@ use std::iter::successors;
use ra_syntax::{ast, AstNode, T}; use ra_syntax::{ast, AstNode, T};
use crate::{AssistContext, AssistId, Assists}; use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: split_import // Assist: split_import
// //
@ -28,7 +28,7 @@ pub(crate) fn split_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
} }
let target = colon_colon.text_range(); let target = colon_colon.text_range();
acc.add(AssistId("split_import"), "Split import", target, |edit| { acc.add(AssistId("split_import", AssistKind::RefactorRewrite), "Split import", target, |edit| {
edit.replace_ast(use_tree, new_tree); edit.replace_ast(use_tree, new_tree);
}) })
} }

View file

@ -7,7 +7,7 @@ use ra_syntax::{
AstNode, TextRange, T, AstNode, TextRange, T,
}; };
use crate::{AssistContext, AssistId, Assists}; use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: unwrap_block // Assist: unwrap_block
// //
@ -27,7 +27,7 @@ use crate::{AssistContext, AssistId, Assists};
// } // }
// ``` // ```
pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let assist_id = AssistId("unwrap_block"); let assist_id = AssistId("unwrap_block", AssistKind::RefactorRewrite);
let assist_label = "Unwrap block"; let assist_label = "Unwrap block";
let l_curly_token = ctx.find_token_at_offset(T!['{'])?; let l_curly_token = ctx.find_token_at_offset(T!['{'])?;

View file

@ -26,10 +26,40 @@ pub(crate) use crate::assist_context::{AssistContext, Assists};
pub use assist_config::AssistConfig; pub use assist_config::AssistConfig;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AssistKind {
None,
QuickFix,
Generate,
Refactor,
RefactorExtract,
RefactorInline,
RefactorRewrite,
}
impl AssistKind {
pub fn contains(self, other: AssistKind) -> bool {
if self == other {
return true;
}
match self {
AssistKind::None | AssistKind::Generate => return true,
AssistKind::Refactor => match other {
AssistKind::RefactorExtract
| AssistKind::RefactorInline
| AssistKind::RefactorRewrite => return true,
_ => return false,
},
_ => return false,
}
}
}
/// Unique identifier of the assist, should not be shown to the user /// Unique identifier of the assist, should not be shown to the user
/// directly. /// directly.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AssistId(pub &'static str); pub struct AssistId(pub &'static str, pub AssistKind);
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct GroupLabel(pub String); pub struct GroupLabel(pub String);
@ -102,13 +132,8 @@ mod handlers {
pub(crate) type Handler = fn(&mut Assists, &AssistContext) -> Option<()>; pub(crate) type Handler = fn(&mut Assists, &AssistContext) -> Option<()>;
mod add_custom_impl; mod add_custom_impl;
mod add_derive;
mod add_explicit_type; mod add_explicit_type;
mod add_from_impl_for_enum;
mod add_function;
mod add_impl;
mod add_missing_impl_members; mod add_missing_impl_members;
mod add_new;
mod add_turbo_fish; mod add_turbo_fish;
mod apply_demorgan; mod apply_demorgan;
mod auto_import; mod auto_import;
@ -122,6 +147,11 @@ mod handlers {
mod flip_binexpr; mod flip_binexpr;
mod flip_comma; mod flip_comma;
mod flip_trait_bound; mod flip_trait_bound;
mod generate_derive;
mod generate_from_impl_for_enum;
mod generate_function;
mod generate_impl;
mod generate_new;
mod inline_local_variable; mod inline_local_variable;
mod introduce_named_lifetime; mod introduce_named_lifetime;
mod invert_if; mod invert_if;
@ -144,12 +174,7 @@ mod handlers {
&[ &[
// These are alphabetic for the foolish consistency // These are alphabetic for the foolish consistency
add_custom_impl::add_custom_impl, add_custom_impl::add_custom_impl,
add_derive::add_derive,
add_explicit_type::add_explicit_type, add_explicit_type::add_explicit_type,
add_from_impl_for_enum::add_from_impl_for_enum,
add_function::add_function,
add_impl::add_impl,
add_new::add_new,
add_turbo_fish::add_turbo_fish, add_turbo_fish::add_turbo_fish,
apply_demorgan::apply_demorgan, apply_demorgan::apply_demorgan,
auto_import::auto_import, auto_import::auto_import,
@ -163,6 +188,11 @@ mod handlers {
flip_binexpr::flip_binexpr, flip_binexpr::flip_binexpr,
flip_comma::flip_comma, flip_comma::flip_comma,
flip_trait_bound::flip_trait_bound, flip_trait_bound::flip_trait_bound,
generate_derive::generate_derive,
generate_from_impl_for_enum::generate_from_impl_for_enum,
generate_function::generate_function,
generate_impl::generate_impl,
generate_new::generate_new,
inline_local_variable::inline_local_variable, inline_local_variable::inline_local_variable,
introduce_named_lifetime::introduce_named_lifetime, introduce_named_lifetime::introduce_named_lifetime,
invert_if::invert_if, invert_if::invert_if,

View file

@ -6,7 +6,7 @@ use ra_ide_db::RootDatabase;
use ra_syntax::TextRange; use ra_syntax::TextRange;
use test_utils::{assert_eq_text, extract_offset, extract_range}; use test_utils::{assert_eq_text, extract_offset, extract_range};
use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, Assists}; use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, AssistKind, Assists};
use stdx::trim_indent; use stdx::trim_indent;
pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) { pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) {
@ -134,3 +134,46 @@ fn assist_order_if_expr() {
assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable"); assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable");
assert_eq!(assists.next().expect("expected assist").assist.label, "Replace with match"); assert_eq!(assists.next().expect("expected assist").assist.label, "Replace with match");
} }
#[test]
fn assist_filter_works() {
let before = "
pub fn test_some_range(a: int) -> bool {
if let 2..6 = <|>5<|> {
true
} else {
false
}
}";
let (range, before) = extract_range(before);
let (db, file_id) = with_single_file(&before);
let frange = FileRange { file_id, range };
{
let mut cfg = AssistConfig::default();
cfg.allowed = Some(vec![AssistKind::Refactor]);
let assists = Assist::resolved(&db, &cfg, frange);
let mut assists = assists.iter();
assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable");
assert_eq!(assists.next().expect("expected assist").assist.label, "Replace with match");
}
{
let mut cfg = AssistConfig::default();
cfg.allowed = Some(vec![AssistKind::RefactorExtract]);
let assists = Assist::resolved(&db, &cfg, frange);
assert_eq!(assists.len(), 1);
let mut assists = assists.iter();
assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable");
}
{
let mut cfg = AssistConfig::default();
cfg.allowed = Some(vec![AssistKind::QuickFix]);
let assists = Assist::resolved(&db, &cfg, frange);
assert!(assists.is_empty(), "All asserts but quickfixes should be filtered out");
}
}

View file

@ -21,26 +21,6 @@ impl Debug for S {
) )
} }
#[test]
fn doctest_add_derive() {
check_doc_test(
"add_derive",
r#####"
struct Point {
x: u32,
y: u32,<|>
}
"#####,
r#####"
#[derive($0)]
struct Point {
x: u32,
y: u32,
}
"#####,
)
}
#[test] #[test]
fn doctest_add_explicit_type() { fn doctest_add_explicit_type() {
check_doc_test( check_doc_test(
@ -58,52 +38,6 @@ fn main() {
) )
} }
#[test]
fn doctest_add_from_impl_for_enum() {
check_doc_test(
"add_from_impl_for_enum",
r#####"
enum A { <|>One(u32) }
"#####,
r#####"
enum A { One(u32) }
impl From<u32> for A {
fn from(v: u32) -> Self {
A::One(v)
}
}
"#####,
)
}
#[test]
fn doctest_add_function() {
check_doc_test(
"add_function",
r#####"
struct Baz;
fn baz() -> Baz { Baz }
fn foo() {
bar<|>("", baz());
}
"#####,
r#####"
struct Baz;
fn baz() -> Baz { Baz }
fn foo() {
bar("", baz());
}
fn bar(arg: &str, baz: Baz) {
${0:todo!()}
}
"#####,
)
}
#[test] #[test]
fn doctest_add_hash() { fn doctest_add_hash() {
check_doc_test( check_doc_test(
@ -121,27 +55,6 @@ fn main() {
) )
} }
#[test]
fn doctest_add_impl() {
check_doc_test(
"add_impl",
r#####"
struct Ctx<T: Clone> {
data: T,<|>
}
"#####,
r#####"
struct Ctx<T: Clone> {
data: T,
}
impl<T: Clone> Ctx<T> {
$0
}
"#####,
)
}
#[test] #[test]
fn doctest_add_impl_default_members() { fn doctest_add_impl_default_members() {
check_doc_test( check_doc_test(
@ -208,28 +121,6 @@ impl Trait<u32> for () {
) )
} }
#[test]
fn doctest_add_new() {
check_doc_test(
"add_new",
r#####"
struct Ctx<T: Clone> {
data: T,<|>
}
"#####,
r#####"
struct Ctx<T: Clone> {
data: T,
}
impl<T: Clone> Ctx<T> {
fn $0new(data: T) -> Self { Self { data } }
}
"#####,
)
}
#[test] #[test]
fn doctest_add_turbo_fish() { fn doctest_add_turbo_fish() {
check_doc_test( check_doc_test(
@ -466,6 +357,115 @@ fn foo<T: Copy + Clone>() { }
) )
} }
#[test]
fn doctest_generate_derive() {
check_doc_test(
"generate_derive",
r#####"
struct Point {
x: u32,
y: u32,<|>
}
"#####,
r#####"
#[derive($0)]
struct Point {
x: u32,
y: u32,
}
"#####,
)
}
#[test]
fn doctest_generate_from_impl_for_enum() {
check_doc_test(
"generate_from_impl_for_enum",
r#####"
enum A { <|>One(u32) }
"#####,
r#####"
enum A { One(u32) }
impl From<u32> for A {
fn from(v: u32) -> Self {
A::One(v)
}
}
"#####,
)
}
#[test]
fn doctest_generate_function() {
check_doc_test(
"generate_function",
r#####"
struct Baz;
fn baz() -> Baz { Baz }
fn foo() {
bar<|>("", baz());
}
"#####,
r#####"
struct Baz;
fn baz() -> Baz { Baz }
fn foo() {
bar("", baz());
}
fn bar(arg: &str, baz: Baz) {
${0:todo!()}
}
"#####,
)
}
#[test]
fn doctest_generate_impl() {
check_doc_test(
"generate_impl",
r#####"
struct Ctx<T: Clone> {
data: T,<|>
}
"#####,
r#####"
struct Ctx<T: Clone> {
data: T,
}
impl<T: Clone> Ctx<T> {
$0
}
"#####,
)
}
#[test]
fn doctest_generate_new() {
check_doc_test(
"generate_new",
r#####"
struct Ctx<T: Clone> {
data: T,<|>
}
"#####,
r#####"
struct Ctx<T: Clone> {
data: T,
}
impl<T: Clone> Ctx<T> {
fn $0new(data: T) -> Self { Self { data } }
}
"#####,
)
}
#[test] #[test]
fn doctest_inline_local_variable() { fn doctest_inline_local_variable() {
check_doc_test( check_doc_test(

View file

@ -56,33 +56,34 @@ pub(crate) fn render_snippet(_cap: SnippetCap, node: &SyntaxNode, cursor: Cursor
pub fn get_missing_assoc_items( pub fn get_missing_assoc_items(
sema: &Semantics<RootDatabase>, sema: &Semantics<RootDatabase>,
impl_def: &ast::ImplDef, impl_def: &ast::Impl,
) -> Vec<hir::AssocItem> { ) -> Vec<hir::AssocItem> {
// Names must be unique between constants and functions. However, type aliases // Names must be unique between constants and functions. However, type aliases
// may share the same name as a function or constant. // may share the same name as a function or constant.
let mut impl_fns_consts = FxHashSet::default(); let mut impl_fns_consts = FxHashSet::default();
let mut impl_type = FxHashSet::default(); let mut impl_type = FxHashSet::default();
if let Some(item_list) = impl_def.item_list() { if let Some(item_list) = impl_def.assoc_item_list() {
for item in item_list.assoc_items() { for item in item_list.assoc_items() {
match item { match item {
ast::AssocItem::FnDef(f) => { ast::AssocItem::Fn(f) => {
if let Some(n) = f.name() { if let Some(n) = f.name() {
impl_fns_consts.insert(n.syntax().to_string()); impl_fns_consts.insert(n.syntax().to_string());
} }
} }
ast::AssocItem::TypeAliasDef(t) => { ast::AssocItem::TypeAlias(t) => {
if let Some(n) = t.name() { if let Some(n) = t.name() {
impl_type.insert(n.syntax().to_string()); impl_type.insert(n.syntax().to_string());
} }
} }
ast::AssocItem::ConstDef(c) => { ast::AssocItem::Const(c) => {
if let Some(n) = c.name() { if let Some(n) = c.name() {
impl_fns_consts.insert(n.syntax().to_string()); impl_fns_consts.insert(n.syntax().to_string());
} }
} }
ast::AssocItem::MacroCall(_) => (),
} }
} }
} }
@ -108,7 +109,7 @@ pub fn get_missing_assoc_items(
pub(crate) fn resolve_target_trait( pub(crate) fn resolve_target_trait(
sema: &Semantics<RootDatabase>, sema: &Semantics<RootDatabase>,
impl_def: &ast::ImplDef, impl_def: &ast::Impl,
) -> Option<hir::Trait> { ) -> Option<hir::Trait> {
let ast_path = impl_def let ast_path = impl_def
.target_trait() .target_trait()

View file

@ -215,7 +215,7 @@ fn walk_use_tree_for_best_action(
let prev_len = current_path_segments.len(); let prev_len = current_path_segments.len();
let tree_list = current_use_tree.use_tree_list(); let tree_list = current_use_tree.use_tree_list();
let alias = current_use_tree.alias(); let alias = current_use_tree.rename();
let path = match current_use_tree.path() { let path = match current_use_tree.path() {
Some(path) => path, Some(path) => path,
@ -225,7 +225,7 @@ fn walk_use_tree_for_best_action(
current_use_tree current_use_tree
.syntax() .syntax()
.ancestors() .ancestors()
.find_map(ast::UseItem::cast) .find_map(ast::Use::cast)
.map(|it| it.syntax().clone()), .map(|it| it.syntax().clone()),
true, true,
); );
@ -254,7 +254,7 @@ fn walk_use_tree_for_best_action(
current_use_tree current_use_tree
.syntax() .syntax()
.ancestors() .ancestors()
.find_map(ast::UseItem::cast) .find_map(ast::Use::cast)
.map(|it| it.syntax().clone()), .map(|it| it.syntax().clone()),
true, true,
), ),
@ -304,7 +304,7 @@ fn walk_use_tree_for_best_action(
current_use_tree current_use_tree
.syntax() .syntax()
.ancestors() .ancestors()
.find_map(ast::UseItem::cast) .find_map(ast::Use::cast)
.map(|it| it.syntax().clone()), .map(|it| it.syntax().clone()),
true, true,
); );
@ -377,7 +377,7 @@ fn best_action_for_target(
let mut storage = Vec::with_capacity(16); // this should be the only allocation let mut storage = Vec::with_capacity(16); // this should be the only allocation
let best_action = container let best_action = container
.children() .children()
.filter_map(ast::UseItem::cast) .filter_map(ast::Use::cast)
.filter_map(|it| it.use_tree()) .filter_map(|it| it.use_tree())
.map(|u| walk_use_tree_for_best_action(&mut storage, None, u, target)) .map(|u| walk_use_tree_for_best_action(&mut storage, None, u, target))
.fold(None, |best, a| match best { .fold(None, |best, a| match best {

View file

@ -3,6 +3,7 @@ edition = "2018"
name = "ra_cfg" name = "ra_cfg"
version = "0.1.0" version = "0.1.0"
authors = ["rust-analyzer developers"] authors = ["rust-analyzer developers"]
license = "MIT OR Apache-2.0"
[lib] [lib]
doctest = false doctest = false

View file

@ -5,7 +5,6 @@
use std::slice::Iter as SliceIter; use std::slice::Iter as SliceIter;
use ra_syntax::SmolStr; use ra_syntax::SmolStr;
use tt::{Leaf, Subtree, TokenTree};
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum CfgExpr { pub enum CfgExpr {
@ -18,6 +17,9 @@ pub enum CfgExpr {
} }
impl CfgExpr { impl CfgExpr {
pub fn parse(tt: &tt::Subtree) -> CfgExpr {
next_cfg_expr(&mut tt.token_trees.iter()).unwrap_or(CfgExpr::Invalid)
}
/// Fold the cfg by querying all basic `Atom` and `KeyValue` predicates. /// Fold the cfg by querying all basic `Atom` and `KeyValue` predicates.
pub fn fold(&self, query: &dyn Fn(&SmolStr, Option<&SmolStr>) -> bool) -> Option<bool> { pub fn fold(&self, query: &dyn Fn(&SmolStr, Option<&SmolStr>) -> bool) -> Option<bool> {
match self { match self {
@ -35,22 +37,18 @@ impl CfgExpr {
} }
} }
pub fn parse_cfg(tt: &Subtree) -> CfgExpr {
next_cfg_expr(&mut tt.token_trees.iter()).unwrap_or(CfgExpr::Invalid)
}
fn next_cfg_expr(it: &mut SliceIter<tt::TokenTree>) -> Option<CfgExpr> { fn next_cfg_expr(it: &mut SliceIter<tt::TokenTree>) -> Option<CfgExpr> {
let name = match it.next() { let name = match it.next() {
None => return None, None => return None,
Some(TokenTree::Leaf(Leaf::Ident(ident))) => ident.text.clone(), Some(tt::TokenTree::Leaf(tt::Leaf::Ident(ident))) => ident.text.clone(),
Some(_) => return Some(CfgExpr::Invalid), Some(_) => return Some(CfgExpr::Invalid),
}; };
// Peek // Peek
let ret = match it.as_slice().first() { let ret = match it.as_slice().first() {
Some(TokenTree::Leaf(Leaf::Punct(punct))) if punct.char == '=' => { Some(tt::TokenTree::Leaf(tt::Leaf::Punct(punct))) if punct.char == '=' => {
match it.as_slice().get(1) { match it.as_slice().get(1) {
Some(TokenTree::Leaf(Leaf::Literal(literal))) => { Some(tt::TokenTree::Leaf(tt::Leaf::Literal(literal))) => {
it.next(); it.next();
it.next(); it.next();
// FIXME: escape? raw string? // FIXME: escape? raw string?
@ -61,7 +59,7 @@ fn next_cfg_expr(it: &mut SliceIter<tt::TokenTree>) -> Option<CfgExpr> {
_ => return Some(CfgExpr::Invalid), _ => return Some(CfgExpr::Invalid),
} }
} }
Some(TokenTree::Subtree(subtree)) => { Some(tt::TokenTree::Subtree(subtree)) => {
it.next(); it.next();
let mut sub_it = subtree.token_trees.iter(); let mut sub_it = subtree.token_trees.iter();
let mut subs = std::iter::from_fn(|| next_cfg_expr(&mut sub_it)).collect(); let mut subs = std::iter::from_fn(|| next_cfg_expr(&mut sub_it)).collect();
@ -76,7 +74,7 @@ fn next_cfg_expr(it: &mut SliceIter<tt::TokenTree>) -> Option<CfgExpr> {
}; };
// Eat comma separator // Eat comma separator
if let Some(TokenTree::Leaf(Leaf::Punct(punct))) = it.as_slice().first() { if let Some(tt::TokenTree::Leaf(tt::Leaf::Punct(punct))) = it.as_slice().first() {
if punct.char == ',' { if punct.char == ',' {
it.next(); it.next();
} }
@ -99,7 +97,8 @@ mod tests {
fn assert_parse_result(input: &str, expected: CfgExpr) { fn assert_parse_result(input: &str, expected: CfgExpr) {
let (tt, _) = get_token_tree_generated(input); let (tt, _) = get_token_tree_generated(input);
assert_eq!(parse_cfg(&tt), expected); let cfg = CfgExpr::parse(&tt);
assert_eq!(cfg, expected);
} }
#[test] #[test]

View file

@ -5,7 +5,7 @@ mod cfg_expr;
use ra_syntax::SmolStr; use ra_syntax::SmolStr;
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
pub use cfg_expr::{parse_cfg, CfgExpr}; pub use cfg_expr::CfgExpr;
/// Configuration options used for conditional compilition on items with `cfg` attributes. /// Configuration options used for conditional compilition on items with `cfg` attributes.
/// We have two kind of options in different namespaces: atomic options like `unix`, and /// We have two kind of options in different namespaces: atomic options like `unix`, and
@ -31,19 +31,21 @@ impl CfgOptions {
}) })
} }
pub fn is_cfg_enabled(&self, attr: &tt::Subtree) -> Option<bool> {
self.check(&parse_cfg(attr))
}
pub fn insert_atom(&mut self, key: SmolStr) { pub fn insert_atom(&mut self, key: SmolStr) {
self.atoms.insert(key); self.atoms.insert(key);
} }
pub fn remove_atom(&mut self, name: &str) {
self.atoms.remove(name);
}
pub fn insert_key_value(&mut self, key: SmolStr, value: SmolStr) { pub fn insert_key_value(&mut self, key: SmolStr, value: SmolStr) {
self.key_values.insert((key, value)); self.key_values.insert((key, value));
} }
pub fn append(&mut self, other: &CfgOptions) {
for atom in &other.atoms {
self.atoms.insert(atom.clone());
}
for (key, value) in &other.key_values {
self.key_values.insert((key.clone(), value.clone()));
}
}
} }

View file

@ -3,13 +3,13 @@ edition = "2018"
name = "ra_db" name = "ra_db"
version = "0.1.0" version = "0.1.0"
authors = ["rust-analyzer developers"] authors = ["rust-analyzer developers"]
license = "MIT OR Apache-2.0"
[lib] [lib]
doctest = false doctest = false
[dependencies] [dependencies]
salsa = "0.14.1" salsa = "0.15.0"
relative-path = "1.0.0"
rustc-hash = "1.1.0" rustc-hash = "1.1.0"
ra_syntax = { path = "../ra_syntax" } ra_syntax = { path = "../ra_syntax" }

View file

@ -149,15 +149,17 @@ fn with_files(
let crate_id = crate_graph.add_crate_root( let crate_id = crate_graph.add_crate_root(
file_id, file_id,
meta.edition, meta.edition,
Some(CrateName::new(&krate).unwrap()), Some(krate.clone()),
meta.cfg, meta.cfg,
meta.env, meta.env,
Default::default(), Default::default(),
); );
let prev = crates.insert(krate.clone(), crate_id); let crate_name = CrateName::new(&krate).unwrap();
let prev = crates.insert(crate_name.clone(), crate_id);
assert!(prev.is_none()); assert!(prev.is_none());
for dep in meta.deps { for dep in meta.deps {
crate_deps.push((krate.clone(), dep)) let dep = CrateName::new(&dep).unwrap();
crate_deps.push((crate_name.clone(), dep))
} }
} else if meta.path == "/main.rs" || meta.path == "/lib.rs" { } else if meta.path == "/main.rs" || meta.path == "/lib.rs" {
assert!(default_crate_root.is_none()); assert!(default_crate_root.is_none());
@ -220,7 +222,7 @@ impl From<Fixture> for FileMeta {
.edition .edition
.as_ref() .as_ref()
.map_or(Edition::Edition2018, |v| Edition::from_str(&v).unwrap()), .map_or(Edition::Edition2018, |v| Edition::from_str(&v).unwrap()),
env: Env::from(f.env.iter()), env: f.env.into_iter().collect(),
} }
} }
} }

View file

@ -6,7 +6,7 @@
//! actual IO. See `vfs` and `project_model` in the `rust-analyzer` crate for how //! actual IO. See `vfs` and `project_model` in the `rust-analyzer` crate for how
//! actual IO is done and lowered to input. //! actual IO is done and lowered to input.
use std::{fmt, ops, str::FromStr, sync::Arc}; use std::{fmt, iter::FromIterator, ops, str::FromStr, sync::Arc};
use ra_cfg::CfgOptions; use ra_cfg::CfgOptions;
use ra_syntax::SmolStr; use ra_syntax::SmolStr;
@ -67,7 +67,7 @@ pub struct CrateGraph {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CrateId(pub u32); pub struct CrateId(pub u32);
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CrateName(SmolStr); pub struct CrateName(SmolStr);
impl CrateName { impl CrateName {
@ -94,6 +94,13 @@ impl fmt::Display for CrateName {
} }
} }
impl ops::Deref for CrateName {
type Target = str;
fn deref(&self) -> &Self::Target {
&*self.0
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct ProcMacroId(pub u32); pub struct ProcMacroId(pub u32);
@ -117,7 +124,7 @@ pub struct CrateData {
/// The name to display to the end user. /// The name to display to the end user.
/// This actual crate name can be different in a particular dependent crate /// This actual crate name can be different in a particular dependent crate
/// or may even be missing for some cases, such as a dummy crate for the code snippet. /// or may even be missing for some cases, such as a dummy crate for the code snippet.
pub display_name: Option<CrateName>, pub display_name: Option<String>,
pub cfg_options: CfgOptions, pub cfg_options: CfgOptions,
pub env: Env, pub env: Env,
pub dependencies: Vec<Dependency>, pub dependencies: Vec<Dependency>,
@ -138,7 +145,7 @@ pub struct Env {
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct Dependency { pub struct Dependency {
pub crate_id: CrateId, pub crate_id: CrateId,
pub name: SmolStr, pub name: CrateName,
} }
impl CrateGraph { impl CrateGraph {
@ -146,7 +153,7 @@ impl CrateGraph {
&mut self, &mut self,
file_id: FileId, file_id: FileId,
edition: Edition, edition: Edition,
display_name: Option<CrateName>, display_name: Option<String>,
cfg_options: CfgOptions, cfg_options: CfgOptions,
env: Env, env: Env,
proc_macro: Vec<(SmolStr, Arc<dyn ra_tt::TokenExpander>)>, proc_macro: Vec<(SmolStr, Arc<dyn ra_tt::TokenExpander>)>,
@ -178,7 +185,7 @@ impl CrateGraph {
if self.dfs_find(from, to, &mut FxHashSet::default()) { if self.dfs_find(from, to, &mut FxHashSet::default()) {
return Err(CyclicDependenciesError); return Err(CyclicDependenciesError);
} }
self.arena.get_mut(&from).unwrap().add_dep(name.0, to); self.arena.get_mut(&from).unwrap().add_dep(name, to);
Ok(()) Ok(())
} }
@ -190,6 +197,23 @@ impl CrateGraph {
self.arena.keys().copied() self.arena.keys().copied()
} }
/// Returns an iterator over all transitive dependencies of the given crate.
pub fn transitive_deps(&self, of: CrateId) -> impl Iterator<Item = CrateId> + '_ {
let mut worklist = vec![of];
let mut deps = FxHashSet::default();
while let Some(krate) = worklist.pop() {
if !deps.insert(krate) {
continue;
}
worklist.extend(self[krate].dependencies.iter().map(|dep| dep.crate_id));
}
deps.remove(&of);
deps.into_iter()
}
// FIXME: this only finds one crate with the given root; we could have multiple // 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> { pub fn crate_id_for_crate_root(&self, file_id: FileId) -> Option<CrateId> {
let (&crate_id, _) = let (&crate_id, _) =
@ -247,7 +271,7 @@ impl CrateId {
} }
impl CrateData { impl CrateData {
fn add_dep(&mut self, name: SmolStr, crate_id: CrateId) { fn add_dep(&mut self, name: CrateName, crate_id: CrateId) {
self.dependencies.push(Dependency { name, crate_id }) self.dependencies.push(Dependency { name, crate_id })
} }
} }
@ -274,18 +298,9 @@ impl fmt::Display for Edition {
} }
} }
impl<'a, T> From<T> for Env impl FromIterator<(String, String)> for Env {
where fn from_iter<T: IntoIterator<Item = (String, String)>>(iter: T) -> Self {
T: Iterator<Item = (&'a String, &'a String)>, Env { entries: FromIterator::from_iter(iter) }
{
fn from(iter: T) -> Self {
let mut result = Self::default();
for (k, v) in iter {
result.entries.insert(k.to_owned(), v.to_owned());
}
result
} }
} }
@ -429,7 +444,10 @@ mod tests {
.is_ok()); .is_ok());
assert_eq!( assert_eq!(
graph[crate1].dependencies, graph[crate1].dependencies,
vec![Dependency { crate_id: crate2, name: "crate_name_with_dashes".into() }] vec![Dependency {
crate_id: crate2,
name: CrateName::new("crate_name_with_dashes").unwrap()
}]
); );
} }
} }

View file

@ -16,9 +16,8 @@ pub use crate::{
SourceRoot, SourceRootId, SourceRoot, SourceRootId,
}, },
}; };
pub use relative_path::{RelativePath, RelativePathBuf};
pub use salsa; pub use salsa;
pub use vfs::{file_set::FileSet, AbsPathBuf, VfsPath}; pub use vfs::{file_set::FileSet, VfsPath};
#[macro_export] #[macro_export]
macro_rules! impl_intern_key { macro_rules! impl_intern_key {
@ -80,7 +79,7 @@ pub struct FilePosition {
pub offset: TextSize, pub offset: TextSize,
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct FileRange { pub struct FileRange {
pub file_id: FileId, pub file_id: FileId,
pub range: TextRange, pub range: TextRange,
@ -93,9 +92,9 @@ pub trait FileLoader {
fn file_text(&self, file_id: FileId) -> Arc<String>; fn file_text(&self, file_id: FileId) -> Arc<String>;
/// Note that we intentionally accept a `&str` and not a `&Path` here. This /// Note that we intentionally accept a `&str` and not a `&Path` here. This
/// method exists to handle `#[path = "/some/path.rs"] mod foo;` and such, /// method exists to handle `#[path = "/some/path.rs"] mod foo;` and such,
/// so the input is guaranteed to be utf-8 string. We might introduce /// so the input is guaranteed to be utf-8 string. One might be tempted to
/// `struct StrPath(str)` for clarity some day, but it's a bit messy, so we /// introduce some kind of "utf-8 path with / separators", but that's a bad idea. Behold
/// get by with a `&str` for the time being. /// `#[path = "C://no/way"]`
fn resolve_path(&self, anchor: FileId, path: &str) -> Option<FileId>; fn resolve_path(&self, anchor: FileId, path: &str) -> Option<FileId>;
fn relevant_crates(&self, file_id: FileId) -> Arc<FxHashSet<CrateId>>; fn relevant_crates(&self, file_id: FileId) -> Arc<FxHashSet<CrateId>>;
} }
@ -113,7 +112,7 @@ pub trait SourceDatabase: CheckCanceled + FileLoader + std::fmt::Debug {
fn crate_graph(&self) -> Arc<CrateGraph>; fn crate_graph(&self) -> Arc<CrateGraph>;
} }
fn parse_query(db: &impl SourceDatabase, file_id: FileId) -> Parse<ast::SourceFile> { fn parse_query(db: &dyn SourceDatabase, file_id: FileId) -> Parse<ast::SourceFile> {
let _p = profile("parse_query").detail(|| format!("{:?}", file_id)); let _p = profile("parse_query").detail(|| format!("{:?}", file_id));
let text = db.file_text(file_id); let text = db.file_text(file_id);
SourceFile::parse(&*text) SourceFile::parse(&*text)
@ -136,10 +135,7 @@ pub trait SourceDatabaseExt: SourceDatabase {
fn source_root_crates(&self, id: SourceRootId) -> Arc<FxHashSet<CrateId>>; fn source_root_crates(&self, id: SourceRootId) -> Arc<FxHashSet<CrateId>>;
} }
fn source_root_crates( fn source_root_crates(db: &dyn SourceDatabaseExt, id: SourceRootId) -> Arc<FxHashSet<CrateId>> {
db: &(impl SourceDatabaseExt + SourceDatabase),
id: SourceRootId,
) -> Arc<FxHashSet<CrateId>> {
let graph = db.crate_graph(); let graph = db.crate_graph();
let res = graph let res = graph
.iter() .iter()

View file

@ -4,6 +4,7 @@ name = "ra_fmt"
version = "0.1.0" version = "0.1.0"
authors = ["rust-analyzer developers"] authors = ["rust-analyzer developers"]
publish = false publish = false
license = "MIT OR Apache-2.0"
[lib] [lib]
doctest = false doctest = false

View file

@ -3,6 +3,7 @@ edition = "2018"
name = "ra_hir" name = "ra_hir"
version = "0.1.0" version = "0.1.0"
authors = ["rust-analyzer developers"] authors = ["rust-analyzer developers"]
license = "MIT OR Apache-2.0"
[lib] [lib]
doctest = false doctest = false
@ -15,6 +16,7 @@ arrayvec = "0.5.1"
itertools = "0.9.0" itertools = "0.9.0"
stdx = { path = "../stdx" }
ra_syntax = { path = "../ra_syntax" } ra_syntax = { path = "../ra_syntax" }
ra_db = { path = "../ra_db" } ra_db = { path = "../ra_db" }
ra_prof = { path = "../ra_prof" } ra_prof = { path = "../ra_prof" }

View file

@ -1,5 +1,5 @@
//! FIXME: write short doc here //! FIXME: write short doc here
use std::sync::Arc; use std::{iter, sync::Arc};
use arrayvec::ArrayVec; use arrayvec::ArrayVec;
use either::Either; use either::Either;
@ -12,6 +12,7 @@ use hir_def::{
import_map, import_map,
per_ns::PerNs, per_ns::PerNs,
resolver::{HasResolver, Resolver}, resolver::{HasResolver, Resolver},
src::HasSource as _,
type_ref::{Mutability, TypeRef}, type_ref::{Mutability, TypeRef},
AdtId, AssocContainerId, ConstId, DefWithBodyId, EnumId, FunctionId, GenericDefId, HasModule, AdtId, AssocContainerId, ConstId, DefWithBodyId, EnumId, FunctionId, GenericDefId, HasModule,
ImplId, LocalEnumVariantId, LocalFieldId, LocalModuleId, Lookup, ModuleId, StaticId, StructId, ImplId, LocalEnumVariantId, LocalFieldId, LocalModuleId, Lookup, ModuleId, StaticId, StructId,
@ -25,21 +26,22 @@ use hir_expand::{
use hir_ty::{ use hir_ty::{
autoderef, autoderef,
display::{HirDisplayError, HirFormatter}, display::{HirDisplayError, HirFormatter},
expr::ExprValidator, method_resolution, ApplicationTy, CallableDefId, Canonical, FnSig, GenericPredicate,
method_resolution, InEnvironment, Substs, TraitEnvironment, Ty, TyDefId, TypeCtor,
unsafe_validation::UnsafeValidator,
ApplicationTy, Canonical, GenericPredicate, InEnvironment, Substs, TraitEnvironment, Ty,
TyDefId, TypeCtor,
}; };
use ra_db::{CrateId, CrateName, Edition, FileId}; use ra_db::{CrateId, Edition, FileId};
use ra_prof::profile; use ra_prof::profile;
use ra_syntax::ast::{self, AttrsOwner, NameOwner}; use ra_syntax::{
ast::{self, AttrsOwner, NameOwner},
AstNode,
};
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use stdx::impl_from;
use crate::{ use crate::{
db::{DefDatabase, HirDatabase}, db::{DefDatabase, HirDatabase},
has_source::HasSource, has_source::HasSource,
CallableDef, HirDisplay, InFile, Name, HirDisplay, InFile, Name,
}; };
/// hir::Crate describes a single crate. It's the main interface with which /// hir::Crate describes a single crate. It's the main interface with which
@ -94,8 +96,8 @@ impl Crate {
db.crate_graph()[self.id].edition db.crate_graph()[self.id].edition
} }
pub fn display_name(self, db: &dyn HirDatabase) -> Option<CrateName> { pub fn display_name(self, db: &dyn HirDatabase) -> Option<String> {
db.crate_graph()[self.id].display_name.as_ref().cloned() db.crate_graph()[self.id].display_name.clone()
} }
pub fn query_external_importables( pub fn query_external_importables(
@ -139,8 +141,8 @@ pub enum ModuleDef {
TypeAlias(TypeAlias), TypeAlias(TypeAlias),
BuiltinType(BuiltinType), BuiltinType(BuiltinType),
} }
impl_froms!( impl_from!(
ModuleDef: Module, Module,
Function, Function,
Adt(Struct, Enum, Union), Adt(Struct, Enum, Union),
EnumVariant, EnumVariant,
@ -149,6 +151,7 @@ impl_froms!(
Trait, Trait,
TypeAlias, TypeAlias,
BuiltinType BuiltinType
for ModuleDef
); );
impl ModuleDef { impl ModuleDef {
@ -376,8 +379,8 @@ pub struct Field {
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum FieldSource { pub enum FieldSource {
Named(ast::RecordFieldDef), Named(ast::RecordField),
Pos(ast::TupleFieldDef), Pos(ast::TupleField),
} }
impl Field { impl Field {
@ -556,7 +559,7 @@ pub enum Adt {
Union(Union), Union(Union),
Enum(Enum), Enum(Enum),
} }
impl_froms!(Adt: Struct, Union, Enum); impl_from!(Struct, Union, Enum for Adt);
impl Adt { impl Adt {
pub fn has_non_default_type_params(self, db: &dyn HirDatabase) -> bool { pub fn has_non_default_type_params(self, db: &dyn HirDatabase) -> bool {
@ -599,7 +602,7 @@ pub enum VariantDef {
Union(Union), Union(Union),
EnumVariant(EnumVariant), EnumVariant(EnumVariant),
} }
impl_froms!(VariantDef: Struct, Union, EnumVariant); impl_from!(Struct, Union, EnumVariant for VariantDef);
impl VariantDef { impl VariantDef {
pub fn fields(self, db: &dyn HirDatabase) -> Vec<Field> { pub fn fields(self, db: &dyn HirDatabase) -> Vec<Field> {
@ -642,8 +645,7 @@ pub enum DefWithBody {
Static(Static), Static(Static),
Const(Const), Const(Const),
} }
impl_from!(Function, Const, Static for DefWithBody);
impl_froms!(DefWithBody: Function, Const, Static);
impl DefWithBody { impl DefWithBody {
pub fn module(self, db: &dyn HirDatabase) -> Module { pub fn module(self, db: &dyn HirDatabase) -> Module {
@ -694,13 +696,7 @@ impl Function {
} }
pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) { pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) {
let _p = profile("Function::diagnostics"); hir_ty::diagnostics::validate_body(db, self.id.into(), sink)
let infer = db.infer(self.id.into());
infer.add_diagnostics(db, self.id, sink);
let mut validator = ExprValidator::new(self.id, infer.clone(), sink);
validator.validate_body(db);
let mut validator = UnsafeValidator::new(self.id, infer, sink);
validator.validate_body(db);
} }
} }
@ -945,14 +941,15 @@ pub enum GenericDef {
// consts can have type parameters from their parents (i.e. associated consts of traits) // consts can have type parameters from their parents (i.e. associated consts of traits)
Const(Const), Const(Const),
} }
impl_froms!( impl_from!(
GenericDef: Function, Function,
Adt(Struct, Enum, Union), Adt(Struct, Enum, Union),
Trait, Trait,
TypeAlias, TypeAlias,
ImplDef, ImplDef,
EnumVariant, EnumVariant,
Const Const
for GenericDef
); );
impl GenericDef { impl GenericDef {
@ -973,6 +970,16 @@ pub struct Local {
} }
impl Local { impl Local {
pub fn is_param(self, db: &dyn HirDatabase) -> bool {
let src = self.source(db);
match src.value {
Either::Left(bind_pat) => {
bind_pat.syntax().ancestors().any(|it| ast::Param::can_cast(it.kind()))
}
Either::Right(_self_param) => true,
}
}
// FIXME: why is this an option? It shouldn't be? // FIXME: why is this an option? It shouldn't be?
pub fn name(self, db: &dyn HirDatabase) -> Option<Name> { pub fn name(self, db: &dyn HirDatabase) -> Option<Name> {
let body = db.body(self.parent.into()); let body = db.body(self.parent.into());
@ -1071,12 +1078,14 @@ pub struct ImplDef {
impl ImplDef { impl ImplDef {
pub fn all_in_crate(db: &dyn HirDatabase, krate: Crate) -> Vec<ImplDef> { pub fn all_in_crate(db: &dyn HirDatabase, krate: Crate) -> Vec<ImplDef> {
let impls = db.impls_in_crate(krate.id); let inherent = db.inherent_impls_in_crate(krate.id);
impls.all_impls().map(Self::from).collect() let trait_ = db.trait_impls_in_crate(krate.id);
inherent.all_impls().chain(trait_.all_impls()).map(Self::from).collect()
} }
pub fn for_trait(db: &dyn HirDatabase, krate: Crate, trait_: Trait) -> Vec<ImplDef> { pub fn for_trait(db: &dyn HirDatabase, krate: Crate, trait_: Trait) -> Vec<ImplDef> {
let impls = db.impls_in_crate(krate.id); let impls = db.trait_impls_in_crate(krate.id);
impls.lookup_impl_defs_for_trait(trait_.id).map(Self::from).collect() impls.for_trait(trait_.id).map(Self::from).collect()
} }
pub fn target_trait(self, db: &dyn HirDatabase) -> Option<TypeRef> { pub fn target_trait(self, db: &dyn HirDatabase) -> Option<TypeRef> {
@ -1178,6 +1187,12 @@ impl Type {
Type::new(db, krate, def, ty) Type::new(db, krate, def, ty)
} }
pub fn is_unit(&self) -> bool {
matches!(
self.ty.value,
Ty::Apply(ApplicationTy { ctor: TypeCtor::Tuple { cardinality: 0 }, .. })
)
}
pub fn is_bool(&self) -> bool { pub fn is_bool(&self) -> bool {
matches!(self.ty.value, Ty::Apply(ApplicationTy { ctor: TypeCtor::Bool, .. })) matches!(self.ty.value, Ty::Apply(ApplicationTy { ctor: TypeCtor::Bool, .. }))
} }
@ -1205,7 +1220,7 @@ impl Type {
None => return false, None => return false,
}; };
let canonical_ty = Canonical { value: self.ty.value.clone(), num_vars: 0 }; let canonical_ty = Canonical { value: self.ty.value.clone(), kinds: Arc::new([]) };
method_resolution::implements_trait( method_resolution::implements_trait(
&canonical_ty, &canonical_ty,
db, db,
@ -1229,15 +1244,20 @@ impl Type {
self.ty.environment.clone(), self.ty.environment.clone(),
hir_ty::Obligation::Trait(trait_ref), hir_ty::Obligation::Trait(trait_ref),
), ),
num_vars: 0, kinds: Arc::new([]),
}; };
db.trait_solve(self.krate, goal).is_some() db.trait_solve(self.krate, goal).is_some()
} }
// FIXME: this method is broken, as it doesn't take closures into account. pub fn as_callable(&self, db: &dyn HirDatabase) -> Option<Callable> {
pub fn as_callable(&self) -> Option<CallableDef> { let def = match self.ty.value {
Some(self.ty.value.as_callable()?.0) Ty::Apply(ApplicationTy { ctor: TypeCtor::FnDef(def), parameters: _ }) => Some(def),
_ => None,
};
let sig = self.ty.value.callable_sig(db)?;
Some(Callable { ty: self.clone(), sig, def, is_bound_method: false })
} }
pub fn is_closure(&self) -> bool { pub fn is_closure(&self) -> bool {
@ -1304,7 +1324,7 @@ impl Type {
pub fn autoderef<'a>(&'a self, db: &'a dyn HirDatabase) -> impl Iterator<Item = Type> + 'a { pub fn autoderef<'a>(&'a self, db: &'a dyn HirDatabase) -> impl Iterator<Item = Type> + 'a {
// There should be no inference vars in types passed here // There should be no inference vars in types passed here
// FIXME check that? // FIXME check that?
let canonical = Canonical { value: self.ty.value.clone(), num_vars: 0 }; let canonical = Canonical { value: self.ty.value.clone(), kinds: Arc::new([]) };
let environment = self.ty.environment.clone(); let environment = self.ty.environment.clone();
let ty = InEnvironment { value: canonical, environment }; let ty = InEnvironment { value: canonical, environment };
autoderef(db, Some(self.krate), ty) autoderef(db, Some(self.krate), ty)
@ -1321,10 +1341,10 @@ impl Type {
mut callback: impl FnMut(AssocItem) -> Option<T>, mut callback: impl FnMut(AssocItem) -> Option<T>,
) -> Option<T> { ) -> Option<T> {
for krate in self.ty.value.def_crates(db, krate.id)? { for krate in self.ty.value.def_crates(db, krate.id)? {
let impls = db.impls_in_crate(krate); let impls = db.inherent_impls_in_crate(krate);
for impl_def in impls.lookup_impl_defs(&self.ty.value) { for impl_def in impls.for_self_ty(&self.ty.value) {
for &item in db.impl_data(impl_def).items.iter() { for &item in db.impl_data(*impl_def).items.iter() {
if let Some(result) = callback(item.into()) { if let Some(result) = callback(item.into()) {
return Some(result); return Some(result);
} }
@ -1345,7 +1365,7 @@ impl Type {
// There should be no inference vars in types passed here // There should be no inference vars in types passed here
// FIXME check that? // FIXME check that?
// FIXME replace Unknown by bound vars here // FIXME replace Unknown by bound vars here
let canonical = Canonical { value: self.ty.value.clone(), num_vars: 0 }; let canonical = Canonical { value: self.ty.value.clone(), kinds: Arc::new([]) };
let env = self.ty.environment.clone(); let env = self.ty.environment.clone();
let krate = krate.id; let krate = krate.id;
@ -1376,7 +1396,7 @@ impl Type {
// There should be no inference vars in types passed here // There should be no inference vars in types passed here
// FIXME check that? // FIXME check that?
// FIXME replace Unknown by bound vars here // FIXME replace Unknown by bound vars here
let canonical = Canonical { value: self.ty.value.clone(), num_vars: 0 }; let canonical = Canonical { value: self.ty.value.clone(), kinds: Arc::new([]) };
let env = self.ty.environment.clone(); let env = self.ty.environment.clone();
let krate = krate.id; let krate = krate.id;
@ -1522,6 +1542,74 @@ impl HirDisplay for Type {
} }
} }
// FIXME: closures
#[derive(Debug)]
pub struct Callable {
ty: Type,
sig: FnSig,
def: Option<CallableDefId>,
pub(crate) is_bound_method: bool,
}
pub enum CallableKind {
Function(Function),
TupleStruct(Struct),
TupleEnumVariant(EnumVariant),
Closure,
}
impl Callable {
pub fn kind(&self) -> CallableKind {
match self.def {
Some(CallableDefId::FunctionId(it)) => CallableKind::Function(it.into()),
Some(CallableDefId::StructId(it)) => CallableKind::TupleStruct(it.into()),
Some(CallableDefId::EnumVariantId(it)) => CallableKind::TupleEnumVariant(it.into()),
None => CallableKind::Closure,
}
}
pub fn receiver_param(&self, db: &dyn HirDatabase) -> Option<ast::SelfParam> {
let func = match self.def {
Some(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 patterns = match self.def {
Some(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(|it| it.pat().map(Either::Right)))
})
}
_ => 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())
}
}
/// For IDE only /// For IDE only
#[derive(Debug)] #[derive(Debug)]
pub enum ScopeDef { pub enum ScopeDef {
@ -1581,8 +1669,8 @@ pub enum AttrDef {
MacroDef(MacroDef), MacroDef(MacroDef),
} }
impl_froms!( impl_from!(
AttrDef: Module, Module,
Field, Field,
Adt(Struct, Enum, Union), Adt(Struct, Enum, Union),
EnumVariant, EnumVariant,
@ -1592,6 +1680,7 @@ impl_froms!(
Trait, Trait,
TypeAlias, TypeAlias,
MacroDef MacroDef
for AttrDef
); );
pub trait HasAttrs { pub trait HasAttrs {

View file

@ -11,15 +11,15 @@ pub use hir_def::db::{
}; };
pub use hir_expand::db::{ pub use hir_expand::db::{
AstDatabase, AstDatabaseStorage, AstIdMapQuery, InternEagerExpansionQuery, InternMacroQuery, AstDatabase, AstDatabaseStorage, AstIdMapQuery, InternEagerExpansionQuery, InternMacroQuery,
MacroArgQuery, MacroDefQuery, MacroExpandQuery, ParseMacroQuery, MacroArgTextQuery, MacroDefQuery, MacroExpandQuery, ParseMacroQuery,
}; };
pub use hir_ty::db::{ pub use hir_ty::db::{
AssociatedTyDataQuery, AssociatedTyValueQuery, CallableItemSignatureQuery, FieldTypesQuery, AssociatedTyDataQuery, AssociatedTyValueQuery, CallableItemSignatureQuery, FieldTypesQuery,
GenericDefaultsQuery, GenericPredicatesForParamQuery, GenericPredicatesQuery, HirDatabase, GenericDefaultsQuery, GenericPredicatesForParamQuery, GenericPredicatesQuery, HirDatabase,
HirDatabaseStorage, ImplDatumQuery, ImplSelfTyQuery, ImplTraitQuery, ImplsFromDepsQuery, HirDatabaseStorage, ImplDatumQuery, ImplSelfTyQuery, ImplTraitQuery, InferQueryQuery,
ImplsInCrateQuery, InferQueryQuery, InternAssocTyValueQuery, InternChalkImplQuery, InherentImplsInCrateQuery, InternTypeParamIdQuery, ReturnTypeImplTraitsQuery, StructDatumQuery,
InternTypeCtorQuery, InternTypeParamIdQuery, ReturnTypeImplTraitsQuery, StructDatumQuery, TraitDatumQuery, TraitImplsInCrateQuery, TraitImplsInDepsQuery, TraitSolveQuery, TyQuery,
TraitDatumQuery, TraitSolveQuery, TyQuery, ValueTyQuery, ValueTyQuery,
}; };
#[test] #[test]

View file

@ -1,4 +1,8 @@
//! FIXME: write short doc here //! FIXME: write short doc here
pub use hir_def::diagnostics::UnresolvedModule; pub use hir_def::diagnostics::UnresolvedModule;
pub use hir_expand::diagnostics::{AstDiagnostic, Diagnostic, DiagnosticSink}; pub use hir_expand::diagnostics::{
pub use hir_ty::diagnostics::{MissingFields, MissingMatchArms, MissingOkInTailExpr, NoSuchField}; AstDiagnostic, Diagnostic, DiagnosticSink, DiagnosticSinkBuilder,
};
pub use hir_ty::diagnostics::{
MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkInTailExpr, NoSuchField,
};

View file

@ -57,56 +57,56 @@ impl HasSource for Field {
} }
} }
impl HasSource for Struct { impl HasSource for Struct {
type Ast = ast::StructDef; type Ast = ast::Struct;
fn source(self, db: &dyn HirDatabase) -> InFile<ast::StructDef> { fn source(self, db: &dyn HirDatabase) -> InFile<ast::Struct> {
self.id.lookup(db.upcast()).source(db.upcast()) self.id.lookup(db.upcast()).source(db.upcast())
} }
} }
impl HasSource for Union { impl HasSource for Union {
type Ast = ast::UnionDef; type Ast = ast::Union;
fn source(self, db: &dyn HirDatabase) -> InFile<ast::UnionDef> { fn source(self, db: &dyn HirDatabase) -> InFile<ast::Union> {
self.id.lookup(db.upcast()).source(db.upcast()) self.id.lookup(db.upcast()).source(db.upcast())
} }
} }
impl HasSource for Enum { impl HasSource for Enum {
type Ast = ast::EnumDef; type Ast = ast::Enum;
fn source(self, db: &dyn HirDatabase) -> InFile<ast::EnumDef> { fn source(self, db: &dyn HirDatabase) -> InFile<ast::Enum> {
self.id.lookup(db.upcast()).source(db.upcast()) self.id.lookup(db.upcast()).source(db.upcast())
} }
} }
impl HasSource for EnumVariant { impl HasSource for EnumVariant {
type Ast = ast::EnumVariant; type Ast = ast::Variant;
fn source(self, db: &dyn HirDatabase) -> InFile<ast::EnumVariant> { fn source(self, db: &dyn HirDatabase) -> InFile<ast::Variant> {
self.parent.id.child_source(db.upcast()).map(|map| map[self.id].clone()) self.parent.id.child_source(db.upcast()).map(|map| map[self.id].clone())
} }
} }
impl HasSource for Function { impl HasSource for Function {
type Ast = ast::FnDef; type Ast = ast::Fn;
fn source(self, db: &dyn HirDatabase) -> InFile<ast::FnDef> { fn source(self, db: &dyn HirDatabase) -> InFile<ast::Fn> {
self.id.lookup(db.upcast()).source(db.upcast()) self.id.lookup(db.upcast()).source(db.upcast())
} }
} }
impl HasSource for Const { impl HasSource for Const {
type Ast = ast::ConstDef; type Ast = ast::Const;
fn source(self, db: &dyn HirDatabase) -> InFile<ast::ConstDef> { fn source(self, db: &dyn HirDatabase) -> InFile<ast::Const> {
self.id.lookup(db.upcast()).source(db.upcast()) self.id.lookup(db.upcast()).source(db.upcast())
} }
} }
impl HasSource for Static { impl HasSource for Static {
type Ast = ast::StaticDef; type Ast = ast::Static;
fn source(self, db: &dyn HirDatabase) -> InFile<ast::StaticDef> { fn source(self, db: &dyn HirDatabase) -> InFile<ast::Static> {
self.id.lookup(db.upcast()).source(db.upcast()) self.id.lookup(db.upcast()).source(db.upcast())
} }
} }
impl HasSource for Trait { impl HasSource for Trait {
type Ast = ast::TraitDef; type Ast = ast::Trait;
fn source(self, db: &dyn HirDatabase) -> InFile<ast::TraitDef> { fn source(self, db: &dyn HirDatabase) -> InFile<ast::Trait> {
self.id.lookup(db.upcast()).source(db.upcast()) self.id.lookup(db.upcast()).source(db.upcast())
} }
} }
impl HasSource for TypeAlias { impl HasSource for TypeAlias {
type Ast = ast::TypeAliasDef; type Ast = ast::TypeAlias;
fn source(self, db: &dyn HirDatabase) -> InFile<ast::TypeAliasDef> { fn source(self, db: &dyn HirDatabase) -> InFile<ast::TypeAlias> {
self.id.lookup(db.upcast()).source(db.upcast()) self.id.lookup(db.upcast()).source(db.upcast())
} }
} }
@ -120,14 +120,14 @@ impl HasSource for MacroDef {
} }
} }
impl HasSource for ImplDef { impl HasSource for ImplDef {
type Ast = ast::ImplDef; type Ast = ast::Impl;
fn source(self, db: &dyn HirDatabase) -> InFile<ast::ImplDef> { fn source(self, db: &dyn HirDatabase) -> InFile<ast::Impl> {
self.id.lookup(db.upcast()).source(db.upcast()) self.id.lookup(db.upcast()).source(db.upcast())
} }
} }
impl HasSource for TypeParam { impl HasSource for TypeParam {
type Ast = Either<ast::TraitDef, ast::TypeParam>; type Ast = Either<ast::Trait, ast::TypeParam>;
fn source(self, db: &dyn HirDatabase) -> InFile<Self::Ast> { fn source(self, db: &dyn HirDatabase) -> InFile<Self::Ast> {
let child_source = self.id.parent.child_source(db.upcast()); let child_source = self.id.parent.child_source(db.upcast());
child_source.map(|it| it[self.id.local_id].clone()) child_source.map(|it| it[self.id.local_id].clone())

View file

@ -19,25 +19,6 @@
#![recursion_limit = "512"] #![recursion_limit = "512"]
macro_rules! impl_froms {
($e:ident: $($v:ident $(($($sv:ident),*))?),*$(,)?) => {
$(
impl From<$v> for $e {
fn from(it: $v) -> $e {
$e::$v(it)
}
}
$($(
impl From<$sv> for $e {
fn from(it: $sv) -> $e {
$e::$v($v::$sv(it))
}
}
)*)?
)*
}
}
mod semantics; mod semantics;
pub mod db; pub mod db;
mod source_analyzer; mod source_analyzer;
@ -51,10 +32,10 @@ mod has_source;
pub use crate::{ pub use crate::{
code_model::{ code_model::{
Adt, AsAssocItem, AssocItem, AssocItemContainer, AttrDef, Const, Crate, CrateDependency, Adt, AsAssocItem, AssocItem, AssocItemContainer, AttrDef, Callable, CallableKind, Const,
DefWithBody, Docs, Enum, EnumVariant, Field, FieldSource, Function, GenericDef, HasAttrs, Crate, CrateDependency, DefWithBody, Docs, Enum, EnumVariant, Field, FieldSource, Function,
HasVisibility, ImplDef, Local, MacroDef, Module, ModuleDef, ScopeDef, Static, Struct, GenericDef, HasAttrs, HasVisibility, ImplDef, Local, MacroDef, Module, ModuleDef, ScopeDef,
Trait, Type, TypeAlias, TypeParam, Union, VariantDef, Visibility, Static, Struct, Trait, Type, TypeAlias, TypeParam, Union, VariantDef, Visibility,
}, },
has_source::HasSource, has_source::HasSource,
semantics::{original_range, PathResolution, Semantics, SemanticsScope}, semantics::{original_range, PathResolution, Semantics, SemanticsScope},
@ -74,6 +55,7 @@ pub use hir_def::{
pub use hir_expand::{ pub use hir_expand::{
hygiene::Hygiene, hygiene::Hygiene,
name::{AsName, Name}, name::{AsName, Name},
HirFileId, InFile, MacroCallId, MacroCallLoc, MacroDefId, MacroFile, Origin, HirFileId, InFile, MacroCallId, MacroCallLoc, MacroDefId, /* FIXME */
MacroFile, Origin,
}; };
pub use hir_ty::{display::HirDisplay, CallableDef}; pub use hir_ty::display::HirDisplay;

View file

@ -6,7 +6,7 @@ use std::{cell::RefCell, fmt, iter::successors};
use hir_def::{ use hir_def::{
resolver::{self, HasResolver, Resolver}, resolver::{self, HasResolver, Resolver},
AsMacroCall, TraitId, VariantId, AsMacroCall, FunctionId, TraitId, VariantId,
}; };
use hir_expand::{diagnostics::AstDiagnostic, hygiene::Hygiene, ExpansionInfo}; use hir_expand::{diagnostics::AstDiagnostic, hygiene::Hygiene, ExpansionInfo};
use hir_ty::associated_type_shorthand_candidates; use hir_ty::associated_type_shorthand_candidates;
@ -24,8 +24,8 @@ use crate::{
diagnostics::Diagnostic, diagnostics::Diagnostic,
semantics::source_to_def::{ChildContainer, SourceToDefCache, SourceToDefCtx}, semantics::source_to_def::{ChildContainer, SourceToDefCache, SourceToDefCtx},
source_analyzer::{resolve_hir_path, resolve_hir_path_qualifier, SourceAnalyzer}, source_analyzer::{resolve_hir_path, resolve_hir_path_qualifier, SourceAnalyzer},
AssocItem, Field, Function, HirFileId, ImplDef, InFile, Local, MacroDef, Module, ModuleDef, AssocItem, Callable, Field, Function, HirFileId, ImplDef, InFile, Local, MacroDef, Module,
Name, Origin, Path, ScopeDef, Trait, Type, TypeAlias, TypeParam, ModuleDef, Name, Origin, Path, ScopeDef, Trait, Type, TypeAlias, TypeParam, VariantDef,
}; };
use resolver::TypeNs; use resolver::TypeNs;
@ -83,7 +83,13 @@ impl PathResolution {
/// Primary API to get semantic information, like types, from syntax trees. /// Primary API to get semantic information, like types, from syntax trees.
pub struct Semantics<'db, DB> { pub struct Semantics<'db, DB> {
pub db: &'db DB, pub db: &'db DB,
imp: SemanticsImpl<'db>,
}
pub struct SemanticsImpl<'db> {
pub db: &'db dyn HirDatabase,
s2d_cache: RefCell<SourceToDefCache>, s2d_cache: RefCell<SourceToDefCache>,
expansion_info_cache: RefCell<FxHashMap<HirFileId, Option<ExpansionInfo>>>,
cache: RefCell<FxHashMap<SyntaxNode, HirFileId>>, cache: RefCell<FxHashMap<SyntaxNode, HirFileId>>,
} }
@ -95,29 +101,23 @@ impl<DB> fmt::Debug for Semantics<'_, DB> {
impl<'db, DB: HirDatabase> Semantics<'db, DB> { impl<'db, DB: HirDatabase> Semantics<'db, DB> {
pub fn new(db: &DB) -> Semantics<DB> { pub fn new(db: &DB) -> Semantics<DB> {
Semantics { db, s2d_cache: Default::default(), cache: Default::default() } let impl_ = SemanticsImpl::new(db);
Semantics { db, imp: impl_ }
} }
pub fn parse(&self, file_id: FileId) -> ast::SourceFile { pub fn parse(&self, file_id: FileId) -> ast::SourceFile {
let tree = self.db.parse(file_id).tree(); self.imp.parse(file_id)
self.cache(tree.syntax().clone(), file_id.into());
tree
} }
pub fn ast<T: AstDiagnostic + Diagnostic>(&self, d: &T) -> <T as AstDiagnostic>::AST { pub fn ast<T: AstDiagnostic + Diagnostic>(&self, d: &T) -> <T as AstDiagnostic>::AST {
let file_id = d.source().file_id; let file_id = d.source().file_id;
let root = self.db.parse_or_expand(file_id).unwrap(); let root = self.db.parse_or_expand(file_id).unwrap();
self.cache(root, file_id); self.imp.cache(root, file_id);
d.ast(self.db) d.ast(self.db.upcast())
} }
pub fn expand(&self, macro_call: &ast::MacroCall) -> Option<SyntaxNode> { pub fn expand(&self, macro_call: &ast::MacroCall) -> Option<SyntaxNode> {
let macro_call = self.find_file(macro_call.syntax().clone()).with_value(macro_call); self.imp.expand(macro_call)
let sa = self.analyze2(macro_call.map(|it| it.syntax()), None);
let file_id = sa.expand(self.db, macro_call)?;
let node = self.db.parse_or_expand(file_id)?;
self.cache(node.clone(), file_id);
Some(node)
} }
pub fn expand_hypothetical( pub fn expand_hypothetical(
@ -126,37 +126,11 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
hypothetical_args: &ast::TokenTree, hypothetical_args: &ast::TokenTree,
token_to_map: SyntaxToken, token_to_map: SyntaxToken,
) -> Option<(SyntaxNode, SyntaxToken)> { ) -> Option<(SyntaxNode, SyntaxToken)> {
let macro_call = self.imp.expand_hypothetical(actual_macro_call, hypothetical_args, token_to_map)
self.find_file(actual_macro_call.syntax().clone()).with_value(actual_macro_call);
let sa = self.analyze2(macro_call.map(|it| it.syntax()), None);
let krate = sa.resolver.krate()?;
let macro_call_id = macro_call
.as_call_id(self.db, krate, |path| sa.resolver.resolve_path_as_macro(self.db, &path))?;
hir_expand::db::expand_hypothetical(self.db, macro_call_id, hypothetical_args, token_to_map)
} }
pub fn descend_into_macros(&self, token: SyntaxToken) -> SyntaxToken { pub fn descend_into_macros(&self, token: SyntaxToken) -> SyntaxToken {
let parent = token.parent(); self.imp.descend_into_macros(token)
let parent = self.find_file(parent);
let sa = self.analyze2(parent.as_ref(), None);
let token = successors(Some(parent.with_value(token)), |token| {
let macro_call = token.value.ancestors().find_map(ast::MacroCall::cast)?;
let tt = macro_call.token_tree()?;
if !tt.syntax().text_range().contains_range(token.value.text_range()) {
return None;
}
let file_id = sa.expand(self.db, token.with_value(&macro_call))?;
let token = file_id.expansion_info(self.db)?.map_token_down(token.as_ref())?;
self.cache(find_root(&token.value.parent()), token.file_id);
Some(token)
})
.last()
.unwrap();
token.value
} }
pub fn descend_node_at_offset<N: ast::AstNode>( pub fn descend_node_at_offset<N: ast::AstNode>(
@ -164,27 +138,19 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
node: &SyntaxNode, node: &SyntaxNode,
offset: TextSize, offset: TextSize,
) -> Option<N> { ) -> Option<N> {
// Handle macro token cases self.imp.descend_node_at_offset(node, offset).find_map(N::cast)
node.token_at_offset(offset)
.map(|token| self.descend_into_macros(token))
.find_map(|it| self.ancestors_with_macros(it.parent()).find_map(N::cast))
} }
pub fn original_range(&self, node: &SyntaxNode) -> FileRange { pub fn original_range(&self, node: &SyntaxNode) -> FileRange {
let node = self.find_file(node.clone()); self.imp.original_range(node)
original_range(self.db, node.as_ref())
} }
pub fn diagnostics_range(&self, diagnostics: &dyn Diagnostic) -> FileRange { pub fn diagnostics_range(&self, diagnostics: &dyn Diagnostic) -> FileRange {
let src = diagnostics.source(); self.imp.diagnostics_range(diagnostics)
let root = self.db.parse_or_expand(src.file_id).unwrap();
let node = src.value.to_node(&root);
original_range(self.db, src.with_value(&node))
} }
pub fn ancestors_with_macros(&self, node: SyntaxNode) -> impl Iterator<Item = SyntaxNode> + '_ { pub fn ancestors_with_macros(&self, node: SyntaxNode) -> impl Iterator<Item = SyntaxNode> + '_ {
let node = self.find_file(node); self.imp.ancestors_with_macros(node)
node.ancestors_with_macros(self.db).map(|it| it.value)
} }
pub fn ancestors_at_offset_with_macros( pub fn ancestors_at_offset_with_macros(
@ -192,9 +158,7 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
node: &SyntaxNode, node: &SyntaxNode,
offset: TextSize, offset: TextSize,
) -> impl Iterator<Item = SyntaxNode> + '_ { ) -> impl Iterator<Item = SyntaxNode> + '_ {
node.token_at_offset(offset) self.imp.ancestors_at_offset_with_macros(node, offset)
.map(|token| self.ancestors_with_macros(token.parent()))
.kmerge_by(|node1, node2| node1.text_range().len() < node2.text_range().len())
} }
/// Find a AstNode by offset inside SyntaxNode, if it is inside *Macrofile*, /// Find a AstNode by offset inside SyntaxNode, if it is inside *Macrofile*,
@ -204,7 +168,7 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
node: &SyntaxNode, node: &SyntaxNode,
offset: TextSize, offset: TextSize,
) -> Option<N> { ) -> Option<N> {
self.ancestors_at_offset_with_macros(node, offset).find_map(N::cast) self.imp.ancestors_at_offset_with_macros(node, offset).find_map(N::cast)
} }
/// Find a AstNode by offset inside SyntaxNode, if it is inside *MacroCall*, /// Find a AstNode by offset inside SyntaxNode, if it is inside *MacroCall*,
@ -217,100 +181,317 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
if let Some(it) = find_node_at_offset(&node, offset) { if let Some(it) = find_node_at_offset(&node, offset) {
return Some(it); return Some(it);
} }
self.descend_node_at_offset(&node, offset)
self.imp.descend_node_at_offset(node, offset).find_map(N::cast)
} }
pub fn type_of_expr(&self, expr: &ast::Expr) -> Option<Type> { pub fn type_of_expr(&self, expr: &ast::Expr) -> Option<Type> {
self.analyze(expr.syntax()).type_of(self.db, &expr) self.imp.type_of_expr(expr)
} }
pub fn type_of_pat(&self, pat: &ast::Pat) -> Option<Type> { pub fn type_of_pat(&self, pat: &ast::Pat) -> Option<Type> {
self.analyze(pat.syntax()).type_of_pat(self.db, &pat) 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 resolve_method_call(&self, call: &ast::MethodCallExpr) -> Option<Function> { pub fn resolve_method_call(&self, call: &ast::MethodCallExpr) -> Option<Function> {
self.analyze(call.syntax()).resolve_method_call(self.db, call) self.imp.resolve_method_call(call).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> { pub fn resolve_field(&self, field: &ast::FieldExpr) -> Option<Field> {
self.analyze(field.syntax()).resolve_field(self.db, field) self.imp.resolve_field(field)
} }
pub fn resolve_record_field(&self, field: &ast::RecordField) -> Option<(Field, Option<Local>)> { pub fn resolve_record_field(
self.analyze(field.syntax()).resolve_record_field(self.db, field) &self,
field: &ast::RecordExprField,
) -> Option<(Field, Option<Local>)> {
self.imp.resolve_record_field(field)
} }
pub fn resolve_record_field_pat(&self, field: &ast::RecordFieldPat) -> Option<Field> { pub fn resolve_record_field_pat(&self, field: &ast::RecordFieldPat) -> Option<Field> {
self.analyze(field.syntax()).resolve_record_field_pat(self.db, field) self.imp.resolve_record_field_pat(field)
} }
pub fn resolve_macro_call(&self, macro_call: &ast::MacroCall) -> Option<MacroDef> { pub fn resolve_macro_call(&self, macro_call: &ast::MacroCall) -> Option<MacroDef> {
let sa = self.analyze(macro_call.syntax()); self.imp.resolve_macro_call(macro_call)
let macro_call = self.find_file(macro_call.syntax().clone()).with_value(macro_call);
sa.resolve_macro_call(self.db, macro_call)
} }
pub fn resolve_path(&self, path: &ast::Path) -> Option<PathResolution> { pub fn resolve_path(&self, path: &ast::Path) -> Option<PathResolution> {
self.analyze(path.syntax()).resolve_path(self.db, path) self.imp.resolve_path(path)
} }
pub fn resolve_variant(&self, record_lit: ast::RecordLit) -> Option<VariantId> { pub fn resolve_variant(&self, record_lit: ast::RecordExpr) -> Option<VariantDef> {
self.analyze(record_lit.syntax()).resolve_variant(self.db, record_lit) self.imp.resolve_variant(record_lit).map(VariantDef::from)
} }
pub fn lower_path(&self, path: &ast::Path) -> Option<Path> { pub fn lower_path(&self, path: &ast::Path) -> Option<Path> {
let src = self.find_file(path.syntax().clone()); self.imp.lower_path(path)
Path::from_src(path.clone(), &Hygiene::new(self.db.upcast(), src.file_id.into()))
} }
pub fn resolve_bind_pat_to_const(&self, pat: &ast::BindPat) -> Option<ModuleDef> { pub fn resolve_bind_pat_to_const(&self, pat: &ast::BindPat) -> Option<ModuleDef> {
self.analyze(pat.syntax()).resolve_bind_pat_to_const(self.db, pat) self.imp.resolve_bind_pat_to_const(pat)
} }
// FIXME: use this instead? // FIXME: use this instead?
// pub fn resolve_name_ref(&self, name_ref: &ast::NameRef) -> Option<???>; // pub fn resolve_name_ref(&self, name_ref: &ast::NameRef) -> Option<???>;
pub fn record_literal_missing_fields(&self, literal: &ast::RecordLit) -> Vec<(Field, Type)> { 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().clone()).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)
}
pub fn scope(&self, node: &SyntaxNode) -> SemanticsScope<'db> {
self.imp.scope(node)
}
pub fn scope_at_offset(&self, node: &SyntaxNode, offset: TextSize) -> 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)
}
}
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(),
}
}
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 expand(&self, macro_call: &ast::MacroCall) -> Option<SyntaxNode> {
let macro_call = self.find_file(macro_call.syntax().clone()).with_value(macro_call);
let sa = self.analyze2(macro_call.map(|it| it.syntax()), None);
let file_id = sa.expand(self.db, macro_call)?;
let node = self.db.parse_or_expand(file_id)?;
self.cache(node.clone(), file_id);
Some(node)
}
fn expand_hypothetical(
&self,
actual_macro_call: &ast::MacroCall,
hypothetical_args: &ast::TokenTree,
token_to_map: SyntaxToken,
) -> Option<(SyntaxNode, SyntaxToken)> {
let macro_call =
self.find_file(actual_macro_call.syntax().clone()).with_value(actual_macro_call);
let sa = self.analyze2(macro_call.map(|it| it.syntax()), None);
let krate = sa.resolver.krate()?;
let macro_call_id = macro_call.as_call_id(self.db.upcast(), krate, |path| {
sa.resolver.resolve_path_as_macro(self.db.upcast(), &path)
})?;
hir_expand::db::expand_hypothetical(
self.db.upcast(),
macro_call_id,
hypothetical_args,
token_to_map,
)
}
fn descend_into_macros(&self, token: SyntaxToken) -> SyntaxToken {
let _p = profile("descend_into_macros");
let parent = token.parent();
let parent = self.find_file(parent);
let sa = self.analyze2(parent.as_ref(), None);
let token = successors(Some(parent.with_value(token)), |token| {
self.db.check_canceled();
let macro_call = token.value.ancestors().find_map(ast::MacroCall::cast)?;
let tt = macro_call.token_tree()?;
if !tt.syntax().text_range().contains_range(token.value.text_range()) {
return None;
}
let file_id = sa.expand(self.db, token.with_value(&macro_call))?;
let token = self
.expansion_info_cache
.borrow_mut()
.entry(file_id)
.or_insert_with(|| file_id.expansion_info(self.db.upcast()))
.as_ref()?
.map_token_down(token.as_ref())?;
self.cache(find_root(&token.value.parent()), token.file_id);
Some(token)
})
.last()
.unwrap();
token.value
}
fn descend_node_at_offset(
&self,
node: &SyntaxNode,
offset: TextSize,
) -> impl Iterator<Item = SyntaxNode> + '_ {
// Handle macro token cases
node.token_at_offset(offset)
.map(|token| self.descend_into_macros(token))
.map(|it| self.ancestors_with_macros(it.parent()))
.flatten()
}
fn original_range(&self, node: &SyntaxNode) -> FileRange {
let node = self.find_file(node.clone());
original_range(self.db, node.as_ref())
}
fn diagnostics_range(&self, diagnostics: &dyn Diagnostic) -> FileRange {
let src = diagnostics.source();
let root = self.db.parse_or_expand(src.file_id).unwrap();
let node = src.value.to_node(&root);
original_range(self.db, src.with_value(&node))
}
fn ancestors_with_macros(&self, node: SyntaxNode) -> impl Iterator<Item = SyntaxNode> + '_ {
let node = self.find_file(node);
node.ancestors_with_macros(self.db.upcast()).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.ancestors_with_macros(token.parent()))
.kmerge_by(|node1, node2| node1.text_range().len() < node2.text_range().len())
}
fn type_of_expr(&self, expr: &ast::Expr) -> Option<Type> {
self.analyze(expr.syntax()).type_of_expr(self.db, &expr)
}
fn type_of_pat(&self, pat: &ast::Pat) -> Option<Type> {
self.analyze(pat.syntax()).type_of_pat(self.db, &pat)
}
fn type_of_self(&self, param: &ast::SelfParam) -> Option<Type> {
self.analyze(param.syntax()).type_of_self(self.db, &param)
}
fn resolve_method_call(&self, call: &ast::MethodCallExpr) -> Option<FunctionId> {
self.analyze(call.syntax()).resolve_method_call(self.db, call)
}
fn resolve_method_call_as_callable(&self, call: &ast::MethodCallExpr) -> Option<Callable> {
// FIXME: this erases Substs
let func = self.resolve_method_call(call)?;
let ty = self.db.value_ty(func.into());
let resolver = self.analyze(call.syntax()).resolver;
let ty = Type::new_with_resolver(self.db, &resolver, ty.value)?;
let mut res = ty.as_callable(self.db)?;
res.is_bound_method = true;
Some(res)
}
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>)> {
self.analyze(field.syntax()).resolve_record_field(self.db, field)
}
fn resolve_record_field_pat(&self, field: &ast::RecordFieldPat) -> Option<Field> {
self.analyze(field.syntax()).resolve_record_field_pat(self.db, field)
}
fn resolve_macro_call(&self, macro_call: &ast::MacroCall) -> Option<MacroDef> {
let sa = self.analyze(macro_call.syntax());
let macro_call = self.find_file(macro_call.syntax().clone()).with_value(macro_call);
sa.resolve_macro_call(self.db, macro_call)
}
fn resolve_path(&self, path: &ast::Path) -> Option<PathResolution> {
self.analyze(path.syntax()).resolve_path(self.db, path)
}
fn resolve_variant(&self, record_lit: ast::RecordExpr) -> Option<VariantId> {
self.analyze(record_lit.syntax()).resolve_variant(self.db, record_lit)
}
fn lower_path(&self, path: &ast::Path) -> Option<Path> {
let src = self.find_file(path.syntax().clone());
Path::from_src(path.clone(), &Hygiene::new(self.db.upcast(), src.file_id.into()))
}
fn resolve_bind_pat_to_const(&self, pat: &ast::BindPat) -> 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()) self.analyze(literal.syntax())
.record_literal_missing_fields(self.db, literal) .record_literal_missing_fields(self.db, literal)
.unwrap_or_default() .unwrap_or_default()
} }
pub fn record_pattern_missing_fields(&self, pattern: &ast::RecordPat) -> Vec<(Field, Type)> { fn record_pattern_missing_fields(&self, pattern: &ast::RecordPat) -> Vec<(Field, Type)> {
self.analyze(pattern.syntax()) self.analyze(pattern.syntax())
.record_pattern_missing_fields(self.db, pattern) .record_pattern_missing_fields(self.db, pattern)
.unwrap_or_default() .unwrap_or_default()
} }
pub fn to_def<T: ToDef>(&self, src: &T) -> Option<T::Def> {
let src = self.find_file(src.syntax().clone()).with_value(src).cloned();
T::to_def(self, src)
}
fn with_ctx<F: FnOnce(&mut SourceToDefCtx) -> T, T>(&self, f: F) -> T { fn with_ctx<F: FnOnce(&mut SourceToDefCtx) -> T, T>(&self, f: F) -> T {
let mut cache = self.s2d_cache.borrow_mut(); let mut cache = self.s2d_cache.borrow_mut();
let mut ctx = SourceToDefCtx { db: self.db, cache: &mut *cache }; let mut ctx = SourceToDefCtx { db: self.db, cache: &mut *cache };
f(&mut ctx) f(&mut ctx)
} }
pub fn to_module_def(&self, file: FileId) -> Option<Module> { fn to_module_def(&self, file: FileId) -> Option<Module> {
self.with_ctx(|ctx| ctx.file_to_def(file)).map(Module::from) self.with_ctx(|ctx| ctx.file_to_def(file)).map(Module::from)
} }
pub fn scope(&self, node: &SyntaxNode) -> SemanticsScope<'db, DB> { fn scope(&self, node: &SyntaxNode) -> SemanticsScope<'db> {
let node = self.find_file(node.clone()); let node = self.find_file(node.clone());
let resolver = self.analyze2(node.as_ref(), None).resolver; let resolver = self.analyze2(node.as_ref(), None).resolver;
SemanticsScope { db: self.db, resolver } SemanticsScope { db: self.db, resolver }
} }
pub fn scope_at_offset(&self, node: &SyntaxNode, offset: TextSize) -> SemanticsScope<'db, DB> { fn scope_at_offset(&self, node: &SyntaxNode, offset: TextSize) -> SemanticsScope<'db> {
let node = self.find_file(node.clone()); let node = self.find_file(node.clone());
let resolver = self.analyze2(node.as_ref(), Some(offset)).resolver; let resolver = self.analyze2(node.as_ref(), Some(offset)).resolver;
SemanticsScope { db: self.db, resolver } SemanticsScope { db: self.db, resolver }
} }
pub fn scope_for_def(&self, def: Trait) -> SemanticsScope<'db, DB> { fn scope_for_def(&self, def: Trait) -> SemanticsScope<'db> {
let resolver = def.id.resolver(self.db); let resolver = def.id.resolver(self.db.upcast());
SemanticsScope { db: self.db, resolver } SemanticsScope { db: self.db, resolver }
} }
@ -331,12 +512,13 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
ChildContainer::DefWithBodyId(def) => { ChildContainer::DefWithBodyId(def) => {
return SourceAnalyzer::new_for_body(self.db, def, src, offset) return SourceAnalyzer::new_for_body(self.db, def, src, offset)
} }
ChildContainer::TraitId(it) => it.resolver(self.db), ChildContainer::TraitId(it) => it.resolver(self.db.upcast()),
ChildContainer::ImplId(it) => it.resolver(self.db), ChildContainer::ImplId(it) => it.resolver(self.db.upcast()),
ChildContainer::ModuleId(it) => it.resolver(self.db), ChildContainer::ModuleId(it) => it.resolver(self.db.upcast()),
ChildContainer::EnumId(it) => it.resolver(self.db), ChildContainer::EnumId(it) => it.resolver(self.db.upcast()),
ChildContainer::VariantId(it) => it.resolver(self.db), ChildContainer::VariantId(it) => it.resolver(self.db.upcast()),
ChildContainer::GenericDefId(it) => it.resolver(self.db), ChildContainer::TypeAliasId(it) => it.resolver(self.db.upcast()),
ChildContainer::GenericDefId(it) => it.resolver(self.db.upcast()),
}; };
SourceAnalyzer::new_for_resolver(resolver, src) SourceAnalyzer::new_for_resolver(resolver, src)
} }
@ -348,7 +530,7 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
assert!(prev == None || prev == Some(file_id)) assert!(prev == None || prev == Some(file_id))
} }
pub fn assert_contains_node(&self, node: &SyntaxNode) { fn assert_contains_node(&self, node: &SyntaxNode) {
self.find_file(node.clone()); self.find_file(node.clone());
} }
@ -382,14 +564,14 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
pub trait ToDef: AstNode + Clone { pub trait ToDef: AstNode + Clone {
type Def; type Def;
fn to_def<DB: HirDatabase>(sema: &Semantics<DB>, src: InFile<Self>) -> Option<Self::Def>; fn to_def(sema: &SemanticsImpl, src: InFile<Self>) -> Option<Self::Def>;
} }
macro_rules! to_def_impls { macro_rules! to_def_impls {
($(($def:path, $ast:path, $meth:ident)),* ,) => {$( ($(($def:path, $ast:path, $meth:ident)),* ,) => {$(
impl ToDef for $ast { impl ToDef for $ast {
type Def = $def; type Def = $def;
fn to_def<DB: HirDatabase>(sema: &Semantics<DB>, src: InFile<Self>) -> Option<Self::Def> { fn to_def(sema: &SemanticsImpl, src: InFile<Self>) -> Option<Self::Def> {
sema.with_ctx(|ctx| ctx.$meth(src)).map(<$def>::from) sema.with_ctx(|ctx| ctx.$meth(src)).map(<$def>::from)
} }
} }
@ -398,18 +580,18 @@ macro_rules! to_def_impls {
to_def_impls![ to_def_impls![
(crate::Module, ast::Module, module_to_def), (crate::Module, ast::Module, module_to_def),
(crate::Struct, ast::StructDef, struct_to_def), (crate::Struct, ast::Struct, struct_to_def),
(crate::Enum, ast::EnumDef, enum_to_def), (crate::Enum, ast::Enum, enum_to_def),
(crate::Union, ast::UnionDef, union_to_def), (crate::Union, ast::Union, union_to_def),
(crate::Trait, ast::TraitDef, trait_to_def), (crate::Trait, ast::Trait, trait_to_def),
(crate::ImplDef, ast::ImplDef, impl_to_def), (crate::ImplDef, ast::Impl, impl_to_def),
(crate::TypeAlias, ast::TypeAliasDef, type_alias_to_def), (crate::TypeAlias, ast::TypeAlias, type_alias_to_def),
(crate::Const, ast::ConstDef, const_to_def), (crate::Const, ast::Const, const_to_def),
(crate::Static, ast::StaticDef, static_to_def), (crate::Static, ast::Static, static_to_def),
(crate::Function, ast::FnDef, fn_to_def), (crate::Function, ast::Fn, fn_to_def),
(crate::Field, ast::RecordFieldDef, record_field_to_def), (crate::Field, ast::RecordField, record_field_to_def),
(crate::Field, ast::TupleFieldDef, tuple_field_to_def), (crate::Field, ast::TupleField, tuple_field_to_def),
(crate::EnumVariant, ast::EnumVariant, enum_variant_to_def), (crate::EnumVariant, ast::Variant, enum_variant_to_def),
(crate::TypeParam, ast::TypeParam, type_param_to_def), (crate::TypeParam, ast::TypeParam, type_param_to_def),
(crate::MacroDef, ast::MacroCall, macro_call_to_def), // this one is dubious, not all calls are macros (crate::MacroDef, ast::MacroCall, macro_call_to_def), // this one is dubious, not all calls are macros
(crate::Local, ast::BindPat, bind_pat_to_def), (crate::Local, ast::BindPat, bind_pat_to_def),
@ -419,12 +601,13 @@ fn find_root(node: &SyntaxNode) -> SyntaxNode {
node.ancestors().last().unwrap() node.ancestors().last().unwrap()
} }
pub struct SemanticsScope<'a, DB> { #[derive(Debug)]
pub db: &'a DB, pub struct SemanticsScope<'a> {
pub db: &'a dyn HirDatabase,
resolver: Resolver, resolver: Resolver,
} }
impl<'a, DB: HirDatabase> SemanticsScope<'a, DB> { impl<'a> SemanticsScope<'a> {
pub fn module(&self) -> Option<Module> { pub fn module(&self) -> Option<Module> {
Some(Module { id: self.resolver.module()? }) Some(Module { id: self.resolver.module()? })
} }
@ -433,13 +616,13 @@ impl<'a, DB: HirDatabase> SemanticsScope<'a, DB> {
// FIXME: rename to visible_traits to not repeat scope? // FIXME: rename to visible_traits to not repeat scope?
pub fn traits_in_scope(&self) -> FxHashSet<TraitId> { pub fn traits_in_scope(&self) -> FxHashSet<TraitId> {
let resolver = &self.resolver; let resolver = &self.resolver;
resolver.traits_in_scope(self.db) resolver.traits_in_scope(self.db.upcast())
} }
pub fn process_all_names(&self, f: &mut dyn FnMut(Name, ScopeDef)) { pub fn process_all_names(&self, f: &mut dyn FnMut(Name, ScopeDef)) {
let resolver = &self.resolver; let resolver = &self.resolver;
resolver.process_all_names(self.db, &mut |name, def| { resolver.process_all_names(self.db.upcast(), &mut |name, def| {
let def = match def { let def = match def {
resolver::ScopeDef::PerNs(it) => { resolver::ScopeDef::PerNs(it) => {
let items = ScopeDef::all_items(it); let items = ScopeDef::all_items(it);

View file

@ -16,6 +16,7 @@ use ra_syntax::{
match_ast, AstNode, SyntaxNode, match_ast, AstNode, SyntaxNode,
}; };
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use stdx::impl_from;
use crate::{db::HirDatabase, InFile, MacroDefId}; use crate::{db::HirDatabase, InFile, MacroDefId};
@ -64,53 +65,44 @@ impl SourceToDefCtx<'_, '_> {
Some(ModuleId { krate: parent_module.krate, local_id: child_id }) Some(ModuleId { krate: parent_module.krate, local_id: child_id })
} }
pub(super) fn trait_to_def(&mut self, src: InFile<ast::TraitDef>) -> Option<TraitId> { pub(super) fn trait_to_def(&mut self, src: InFile<ast::Trait>) -> Option<TraitId> {
self.to_def(src, keys::TRAIT) self.to_def(src, keys::TRAIT)
} }
pub(super) fn impl_to_def(&mut self, src: InFile<ast::ImplDef>) -> Option<ImplId> { pub(super) fn impl_to_def(&mut self, src: InFile<ast::Impl>) -> Option<ImplId> {
self.to_def(src, keys::IMPL) self.to_def(src, keys::IMPL)
} }
pub(super) fn fn_to_def(&mut self, src: InFile<ast::FnDef>) -> Option<FunctionId> { pub(super) fn fn_to_def(&mut self, src: InFile<ast::Fn>) -> Option<FunctionId> {
self.to_def(src, keys::FUNCTION) self.to_def(src, keys::FUNCTION)
} }
pub(super) fn struct_to_def(&mut self, src: InFile<ast::StructDef>) -> Option<StructId> { pub(super) fn struct_to_def(&mut self, src: InFile<ast::Struct>) -> Option<StructId> {
self.to_def(src, keys::STRUCT) self.to_def(src, keys::STRUCT)
} }
pub(super) fn enum_to_def(&mut self, src: InFile<ast::EnumDef>) -> Option<EnumId> { pub(super) fn enum_to_def(&mut self, src: InFile<ast::Enum>) -> Option<EnumId> {
self.to_def(src, keys::ENUM) self.to_def(src, keys::ENUM)
} }
pub(super) fn union_to_def(&mut self, src: InFile<ast::UnionDef>) -> Option<UnionId> { pub(super) fn union_to_def(&mut self, src: InFile<ast::Union>) -> Option<UnionId> {
self.to_def(src, keys::UNION) self.to_def(src, keys::UNION)
} }
pub(super) fn static_to_def(&mut self, src: InFile<ast::StaticDef>) -> Option<StaticId> { pub(super) fn static_to_def(&mut self, src: InFile<ast::Static>) -> Option<StaticId> {
self.to_def(src, keys::STATIC) self.to_def(src, keys::STATIC)
} }
pub(super) fn const_to_def(&mut self, src: InFile<ast::ConstDef>) -> Option<ConstId> { pub(super) fn const_to_def(&mut self, src: InFile<ast::Const>) -> Option<ConstId> {
self.to_def(src, keys::CONST) self.to_def(src, keys::CONST)
} }
pub(super) fn type_alias_to_def( pub(super) fn type_alias_to_def(&mut self, src: InFile<ast::TypeAlias>) -> Option<TypeAliasId> {
&mut self,
src: InFile<ast::TypeAliasDef>,
) -> Option<TypeAliasId> {
self.to_def(src, keys::TYPE_ALIAS) self.to_def(src, keys::TYPE_ALIAS)
} }
pub(super) fn record_field_to_def( pub(super) fn record_field_to_def(&mut self, src: InFile<ast::RecordField>) -> Option<FieldId> {
&mut self,
src: InFile<ast::RecordFieldDef>,
) -> Option<FieldId> {
self.to_def(src, keys::RECORD_FIELD) self.to_def(src, keys::RECORD_FIELD)
} }
pub(super) fn tuple_field_to_def( pub(super) fn tuple_field_to_def(&mut self, src: InFile<ast::TupleField>) -> Option<FieldId> {
&mut self,
src: InFile<ast::TupleFieldDef>,
) -> Option<FieldId> {
self.to_def(src, keys::TUPLE_FIELD) self.to_def(src, keys::TUPLE_FIELD)
} }
pub(super) fn enum_variant_to_def( pub(super) fn enum_variant_to_def(
&mut self, &mut self,
src: InFile<ast::EnumVariant>, src: InFile<ast::Variant>,
) -> Option<EnumVariantId> { ) -> Option<EnumVariantId> {
self.to_def(src, keys::ENUM_VARIANT) self.to_def(src, keys::VARIANT)
} }
pub(super) fn bind_pat_to_def( pub(super) fn bind_pat_to_def(
&mut self, &mut self,
@ -162,38 +154,42 @@ impl SourceToDefCtx<'_, '_> {
let def = self.module_to_def(container.with_value(it))?; let def = self.module_to_def(container.with_value(it))?;
def.into() def.into()
}, },
ast::TraitDef(it) => { ast::Trait(it) => {
let def = self.trait_to_def(container.with_value(it))?; let def = self.trait_to_def(container.with_value(it))?;
def.into() def.into()
}, },
ast::ImplDef(it) => { ast::Impl(it) => {
let def = self.impl_to_def(container.with_value(it))?; let def = self.impl_to_def(container.with_value(it))?;
def.into() def.into()
}, },
ast::FnDef(it) => { ast::Fn(it) => {
let def = self.fn_to_def(container.with_value(it))?; let def = self.fn_to_def(container.with_value(it))?;
DefWithBodyId::from(def).into() DefWithBodyId::from(def).into()
}, },
ast::StructDef(it) => { ast::Struct(it) => {
let def = self.struct_to_def(container.with_value(it))?; let def = self.struct_to_def(container.with_value(it))?;
VariantId::from(def).into() VariantId::from(def).into()
}, },
ast::EnumDef(it) => { ast::Enum(it) => {
let def = self.enum_to_def(container.with_value(it))?; let def = self.enum_to_def(container.with_value(it))?;
def.into() def.into()
}, },
ast::UnionDef(it) => { ast::Union(it) => {
let def = self.union_to_def(container.with_value(it))?; let def = self.union_to_def(container.with_value(it))?;
VariantId::from(def).into() VariantId::from(def).into()
}, },
ast::StaticDef(it) => { ast::Static(it) => {
let def = self.static_to_def(container.with_value(it))?; let def = self.static_to_def(container.with_value(it))?;
DefWithBodyId::from(def).into() DefWithBodyId::from(def).into()
}, },
ast::ConstDef(it) => { ast::Const(it) => {
let def = self.const_to_def(container.with_value(it))?; let def = self.const_to_def(container.with_value(it))?;
DefWithBodyId::from(def).into() DefWithBodyId::from(def).into()
}, },
ast::TypeAlias(it) => {
let def = self.type_alias_to_def(container.with_value(it))?;
def.into()
},
_ => continue, _ => continue,
} }
}; };
@ -208,12 +204,12 @@ impl SourceToDefCtx<'_, '_> {
for container in src.cloned().ancestors_with_macros(self.db.upcast()).skip(1) { for container in src.cloned().ancestors_with_macros(self.db.upcast()).skip(1) {
let res: GenericDefId = match_ast! { let res: GenericDefId = match_ast! {
match (container.value) { match (container.value) {
ast::FnDef(it) => self.fn_to_def(container.with_value(it))?.into(), ast::Fn(it) => self.fn_to_def(container.with_value(it))?.into(),
ast::StructDef(it) => self.struct_to_def(container.with_value(it))?.into(), ast::Struct(it) => self.struct_to_def(container.with_value(it))?.into(),
ast::EnumDef(it) => self.enum_to_def(container.with_value(it))?.into(), ast::Enum(it) => self.enum_to_def(container.with_value(it))?.into(),
ast::TraitDef(it) => self.trait_to_def(container.with_value(it))?.into(), ast::Trait(it) => self.trait_to_def(container.with_value(it))?.into(),
ast::TypeAliasDef(it) => self.type_alias_to_def(container.with_value(it))?.into(), ast::TypeAlias(it) => self.type_alias_to_def(container.with_value(it))?.into(),
ast::ImplDef(it) => self.impl_to_def(container.with_value(it))?.into(), ast::Impl(it) => self.impl_to_def(container.with_value(it))?.into(),
_ => continue, _ => continue,
} }
}; };
@ -226,9 +222,9 @@ impl SourceToDefCtx<'_, '_> {
for container in src.cloned().ancestors_with_macros(self.db.upcast()).skip(1) { for container in src.cloned().ancestors_with_macros(self.db.upcast()).skip(1) {
let res: DefWithBodyId = match_ast! { let res: DefWithBodyId = match_ast! {
match (container.value) { match (container.value) {
ast::ConstDef(it) => self.const_to_def(container.with_value(it))?.into(), ast::Const(it) => self.const_to_def(container.with_value(it))?.into(),
ast::StaticDef(it) => self.static_to_def(container.with_value(it))?.into(), ast::Static(it) => self.static_to_def(container.with_value(it))?.into(),
ast::FnDef(it) => self.fn_to_def(container.with_value(it))?.into(), ast::Fn(it) => self.fn_to_def(container.with_value(it))?.into(),
_ => continue, _ => continue,
} }
}; };
@ -246,19 +242,21 @@ pub(crate) enum ChildContainer {
ImplId(ImplId), ImplId(ImplId),
EnumId(EnumId), EnumId(EnumId),
VariantId(VariantId), VariantId(VariantId),
TypeAliasId(TypeAliasId),
/// XXX: this might be the same def as, for example an `EnumId`. However, /// XXX: this might be the same def as, for example an `EnumId`. However,
/// here the children generic parameters, and not, eg enum variants. /// here the children generic parameters, and not, eg enum variants.
GenericDefId(GenericDefId), GenericDefId(GenericDefId),
} }
impl_froms! { impl_from! {
ChildContainer:
DefWithBodyId, DefWithBodyId,
ModuleId, ModuleId,
TraitId, TraitId,
ImplId, ImplId,
EnumId, EnumId,
VariantId, VariantId,
TypeAliasId,
GenericDefId GenericDefId
for ChildContainer
} }
impl ChildContainer { impl ChildContainer {
@ -271,6 +269,7 @@ impl ChildContainer {
ChildContainer::ImplId(it) => it.child_by_source(db), ChildContainer::ImplId(it) => it.child_by_source(db),
ChildContainer::EnumId(it) => it.child_by_source(db), ChildContainer::EnumId(it) => it.child_by_source(db),
ChildContainer::VariantId(it) => it.child_by_source(db), ChildContainer::VariantId(it) => it.child_by_source(db),
ChildContainer::TypeAliasId(_) => DynMap::default(),
ChildContainer::GenericDefId(it) => it.child_by_source(db), ChildContainer::GenericDefId(it) => it.child_by_source(db),
} }
} }

View file

@ -14,11 +14,11 @@ use hir_def::{
}, },
expr::{ExprId, Pat, PatId}, expr::{ExprId, Pat, PatId},
resolver::{resolver_for_scope, Resolver, TypeNs, ValueNs}, resolver::{resolver_for_scope, Resolver, TypeNs, ValueNs},
AsMacroCall, DefWithBodyId, FieldId, LocalFieldId, VariantId, AsMacroCall, DefWithBodyId, FieldId, FunctionId, LocalFieldId, VariantId,
}; };
use hir_expand::{hygiene::Hygiene, name::AsName, HirFileId, InFile}; use hir_expand::{hygiene::Hygiene, name::AsName, HirFileId, InFile};
use hir_ty::{ use hir_ty::{
expr::{record_literal_missing_fields, record_pattern_missing_fields}, diagnostics::{record_literal_missing_fields, record_pattern_missing_fields},
InferenceResult, Substs, Ty, InferenceResult, Substs, Ty,
}; };
use ra_syntax::{ use ra_syntax::{
@ -115,7 +115,7 @@ impl SourceAnalyzer {
Some(res) Some(res)
} }
pub(crate) fn type_of(&self, db: &dyn HirDatabase, expr: &ast::Expr) -> Option<Type> { pub(crate) fn type_of_expr(&self, db: &dyn HirDatabase, expr: &ast::Expr) -> Option<Type> {
let expr_id = self.expr_id(db, expr)?; let expr_id = self.expr_id(db, expr)?;
let ty = self.infer.as_ref()?[expr_id].clone(); let ty = self.infer.as_ref()?[expr_id].clone();
Type::new_with_resolver(db, &self.resolver, ty) Type::new_with_resolver(db, &self.resolver, ty)
@ -127,13 +127,24 @@ impl SourceAnalyzer {
Type::new_with_resolver(db, &self.resolver, ty) Type::new_with_resolver(db, &self.resolver, 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.as_ref()?.node_self_param(src)?;
let ty = self.infer.as_ref()?[pat_id].clone();
Type::new_with_resolver(db, &self.resolver, ty)
}
pub(crate) fn resolve_method_call( pub(crate) fn resolve_method_call(
&self, &self,
db: &dyn HirDatabase, db: &dyn HirDatabase,
call: &ast::MethodCallExpr, call: &ast::MethodCallExpr,
) -> Option<Function> { ) -> Option<FunctionId> {
let expr_id = self.expr_id(db, &call.clone().into())?; let expr_id = self.expr_id(db, &call.clone().into())?;
self.infer.as_ref()?.method_resolution(expr_id).map(Function::from) self.infer.as_ref()?.method_resolution(expr_id)
} }
pub(crate) fn resolve_field( pub(crate) fn resolve_field(
@ -148,7 +159,7 @@ impl SourceAnalyzer {
pub(crate) fn resolve_record_field( pub(crate) fn resolve_record_field(
&self, &self,
db: &dyn HirDatabase, db: &dyn HirDatabase,
field: &ast::RecordField, field: &ast::RecordExprField,
) -> Option<(Field, Option<Local>)> { ) -> Option<(Field, Option<Local>)> {
let expr = field.expr()?; let expr = field.expr()?;
let expr_id = self.expr_id(db, &expr)?; let expr_id = self.expr_id(db, &expr)?;
@ -235,7 +246,7 @@ impl SourceAnalyzer {
} }
} }
if let Some(rec_lit) = path.syntax().parent().and_then(ast::RecordLit::cast) { if let Some(rec_lit) = path.syntax().parent().and_then(ast::RecordExpr::cast) {
let expr_id = self.expr_id(db, &rec_lit.into())?; let expr_id = self.expr_id(db, &rec_lit.into())?;
if let Some(VariantId::EnumVariantId(variant)) = if let Some(VariantId::EnumVariantId(variant)) =
self.infer.as_ref()?.variant_resolution_for_expr(expr_id) self.infer.as_ref()?.variant_resolution_for_expr(expr_id)
@ -273,7 +284,7 @@ impl SourceAnalyzer {
pub(crate) fn record_literal_missing_fields( pub(crate) fn record_literal_missing_fields(
&self, &self,
db: &dyn HirDatabase, db: &dyn HirDatabase,
literal: &ast::RecordLit, literal: &ast::RecordExpr,
) -> Option<Vec<(Field, Type)>> { ) -> Option<Vec<(Field, Type)>> {
let krate = self.resolver.krate()?; let krate = self.resolver.krate()?;
let body = self.body.as_ref()?; let body = self.body.as_ref()?;
@ -341,13 +352,13 @@ impl SourceAnalyzer {
let macro_call_id = macro_call.as_call_id(db.upcast(), krate, |path| { let macro_call_id = macro_call.as_call_id(db.upcast(), krate, |path| {
self.resolver.resolve_path_as_macro(db.upcast(), &path) self.resolver.resolve_path_as_macro(db.upcast(), &path)
})?; })?;
Some(macro_call_id.as_file()) Some(macro_call_id.as_file()).filter(|it| it.expansion_level(db.upcast()) < 64)
} }
pub(crate) fn resolve_variant( pub(crate) fn resolve_variant(
&self, &self,
db: &dyn HirDatabase, db: &dyn HirDatabase,
record_lit: ast::RecordLit, record_lit: ast::RecordExpr,
) -> Option<VariantId> { ) -> Option<VariantId> {
let infer = self.infer.as_ref()?; let infer = self.infer.as_ref()?;
let expr_id = self.expr_id(db, &record_lit.into())?; let expr_id = self.expr_id(db, &record_lit.into())?;
@ -394,8 +405,7 @@ fn scope_for_offset(
) )
}) })
.map(|(expr_range, scope)| { .map(|(expr_range, scope)| {
adjust(db, scopes, source_map, expr_range, offset.file_id, offset.value) adjust(db, scopes, source_map, expr_range, offset).unwrap_or(*scope)
.unwrap_or(*scope)
}) })
} }
@ -406,8 +416,7 @@ fn adjust(
scopes: &ExprScopes, scopes: &ExprScopes,
source_map: &BodySourceMap, source_map: &BodySourceMap,
expr_range: TextRange, expr_range: TextRange,
file_id: HirFileId, offset: InFile<TextSize>,
offset: TextSize,
) -> Option<ScopeId> { ) -> Option<ScopeId> {
let child_scopes = scopes let child_scopes = scopes
.scope_by_expr() .scope_by_expr()
@ -415,7 +424,7 @@ fn adjust(
.filter_map(|(id, scope)| { .filter_map(|(id, scope)| {
let source = source_map.expr_syntax(*id).ok()?; let source = source_map.expr_syntax(*id).ok()?;
// FIXME: correctly handle macro expansion // FIXME: correctly handle macro expansion
if source.file_id != file_id { if source.file_id != offset.file_id {
return None; return None;
} }
let root = source.file_syntax(db.upcast()); let root = source.file_syntax(db.upcast());
@ -423,7 +432,7 @@ fn adjust(
Some((node.syntax().text_range(), scope)) Some((node.syntax().text_range(), scope))
}) })
.filter(|&(range, _)| { .filter(|&(range, _)| {
range.start() <= offset && expr_range.contains_range(range) && range != expr_range range.start() <= offset.value && expr_range.contains_range(range) && range != expr_range
}); });
child_scopes child_scopes

View file

@ -3,6 +3,7 @@ edition = "2018"
name = "ra_hir_def" name = "ra_hir_def"
version = "0.1.0" version = "0.1.0"
authors = ["rust-analyzer developers"] authors = ["rust-analyzer developers"]
license = "MIT OR Apache-2.0"
[lib] [lib]
doctest = false doctest = false
@ -32,4 +33,4 @@ ra_cfg = { path = "../ra_cfg" }
tt = { path = "../ra_tt", package = "ra_tt" } tt = { path = "../ra_tt", package = "ra_tt" }
[dev-dependencies] [dev-dependencies]
insta = "0.16.0" expect = { path = "../expect" }

View file

@ -8,7 +8,7 @@ use hir_expand::{
InFile, InFile,
}; };
use ra_arena::{map::ArenaMap, Arena}; use ra_arena::{map::ArenaMap, Arena};
use ra_syntax::ast::{self, NameOwner, TypeAscriptionOwner, VisibilityOwner}; use ra_syntax::ast::{self, NameOwner, VisibilityOwner};
use crate::{ use crate::{
body::{CfgExpander, LowerCtx}, body::{CfgExpander, LowerCtx},
@ -112,7 +112,7 @@ impl EnumData {
impl HasChildSource for EnumId { impl HasChildSource for EnumId {
type ChildId = LocalEnumVariantId; type ChildId = LocalEnumVariantId;
type Value = ast::EnumVariant; type Value = ast::Variant;
fn child_source(&self, db: &dyn DefDatabase) -> InFile<ArenaMap<Self::ChildId, Self::Value>> { fn child_source(&self, db: &dyn DefDatabase) -> InFile<ArenaMap<Self::ChildId, Self::Value>> {
let src = self.lookup(db).source(db); let src = self.lookup(db).source(db);
let mut trace = Trace::new_for_map(); let mut trace = Trace::new_for_map();
@ -123,8 +123,8 @@ impl HasChildSource for EnumId {
fn lower_enum( fn lower_enum(
db: &dyn DefDatabase, db: &dyn DefDatabase,
trace: &mut Trace<EnumVariantData, ast::EnumVariant>, trace: &mut Trace<EnumVariantData, ast::Variant>,
ast: &InFile<ast::EnumDef>, ast: &InFile<ast::Enum>,
module_id: ModuleId, module_id: ModuleId,
) { ) {
let expander = CfgExpander::new(db, ast.file_id, module_id.krate); let expander = CfgExpander::new(db, ast.file_id, module_id.krate);
@ -179,7 +179,7 @@ impl VariantData {
impl HasChildSource for VariantId { impl HasChildSource for VariantId {
type ChildId = LocalFieldId; type ChildId = LocalFieldId;
type Value = Either<ast::TupleFieldDef, ast::RecordFieldDef>; type Value = Either<ast::TupleField, ast::RecordField>;
fn child_source(&self, db: &dyn DefDatabase) -> InFile<ArenaMap<Self::ChildId, Self::Value>> { fn child_source(&self, db: &dyn DefDatabase) -> InFile<ArenaMap<Self::ChildId, Self::Value>> {
let (src, module_id) = match self { let (src, module_id) = match self {
@ -194,7 +194,7 @@ impl HasChildSource for VariantId {
} }
VariantId::UnionId(it) => ( VariantId::UnionId(it) => (
it.lookup(db).source(db).map(|it| { it.lookup(db).source(db).map(|it| {
it.record_field_def_list() it.record_field_list()
.map(ast::StructKind::Record) .map(ast::StructKind::Record)
.unwrap_or(ast::StructKind::Unit) .unwrap_or(ast::StructKind::Unit)
}), }),
@ -218,7 +218,7 @@ pub enum StructKind {
fn lower_struct( fn lower_struct(
db: &dyn DefDatabase, db: &dyn DefDatabase,
expander: &mut CfgExpander, expander: &mut CfgExpander,
trace: &mut Trace<FieldData, Either<ast::TupleFieldDef, ast::RecordFieldDef>>, trace: &mut Trace<FieldData, Either<ast::TupleField, ast::RecordField>>,
ast: &InFile<ast::StructKind>, ast: &InFile<ast::StructKind>,
) -> StructKind { ) -> StructKind {
let ctx = LowerCtx::new(db, ast.file_id); let ctx = LowerCtx::new(db, ast.file_id);
@ -234,7 +234,7 @@ fn lower_struct(
|| Either::Left(fd.clone()), || Either::Left(fd.clone()),
|| FieldData { || FieldData {
name: Name::new_tuple_field(i), name: Name::new_tuple_field(i),
type_ref: TypeRef::from_ast_opt(&ctx, fd.type_ref()), type_ref: TypeRef::from_ast_opt(&ctx, fd.ty()),
visibility: RawVisibility::from_ast(db, ast.with_value(fd.visibility())), visibility: RawVisibility::from_ast(db, ast.with_value(fd.visibility())),
}, },
); );
@ -251,7 +251,7 @@ fn lower_struct(
|| Either::Right(fd.clone()), || Either::Right(fd.clone()),
|| FieldData { || FieldData {
name: fd.name().map(|n| n.as_name()).unwrap_or_else(Name::missing), name: fd.name().map(|n| n.as_name()).unwrap_or_else(Name::missing),
type_ref: TypeRef::from_ast_opt(&ctx, fd.ascribed_type()), type_ref: TypeRef::from_ast_opt(&ctx, fd.ty()),
visibility: RawVisibility::from_ast(db, ast.with_value(fd.visibility())), visibility: RawVisibility::from_ast(db, ast.with_value(fd.visibility())),
}, },
); );

View file

@ -5,7 +5,7 @@ use std::{ops, sync::Arc};
use either::Either; use either::Either;
use hir_expand::{hygiene::Hygiene, AstId, InFile}; use hir_expand::{hygiene::Hygiene, AstId, InFile};
use mbe::ast_to_token_tree; use mbe::ast_to_token_tree;
use ra_cfg::CfgOptions; use ra_cfg::{CfgExpr, CfgOptions};
use ra_syntax::{ use ra_syntax::{
ast::{self, AstNode, AttrsOwner}, ast::{self, AstNode, AttrsOwner},
SmolStr, SmolStr,
@ -125,9 +125,12 @@ impl Attrs {
AttrQuery { attrs: self, key } AttrQuery { attrs: self, key }
} }
pub(crate) fn is_cfg_enabled(&self, cfg_options: &CfgOptions) -> bool { pub fn cfg(&self) -> impl Iterator<Item = CfgExpr> + '_ {
// FIXME: handle cfg_attr :-) // FIXME: handle cfg_attr :-)
self.by_key("cfg").tt_values().all(|tt| cfg_options.is_cfg_enabled(tt) != Some(false)) self.by_key("cfg").tt_values().map(CfgExpr::parse)
}
pub(crate) fn is_cfg_enabled(&self, cfg_options: &CfgOptions) -> bool {
self.cfg().all(|cfg| cfg_options.check(&cfg) != Some(false))
} }
} }
@ -148,18 +151,15 @@ pub enum AttrInput {
impl Attr { impl Attr {
fn from_src(ast: ast::Attr, hygiene: &Hygiene) -> Option<Attr> { fn from_src(ast: ast::Attr, hygiene: &Hygiene) -> Option<Attr> {
let path = ModPath::from_src(ast.path()?, hygiene)?; let path = ModPath::from_src(ast.path()?, hygiene)?;
let input = match ast.input() { let input = if let Some(lit) = ast.literal() {
None => None,
Some(ast::AttrInput::Literal(lit)) => {
// FIXME: escape? raw string? // FIXME: escape? raw string?
let value = lit.syntax().first_token()?.text().trim_matches('"').into(); let value = lit.syntax().first_token()?.text().trim_matches('"').into();
Some(AttrInput::Literal(value)) Some(AttrInput::Literal(value))
} } else if let Some(tt) = ast.token_tree() {
Some(ast::AttrInput::TokenTree(tt)) => {
Some(AttrInput::TokenTree(ast_to_token_tree(&tt)?.0)) Some(AttrInput::TokenTree(ast_to_token_tree(&tt)?.0))
} } else {
None
}; };
Some(Attr { path, input }) Some(Attr { path, input })
} }
} }

View file

@ -14,6 +14,7 @@ use ra_db::CrateId;
use ra_prof::profile; use ra_prof::profile;
use ra_syntax::{ast, AstNode, AstPtr}; use ra_syntax::{ast, AstNode, AstPtr};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use test_utils::mark;
pub(crate) use lower::LowerCtx; pub(crate) use lower::LowerCtx;
@ -42,9 +43,15 @@ pub(crate) struct Expander {
current_file_id: HirFileId, current_file_id: HirFileId,
ast_id_map: Arc<AstIdMap>, ast_id_map: Arc<AstIdMap>,
module: ModuleId, module: ModuleId,
recursive_limit: usize, recursion_limit: usize,
} }
#[cfg(test)]
const EXPANSION_RECURSION_LIMIT: usize = 32;
#[cfg(not(test))]
const EXPANSION_RECURSION_LIMIT: usize = 128;
impl CfgExpander { impl CfgExpander {
pub(crate) fn new( pub(crate) fn new(
db: &dyn DefDatabase, db: &dyn DefDatabase,
@ -81,7 +88,7 @@ impl Expander {
current_file_id, current_file_id,
ast_id_map, ast_id_map,
module, module,
recursive_limit: 0, recursion_limit: 0,
} }
} }
@ -91,7 +98,9 @@ impl Expander {
local_scope: Option<&ItemScope>, local_scope: Option<&ItemScope>,
macro_call: ast::MacroCall, macro_call: ast::MacroCall,
) -> Option<(Mark, T)> { ) -> Option<(Mark, T)> {
if self.recursive_limit > 1024 { self.recursion_limit += 1;
if self.recursion_limit > EXPANSION_RECURSION_LIMIT {
mark::hit!(your_stack_belongs_to_me);
return None; return None;
} }
@ -118,8 +127,6 @@ impl Expander {
self.cfg_expander.hygiene = Hygiene::new(db.upcast(), file_id); self.cfg_expander.hygiene = Hygiene::new(db.upcast(), file_id);
self.current_file_id = file_id; self.current_file_id = file_id;
self.ast_id_map = db.ast_id_map(file_id); self.ast_id_map = db.ast_id_map(file_id);
self.recursive_limit += 1;
return Some((mark, expr)); return Some((mark, expr));
} }
} }
@ -134,7 +141,7 @@ impl Expander {
self.cfg_expander.hygiene = Hygiene::new(db.upcast(), mark.file_id); self.cfg_expander.hygiene = Hygiene::new(db.upcast(), mark.file_id);
self.current_file_id = mark.file_id; self.current_file_id = mark.file_id;
self.ast_id_map = mem::take(&mut mark.ast_id_map); self.ast_id_map = mem::take(&mut mark.ast_id_map);
self.recursive_limit -= 1; self.recursion_limit -= 1;
mark.bomb.defuse(); mark.bomb.defuse();
} }
@ -209,7 +216,7 @@ pub struct BodySourceMap {
expr_map_back: ArenaMap<ExprId, Result<ExprSource, SyntheticSyntax>>, expr_map_back: ArenaMap<ExprId, Result<ExprSource, SyntheticSyntax>>,
pat_map: FxHashMap<PatSource, PatId>, pat_map: FxHashMap<PatSource, PatId>,
pat_map_back: ArenaMap<PatId, Result<PatSource, SyntheticSyntax>>, pat_map_back: ArenaMap<PatId, Result<PatSource, SyntheticSyntax>>,
field_map: FxHashMap<(ExprId, usize), InFile<AstPtr<ast::RecordField>>>, field_map: FxHashMap<(ExprId, usize), InFile<AstPtr<ast::RecordExprField>>>,
expansions: FxHashMap<InFile<AstPtr<ast::MacroCall>>, HirFileId>, expansions: FxHashMap<InFile<AstPtr<ast::MacroCall>>, HirFileId>,
} }
@ -302,7 +309,53 @@ impl BodySourceMap {
self.pat_map.get(&src).cloned() self.pat_map.get(&src).cloned()
} }
pub fn field_syntax(&self, expr: ExprId, field: usize) -> InFile<AstPtr<ast::RecordField>> { 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 field_syntax(&self, expr: ExprId, field: usize) -> InFile<AstPtr<ast::RecordExprField>> {
self.field_map[&(expr, field)].clone() self.field_map[&(expr, field)].clone()
} }
} }
#[cfg(test)]
mod tests {
use ra_db::{fixture::WithFixture, SourceDatabase};
use test_utils::mark;
use crate::ModuleDefId;
use super::*;
fn lower(ra_fixture: &str) -> Arc<Body> {
let (db, file_id) = crate::test_db::TestDB::with_single_file(ra_fixture);
let krate = db.crate_graph().iter().next().unwrap();
let def_map = db.crate_def_map(krate);
let module = def_map.modules_for_file(file_id).next().unwrap();
let module = &def_map[module];
let fn_def = match module.scope.declarations().next().unwrap() {
ModuleDefId::FunctionId(it) => it,
_ => panic!(),
};
db.body(fn_def.into())
}
#[test]
fn your_stack_belongs_to_me() {
mark::check!(your_stack_belongs_to_me);
lower(
"
macro_rules! n_nuple {
($e:tt) => ();
($($rest:tt)*) => {{
(n_nuple!($($rest)*)None,)
}};
}
fn main() { n_nuple!(1,2,3); }
",
);
}
}

View file

@ -11,7 +11,7 @@ use ra_arena::Arena;
use ra_syntax::{ use ra_syntax::{
ast::{ ast::{
self, ArgListOwner, ArrayExprKind, LiteralKind, LoopBodyOwner, ModuleItemOwner, NameOwner, self, ArgListOwner, ArrayExprKind, LiteralKind, LoopBodyOwner, ModuleItemOwner, NameOwner,
SlicePatComponents, TypeAscriptionOwner, SlicePatComponents,
}, },
AstNode, AstPtr, AstNode, AstPtr,
}; };
@ -379,10 +379,10 @@ impl ExprCollector<'_> {
let expr = e.expr().map(|e| self.collect_expr(e)); let expr = e.expr().map(|e| self.collect_expr(e));
self.alloc_expr(Expr::Return { expr }, syntax_ptr) self.alloc_expr(Expr::Return { expr }, syntax_ptr)
} }
ast::Expr::RecordLit(e) => { ast::Expr::RecordExpr(e) => {
let path = e.path().and_then(|path| self.expander.parse_path(path)); let path = e.path().and_then(|path| self.expander.parse_path(path));
let mut field_ptrs = Vec::new(); let mut field_ptrs = Vec::new();
let record_lit = if let Some(nfl) = e.record_field_list() { let record_lit = if let Some(nfl) = e.record_expr_field_list() {
let fields = nfl let fields = nfl
.fields() .fields()
.inspect(|field| field_ptrs.push(AstPtr::new(field))) .inspect(|field| field_ptrs.push(AstPtr::new(field)))
@ -432,7 +432,7 @@ impl ExprCollector<'_> {
} }
ast::Expr::CastExpr(e) => { ast::Expr::CastExpr(e) => {
let expr = self.collect_expr_opt(e.expr()); let expr = self.collect_expr_opt(e.expr());
let type_ref = TypeRef::from_ast_opt(&self.ctx(), e.type_ref()); let type_ref = TypeRef::from_ast_opt(&self.ctx(), e.ty());
self.alloc_expr(Expr::Cast { expr, type_ref }, syntax_ptr) self.alloc_expr(Expr::Cast { expr, type_ref }, syntax_ptr)
} }
ast::Expr::RefExpr(e) => { ast::Expr::RefExpr(e) => {
@ -466,16 +466,13 @@ impl ExprCollector<'_> {
if let Some(pl) = e.param_list() { if let Some(pl) = e.param_list() {
for param in pl.params() { for param in pl.params() {
let pat = self.collect_pat_opt(param.pat()); let pat = self.collect_pat_opt(param.pat());
let type_ref = let type_ref = param.ty().map(|it| TypeRef::from_ast(&self.ctx(), it));
param.ascribed_type().map(|it| TypeRef::from_ast(&self.ctx(), it));
args.push(pat); args.push(pat);
arg_types.push(type_ref); arg_types.push(type_ref);
} }
} }
let ret_type = e let ret_type =
.ret_type() e.ret_type().and_then(|r| r.ty()).map(|it| TypeRef::from_ast(&self.ctx(), it));
.and_then(|r| r.type_ref())
.map(|it| TypeRef::from_ast(&self.ctx(), it));
let body = self.collect_expr_opt(e.body()); let body = self.collect_expr_opt(e.body());
self.alloc_expr(Expr::Lambda { args, arg_types, ret_type, body }, syntax_ptr) self.alloc_expr(Expr::Lambda { args, arg_types, ret_type, body }, syntax_ptr)
} }
@ -607,8 +604,7 @@ impl ExprCollector<'_> {
.map(|s| match s { .map(|s| match s {
ast::Stmt::LetStmt(stmt) => { ast::Stmt::LetStmt(stmt) => {
let pat = self.collect_pat_opt(stmt.pat()); let pat = self.collect_pat_opt(stmt.pat());
let type_ref = let type_ref = stmt.ty().map(|it| TypeRef::from_ast(&self.ctx(), it));
stmt.ascribed_type().map(|it| TypeRef::from_ast(&self.ctx(), it));
let initializer = stmt.initializer().map(|e| self.collect_expr(e)); let initializer = stmt.initializer().map(|e| self.collect_expr(e));
Statement::Let { pat, type_ref, initializer } Statement::Let { pat, type_ref, initializer }
} }
@ -627,53 +623,53 @@ impl ExprCollector<'_> {
.items() .items()
.filter_map(|item| { .filter_map(|item| {
let (def, name): (ModuleDefId, Option<ast::Name>) = match item { let (def, name): (ModuleDefId, Option<ast::Name>) = match item {
ast::ModuleItem::FnDef(def) => { ast::Item::Fn(def) => {
let id = self.find_inner_item(&def)?; let id = self.find_inner_item(&def)?;
( (
FunctionLoc { container: container.into(), id }.intern(self.db).into(), FunctionLoc { container: container.into(), id }.intern(self.db).into(),
def.name(), def.name(),
) )
} }
ast::ModuleItem::TypeAliasDef(def) => { ast::Item::TypeAlias(def) => {
let id = self.find_inner_item(&def)?; let id = self.find_inner_item(&def)?;
( (
TypeAliasLoc { container: container.into(), id }.intern(self.db).into(), TypeAliasLoc { container: container.into(), id }.intern(self.db).into(),
def.name(), def.name(),
) )
} }
ast::ModuleItem::ConstDef(def) => { ast::Item::Const(def) => {
let id = self.find_inner_item(&def)?; let id = self.find_inner_item(&def)?;
( (
ConstLoc { container: container.into(), id }.intern(self.db).into(), ConstLoc { container: container.into(), id }.intern(self.db).into(),
def.name(), def.name(),
) )
} }
ast::ModuleItem::StaticDef(def) => { ast::Item::Static(def) => {
let id = self.find_inner_item(&def)?; let id = self.find_inner_item(&def)?;
(StaticLoc { container, id }.intern(self.db).into(), def.name()) (StaticLoc { container, id }.intern(self.db).into(), def.name())
} }
ast::ModuleItem::StructDef(def) => { ast::Item::Struct(def) => {
let id = self.find_inner_item(&def)?; let id = self.find_inner_item(&def)?;
(StructLoc { container, id }.intern(self.db).into(), def.name()) (StructLoc { container, id }.intern(self.db).into(), def.name())
} }
ast::ModuleItem::EnumDef(def) => { ast::Item::Enum(def) => {
let id = self.find_inner_item(&def)?; let id = self.find_inner_item(&def)?;
(EnumLoc { container, id }.intern(self.db).into(), def.name()) (EnumLoc { container, id }.intern(self.db).into(), def.name())
} }
ast::ModuleItem::UnionDef(def) => { ast::Item::Union(def) => {
let id = self.find_inner_item(&def)?; let id = self.find_inner_item(&def)?;
(UnionLoc { container, id }.intern(self.db).into(), def.name()) (UnionLoc { container, id }.intern(self.db).into(), def.name())
} }
ast::ModuleItem::TraitDef(def) => { ast::Item::Trait(def) => {
let id = self.find_inner_item(&def)?; let id = self.find_inner_item(&def)?;
(TraitLoc { container, id }.intern(self.db).into(), def.name()) (TraitLoc { container, id }.intern(self.db).into(), def.name())
} }
ast::ModuleItem::ExternBlock(_) => return None, // FIXME: collect from extern blocks ast::Item::ExternBlock(_) => return None, // FIXME: collect from extern blocks
ast::ModuleItem::ImplDef(_) ast::Item::Impl(_)
| ast::ModuleItem::UseItem(_) | ast::Item::Use(_)
| ast::ModuleItem::ExternCrateItem(_) | ast::Item::ExternCrate(_)
| ast::ModuleItem::Module(_) | ast::Item::Module(_)
| ast::ModuleItem::MacroCall(_) => return None, | ast::Item::MacroCall(_) => return None,
}; };
Some((def, name)) Some((def, name))

View file

@ -162,7 +162,7 @@ impl ChildBySource for EnumId {
let arena_map = arena_map.as_ref(); let arena_map = arena_map.as_ref();
for (local_id, source) in arena_map.value.iter() { for (local_id, source) in arena_map.value.iter() {
let id = EnumVariantId { parent: *self, local_id }; let id = EnumVariantId { parent: *self, local_id };
res[keys::ENUM_VARIANT].insert(arena_map.with_value(source.clone()), id) res[keys::VARIANT].insert(arena_map.with_value(source.clone()), id)
} }
res res

View file

@ -27,11 +27,12 @@ pub struct FunctionData {
/// can be called as a method. /// can be called as a method.
pub has_self_param: bool, pub has_self_param: bool,
pub is_unsafe: bool, pub is_unsafe: bool,
pub is_varargs: bool,
pub visibility: RawVisibility, pub visibility: RawVisibility,
} }
impl FunctionData { impl FunctionData {
pub(crate) fn fn_data_query(db: &impl DefDatabase, func: FunctionId) -> Arc<FunctionData> { pub(crate) fn fn_data_query(db: &dyn DefDatabase, func: FunctionId) -> Arc<FunctionData> {
let loc = func.lookup(db); let loc = func.lookup(db);
let item_tree = db.item_tree(loc.id.file_id); let item_tree = db.item_tree(loc.id.file_id);
let func = &item_tree[loc.id.value]; let func = &item_tree[loc.id.value];
@ -43,6 +44,7 @@ impl FunctionData {
attrs: item_tree.attrs(ModItem::from(loc.id.value).into()).clone(), attrs: item_tree.attrs(ModItem::from(loc.id.value).into()).clone(),
has_self_param: func.has_self_param, has_self_param: func.has_self_param,
is_unsafe: func.is_unsafe, is_unsafe: func.is_unsafe,
is_varargs: func.is_varargs,
visibility: item_tree[func.visibility].clone(), visibility: item_tree[func.visibility].clone(),
}) })
} }

View file

@ -12,7 +12,7 @@ use hir_expand::{
use ra_arena::{map::ArenaMap, Arena}; use ra_arena::{map::ArenaMap, Arena};
use ra_db::FileId; use ra_db::FileId;
use ra_prof::profile; use ra_prof::profile;
use ra_syntax::ast::{self, NameOwner, TypeBoundsOwner, TypeParamsOwner}; use ra_syntax::ast::{self, GenericParamsOwner, NameOwner, TypeBoundsOwner};
use crate::{ use crate::{
body::LowerCtx, body::LowerCtx,
@ -66,7 +66,7 @@ pub enum WherePredicateTarget {
TypeParam(LocalTypeParamId), TypeParam(LocalTypeParamId),
} }
type SourceMap = ArenaMap<LocalTypeParamId, Either<ast::TraitDef, ast::TypeParam>>; type SourceMap = ArenaMap<LocalTypeParamId, Either<ast::Trait, ast::TypeParam>>;
impl GenericParams { impl GenericParams {
pub(crate) fn generic_params_query( pub(crate) fn generic_params_query(
@ -205,9 +205,9 @@ impl GenericParams {
&mut self, &mut self,
lower_ctx: &LowerCtx, lower_ctx: &LowerCtx,
sm: &mut SourceMap, sm: &mut SourceMap,
node: &dyn TypeParamsOwner, node: &dyn GenericParamsOwner,
) { ) {
if let Some(params) = node.type_param_list() { if let Some(params) = node.generic_param_list() {
self.fill_params(lower_ctx, sm, params) self.fill_params(lower_ctx, sm, params)
} }
if let Some(where_clause) = node.where_clause() { if let Some(where_clause) = node.where_clause() {
@ -232,7 +232,7 @@ impl GenericParams {
&mut self, &mut self,
lower_ctx: &LowerCtx, lower_ctx: &LowerCtx,
sm: &mut SourceMap, sm: &mut SourceMap,
params: ast::TypeParamList, params: ast::GenericParamList,
) { ) {
for type_param in params.type_params() { for type_param in params.type_params() {
let name = type_param.name().map_or_else(Name::missing, |it| it.as_name()); let name = type_param.name().map_or_else(Name::missing, |it| it.as_name());
@ -317,7 +317,7 @@ impl GenericParams {
impl HasChildSource for GenericDefId { impl HasChildSource for GenericDefId {
type ChildId = LocalTypeParamId; type ChildId = LocalTypeParamId;
type Value = Either<ast::TraitDef, ast::TypeParam>; type Value = Either<ast::Trait, ast::TypeParam>;
fn child_source(&self, db: &dyn DefDatabase) -> InFile<SourceMap> { fn child_source(&self, db: &dyn DefDatabase) -> InFile<SourceMap> {
let (_, sm) = GenericParams::new(db, *self); let (_, sm) = GenericParams::new(db, *self);
sm sm

View file

@ -5,14 +5,16 @@ use std::{cmp::Ordering, fmt, hash::BuildHasherDefault, sync::Arc};
use fst::{self, Streamer}; use fst::{self, Streamer};
use indexmap::{map::Entry, IndexMap}; use indexmap::{map::Entry, IndexMap};
use ra_db::CrateId; use ra_db::CrateId;
use rustc_hash::FxHasher; use ra_syntax::SmolStr;
use rustc_hash::{FxHashMap, FxHasher};
use smallvec::SmallVec;
use crate::{ use crate::{
db::DefDatabase, db::DefDatabase,
item_scope::ItemInNs, item_scope::ItemInNs,
path::{ModPath, PathKind}, path::{ModPath, PathKind},
visibility::Visibility, visibility::Visibility,
ModuleDefId, ModuleId, AssocItemId, ModuleDefId, ModuleId, TraitId,
}; };
type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<FxHasher>>; type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<FxHasher>>;
@ -34,6 +36,7 @@ pub struct ImportInfo {
/// ///
/// Note that all paths are relative to the containing crate's root, so the crate name still needs /// Note that all paths are relative to the containing crate's root, so the crate name still needs
/// to be prepended to the `ModPath` before the path is valid. /// to be prepended to the `ModPath` before the path is valid.
#[derive(Default)]
pub struct ImportMap { pub struct ImportMap {
map: FxIndexMap<ItemInNs, ImportInfo>, map: FxIndexMap<ItemInNs, ImportInfo>,
@ -45,13 +48,17 @@ pub struct ImportMap {
/// the index of the first one. /// the index of the first one.
importables: Vec<ItemInNs>, importables: Vec<ItemInNs>,
fst: fst::Map<Vec<u8>>, fst: fst::Map<Vec<u8>>,
/// Maps names of associated items to the item's ID. Only includes items whose defining trait is
/// exported.
assoc_map: FxHashMap<SmolStr, SmallVec<[AssocItemId; 1]>>,
} }
impl ImportMap { impl ImportMap {
pub fn import_map_query(db: &dyn DefDatabase, krate: CrateId) -> Arc<Self> { pub fn import_map_query(db: &dyn DefDatabase, krate: CrateId) -> Arc<Self> {
let _p = ra_prof::profile("import_map_query"); let _p = ra_prof::profile("import_map_query");
let def_map = db.crate_def_map(krate); let def_map = db.crate_def_map(krate);
let mut import_map = FxIndexMap::with_capacity_and_hasher(64, Default::default()); let mut import_map = Self::default();
// We look only into modules that are public(ly reexported), starting with the crate root. // We look only into modules that are public(ly reexported), starting with the crate root.
let empty = ModPath { kind: PathKind::Plain, segments: vec![] }; let empty = ModPath { kind: PathKind::Plain, segments: vec![] };
@ -85,7 +92,7 @@ impl ImportMap {
for item in per_ns.iter_items() { for item in per_ns.iter_items() {
let path = mk_path(); let path = mk_path();
match import_map.entry(item) { match import_map.map.entry(item) {
Entry::Vacant(entry) => { Entry::Vacant(entry) => {
entry.insert(ImportInfo { path, container: module }); entry.insert(ImportInfo { path, container: module });
} }
@ -105,11 +112,16 @@ impl ImportMap {
if let Some(ModuleDefId::ModuleId(mod_id)) = item.as_module_def_id() { if let Some(ModuleDefId::ModuleId(mod_id)) = item.as_module_def_id() {
worklist.push((mod_id, mk_path())); worklist.push((mod_id, mk_path()));
} }
// If we've added a path to a trait, add the trait's methods to the method map.
if let Some(ModuleDefId::TraitId(tr)) = item.as_module_def_id() {
import_map.collect_trait_methods(db, tr);
}
} }
} }
} }
let mut importables = import_map.iter().collect::<Vec<_>>(); let mut importables = import_map.map.iter().collect::<Vec<_>>();
importables.sort_by(cmp); importables.sort_by(cmp);
@ -133,10 +145,10 @@ impl ImportMap {
builder.insert(key, start as u64).unwrap(); builder.insert(key, start as u64).unwrap();
} }
let fst = fst::Map::new(builder.into_inner().unwrap()).unwrap(); import_map.fst = fst::Map::new(builder.into_inner().unwrap()).unwrap();
let importables = importables.iter().map(|(item, _)| **item).collect(); import_map.importables = importables.iter().map(|(item, _)| **item).collect();
Arc::new(Self { map: import_map, fst, importables }) Arc::new(import_map)
} }
/// Returns the `ModPath` needed to import/mention `item`, relative to this crate's root. /// Returns the `ModPath` needed to import/mention `item`, relative to this crate's root.
@ -147,6 +159,13 @@ impl ImportMap {
pub fn import_info_for(&self, item: ItemInNs) -> Option<&ImportInfo> { pub fn import_info_for(&self, item: ItemInNs) -> Option<&ImportInfo> {
self.map.get(&item) self.map.get(&item)
} }
fn collect_trait_methods(&mut self, db: &dyn DefDatabase, tr: TraitId) {
let data = db.trait_data(tr);
for (name, item) in data.items.iter() {
self.assoc_map.entry(name.to_string().into()).or_default().push(*item);
}
}
} }
impl PartialEq for ImportMap { impl PartialEq for ImportMap {
@ -290,37 +309,32 @@ pub fn search_dependencies<'a>(
} }
} }
// Add all exported associated items whose names match the query (exactly).
for map in &import_maps {
if let Some(v) = map.assoc_map.get(&*query.query) {
res.extend(v.iter().map(|&assoc| {
ItemInNs::Types(match assoc {
AssocItemId::FunctionId(it) => it.into(),
AssocItemId::ConstId(it) => it.into(),
AssocItemId::TypeAliasId(it) => it.into(),
})
}));
}
}
res res
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use expect::{expect, Expect};
use ra_db::{fixture::WithFixture, SourceDatabase, Upcast};
use crate::{test_db::TestDB, AssocContainerId, Lookup};
use super::*; use super::*;
use crate::test_db::TestDB;
use insta::assert_snapshot;
use itertools::Itertools;
use ra_db::fixture::WithFixture;
use ra_db::{SourceDatabase, Upcast};
fn import_map(ra_fixture: &str) -> String { fn check_search(ra_fixture: &str, krate_name: &str, query: Query, expect: Expect) {
let db = TestDB::with_files(ra_fixture);
let crate_graph = db.crate_graph();
let s = crate_graph
.iter()
.filter_map(|krate| {
let cdata = &crate_graph[krate];
let name = cdata.display_name.as_ref()?;
let map = db.import_map(krate);
Some(format!("{}:\n{:?}", name, map))
})
.join("\n");
s
}
fn search_dependencies_of(ra_fixture: &str, krate_name: &str, query: Query) -> String {
let db = TestDB::with_files(ra_fixture); let db = TestDB::with_files(ra_fixture);
let crate_graph = db.crate_graph(); let crate_graph = db.crate_graph();
let krate = crate_graph let krate = crate_graph
@ -331,7 +345,7 @@ mod tests {
}) })
.unwrap(); .unwrap();
search_dependencies(db.upcast(), krate, query) let actual = search_dependencies(db.upcast(), krate, query)
.into_iter() .into_iter()
.filter_map(|item| { .filter_map(|item| {
let mark = match item { let mark = match item {
@ -339,23 +353,67 @@ mod tests {
ItemInNs::Values(_) => "v", ItemInNs::Values(_) => "v",
ItemInNs::Macros(_) => "m", ItemInNs::Macros(_) => "m",
}; };
let item = assoc_to_trait(&db, item);
item.krate(db.upcast()).map(|krate| { item.krate(db.upcast()).map(|krate| {
let map = db.import_map(krate); let map = db.import_map(krate);
let path = map.path_of(item).unwrap(); let path = map.path_of(item).unwrap();
format!( format!(
"{}::{} ({})", "{}::{} ({})\n",
crate_graph[krate].display_name.as_ref().unwrap(), crate_graph[krate].display_name.as_ref().unwrap(),
path, path,
mark mark
) )
}) })
}) })
.join("\n") .collect::<String>();
expect.assert_eq(&actual)
}
fn assoc_to_trait(db: &dyn DefDatabase, item: ItemInNs) -> ItemInNs {
let assoc: AssocItemId = match item {
ItemInNs::Types(it) | ItemInNs::Values(it) => match it {
ModuleDefId::TypeAliasId(it) => it.into(),
ModuleDefId::FunctionId(it) => it.into(),
ModuleDefId::ConstId(it) => it.into(),
_ => return item,
},
_ => return item,
};
let container = match assoc {
AssocItemId::FunctionId(it) => it.lookup(db).container,
AssocItemId::ConstId(it) => it.lookup(db).container,
AssocItemId::TypeAliasId(it) => it.lookup(db).container,
};
match container {
AssocContainerId::TraitId(it) => ItemInNs::Types(it.into()),
_ => item,
}
}
fn check(ra_fixture: &str, expect: Expect) {
let db = TestDB::with_files(ra_fixture);
let crate_graph = db.crate_graph();
let actual = crate_graph
.iter()
.filter_map(|krate| {
let cdata = &crate_graph[krate];
let name = cdata.display_name.as_ref()?;
let map = db.import_map(krate);
Some(format!("{}:\n{:?}\n", name, map))
})
.collect::<String>();
expect.assert_eq(&actual)
} }
#[test] #[test]
fn smoke() { fn smoke() {
let map = import_map( check(
r" r"
//- /main.rs crate:main deps:lib //- /main.rs crate:main deps:lib
@ -380,9 +438,7 @@ mod tests {
pub struct Pub2; // t + v pub struct Pub2; // t + v
struct Priv; struct Priv;
", ",
); expect![[r#"
assert_snapshot!(map, @r###"
main: main:
- publ1 (t) - publ1 (t)
- real_pu2 (t) - real_pu2 (t)
@ -392,12 +448,13 @@ mod tests {
- Pub (t) - Pub (t)
- Pub2 (t) - Pub2 (t)
- Pub2 (v) - Pub2 (v)
"###); "#]],
);
} }
#[test] #[test]
fn prefers_shortest_path() { fn prefers_shortest_path() {
let map = import_map( check(
r" r"
//- /main.rs crate:main //- /main.rs crate:main
@ -409,21 +466,20 @@ mod tests {
pub use super::sub::subsub::Def; pub use super::sub::subsub::Def;
} }
", ",
); expect![[r#"
assert_snapshot!(map, @r###"
main: main:
- sub (t) - sub (t)
- sub::Def (t) - sub::Def (t)
- sub::subsub (t) - sub::subsub (t)
"###); "#]],
);
} }
#[test] #[test]
fn type_reexport_cross_crate() { fn type_reexport_cross_crate() {
// Reexports need to be visible from a crate, even if the original crate exports the item // Reexports need to be visible from a crate, even if the original crate exports the item
// at a shorter path. // at a shorter path.
let map = import_map( check(
r" r"
//- /main.rs crate:main deps:lib //- /main.rs crate:main deps:lib
pub mod m { pub mod m {
@ -432,9 +488,7 @@ mod tests {
//- /lib.rs crate:lib //- /lib.rs crate:lib
pub struct S; pub struct S;
", ",
); expect![[r#"
assert_snapshot!(map, @r###"
main: main:
- m (t) - m (t)
- m::S (t) - m::S (t)
@ -442,12 +496,13 @@ mod tests {
lib: lib:
- S (t) - S (t)
- S (v) - S (v)
"###); "#]],
);
} }
#[test] #[test]
fn macro_reexport() { fn macro_reexport() {
let map = import_map( check(
r" r"
//- /main.rs crate:main deps:lib //- /main.rs crate:main deps:lib
pub mod m { pub mod m {
@ -459,21 +514,20 @@ mod tests {
() => {}; () => {};
} }
", ",
); expect![[r#"
assert_snapshot!(map, @r###"
main: main:
- m (t) - m (t)
- m::pub_macro (m) - m::pub_macro (m)
lib: lib:
- pub_macro (m) - pub_macro (m)
"###); "#]],
);
} }
#[test] #[test]
fn module_reexport() { fn module_reexport() {
// Reexporting modules from a dependency adds all contents to the import map. // Reexporting modules from a dependency adds all contents to the import map.
let map = import_map( check(
r" r"
//- /main.rs crate:main deps:lib //- /main.rs crate:main deps:lib
pub use lib::module as reexported_module; pub use lib::module as reexported_module;
@ -482,9 +536,7 @@ mod tests {
pub struct S; pub struct S;
} }
", ",
); expect![[r#"
assert_snapshot!(map, @r###"
main: main:
- reexported_module (t) - reexported_module (t)
- reexported_module::S (t) - reexported_module::S (t)
@ -493,13 +545,14 @@ mod tests {
- module (t) - module (t)
- module::S (t) - module::S (t)
- module::S (v) - module::S (v)
"###); "#]],
);
} }
#[test] #[test]
fn cyclic_module_reexport() { fn cyclic_module_reexport() {
// A cyclic reexport does not hang. // A cyclic reexport does not hang.
let map = import_map( check(
r" r"
//- /lib.rs crate:lib //- /lib.rs crate:lib
pub mod module { pub mod module {
@ -511,36 +564,35 @@ mod tests {
pub use super::module; pub use super::module;
} }
", ",
); expect![[r#"
assert_snapshot!(map, @r###"
lib: lib:
- module (t) - module (t)
- module::S (t) - module::S (t)
- module::S (v) - module::S (v)
- sub (t) - sub (t)
"###); "#]],
);
} }
#[test] #[test]
fn private_macro() { fn private_macro() {
let map = import_map( check(
r" r"
//- /lib.rs crate:lib //- /lib.rs crate:lib
macro_rules! private_macro { macro_rules! private_macro {
() => {}; () => {};
} }
", ",
); expect![[r#"
assert_snapshot!(map, @r###"
lib: lib:
"###);
"#]],
);
} }
#[test] #[test]
fn namespacing() { fn namespacing() {
let map = import_map( check(
r" r"
//- /lib.rs crate:lib //- /lib.rs crate:lib
pub struct Thing; // t + v pub struct Thing; // t + v
@ -549,16 +601,15 @@ mod tests {
() => {}; () => {};
} }
", ",
); expect![[r#"
assert_snapshot!(map, @r###"
lib: lib:
- Thing (m) - Thing (m)
- Thing (t) - Thing (t)
- Thing (v) - Thing (v)
"###); "#]],
);
let map = import_map( check(
r" r"
//- /lib.rs crate:lib //- /lib.rs crate:lib
pub mod Thing {} // t pub mod Thing {} // t
@ -567,13 +618,12 @@ mod tests {
() => {}; () => {};
} }
", ",
); expect![[r#"
assert_snapshot!(map, @r###"
lib: lib:
- Thing (m) - Thing (m)
- Thing (t) - Thing (t)
"###); "#]],
);
} }
#[test] #[test]
@ -602,23 +652,33 @@ mod tests {
} }
"#; "#;
let res = search_dependencies_of(ra_fixture, "main", Query::new("fmt")); check_search(
assert_snapshot!(res, @r###" ra_fixture,
"main",
Query::new("fmt"),
expect![[r#"
dep::fmt (t) dep::fmt (t)
dep::Fmt (t) dep::Fmt (t)
dep::Fmt (v) dep::Fmt (v)
dep::Fmt (m) dep::Fmt (m)
dep::fmt::Display (t) dep::fmt::Display (t)
dep::format (v) dep::format (v)
"###); dep::fmt::Display (t)
"#]],
);
let res = search_dependencies_of(ra_fixture, "main", Query::new("fmt").anchor_end()); check_search(
assert_snapshot!(res, @r###" ra_fixture,
"main",
Query::new("fmt").anchor_end(),
expect![[r#"
dep::fmt (t) dep::fmt (t)
dep::Fmt (t) dep::Fmt (t)
dep::Fmt (v) dep::Fmt (v)
dep::Fmt (m) dep::Fmt (m)
"###); dep::fmt::Display (t)
"#]],
);
} }
#[test] #[test]
@ -631,26 +691,32 @@ mod tests {
pub struct FMT; pub struct FMT;
"#; "#;
let res = search_dependencies_of(ra_fixture, "main", Query::new("FMT")); check_search(
ra_fixture,
assert_snapshot!(res, @r###" "main",
Query::new("FMT"),
expect![[r#"
dep::fmt (t) dep::fmt (t)
dep::fmt (v) dep::fmt (v)
dep::FMT (t) dep::FMT (t)
dep::FMT (v) dep::FMT (v)
"###); "#]],
);
let res = search_dependencies_of(ra_fixture, "main", Query::new("FMT").case_sensitive()); check_search(
ra_fixture,
assert_snapshot!(res, @r###" "main",
Query::new("FMT").case_sensitive(),
expect![[r#"
dep::FMT (t) dep::FMT (t)
dep::FMT (v) dep::FMT (v)
"###); "#]],
);
} }
#[test] #[test]
fn search_limit() { fn search_limit() {
let res = search_dependencies_of( check_search(
r#" r#"
//- /main.rs crate:main deps:dep //- /main.rs crate:main deps:dep
//- /dep.rs crate:dep //- /dep.rs crate:dep
@ -670,10 +736,10 @@ mod tests {
"#, "#,
"main", "main",
Query::new("").limit(2), Query::new("").limit(2),
); expect![[r#"
assert_snapshot!(res, @r###"
dep::fmt (t) dep::fmt (t)
dep::Fmt (t) dep::Fmt (t)
"###); "#]],
);
} }
} }

View file

@ -1,6 +1,8 @@
//! Describes items defined or visible (ie, imported) in a certain scope. //! Describes items defined or visible (ie, imported) in a certain scope.
//! This is shared between modules and blocks. //! This is shared between modules and blocks.
use std::collections::hash_map::Entry;
use hir_expand::name::Name; use hir_expand::name::Name;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use ra_db::CrateId; use ra_db::CrateId;
@ -27,9 +29,15 @@ pub struct PerNsGlobImports {
#[derive(Debug, Default, PartialEq, Eq)] #[derive(Debug, Default, PartialEq, Eq)]
pub struct ItemScope { pub struct ItemScope {
visible: FxHashMap<Name, PerNs>, types: FxHashMap<Name, (ModuleDefId, Visibility)>,
values: FxHashMap<Name, (ModuleDefId, Visibility)>,
macros: FxHashMap<Name, (MacroDefId, Visibility)>,
unresolved: FxHashSet<Name>,
defs: Vec<ModuleDefId>, defs: Vec<ModuleDefId>,
impls: Vec<ImplId>, impls: Vec<ImplId>,
/// Traits imported via `use Trait as _;`.
unnamed_trait_imports: FxHashMap<TraitId, Visibility>,
/// Macros visible in current module in legacy textual scope /// Macros visible in current module in legacy textual scope
/// ///
/// For macros invoked by an unqualified identifier like `bar!()`, `legacy_macros` will be searched in first. /// For macros invoked by an unqualified identifier like `bar!()`, `legacy_macros` will be searched in first.
@ -66,13 +74,15 @@ pub(crate) enum BuiltinShadowMode {
impl ItemScope { impl ItemScope {
pub fn entries<'a>(&'a self) -> impl Iterator<Item = (&'a Name, PerNs)> + 'a { pub fn entries<'a>(&'a self) -> impl Iterator<Item = (&'a Name, PerNs)> + 'a {
// FIXME: shadowing // FIXME: shadowing
self.visible.iter().map(|(n, def)| (n, *def)) let keys: FxHashSet<_> = self
} .types
.keys()
.chain(self.values.keys())
.chain(self.macros.keys())
.chain(self.unresolved.iter())
.collect();
pub fn entries_without_primitives<'a>( keys.into_iter().map(move |name| (name, self.get(name)))
&'a self,
) -> impl Iterator<Item = (&'a Name, PerNs)> + 'a {
self.visible.iter().map(|(n, def)| (n, *def))
} }
pub fn declarations(&self) -> impl Iterator<Item = ModuleDefId> + '_ { pub fn declarations(&self) -> impl Iterator<Item = ModuleDefId> + '_ {
@ -91,7 +101,7 @@ impl ItemScope {
/// Iterate over all module scoped macros /// Iterate over all module scoped macros
pub(crate) fn macros<'a>(&'a self) -> impl Iterator<Item = (&'a Name, MacroDefId)> + 'a { pub(crate) fn macros<'a>(&'a self) -> impl Iterator<Item = (&'a Name, MacroDefId)> + 'a {
self.visible.iter().filter_map(|(name, def)| def.take_macros().map(|macro_| (name, macro_))) self.entries().filter_map(|(name, def)| def.take_macros().map(|macro_| (name, macro_)))
} }
/// Iterate over all legacy textual scoped macros visible at the end of the module /// Iterate over all legacy textual scoped macros visible at the end of the module
@ -101,12 +111,16 @@ impl ItemScope {
/// Get a name from current module scope, legacy macros are not included /// Get a name from current module scope, legacy macros are not included
pub(crate) fn get(&self, name: &Name) -> PerNs { pub(crate) fn get(&self, name: &Name) -> PerNs {
self.visible.get(name).copied().unwrap_or_else(PerNs::none) PerNs {
types: self.types.get(name).copied(),
values: self.values.get(name).copied(),
macros: self.macros.get(name).copied(),
}
} }
pub(crate) fn name_of(&self, item: ItemInNs) -> Option<(&Name, Visibility)> { pub(crate) fn name_of(&self, item: ItemInNs) -> Option<(&Name, Visibility)> {
for (name, per_ns) in &self.visible { for (name, per_ns) in self.entries() {
if let Some(vis) = item.match_with(*per_ns) { if let Some(vis) = item.match_with(per_ns) {
return Some((name, vis)); return Some((name, vis));
} }
} }
@ -114,10 +128,13 @@ impl ItemScope {
} }
pub(crate) fn traits<'a>(&'a self) -> impl Iterator<Item = TraitId> + 'a { pub(crate) fn traits<'a>(&'a self) -> impl Iterator<Item = TraitId> + 'a {
self.visible.values().filter_map(|def| match def.take_types() { self.types
Some(ModuleDefId::TraitId(t)) => Some(t), .values()
.filter_map(|(def, _)| match def {
ModuleDefId::TraitId(t) => Some(*t),
_ => None, _ => None,
}) })
.chain(self.unnamed_trait_imports.keys().copied())
} }
pub(crate) fn define_def(&mut self, def: ModuleDefId) { pub(crate) fn define_def(&mut self, def: ModuleDefId) {
@ -136,23 +153,40 @@ impl ItemScope {
self.legacy_macros.insert(name, mac); self.legacy_macros.insert(name, mac);
} }
pub(crate) fn unnamed_trait_vis(&self, tr: TraitId) -> Option<Visibility> {
self.unnamed_trait_imports.get(&tr).copied()
}
pub(crate) fn push_unnamed_trait(&mut self, tr: TraitId, vis: Visibility) {
self.unnamed_trait_imports.insert(tr, vis);
}
pub(crate) fn push_res(&mut self, name: Name, def: PerNs) -> bool { pub(crate) fn push_res(&mut self, name: Name, def: PerNs) -> bool {
let mut changed = false; let mut changed = false;
let existing = self.visible.entry(name).or_default();
if existing.types.is_none() && def.types.is_some() { if let Some(types) = def.types {
existing.types = def.types; self.types.entry(name.clone()).or_insert_with(|| {
changed = true; changed = true;
types
});
}
if let Some(values) = def.values {
self.values.entry(name.clone()).or_insert_with(|| {
changed = true;
values
});
}
if let Some(macros) = def.macros {
self.macros.entry(name.clone()).or_insert_with(|| {
changed = true;
macros
});
} }
if existing.values.is_none() && def.values.is_some() { if def.is_none() {
existing.values = def.values; if self.unresolved.insert(name) {
changed = true; changed = true;
} }
if existing.macros.is_none() && def.macros.is_some() {
existing.macros = def.macros;
changed = true;
} }
changed changed
@ -166,17 +200,17 @@ impl ItemScope {
def_import_type: ImportType, def_import_type: ImportType,
) -> bool { ) -> bool {
let mut changed = false; let mut changed = false;
let existing = self.visible.entry(lookup.1.clone()).or_default();
macro_rules! check_changed { macro_rules! check_changed {
( (
$changed:ident, $changed:ident,
( $existing:ident / $def:ident ) . $field:ident, ( $this:ident / $def:ident ) . $field:ident,
$glob_imports:ident [ $lookup:ident ], $glob_imports:ident [ $lookup:ident ],
$def_import_type:ident $def_import_type:ident
) => { ) => {{
match ($existing.$field, $def.$field) { let existing = $this.$field.entry($lookup.1.clone());
(None, Some(_)) => { match (existing, $def.$field) {
(Entry::Vacant(entry), Some(_)) => {
match $def_import_type { match $def_import_type {
ImportType::Glob => { ImportType::Glob => {
$glob_imports.$field.insert($lookup.clone()); $glob_imports.$field.insert($lookup.clone());
@ -186,32 +220,46 @@ impl ItemScope {
} }
} }
$existing.$field = $def.$field; if let Some(fld) = $def.$field {
entry.insert(fld);
}
$changed = true; $changed = true;
} }
(Some(_), Some(_)) (Entry::Occupied(mut entry), Some(_))
if $glob_imports.$field.contains(&$lookup) if $glob_imports.$field.contains(&$lookup)
&& matches!($def_import_type, ImportType::Named) => && matches!($def_import_type, ImportType::Named) =>
{ {
mark::hit!(import_shadowed); mark::hit!(import_shadowed);
$glob_imports.$field.remove(&$lookup); $glob_imports.$field.remove(&$lookup);
$existing.$field = $def.$field; if let Some(fld) = $def.$field {
entry.insert(fld);
}
$changed = true; $changed = true;
} }
_ => {} _ => {}
} }
}; }};
} }
check_changed!(changed, (existing / def).types, glob_imports[lookup], def_import_type); check_changed!(changed, (self / def).types, glob_imports[lookup], def_import_type);
check_changed!(changed, (existing / def).values, glob_imports[lookup], def_import_type); check_changed!(changed, (self / def).values, glob_imports[lookup], def_import_type);
check_changed!(changed, (existing / def).macros, glob_imports[lookup], def_import_type); check_changed!(changed, (self / def).macros, glob_imports[lookup], def_import_type);
if def.is_none() {
if self.unresolved.insert(lookup.1) {
changed = true;
}
}
changed changed
} }
pub(crate) fn resolutions<'a>(&'a self) -> impl Iterator<Item = (Name, PerNs)> + 'a { pub(crate) fn resolutions<'a>(&'a self) -> impl Iterator<Item = (Option<Name>, PerNs)> + 'a {
self.visible.iter().map(|(name, res)| (name.clone(), *res)) self.entries().map(|(name, res)| (Some(name.clone()), res)).chain(
self.unnamed_trait_imports
.iter()
.map(|(tr, vis)| (None, PerNs::types(ModuleDefId::TraitId(*tr), *vis))),
)
} }
pub(crate) fn collect_legacy_macros(&self) -> FxHashMap<Name, MacroDefId> { pub(crate) fn collect_legacy_macros(&self) -> FxHashMap<Name, MacroDefId> {

View file

@ -13,7 +13,7 @@ use std::{
sync::Arc, sync::Arc,
}; };
use ast::{AstNode, AttrsOwner, NameOwner, StructKind, TypeAscriptionOwner}; use ast::{AstNode, AttrsOwner, NameOwner, StructKind};
use either::Either; use either::Either;
use hir_expand::{ use hir_expand::{
ast_id_map::FileAstId, ast_id_map::FileAstId,
@ -70,7 +70,7 @@ impl GenericParamsId {
pub struct ItemTree { pub struct ItemTree {
top_level: SmallVec<[ModItem; 1]>, top_level: SmallVec<[ModItem; 1]>,
attrs: FxHashMap<AttrOwner, Attrs>, attrs: FxHashMap<AttrOwner, Attrs>,
inner_items: FxHashMap<FileAstId<ast::ModuleItem>, SmallVec<[ModItem; 1]>>, inner_items: FxHashMap<FileAstId<ast::Item>, SmallVec<[ModItem; 1]>>,
data: Option<Box<ItemTreeData>>, data: Option<Box<ItemTreeData>>,
} }
@ -187,7 +187,7 @@ impl ItemTree {
/// ///
/// Most AST items are lowered to a single `ModItem`, but some (eg. `use` items) may be lowered /// Most AST items are lowered to a single `ModItem`, but some (eg. `use` items) may be lowered
/// to multiple items in the `ItemTree`. /// to multiple items in the `ItemTree`.
pub fn inner_items(&self, ast: FileAstId<ast::ModuleItem>) -> &[ModItem] { pub fn inner_items(&self, ast: FileAstId<ast::Item>) -> &[ModItem] {
&self.inner_items[&ast] &self.inner_items[&ast]
} }
@ -310,7 +310,7 @@ from_attrs!(ModItem(ModItem), Variant(Idx<Variant>), Field(Idx<Field>));
/// Trait implemented by all item nodes in the item tree. /// Trait implemented by all item nodes in the item tree.
pub trait ItemTreeNode: Clone { pub trait ItemTreeNode: Clone {
type Source: AstNode + Into<ast::ModuleItem>; type Source: AstNode + Into<ast::Item>;
fn ast_id(&self) -> FileAstId<Self::Source>; fn ast_id(&self) -> FileAstId<Self::Source>;
@ -411,17 +411,17 @@ macro_rules! mod_items {
} }
mod_items! { mod_items! {
Import in imports -> ast::UseItem, Import in imports -> ast::Use,
ExternCrate in extern_crates -> ast::ExternCrateItem, ExternCrate in extern_crates -> ast::ExternCrate,
Function in functions -> ast::FnDef, Function in functions -> ast::Fn,
Struct in structs -> ast::StructDef, Struct in structs -> ast::Struct,
Union in unions -> ast::UnionDef, Union in unions -> ast::Union,
Enum in enums -> ast::EnumDef, Enum in enums -> ast::Enum,
Const in consts -> ast::ConstDef, Const in consts -> ast::Const,
Static in statics -> ast::StaticDef, Static in statics -> ast::Static,
Trait in traits -> ast::TraitDef, Trait in traits -> ast::Trait,
Impl in impls -> ast::ImplDef, Impl in impls -> ast::Impl,
TypeAlias in type_aliases -> ast::TypeAliasDef, TypeAlias in type_aliases -> ast::TypeAlias,
Mod in mods -> ast::Module, Mod in mods -> ast::Module,
MacroCall in macro_calls -> ast::MacroCall, MacroCall in macro_calls -> ast::MacroCall,
} }
@ -482,7 +482,7 @@ pub struct Import {
pub is_prelude: bool, pub is_prelude: bool,
/// AST ID of the `use` or `extern crate` item this import was derived from. Note that many /// AST ID of the `use` or `extern crate` item this import was derived from. Note that many
/// `Import`s can map to the same `use` item. /// `Import`s can map to the same `use` item.
pub ast_id: FileAstId<ast::UseItem>, pub ast_id: FileAstId<ast::Use>,
} }
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
@ -492,7 +492,7 @@ pub struct ExternCrate {
pub visibility: RawVisibilityId, pub visibility: RawVisibilityId,
/// Whether this is a `#[macro_use] extern crate ...`. /// Whether this is a `#[macro_use] extern crate ...`.
pub is_macro_use: bool, pub is_macro_use: bool,
pub ast_id: FileAstId<ast::ExternCrateItem>, pub ast_id: FileAstId<ast::ExternCrate>,
} }
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
@ -503,8 +503,9 @@ pub struct Function {
pub has_self_param: bool, pub has_self_param: bool,
pub is_unsafe: bool, pub is_unsafe: bool,
pub params: Box<[TypeRef]>, pub params: Box<[TypeRef]>,
pub is_varargs: bool,
pub ret_type: TypeRef, pub ret_type: TypeRef,
pub ast_id: FileAstId<ast::FnDef>, pub ast_id: FileAstId<ast::Fn>,
} }
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
@ -513,7 +514,7 @@ pub struct Struct {
pub visibility: RawVisibilityId, pub visibility: RawVisibilityId,
pub generic_params: GenericParamsId, pub generic_params: GenericParamsId,
pub fields: Fields, pub fields: Fields,
pub ast_id: FileAstId<ast::StructDef>, pub ast_id: FileAstId<ast::Struct>,
pub kind: StructDefKind, pub kind: StructDefKind,
} }
@ -533,7 +534,7 @@ pub struct Union {
pub visibility: RawVisibilityId, pub visibility: RawVisibilityId,
pub generic_params: GenericParamsId, pub generic_params: GenericParamsId,
pub fields: Fields, pub fields: Fields,
pub ast_id: FileAstId<ast::UnionDef>, pub ast_id: FileAstId<ast::Union>,
} }
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
@ -542,7 +543,7 @@ pub struct Enum {
pub visibility: RawVisibilityId, pub visibility: RawVisibilityId,
pub generic_params: GenericParamsId, pub generic_params: GenericParamsId,
pub variants: IdRange<Variant>, pub variants: IdRange<Variant>,
pub ast_id: FileAstId<ast::EnumDef>, pub ast_id: FileAstId<ast::Enum>,
} }
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
@ -551,7 +552,7 @@ pub struct Const {
pub name: Option<Name>, pub name: Option<Name>,
pub visibility: RawVisibilityId, pub visibility: RawVisibilityId,
pub type_ref: TypeRef, pub type_ref: TypeRef,
pub ast_id: FileAstId<ast::ConstDef>, pub ast_id: FileAstId<ast::Const>,
} }
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
@ -560,7 +561,7 @@ pub struct Static {
pub visibility: RawVisibilityId, pub visibility: RawVisibilityId,
pub mutable: bool, pub mutable: bool,
pub type_ref: TypeRef, pub type_ref: TypeRef,
pub ast_id: FileAstId<ast::StaticDef>, pub ast_id: FileAstId<ast::Static>,
} }
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
@ -570,7 +571,7 @@ pub struct Trait {
pub generic_params: GenericParamsId, pub generic_params: GenericParamsId,
pub auto: bool, pub auto: bool,
pub items: Box<[AssocItem]>, pub items: Box<[AssocItem]>,
pub ast_id: FileAstId<ast::TraitDef>, pub ast_id: FileAstId<ast::Trait>,
} }
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
@ -580,7 +581,7 @@ pub struct Impl {
pub target_type: TypeRef, pub target_type: TypeRef,
pub is_negative: bool, pub is_negative: bool,
pub items: Box<[AssocItem]>, pub items: Box<[AssocItem]>,
pub ast_id: FileAstId<ast::ImplDef>, pub ast_id: FileAstId<ast::Impl>,
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
@ -591,7 +592,7 @@ pub struct TypeAlias {
pub bounds: Box<[TypeBound]>, pub bounds: Box<[TypeBound]>,
pub generic_params: GenericParamsId, pub generic_params: GenericParamsId,
pub type_ref: Option<TypeRef>, pub type_ref: Option<TypeRef>,
pub ast_id: FileAstId<ast::TypeAliasDef>, pub ast_id: FileAstId<ast::TypeAlias>,
} }
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]

View file

@ -1,10 +1,7 @@
//! AST -> `ItemTree` lowering code. //! AST -> `ItemTree` lowering code.
use super::*; use std::{collections::hash_map::Entry, mem, sync::Arc};
use crate::{
attr::Attrs,
generics::{GenericParams, TypeParamData, TypeParamProvenance},
};
use hir_expand::{ast_id_map::AstIdMap, hygiene::Hygiene, HirFileId}; use hir_expand::{ast_id_map::AstIdMap, hygiene::Hygiene, HirFileId};
use ra_arena::map::ArenaMap; use ra_arena::map::ArenaMap;
use ra_syntax::{ use ra_syntax::{
@ -12,7 +9,13 @@ use ra_syntax::{
SyntaxNode, SyntaxNode,
}; };
use smallvec::SmallVec; use smallvec::SmallVec;
use std::{collections::hash_map::Entry, mem, sync::Arc};
use crate::{
attr::Attrs,
generics::{GenericParams, TypeParamData, TypeParamProvenance},
};
use super::*;
fn id<N: ItemTreeNode>(index: Idx<N>) -> FileItemTreeId<N> { fn id<N: ItemTreeNode>(index: Idx<N>) -> FileItemTreeId<N> {
FileItemTreeId { index, _p: PhantomData } FileItemTreeId { index, _p: PhantomData }
@ -70,19 +73,19 @@ impl Ctx {
self.tree.data_mut() self.tree.data_mut()
} }
fn lower_mod_item(&mut self, item: &ast::ModuleItem, inner: bool) -> Option<ModItems> { fn lower_mod_item(&mut self, item: &ast::Item, inner: bool) -> Option<ModItems> {
assert!(inner || self.inner_items.is_empty()); assert!(inner || self.inner_items.is_empty());
// Collect inner items for 1-to-1-lowered items. // Collect inner items for 1-to-1-lowered items.
match item { match item {
ast::ModuleItem::StructDef(_) ast::Item::Struct(_)
| ast::ModuleItem::UnionDef(_) | ast::Item::Union(_)
| ast::ModuleItem::EnumDef(_) | ast::Item::Enum(_)
| ast::ModuleItem::FnDef(_) | ast::Item::Fn(_)
| ast::ModuleItem::TypeAliasDef(_) | ast::Item::TypeAlias(_)
| ast::ModuleItem::ConstDef(_) | ast::Item::Const(_)
| ast::ModuleItem::StaticDef(_) | ast::Item::Static(_)
| ast::ModuleItem::MacroCall(_) => { | ast::Item::MacroCall(_) => {
// Skip this if we're already collecting inner items. We'll descend into all nodes // Skip this if we're already collecting inner items. We'll descend into all nodes
// already. // already.
if !inner { if !inner {
@ -92,34 +95,30 @@ impl Ctx {
// These are handled in their respective `lower_X` method (since we can't just blindly // These are handled in their respective `lower_X` method (since we can't just blindly
// walk them). // walk them).
ast::ModuleItem::TraitDef(_) ast::Item::Trait(_) | ast::Item::Impl(_) | ast::Item::ExternBlock(_) => {}
| ast::ModuleItem::ImplDef(_)
| ast::ModuleItem::ExternBlock(_) => {}
// These don't have inner items. // These don't have inner items.
ast::ModuleItem::Module(_) ast::Item::Module(_) | ast::Item::ExternCrate(_) | ast::Item::Use(_) => {}
| ast::ModuleItem::ExternCrateItem(_)
| ast::ModuleItem::UseItem(_) => {}
}; };
let attrs = Attrs::new(item, &self.hygiene); let attrs = Attrs::new(item, &self.hygiene);
let items = match item { let items = match item {
ast::ModuleItem::StructDef(ast) => self.lower_struct(ast).map(Into::into), ast::Item::Struct(ast) => self.lower_struct(ast).map(Into::into),
ast::ModuleItem::UnionDef(ast) => self.lower_union(ast).map(Into::into), ast::Item::Union(ast) => self.lower_union(ast).map(Into::into),
ast::ModuleItem::EnumDef(ast) => self.lower_enum(ast).map(Into::into), ast::Item::Enum(ast) => self.lower_enum(ast).map(Into::into),
ast::ModuleItem::FnDef(ast) => self.lower_function(ast).map(Into::into), ast::Item::Fn(ast) => self.lower_function(ast).map(Into::into),
ast::ModuleItem::TypeAliasDef(ast) => self.lower_type_alias(ast).map(Into::into), ast::Item::TypeAlias(ast) => self.lower_type_alias(ast).map(Into::into),
ast::ModuleItem::StaticDef(ast) => self.lower_static(ast).map(Into::into), ast::Item::Static(ast) => self.lower_static(ast).map(Into::into),
ast::ModuleItem::ConstDef(ast) => Some(self.lower_const(ast).into()), ast::Item::Const(ast) => Some(self.lower_const(ast).into()),
ast::ModuleItem::Module(ast) => self.lower_module(ast).map(Into::into), ast::Item::Module(ast) => self.lower_module(ast).map(Into::into),
ast::ModuleItem::TraitDef(ast) => self.lower_trait(ast).map(Into::into), ast::Item::Trait(ast) => self.lower_trait(ast).map(Into::into),
ast::ModuleItem::ImplDef(ast) => self.lower_impl(ast).map(Into::into), ast::Item::Impl(ast) => self.lower_impl(ast).map(Into::into),
ast::ModuleItem::UseItem(ast) => Some(ModItems( ast::Item::Use(ast) => Some(ModItems(
self.lower_use(ast).into_iter().map(Into::into).collect::<SmallVec<_>>(), self.lower_use(ast).into_iter().map(Into::into).collect::<SmallVec<_>>(),
)), )),
ast::ModuleItem::ExternCrateItem(ast) => self.lower_extern_crate(ast).map(Into::into), ast::Item::ExternCrate(ast) => self.lower_extern_crate(ast).map(Into::into),
ast::ModuleItem::MacroCall(ast) => self.lower_macro_call(ast).map(Into::into), ast::Item::MacroCall(ast) => self.lower_macro_call(ast).map(Into::into),
ast::ModuleItem::ExternBlock(ast) => { ast::Item::ExternBlock(ast) => {
Some(ModItems(self.lower_extern_block(ast).into_iter().collect::<SmallVec<_>>())) Some(ModItems(self.lower_extern_block(ast).into_iter().collect::<SmallVec<_>>()))
} }
}; };
@ -147,27 +146,26 @@ impl Ctx {
fn collect_inner_items(&mut self, container: &SyntaxNode) { fn collect_inner_items(&mut self, container: &SyntaxNode) {
let forced_vis = self.forced_visibility.take(); let forced_vis = self.forced_visibility.take();
let mut inner_items = mem::take(&mut self.tree.inner_items); let mut inner_items = mem::take(&mut self.tree.inner_items);
inner_items.extend( inner_items.extend(container.descendants().skip(1).filter_map(ast::Item::cast).filter_map(
container.descendants().skip(1).filter_map(ast::ModuleItem::cast).filter_map(|item| { |item| {
let ast_id = self.source_ast_id_map.ast_id(&item); let ast_id = self.source_ast_id_map.ast_id(&item);
Some((ast_id, self.lower_mod_item(&item, true)?.0)) Some((ast_id, self.lower_mod_item(&item, true)?.0))
}), },
); ));
self.tree.inner_items = inner_items; self.tree.inner_items = inner_items;
self.forced_visibility = forced_vis; self.forced_visibility = forced_vis;
} }
fn lower_assoc_item(&mut self, item: &ast::ModuleItem) -> Option<AssocItem> { fn lower_assoc_item(&mut self, item: &ast::AssocItem) -> Option<AssocItem> {
match item { match item {
ast::ModuleItem::FnDef(ast) => self.lower_function(ast).map(Into::into), ast::AssocItem::Fn(ast) => self.lower_function(ast).map(Into::into),
ast::ModuleItem::TypeAliasDef(ast) => self.lower_type_alias(ast).map(Into::into), ast::AssocItem::TypeAlias(ast) => self.lower_type_alias(ast).map(Into::into),
ast::ModuleItem::ConstDef(ast) => Some(self.lower_const(ast).into()), ast::AssocItem::Const(ast) => Some(self.lower_const(ast).into()),
ast::ModuleItem::MacroCall(ast) => self.lower_macro_call(ast).map(Into::into), ast::AssocItem::MacroCall(ast) => self.lower_macro_call(ast).map(Into::into),
_ => None,
} }
} }
fn lower_struct(&mut self, strukt: &ast::StructDef) -> Option<FileItemTreeId<Struct>> { fn lower_struct(&mut self, strukt: &ast::Struct) -> Option<FileItemTreeId<Struct>> {
let visibility = self.lower_visibility(strukt); let visibility = self.lower_visibility(strukt);
let name = strukt.name()?.as_name(); let name = strukt.name()?.as_name();
let generic_params = self.lower_generic_params(GenericsOwner::Struct, strukt); let generic_params = self.lower_generic_params(GenericsOwner::Struct, strukt);
@ -196,7 +194,7 @@ impl Ctx {
} }
} }
fn lower_record_fields(&mut self, fields: &ast::RecordFieldDefList) -> IdRange<Field> { fn lower_record_fields(&mut self, fields: &ast::RecordFieldList) -> IdRange<Field> {
let start = self.next_field_idx(); let start = self.next_field_idx();
for field in fields.fields() { for field in fields.fields() {
if let Some(data) = self.lower_record_field(&field) { if let Some(data) = self.lower_record_field(&field) {
@ -208,42 +206,39 @@ impl Ctx {
IdRange::new(start..end) IdRange::new(start..end)
} }
fn lower_record_field(&mut self, field: &ast::RecordFieldDef) -> Option<Field> { fn lower_record_field(&mut self, field: &ast::RecordField) -> Option<Field> {
let name = field.name()?.as_name(); let name = field.name()?.as_name();
let visibility = self.lower_visibility(field); let visibility = self.lower_visibility(field);
let type_ref = self.lower_type_ref(&field.ascribed_type()?); let type_ref = self.lower_type_ref_opt(field.ty());
let res = Field { name, type_ref, visibility }; let res = Field { name, type_ref, visibility };
Some(res) Some(res)
} }
fn lower_tuple_fields(&mut self, fields: &ast::TupleFieldDefList) -> IdRange<Field> { fn lower_tuple_fields(&mut self, fields: &ast::TupleFieldList) -> IdRange<Field> {
let start = self.next_field_idx(); let start = self.next_field_idx();
for (i, field) in fields.fields().enumerate() { for (i, field) in fields.fields().enumerate() {
if let Some(data) = self.lower_tuple_field(i, &field) { let data = self.lower_tuple_field(i, &field);
let idx = self.data().fields.alloc(data); let idx = self.data().fields.alloc(data);
self.add_attrs(idx.into(), Attrs::new(&field, &self.hygiene)); self.add_attrs(idx.into(), Attrs::new(&field, &self.hygiene));
} }
}
let end = self.next_field_idx(); let end = self.next_field_idx();
IdRange::new(start..end) IdRange::new(start..end)
} }
fn lower_tuple_field(&mut self, idx: usize, field: &ast::TupleFieldDef) -> Option<Field> { fn lower_tuple_field(&mut self, idx: usize, field: &ast::TupleField) -> Field {
let name = Name::new_tuple_field(idx); let name = Name::new_tuple_field(idx);
let visibility = self.lower_visibility(field); let visibility = self.lower_visibility(field);
let type_ref = self.lower_type_ref(&field.type_ref()?); let type_ref = self.lower_type_ref_opt(field.ty());
let res = Field { name, type_ref, visibility }; let res = Field { name, type_ref, visibility };
Some(res) res
} }
fn lower_union(&mut self, union: &ast::UnionDef) -> Option<FileItemTreeId<Union>> { fn lower_union(&mut self, union: &ast::Union) -> Option<FileItemTreeId<Union>> {
let visibility = self.lower_visibility(union); let visibility = self.lower_visibility(union);
let name = union.name()?.as_name(); let name = union.name()?.as_name();
let generic_params = self.lower_generic_params(GenericsOwner::Union, union); let generic_params = self.lower_generic_params(GenericsOwner::Union, union);
let fields = match union.record_field_def_list() { let fields = match union.record_field_list() {
Some(record_field_def_list) => { Some(record_field_list) => self.lower_fields(&StructKind::Record(record_field_list)),
self.lower_fields(&StructKind::Record(record_field_def_list))
}
None => Fields::Record(IdRange::new(self.next_field_idx()..self.next_field_idx())), None => Fields::Record(IdRange::new(self.next_field_idx()..self.next_field_idx())),
}; };
let ast_id = self.source_ast_id_map.ast_id(union); let ast_id = self.source_ast_id_map.ast_id(union);
@ -251,7 +246,7 @@ impl Ctx {
Some(id(self.data().unions.alloc(res))) Some(id(self.data().unions.alloc(res)))
} }
fn lower_enum(&mut self, enum_: &ast::EnumDef) -> Option<FileItemTreeId<Enum>> { fn lower_enum(&mut self, enum_: &ast::Enum) -> Option<FileItemTreeId<Enum>> {
let visibility = self.lower_visibility(enum_); let visibility = self.lower_visibility(enum_);
let name = enum_.name()?.as_name(); let name = enum_.name()?.as_name();
let generic_params = self.lower_generic_params(GenericsOwner::Enum, enum_); let generic_params = self.lower_generic_params(GenericsOwner::Enum, enum_);
@ -264,7 +259,7 @@ impl Ctx {
Some(id(self.data().enums.alloc(res))) Some(id(self.data().enums.alloc(res)))
} }
fn lower_variants(&mut self, variants: &ast::EnumVariantList) -> IdRange<Variant> { fn lower_variants(&mut self, variants: &ast::VariantList) -> IdRange<Variant> {
let start = self.next_variant_idx(); let start = self.next_variant_idx();
for variant in variants.variants() { for variant in variants.variants() {
if let Some(data) = self.lower_variant(&variant) { if let Some(data) = self.lower_variant(&variant) {
@ -276,14 +271,14 @@ impl Ctx {
IdRange::new(start..end) IdRange::new(start..end)
} }
fn lower_variant(&mut self, variant: &ast::EnumVariant) -> Option<Variant> { fn lower_variant(&mut self, variant: &ast::Variant) -> Option<Variant> {
let name = variant.name()?.as_name(); let name = variant.name()?.as_name();
let fields = self.lower_fields(&variant.kind()); let fields = self.lower_fields(&variant.kind());
let res = Variant { name, fields }; let res = Variant { name, fields };
Some(res) Some(res)
} }
fn lower_function(&mut self, func: &ast::FnDef) -> Option<FileItemTreeId<Function>> { fn lower_function(&mut self, func: &ast::Fn) -> Option<FileItemTreeId<Function>> {
let visibility = self.lower_visibility(func); let visibility = self.lower_visibility(func);
let name = func.name()?.as_name(); let name = func.name()?.as_name();
@ -291,7 +286,7 @@ impl Ctx {
let mut has_self_param = false; let mut has_self_param = false;
if let Some(param_list) = func.param_list() { if let Some(param_list) = func.param_list() {
if let Some(self_param) = param_list.self_param() { if let Some(self_param) = param_list.self_param() {
let self_type = match self_param.ascribed_type() { let self_type = match self_param.ty() {
Some(type_ref) => TypeRef::from_ast(&self.body_ctx, type_ref), Some(type_ref) => TypeRef::from_ast(&self.body_ctx, type_ref),
None => { None => {
let self_type = TypeRef::Path(name![Self].into()); let self_type = TypeRef::Path(name![Self].into());
@ -310,11 +305,19 @@ impl Ctx {
has_self_param = true; has_self_param = true;
} }
for param in param_list.params() { for param in param_list.params() {
let type_ref = TypeRef::from_ast_opt(&self.body_ctx, param.ascribed_type()); let type_ref = TypeRef::from_ast_opt(&self.body_ctx, param.ty());
params.push(type_ref); params.push(type_ref);
} }
} }
let ret_type = match func.ret_type().and_then(|rt| rt.type_ref()) {
let mut is_varargs = false;
if let Some(params) = func.param_list() {
if let Some(last) = params.params().last() {
is_varargs = last.dotdotdot_token().is_some();
}
}
let ret_type = match func.ret_type().and_then(|rt| rt.ty()) {
Some(type_ref) => TypeRef::from_ast(&self.body_ctx, type_ref), Some(type_ref) => TypeRef::from_ast(&self.body_ctx, type_ref),
_ => TypeRef::unit(), _ => TypeRef::unit(),
}; };
@ -335,6 +338,7 @@ impl Ctx {
has_self_param, has_self_param,
is_unsafe: func.unsafe_token().is_some(), is_unsafe: func.unsafe_token().is_some(),
params: params.into_boxed_slice(), params: params.into_boxed_slice(),
is_varargs,
ret_type, ret_type,
ast_id, ast_id,
}; };
@ -345,10 +349,10 @@ impl Ctx {
fn lower_type_alias( fn lower_type_alias(
&mut self, &mut self,
type_alias: &ast::TypeAliasDef, type_alias: &ast::TypeAlias,
) -> Option<FileItemTreeId<TypeAlias>> { ) -> Option<FileItemTreeId<TypeAlias>> {
let name = type_alias.name()?.as_name(); let name = type_alias.name()?.as_name();
let type_ref = type_alias.type_ref().map(|it| self.lower_type_ref(&it)); let type_ref = type_alias.ty().map(|it| self.lower_type_ref(&it));
let visibility = self.lower_visibility(type_alias); let visibility = self.lower_visibility(type_alias);
let bounds = self.lower_type_bounds(type_alias); let bounds = self.lower_type_bounds(type_alias);
let generic_params = self.lower_generic_params(GenericsOwner::TypeAlias, type_alias); let generic_params = self.lower_generic_params(GenericsOwner::TypeAlias, type_alias);
@ -364,9 +368,9 @@ impl Ctx {
Some(id(self.data().type_aliases.alloc(res))) Some(id(self.data().type_aliases.alloc(res)))
} }
fn lower_static(&mut self, static_: &ast::StaticDef) -> Option<FileItemTreeId<Static>> { fn lower_static(&mut self, static_: &ast::Static) -> Option<FileItemTreeId<Static>> {
let name = static_.name()?.as_name(); let name = static_.name()?.as_name();
let type_ref = self.lower_type_ref_opt(static_.ascribed_type()); let type_ref = self.lower_type_ref_opt(static_.ty());
let visibility = self.lower_visibility(static_); let visibility = self.lower_visibility(static_);
let mutable = static_.mut_token().is_some(); let mutable = static_.mut_token().is_some();
let ast_id = self.source_ast_id_map.ast_id(static_); let ast_id = self.source_ast_id_map.ast_id(static_);
@ -374,9 +378,9 @@ impl Ctx {
Some(id(self.data().statics.alloc(res))) Some(id(self.data().statics.alloc(res)))
} }
fn lower_const(&mut self, konst: &ast::ConstDef) -> FileItemTreeId<Const> { fn lower_const(&mut self, konst: &ast::Const) -> FileItemTreeId<Const> {
let name = konst.name().map(|it| it.as_name()); let name = konst.name().map(|it| it.as_name());
let type_ref = self.lower_type_ref_opt(konst.ascribed_type()); let type_ref = self.lower_type_ref_opt(konst.ty());
let visibility = self.lower_visibility(konst); let visibility = self.lower_visibility(konst);
let ast_id = self.source_ast_id_map.ast_id(konst); let ast_id = self.source_ast_id_map.ast_id(konst);
let res = Const { name, visibility, type_ref, ast_id }; let res = Const { name, visibility, type_ref, ast_id };
@ -409,15 +413,15 @@ impl Ctx {
Some(id(self.data().mods.alloc(res))) Some(id(self.data().mods.alloc(res)))
} }
fn lower_trait(&mut self, trait_def: &ast::TraitDef) -> Option<FileItemTreeId<Trait>> { fn lower_trait(&mut self, trait_def: &ast::Trait) -> Option<FileItemTreeId<Trait>> {
let name = trait_def.name()?.as_name(); let name = trait_def.name()?.as_name();
let visibility = self.lower_visibility(trait_def); let visibility = self.lower_visibility(trait_def);
let generic_params = let generic_params =
self.lower_generic_params_and_inner_items(GenericsOwner::Trait(trait_def), trait_def); self.lower_generic_params_and_inner_items(GenericsOwner::Trait(trait_def), trait_def);
let auto = trait_def.auto_token().is_some(); let auto = trait_def.auto_token().is_some();
let items = trait_def.item_list().map(|list| { let items = trait_def.assoc_item_list().map(|list| {
self.with_inherited_visibility(visibility, |this| { self.with_inherited_visibility(visibility, |this| {
list.items() list.assoc_items()
.filter_map(|item| { .filter_map(|item| {
let attrs = Attrs::new(&item, &this.hygiene); let attrs = Attrs::new(&item, &this.hygiene);
this.collect_inner_items(item.syntax()); this.collect_inner_items(item.syntax());
@ -441,7 +445,7 @@ impl Ctx {
Some(id(self.data().traits.alloc(res))) Some(id(self.data().traits.alloc(res)))
} }
fn lower_impl(&mut self, impl_def: &ast::ImplDef) -> Option<FileItemTreeId<Impl>> { fn lower_impl(&mut self, impl_def: &ast::Impl) -> Option<FileItemTreeId<Impl>> {
let generic_params = let generic_params =
self.lower_generic_params_and_inner_items(GenericsOwner::Impl, impl_def); self.lower_generic_params_and_inner_items(GenericsOwner::Impl, impl_def);
let target_trait = impl_def.target_trait().map(|tr| self.lower_type_ref(&tr)); let target_trait = impl_def.target_trait().map(|tr| self.lower_type_ref(&tr));
@ -450,8 +454,9 @@ impl Ctx {
// We cannot use `assoc_items()` here as that does not include macro calls. // We cannot use `assoc_items()` here as that does not include macro calls.
let items = impl_def let items = impl_def
.item_list()? .assoc_item_list()
.items() .into_iter()
.flat_map(|it| it.assoc_items())
.filter_map(|item| { .filter_map(|item| {
self.collect_inner_items(item.syntax()); self.collect_inner_items(item.syntax());
let assoc = self.lower_assoc_item(&item)?; let assoc = self.lower_assoc_item(&item)?;
@ -465,7 +470,7 @@ impl Ctx {
Some(id(self.data().impls.alloc(res))) Some(id(self.data().impls.alloc(res)))
} }
fn lower_use(&mut self, use_item: &ast::UseItem) -> Vec<FileItemTreeId<Import>> { fn lower_use(&mut self, use_item: &ast::Use) -> Vec<FileItemTreeId<Import>> {
// FIXME: cfg_attr // FIXME: cfg_attr
let is_prelude = use_item.has_atom_attr("prelude_import"); let is_prelude = use_item.has_atom_attr("prelude_import");
let visibility = self.lower_visibility(use_item); let visibility = self.lower_visibility(use_item);
@ -494,10 +499,10 @@ impl Ctx {
fn lower_extern_crate( fn lower_extern_crate(
&mut self, &mut self,
extern_crate: &ast::ExternCrateItem, extern_crate: &ast::ExternCrate,
) -> Option<FileItemTreeId<ExternCrate>> { ) -> Option<FileItemTreeId<ExternCrate>> {
let path = ModPath::from_name_ref(&extern_crate.name_ref()?); let path = ModPath::from_name_ref(&extern_crate.name_ref()?);
let alias = extern_crate.alias().map(|a| { let alias = extern_crate.rename().map(|a| {
a.name().map(|it| it.as_name()).map_or(ImportAlias::Underscore, ImportAlias::Alias) a.name().map(|it| it.as_name()).map_or(ImportAlias::Underscore, ImportAlias::Alias)
}); });
let visibility = self.lower_visibility(extern_crate); let visibility = self.lower_visibility(extern_crate);
@ -543,14 +548,16 @@ impl Ctx {
self.collect_inner_items(item.syntax()); self.collect_inner_items(item.syntax());
let attrs = Attrs::new(&item, &self.hygiene); let attrs = Attrs::new(&item, &self.hygiene);
let id: ModItem = match item { let id: ModItem = match item {
ast::ExternItem::FnDef(ast) => { ast::ExternItem::Fn(ast) => {
let func = self.lower_function(&ast)?; let func = self.lower_function(&ast)?;
self.data().functions[func.index].is_unsafe = true;
func.into() func.into()
} }
ast::ExternItem::StaticDef(ast) => { ast::ExternItem::Static(ast) => {
let statik = self.lower_static(&ast)?; let statik = self.lower_static(&ast)?;
statik.into() statik.into()
} }
ast::ExternItem::MacroCall(_) => return None,
}; };
self.add_attrs(id.into(), attrs); self.add_attrs(id.into(), attrs);
Some(id) Some(id)
@ -563,10 +570,10 @@ impl Ctx {
fn lower_generic_params_and_inner_items( fn lower_generic_params_and_inner_items(
&mut self, &mut self,
owner: GenericsOwner<'_>, owner: GenericsOwner<'_>,
node: &impl ast::TypeParamsOwner, node: &impl ast::GenericParamsOwner,
) -> GenericParamsId { ) -> GenericParamsId {
// Generics are part of item headers and may contain inner items we need to collect. // Generics are part of item headers and may contain inner items we need to collect.
if let Some(params) = node.type_param_list() { if let Some(params) = node.generic_param_list() {
self.collect_inner_items(params.syntax()); self.collect_inner_items(params.syntax());
} }
if let Some(clause) = node.where_clause() { if let Some(clause) = node.where_clause() {
@ -579,7 +586,7 @@ impl Ctx {
fn lower_generic_params( fn lower_generic_params(
&mut self, &mut self,
owner: GenericsOwner<'_>, owner: GenericsOwner<'_>,
node: &impl ast::TypeParamsOwner, node: &impl ast::GenericParamsOwner,
) -> GenericParamsId { ) -> GenericParamsId {
let mut sm = &mut ArenaMap::default(); let mut sm = &mut ArenaMap::default();
let mut generics = GenericParams::default(); let mut generics = GenericParams::default();
@ -692,7 +699,7 @@ enum GenericsOwner<'a> {
Enum, Enum,
Union, Union,
/// The `TraitDef` is needed to fill the source map for the implicit `Self` parameter. /// The `TraitDef` is needed to fill the source map for the implicit `Self` parameter.
Trait(&'a ast::TraitDef), Trait(&'a ast::Trait),
TypeAlias, TypeAlias,
Impl, Impl,
} }

View file

@ -1,13 +1,15 @@
use super::{ItemTree, ModItem, ModKind}; use expect::{expect, Expect};
use crate::{db::DefDatabase, test_db::TestDB};
use hir_expand::{db::AstDatabase, HirFileId, InFile}; use hir_expand::{db::AstDatabase, HirFileId, InFile};
use insta::assert_snapshot;
use ra_db::fixture::WithFixture; use ra_db::fixture::WithFixture;
use ra_syntax::{ast, AstNode}; use ra_syntax::{ast, AstNode};
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use std::sync::Arc; use std::sync::Arc;
use stdx::format_to; use stdx::format_to;
use crate::{db::DefDatabase, test_db::TestDB};
use super::{ItemTree, ModItem, ModKind};
fn test_inner_items(ra_fixture: &str) { fn test_inner_items(ra_fixture: &str) {
let (db, file_id) = TestDB::with_single_file(ra_fixture); let (db, file_id) = TestDB::with_single_file(ra_fixture);
let file_id = HirFileId::from(file_id); let file_id = HirFileId::from(file_id);
@ -19,7 +21,7 @@ fn test_inner_items(ra_fixture: &str) {
let mut outer_items = FxHashSet::default(); let mut outer_items = FxHashSet::default();
let mut worklist = tree.top_level_items().to_vec(); let mut worklist = tree.top_level_items().to_vec();
while let Some(item) = worklist.pop() { while let Some(item) = worklist.pop() {
let node: ast::ModuleItem = match item { let node: ast::Item = match item {
ModItem::Import(it) => tree.source(&db, InFile::new(file_id, it)).into(), ModItem::Import(it) => tree.source(&db, InFile::new(file_id, it)).into(),
ModItem::ExternCrate(it) => tree.source(&db, InFile::new(file_id, it)).into(), ModItem::ExternCrate(it) => tree.source(&db, InFile::new(file_id, it)).into(),
ModItem::Function(it) => tree.source(&db, InFile::new(file_id, it)).into(), ModItem::Function(it) => tree.source(&db, InFile::new(file_id, it)).into(),
@ -51,7 +53,7 @@ fn test_inner_items(ra_fixture: &str) {
// Now descend the root node and check that all `ast::ModuleItem`s are either recorded above, or // Now descend the root node and check that all `ast::ModuleItem`s are either recorded above, or
// registered as inner items. // registered as inner items.
for item in root.descendants().skip(1).filter_map(ast::ModuleItem::cast) { for item in root.descendants().skip(1).filter_map(ast::Item::cast) {
if outer_items.contains(&item) { if outer_items.contains(&item) {
continue; continue;
} }
@ -162,9 +164,15 @@ fn fmt_mod_item(out: &mut String, tree: &ItemTree, item: ModItem) {
} }
} }
fn check(ra_fixture: &str, expect: Expect) {
let actual = print_item_tree(ra_fixture);
expect.assert_eq(&actual);
}
#[test] #[test]
fn smoke() { fn smoke() {
assert_snapshot!(print_item_tree(r" check(
r"
#![attr] #![attr]
#[attr_on_use] #[attr_on_use]
@ -214,42 +222,44 @@ fn smoke() {
#[union_fld] #[union_fld]
fld: u16, fld: u16,
} }
"), @r###" ",
expect![[r##"
inner attrs: Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr"))] }, input: None }]) } inner attrs: Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr"))] }, input: None }]) }
top-level items: top-level items:
#[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_on_use"))] }, input: None }]) }] #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_on_use"))] }, input: None }]) }]
Import { path: ModPath { kind: Plain, segments: [Name(Text("a"))] }, alias: None, visibility: RawVisibilityId("pub(self)"), is_glob: false, is_prelude: false, ast_id: FileAstId::<ra_syntax::ast::generated::nodes::UseItem>(0) } Import { path: ModPath { kind: Plain, segments: [Name(Text("a"))] }, alias: None, visibility: RawVisibilityId("pub(self)"), is_glob: false, is_prelude: false, ast_id: FileAstId::<ra_syntax::ast::generated::nodes::Use>(0) }
#[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_on_use"))] }, input: None }]) }] #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_on_use"))] }, input: None }]) }]
Import { path: ModPath { kind: Plain, segments: [Name(Text("b"))] }, alias: None, visibility: RawVisibilityId("pub(self)"), is_glob: true, is_prelude: false, ast_id: FileAstId::<ra_syntax::ast::generated::nodes::UseItem>(0) } Import { path: ModPath { kind: Plain, segments: [Name(Text("b"))] }, alias: None, visibility: RawVisibilityId("pub(self)"), is_glob: true, is_prelude: false, ast_id: FileAstId::<ra_syntax::ast::generated::nodes::Use>(0) }
#[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("ext_crate"))] }, input: None }]) }] #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("ext_crate"))] }, input: None }]) }]
ExternCrate { path: ModPath { kind: Plain, segments: [Name(Text("krate"))] }, alias: None, visibility: RawVisibilityId("pub(self)"), is_macro_use: false, ast_id: FileAstId::<ra_syntax::ast::generated::nodes::ExternCrateItem>(1) } ExternCrate { path: ModPath { kind: Plain, segments: [Name(Text("krate"))] }, alias: None, visibility: RawVisibilityId("pub(self)"), is_macro_use: false, ast_id: FileAstId::<ra_syntax::ast::generated::nodes::ExternCrate>(1) }
#[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("on_trait"))] }, input: None }]) }] #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("on_trait"))] }, input: None }]) }]
Trait { name: Name(Text("Tr")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(0), auto: false, items: [TypeAlias(Idx::<TypeAlias>(0)), Const(Idx::<Const>(0)), Function(Idx::<Function>(0)), Function(Idx::<Function>(1))], ast_id: FileAstId::<ra_syntax::ast::generated::nodes::TraitDef>(2) } Trait { name: Name(Text("Tr")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(0), auto: false, items: [TypeAlias(Idx::<TypeAlias>(0)), Const(Idx::<Const>(0)), Function(Idx::<Function>(0)), Function(Idx::<Function>(1))], ast_id: FileAstId::<ra_syntax::ast::generated::nodes::Trait>(2) }
> #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("assoc_ty"))] }, input: None }]) }] > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("assoc_ty"))] }, input: None }]) }]
> TypeAlias { name: Name(Text("AssocTy")), visibility: RawVisibilityId("pub(self)"), bounds: [Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("Tr"))] }, generic_args: [Some(GenericArgs { args: [Type(Tuple([]))], has_self_type: false, bindings: [] })] })], generic_params: GenericParamsId(4294967295), type_ref: None, ast_id: FileAstId::<ra_syntax::ast::generated::nodes::TypeAliasDef>(8) } > TypeAlias { name: Name(Text("AssocTy")), visibility: RawVisibilityId("pub(self)"), bounds: [Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("Tr"))] }, generic_args: [Some(GenericArgs { args: [Type(Tuple([]))], has_self_type: false, bindings: [] })] })], generic_params: GenericParamsId(4294967295), type_ref: None, ast_id: FileAstId::<ra_syntax::ast::generated::nodes::TypeAlias>(8) }
> #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("assoc_const"))] }, input: None }]) }] > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("assoc_const"))] }, input: None }]) }]
> Const { name: Some(Name(Text("CONST"))), visibility: RawVisibilityId("pub(self)"), type_ref: Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("u8"))] }, generic_args: [None] }), ast_id: FileAstId::<ra_syntax::ast::generated::nodes::ConstDef>(9) } > Const { name: Some(Name(Text("CONST"))), visibility: RawVisibilityId("pub(self)"), type_ref: Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("u8"))] }, generic_args: [None] }), ast_id: FileAstId::<ra_syntax::ast::generated::nodes::Const>(9) }
> #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("assoc_method"))] }, input: None }]) }] > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("assoc_method"))] }, input: None }]) }]
> Function { name: Name(Text("method")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: true, is_unsafe: false, params: [Reference(Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("Self"))] }, generic_args: [None] }), Shared)], ret_type: Tuple([]), ast_id: FileAstId::<ra_syntax::ast::generated::nodes::FnDef>(10) } > Function { name: Name(Text("method")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: true, is_unsafe: false, params: [Reference(Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("Self"))] }, generic_args: [None] }), Shared)], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<ra_syntax::ast::generated::nodes::Fn>(10) }
> #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("assoc_dfl_method"))] }, input: None }]) }] > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("assoc_dfl_method"))] }, input: None }]) }]
> Function { name: Name(Text("dfl_method")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: true, is_unsafe: false, params: [Reference(Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("Self"))] }, generic_args: [None] }), Mut)], ret_type: Tuple([]), ast_id: FileAstId::<ra_syntax::ast::generated::nodes::FnDef>(11) } > Function { name: Name(Text("dfl_method")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: true, is_unsafe: false, params: [Reference(Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("Self"))] }, generic_args: [None] }), Mut)], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<ra_syntax::ast::generated::nodes::Fn>(11) }
#[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("struct0"))] }, input: None }]) }] #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("struct0"))] }, input: None }]) }]
Struct { name: Name(Text("Struct0")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(1), fields: Unit, ast_id: FileAstId::<ra_syntax::ast::generated::nodes::StructDef>(3), kind: Unit } Struct { name: Name(Text("Struct0")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(1), fields: Unit, ast_id: FileAstId::<ra_syntax::ast::generated::nodes::Struct>(3), kind: Unit }
#[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("struct1"))] }, input: None }]) }] #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("struct1"))] }, input: None }]) }]
Struct { name: Name(Text("Struct1")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(2), fields: Tuple(IdRange::<ra_hir_def::item_tree::Field>(0..1)), ast_id: FileAstId::<ra_syntax::ast::generated::nodes::StructDef>(4), kind: Tuple } Struct { name: Name(Text("Struct1")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(2), fields: Tuple(IdRange::<ra_hir_def::item_tree::Field>(0..1)), ast_id: FileAstId::<ra_syntax::ast::generated::nodes::Struct>(4), kind: Tuple }
#[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("struct2"))] }, input: None }]) }] #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("struct2"))] }, input: None }]) }]
Struct { name: Name(Text("Struct2")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(3), fields: Record(IdRange::<ra_hir_def::item_tree::Field>(1..2)), ast_id: FileAstId::<ra_syntax::ast::generated::nodes::StructDef>(5), kind: Record } Struct { name: Name(Text("Struct2")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(3), fields: Record(IdRange::<ra_hir_def::item_tree::Field>(1..2)), ast_id: FileAstId::<ra_syntax::ast::generated::nodes::Struct>(5), kind: Record }
#[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("en"))] }, input: None }]) }] #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("en"))] }, input: None }]) }]
Enum { name: Name(Text("En")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), variants: IdRange::<ra_hir_def::item_tree::Variant>(0..1), ast_id: FileAstId::<ra_syntax::ast::generated::nodes::EnumDef>(6) } Enum { name: Name(Text("En")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), variants: IdRange::<ra_hir_def::item_tree::Variant>(0..1), ast_id: FileAstId::<ra_syntax::ast::generated::nodes::Enum>(6) }
#[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("un"))] }, input: None }]) }] #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("un"))] }, input: None }]) }]
Union { name: Name(Text("Un")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), fields: Record(IdRange::<ra_hir_def::item_tree::Field>(3..4)), ast_id: FileAstId::<ra_syntax::ast::generated::nodes::UnionDef>(7) } Union { name: Name(Text("Un")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), fields: Record(IdRange::<ra_hir_def::item_tree::Field>(3..4)), ast_id: FileAstId::<ra_syntax::ast::generated::nodes::Union>(7) }
"###); "##]],
);
} }
#[test] #[test]
fn simple_inner_items() { fn simple_inner_items() {
let tree = print_item_tree( check(
r" r"
impl<T:A> D for Response<T> { impl<T:A> D for Response<T> {
fn foo() { fn foo() {
@ -260,26 +270,25 @@ fn simple_inner_items() {
} }
} }
", ",
); expect![[r#"
assert_snapshot!(tree, @r###"
inner attrs: Attrs { entries: None } inner attrs: Attrs { entries: None }
top-level items: top-level items:
Impl { generic_params: GenericParamsId(0), target_trait: Some(Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("D"))] }, generic_args: [None] })), target_type: Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("Response"))] }, generic_args: [Some(GenericArgs { args: [Type(Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("T"))] }, generic_args: [None] }))], has_self_type: false, bindings: [] })] }), is_negative: false, items: [Function(Idx::<Function>(1))], ast_id: FileAstId::<ra_syntax::ast::generated::nodes::ImplDef>(0) } Impl { generic_params: GenericParamsId(0), target_trait: Some(Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("D"))] }, generic_args: [None] })), target_type: Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("Response"))] }, generic_args: [Some(GenericArgs { args: [Type(Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("T"))] }, generic_args: [None] }))], has_self_type: false, bindings: [] })] }), is_negative: false, items: [Function(Idx::<Function>(1))], ast_id: FileAstId::<ra_syntax::ast::generated::nodes::Impl>(0) }
> Function { name: Name(Text("foo")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: false, params: [], ret_type: Tuple([]), ast_id: FileAstId::<ra_syntax::ast::generated::nodes::FnDef>(1) } > Function { name: Name(Text("foo")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<ra_syntax::ast::generated::nodes::Fn>(1) }
inner items: inner items:
for AST FileAstId::<ra_syntax::ast::generated::nodes::ModuleItem>(2): for AST FileAstId::<ra_syntax::ast::generated::nodes::Item>(2):
Function { name: Name(Text("end")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(1), has_self_param: false, is_unsafe: false, params: [], ret_type: Tuple([]), ast_id: FileAstId::<ra_syntax::ast::generated::nodes::FnDef>(2) } Function { name: Name(Text("end")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(1), has_self_param: false, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<ra_syntax::ast::generated::nodes::Fn>(2) }
"###); "#]],
);
} }
#[test] #[test]
fn extern_attrs() { fn extern_attrs() {
let tree = print_item_tree( check(
r#" r#"
#[block_attr] #[block_attr]
extern "C" { extern "C" {
@ -289,22 +298,21 @@ fn extern_attrs() {
fn b() {} fn b() {}
} }
"#, "#,
); expect![[r##"
assert_snapshot!(tree, @r###"
inner attrs: Attrs { entries: None } inner attrs: Attrs { entries: None }
top-level items: top-level items:
#[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_a"))] }, input: None }, Attr { path: ModPath { kind: Plain, segments: [Name(Text("block_attr"))] }, input: None }]) }] #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_a"))] }, input: None }, Attr { path: ModPath { kind: Plain, segments: [Name(Text("block_attr"))] }, input: None }]) }]
Function { name: Name(Text("a")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: false, params: [], ret_type: Tuple([]), ast_id: FileAstId::<ra_syntax::ast::generated::nodes::FnDef>(1) } Function { name: Name(Text("a")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: true, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<ra_syntax::ast::generated::nodes::Fn>(1) }
#[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_b"))] }, input: None }, Attr { path: ModPath { kind: Plain, segments: [Name(Text("block_attr"))] }, input: None }]) }] #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_b"))] }, input: None }, Attr { path: ModPath { kind: Plain, segments: [Name(Text("block_attr"))] }, input: None }]) }]
Function { name: Name(Text("b")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: false, params: [], ret_type: Tuple([]), ast_id: FileAstId::<ra_syntax::ast::generated::nodes::FnDef>(2) } Function { name: Name(Text("b")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: true, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<ra_syntax::ast::generated::nodes::Fn>(2) }
"###); "##]],
);
} }
#[test] #[test]
fn trait_attrs() { fn trait_attrs() {
let tree = print_item_tree( check(
r#" r#"
#[trait_attr] #[trait_attr]
trait Tr { trait Tr {
@ -314,24 +322,23 @@ fn trait_attrs() {
fn b() {} fn b() {}
} }
"#, "#,
); expect![[r##"
assert_snapshot!(tree, @r###"
inner attrs: Attrs { entries: None } inner attrs: Attrs { entries: None }
top-level items: top-level items:
#[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("trait_attr"))] }, input: None }]) }] #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("trait_attr"))] }, input: None }]) }]
Trait { name: Name(Text("Tr")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(0), auto: false, items: [Function(Idx::<Function>(0)), Function(Idx::<Function>(1))], ast_id: FileAstId::<ra_syntax::ast::generated::nodes::TraitDef>(0) } Trait { name: Name(Text("Tr")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(0), auto: false, items: [Function(Idx::<Function>(0)), Function(Idx::<Function>(1))], ast_id: FileAstId::<ra_syntax::ast::generated::nodes::Trait>(0) }
> #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_a"))] }, input: None }]) }] > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_a"))] }, input: None }]) }]
> Function { name: Name(Text("a")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: false, params: [], ret_type: Tuple([]), ast_id: FileAstId::<ra_syntax::ast::generated::nodes::FnDef>(1) } > Function { name: Name(Text("a")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<ra_syntax::ast::generated::nodes::Fn>(1) }
> #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_b"))] }, input: None }]) }] > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_b"))] }, input: None }]) }]
> Function { name: Name(Text("b")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: false, params: [], ret_type: Tuple([]), ast_id: FileAstId::<ra_syntax::ast::generated::nodes::FnDef>(2) } > Function { name: Name(Text("b")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<ra_syntax::ast::generated::nodes::Fn>(2) }
"###); "##]],
);
} }
#[test] #[test]
fn impl_attrs() { fn impl_attrs() {
let tree = print_item_tree( check(
r#" r#"
#[impl_attr] #[impl_attr]
impl Ty { impl Ty {
@ -341,19 +348,18 @@ fn impl_attrs() {
fn b() {} fn b() {}
} }
"#, "#,
); expect![[r##"
assert_snapshot!(tree, @r###"
inner attrs: Attrs { entries: None } inner attrs: Attrs { entries: None }
top-level items: top-level items:
#[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("impl_attr"))] }, input: None }]) }] #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("impl_attr"))] }, input: None }]) }]
Impl { generic_params: GenericParamsId(4294967295), target_trait: None, target_type: Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("Ty"))] }, generic_args: [None] }), is_negative: false, items: [Function(Idx::<Function>(0)), Function(Idx::<Function>(1))], ast_id: FileAstId::<ra_syntax::ast::generated::nodes::ImplDef>(0) } Impl { generic_params: GenericParamsId(4294967295), target_trait: None, target_type: Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("Ty"))] }, generic_args: [None] }), is_negative: false, items: [Function(Idx::<Function>(0)), Function(Idx::<Function>(1))], ast_id: FileAstId::<ra_syntax::ast::generated::nodes::Impl>(0) }
> #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_a"))] }, input: None }]) }] > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_a"))] }, input: None }]) }]
> Function { name: Name(Text("a")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: false, params: [], ret_type: Tuple([]), ast_id: FileAstId::<ra_syntax::ast::generated::nodes::FnDef>(1) } > Function { name: Name(Text("a")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<ra_syntax::ast::generated::nodes::Fn>(1) }
> #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_b"))] }, input: None }]) }] > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_b"))] }, input: None }]) }]
> Function { name: Name(Text("b")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: false, params: [], ret_type: Tuple([]), ast_id: FileAstId::<ra_syntax::ast::generated::nodes::FnDef>(2) } > Function { name: Name(Text("b")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<ra_syntax::ast::generated::nodes::Fn>(2) }
"###); "##]],
);
} }
#[test] #[test]
@ -391,45 +397,43 @@ fn cursed_inner_items() {
#[test] #[test]
fn inner_item_attrs() { fn inner_item_attrs() {
let tree = print_item_tree( check(
r" r"
fn foo() { fn foo() {
#[on_inner] #[on_inner]
fn inner() {} fn inner() {}
} }
", ",
); expect![[r##"
assert_snapshot!(tree, @r###"
inner attrs: Attrs { entries: None } inner attrs: Attrs { entries: None }
top-level items: top-level items:
Function { name: Name(Text("foo")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: false, params: [], ret_type: Tuple([]), ast_id: FileAstId::<ra_syntax::ast::generated::nodes::FnDef>(0) } Function { name: Name(Text("foo")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<ra_syntax::ast::generated::nodes::Fn>(0) }
inner items: inner items:
for AST FileAstId::<ra_syntax::ast::generated::nodes::ModuleItem>(1): for AST FileAstId::<ra_syntax::ast::generated::nodes::Item>(1):
#[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("on_inner"))] }, input: None }]) }] #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("on_inner"))] }, input: None }]) }]
Function { name: Name(Text("inner")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: false, params: [], ret_type: Tuple([]), ast_id: FileAstId::<ra_syntax::ast::generated::nodes::FnDef>(1) } Function { name: Name(Text("inner")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<ra_syntax::ast::generated::nodes::Fn>(1) }
"###); "##]],
);
} }
#[test] #[test]
fn assoc_item_macros() { fn assoc_item_macros() {
let tree = print_item_tree( check(
r" r"
impl S { impl S {
items!(); items!();
} }
", ",
); expect![[r#"
assert_snapshot!(tree, @r###"
inner attrs: Attrs { entries: None } inner attrs: Attrs { entries: None }
top-level items: top-level items:
Impl { generic_params: GenericParamsId(4294967295), target_trait: None, target_type: Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("S"))] }, generic_args: [None] }), is_negative: false, items: [MacroCall(Idx::<MacroCall>(0))], ast_id: FileAstId::<ra_syntax::ast::generated::nodes::ImplDef>(0) } Impl { generic_params: GenericParamsId(4294967295), target_trait: None, target_type: Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("S"))] }, generic_args: [None] }), is_negative: false, items: [MacroCall(Idx::<MacroCall>(0))], ast_id: FileAstId::<ra_syntax::ast::generated::nodes::Impl>(0) }
> MacroCall { name: None, path: ModPath { kind: Plain, segments: [Name(Text("items"))] }, is_export: false, is_local_inner: false, is_builtin: false, ast_id: FileAstId::<ra_syntax::ast::generated::nodes::MacroCall>(1) } > MacroCall { name: None, path: ModPath { kind: Plain, segments: [Name(Text("items"))] }, is_export: false, is_local_inner: false, is_builtin: false, ast_id: FileAstId::<ra_syntax::ast::generated::nodes::MacroCall>(1) }
"###); "#]],
);
} }

View file

@ -14,19 +14,19 @@ use crate::{
pub type Key<K, V> = crate::dyn_map::Key<InFile<K>, V, AstPtrPolicy<K, V>>; pub type Key<K, V> = crate::dyn_map::Key<InFile<K>, V, AstPtrPolicy<K, V>>;
pub const FUNCTION: Key<ast::FnDef, FunctionId> = Key::new(); pub const FUNCTION: Key<ast::Fn, FunctionId> = Key::new();
pub const CONST: Key<ast::ConstDef, ConstId> = Key::new(); pub const CONST: Key<ast::Const, ConstId> = Key::new();
pub const STATIC: Key<ast::StaticDef, StaticId> = Key::new(); pub const STATIC: Key<ast::Static, StaticId> = Key::new();
pub const TYPE_ALIAS: Key<ast::TypeAliasDef, TypeAliasId> = Key::new(); pub const TYPE_ALIAS: Key<ast::TypeAlias, TypeAliasId> = Key::new();
pub const IMPL: Key<ast::ImplDef, ImplId> = Key::new(); pub const IMPL: Key<ast::Impl, ImplId> = Key::new();
pub const TRAIT: Key<ast::TraitDef, TraitId> = Key::new(); pub const TRAIT: Key<ast::Trait, TraitId> = Key::new();
pub const STRUCT: Key<ast::StructDef, StructId> = Key::new(); pub const STRUCT: Key<ast::Struct, StructId> = Key::new();
pub const UNION: Key<ast::UnionDef, UnionId> = Key::new(); pub const UNION: Key<ast::Union, UnionId> = Key::new();
pub const ENUM: Key<ast::EnumDef, EnumId> = Key::new(); pub const ENUM: Key<ast::Enum, EnumId> = Key::new();
pub const ENUM_VARIANT: Key<ast::EnumVariant, EnumVariantId> = Key::new(); pub const VARIANT: Key<ast::Variant, EnumVariantId> = Key::new();
pub const TUPLE_FIELD: Key<ast::TupleFieldDef, FieldId> = Key::new(); pub const TUPLE_FIELD: Key<ast::TupleField, FieldId> = Key::new();
pub const RECORD_FIELD: Key<ast::RecordFieldDef, FieldId> = Key::new(); pub const RECORD_FIELD: Key<ast::RecordField, FieldId> = Key::new();
pub const TYPE_PARAM: Key<ast::TypeParam, TypeParamId> = Key::new(); pub const TYPE_PARAM: Key<ast::TypeParam, TypeParamId> = Key::new();
pub const MACRO: Key<ast::MacroCall, MacroDefId> = Key::new(); pub const MACRO: Key<ast::MacroCall, MacroDefId> = Key::new();

View file

@ -65,6 +65,7 @@ use item_tree::{
Const, Enum, Function, Impl, ItemTreeId, ItemTreeNode, ModItem, Static, Struct, Trait, Const, Enum, Function, Impl, ItemTreeId, ItemTreeNode, ModItem, Static, Struct, Trait,
TypeAlias, Union, TypeAlias, Union,
}; };
use stdx::impl_from;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ModuleId { pub struct ModuleId {
@ -158,17 +159,17 @@ pub struct FunctionId(salsa::InternId);
type FunctionLoc = AssocItemLoc<Function>; type FunctionLoc = AssocItemLoc<Function>;
impl_intern!(FunctionId, FunctionLoc, intern_function, lookup_intern_function); impl_intern!(FunctionId, FunctionLoc, intern_function, lookup_intern_function);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct StructId(salsa::InternId); pub struct StructId(salsa::InternId);
type StructLoc = ItemLoc<Struct>; type StructLoc = ItemLoc<Struct>;
impl_intern!(StructId, StructLoc, intern_struct, lookup_intern_struct); impl_intern!(StructId, StructLoc, intern_struct, lookup_intern_struct);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct UnionId(salsa::InternId); pub struct UnionId(salsa::InternId);
pub type UnionLoc = ItemLoc<Union>; pub type UnionLoc = ItemLoc<Union>;
impl_intern!(UnionId, UnionLoc, intern_union, lookup_intern_union); impl_intern!(UnionId, UnionLoc, intern_union, lookup_intern_union);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct EnumId(salsa::InternId); pub struct EnumId(salsa::InternId);
pub type EnumLoc = ItemLoc<Enum>; pub type EnumLoc = ItemLoc<Enum>;
impl_intern!(EnumId, EnumLoc, intern_enum, lookup_intern_enum); impl_intern!(EnumId, EnumLoc, intern_enum, lookup_intern_enum);
@ -223,25 +224,6 @@ pub struct TypeParamId {
pub type LocalTypeParamId = Idx<generics::TypeParamData>; pub type LocalTypeParamId = Idx<generics::TypeParamData>;
macro_rules! impl_froms {
($e:ident: $($v:ident $(($($sv:ident),*))?),*) => {
$(
impl From<$v> for $e {
fn from(it: $v) -> $e {
$e::$v(it)
}
}
$($(
impl From<$sv> for $e {
fn from(it: $sv) -> $e {
$e::$v($v::$sv(it))
}
}
)*)?
)*
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ContainerId { pub enum ContainerId {
ModuleId(ModuleId), ModuleId(ModuleId),
@ -254,16 +236,16 @@ pub enum AssocContainerId {
ImplId(ImplId), ImplId(ImplId),
TraitId(TraitId), TraitId(TraitId),
} }
impl_froms!(AssocContainerId: ContainerId); impl_from!(ContainerId for AssocContainerId);
/// A Data Type /// A Data Type
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum AdtId { pub enum AdtId {
StructId(StructId), StructId(StructId),
UnionId(UnionId), UnionId(UnionId),
EnumId(EnumId), EnumId(EnumId),
} }
impl_froms!(AdtId: StructId, UnionId, EnumId); impl_from!(StructId, UnionId, EnumId for AdtId);
/// The defs which can be visible in the module. /// The defs which can be visible in the module.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@ -279,8 +261,8 @@ pub enum ModuleDefId {
TypeAliasId(TypeAliasId), TypeAliasId(TypeAliasId),
BuiltinType(BuiltinType), BuiltinType(BuiltinType),
} }
impl_froms!( impl_from!(
ModuleDefId: ModuleId, ModuleId,
FunctionId, FunctionId,
AdtId(StructId, EnumId, UnionId), AdtId(StructId, EnumId, UnionId),
EnumVariantId, EnumVariantId,
@ -289,6 +271,7 @@ impl_froms!(
TraitId, TraitId,
TypeAliasId, TypeAliasId,
BuiltinType BuiltinType
for ModuleDefId
); );
/// The defs which have a body. /// The defs which have a body.
@ -299,7 +282,7 @@ pub enum DefWithBodyId {
ConstId(ConstId), ConstId(ConstId),
} }
impl_froms!(DefWithBodyId: FunctionId, ConstId, StaticId); impl_from!(FunctionId, ConstId, StaticId for DefWithBodyId);
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum AssocItemId { pub enum AssocItemId {
@ -311,7 +294,7 @@ pub enum AssocItemId {
// sure that you can only turn actual assoc items into AssocItemIds. This would // sure that you can only turn actual assoc items into AssocItemIds. This would
// require not implementing From, and instead having some checked way of // require not implementing From, and instead having some checked way of
// casting them, and somehow making the constructors private, which would be annoying. // casting them, and somehow making the constructors private, which would be annoying.
impl_froms!(AssocItemId: FunctionId, ConstId, TypeAliasId); impl_from!(FunctionId, ConstId, TypeAliasId for AssocItemId);
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
pub enum GenericDefId { pub enum GenericDefId {
@ -326,14 +309,15 @@ pub enum GenericDefId {
// consts can have type parameters from their parents (i.e. associated consts of traits) // consts can have type parameters from their parents (i.e. associated consts of traits)
ConstId(ConstId), ConstId(ConstId),
} }
impl_froms!( impl_from!(
GenericDefId: FunctionId, FunctionId,
AdtId(StructId, EnumId, UnionId), AdtId(StructId, EnumId, UnionId),
TraitId, TraitId,
TypeAliasId, TypeAliasId,
ImplId, ImplId,
EnumVariantId, EnumVariantId,
ConstId ConstId
for GenericDefId
); );
impl From<AssocItemId> for GenericDefId { impl From<AssocItemId> for GenericDefId {
@ -361,8 +345,8 @@ pub enum AttrDefId {
ImplId(ImplId), ImplId(ImplId),
} }
impl_froms!( impl_from!(
AttrDefId: ModuleId, ModuleId,
FieldId, FieldId,
AdtId(StructId, EnumId, UnionId), AdtId(StructId, EnumId, UnionId),
EnumVariantId, EnumVariantId,
@ -373,6 +357,7 @@ impl_froms!(
TypeAliasId, TypeAliasId,
MacroDefId, MacroDefId,
ImplId ImplId
for AttrDefId
); );
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@ -381,7 +366,7 @@ pub enum VariantId {
StructId(StructId), StructId(StructId),
UnionId(UnionId), UnionId(UnionId),
} }
impl_froms!(VariantId: EnumVariantId, StructId, UnionId); impl_from!(EnumVariantId, StructId, UnionId for VariantId);
trait Intern { trait Intern {
type ID; type ID;
@ -536,7 +521,7 @@ impl AsMacroCall for AstIdWithPath<ast::MacroCall> {
} }
} }
impl AsMacroCall for AstIdWithPath<ast::ModuleItem> { impl AsMacroCall for AstIdWithPath<ast::Item> {
fn as_call_id( fn as_call_id(
&self, &self,
db: &dyn db::DefDatabase, db: &dyn db::DefDatabase,

View file

@ -229,37 +229,37 @@ impl CrateDefMap {
// even), as this should be a great debugging aid. // even), as this should be a great debugging aid.
pub fn dump(&self) -> String { pub fn dump(&self) -> String {
let mut buf = String::new(); let mut buf = String::new();
go(&mut buf, self, "\ncrate", self.root); go(&mut buf, self, "crate", self.root);
return buf.trim().to_string(); return buf;
fn go(buf: &mut String, map: &CrateDefMap, path: &str, module: LocalModuleId) { fn go(buf: &mut String, map: &CrateDefMap, path: &str, module: LocalModuleId) {
*buf += path; format_to!(buf, "{}\n", path);
*buf += "\n";
let mut entries: Vec<_> = map.modules[module].scope.resolutions().collect(); let mut entries: Vec<_> = map.modules[module].scope.resolutions().collect();
entries.sort_by_key(|(name, _)| name.clone()); entries.sort_by_key(|(name, _)| name.clone());
for (name, def) in entries { for (name, def) in entries {
format_to!(buf, "{}:", name); format_to!(buf, "{}:", name.map_or("_".to_string(), |name| name.to_string()));
if def.types.is_some() { if def.types.is_some() {
*buf += " t"; buf.push_str(" t");
} }
if def.values.is_some() { if def.values.is_some() {
*buf += " v"; buf.push_str(" v");
} }
if def.macros.is_some() { if def.macros.is_some() {
*buf += " m"; buf.push_str(" m");
} }
if def.is_none() { if def.is_none() {
*buf += " _"; buf.push_str(" _");
} }
*buf += "\n"; buf.push_str("\n");
} }
for (name, child) in map.modules[module].children.iter() { for (name, child) in map.modules[module].children.iter() {
let path = &format!("{}::{}", path, name); let path = format!("{}::{}", path, name);
buf.push('\n');
go(buf, map, &path, *child); go(buf, map, &path, *child);
} }
} }

View file

@ -36,6 +36,10 @@ use crate::{
TraitLoc, TypeAliasLoc, UnionLoc, TraitLoc, TypeAliasLoc, UnionLoc,
}; };
const GLOB_RECURSION_LIMIT: usize = 100;
const EXPANSION_DEPTH_LIMIT: usize = 128;
const FIXED_POINT_LIMIT: usize = 8192;
pub(super) fn collect_defs(db: &dyn DefDatabase, mut def_map: CrateDefMap) -> CrateDefMap { pub(super) fn collect_defs(db: &dyn DefDatabase, mut def_map: CrateDefMap) -> CrateDefMap {
let crate_graph = db.crate_graph(); let crate_graph = db.crate_graph();
@ -166,7 +170,7 @@ struct MacroDirective {
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
struct DeriveDirective { struct DeriveDirective {
module_id: LocalModuleId, module_id: LocalModuleId,
ast_id: AstIdWithPath<ast::ModuleItem>, ast_id: AstIdWithPath<ast::Item>,
} }
struct DefData<'a> { struct DefData<'a> {
@ -217,7 +221,7 @@ impl DefCollector<'_> {
ReachedFixedPoint::Yes => break, ReachedFixedPoint::Yes => break,
ReachedFixedPoint::No => i += 1, ReachedFixedPoint::No => i += 1,
} }
if i == 10000 { if i == FIXED_POINT_LIMIT {
log::error!("name resolution is stuck"); log::error!("name resolution is stuck");
break; break;
} }
@ -306,7 +310,7 @@ impl DefCollector<'_> {
if export { if export {
self.update( self.update(
self.def_map.root, self.def_map.root,
&[(name, PerNs::macros(macro_, Visibility::Public))], &[(Some(name), PerNs::macros(macro_, Visibility::Public))],
Visibility::Public, Visibility::Public,
ImportType::Named, ImportType::Named,
); );
@ -332,7 +336,7 @@ impl DefCollector<'_> {
fn define_proc_macro(&mut self, name: Name, macro_: MacroDefId) { fn define_proc_macro(&mut self, name: Name, macro_: MacroDefId) {
self.update( self.update(
self.def_map.root, self.def_map.root,
&[(name, PerNs::macros(macro_, Visibility::Public))], &[(Some(name), PerNs::macros(macro_, Visibility::Public))],
Visibility::Public, Visibility::Public,
ImportType::Named, ImportType::Named,
); );
@ -530,7 +534,7 @@ impl DefCollector<'_> {
let name = variant_data.name.clone(); let name = variant_data.name.clone();
let variant = EnumVariantId { parent: e, local_id }; let variant = EnumVariantId { parent: e, local_id };
let res = PerNs::both(variant.into(), variant.into(), vis); let res = PerNs::both(variant.into(), variant.into(), vis);
(name, res) (Some(name), res)
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
self.update(module_id, &resolutions, vis, ImportType::Glob); self.update(module_id, &resolutions, vis, ImportType::Glob);
@ -546,15 +550,15 @@ impl DefCollector<'_> {
match import.path.segments.last() { match import.path.segments.last() {
Some(last_segment) => { Some(last_segment) => {
let name = match &import.alias { let name = match &import.alias {
Some(ImportAlias::Alias(name)) => name.clone(), Some(ImportAlias::Alias(name)) => Some(name.clone()),
Some(ImportAlias::Underscore) => last_segment.clone(), // FIXME rust-analyzer#2736 Some(ImportAlias::Underscore) => None,
None => last_segment.clone(), None => Some(last_segment.clone()),
}; };
log::debug!("resolved import {:?} ({:?}) to {:?}", name, import, def); log::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 // 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 && module_id == self.def_map.root { if import.is_extern_crate && module_id == self.def_map.root {
if let Some(def) = def.take_types() { if let (Some(def), Some(name)) = (def.take_types(), name.as_ref()) {
self.def_map.extern_prelude.insert(name.clone(), def); self.def_map.extern_prelude.insert(name.clone(), def);
} }
} }
@ -569,30 +573,34 @@ impl DefCollector<'_> {
fn update( fn update(
&mut self, &mut self,
module_id: LocalModuleId, module_id: LocalModuleId,
resolutions: &[(Name, PerNs)], resolutions: &[(Option<Name>, PerNs)],
vis: Visibility, vis: Visibility,
import_type: ImportType, import_type: ImportType,
) { ) {
self.db.check_canceled();
self.update_recursive(module_id, resolutions, vis, import_type, 0) self.update_recursive(module_id, resolutions, vis, import_type, 0)
} }
fn update_recursive( fn update_recursive(
&mut self, &mut self,
module_id: LocalModuleId, module_id: LocalModuleId,
resolutions: &[(Name, PerNs)], resolutions: &[(Option<Name>, PerNs)],
// All resolutions are imported with this visibility; the visibilies in // All resolutions are imported with this visibility; the visibilies in
// the `PerNs` values are ignored and overwritten // the `PerNs` values are ignored and overwritten
vis: Visibility, vis: Visibility,
import_type: ImportType, import_type: ImportType,
depth: usize, depth: usize,
) { ) {
if depth > 100 { if depth > GLOB_RECURSION_LIMIT {
// prevent stack overflows (but this shouldn't be possible) // prevent stack overflows (but this shouldn't be possible)
panic!("infinite recursion in glob imports!"); panic!("infinite recursion in glob imports!");
} }
let scope = &mut self.def_map.modules[module_id].scope;
let mut changed = false; let mut changed = false;
for (name, res) in resolutions { 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( changed |= scope.push_res_with_import(
&mut self.from_glob_import, &mut self.from_glob_import,
(module_id, name.clone()), (module_id, name.clone()),
@ -600,6 +608,39 @@ impl DefCollector<'_> {
import_type, import_type,
); );
} }
None => {
let tr = match res.take_types() {
Some(ModuleDefId::TraitId(tr)) => tr,
Some(other) => {
log::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 {
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 { if !changed {
return; return;
@ -609,14 +650,15 @@ impl DefCollector<'_> {
.get(&module_id) .get(&module_id)
.into_iter() .into_iter()
.flat_map(|v| v.iter()) .flat_map(|v| v.iter())
.cloned() .filter(|(glob_importing_module, _)| {
.collect::<Vec<_>>();
for (glob_importing_module, glob_import_vis) in glob_imports {
// we know all resolutions have the same visibility (`vis`), so we // we know all resolutions have the same visibility (`vis`), so we
// just need to check that once // just need to check that once
if !vis.is_visible_from_def_map(&self.def_map, glob_importing_module) { vis.is_visible_from_def_map(&self.def_map, *glob_importing_module)
continue; })
} .cloned()
.collect::<Vec<_>>();
for (glob_importing_module, glob_import_vis) in glob_imports {
self.update_recursive( self.update_recursive(
glob_importing_module, glob_importing_module,
resolutions, resolutions,
@ -677,10 +719,6 @@ impl DefCollector<'_> {
self.unexpanded_attribute_macros = attribute_macros; self.unexpanded_attribute_macros = attribute_macros;
for (module_id, macro_call_id, depth) in resolved { for (module_id, macro_call_id, depth) in resolved {
if depth > 1024 {
log::debug!("Max macro expansion depth reached");
continue;
}
self.collect_macro_expansion(module_id, macro_call_id, depth); self.collect_macro_expansion(module_id, macro_call_id, depth);
} }
@ -717,6 +755,11 @@ impl DefCollector<'_> {
macro_call_id: MacroCallId, macro_call_id: MacroCallId,
depth: usize, depth: usize,
) { ) {
if depth > EXPANSION_DEPTH_LIMIT {
mark::hit!(macro_expansion_overflow);
log::warn!("macro expansion is too deep");
return;
}
let file_id: HirFileId = macro_call_id.as_file(); let file_id: HirFileId = macro_call_id.as_file();
let item_tree = self.db.item_tree(file_id); let item_tree = self.db.item_tree(file_id);
let mod_dir = self.mod_dirs[&module_id].clone(); let mod_dir = self.mod_dirs[&module_id].clone();
@ -943,7 +986,7 @@ impl ModCollector<'_, '_> {
.unwrap_or(Visibility::Public); .unwrap_or(Visibility::Public);
self.def_collector.update( self.def_collector.update(
self.module_id, self.module_id,
&[(name.clone(), PerNs::from_def(id, vis, has_constructor))], &[(Some(name.clone()), PerNs::from_def(id, vis, has_constructor))],
vis, vis,
ImportType::Named, ImportType::Named,
) )
@ -1050,14 +1093,14 @@ impl ModCollector<'_, '_> {
self.def_collector.def_map.modules[self.module_id].scope.define_def(def); self.def_collector.def_map.modules[self.module_id].scope.define_def(def);
self.def_collector.update( self.def_collector.update(
self.module_id, self.module_id,
&[(name, PerNs::from_def(def, vis, false))], &[(Some(name), PerNs::from_def(def, vis, false))],
vis, vis,
ImportType::Named, ImportType::Named,
); );
res res
} }
fn collect_derives(&mut self, attrs: &Attrs, ast_id: FileAstId<ast::ModuleItem>) { fn collect_derives(&mut self, attrs: &Attrs, ast_id: FileAstId<ast::Item>) {
for derive_subtree in attrs.by_key("derive").tt_values() { for derive_subtree in attrs.by_key("derive").tt_values() {
// for #[derive(Copy, Clone)], `derive_subtree` is the `(Copy, Clone)` subtree // for #[derive(Copy, Clone)], `derive_subtree` is the `(Copy, Clone)` subtree
for tt in &derive_subtree.token_trees { for tt in &derive_subtree.token_trees {

View file

@ -1,23 +1,24 @@
//! This module resolves `mod foo;` declaration to file. //! This module resolves `mod foo;` declaration to file.
use hir_expand::name::Name; use hir_expand::name::Name;
use ra_db::{FileId, RelativePathBuf}; use ra_db::FileId;
use ra_syntax::SmolStr; use ra_syntax::SmolStr;
use crate::{db::DefDatabase, HirFileId}; use crate::{db::DefDatabase, HirFileId};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub(super) struct ModDir { pub(super) struct ModDir {
/// `.` for `mod.rs`, `lib.rs` /// `` for `mod.rs`, `lib.rs`
/// `./foo` for `foo.rs` /// `foo/` for `foo.rs`
/// `./foo/bar` for `mod bar { mod x; }` nested in `foo.rs` /// `foo/bar/` for `mod bar { mod x; }` nested in `foo.rs`
path: RelativePathBuf, /// Invariant: path.is_empty() || path.ends_with('/')
dir_path: DirPath,
/// inside `./foo.rs`, mods with `#[path]` should *not* be relative to `./foo/` /// inside `./foo.rs`, mods with `#[path]` should *not* be relative to `./foo/`
root_non_dir_owner: bool, root_non_dir_owner: bool,
} }
impl ModDir { impl ModDir {
pub(super) fn root() -> ModDir { pub(super) fn root() -> ModDir {
ModDir { path: RelativePathBuf::default(), root_non_dir_owner: false } ModDir { dir_path: DirPath::empty(), root_non_dir_owner: false }
} }
pub(super) fn descend_into_definition( pub(super) fn descend_into_definition(
@ -25,17 +26,21 @@ impl ModDir {
name: &Name, name: &Name,
attr_path: Option<&SmolStr>, attr_path: Option<&SmolStr>,
) -> ModDir { ) -> ModDir {
let mut path = self.path.clone(); let path = match attr_path.map(|it| it.as_str()) {
match attr_to_path(attr_path) { None => {
None => path.push(&name.to_string()), let mut path = self.dir_path.clone();
path.push(&name.to_string());
path
}
Some(attr_path) => { Some(attr_path) => {
if self.root_non_dir_owner { let mut path = self.dir_path.join_attr(attr_path, self.root_non_dir_owner);
assert!(path.pop()); if !(path.is_empty() || path.ends_with('/')) {
path.push('/')
} }
path.push(attr_path); DirPath::new(path)
} }
} };
ModDir { path, root_non_dir_owner: false } ModDir { dir_path: path, root_non_dir_owner: false }
} }
pub(super) fn resolve_declaration( pub(super) fn resolve_declaration(
@ -48,34 +53,87 @@ impl ModDir {
let file_id = file_id.original_file(db.upcast()); let file_id = file_id.original_file(db.upcast());
let mut candidate_files = Vec::new(); let mut candidate_files = Vec::new();
match attr_to_path(attr_path) { match attr_path {
Some(attr_path) => { Some(attr_path) => {
let base = candidate_files.push(self.dir_path.join_attr(attr_path, self.root_non_dir_owner))
if self.root_non_dir_owner { self.path.parent().unwrap() } else { &self.path };
candidate_files.push(base.join(attr_path).to_string())
} }
None => { None => {
candidate_files.push(self.path.join(&format!("{}.rs", name)).to_string()); candidate_files.push(format!("{}{}.rs", self.dir_path.0, name));
candidate_files.push(self.path.join(&format!("{}/mod.rs", name)).to_string()); candidate_files.push(format!("{}{}/mod.rs", self.dir_path.0, name));
} }
}; };
for candidate in candidate_files.iter() { for candidate in candidate_files.iter() {
if let Some(file_id) = db.resolve_path(file_id, candidate.as_str()) { if let Some(file_id) = db.resolve_path(file_id, candidate.as_str()) {
let mut root_non_dir_owner = false;
let mut mod_path = RelativePathBuf::new();
let is_mod_rs = candidate.ends_with("mod.rs"); let is_mod_rs = candidate.ends_with("mod.rs");
if !(is_mod_rs || attr_path.is_some()) {
root_non_dir_owner = true; let (dir_path, root_non_dir_owner) = if is_mod_rs || attr_path.is_some() {
mod_path.push(&name.to_string()); (DirPath::empty(), false)
} } else {
return Ok((file_id, is_mod_rs, ModDir { path: mod_path, root_non_dir_owner })); (DirPath::new(format!("{}/", name)), true)
};
return Ok((file_id, is_mod_rs, ModDir { dir_path, root_non_dir_owner }));
} }
} }
Err(candidate_files.remove(0)) Err(candidate_files.remove(0))
} }
} }
fn attr_to_path(attr: Option<&SmolStr>) -> Option<RelativePathBuf> { #[derive(Clone, Debug)]
attr.and_then(|it| RelativePathBuf::from_path(&it.replace("\\", "/")).ok()) struct DirPath(String);
impl DirPath {
fn assert_invariant(&self) {
assert!(self.0.is_empty() || self.0.ends_with('/'));
}
fn new(repr: String) -> DirPath {
let res = DirPath(repr);
res.assert_invariant();
res
}
fn empty() -> DirPath {
DirPath::new(String::new())
}
fn push(&mut self, name: &str) {
self.0.push_str(name);
self.0.push('/');
self.assert_invariant();
}
fn parent(&self) -> Option<&str> {
if self.0.is_empty() {
return None;
};
let idx =
self.0[..self.0.len() - '/'.len_utf8()].rfind('/').map_or(0, |it| it + '/'.len_utf8());
Some(&self.0[..idx])
}
/// So this is the case which doesn't really work I think if we try to be
/// 100% platform agnostic:
///
/// ```
/// mod a {
/// #[path="C://sad/face"]
/// mod b { mod c; }
/// }
/// ```
///
/// Here, we need to join logical dir path to a string path from an
/// attribute. Ideally, we should somehow losslessly communicate the whole
/// construction to `FileLoader`.
fn join_attr(&self, mut attr: &str, relative_to_parent: bool) -> String {
let base = if relative_to_parent { self.parent().unwrap() } else { &self.0 };
if attr.starts_with("./") {
attr = &attr["./".len()..];
}
let tmp;
let attr = if attr.contains('\\') {
tmp = attr.replace('\\', "/");
&tmp
} else {
attr
};
let res = format!("{}{}", base, attr);
res
}
} }

View file

@ -226,8 +226,16 @@ impl CrateDefMap {
match enum_data.variant(&segment) { match enum_data.variant(&segment) {
Some(local_id) => { Some(local_id) => {
let variant = EnumVariantId { parent: e, local_id }; let variant = EnumVariantId { parent: e, local_id };
match &*enum_data.variants[local_id].variant_data {
crate::adt::VariantData::Record(_) => {
PerNs::types(variant.into(), Visibility::Public)
}
crate::adt::VariantData::Tuple(_)
| crate::adt::VariantData::Unit => {
PerNs::both(variant.into(), variant.into(), Visibility::Public) PerNs::both(variant.into(), variant.into(), Visibility::Public)
} }
}
}
None => { None => {
return ResolvePathResult::with( return ResolvePathResult::with(
PerNs::types(e.into(), vis), PerNs::types(e.into(), vis),

View file

@ -6,26 +6,29 @@ mod primitives;
use std::sync::Arc; use std::sync::Arc;
use insta::assert_snapshot; use expect::{expect, Expect};
use ra_db::{fixture::WithFixture, SourceDatabase}; use ra_db::{fixture::WithFixture, SourceDatabase};
use test_utils::mark; use test_utils::mark;
use crate::{db::DefDatabase, nameres::*, test_db::TestDB}; use crate::{db::DefDatabase, nameres::*, test_db::TestDB};
fn def_map(ra_fixture: &str) -> String {
compute_crate_def_map(ra_fixture).dump()
}
fn compute_crate_def_map(fixture: &str) -> Arc<CrateDefMap> { fn compute_crate_def_map(fixture: &str) -> Arc<CrateDefMap> {
let db = TestDB::with_files(fixture); let db = TestDB::with_files(fixture);
let krate = db.crate_graph().iter().next().unwrap(); let krate = db.crate_graph().iter().next().unwrap();
db.crate_def_map(krate) db.crate_def_map(krate)
} }
fn check(ra_fixture: &str, expect: Expect) {
let db = TestDB::with_files(ra_fixture);
let krate = db.crate_graph().iter().next().unwrap();
let actual = db.crate_def_map(krate).dump();
expect.assert_eq(&actual);
}
#[test] #[test]
fn crate_def_map_smoke_test() { fn crate_def_map_smoke_test() {
let map = def_map( check(
r" r#"
//- /lib.rs //- /lib.rs
mod foo; mod foo;
struct S; struct S;
@ -39,102 +42,94 @@ fn crate_def_map_smoke_test() {
//- /foo/bar.rs //- /foo/bar.rs
pub struct Baz; pub struct Baz;
union U { union U { to_be: bool, not_to_be: u8 }
to_be: bool,
not_to_be: u8,
}
enum E { V } enum E { V }
extern { extern {
static EXT: u8; static EXT: u8;
fn ext(); fn ext();
} }
", "#,
expect![[r#"
crate
E: t
S: t v
V: t v
foo: t
crate::foo
bar: t
f: v
crate::foo::bar
Baz: t v
E: t
EXT: v
U: t
ext: v
"#]],
); );
assert_snapshot!(map, @r###"
crate
E: t
S: t v
V: t v
foo: t
crate::foo
bar: t
f: v
crate::foo::bar
Baz: t v
E: t
EXT: v
U: t
ext: v
"###)
} }
#[test] #[test]
fn crate_def_map_super_super() { fn crate_def_map_super_super() {
let map = def_map( check(
" r#"
//- /lib.rs
mod a { mod a {
const A: usize = 0; const A: usize = 0;
mod b { mod b {
const B: usize = 0; const B: usize = 0;
mod c { mod c {
use super::super::*; use super::super::*;
} }
} }
} }
", "#,
expect![[r#"
crate
a: t
crate::a
A: v
b: t
crate::a::b
B: v
c: t
crate::a::b::c
A: v
b: t
"#]],
); );
assert_snapshot!(map, @r###"
crate
a: t
crate::a
A: v
b: t
crate::a::b
B: v
c: t
crate::a::b::c
A: v
b: t
"###)
} }
#[test] #[test]
fn crate_def_map_fn_mod_same_name() { fn crate_def_map_fn_mod_same_name() {
let map = def_map( check(
" r#"
//- /lib.rs
mod m { mod m {
pub mod z {} pub mod z {}
pub fn z() {} pub fn z() {}
} }
", "#,
expect![[r#"
crate
m: t
crate::m
z: t v
crate::m::z
"#]],
); );
assert_snapshot!(map, @r###"
crate
m: t
crate::m
z: t v
crate::m::z
"###)
} }
#[test] #[test]
fn bogus_paths() { fn bogus_paths() {
mark::check!(bogus_paths); mark::check!(bogus_paths);
let map = def_map( check(
" r#"
//- /lib.rs //- /lib.rs
mod foo; mod foo;
struct S; struct S;
@ -143,51 +138,45 @@ fn bogus_paths() {
//- /foo/mod.rs //- /foo/mod.rs
use super; use super;
use crate; use crate;
"#,
expect![[r#"
crate
S: t v
foo: t
", crate::foo
"#]],
); );
assert_snapshot!(map, @r###"
crate
S: t v
foo: t
crate::foo
"###
)
} }
#[test] #[test]
fn use_as() { fn use_as() {
let map = def_map( check(
" r#"
//- /lib.rs //- /lib.rs
mod foo; mod foo;
use crate::foo::Baz as Foo; use crate::foo::Baz as Foo;
//- /foo/mod.rs //- /foo/mod.rs
pub struct Baz; pub struct Baz;
", "#,
); expect![[r#"
assert_snapshot!(map, crate
@r###" Foo: t v
crate foo: t
Foo: t v
foo: t crate::foo
Baz: t v
crate::foo "#]],
Baz: t v
"###
); );
} }
#[test] #[test]
fn use_trees() { fn use_trees() {
let map = def_map( check(
" r#"
//- /lib.rs //- /lib.rs
mod foo; mod foo;
use crate::foo::bar::{Baz, Quux}; use crate::foo::bar::{Baz, Quux};
//- /foo/mod.rs //- /foo/mod.rs
@ -196,60 +185,58 @@ fn use_trees() {
//- /foo/bar.rs //- /foo/bar.rs
pub struct Baz; pub struct Baz;
pub enum Quux {}; pub enum Quux {};
", "#,
expect![[r#"
crate
Baz: t v
Quux: t
foo: t
crate::foo
bar: t
crate::foo::bar
Baz: t v
Quux: t
"#]],
); );
assert_snapshot!(map, @r###"
crate
Baz: t v
Quux: t
foo: t
crate::foo
bar: t
crate::foo::bar
Baz: t v
Quux: t
"###);
} }
#[test] #[test]
fn re_exports() { fn re_exports() {
let map = def_map( check(
" r#"
//- /lib.rs //- /lib.rs
mod foo; mod foo;
use self::foo::Baz; use self::foo::Baz;
//- /foo/mod.rs //- /foo/mod.rs
pub mod bar; pub mod bar;
pub use self::bar::Baz; pub use self::bar::Baz;
//- /foo/bar.rs //- /foo/bar.rs
pub struct Baz; pub struct Baz;
", "#,
expect![[r#"
crate
Baz: t v
foo: t
crate::foo
Baz: t v
bar: t
crate::foo::bar
Baz: t v
"#]],
); );
assert_snapshot!(map, @r###"
crate
Baz: t v
foo: t
crate::foo
Baz: t v
bar: t
crate::foo::bar
Baz: t v
"###);
} }
#[test] #[test]
fn std_prelude() { fn std_prelude() {
mark::check!(std_prelude); mark::check!(std_prelude);
let map = def_map( check(
" r#"
//- /main.rs crate:main deps:test_crate //- /main.rs crate:main deps:test_crate
use Foo::*; use Foo::*;
@ -260,37 +247,35 @@ fn std_prelude() {
//- /prelude.rs //- /prelude.rs
pub enum Foo { Bar, Baz }; pub enum Foo { Bar, Baz };
", "#,
expect![[r#"
crate
Bar: t v
Baz: t v
"#]],
); );
assert_snapshot!(map, @r###"
crate
Bar: t v
Baz: t v
"###);
} }
#[test] #[test]
fn can_import_enum_variant() { fn can_import_enum_variant() {
mark::check!(can_import_enum_variant); mark::check!(can_import_enum_variant);
let map = def_map( check(
" r#"
//- /lib.rs
enum E { V } enum E { V }
use self::E::V; use self::E::V;
", "#,
); expect![[r#"
assert_snapshot!(map, @r###" crate
crate E: t
E: t V: t v
V: t v "#]],
"###
); );
} }
#[test] #[test]
fn edition_2015_imports() { fn edition_2015_imports() {
let map = def_map( check(
" r#"
//- /main.rs crate:main deps:other_crate edition:2015 //- /main.rs crate:main deps:other_crate edition:2015
mod foo; mod foo;
mod bar; mod bar;
@ -304,74 +289,73 @@ fn edition_2015_imports() {
//- /lib.rs crate:other_crate edition:2018 //- /lib.rs crate:other_crate edition:2018
struct FromLib; struct FromLib;
", "#,
); expect![[r#"
crate
bar: t
foo: t
assert_snapshot!(map, @r###" crate::bar
crate Bar: t v
bar: t
foo: t crate::foo
Bar: t v
crate::bar FromLib: t v
Bar: t v "#]],
);
crate::foo
Bar: t v
FromLib: t v
"###);
} }
#[test] #[test]
fn item_map_using_self() { fn item_map_using_self() {
let map = def_map( check(
" r#"
//- /lib.rs //- /lib.rs
mod foo; mod foo;
use crate::foo::bar::Baz::{self}; use crate::foo::bar::Baz::{self};
//- /foo/mod.rs //- /foo/mod.rs
pub mod bar; pub mod bar;
//- /foo/bar.rs //- /foo/bar.rs
pub struct Baz; pub struct Baz;
", "#,
expect![[r#"
crate
Baz: t v
foo: t
crate::foo
bar: t
crate::foo::bar
Baz: t v
"#]],
); );
assert_snapshot!(map, @r###"
crate
Baz: t v
foo: t
crate::foo
bar: t
crate::foo::bar
Baz: t v
"###);
} }
#[test] #[test]
fn item_map_across_crates() { fn item_map_across_crates() {
let map = def_map( check(
" r#"
//- /main.rs crate:main deps:test_crate //- /main.rs crate:main deps:test_crate
use test_crate::Baz; use test_crate::Baz;
//- /lib.rs crate:test_crate //- /lib.rs crate:test_crate
pub struct Baz; pub struct Baz;
", "#,
expect![[r#"
crate
Baz: t v
"#]],
); );
assert_snapshot!(map, @r###"
crate
Baz: t v
"###);
} }
#[test] #[test]
fn extern_crate_rename() { fn extern_crate_rename() {
let map = def_map( check(
" r#"
//- /main.rs crate:main deps:alloc //- /main.rs crate:main deps:alloc
extern crate alloc as alloc_crate; extern crate alloc as alloc_crate;
mod alloc; mod alloc;
mod sync; mod sync;
@ -380,26 +364,24 @@ fn extern_crate_rename() {
//- /lib.rs crate:alloc //- /lib.rs crate:alloc
struct Arc; struct Arc;
", "#,
); expect![[r#"
crate
alloc_crate: t
sync: t
assert_snapshot!(map, @r###" crate::sync
crate Arc: t v
alloc_crate: t "#]],
sync: t );
crate::sync
Arc: t v
"###);
} }
#[test] #[test]
fn extern_crate_rename_2015_edition() { fn extern_crate_rename_2015_edition() {
let map = def_map( check(
" r#"
//- /main.rs crate:main deps:alloc edition:2015 //- /main.rs crate:main deps:alloc edition:2015
extern crate alloc as alloc_crate; extern crate alloc as alloc_crate;
mod alloc; mod alloc;
mod sync; mod sync;
@ -408,67 +390,61 @@ fn extern_crate_rename_2015_edition() {
//- /lib.rs crate:alloc //- /lib.rs crate:alloc
struct Arc; struct Arc;
", "#,
); expect![[r#"
crate
alloc_crate: t
sync: t
assert_snapshot!(map, crate::sync
@r###" Arc: t v
crate "#]],
alloc_crate: t
sync: t
crate::sync
Arc: t v
"###
); );
} }
#[test] #[test]
fn reexport_across_crates() { fn reexport_across_crates() {
let map = def_map( check(
" r#"
//- /main.rs crate:main deps:test_crate //- /main.rs crate:main deps:test_crate
use test_crate::Baz; use test_crate::Baz;
//- /lib.rs crate:test_crate //- /lib.rs crate:test_crate
pub use foo::Baz; pub use foo::Baz;
mod foo; mod foo;
//- /foo.rs //- /foo.rs
pub struct Baz; pub struct Baz;
", "#,
expect![[r#"
crate
Baz: t v
"#]],
); );
assert_snapshot!(map, @r###"
crate
Baz: t v
"###);
} }
#[test] #[test]
fn values_dont_shadow_extern_crates() { fn values_dont_shadow_extern_crates() {
let map = def_map( check(
" r#"
//- /main.rs crate:main deps:foo //- /main.rs crate:main deps:foo
fn foo() {} fn foo() {}
use foo::Bar; use foo::Bar;
//- /foo/lib.rs crate:foo //- /foo/lib.rs crate:foo
pub struct Bar; pub struct Bar;
", "#,
expect![[r#"
crate
Bar: t v
foo: v
"#]],
); );
assert_snapshot!(map, @r###"
crate
Bar: t v
foo: v
"###);
} }
#[test] #[test]
fn std_prelude_takes_precedence_above_core_prelude() { fn std_prelude_takes_precedence_above_core_prelude() {
let map = def_map( check(
r#" r#"
//- /main.rs crate:main deps:core,std //- /main.rs crate:main deps:core,std
use {Foo, Bar}; use {Foo, Bar};
@ -488,18 +464,17 @@ fn std_prelude_takes_precedence_above_core_prelude() {
pub struct Bar; pub struct Bar;
} }
"#, "#,
expect![[r#"
crate
Bar: t v
Foo: t v
"#]],
); );
assert_snapshot!(map, @r###"
crate
Bar: t v
Foo: t v
"###);
} }
#[test] #[test]
fn cfg_not_test() { fn cfg_not_test() {
let map = def_map( check(
r#" r#"
//- /main.rs crate:main deps:std //- /main.rs crate:main deps:std
use {Foo, Bar, Baz}; use {Foo, Bar, Baz};
@ -516,19 +491,18 @@ fn cfg_not_test() {
pub struct Baz; pub struct Baz;
} }
"#, "#,
expect![[r#"
crate
Bar: t v
Baz: _
Foo: _
"#]],
); );
assert_snapshot!(map, @r###"
crate
Bar: t v
Baz: _
Foo: _
"###);
} }
#[test] #[test]
fn cfg_test() { fn cfg_test() {
let map = def_map( check(
r#" r#"
//- /main.rs crate:main deps:std //- /main.rs crate:main deps:std
use {Foo, Bar, Baz}; use {Foo, Bar, Baz};
@ -545,19 +519,18 @@ fn cfg_test() {
pub struct Baz; pub struct Baz;
} }
"#, "#,
expect![[r#"
crate
Bar: _
Baz: t v
Foo: t v
"#]],
); );
assert_snapshot!(map, @r###"
crate
Bar: _
Baz: t v
Foo: t v
"###);
} }
#[test] #[test]
fn infer_multiple_namespace() { fn infer_multiple_namespace() {
let map = def_map( check(
r#" r#"
//- /main.rs //- /main.rs
mod a { mod a {
@ -571,18 +544,147 @@ mod b {
pub const T: () = (); pub const T: () = ();
} }
"#, "#,
); expect![[r#"
crate
T: t v
a: t
b: t
assert_snapshot!(map, @r###" crate::b
crate T: v
T: t v
a: t crate::a
b: t T: t v
"#]],
crate::b );
T: v }
crate::a #[test]
T: t v fn underscore_import() {
"###); check(
r#"
//- /main.rs
use tr::Tr as _;
use tr::Tr2 as _;
mod tr {
pub trait Tr {}
pub trait Tr2 {}
}
"#,
expect![[r#"
crate
_: t
_: t
tr: t
crate::tr
Tr: t
Tr2: t
"#]],
);
}
#[test]
fn underscore_reexport() {
check(
r#"
//- /main.rs
mod tr {
pub trait PubTr {}
pub trait PrivTr {}
}
mod reex {
use crate::tr::PrivTr as _;
pub use crate::tr::PubTr as _;
}
use crate::reex::*;
"#,
expect![[r#"
crate
_: t
reex: t
tr: t
crate::tr
PrivTr: t
PubTr: t
crate::reex
_: t
_: t
"#]],
);
}
#[test]
fn underscore_pub_crate_reexport() {
mark::check!(upgrade_underscore_visibility);
check(
r#"
//- /main.rs crate:main deps:lib
use lib::*;
//- /lib.rs crate:lib
use tr::Tr as _;
pub use tr::Tr as _;
mod tr {
pub trait Tr {}
}
"#,
expect![[r#"
crate
_: t
"#]],
);
}
#[test]
fn underscore_nontrait() {
check(
r#"
//- /main.rs
mod m {
pub struct Struct;
pub enum Enum {}
pub const CONST: () = ();
}
use crate::m::{Struct as _, Enum as _, CONST as _};
"#,
expect![[r#"
crate
m: t
crate::m
CONST: v
Enum: t
Struct: t v
"#]],
);
}
#[test]
fn underscore_name_conflict() {
check(
r#"
//- /main.rs
struct Tr;
use tr::Tr as _;
mod tr {
pub trait Tr {}
}
"#,
expect![[r#"
crate
_: t
Tr: t v
tr: t
crate::tr
Tr: t
"#]],
);
} }

View file

@ -2,8 +2,8 @@ use super::*;
#[test] #[test]
fn glob_1() { fn glob_1() {
let map = def_map( check(
r" r#"
//- /lib.rs //- /lib.rs
mod foo; mod foo;
use foo::*; use foo::*;
@ -15,30 +15,29 @@ fn glob_1() {
//- /foo/bar.rs //- /foo/bar.rs
pub struct Baz; pub struct Baz;
", "#,
); expect![[r#"
assert_snapshot!(map, @r###" crate
crate Baz: t v
Baz: t v Foo: t v
Foo: t v bar: t
bar: t foo: t
foo: t
crate::foo
crate::foo Baz: t v
Baz: t v Foo: t v
Foo: t v bar: t
bar: t
crate::foo::bar
crate::foo::bar Baz: t v
Baz: t v "#]],
"###
); );
} }
#[test] #[test]
fn glob_2() { fn glob_2() {
let map = def_map( check(
" r#"
//- /lib.rs //- /lib.rs
mod foo; mod foo;
use foo::*; use foo::*;
@ -51,31 +50,30 @@ fn glob_2() {
//- /foo/bar.rs //- /foo/bar.rs
pub struct Baz; pub struct Baz;
pub use super::*; pub use super::*;
", "#,
); expect![[r#"
assert_snapshot!(map, @r###" crate
crate Baz: t v
Baz: t v Foo: t v
Foo: t v bar: t
bar: t foo: t
foo: t
crate::foo
crate::foo Baz: t v
Baz: t v Foo: t v
Foo: t v bar: t
bar: t
crate::foo::bar
crate::foo::bar Baz: t v
Baz: t v Foo: t v
Foo: t v bar: t
bar: t "#]],
"###
); );
} }
#[test] #[test]
fn glob_privacy_1() { fn glob_privacy_1() {
let map = def_map( check(
r" r"
//- /lib.rs //- /lib.rs
mod foo; mod foo;
@ -91,30 +89,29 @@ fn glob_privacy_1() {
struct PrivateStructBar; struct PrivateStructBar;
pub use super::*; pub use super::*;
", ",
); expect![[r#"
assert_snapshot!(map, @r###" crate
crate Baz: t v
Baz: t v bar: t
bar: t foo: t
foo: t
crate::foo
crate::foo Baz: t v
Baz: t v PrivateStructFoo: t v
PrivateStructFoo: t v bar: t
bar: t
crate::foo::bar
crate::foo::bar Baz: t v
Baz: t v PrivateStructBar: t v
PrivateStructBar: t v PrivateStructFoo: t v
PrivateStructFoo: t v bar: t
bar: t "#]],
"###
); );
} }
#[test] #[test]
fn glob_privacy_2() { fn glob_privacy_2() {
let map = def_map( check(
r" r"
//- /lib.rs //- /lib.rs
mod foo; mod foo;
@ -131,203 +128,177 @@ fn glob_privacy_2() {
struct PrivateBar; struct PrivateBar;
pub(crate) struct PubCrateStruct; pub(crate) struct PubCrateStruct;
", ",
); expect![[r#"
assert_snapshot!(map, @r###" crate
crate Foo: t
Foo: t PubCrateStruct: t v
PubCrateStruct: t v foo: t
foo: t
crate::foo
crate::foo Foo: t v
Foo: t v bar: t
bar: t
crate::foo::bar
crate::foo::bar PrivateBar: t v
PrivateBar: t v PrivateBaz: t v
PrivateBaz: t v PubCrateStruct: t v
PubCrateStruct: t v "#]],
"###
); );
} }
#[test] #[test]
fn glob_across_crates() { fn glob_across_crates() {
mark::check!(glob_across_crates); mark::check!(glob_across_crates);
let map = def_map( check(
r" r#"
//- /main.rs crate:main deps:test_crate //- /main.rs crate:main deps:test_crate
use test_crate::*; use test_crate::*;
//- /lib.rs crate:test_crate //- /lib.rs crate:test_crate
pub struct Baz; pub struct Baz;
", "#,
); expect![[r#"
assert_snapshot!(map, @r###" crate
crate Baz: t v
Baz: t v "#]],
"###
); );
} }
#[test] #[test]
fn glob_privacy_across_crates() { fn glob_privacy_across_crates() {
let map = def_map( check(
r" r#"
//- /main.rs crate:main deps:test_crate //- /main.rs crate:main deps:test_crate
use test_crate::*; use test_crate::*;
//- /lib.rs crate:test_crate //- /lib.rs crate:test_crate
pub struct Baz; pub struct Baz;
struct Foo; struct Foo;
", "#,
); expect![[r#"
assert_snapshot!(map, @r###" crate
crate Baz: t v
Baz: t v "#]],
"###
); );
} }
#[test] #[test]
fn glob_enum() { fn glob_enum() {
mark::check!(glob_enum); mark::check!(glob_enum);
let map = def_map( check(
" r#"
//- /lib.rs enum Foo { Bar, Baz }
enum Foo {
Bar, Baz
}
use self::Foo::*; use self::Foo::*;
", "#,
); expect![[r#"
assert_snapshot!(map, @r###" crate
crate Bar: t v
Bar: t v Baz: t v
Baz: t v Foo: t
Foo: t "#]],
"###
); );
} }
#[test] #[test]
fn glob_enum_group() { fn glob_enum_group() {
mark::check!(glob_enum_group); mark::check!(glob_enum_group);
let map = def_map( check(
r" r#"
//- /lib.rs enum Foo { Bar, Baz }
enum Foo {
Bar, Baz
}
use self::Foo::{*}; use self::Foo::{*};
", "#,
); expect![[r#"
assert_snapshot!(map, @r###" crate
crate Bar: t v
Bar: t v Baz: t v
Baz: t v Foo: t
Foo: t "#]],
"###
); );
} }
#[test] #[test]
fn glob_shadowed_def() { fn glob_shadowed_def() {
mark::check!(import_shadowed); mark::check!(import_shadowed);
let map = def_map( check(
r###" r#"
//- /lib.rs //- /lib.rs
mod foo; mod foo;
mod bar; mod bar;
use foo::*; use foo::*;
use bar::baz; use bar::baz;
use baz::Bar; use baz::Bar;
//- /foo.rs //- /foo.rs
pub mod baz { pub mod baz { pub struct Foo; }
pub struct Foo;
}
//- /bar.rs //- /bar.rs
pub mod baz { pub mod baz { pub struct Bar; }
pub struct Bar; "#,
} expect![[r#"
"###, crate
); Bar: t v
assert_snapshot!(map, @r###" bar: t
crate baz: t
Bar: t v foo: t
bar: t
baz: t crate::bar
foo: t baz: t
crate::bar crate::bar::baz
baz: t Bar: t v
crate::bar::baz crate::foo
Bar: t v baz: t
crate::foo crate::foo::baz
baz: t Foo: t v
"#]],
crate::foo::baz
Foo: t v
"###
); );
} }
#[test] #[test]
fn glob_shadowed_def_reversed() { fn glob_shadowed_def_reversed() {
let map = def_map( check(
r###" r#"
//- /lib.rs //- /lib.rs
mod foo; mod foo;
mod bar; mod bar;
use bar::baz; use bar::baz;
use foo::*; use foo::*;
use baz::Bar; use baz::Bar;
//- /foo.rs //- /foo.rs
pub mod baz { pub mod baz { pub struct Foo; }
pub struct Foo;
}
//- /bar.rs //- /bar.rs
pub mod baz { pub mod baz { pub struct Bar; }
pub struct Bar; "#,
} expect![[r#"
"###, crate
); Bar: t v
assert_snapshot!(map, @r###" bar: t
crate baz: t
Bar: t v foo: t
bar: t
baz: t crate::bar
foo: t baz: t
crate::bar crate::bar::baz
baz: t Bar: t v
crate::bar::baz crate::foo
Bar: t v baz: t
crate::foo crate::foo::baz
baz: t Foo: t v
"#]],
crate::foo::baz
Foo: t v
"###
); );
} }
#[test] #[test]
fn glob_shadowed_def_dependencies() { fn glob_shadowed_def_dependencies() {
let map = def_map( check(
r###" r#"
//- /lib.rs
mod a { pub mod foo { pub struct X; } } mod a { pub mod foo { pub struct X; } }
mod b { pub use super::a::foo; } mod b { pub use super::a::foo; }
mod c { pub mod foo { pub struct Y; } } mod c { pub mod foo { pub struct Y; } }
@ -336,33 +307,32 @@ fn glob_shadowed_def_dependencies() {
use super::b::*; use super::b::*;
use foo::Y; use foo::Y;
} }
"###, "#,
); expect![[r#"
assert_snapshot!(map, @r###" crate
crate a: t
a: t b: t
b: t c: t
c: t d: t
d: t
crate::d
crate::d Y: t v
Y: t v foo: t
foo: t
crate::c
crate::c foo: t
foo: t
crate::c::foo
crate::c::foo Y: t v
Y: t v
crate::b
crate::b foo: t
foo: t
crate::a
crate::a foo: t
foo: t
crate::a::foo
crate::a::foo X: t v
X: t v "#]],
"###
); );
} }

View file

@ -2,8 +2,8 @@ use super::*;
#[test] #[test]
fn macro_rules_are_globally_visible() { fn macro_rules_are_globally_visible() {
let map = def_map( check(
r" r#"
//- /lib.rs //- /lib.rs
macro_rules! structs { macro_rules! structs {
($($i:ident),*) => { ($($i:ident),*) => {
@ -15,32 +15,29 @@ fn macro_rules_are_globally_visible() {
//- /nested.rs //- /nested.rs
structs!(Bar, Baz); structs!(Bar, Baz);
", "#,
expect![[r#"
crate
Foo: t
nested: t
crate::nested
Bar: t
Baz: t
"#]],
); );
assert_snapshot!(map, @r###"
crate
Foo: t
nested: t
crate::nested
Bar: t
Baz: t
"###);
} }
#[test] #[test]
fn macro_rules_can_define_modules() { fn macro_rules_can_define_modules() {
let map = def_map( check(
r" r#"
//- /lib.rs //- /lib.rs
macro_rules! m { macro_rules! m {
($name:ident) => { mod $name; } ($name:ident) => { mod $name; }
} }
m!(n1); m!(n1);
mod m { m!(n3) }
mod m {
m!(n3)
}
//- /n1.rs //- /n1.rs
m!(n2) m!(n2)
@ -48,31 +45,31 @@ fn macro_rules_can_define_modules() {
struct X; struct X;
//- /m/n3.rs //- /m/n3.rs
struct Y; struct Y;
", "#,
expect![[r#"
crate
m: t
n1: t
crate::m
n3: t
crate::m::n3
Y: t v
crate::n1
n2: t
crate::n1::n2
X: t v
"#]],
); );
assert_snapshot!(map, @r###"
crate
m: t
n1: t
crate::m
n3: t
crate::m::n3
Y: t v
crate::n1
n2: t
crate::n1::n2
X: t v
"###);
} }
#[test] #[test]
fn macro_rules_from_other_crates_are_visible() { fn macro_rules_from_other_crates_are_visible() {
let map = def_map( check(
" r#"
//- /main.rs crate:main deps:foo //- /main.rs crate:main deps:foo
foo::structs!(Foo, Bar) foo::structs!(Foo, Bar)
mod bar; mod bar;
@ -87,25 +84,25 @@ fn macro_rules_from_other_crates_are_visible() {
$(struct $i { field: u32 } )* $(struct $i { field: u32 } )*
} }
} }
", "#,
expect![[r#"
crate
Bar: t
Foo: t
bar: t
crate::bar
Bar: t
Foo: t
bar: t
"#]],
); );
assert_snapshot!(map, @r###"
crate
Bar: t
Foo: t
bar: t
crate::bar
Bar: t
Foo: t
bar: t
"###);
} }
#[test] #[test]
fn macro_rules_export_with_local_inner_macros_are_visible() { fn macro_rules_export_with_local_inner_macros_are_visible() {
let map = def_map( check(
" r#"
//- /main.rs crate:main deps:foo //- /main.rs crate:main deps:foo
foo::structs!(Foo, Bar) foo::structs!(Foo, Bar)
mod bar; mod bar;
@ -120,30 +117,32 @@ fn macro_rules_export_with_local_inner_macros_are_visible() {
$(struct $i { field: u32 } )* $(struct $i { field: u32 } )*
} }
} }
", "#,
expect![[r#"
crate
Bar: t
Foo: t
bar: t
crate::bar
Bar: t
Foo: t
bar: t
"#]],
); );
assert_snapshot!(map, @r###"
crate
Bar: t
Foo: t
bar: t
crate::bar
Bar: t
Foo: t
bar: t
"###);
} }
#[test] #[test]
fn local_inner_macros_makes_local_macros_usable() { fn local_inner_macros_makes_local_macros_usable() {
let map = def_map( check(
" r#"
//- /main.rs crate:main deps:foo //- /main.rs crate:main deps:foo
foo::structs!(Foo, Bar); foo::structs!(Foo, Bar);
mod bar; mod bar;
//- /bar.rs //- /bar.rs
use crate::*; use crate::*;
//- /lib.rs crate:foo //- /lib.rs crate:foo
#[macro_export(local_inner_macros)] #[macro_export(local_inner_macros)]
macro_rules! structs { macro_rules! structs {
@ -157,32 +156,31 @@ fn local_inner_macros_makes_local_macros_usable() {
$(struct $i { field: u32 } )* $(struct $i { field: u32 } )*
} }
} }
", "#,
expect![[r#"
crate
Bar: t
Foo: t
bar: t
crate::bar
Bar: t
Foo: t
bar: t
"#]],
); );
assert_snapshot!(map, @r###"
crate
Bar: t
Foo: t
bar: t
crate::bar
Bar: t
Foo: t
bar: t
"###);
} }
#[test] #[test]
fn unexpanded_macro_should_expand_by_fixedpoint_loop() { fn unexpanded_macro_should_expand_by_fixedpoint_loop() {
let map = def_map( check(
" r#"
//- /main.rs crate:main deps:foo //- /main.rs crate:main deps:foo
macro_rules! baz { macro_rules! baz {
() => { () => {
use foo::bar; use foo::bar;
} }
} }
foo!(); foo!();
bar!(); bar!();
baz!(); baz!();
@ -200,21 +198,21 @@ fn unexpanded_macro_should_expand_by_fixedpoint_loop() {
use foo::foo; use foo::foo;
} }
} }
", "#,
expect![[r#"
crate
Foo: t
bar: m
foo: m
"#]],
); );
assert_snapshot!(map, @r###"
crate
Foo: t
bar: m
foo: m
"###);
} }
#[test] #[test]
fn macro_rules_from_other_crates_are_visible_with_macro_use() { fn macro_rules_from_other_crates_are_visible_with_macro_use() {
mark::check!(macro_rules_from_other_crates_are_visible_with_macro_use); mark::check!(macro_rules_from_other_crates_are_visible_with_macro_use);
let map = def_map( check(
" r#"
//- /main.rs crate:main deps:foo //- /main.rs crate:main deps:foo
structs!(Foo); structs!(Foo);
structs_priv!(Bar); structs_priv!(Bar);
@ -246,25 +244,25 @@ fn macro_rules_from_other_crates_are_visible_with_macro_use() {
($i:ident) => { struct $i; } ($i:ident) => { struct $i; }
} }
} }
", "#,
expect![[r#"
crate
Bar: t v
Foo: t v
bar: t
foo: t
crate::bar
Baz: t v
"#]],
); );
assert_snapshot!(map, @r###"
crate
Bar: t v
Foo: t v
bar: t
foo: t
crate::bar
Baz: t v
"###);
} }
#[test] #[test]
fn prelude_is_macro_use() { fn prelude_is_macro_use() {
mark::check!(prelude_is_macro_use); mark::check!(prelude_is_macro_use);
let map = def_map( check(
" r#"
//- /main.rs crate:main deps:foo //- /main.rs crate:main deps:foo
structs!(Foo); structs!(Foo);
structs_priv!(Bar); structs_priv!(Bar);
@ -299,25 +297,24 @@ fn prelude_is_macro_use() {
macro_rules! structs_outside { macro_rules! structs_outside {
($i:ident) => { struct $i; } ($i:ident) => { struct $i; }
} }
", "#,
expect![[r#"
crate
Bar: t v
Foo: t v
Out: t v
bar: t
crate::bar
Baz: t v
"#]],
); );
assert_snapshot!(map, @r###"
crate
Bar: t v
Foo: t v
Out: t v
bar: t
crate::bar
Baz: t v
"###);
} }
#[test] #[test]
fn prelude_cycle() { fn prelude_cycle() {
let map = def_map( check(
" r#"
//- /lib.rs
#[prelude_import] #[prelude_import]
use self::prelude::*; use self::prelude::*;
@ -328,27 +325,25 @@ fn prelude_cycle() {
() => (mod foo {}) () => (mod foo {})
} }
} }
", "#,
expect![[r#"
crate
prelude: t
crate::prelude
"#]],
); );
assert_snapshot!(map, @r###"
crate
prelude: t
crate::prelude
"###);
} }
#[test] #[test]
fn plain_macros_are_legacy_textual_scoped() { fn plain_macros_are_legacy_textual_scoped() {
let map = def_map( check(
r#" r#"
//- /main.rs //- /main.rs
mod m1; mod m1;
bar!(NotFoundNotMacroUse); bar!(NotFoundNotMacroUse);
mod m2 { mod m2 { foo!(NotFoundBeforeInside2); }
foo!(NotFoundBeforeInside2);
}
macro_rules! foo { macro_rules! foo {
($x:ident) => { struct $x; } ($x:ident) => { struct $x; }
@ -402,46 +397,45 @@ fn plain_macros_are_legacy_textual_scoped() {
($x:ident) => { struct $x; } ($x:ident) => { struct $x; }
} }
"#, "#,
expect![[r#"
crate
Ok: t v
OkAfter: t v
OkShadowStop: t v
m1: t
m2: t
m3: t
m5: t
m7: t
ok_double_macro_use_shadow: v
crate::m7
crate::m1
crate::m5
m6: t
crate::m5::m6
crate::m2
crate::m3
OkAfterInside: t v
OkMacroUse: t v
m4: t
ok_shadow: v
crate::m3::m4
ok_shadow_deep: v
"#]],
); );
assert_snapshot!(map, @r###"
crate
Ok: t v
OkAfter: t v
OkShadowStop: t v
m1: t
m2: t
m3: t
m5: t
m7: t
ok_double_macro_use_shadow: v
crate::m7
crate::m1
crate::m5
m6: t
crate::m5::m6
crate::m2
crate::m3
OkAfterInside: t v
OkMacroUse: t v
m4: t
ok_shadow: v
crate::m3::m4
ok_shadow_deep: v
"###);
} }
#[test] #[test]
fn type_value_macro_live_in_different_scopes() { fn type_value_macro_live_in_different_scopes() {
let map = def_map( check(
" r#"
//- /main.rs
#[macro_export] #[macro_export]
macro_rules! foo { macro_rules! foo {
($x:ident) => { type $x = (); } ($x:ident) => { type $x = (); }
@ -452,20 +446,20 @@ fn type_value_macro_live_in_different_scopes() {
use self::foo as baz; use self::foo as baz;
fn baz() {} fn baz() {}
", "#,
expect![[r#"
crate
bar: t m
baz: t v m
foo: t m
"#]],
); );
assert_snapshot!(map, @r###"
crate
bar: t m
baz: t v m
foo: t m
"###);
} }
#[test] #[test]
fn macro_use_can_be_aliased() { fn macro_use_can_be_aliased() {
let map = def_map( check(
" r#"
//- /main.rs crate:main deps:foo //- /main.rs crate:main deps:foo
#[macro_use] #[macro_use]
extern crate foo; extern crate foo;
@ -482,21 +476,20 @@ fn macro_use_can_be_aliased() {
($x:ident) => { struct $x; } ($x:ident) => { struct $x; }
} }
} }
", "#,
expect![[r#"
crate
Alias: t v
Direct: t v
foo: t
"#]],
); );
assert_snapshot!(map, @r###"
crate
Alias: t v
Direct: t v
foo: t
"###);
} }
#[test] #[test]
fn path_qualified_macros() { fn path_qualified_macros() {
let map = def_map( check(
" r#"
//- /main.rs
macro_rules! foo { macro_rules! foo {
($x:ident) => { struct $x; } ($x:ident) => { struct $x; }
} }
@ -516,37 +509,36 @@ fn path_qualified_macros() {
macro_rules! bar { macro_rules! bar {
($x:ident) => { struct $x; } ($x:ident) => { struct $x; }
} }
pub use bar as alias1; pub use bar as alias1;
pub use super::bar as alias2; pub use super::bar as alias2;
pub use crate::bar as alias3; pub use crate::bar as alias3;
pub use self::bar as not_found; pub use self::bar as not_found;
} }
", "#,
expect![[r#"
crate
OkAliasCrate: t v
OkAliasPlain: t v
OkAliasSuper: t v
OkCrate: t v
OkPlain: t v
bar: m
m: t
crate::m
alias1: m
alias2: m
alias3: m
not_found: _
"#]],
); );
assert_snapshot!(map, @r###"
crate
OkAliasCrate: t v
OkAliasPlain: t v
OkAliasSuper: t v
OkCrate: t v
OkPlain: t v
bar: m
m: t
crate::m
alias1: m
alias2: m
alias3: m
not_found: _
"###);
} }
#[test] #[test]
fn macro_dollar_crate_is_correct_in_item() { fn macro_dollar_crate_is_correct_in_item() {
mark::check!(macro_dollar_crate_self); mark::check!(macro_dollar_crate_self);
let map = def_map( check(
" r#"
//- /main.rs crate:main deps:foo //- /main.rs crate:main deps:foo
#[macro_use] #[macro_use]
extern crate foo; extern crate foo;
@ -585,26 +577,26 @@ fn macro_dollar_crate_is_correct_in_item() {
struct Bar; struct Bar;
struct Baz; struct Baz;
", "#,
expect![[r#"
crate
Bar: t v
Baz: t v
Foo: t v
FooSelf: t v
foo: t
m: t
crate::m
"#]],
); );
assert_snapshot!(map, @r###"
crate
Bar: t v
Baz: t v
Foo: t v
FooSelf: t v
foo: t
m: t
crate::m
"###);
} }
#[test] #[test]
fn macro_dollar_crate_is_correct_in_indirect_deps() { fn macro_dollar_crate_is_correct_in_indirect_deps() {
mark::check!(macro_dollar_crate_other); mark::check!(macro_dollar_crate_other);
// From std // From std
let map = def_map( check(
r#" r#"
//- /main.rs crate:main deps:std //- /main.rs crate:main deps:std
foo!(); foo!();
@ -630,27 +622,15 @@ fn macro_dollar_crate_is_correct_in_indirect_deps() {
pub struct bar; pub struct bar;
"#, "#,
expect![[r#"
crate
bar: t v
"#]],
); );
assert_snapshot!(map, @r###"
crate
bar: t v
"###);
} }
#[test] #[test]
fn expand_derive() { fn expand_derive() {
let map = compute_crate_def_map(
"
//- /main.rs
#[derive(Clone)]
struct Foo;
",
);
assert_eq!(map.modules[map.root].scope.impls().len(), 1);
}
#[test]
fn expand_multiple_derive() {
let map = compute_crate_def_map( let map = compute_crate_def_map(
" "
//- /main.rs //- /main.rs
@ -660,3 +640,30 @@ fn expand_multiple_derive() {
); );
assert_eq!(map.modules[map.root].scope.impls().len(), 2); assert_eq!(map.modules[map.root].scope.impls().len(), 2);
} }
#[test]
fn macro_expansion_overflow() {
mark::check!(macro_expansion_overflow);
check(
r#"
macro_rules! a {
($e:expr; $($t:tt)*) => {
b!($($t)*);
};
() => {};
}
macro_rules! b {
(static = $e:expr; $($t:tt)*) => {
a!($e; $($t)*);
};
() => {};
}
b! { static = #[] (); }
"#,
expect![[r#"
crate
"#]],
);
}

File diff suppressed because it is too large Load diff

Some files were not shown because too many files have changed in this diff Show more