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
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,25 +16,25 @@ env:
RUSTUP_MAX_RETRIES: 10
jobs:
rust-audit:
name: Audit Rust vulnerabilities
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
# rust-audit:
# name: Audit Rust vulnerabilities
# runs-on: ubuntu-latest
# steps:
# - name: Checkout repository
# uses: actions/checkout@v2
- uses: actions-rs/install@v0.1
with:
crate: cargo-audit
use-tool-cache: true
# - uses: actions-rs/install@v0.1
# with:
# crate: cargo-audit
# use-tool-cache: true
- run: cargo audit
# - run: cargo audit
rust:
name: Rust
runs-on: ${{ matrix.os }}
env:
CC: deny_c
CC: deny_c
strategy:
fail-fast: false
@ -61,29 +61,22 @@ jobs:
override: true
components: rustfmt, rust-src
- if: matrix.os == 'ubuntu-latest'
run: sudo chown -R $(whoami):$(id -ng) ~/.cargo/
- name: Cache cargo registry
uses: actions/cache@v1
- name: Cache cargo directories
uses: actions/cache@v2
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo index
uses: actions/cache@v1
with:
path: ~/.cargo/git
key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}
path: |
~/.cargo/registry
~/.cargo/git
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo target dir
uses: actions/cache@v1
uses: actions/cache@v2
with:
path: target
key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
- name: Compile
run: cargo test --no-run
run: cargo test --no-run --locked
- name: Test
run: cargo test
@ -95,6 +88,34 @@ jobs:
if: matrix.os == 'windows-latest'
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:
name: TypeScript
strategy:
@ -103,7 +124,7 @@ jobs:
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout repository
uses: actions/checkout@v2
@ -116,9 +137,9 @@ jobs:
- run: npm ci
working-directory: ./editors/code
- run: npm audit || { sleep 10 && npm audit; } || { sleep 30 && npm audit; }
if: runner.os == 'Linux'
working-directory: ./editors/code
# - run: npm audit || { sleep 10 && npm audit; } || { sleep 30 && npm audit; }
# if: runner.os == 'Linux'
# working-directory: ./editors/code
- run: npm run lint
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
[profile.release.package.salsa-macros]
opt-level = 0
[profile.release.package.tracing-attributes]
opt-level = 0
[profile.release.package.xtask]
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']
# 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">
</p>
rust-analyzer is an **experimental** modular compiler frontend for the Rust
language. It is a part of a larger rls-2.0 effort to create excellent IDE
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
rust-analyzer is an **experimental** modular compiler frontend for the Rust language.
It is a part of a larger rls-2.0 effort to create excellent IDE support for Rust.
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.
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
you be more productive when using rust-analyzer.
choice, check [the manual](https://rust-analyzer.github.io/manual.html) folder.
It also contains some tips & tricks to help you be more productive when using rust-analyzer.
## Communication
@ -40,8 +37,9 @@ https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Frls-2.2E0
## Quick Links
* API docs: https://rust-analyzer.github.io/rust-analyzer/ra_ide/
* 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

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"
version = "0.1.0"
authors = ["rust-analyzer developers"]
license = "MIT OR Apache-2.0"
[lib]
doctest = false
@ -10,7 +11,7 @@ doctest = false
[dependencies]
crossbeam-channel = "0.4.0"
log = "0.4.8"
cargo_metadata = "0.10.0"
cargo_metadata = "0.11.1"
serde_json = "1.0.48"
jod-thread = "0.1.1"
ra_toolchain = { path = "../ra_toolchain" }

View file

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

View file

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

View file

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

View file

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

View file

@ -4,9 +4,12 @@
//! module, and we use to statically check that we only produce snippet
//! assists if we are allowed to.
use crate::AssistKind;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AssistConfig {
pub snippet_cap: Option<SnippetCap>,
pub allowed: Option<Vec<AssistKind>>,
}
impl AssistConfig {
@ -22,6 +25,6 @@ pub struct SnippetCap {
impl Default for AssistConfig {
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::{
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.
@ -55,7 +55,6 @@ use crate::{
pub(crate) struct AssistContext<'a> {
pub(crate) config: &'a AssistConfig,
pub(crate) sema: Semantics<'a, RootDatabase>,
pub(crate) db: &'a RootDatabase,
pub(crate) frange: FileRange,
source_file: SourceFile,
}
@ -67,8 +66,11 @@ impl<'a> AssistContext<'a> {
frange: FileRange,
) -> AssistContext<'a> {
let source_file = sema.parse(frange.file_id);
let db = sema.db;
AssistContext { config, sema, db, frange, source_file }
AssistContext { config, sema, frange, source_file }
}
pub(crate) fn db(&self) -> &RootDatabase {
self.sema.db
}
// NB, this ignores active selection.
@ -101,14 +103,26 @@ pub(crate) struct Assists {
resolve: bool,
file: FileId,
buf: Vec<(Assist, Option<SourceChange>)>,
allowed: Option<Vec<AssistKind>>,
}
impl 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 {
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> {
@ -137,9 +151,13 @@ impl Assists {
target: TextRange,
f: impl FnOnce(&mut AssistBuilder),
) -> Option<()> {
if !self.is_allowed(&id) {
return None;
}
let label = Assist::new(id, label.into(), None, target);
self.add_impl(label, f)
}
pub(crate) fn add_group(
&mut self,
group: &GroupLabel,
@ -148,9 +166,14 @@ impl Assists {
target: TextRange,
f: impl FnOnce(&mut AssistBuilder),
) -> Option<()> {
if !self.is_allowed(&id) {
return None;
}
let label = Assist::new(id, label.into(), Some(group.clone()), target);
self.add_impl(label, f)
}
fn add_impl(&mut self, label: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> {
let source_change = if self.resolve {
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
}
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 {
edit: TextEditBuilder,
file_id: FileId,
is_snippet: bool,
edits: Vec<SourceFileEdit>,
change: SourceChange,
}
impl AssistBuilder {
@ -183,7 +213,7 @@ impl AssistBuilder {
edit: TextEditBuilder::default(),
file_id,
is_snippet: false,
edits: Vec::new(),
change: SourceChange::default(),
}
}
@ -195,8 +225,8 @@ impl AssistBuilder {
let edit = mem::take(&mut self.edit).finish();
if !edit.is_empty() {
let new_edit = SourceFileEdit { file_id: self.file_id, edit };
assert!(!self.edits.iter().any(|it| it.file_id == new_edit.file_id));
self.edits.push(new_edit);
assert!(!self.change.source_file_edits.iter().any(|it| it.file_id == new_edit.file_id));
self.change.source_file_edits.push(new_edit);
}
}
@ -263,10 +293,10 @@ impl AssistBuilder {
fn finish(mut self) -> SourceChange {
self.commit();
let mut res: SourceChange = mem::take(&mut self.edits).into();
let mut change = mem::take(&mut self.change);
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 hir::{HirDisplay, PathResolution, SemanticsScope};
use ra_ide_db::RootDatabase;
use ra_syntax::{
algo::SyntaxRewriter,
ast::{self, AstNode},
@ -32,17 +31,17 @@ impl<'a> AstTransform<'a> for NullTransformer {
}
pub struct SubstituteTypeParams<'a> {
source_scope: &'a SemanticsScope<'a, RootDatabase>,
source_scope: &'a SemanticsScope<'a>,
substs: FxHashMap<hir::TypeParam, ast::TypeRef>,
previous: Box<dyn AstTransform<'a> + 'a>,
}
impl<'a> SubstituteTypeParams<'a> {
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...
trait_: hir::Trait,
impl_def: ast::ImplDef,
impl_def: ast::Impl,
) -> SubstituteTypeParams<'a> {
let substs = get_syntactic_substs(impl_def).unwrap_or_default();
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
// 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 path_type = match target_trait {
ast::TypeRef::PathType(path) => path,
@ -126,16 +125,13 @@ impl<'a> AstTransform<'a> for SubstituteTypeParams<'a> {
}
pub struct QualifyPaths<'a> {
target_scope: &'a SemanticsScope<'a, RootDatabase>,
source_scope: &'a SemanticsScope<'a, RootDatabase>,
target_scope: &'a SemanticsScope<'a>,
source_scope: &'a SemanticsScope<'a>,
previous: Box<dyn AstTransform<'a> + 'a>,
}
impl<'a> QualifyPaths<'a> {
pub fn new(
target_scope: &'a SemanticsScope<'a, RootDatabase>,
source_scope: &'a SemanticsScope<'a, RootDatabase>,
) -> Self {
pub fn new(target_scope: &'a SemanticsScope<'a>, source_scope: &'a SemanticsScope<'a>) -> Self {
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?)?;
match resolution {
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 type_args = p

View file

@ -8,7 +8,7 @@ use stdx::SepBy;
use crate::{
assist_context::{AssistContext, Assists},
AssistId,
AssistId, AssistKind,
};
// Assist: add_custom_impl
@ -29,8 +29,8 @@ use crate::{
// }
// ```
pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let input = ctx.find_node_at_offset::<ast::AttrInput>()?;
let attr = input.syntax().parent().and_then(ast::Attr::cast)?;
let attr = ctx.find_node_at_offset::<ast::Attr>()?;
let input = attr.token_tree()?;
let attr_name = attr
.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);
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
.syntax()
.descendants_with_tokens()

View file

@ -1,10 +1,10 @@
use hir::HirDisplay;
use ra_syntax::{
ast::{self, AstNode, LetStmt, NameOwner, TypeAscriptionOwner},
ast::{self, AstNode, LetStmt, NameOwner},
TextRange,
};
use crate::{AssistContext, AssistId, Assists};
use crate::{AssistContext, AssistId, AssistKind, Assists};
// 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<()> {
let stmt = ctx.find_node_at_offset::<LetStmt>()?;
let module = ctx.sema.scope(stmt.syntax()).module()?;
let expr = stmt.initializer()?;
let let_stmt = ctx.find_node_at_offset::<LetStmt>()?;
let module = ctx.sema.scope(let_stmt.syntax()).module()?;
let expr = let_stmt.initializer()?;
// Must be a binding
let pat = match stmt.pat()? {
let pat = match let_stmt.pat()? {
ast::Pat::BindPat(bind_pat) => bind_pat,
_ => return None,
};
@ -34,8 +34,8 @@ pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext) -> Optio
// The binding must have a name
let name = pat.name()?;
let name_range = name.syntax().text_range();
let stmt_range = stmt.syntax().text_range();
let eq_range = stmt.eq_token()?.text_range();
let stmt_range = let_stmt.syntax().text_range();
let eq_range = let_stmt.eq_token()?.text_range();
// Assist should only be applicable if cursor is between 'let' and '='
let let_range = TextRange::new(stmt_range.start(), eq_range.start());
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
// and it has no placeholders
let ascribed_ty = stmt.ascribed_type();
let ascribed_ty = let_stmt.ty();
if let Some(ty) = &ascribed_ty {
if ty.syntax().descendants().find_map(ast::PlaceholderType::cast).is_none() {
return None;
@ -57,9 +57,9 @@ pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext) -> Optio
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(
AssistId("add_explicit_type"),
AssistId("add_explicit_type", AssistKind::RefactorRewrite),
format!("Insert explicit type `{}`", inferred_type),
pat_range,
|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},
ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams},
utils::{get_missing_assoc_items, render_snippet, resolve_target_trait, Cursor},
AssistId,
AssistId, AssistKind,
};
#[derive(PartialEq)]
@ -111,16 +111,17 @@ fn add_missing_impl_members_inner(
label: &'static str,
) -> Option<()> {
let _p = ra_prof::profile("add_missing_impl_members_inner");
let impl_def = ctx.find_node_at_offset::<ast::ImplDef>()?;
let impl_item_list = impl_def.item_list()?;
let impl_def = ctx.find_node_at_offset::<ast::Impl>()?;
let impl_item_list = impl_def.assoc_item_list()?;
let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?;
let def_name = |item: &ast::AssocItem| -> Option<SmolStr> {
match item {
ast::AssocItem::FnDef(def) => def.name(),
ast::AssocItem::TypeAliasDef(def) => def.name(),
ast::AssocItem::ConstDef(def) => def.name(),
ast::AssocItem::Fn(def) => def.name(),
ast::AssocItem::TypeAlias(def) => def.name(),
ast::AssocItem::Const(def) => def.name(),
ast::AssocItem::MacroCall(_) => None,
}
.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)
.iter()
.map(|i| match i {
hir::AssocItem::Function(i) => ast::AssocItem::FnDef(i.source(ctx.db).value),
hir::AssocItem::TypeAlias(i) => ast::AssocItem::TypeAliasDef(i.source(ctx.db).value),
hir::AssocItem::Const(i) => ast::AssocItem::ConstDef(i.source(ctx.db).value),
hir::AssocItem::Function(i) => ast::AssocItem::Fn(i.source(ctx.db()).value),
hir::AssocItem::TypeAlias(i) => ast::AssocItem::TypeAlias(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| match t {
ast::AssocItem::FnDef(def) => match mode {
ast::AssocItem::Fn(def) => match mode {
AddMissingImplMembersMode::DefaultMethodsOnly => def.body().is_some(),
AddMissingImplMembersMode::NoDefaultMethods => def.body().is_none(),
},
@ -147,7 +148,7 @@ fn add_missing_impl_members_inner(
}
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 source_scope = ctx.sema.scope_for_def(trait_);
let target_scope = ctx.sema.scope(impl_item_list.syntax());
@ -157,7 +158,8 @@ fn add_missing_impl_members_inner(
.into_iter()
.map(|it| ast_transform::apply(&*ast_transform, 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,
})
.map(|it| edit::remove_attrs_and_docs(&it));
@ -170,7 +172,7 @@ fn add_missing_impl_members_inner(
Some(cap) => {
let mut cursor = Cursor::Before(first_new_item.syntax());
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 m.syntax().text() == "todo!()" {
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() {
return fn_def;
}
@ -681,6 +683,28 @@ impl Foo<T> for S<T> {
fn bar(&self, this: &T, that: &Self) {
${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::{
assist_context::{AssistContext, Assists},
AssistId,
AssistId, AssistKind,
};
// Assist: add_turbo_fish
@ -25,7 +25,14 @@ use crate::{
// }
// ```
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()?;
if next_token.kind() == T![::] {
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);
return None;
}
acc.add(AssistId("add_turbo_fish"), "Add `::<>`", ident.text_range(), |builder| {
match ctx.config.snippet_cap {
acc.add(
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:_}>"),
None => builder.insert(ident.text_range().end(), "::<_>"),
}
})
},
)
}
#[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]
fn add_turbo_fish_method() {
check_assist(

View file

@ -1,6 +1,6 @@
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
//
@ -39,11 +39,16 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<(
let rhs_range = rhs.syntax().text_range();
let not_rhs = invert_boolean_expression(rhs);
acc.add(AssistId("apply_demorgan"), "Apply De Morgan's law", op_range, |edit| {
edit.replace(op_range, opposite_op);
edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text()));
edit.replace(rhs_range, format!("{})", not_rhs.syntax().text()));
})
acc.add(
AssistId("apply_demorgan", AssistKind::RefactorRewrite),
"Apply De Morgan's law",
op_range,
|edit| {
edit.replace(op_range, opposite_op);
edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text()));
edit.replace(rhs_range, format!("{})", not_rhs.syntax().text()));
},
)
}
// 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,
Type,
};
use ra_ide_db::{imports_locator::ImportsLocator, RootDatabase};
use ra_ide_db::{imports_locator, RootDatabase};
use ra_prof::profile;
use ra_syntax::{
ast::{self, AstNode},
@ -13,7 +13,9 @@ use ra_syntax::{
};
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
//
@ -35,8 +37,8 @@ use crate::{utils::insert_use_statement, AssistContext, AssistId, Assists, Group
// # pub mod std { pub mod collections { pub struct HashMap { } } }
// ```
pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let auto_import_assets = AutoImportAssets::new(&ctx)?;
let proposed_imports = auto_import_assets.search_for_imports(ctx.db);
let auto_import_assets = AutoImportAssets::new(ctx)?;
let proposed_imports = auto_import_assets.search_for_imports(ctx);
if proposed_imports.is_empty() {
return None;
}
@ -46,7 +48,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
for import in proposed_imports {
acc.add_group(
&group,
AssistId("auto_import"),
AssistId("auto_import", AssistKind::QuickFix),
format!("Import `{}`", &import),
range,
|builder| {
@ -90,7 +92,7 @@ impl AutoImportAssets {
fn for_regular_path(path_under_caret: ast::Path, ctx: &AssistContext) -> Option<Self> {
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;
}
@ -127,11 +129,11 @@ impl AutoImportAssets {
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 db = ctx.db();
let current_crate = self.module_with_name_to_import.krate();
ImportsLocator::new(db, current_crate)
.find_imports(&self.get_search_query())
imports_locator::find_imports(&ctx.sema, current_crate, &self.get_search_query())
.into_iter()
.filter_map(|candidate| match &self.import_candidate {
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]
fn not_applicable_for_imported_trait_for_method() {
check_assist_not_applicable(

View file

@ -3,7 +3,7 @@ use ra_syntax::{
AstNode, SyntaxNode,
};
use crate::{AssistContext, AssistId, Assists};
use crate::{AssistContext, AssistId, AssistKind, Assists};
use test_utils::mark;
// 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<()> {
let ret_type = ctx.find_node_at_offset::<ast::RetType>()?;
// 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 first_part_ret_type = ret_type_str.splitn(2, '<').next();
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()?;
acc.add(
AssistId("change_return_type_to_result"),
"Change return type to Result",
AssistId("change_return_type_to_result", AssistKind::RefactorRewrite),
"Wrap return type in Result",
type_ref.syntax().text_range(),
|builder| {
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::PathExpr(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::MethodCallExpr(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::{
ast::{self, NameOwner, VisibilityOwner},
AstNode,
SyntaxKind::{CONST_DEF, ENUM_DEF, FN_DEF, MODULE, STRUCT_DEF, TRAIT_DEF, VISIBILITY},
SyntaxKind::{CONST, ENUM, FN, MODULE, STATIC, STRUCT, TRAIT, VISIBILITY},
T,
};
use test_utils::mark;
use crate::{utils::vis_offset, AssistContext, AssistId, Assists};
use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists};
// 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<()> {
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 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
if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) {
return None;
@ -44,7 +47,7 @@ fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
}
(vis_offset(&parent), keyword.text_range())
} 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 {
mark::hit!(change_visibility_field_false_positive);
return None;
@ -53,7 +56,7 @@ fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
return None;
}
(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() {
return None;
}
@ -62,16 +65,21 @@ fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
return None;
};
acc.add(AssistId("change_visibility"), "Change visibility to pub(crate)", target, |edit| {
edit.insert(offset, "pub(crate) ");
})
acc.add(
AssistId("change_visibility", AssistKind::RefactorRewrite),
"Change visibility to pub(crate)",
target,
|edit| {
edit.insert(offset, "pub(crate) ");
},
)
}
fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> {
if vis.syntax().text() == "pub" {
let target = vis.syntax().text_range();
return acc.add(
AssistId("change_visibility"),
AssistId("change_visibility", AssistKind::RefactorRewrite),
"Change Visibility to pub(crate)",
target,
|edit| {
@ -82,7 +90,7 @@ fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> {
if vis.syntax().text() == "pub(crate)" {
let target = vis.syntax().text_range();
return acc.add(
AssistId("change_visibility"),
AssistId("change_visibility", AssistKind::RefactorRewrite),
"Change visibility to pub",
target,
|edit| {
@ -146,6 +154,11 @@ mod tests {
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]
fn change_visibility_handles_comment_attrs() {
check_assist(

View file

@ -8,14 +8,14 @@ use ra_syntax::{
make,
},
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,
};
use crate::{
assist_context::{AssistContext, Assists},
utils::invert_boolean_expression,
AssistId,
AssistId, AssistKind,
};
// 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() {
WHILE_EXPR | LOOP_EXPR => make::expr_continue(),
FN_DEF => make::expr_return(),
FN => make::expr_return(),
_ => return None,
};
@ -99,86 +99,92 @@ 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)?;
let target = if_expr.syntax().text_range();
acc.add(AssistId("convert_to_guarded_return"), "Convert to guarded return", target, |edit| {
let if_indent_level = IndentLevel::from_node(&if_expr.syntax());
let new_block = match if_let_pat {
None => {
// If.
let new_expr = {
let then_branch =
make::block_expr(once(make::expr_stmt(early_expression).into()), None);
let cond = invert_boolean_expression(cond_expr);
make::expr_if(make::condition(cond, None), then_branch).indent(if_indent_level)
};
replace(new_expr.syntax(), &then_block, &parent_block, &if_expr)
}
Some((path, bound_ident)) => {
// If-let.
let match_expr = {
let happy_arm = {
let pat = make::tuple_struct_pat(
path,
once(make::bind_pat(make::name("it")).into()),
);
let expr = {
let name_ref = make::name_ref("it");
let segment = make::path_segment(name_ref);
let path = make::path_unqualified(segment);
make::expr_path(path)
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 new_block = match if_let_pat {
None => {
// If.
let new_expr = {
let then_branch =
make::block_expr(once(make::expr_stmt(early_expression).into()), None);
let cond = invert_boolean_expression(cond_expr);
make::expr_if(make::condition(cond, None), then_branch)
.indent(if_indent_level)
};
replace(new_expr.syntax(), &then_block, &parent_block, &if_expr)
}
Some((path, bound_ident)) => {
// If-let.
let match_expr = {
let happy_arm = {
let pat = make::tuple_struct_pat(
path,
once(make::bind_pat(make::name("it")).into()),
);
let expr = {
let name_ref = make::name_ref("it");
let segment = make::path_segment(name_ref);
let path = make::path_unqualified(segment);
make::expr_path(path)
};
make::match_arm(once(pat.into()), expr)
};
make::match_arm(once(pat.into()), expr)
let sad_arm = make::match_arm(
// FIXME: would be cool to use `None` or `Err(_)` if appropriate
once(make::placeholder_pat().into()),
early_expression,
);
make::expr_match(cond_expr, make::match_arm_list(vec![happy_arm, sad_arm]))
};
let sad_arm = make::match_arm(
// FIXME: would be cool to use `None` or `Err(_)` if appropriate
once(make::placeholder_pat().into()),
early_expression,
let let_stmt = make::let_stmt(
make::bind_pat(make::name(&bound_ident.syntax().to_string())).into(),
Some(match_expr),
);
let let_stmt = let_stmt.indent(if_indent_level);
replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr)
}
};
edit.replace_ast(parent_block, ast::BlockExpr::cast(new_block).unwrap());
make::expr_match(cond_expr, make::match_arm_list(vec![happy_arm, sad_arm]))
};
let let_stmt = make::let_stmt(
make::bind_pat(make::name(&bound_ident.syntax().to_string())).into(),
Some(match_expr),
fn replace(
new_expr: &SyntaxNode,
then_block: &ast::BlockExpr,
parent_block: &ast::BlockExpr,
if_expr: &ast::IfExpr,
) -> SyntaxNode {
let then_block_items = then_block.dedent(IndentLevel(1));
let end_of_then = then_block_items.syntax().last_child_or_token().unwrap();
let end_of_then =
if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) {
end_of_then.prev_sibling_or_token().unwrap()
} else {
end_of_then
};
let mut then_statements = new_expr.children_with_tokens().chain(
then_block_items
.syntax()
.children_with_tokens()
.skip(1)
.take_while(|i| *i != end_of_then),
);
let let_stmt = let_stmt.indent(if_indent_level);
replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr)
replace_children(
&parent_block.syntax(),
RangeInclusive::new(
if_expr.clone().syntax().clone().into(),
if_expr.syntax().clone().into(),
),
&mut then_statements,
)
}
};
edit.replace_ast(parent_block, ast::BlockExpr::cast(new_block).unwrap());
fn replace(
new_expr: &SyntaxNode,
then_block: &ast::BlockExpr,
parent_block: &ast::BlockExpr,
if_expr: &ast::IfExpr,
) -> SyntaxNode {
let then_block_items = then_block.dedent(IndentLevel(1));
let end_of_then = then_block_items.syntax().last_child_or_token().unwrap();
let end_of_then =
if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) {
end_of_then.prev_sibling_or_token().unwrap()
} else {
end_of_then
};
let mut then_statements = new_expr.children_with_tokens().chain(
then_block_items
.syntax()
.children_with_tokens()
.skip(1)
.take_while(|i| *i != end_of_then),
);
replace_children(
&parent_block.syntax(),
RangeInclusive::new(
if_expr.clone().syntax().clone().into(),
if_expr.syntax().clone().into(),
),
&mut then_statements,
)
}
})
},
)
}
#[cfg(test)]

View file

@ -10,7 +10,8 @@ use ra_syntax::{
use rustc_hash::FxHashSet;
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
@ -30,30 +31,30 @@ pub(crate) fn extract_struct_from_enum_variant(
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 field_list = match variant.kind() {
ast::StructKind::Tuple(field_list) => field_list,
_ => return None,
};
let variant_name = variant.name()?.to_string();
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;
}
let enum_ast = variant.parent_enum();
let visibility = enum_ast.visibility();
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 current_module = enum_hir.module(ctx.db);
let current_module = enum_hir.module(ctx.db());
let target = variant.syntax().text_range();
acc.add(
AssistId("extract_struct_from_enum_variant"),
AssistId("extract_struct_from_enum_variant", AssistKind::RefactorRewrite),
"Extract struct from enum variant",
target,
|builder| {
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 mut visited_modules_set = FxHashSet::default();
visited_modules_set.insert(current_module);
@ -101,7 +102,7 @@ fn insert_import(
enum_module_def: &ModuleDef,
variant_hir_name: &Name,
) -> Option<()> {
let db = ctx.db;
let db = ctx.db();
let mod_path = module.find_use_path(db, enum_module_def.clone());
if let Some(mut mod_path) = mod_path {
mod_path.segments.pop();

View file

@ -2,14 +2,13 @@ use ra_syntax::{
ast::{self, AstNode},
SyntaxKind::{
BLOCK_EXPR, BREAK_EXPR, COMMENT, LAMBDA_EXPR, LOOP_EXPR, MATCH_ARM, PATH_EXPR, RETURN_EXPR,
WHITESPACE,
},
SyntaxNode,
};
use stdx::format_to;
use test_utils::mark;
use crate::{AssistContext, AssistId, Assists};
use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: extract_variable
//
@ -36,87 +35,84 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext) -> Option
mark::hit!(extract_var_in_comment_is_not_applicable);
return None;
}
let expr = node.ancestors().find_map(valid_target_expr)?;
let (anchor_stmt, wrap_in_block) = anchor_stmt(expr.clone())?;
let indent = anchor_stmt.prev_sibling_or_token()?.as_token()?.clone();
if indent.kind() != WHITESPACE {
return None;
}
let target = expr.syntax().text_range();
acc.add(AssistId("extract_variable"), "Extract into variable", target, move |edit| {
let field_shorthand = match expr.syntax().parent().and_then(ast::RecordField::cast) {
Some(field) => field.name_ref(),
None => None,
};
let to_extract = node.ancestors().find_map(valid_target_expr)?;
let anchor = Anchor::from(&to_extract)?;
let indent = anchor.syntax().prev_sibling_or_token()?.as_token()?.clone();
let target = to_extract.syntax().text_range();
acc.add(
AssistId("extract_variable", AssistKind::RefactorExtract),
"Extract into variable",
target,
move |edit| {
let field_shorthand =
match to_extract.syntax().parent().and_then(ast::RecordExprField::cast) {
Some(field) => field.name_ref(),
None => None,
};
let mut buf = String::new();
let mut buf = String::new();
let var_name = match &field_shorthand {
Some(it) => it.to_string(),
None => "var_name".to_string(),
};
let expr_range = match &field_shorthand {
Some(it) => it.syntax().text_range().cover(expr.syntax().text_range()),
None => expr.syntax().text_range(),
};
let var_name = match &field_shorthand {
Some(it) => it.to_string(),
None => "var_name".to_string(),
};
let expr_range = match &field_shorthand {
Some(it) => it.syntax().text_range().cover(to_extract.syntax().text_range()),
None => to_extract.syntax().text_range(),
};
if wrap_in_block {
format_to!(buf, "{{ let {} = ", var_name);
} else {
format_to!(buf, "let {} = ", var_name);
};
format_to!(buf, "{}", expr.syntax());
if let Anchor::WrapInBlock(_) = anchor {
format_to!(buf, "{{ let {} = ", var_name);
} else {
format_to!(buf, "let {} = ", var_name);
};
format_to!(buf, "{}", to_extract.syntax());
let full_stmt = ast::ExprStmt::cast(anchor_stmt.clone());
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);
if full_stmt.unwrap().semicolon_token().is_none() {
buf.push_str(";");
if let Anchor::Replace(stmt) = anchor {
mark::hit!(test_extract_var_expr_stmt);
if stmt.semicolon_token().is_none() {
buf.push_str(";");
}
match ctx.config.snippet_cap {
Some(cap) => {
let snip = buf
.replace(&format!("let {}", var_name), &format!("let $0{}", var_name));
edit.replace_snippet(cap, expr_range, snip)
}
None => edit.replace(expr_range, buf),
}
return;
}
buf.push_str(";");
// We want to maintain the indent level,
// but we do not want to duplicate possible
// extra newlines in the indent block
let text = indent.text();
if text.starts_with('\n') {
buf.push_str("\n");
buf.push_str(text.trim_start_matches('\n'));
} else {
buf.push_str(text);
}
edit.replace(expr_range, var_name.clone());
let offset = anchor.syntax().text_range().start();
match ctx.config.snippet_cap {
Some(cap) => {
let snip =
buf.replace(&format!("let {}", var_name), &format!("let $0{}", var_name));
edit.replace_snippet(cap, expr_range, snip)
edit.insert_snippet(cap, offset, snip)
}
None => edit.replace(expr_range, buf),
None => edit.insert(offset, buf),
}
return;
}
buf.push_str(";");
// We want to maintain the indent level,
// but we do not want to duplicate possible
// extra newlines in the indent block
let text = indent.text();
if text.starts_with('\n') {
buf.push_str("\n");
buf.push_str(text.trim_start_matches('\n'));
} else {
buf.push_str(text);
}
edit.replace(expr_range, var_name.clone());
let offset = anchor_stmt.text_range().start();
match ctx.config.snippet_cap {
Some(cap) => {
let snip =
buf.replace(&format!("let {}", var_name), &format!("let $0{}", var_name));
edit.insert_snippet(cap, offset, snip)
if let Anchor::WrapInBlock(_) = anchor {
edit.insert(anchor.syntax().text_range().end(), " }");
}
None => edit.insert(offset, buf),
}
if wrap_in_block {
edit.insert(anchor_stmt.text_range().end(), " }");
}
})
},
)
}
/// Check whether the node is a valid expression which can be extracted to a variable.
@ -133,32 +129,48 @@ fn valid_target_expr(node: SyntaxNode) -> Option<ast::Expr> {
}
}
/// Returns the syntax node which will follow the freshly extractd var
/// and a boolean indicating whether we have to wrap it within a { } block
/// to produce correct code.
/// It can be a statement, the last in a block expression or a wanna be block
/// expression like a lambda or match arm.
fn anchor_stmt(expr: ast::Expr) -> Option<(SyntaxNode, bool)> {
expr.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 {
mark::hit!(test_extract_var_last_expr);
return Some((node, false));
enum Anchor {
Before(SyntaxNode),
Replace(ast::ExprStmt),
WrapInBlock(SyntaxNode),
}
impl Anchor {
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 {
mark::hit!(test_extract_var_last_expr);
return Some(Anchor::Before(node));
}
}
}
if let Some(parent) = node.parent() {
if parent.kind() == MATCH_ARM || parent.kind() == LAMBDA_EXPR {
return Some((node, true));
if let Some(parent) = node.parent() {
if parent.kind() == MATCH_ARM || parent.kind() == LAMBDA_EXPR {
return Some(Anchor::WrapInBlock(node));
}
}
}
if ast::Stmt::cast(node.clone()).is_some() {
return Some((node, false));
}
if let Some(stmt) = ast::Stmt::cast(node.clone()) {
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)]

View file

@ -8,7 +8,7 @@ use test_utils::mark;
use crate::{
utils::{render_snippet, Cursor, FamousDefs},
AssistContext, AssistId, Assists,
AssistContext, AssistId, AssistKind, Assists,
};
// 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 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
.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))
.map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block()))
.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.
enum_defs
.into_iter()
.map(|enum_def| enum_def.variants(ctx.db))
.map(|enum_def| enum_def.variants(ctx.db()))
.multi_cartesian_product()
.map(|variants| {
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))
})
.filter(|variant_pat| is_variant_missing(&mut arms, variant_pat))
@ -103,24 +103,37 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
}
let target = match_expr.syntax().text_range();
acc.add(AssistId("fill_match_arms"), "Fill match arms", target, |builder| {
let new_arm_list = match_arm_list.remove_placeholder();
let n_old_arms = new_arm_list.arms().count();
let new_arm_list = new_arm_list.append_arms(missing_arms);
let first_new_arm = new_arm_list.arms().nth(n_old_arms);
let old_range = match_arm_list.syntax().text_range();
match (first_new_arm, ctx.config.snippet_cap) {
(Some(first_new_arm), Some(cap)) => {
let snippet = render_snippet(
cap,
new_arm_list.syntax(),
Cursor::Before(first_new_arm.syntax()),
);
builder.replace_snippet(cap, old_range, snippet);
acc.add(
AssistId("fill_match_arms", AssistKind::QuickFix),
"Fill match arms",
target,
|builder| {
let new_arm_list = match_arm_list.remove_placeholder();
let n_old_arms = new_arm_list.arms().count();
let new_arm_list = new_arm_list.append_arms(missing_arms);
let first_new_arm = new_arm_list.arms().nth(n_old_arms);
let old_range = match_arm_list.syntax().text_range();
match (first_new_arm, ctx.config.snippet_cap) {
(Some(first_new_arm), Some(cap)) => {
let extend_lifetime;
let cursor = match 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(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 {
@ -286,30 +299,22 @@ mod tests {
check_assist(
fill_match_arms,
r#"
enum A {
As,
Bs,
Cs(Option<i32>),
}
fn main() {
match A::As<|> {
A::Cs(_) | A::Bs => {}
}
}
"#,
enum A { As, Bs, Cs(Option<i32>) }
fn main() {
match A::As<|> {
A::Cs(_) | A::Bs => {}
}
}
"#,
r#"
enum A {
As,
Bs,
Cs(Option<i32>),
}
fn main() {
match A::As {
A::Cs(_) | A::Bs => {}
$0A::As => {}
}
}
"#,
enum A { As, Bs, Cs(Option<i32>) }
fn main() {
match A::As {
A::Cs(_) | A::Bs => {}
$0A::As => {}
}
}
"#,
);
}
@ -318,47 +323,29 @@ mod tests {
check_assist(
fill_match_arms,
r#"
enum A {
As,
Bs,
Cs,
Ds(String),
Es(B),
}
enum B {
Xs,
Ys,
}
fn main() {
match A::As<|> {
A::Bs if 0 < 1 => {}
A::Ds(_value) => { let x = 1; }
A::Es(B::Xs) => (),
}
}
"#,
enum A { As, Bs, Cs, Ds(String), Es(B) }
enum B { Xs, Ys }
fn main() {
match A::As<|> {
A::Bs if 0 < 1 => {}
A::Ds(_value) => { let x = 1; }
A::Es(B::Xs) => (),
}
}
"#,
r#"
enum A {
As,
Bs,
Cs,
Ds(String),
Es(B),
}
enum B {
Xs,
Ys,
}
fn main() {
match A::As {
A::Bs if 0 < 1 => {}
A::Ds(_value) => { let x = 1; }
A::Es(B::Xs) => (),
$0A::As => {}
A::Cs => {}
}
}
"#,
enum A { As, Bs, Cs, Ds(String), Es(B) }
enum B { Xs, Ys }
fn main() {
match A::As {
A::Bs if 0 < 1 => {}
A::Ds(_value) => { let x = 1; }
A::Es(B::Xs) => (),
$0A::As => {}
A::Cs => {}
}
}
"#,
);
}
@ -367,32 +354,24 @@ mod tests {
check_assist(
fill_match_arms,
r#"
enum A {
As,
Bs,
Cs(Option<i32>),
}
fn main() {
match A::As<|> {
A::As(_) => {}
a @ A::Bs(_) => {}
}
}
"#,
enum A { As, Bs, Cs(Option<i32>) }
fn main() {
match A::As<|> {
A::As(_) => {}
a @ A::Bs(_) => {}
}
}
"#,
r#"
enum A {
As,
Bs,
Cs(Option<i32>),
}
fn main() {
match A::As {
A::As(_) => {}
a @ A::Bs(_) => {}
$0A::Cs(_) => {}
}
}
"#,
enum A { As, Bs, Cs(Option<i32>) }
fn main() {
match A::As {
A::As(_) => {}
a @ A::Bs(_) => {}
A::Cs(${0:_}) => {}
}
}
"#,
);
}
@ -401,39 +380,27 @@ mod tests {
check_assist(
fill_match_arms,
r#"
enum A {
As,
Bs,
Cs(String),
Ds(String, String),
Es { x: usize, y: usize }
}
enum A { As, Bs, Cs(String), Ds(String, String), Es { x: usize, y: usize } }
fn main() {
let a = A::As;
match a<|> {}
}
"#,
fn main() {
let a = A::As;
match a<|> {}
}
"#,
r#"
enum A {
As,
Bs,
Cs(String),
Ds(String, String),
Es { x: usize, y: usize }
}
enum A { As, Bs, Cs(String), Ds(String, String), Es { x: usize, y: usize } }
fn main() {
let a = A::As;
match a {
$0A::As => {}
A::Bs => {}
A::Cs(_) => {}
A::Ds(_, _) => {}
A::Es { x, y } => {}
}
}
"#,
fn main() {
let a = A::As;
match a {
$0A::As => {}
A::Bs => {}
A::Cs(_) => {}
A::Ds(_, _) => {}
A::Es { x, y } => {}
}
}
"#,
);
}
@ -773,7 +740,7 @@ fn foo(opt: Option<i32>) {
r#"
fn foo(opt: Option<i32>) {
match opt {
$0Some(_) => {}
Some(${0:_}) => {}
None => {}
}
}

View file

@ -2,7 +2,8 @@ use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution};
use ra_db::FileId;
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.
@ -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 target_module = def.module(ctx.db)?;
let target_module = def.module(ctx.db())?;
let vis = target_module.visibility_of(ctx.db, &def)?;
if vis.is_visible_from(ctx.db, current_module.into()) {
let vis = target_module.visibility_of(ctx.db(), &def)?;
if vis.is_visible_from(ctx.db(), current_module.into()) {
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 =
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),
};
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);
match ctx.config.snippet_cap {
Some(cap) => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
None => builder.insert(offset, format!("{} ", 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)),
},
}
})
}
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 current_module = ctx.sema.scope(record_field.syntax()).module()?;
let visibility = record_field_def.visibility(ctx.db);
if visibility.is_visible_from(ctx.db, current_module.into()) {
let visibility = record_field_def.visibility(ctx.db());
if visibility.is_visible_from(ctx.db(), current_module.into()) {
return None;
}
let parent = record_field_def.parent_def(ctx.db);
let parent_name = parent.name(ctx.db);
let target_module = parent.module(ctx.db);
let parent = record_field_def.parent_def(ctx.db());
let parent_name = parent.name(ctx.db());
let target_module = parent.module(ctx.db());
let in_file_source = record_field_def.source(ctx.db);
let (offset, target) = match in_file_source.value {
let in_file_source = record_field_def.source(ctx.db());
let (offset, current_visibility, target) = match in_file_source.value {
hir::FieldSource::Named(it) => {
let s = it.syntax();
(vis_offset(s), s.text_range())
(vis_offset(s), it.visibility(), s.text_range())
}
hir::FieldSource::Pos(it) => {
let s = it.syntax();
(vis_offset(s), s.text_range())
(vis_offset(s), it.visibility(), s.text_range())
}
};
let missing_visibility =
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 =
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);
match ctx.config.snippet_cap {
Some(cap) => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
None => builder.insert(offset, format!("{} ", 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)),
},
}
})
}
@ -113,24 +139,30 @@ fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext) ->
fn target_data_for_def(
db: &dyn HirDatabase,
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>(
db: &dyn HirDatabase,
x: S,
) -> (TextSize, TextRange, FileId)
) -> (TextSize, Option<ast::Visibility>, TextRange, FileId)
where
S: HasSource<Ast = Ast>,
Ast: AstNode,
Ast: AstNode + ast::VisibilityOwner,
{
let source = x.source(db);
let in_file_syntax = source.syntax();
let file_id = in_file_syntax.file_id;
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 (offset, target, target_file) = match def {
let (offset, current_visibility, target, target_file) = match def {
hir::ModuleDef::Function(f) => {
target_name = Some(f.name(db));
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 file_id = in_file_source.file_id.original_file(db.upcast());
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
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)]
@ -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]
#[ignore]
// FIXME handle reexports properly

View file

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

View file

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

View file

@ -4,7 +4,7 @@ use ra_syntax::{
Direction, T,
};
use crate::{AssistContext, AssistId, Assists};
use crate::{AssistContext, AssistId, AssistKind, Assists};
// 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();
acc.add(AssistId("flip_trait_bound"), "Flip trait bounds", target, |edit| {
edit.replace(before.text_range(), after.to_string());
edit.replace(after.text_range(), before.to_string());
})
acc.add(
AssistId("flip_trait_bound", AssistKind::RefactorRewrite),
"Flip trait bounds",
target,
|edit| {
edit.replace(before.text_range(), after.to_string());
edit.replace(after.text_range(), before.to_string());
},
)
}
#[cfg(test)]

View file

@ -4,9 +4,9 @@ use ra_syntax::{
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.
//
@ -24,36 +24,41 @@ use crate::{AssistContext, AssistId, Assists};
// 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 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 target = nominal.syntax().text_range();
acc.add(AssistId("add_derive"), "Add `#[derive]`", target, |builder| {
let derive_attr = nominal
.attrs()
.filter_map(|x| x.as_simple_call())
.filter(|(name, _arg)| name == "derive")
.map(|(_name, arg)| arg)
.next();
match derive_attr {
None => {
builder.insert_snippet(cap, node_start, "#[derive($0)]\n");
}
Some(tt) => {
// Just move the cursor.
builder.insert_snippet(
cap,
tt.syntax().text_range().end() - TextSize::of(')'),
"$0",
)
}
};
})
acc.add(
AssistId("generate_derive", AssistKind::Generate),
"Add `#[derive]`",
target,
|builder| {
let derive_attr = nominal
.attrs()
.filter_map(|x| x.as_simple_call())
.filter(|(name, _arg)| name == "derive")
.map(|(_name, arg)| arg)
.next();
match derive_attr {
None => {
builder.insert_snippet(cap, node_start, "#[derive($0)]\n");
}
Some(tt) => {
// Just move the cursor.
builder.insert_snippet(
cap,
tt.syntax().text_range().end() - TextSize::of(')'),
"$0",
)
}
};
},
)
}
// 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
.syntax()
.children_with_tokens()
@ -70,12 +75,12 @@ mod tests {
#[test]
fn add_derive_new() {
check_assist(
add_derive,
generate_derive,
"struct Foo { a: i32, <|>}",
"#[derive($0)]\nstruct Foo { a: i32, }",
);
check_assist(
add_derive,
generate_derive,
"struct Foo { <|> a: i32, }",
"#[derive($0)]\nstruct Foo { a: i32, }",
);
@ -84,7 +89,7 @@ mod tests {
#[test]
fn add_derive_existing() {
check_assist(
add_derive,
generate_derive,
"#[derive(Clone)]\nstruct Foo { a: i32<|>, }",
"#[derive(Clone$0)]\nstruct Foo { a: i32, }",
);
@ -93,7 +98,7 @@ mod tests {
#[test]
fn add_derive_new_with_doc_comment() {
check_assist(
add_derive,
generate_derive,
"
/// `Foo` is a pretty important struct.
/// It does stuff.
@ -111,7 +116,7 @@ struct Foo { a: i32, }
#[test]
fn add_derive_target() {
check_assist_target(
add_derive,
generate_derive,
"
struct SomeThingIrrelevant;
/// `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 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.
//
@ -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<()> {
let variant = ctx.find_node_at_offset::<ast::EnumVariant>()?;
pub(crate) fn generate_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let variant = ctx.find_node_at_offset::<ast::Variant>()?;
let variant_name = variant.name()?;
let enum_name = variant.parent_enum().name()?;
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 {
return None;
}
let field_type = field_list.fields().next()?.type_ref()?;
let field_type = field_list.fields().next()?.ty()?;
let path = match field_type {
ast::TypeRef::PathType(it) => it,
_ => 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();
acc.add(
AssistId("add_from_impl_for_enum"),
"Add From impl for this enum variant",
AssistId("generate_from_impl_for_enum", AssistKind::Generate),
"Generate `From` impl for this enum variant",
target,
|edit| {
let start_offset = variant.parent_enum().syntax().text_range().end();
@ -69,7 +69,7 @@ impl From<{0}> for {1} {{
fn existing_from_impl(
sema: &'_ hir::Semantics<'_, RootDatabase>,
variant: &ast::EnumVariant,
variant: &ast::Variant,
) -> Option<()> {
let variant = sema.to_def(variant)?;
let enum_ = variant.parent_enum(sema.db);
@ -97,9 +97,9 @@ mod tests {
use super::*;
#[test]
fn test_add_from_impl_for_enum() {
fn test_generate_from_impl_for_enum() {
check_assist(
add_from_impl_for_enum,
generate_from_impl_for_enum,
"enum A { <|>One(u32) }",
r#"enum A { One(u32) }
@ -112,9 +112,9 @@ impl From<u32> for A {
}
#[test]
fn test_add_from_impl_for_enum_complicated_path() {
fn test_generate_from_impl_for_enum_complicated_path() {
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) }
@ -129,7 +129,7 @@ impl From<foo::bar::baz::Boo> for A {
fn check_not_applicable(ra_fixture: &str) {
let 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]
@ -166,7 +166,7 @@ impl From<u32> for A {
#[test]
fn test_add_from_impl_different_variant_impl_exists() {
check_assist(
add_from_impl_for_enum,
generate_from_impl_for_enum,
r#"enum A { <|>One(u32), Two(String), }
impl From<String> for A {

View file

@ -13,10 +13,10 @@ use rustc_hash::{FxHashMap, FxHashSet};
use crate::{
assist_config::SnippetCap,
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.
//
@ -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 call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
let path = path_expr.path()?;
@ -62,22 +62,27 @@ pub(crate) fn add_function(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?;
let target = call.syntax().text_range();
acc.add(AssistId("add_function"), "Add function", target, |builder| {
let function_template = function_builder.render();
builder.edit_file(function_template.file);
let new_fn = function_template.to_string(ctx.config.snippet_cap);
match ctx.config.snippet_cap {
Some(cap) => builder.insert_snippet(cap, function_template.insert_offset, new_fn),
None => builder.insert(function_template.insert_offset, new_fn),
}
})
acc.add(
AssistId("generate_function", AssistKind::Generate),
format!("Generate `{}` function", function_builder.fn_name),
target,
|builder| {
let function_template = function_builder.render();
builder.edit_file(function_template.file);
let new_fn = function_template.to_string(ctx.config.snippet_cap);
match ctx.config.snippet_cap {
Some(cap) => builder.insert_snippet(cap, function_template.insert_offset, new_fn),
None => builder.insert(function_template.insert_offset, new_fn),
}
},
)
}
struct FunctionTemplate {
insert_offset: TextSize,
placeholder_expr: ast::MacroCall,
leading_ws: String,
fn_def: ast::FnDef,
fn_def: ast::Fn,
trailing_ws: String,
file: FileId,
}
@ -99,7 +104,7 @@ impl FunctionTemplate {
struct FunctionBuilder {
target: GeneratedFunctionTarget,
fn_name: ast::Name,
type_params: Option<ast::TypeParamList>,
type_params: Option<ast::GenericParamList>,
params: ast::ParamList,
file: FileId,
needs_pub: bool,
@ -117,7 +122,7 @@ impl FunctionBuilder {
let mut file = ctx.frange.file_id;
let target = match &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)?;
file = in_file;
target
@ -195,7 +200,7 @@ fn fn_args(
ctx: &AssistContext,
target_module: hir::Module,
call: &ast::CallExpr,
) -> Option<(Option<ast::TypeParamList>, ast::ParamList)> {
) -> Option<(Option<ast::GenericParamList>, ast::ParamList)> {
let mut arg_names = Vec::new();
let mut arg_types = Vec::new();
for arg in call.arg_list()?.args() {
@ -269,7 +274,7 @@ fn fn_arg_type(
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)
} else {
None
@ -333,7 +338,7 @@ mod tests {
#[test]
fn add_function_with_no_args() {
check_assist(
add_function,
generate_function,
r"
fn foo() {
bar<|>();
@ -356,7 +361,7 @@ fn bar() {
// This ensures that the function is correctly generated
// in the next outer mod or file
check_assist(
add_function,
generate_function,
r"
impl Foo {
fn foo() {
@ -382,7 +387,7 @@ fn bar() {
fn add_function_directly_after_current_block() {
// The new fn should not be created at the end of the file or module
check_assist(
add_function,
generate_function,
r"
fn foo1() {
bar<|>();
@ -407,7 +412,7 @@ fn foo2() {}
#[test]
fn add_function_with_no_args_in_same_module() {
check_assist(
add_function,
generate_function,
r"
mod baz {
fn foo() {
@ -432,7 +437,7 @@ mod baz {
#[test]
fn add_function_with_function_call_arg() {
check_assist(
add_function,
generate_function,
r"
struct Baz;
fn baz() -> Baz { todo!() }
@ -457,7 +462,7 @@ fn bar(baz: Baz) {
#[test]
fn add_function_with_method_call_arg() {
check_assist(
add_function,
generate_function,
r"
struct Baz;
impl Baz {
@ -490,7 +495,7 @@ fn bar(baz: Baz) {
#[test]
fn add_function_with_string_literal_arg() {
check_assist(
add_function,
generate_function,
r#"
fn foo() {
<|>bar("bar")
@ -511,7 +516,7 @@ fn bar(arg: &str) {
#[test]
fn add_function_with_char_literal_arg() {
check_assist(
add_function,
generate_function,
r#"
fn foo() {
<|>bar('x')
@ -532,7 +537,7 @@ fn bar(arg: char) {
#[test]
fn add_function_with_int_literal_arg() {
check_assist(
add_function,
generate_function,
r"
fn foo() {
<|>bar(42)
@ -553,7 +558,7 @@ fn bar(arg: i32) {
#[test]
fn add_function_with_cast_int_literal_arg() {
check_assist(
add_function,
generate_function,
r"
fn foo() {
<|>bar(42 as u8)
@ -576,7 +581,7 @@ fn bar(arg: u8) {
// Ensures that the name of the cast type isn't used
// in the generated function signature.
check_assist(
add_function,
generate_function,
r"
fn foo() {
let x = 42;
@ -599,7 +604,7 @@ fn bar(x: u8) {
#[test]
fn add_function_with_variable_arg() {
check_assist(
add_function,
generate_function,
r"
fn foo() {
let worble = ();
@ -622,7 +627,7 @@ fn bar(worble: ()) {
#[test]
fn add_function_with_impl_trait_arg() {
check_assist(
add_function,
generate_function,
r"
trait Foo {}
fn foo() -> impl Foo {
@ -651,7 +656,7 @@ fn bar(foo: impl Foo) {
#[test]
fn borrowed_arg() {
check_assist(
add_function,
generate_function,
r"
struct Baz;
fn baz() -> Baz { todo!() }
@ -678,7 +683,7 @@ fn bar(baz: &Baz) {
#[test]
fn add_function_with_qualified_path_arg() {
check_assist(
add_function,
generate_function,
r"
mod Baz {
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
fn add_function_with_generic_arg() {
check_assist(
add_function,
generate_function,
r"
fn foo<T>(t: T) {
<|>bar(t)
@ -732,7 +737,7 @@ fn bar<T>(t: T) {
// FIXME Fix function type printing to make this test pass
fn add_function_with_fn_arg() {
check_assist(
add_function,
generate_function,
r"
struct Baz;
impl Baz {
@ -763,7 +768,7 @@ fn bar(arg: fn() -> Baz) {
// FIXME Fix closure type printing to make this test pass
fn add_function_with_closure_arg() {
check_assist(
add_function,
generate_function,
r"
fn foo() {
let closure = |x: i64| x - 1;
@ -786,7 +791,7 @@ fn bar(closure: impl Fn(i64) -> i64) {
#[test]
fn unresolveable_types_default_to_unit() {
check_assist(
add_function,
generate_function,
r"
fn foo() {
<|>bar(baz)
@ -807,7 +812,7 @@ fn bar(baz: ()) {
#[test]
fn arg_names_dont_overlap() {
check_assist(
add_function,
generate_function,
r"
struct Baz;
fn baz() -> Baz { Baz }
@ -832,7 +837,7 @@ fn bar(baz_1: Baz, baz_2: Baz) {
#[test]
fn arg_name_counters_start_at_1_per_name() {
check_assist(
add_function,
generate_function,
r#"
struct Baz;
fn baz() -> Baz { Baz }
@ -857,7 +862,7 @@ fn bar(baz_1: Baz, baz_2: Baz, arg_1: &str, arg_2: &str) {
#[test]
fn add_function_in_module() {
check_assist(
add_function,
generate_function,
r"
mod bar {}
@ -885,7 +890,7 @@ fn foo() {
// See https://github.com/rust-analyzer/rust-analyzer/issues/1165
fn qualified_path_uses_correct_scope() {
check_assist(
add_function,
generate_function,
"
mod foo {
pub struct Foo;
@ -916,7 +921,7 @@ fn baz(foo: foo::Foo) {
#[test]
fn add_function_in_module_containing_other_items() {
check_assist(
add_function,
generate_function,
r"
mod bar {
fn something_else() {}
@ -945,7 +950,7 @@ fn foo() {
#[test]
fn add_function_in_nested_module() {
check_assist(
add_function,
generate_function,
r"
mod bar {
mod baz {}
@ -974,7 +979,7 @@ fn foo() {
#[test]
fn add_function_in_another_file() {
check_assist(
add_function,
generate_function,
r"
//- /main.rs
mod foo;
@ -996,7 +1001,7 @@ pub(crate) fn bar() {
#[test]
fn add_function_not_applicable_if_function_already_exists() {
check_assist_not_applicable(
add_function,
generate_function,
r"
fn foo() {
bar<|>();
@ -1013,7 +1018,7 @@ fn bar() {}
// bar is resolved, but baz isn't.
// 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.
add_function,
generate_function,
r"
fn foo() {
bar(b<|>az);
@ -1028,7 +1033,7 @@ fn bar(baz: ()) {}
#[ignore]
fn create_method_with_no_args() {
check_assist(
add_function,
generate_function,
r"
struct 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 ra_syntax::{
ast::{
self, AstNode, NameOwner, StructKind, TypeAscriptionOwner, TypeParamsOwner, VisibilityOwner,
},
ast::{self, AstNode, GenericParamsOwner, NameOwner, StructKind, VisibilityOwner},
T,
};
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.
//
@ -29,8 +27,8 @@ use crate::{AssistContext, AssistId, Assists};
// }
//
// ```
pub(crate) fn add_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let strukt = ctx.find_node_at_offset::<ast::StructDef>()?;
pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
// We want to only apply this to non-union structs with named fields
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 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);
if impl_def.is_some() {
@ -53,9 +51,7 @@ pub(crate) fn add_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let params = field_list
.fields()
.filter_map(|f| {
Some(format!("{}: {}", f.name()?.syntax(), f.ascribed_type()?.syntax()))
})
.filter_map(|f| Some(format!("{}: {}", f.name()?.syntax(), f.ty()?.syntax())))
.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
// parameters
fn generate_impl_text(strukt: &ast::StructDef, code: &str) -> String {
let type_params = strukt.type_param_list();
fn generate_impl_text(strukt: &ast::Struct, code: &str) -> String {
let type_params = strukt.generic_param_list();
let mut buf = String::with_capacity(code.len());
buf.push_str("\n\nimpl");
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
// viable (e.g. we process proc macros, etc)
fn find_struct_impl(ctx: &AssistContext, strukt: &ast::StructDef) -> Option<Option<ast::ImplDef>> {
let db = ctx.db;
fn find_struct_impl(ctx: &AssistContext, strukt: &ast::Struct) -> Option<Option<ast::Impl>> {
let db = ctx.db();
let module = strukt.syntax().ancestors().find(|node| {
ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind())
})?;
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)?;
// 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)
}
fn has_new_fn(imp: &ast::ImplDef) -> bool {
if let Some(il) = imp.item_list() {
fn has_new_fn(imp: &ast::Impl) -> bool {
if let Some(il) = imp.assoc_item_list() {
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 name.text().eq_ignore_ascii_case("new") {
return true;
@ -181,10 +177,10 @@ mod tests {
#[test]
#[rustfmt::skip]
fn test_add_new() {
fn test_generate_new() {
// Check output of generation
check_assist(
add_new,
generate_new,
"struct Foo {<|>}",
"struct Foo {}
@ -194,7 +190,7 @@ impl Foo {
",
);
check_assist(
add_new,
generate_new,
"struct Foo<T: Clone> {<|>}",
"struct Foo<T: Clone> {}
@ -204,7 +200,7 @@ impl<T: Clone> Foo<T> {
",
);
check_assist(
add_new,
generate_new,
"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(
add_new,
generate_new,
"struct Foo { baz: String <|>}",
"struct Foo { baz: String }
@ -224,7 +220,7 @@ impl Foo {
",
);
check_assist(
add_new,
generate_new,
"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_assist(
add_new,
generate_new,
"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_assist(
add_new,
generate_new,
"struct Foo {<|>}
impl Foo {}
@ -261,7 +257,7 @@ impl Foo {
",
);
check_assist(
add_new,
generate_new,
"struct Foo {<|>}
impl Foo {
@ -279,7 +275,7 @@ impl Foo {
);
check_assist(
add_new,
generate_new,
"struct Foo {<|>}
impl Foo {
@ -304,7 +300,7 @@ impl Foo {
// Check visibility of new fn based on struct
check_assist(
add_new,
generate_new,
"pub struct Foo {<|>}",
"pub struct Foo {}
@ -314,7 +310,7 @@ impl Foo {
",
);
check_assist(
add_new,
generate_new,
"pub(crate) struct Foo {<|>}",
"pub(crate) struct Foo {}
@ -326,9 +322,9 @@ impl Foo {
}
#[test]
fn add_new_not_applicable_if_fn_exists() {
fn generate_new_not_applicable_if_fn_exists() {
check_assist_not_applicable(
add_new,
generate_new,
"
struct Foo {<|>}
@ -340,7 +336,7 @@ impl Foo {
);
check_assist_not_applicable(
add_new,
generate_new,
"
struct Foo {<|>}
@ -353,9 +349,9 @@ impl Foo {
}
#[test]
fn add_new_target() {
fn generate_new_target() {
check_assist_target(
add_new,
generate_new,
"
struct SomeThingIrrelevant;
/// Has a lifetime parameter
@ -370,7 +366,7 @@ struct Foo<'a, T: Foo<'a>> {}",
#[test]
fn test_unrelated_new() {
check_assist(
add_new,
generate_new,
r##"
pub struct AstId<N: AstNode> {
file_id: HirFileId,

View file

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

View file

@ -1,10 +1,10 @@
use ra_syntax::{
ast::{self, NameOwner, TypeAscriptionOwner, TypeParamsOwner},
ast::{self, GenericParamsOwner, NameOwner},
AstNode, SyntaxKind, TextRange, TextSize,
};
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_LABEL: &str = "Introduce named lifetime";
@ -38,9 +38,9 @@ pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext) -
let lifetime_token = ctx
.find_token_at_offset(SyntaxKind::LIFETIME)
.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())
} 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())
} else {
None
@ -50,11 +50,11 @@ pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext) -
/// Generate the assist for the fn def case
fn generate_fn_def_assist(
acc: &mut Assists,
fn_def: &ast::FnDef,
fn_def: &ast::Fn,
lifetime_loc: TextRange,
) -> Option<()> {
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 self_param =
// 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
let fn_params_without_lifetime: Vec<_> = param_list
.params()
.filter_map(|param| match param.ascribed_type() {
.filter_map(|param| match param.ty() {
Some(ast::TypeRef::ReferenceType(ascribed_type))
if ascribed_type.lifetime_token() == None =>
{
@ -83,7 +83,7 @@ fn generate_fn_def_assist(
_ => 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);
builder.replace(lifetime_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
fn generate_impl_def_assist(
acc: &mut Assists,
impl_def: &ast::ImplDef,
impl_def: &ast::Impl,
lifetime_loc: TextRange,
) -> 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();
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);
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
/// which is not in the list
fn generate_unique_lifetime_param_name(
existing_type_param_list: &Option<ast::TypeParamList>,
existing_type_param_list: &Option<ast::GenericParamList>,
) -> Option<char> {
match existing_type_param_list {
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 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,
builder: &mut AssistBuilder,
new_type_params_loc: TextSize,
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
Some(type_params) => {
builder.insert(

View file

@ -6,7 +6,7 @@ use ra_syntax::{
use crate::{
assist_context::{AssistContext, Assists},
utils::invert_boolean_expression,
AssistId,
AssistId, AssistKind,
};
// 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_range = else_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(else_range, then_node.text());
edit.replace(then_range, else_node.text());

View file

@ -8,7 +8,7 @@ use ra_syntax::{
use crate::{
assist_context::{AssistContext, Assists},
AssistId,
AssistId, AssistKind,
};
// 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 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()
.filter_map(|dir| neighbor(&use_item, dir))
.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();
acc.add(AssistId("merge_imports"), "Merge imports", target, |builder| {
builder.rewrite(rewriter);
})
acc.add(
AssistId("merge_imports", AssistKind::RefactorRewrite),
"Merge imports",
target,
|builder| {
builder.rewrite(rewriter);
},
)
}
fn next_prev() -> impl Iterator<Item = Direction> {

View file

@ -6,7 +6,7 @@ use ra_syntax::{
Direction,
};
use crate::{AssistContext, AssistId, Assists, TextRange};
use crate::{AssistContext, AssistId, AssistKind, Assists, TextRange};
// Assist: merge_match_arms
//
@ -59,25 +59,30 @@ pub(crate) fn merge_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option
return None;
}
acc.add(AssistId("merge_match_arms"), "Merge match arms", current_text_range, |edit| {
let pats = if arms_to_merge.iter().any(contains_placeholder) {
"_".into()
} else {
arms_to_merge
.iter()
.filter_map(ast::MatchArm::pat)
.map(|x| x.syntax().to_string())
.collect::<Vec<String>>()
.join(" | ")
};
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) {
"_".into()
} else {
arms_to_merge
.iter()
.filter_map(ast::MatchArm::pat)
.map(|x| x.syntax().to_string())
.collect::<Vec<String>>()
.join(" | ")
};
let arm = format!("{} => {}", pats, current_expr.syntax().text());
let arm = format!("{} => {}", pats, current_expr.syntax().text());
let start = arms_to_merge.first().unwrap().syntax().text_range().start();
let end = arms_to_merge.last().unwrap().syntax().text_range().end();
let start = arms_to_merge.first().unwrap().syntax().text_range().start();
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 {

View file

@ -5,7 +5,7 @@ use ra_syntax::{
T,
};
use crate::{AssistContext, AssistId, Assists};
use crate::{AssistContext, AssistId, AssistKind, Assists};
// 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<()> {
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();
if type_params.all(|p| p.type_bound_list().is_none()) {
@ -37,42 +37,49 @@ pub(crate) fn move_bounds_to_where_clause(acc: &mut Assists, ctx: &AssistContext
let anchor = match_ast! {
match parent {
ast::FnDef(it) => it.body()?.syntax().clone().into(),
ast::TraitDef(it) => it.item_list()?.syntax().clone().into(),
ast::ImplDef(it) => it.item_list()?.syntax().clone().into(),
ast::EnumDef(it) => it.variant_list()?.syntax().clone().into(),
ast::StructDef(it) => {
ast::Fn(it) => it.body()?.syntax().clone().into(),
ast::Trait(it) => it.assoc_item_list()?.syntax().clone().into(),
ast::Impl(it) => it.assoc_item_list()?.syntax().clone().into(),
ast::Enum(it) => it.variant_list()?.syntax().clone().into(),
ast::Struct(it) => {
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
}
};
let target = type_param_list.syntax().text_range();
acc.add(AssistId("move_bounds_to_where_clause"), "Move to where clause", target, |edit| {
let new_params = type_param_list
.type_params()
.filter(|it| it.type_bound_list().is_some())
.map(|type_param| {
let without_bounds = type_param.remove_bounds();
(type_param, without_bounds)
});
acc.add(
AssistId("move_bounds_to_where_clause", AssistKind::RefactorRewrite),
"Move to where clause",
target,
|edit| {
let new_params = type_param_list
.type_params()
.filter(|it| it.type_bound_list().is_some())
.map(|type_param| {
let without_bounds = type_param.remove_bounds();
(type_param, without_bounds)
});
let new_type_param_list = type_param_list.replace_descendants(new_params);
edit.replace_ast(type_param_list.clone(), new_type_param_list);
let new_type_param_list = type_param_list.replace_descendants(new_params);
edit.replace_ast(type_param_list.clone(), new_type_param_list);
let where_clause = {
let predicates = type_param_list.type_params().filter_map(build_predicate);
make::where_clause(predicates)
};
let where_clause = {
let predicates = type_param_list.type_params().filter_map(build_predicate);
make::where_clause(predicates)
};
let to_insert = match anchor.prev_sibling_or_token() {
Some(ref elem) if elem.kind() == WHITESPACE => format!("{} ", where_clause.syntax()),
_ => format!(" {}", where_clause.syntax()),
};
edit.insert(anchor.text_range().start(), to_insert);
})
let to_insert = match anchor.prev_sibling_or_token() {
Some(ref elem) if elem.kind() == WHITESPACE => {
format!("{} ", where_clause.syntax())
}
_ => format!(" {}", where_clause.syntax()),
};
edit.insert(anchor.text_range().start(), to_insert);
},
)
}
fn build_predicate(param: ast::TypeParam) -> Option<ast::WherePred> {

View file

@ -3,7 +3,7 @@ use ra_syntax::{
SyntaxKind::WHITESPACE,
};
use crate::{AssistContext, AssistId, Assists};
use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: move_guard_to_arm_body
//
@ -40,17 +40,22 @@ 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 target = guard.syntax().text_range();
acc.add(AssistId("move_guard_to_arm_body"), "Move guard to arm body", target, |edit| {
match space_before_guard {
Some(element) if element.kind() == WHITESPACE => {
edit.delete(element.text_range());
}
_ => (),
};
acc.add(
AssistId("move_guard_to_arm_body", AssistKind::RefactorRewrite),
"Move guard to arm body",
target,
|edit| {
match space_before_guard {
Some(element) if element.kind() == WHITESPACE => {
edit.delete(element.text_range());
}
_ => (),
};
edit.delete(guard.syntax().text_range());
edit.replace_node_and_indent(arm_expr.syntax(), buf);
})
edit.delete(guard.syntax().text_range());
edit.replace_node_and_indent(arm_expr.syntax(), buf);
},
)
}
// 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();
acc.add(
AssistId("move_arm_cond_to_match_guard"),
AssistId("move_arm_cond_to_match_guard", AssistKind::RefactorRewrite),
"Move condition to match guard",
target,
|edit| {

View file

@ -1,11 +1,14 @@
use std::borrow::Cow;
use ra_syntax::{
ast::{self, HasStringValue},
ast::{self, HasQuotes, HasStringValue},
AstToken,
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
//
@ -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 value = token.value()?;
let target = token.syntax().text_range();
acc.add(AssistId("make_raw_string"), "Rewrite as raw string", target, |edit| {
let max_hash_streak = count_hashes(&value);
let mut hashes = String::with_capacity(max_hash_streak + 1);
for _ in 0..hashes.capacity() {
hashes.push('#');
}
edit.replace(token.syntax().text_range(), format!("r{}\"{}\"{}", hashes, value, hashes));
})
acc.add(
AssistId("make_raw_string", AssistKind::RefactorRewrite),
"Rewrite as raw string",
target,
|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),
);
}
},
)
}
// 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 value = token.value()?;
let target = token.syntax().text_range();
acc.add(AssistId("make_usual_string"), "Rewrite as regular string", target, |edit| {
// parse inside string to escape `"`
let escaped = value.escape_default().to_string();
edit.replace(token.syntax().text_range(), format!("\"{}\"", escaped));
})
acc.add(
AssistId("make_usual_string", AssistKind::RefactorRewrite),
"Rewrite as regular string",
target,
|edit| {
// parse inside string to escape `"`
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));
},
)
}
// 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<()> {
let token = ctx.find_token_at_offset(RAW_STRING)?;
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().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<()> {
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();
if text.starts_with("r\"") {
// no hash to remove
if !text.starts_with("r#") && text.ends_with('#') {
return None;
}
let target = token.text_range();
acc.add(AssistId("remove_hash"), "Remove hash from raw string", target, |edit| {
let result = &text[2..text.len() - 1];
let result = if result.starts_with('\"') {
// FIXME: this logic is wrong, not only the last has has to handled specially
// no more hash, escape
let internal_str = &result[1..result.len() - 1];
format!("\"{}\"", internal_str.escape_default().to_string())
} else {
result.to_owned()
};
edit.replace(token.text_range(), format!("r{}", result));
let existing_hashes = text.chars().skip(1).take_while(|&it| it == '#').count();
let text_range = token.syntax().text_range();
let internal_text = &text[token.text_range_between_quotes()? - text_range.start()];
if existing_hashes == required_hashes(internal_text) {
mark::hit!(cant_remove_required_hash);
return None;
}
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 {
let mut max_hash_streak = 0usize;
for idx in s.match_indices("\"#").map(|(i, _)| i) {
fn required_hashes(s: &str) -> usize {
let mut res = 0usize;
for idx in s.match_indices('"').map(|(i, _)| i) {
let (_, sub) = s.split_at(idx + 1);
let nb_hash = sub.chars().take_while(|c| *c == '#').count();
if nb_hash > max_hash_streak {
max_hash_streak = nb_hash;
}
let n_hashes = sub.chars().take_while(|c| *c == '#').count();
res = res.max(n_hashes + 1)
}
max_hash_streak
res
}
#[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)]
mod test {
use super::*;
use test_utils::mark;
use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
use super::*;
#[test]
fn make_raw_string_target() {
check_assist_target(
@ -341,33 +381,21 @@ string"###;
fn remove_hash_works() {
check_assist(
remove_hash,
r##"
fn f() {
let s = <|>r#"random string"#;
}
"##,
r#"
fn f() {
let s = r"random string";
}
"#,
r##"fn f() { let s = <|>r#"random string"#; }"##,
r#"fn f() { let s = r"random string"; }"#,
)
}
#[test]
fn remove_hash_with_quote_works() {
check_assist(
fn cant_remove_required_hash() {
mark::check!(cant_remove_required_hash);
check_assist_not_applicable(
remove_hash,
r##"
fn f() {
let s = <|>r#"random"str"ing"#;
}
"##,
r#"
fn f() {
let s = r"random\"str\"ing";
}
"#,
)
}
@ -389,27 +417,13 @@ string"###;
}
#[test]
fn remove_hash_not_works() {
check_assist_not_applicable(
remove_hash,
r#"
fn f() {
let s = <|>"random string";
}
"#,
);
fn remove_hash_doesnt_work() {
check_assist_not_applicable(remove_hash, r#"fn f() { let s = <|>"random string"; }"#);
}
#[test]
fn remove_hash_no_hash_not_works() {
check_assist_not_applicable(
remove_hash,
r#"
fn f() {
let s = <|>r"random string";
}
"#,
);
fn remove_hash_no_hash_doesnt_work() {
check_assist_not_applicable(remove_hash, r#"fn f() { let s = <|>r"random string"; }"#);
}
#[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::{
ast::{self, AstNode},
TextSize, T,
TextRange, TextSize, T,
};
use crate::{AssistContext, AssistId, Assists};
use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: remove_dbg
//
@ -27,19 +27,33 @@ pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
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_args = macro_call.token_tree()?.syntax().clone();
let macro_end = if macro_call.semicolon_token().is_some() {
macro_call.syntax().text_range().end() - TextSize::of(';')
} else {
macro_call.syntax().text_range().end()
};
let text = macro_args.text();
let without_parens = TextSize::of('(')..text.len() - TextSize::of(')');
text.slice(without_parens).to_string()
// macro_range determines what will be deleted and replaced with macro_content
let macro_range = TextRange::new(macro_call.syntax().text_range().start(), macro_end);
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();
acc.add(AssistId("remove_dbg"), "Remove dbg!()", target, |builder| {
builder.replace(macro_range, macro_content);
acc.add(AssistId("remove_dbg", AssistKind::Refactor), "Remove dbg!()", target, |builder| {
builder.replace(macro_range, paste_instead_of_dbg);
})
}
@ -99,6 +113,7 @@ fn foo(n: usize) {
",
);
}
#[test]
fn test_remove_dbg_with_brackets_and_braces() {
check_assist(remove_dbg, "dbg![<|>1 + 1]", "1 + 1");
@ -113,7 +128,7 @@ fn foo(n: usize) {
}
#[test]
fn remove_dbg_target() {
fn test_remove_dbg_target() {
check_assist_target(
remove_dbg,
"
@ -126,4 +141,65 @@ fn foo(n: usize) {
"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 crate::{AssistContext, AssistId, Assists};
use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: remove_mut
//
@ -26,7 +26,12 @@ pub(crate) fn remove_mut(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
};
let target = mut_token.text_range();
acc.add(AssistId("remove_mut"), "Remove `mut` keyword", target, |builder| {
builder.delete(TextRange::new(delete_from, delete_to));
})
acc.add(
AssistId("remove_mut", AssistKind::Refactor),
"Remove `mut` keyword",
target,
|builder| {
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_syntax::{algo, ast, match_ast, AstNode, SyntaxKind, SyntaxKind::*, SyntaxNode};
use crate::{AssistContext, AssistId, Assists};
use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: reorder_fields
//
@ -23,7 +23,7 @@ use crate::{AssistContext, AssistId, Assists};
// ```
//
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<()> {
@ -42,16 +42,21 @@ fn reorder<R: AstNode>(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
}
let target = record.syntax().text_range();
acc.add(AssistId("reorder_fields"), "Reorder record fields", target, |edit| {
for (old, new) in fields.iter().zip(&sorted_fields) {
algo::diff(old, new).into_text_edit(edit.text_edit_builder());
}
})
acc.add(
AssistId("reorder_fields", AssistKind::RefactorRewrite),
"Reorder record fields",
target,
|edit| {
for (old, new) in fields.iter().zip(&sorted_fields) {
algo::diff(old, new).into_text_edit(edit.text_edit_builder());
}
},
)
}
fn get_fields_kind(node: &SyntaxNode) -> Vec<SyntaxKind> {
match node.kind() {
RECORD_LIT => vec![RECORD_FIELD],
RECORD_EXPR => vec![RECORD_EXPR_FIELD],
RECORD_PAT => vec![RECORD_FIELD_PAT, BIND_PAT],
_ => vec![],
}
@ -60,7 +65,7 @@ fn get_fields_kind(node: &SyntaxNode) -> Vec<SyntaxKind> {
fn get_field_name(node: &SyntaxNode) -> String {
let res = match_ast! {
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()),
_ => 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>> {
Some(
struct_definition(path, &ctx.sema)?
.fields(ctx.db)
.fields(ctx.db())
.iter()
.enumerate()
.map(|(idx, field)| (field.name(ctx.db).to_string(), idx))
.map(|(idx, field)| (field.name(ctx.db()).to_string(), idx))
.collect(),
)
}

View file

@ -8,7 +8,7 @@ use ra_syntax::{
AstNode,
};
use crate::{utils::TryEnum, AssistContext, AssistId, Assists};
use crate::{utils::TryEnum, AssistContext, AssistId, AssistKind, Assists};
// Assist: replace_if_let_with_match
//
@ -48,29 +48,35 @@ pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext)
};
let target = if_expr.syntax().text_range();
acc.add(AssistId("replace_if_let_with_match"), "Replace with match", target, move |edit| {
let match_expr = {
let then_arm = {
let then_block = then_block.reset_indent().indent(IndentLevel(1));
let then_expr = unwrap_trivial_block(then_block);
make::match_arm(vec![pat.clone()], then_expr)
acc.add(
AssistId("replace_if_let_with_match", AssistKind::RefactorRewrite),
"Replace with match",
target,
move |edit| {
let match_expr = {
let then_arm = {
let then_block = then_block.reset_indent().indent(IndentLevel(1));
let then_expr = unwrap_trivial_block(then_block);
make::match_arm(vec![pat.clone()], then_expr)
};
let else_arm = {
let pattern = ctx
.sema
.type_of_pat(&pat)
.and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty))
.map(|it| it.sad_pattern())
.unwrap_or_else(|| make::placeholder_pat().into());
let else_expr = unwrap_trivial_block(else_block);
make::match_arm(vec![pattern], else_expr)
};
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()))
};
let else_arm = {
let pattern = ctx
.sema
.type_of_pat(&pat)
.and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty))
.map(|it| it.sad_pattern())
.unwrap_or_else(|| make::placeholder_pat().into());
let else_expr = unwrap_trivial_block(else_block);
make::match_arm(vec![pattern], else_expr)
};
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()))
};
edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr);
})
edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr);
},
)
}
#[cfg(test)]

View file

@ -9,7 +9,7 @@ use ra_syntax::{
AstNode, T,
};
use crate::{utils::TryEnum, AssistContext, AssistId, Assists};
use crate::{utils::TryEnum, AssistContext, AssistId, AssistKind, Assists};
// Assist: replace_let_with_if_let
//
@ -44,24 +44,31 @@ 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 target = let_kw.text_range();
acc.add(AssistId("replace_let_with_if_let"), "Replace with if-let", target, |edit| {
let with_placeholder: ast::Pat = match happy_variant {
None => make::placeholder_pat().into(),
Some(var_name) => make::tuple_struct_pat(
make::path_unqualified(make::path_segment(make::name_ref(var_name))),
once(make::placeholder_pat().into()),
)
.into(),
};
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 stmt = make::expr_stmt(if_);
acc.add(
AssistId("replace_let_with_if_let", AssistKind::RefactorRewrite),
"Replace with if-let",
target,
|edit| {
let with_placeholder: ast::Pat = match happy_variant {
None => make::placeholder_pat().into(),
Some(var_name) => make::tuple_struct_pat(
make::path_unqualified(make::path_segment(make::name_ref(var_name))),
once(make::placeholder_pat().into()),
)
.into(),
};
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 stmt = make::expr_stmt(if_);
let placeholder = stmt.syntax().descendants().find_map(ast::PlaceholderPat::cast).unwrap();
let stmt = stmt.replace_descendant(placeholder.into(), original_pat);
let placeholder =
stmt.syntax().descendants().find_map(ast::PlaceholderPat::cast).unwrap();
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)]

View file

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

View file

@ -11,7 +11,7 @@ use ra_syntax::{
use crate::{
utils::{render_snippet, Cursor, TryEnum},
AssistContext, AssistId, Assists,
AssistContext, AssistId, AssistKind, Assists,
};
// Assist: replace_unwrap_with_match
@ -46,37 +46,43 @@ pub(crate) fn replace_unwrap_with_match(acc: &mut Assists, ctx: &AssistContext)
let ty = ctx.sema.type_of_expr(&caller)?;
let happy_variant = TryEnum::from_ty(&ctx.sema, &ty)?.happy_case();
let target = method_call.syntax().text_range();
acc.add(AssistId("replace_unwrap_with_match"), "Replace unwrap with match", target, |builder| {
let ok_path = make::path_unqualified(make::path_segment(make::name_ref(happy_variant)));
let it = make::bind_pat(make::name("a")).into();
let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into();
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 it = make::bind_pat(make::name("a")).into();
let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into();
let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a")));
let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path));
let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a")));
let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path));
let unreachable_call = make::expr_unreachable();
let err_arm = make::match_arm(iter::once(make::placeholder_pat().into()), unreachable_call);
let unreachable_call = make::expr_unreachable();
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_expr = make::expr_match(caller.clone(), match_arm_list)
.indent(IndentLevel::from_node(method_call.syntax()));
let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]);
let match_expr = make::expr_match(caller.clone(), match_arm_list)
.indent(IndentLevel::from_node(method_call.syntax()));
let range = method_call.syntax().text_range();
match ctx.config.snippet_cap {
Some(cap) => {
let err_arm = match_expr
.syntax()
.descendants()
.filter_map(ast::MatchArm::cast)
.last()
.unwrap();
let snippet =
render_snippet(cap, match_expr.syntax(), Cursor::Before(err_arm.syntax()));
builder.replace_snippet(cap, range, snippet)
let range = method_call.syntax().text_range();
match ctx.config.snippet_cap {
Some(cap) => {
let err_arm = match_expr
.syntax()
.descendants()
.filter_map(ast::MatchArm::cast)
.last()
.unwrap();
let snippet =
render_snippet(cap, match_expr.syntax(), Cursor::Before(err_arm.syntax()));
builder.replace_snippet(cap, range, snippet)
}
None => builder.replace(range, match_expr.to_string()),
}
None => builder.replace(range, match_expr.to_string()),
}
})
},
)
}
#[cfg(test)]

View file

@ -2,7 +2,7 @@ use std::iter::successors;
use ra_syntax::{ast, AstNode, T};
use crate::{AssistContext, AssistId, Assists};
use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: split_import
//
@ -28,7 +28,7 @@ pub(crate) fn split_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
}
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);
})
}

View file

@ -7,7 +7,7 @@ use ra_syntax::{
AstNode, TextRange, T,
};
use crate::{AssistContext, AssistId, Assists};
use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: unwrap_block
//
@ -27,7 +27,7 @@ use crate::{AssistContext, AssistId, Assists};
// }
// ```
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 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;
#[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
/// directly.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AssistId(pub &'static str);
pub struct AssistId(pub &'static str, pub AssistKind);
#[derive(Clone, Debug)]
pub struct GroupLabel(pub String);
@ -102,13 +132,8 @@ mod handlers {
pub(crate) type Handler = fn(&mut Assists, &AssistContext) -> Option<()>;
mod add_custom_impl;
mod add_derive;
mod add_explicit_type;
mod add_from_impl_for_enum;
mod add_function;
mod add_impl;
mod add_missing_impl_members;
mod add_new;
mod add_turbo_fish;
mod apply_demorgan;
mod auto_import;
@ -122,6 +147,11 @@ mod handlers {
mod flip_binexpr;
mod flip_comma;
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 introduce_named_lifetime;
mod invert_if;
@ -144,12 +174,7 @@ mod handlers {
&[
// These are alphabetic for the foolish consistency
add_custom_impl::add_custom_impl,
add_derive::add_derive,
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,
apply_demorgan::apply_demorgan,
auto_import::auto_import,
@ -163,6 +188,11 @@ mod handlers {
flip_binexpr::flip_binexpr,
flip_comma::flip_comma,
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,
introduce_named_lifetime::introduce_named_lifetime,
invert_if::invert_if,

View file

@ -6,7 +6,7 @@ use ra_ide_db::RootDatabase;
use ra_syntax::TextRange;
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;
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, "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]
fn doctest_add_explicit_type() {
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]
fn doctest_add_hash() {
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]
fn doctest_add_impl_default_members() {
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]
fn doctest_add_turbo_fish() {
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]
fn doctest_inline_local_variable() {
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(
sema: &Semantics<RootDatabase>,
impl_def: &ast::ImplDef,
impl_def: &ast::Impl,
) -> Vec<hir::AssocItem> {
// Names must be unique between constants and functions. However, type aliases
// may share the same name as a function or constant.
let mut impl_fns_consts = 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() {
match item {
ast::AssocItem::FnDef(f) => {
ast::AssocItem::Fn(f) => {
if let Some(n) = f.name() {
impl_fns_consts.insert(n.syntax().to_string());
}
}
ast::AssocItem::TypeAliasDef(t) => {
ast::AssocItem::TypeAlias(t) => {
if let Some(n) = t.name() {
impl_type.insert(n.syntax().to_string());
}
}
ast::AssocItem::ConstDef(c) => {
ast::AssocItem::Const(c) => {
if let Some(n) = c.name() {
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(
sema: &Semantics<RootDatabase>,
impl_def: &ast::ImplDef,
impl_def: &ast::Impl,
) -> Option<hir::Trait> {
let ast_path = impl_def
.target_trait()

View file

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

View file

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

View file

@ -5,7 +5,6 @@
use std::slice::Iter as SliceIter;
use ra_syntax::SmolStr;
use tt::{Leaf, Subtree, TokenTree};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CfgExpr {
@ -18,6 +17,9 @@ pub enum 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.
pub fn fold(&self, query: &dyn Fn(&SmolStr, Option<&SmolStr>) -> bool) -> Option<bool> {
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> {
let name = match it.next() {
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),
};
// Peek
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) {
Some(TokenTree::Leaf(Leaf::Literal(literal))) => {
Some(tt::TokenTree::Leaf(tt::Leaf::Literal(literal))) => {
it.next();
it.next();
// FIXME: escape? raw string?
@ -61,7 +59,7 @@ fn next_cfg_expr(it: &mut SliceIter<tt::TokenTree>) -> Option<CfgExpr> {
_ => return Some(CfgExpr::Invalid),
}
}
Some(TokenTree::Subtree(subtree)) => {
Some(tt::TokenTree::Subtree(subtree)) => {
it.next();
let mut sub_it = subtree.token_trees.iter();
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
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 == ',' {
it.next();
}
@ -99,7 +97,8 @@ mod tests {
fn assert_parse_result(input: &str, expected: CfgExpr) {
let (tt, _) = get_token_tree_generated(input);
assert_eq!(parse_cfg(&tt), expected);
let cfg = CfgExpr::parse(&tt);
assert_eq!(cfg, expected);
}
#[test]

View file

@ -5,7 +5,7 @@ mod cfg_expr;
use ra_syntax::SmolStr;
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.
/// 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) {
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) {
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"
version = "0.1.0"
authors = ["rust-analyzer developers"]
license = "MIT OR Apache-2.0"
[lib]
doctest = false
[dependencies]
salsa = "0.14.1"
relative-path = "1.0.0"
salsa = "0.15.0"
rustc-hash = "1.1.0"
ra_syntax = { path = "../ra_syntax" }

View file

@ -149,15 +149,17 @@ fn with_files(
let crate_id = crate_graph.add_crate_root(
file_id,
meta.edition,
Some(CrateName::new(&krate).unwrap()),
Some(krate.clone()),
meta.cfg,
meta.env,
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());
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" {
assert!(default_crate_root.is_none());
@ -220,7 +222,7 @@ impl From<Fixture> for FileMeta {
.edition
.as_ref()
.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 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_syntax::SmolStr;
@ -67,7 +67,7 @@ pub struct CrateGraph {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CrateId(pub u32);
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CrateName(SmolStr);
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)]
pub struct ProcMacroId(pub u32);
@ -117,7 +124,7 @@ pub struct CrateData {
/// The name to display to the end user.
/// 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.
pub display_name: Option<CrateName>,
pub display_name: Option<String>,
pub cfg_options: CfgOptions,
pub env: Env,
pub dependencies: Vec<Dependency>,
@ -138,7 +145,7 @@ pub struct Env {
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Dependency {
pub crate_id: CrateId,
pub name: SmolStr,
pub name: CrateName,
}
impl CrateGraph {
@ -146,7 +153,7 @@ impl CrateGraph {
&mut self,
file_id: FileId,
edition: Edition,
display_name: Option<CrateName>,
display_name: Option<String>,
cfg_options: CfgOptions,
env: Env,
proc_macro: Vec<(SmolStr, Arc<dyn ra_tt::TokenExpander>)>,
@ -178,7 +185,7 @@ impl CrateGraph {
if self.dfs_find(from, to, &mut FxHashSet::default()) {
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(())
}
@ -190,6 +197,23 @@ impl CrateGraph {
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
pub fn crate_id_for_crate_root(&self, file_id: FileId) -> Option<CrateId> {
let (&crate_id, _) =
@ -247,7 +271,7 @@ impl CrateId {
}
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 })
}
}
@ -274,18 +298,9 @@ impl fmt::Display for Edition {
}
}
impl<'a, T> From<T> for Env
where
T: Iterator<Item = (&'a String, &'a String)>,
{
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
impl FromIterator<(String, String)> for Env {
fn from_iter<T: IntoIterator<Item = (String, String)>>(iter: T) -> Self {
Env { entries: FromIterator::from_iter(iter) }
}
}
@ -429,7 +444,10 @@ mod tests {
.is_ok());
assert_eq!(
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,
},
};
pub use relative_path::{RelativePath, RelativePathBuf};
pub use salsa;
pub use vfs::{file_set::FileSet, AbsPathBuf, VfsPath};
pub use vfs::{file_set::FileSet, VfsPath};
#[macro_export]
macro_rules! impl_intern_key {
@ -80,7 +79,7 @@ pub struct FilePosition {
pub offset: TextSize,
}
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct FileRange {
pub file_id: FileId,
pub range: TextRange,
@ -93,9 +92,9 @@ pub trait FileLoader {
fn file_text(&self, file_id: FileId) -> Arc<String>;
/// 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,
/// so the input is guaranteed to be utf-8 string. We might introduce
/// `struct StrPath(str)` for clarity some day, but it's a bit messy, so we
/// get by with a `&str` for the time being.
/// so the input is guaranteed to be utf-8 string. One might be tempted to
/// introduce some kind of "utf-8 path with / separators", but that's a bad idea. Behold
/// `#[path = "C://no/way"]`
fn resolve_path(&self, anchor: FileId, path: &str) -> Option<FileId>;
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 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 text = db.file_text(file_id);
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(
db: &(impl SourceDatabaseExt + SourceDatabase),
id: SourceRootId,
) -> Arc<FxHashSet<CrateId>> {
fn source_root_crates(db: &dyn SourceDatabaseExt, id: SourceRootId) -> Arc<FxHashSet<CrateId>> {
let graph = db.crate_graph();
let res = graph
.iter()

View file

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

View file

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

View file

@ -1,5 +1,5 @@
//! FIXME: write short doc here
use std::sync::Arc;
use std::{iter, sync::Arc};
use arrayvec::ArrayVec;
use either::Either;
@ -12,6 +12,7 @@ use hir_def::{
import_map,
per_ns::PerNs,
resolver::{HasResolver, Resolver},
src::HasSource as _,
type_ref::{Mutability, TypeRef},
AdtId, AssocContainerId, ConstId, DefWithBodyId, EnumId, FunctionId, GenericDefId, HasModule,
ImplId, LocalEnumVariantId, LocalFieldId, LocalModuleId, Lookup, ModuleId, StaticId, StructId,
@ -25,21 +26,22 @@ use hir_expand::{
use hir_ty::{
autoderef,
display::{HirDisplayError, HirFormatter},
expr::ExprValidator,
method_resolution,
unsafe_validation::UnsafeValidator,
ApplicationTy, Canonical, GenericPredicate, InEnvironment, Substs, TraitEnvironment, Ty,
TyDefId, TypeCtor,
method_resolution, ApplicationTy, CallableDefId, Canonical, FnSig, 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_syntax::ast::{self, AttrsOwner, NameOwner};
use ra_syntax::{
ast::{self, AttrsOwner, NameOwner},
AstNode,
};
use rustc_hash::FxHashSet;
use stdx::impl_from;
use crate::{
db::{DefDatabase, HirDatabase},
has_source::HasSource,
CallableDef, HirDisplay, InFile, Name,
HirDisplay, InFile, Name,
};
/// 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
}
pub fn display_name(self, db: &dyn HirDatabase) -> Option<CrateName> {
db.crate_graph()[self.id].display_name.as_ref().cloned()
pub fn display_name(self, db: &dyn HirDatabase) -> Option<String> {
db.crate_graph()[self.id].display_name.clone()
}
pub fn query_external_importables(
@ -139,8 +141,8 @@ pub enum ModuleDef {
TypeAlias(TypeAlias),
BuiltinType(BuiltinType),
}
impl_froms!(
ModuleDef: Module,
impl_from!(
Module,
Function,
Adt(Struct, Enum, Union),
EnumVariant,
@ -149,6 +151,7 @@ impl_froms!(
Trait,
TypeAlias,
BuiltinType
for ModuleDef
);
impl ModuleDef {
@ -376,8 +379,8 @@ pub struct Field {
#[derive(Debug, PartialEq, Eq)]
pub enum FieldSource {
Named(ast::RecordFieldDef),
Pos(ast::TupleFieldDef),
Named(ast::RecordField),
Pos(ast::TupleField),
}
impl Field {
@ -556,7 +559,7 @@ pub enum Adt {
Union(Union),
Enum(Enum),
}
impl_froms!(Adt: Struct, Union, Enum);
impl_from!(Struct, Union, Enum for Adt);
impl Adt {
pub fn has_non_default_type_params(self, db: &dyn HirDatabase) -> bool {
@ -599,7 +602,7 @@ pub enum VariantDef {
Union(Union),
EnumVariant(EnumVariant),
}
impl_froms!(VariantDef: Struct, Union, EnumVariant);
impl_from!(Struct, Union, EnumVariant for VariantDef);
impl VariantDef {
pub fn fields(self, db: &dyn HirDatabase) -> Vec<Field> {
@ -642,8 +645,7 @@ pub enum DefWithBody {
Static(Static),
Const(Const),
}
impl_froms!(DefWithBody: Function, Const, Static);
impl_from!(Function, Const, Static for DefWithBody);
impl DefWithBody {
pub fn module(self, db: &dyn HirDatabase) -> Module {
@ -694,13 +696,7 @@ impl Function {
}
pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) {
let _p = profile("Function::diagnostics");
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);
hir_ty::diagnostics::validate_body(db, self.id.into(), sink)
}
}
@ -945,14 +941,15 @@ pub enum GenericDef {
// consts can have type parameters from their parents (i.e. associated consts of traits)
Const(Const),
}
impl_froms!(
GenericDef: Function,
impl_from!(
Function,
Adt(Struct, Enum, Union),
Trait,
TypeAlias,
ImplDef,
EnumVariant,
Const
for GenericDef
);
impl GenericDef {
@ -973,6 +970,16 @@ pub struct 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?
pub fn name(self, db: &dyn HirDatabase) -> Option<Name> {
let body = db.body(self.parent.into());
@ -1071,12 +1078,14 @@ pub struct ImplDef {
impl ImplDef {
pub fn all_in_crate(db: &dyn HirDatabase, krate: Crate) -> Vec<ImplDef> {
let impls = db.impls_in_crate(krate.id);
impls.all_impls().map(Self::from).collect()
let inherent = db.inherent_impls_in_crate(krate.id);
let trait_ = db.trait_impls_in_crate(krate.id);
inherent.all_impls().chain(trait_.all_impls()).map(Self::from).collect()
}
pub fn for_trait(db: &dyn HirDatabase, krate: Crate, trait_: Trait) -> Vec<ImplDef> {
let impls = db.impls_in_crate(krate.id);
impls.lookup_impl_defs_for_trait(trait_.id).map(Self::from).collect()
let impls = db.trait_impls_in_crate(krate.id);
impls.for_trait(trait_.id).map(Self::from).collect()
}
pub fn target_trait(self, db: &dyn HirDatabase) -> Option<TypeRef> {
@ -1178,6 +1187,12 @@ impl Type {
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 {
matches!(self.ty.value, Ty::Apply(ApplicationTy { ctor: TypeCtor::Bool, .. }))
}
@ -1205,7 +1220,7 @@ impl Type {
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(
&canonical_ty,
db,
@ -1229,15 +1244,20 @@ impl Type {
self.ty.environment.clone(),
hir_ty::Obligation::Trait(trait_ref),
),
num_vars: 0,
kinds: Arc::new([]),
};
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) -> Option<CallableDef> {
Some(self.ty.value.as_callable()?.0)
pub fn as_callable(&self, db: &dyn HirDatabase) -> Option<Callable> {
let def = match self.ty.value {
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 {
@ -1304,7 +1324,7 @@ impl Type {
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
// 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 ty = InEnvironment { value: canonical, environment };
autoderef(db, Some(self.krate), ty)
@ -1321,10 +1341,10 @@ impl Type {
mut callback: impl FnMut(AssocItem) -> Option<T>,
) -> Option<T> {
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 &item in db.impl_data(impl_def).items.iter() {
for impl_def in impls.for_self_ty(&self.ty.value) {
for &item in db.impl_data(*impl_def).items.iter() {
if let Some(result) = callback(item.into()) {
return Some(result);
}
@ -1345,7 +1365,7 @@ impl Type {
// There should be no inference vars in types passed here
// FIXME check that?
// 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 krate = krate.id;
@ -1376,7 +1396,7 @@ impl Type {
// There should be no inference vars in types passed here
// FIXME check that?
// 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 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
#[derive(Debug)]
pub enum ScopeDef {
@ -1581,8 +1669,8 @@ pub enum AttrDef {
MacroDef(MacroDef),
}
impl_froms!(
AttrDef: Module,
impl_from!(
Module,
Field,
Adt(Struct, Enum, Union),
EnumVariant,
@ -1592,6 +1680,7 @@ impl_froms!(
Trait,
TypeAlias,
MacroDef
for AttrDef
);
pub trait HasAttrs {

View file

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

View file

@ -1,4 +1,8 @@
//! FIXME: write short doc here
pub use hir_def::diagnostics::UnresolvedModule;
pub use hir_expand::diagnostics::{AstDiagnostic, Diagnostic, DiagnosticSink};
pub use hir_ty::diagnostics::{MissingFields, MissingMatchArms, MissingOkInTailExpr, NoSuchField};
pub use hir_expand::diagnostics::{
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 {
type Ast = ast::StructDef;
fn source(self, db: &dyn HirDatabase) -> InFile<ast::StructDef> {
type Ast = ast::Struct;
fn source(self, db: &dyn HirDatabase) -> InFile<ast::Struct> {
self.id.lookup(db.upcast()).source(db.upcast())
}
}
impl HasSource for Union {
type Ast = ast::UnionDef;
fn source(self, db: &dyn HirDatabase) -> InFile<ast::UnionDef> {
type Ast = ast::Union;
fn source(self, db: &dyn HirDatabase) -> InFile<ast::Union> {
self.id.lookup(db.upcast()).source(db.upcast())
}
}
impl HasSource for Enum {
type Ast = ast::EnumDef;
fn source(self, db: &dyn HirDatabase) -> InFile<ast::EnumDef> {
type Ast = ast::Enum;
fn source(self, db: &dyn HirDatabase) -> InFile<ast::Enum> {
self.id.lookup(db.upcast()).source(db.upcast())
}
}
impl HasSource for EnumVariant {
type Ast = ast::EnumVariant;
fn source(self, db: &dyn HirDatabase) -> InFile<ast::EnumVariant> {
type Ast = ast::Variant;
fn source(self, db: &dyn HirDatabase) -> InFile<ast::Variant> {
self.parent.id.child_source(db.upcast()).map(|map| map[self.id].clone())
}
}
impl HasSource for Function {
type Ast = ast::FnDef;
fn source(self, db: &dyn HirDatabase) -> InFile<ast::FnDef> {
type Ast = ast::Fn;
fn source(self, db: &dyn HirDatabase) -> InFile<ast::Fn> {
self.id.lookup(db.upcast()).source(db.upcast())
}
}
impl HasSource for Const {
type Ast = ast::ConstDef;
fn source(self, db: &dyn HirDatabase) -> InFile<ast::ConstDef> {
type Ast = ast::Const;
fn source(self, db: &dyn HirDatabase) -> InFile<ast::Const> {
self.id.lookup(db.upcast()).source(db.upcast())
}
}
impl HasSource for Static {
type Ast = ast::StaticDef;
fn source(self, db: &dyn HirDatabase) -> InFile<ast::StaticDef> {
type Ast = ast::Static;
fn source(self, db: &dyn HirDatabase) -> InFile<ast::Static> {
self.id.lookup(db.upcast()).source(db.upcast())
}
}
impl HasSource for Trait {
type Ast = ast::TraitDef;
fn source(self, db: &dyn HirDatabase) -> InFile<ast::TraitDef> {
type Ast = ast::Trait;
fn source(self, db: &dyn HirDatabase) -> InFile<ast::Trait> {
self.id.lookup(db.upcast()).source(db.upcast())
}
}
impl HasSource for TypeAlias {
type Ast = ast::TypeAliasDef;
fn source(self, db: &dyn HirDatabase) -> InFile<ast::TypeAliasDef> {
type Ast = ast::TypeAlias;
fn source(self, db: &dyn HirDatabase) -> InFile<ast::TypeAlias> {
self.id.lookup(db.upcast()).source(db.upcast())
}
}
@ -120,14 +120,14 @@ impl HasSource for MacroDef {
}
}
impl HasSource for ImplDef {
type Ast = ast::ImplDef;
fn source(self, db: &dyn HirDatabase) -> InFile<ast::ImplDef> {
type Ast = ast::Impl;
fn source(self, db: &dyn HirDatabase) -> InFile<ast::Impl> {
self.id.lookup(db.upcast()).source(db.upcast())
}
}
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> {
let child_source = self.id.parent.child_source(db.upcast());
child_source.map(|it| it[self.id.local_id].clone())

View file

@ -19,25 +19,6 @@
#![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;
pub mod db;
mod source_analyzer;
@ -51,10 +32,10 @@ mod has_source;
pub use crate::{
code_model::{
Adt, AsAssocItem, AssocItem, AssocItemContainer, AttrDef, Const, Crate, CrateDependency,
DefWithBody, Docs, Enum, EnumVariant, Field, FieldSource, Function, GenericDef, HasAttrs,
HasVisibility, ImplDef, Local, MacroDef, Module, ModuleDef, ScopeDef, Static, Struct,
Trait, Type, TypeAlias, TypeParam, Union, VariantDef, Visibility,
Adt, AsAssocItem, AssocItem, AssocItemContainer, AttrDef, Callable, CallableKind, Const,
Crate, CrateDependency, DefWithBody, Docs, Enum, EnumVariant, Field, FieldSource, Function,
GenericDef, HasAttrs, HasVisibility, ImplDef, Local, MacroDef, Module, ModuleDef, ScopeDef,
Static, Struct, Trait, Type, TypeAlias, TypeParam, Union, VariantDef, Visibility,
},
has_source::HasSource,
semantics::{original_range, PathResolution, Semantics, SemanticsScope},
@ -74,6 +55,7 @@ pub use hir_def::{
pub use hir_expand::{
hygiene::Hygiene,
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::{
resolver::{self, HasResolver, Resolver},
AsMacroCall, TraitId, VariantId,
AsMacroCall, FunctionId, TraitId, VariantId,
};
use hir_expand::{diagnostics::AstDiagnostic, hygiene::Hygiene, ExpansionInfo};
use hir_ty::associated_type_shorthand_candidates;
@ -24,8 +24,8 @@ use crate::{
diagnostics::Diagnostic,
semantics::source_to_def::{ChildContainer, SourceToDefCache, SourceToDefCtx},
source_analyzer::{resolve_hir_path, resolve_hir_path_qualifier, SourceAnalyzer},
AssocItem, Field, Function, HirFileId, ImplDef, InFile, Local, MacroDef, Module, ModuleDef,
Name, Origin, Path, ScopeDef, Trait, Type, TypeAlias, TypeParam,
AssocItem, Callable, Field, Function, HirFileId, ImplDef, InFile, Local, MacroDef, Module,
ModuleDef, Name, Origin, Path, ScopeDef, Trait, Type, TypeAlias, TypeParam, VariantDef,
};
use resolver::TypeNs;
@ -83,7 +83,13 @@ impl PathResolution {
/// Primary API to get semantic information, like types, from syntax trees.
pub struct Semantics<'db, DB> {
pub db: &'db DB,
imp: SemanticsImpl<'db>,
}
pub struct SemanticsImpl<'db> {
pub db: &'db dyn HirDatabase,
s2d_cache: RefCell<SourceToDefCache>,
expansion_info_cache: RefCell<FxHashMap<HirFileId, Option<ExpansionInfo>>>,
cache: RefCell<FxHashMap<SyntaxNode, HirFileId>>,
}
@ -95,29 +101,23 @@ impl<DB> fmt::Debug for Semantics<'_, DB> {
impl<'db, DB: HirDatabase> Semantics<'db, 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 {
let tree = self.db.parse(file_id).tree();
self.cache(tree.syntax().clone(), file_id.into());
tree
self.imp.parse(file_id)
}
pub fn ast<T: AstDiagnostic + Diagnostic>(&self, d: &T) -> <T as AstDiagnostic>::AST {
let file_id = d.source().file_id;
let root = self.db.parse_or_expand(file_id).unwrap();
self.cache(root, file_id);
d.ast(self.db)
self.imp.cache(root, file_id);
d.ast(self.db.upcast())
}
pub 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)
self.imp.expand(macro_call)
}
pub fn expand_hypothetical(
@ -126,37 +126,11 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
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, 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)
self.imp.expand_hypothetical(actual_macro_call, hypothetical_args, token_to_map)
}
pub fn descend_into_macros(&self, token: SyntaxToken) -> SyntaxToken {
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| {
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
self.imp.descend_into_macros(token)
}
pub fn descend_node_at_offset<N: ast::AstNode>(
@ -164,27 +138,19 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
node: &SyntaxNode,
offset: TextSize,
) -> Option<N> {
// Handle macro token cases
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))
self.imp.descend_node_at_offset(node, offset).find_map(N::cast)
}
pub fn original_range(&self, node: &SyntaxNode) -> FileRange {
let node = self.find_file(node.clone());
original_range(self.db, node.as_ref())
self.imp.original_range(node)
}
pub 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))
self.imp.diagnostics_range(diagnostics)
}
pub fn ancestors_with_macros(&self, node: SyntaxNode) -> impl Iterator<Item = SyntaxNode> + '_ {
let node = self.find_file(node);
node.ancestors_with_macros(self.db).map(|it| it.value)
self.imp.ancestors_with_macros(node)
}
pub fn ancestors_at_offset_with_macros(
@ -192,9 +158,7 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
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())
self.imp.ancestors_at_offset_with_macros(node, offset)
}
/// 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,
offset: TextSize,
) -> 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*,
@ -217,100 +181,317 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
if let Some(it) = find_node_at_offset(&node, offset) {
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> {
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> {
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> {
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> {
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>)> {
self.analyze(field.syntax()).resolve_record_field(self.db, field)
pub fn resolve_record_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> {
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> {
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)
self.imp.resolve_macro_call(macro_call)
}
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> {
self.analyze(record_lit.syntax()).resolve_variant(self.db, record_lit)
pub fn resolve_variant(&self, record_lit: ast::RecordExpr) -> Option<VariantDef> {
self.imp.resolve_variant(record_lit).map(VariantDef::from)
}
pub 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()))
self.imp.lower_path(path)
}
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?
// 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())
.record_literal_missing_fields(self.db, literal)
.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())
.record_pattern_missing_fields(self.db, pattern)
.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 {
let mut cache = self.s2d_cache.borrow_mut();
let mut ctx = SourceToDefCtx { db: self.db, cache: &mut *cache };
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)
}
pub fn scope(&self, node: &SyntaxNode) -> SemanticsScope<'db, DB> {
fn scope(&self, node: &SyntaxNode) -> SemanticsScope<'db> {
let node = self.find_file(node.clone());
let resolver = self.analyze2(node.as_ref(), None).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 resolver = self.analyze2(node.as_ref(), Some(offset)).resolver;
SemanticsScope { db: self.db, resolver }
}
pub fn scope_for_def(&self, def: Trait) -> SemanticsScope<'db, DB> {
let resolver = def.id.resolver(self.db);
fn scope_for_def(&self, def: Trait) -> SemanticsScope<'db> {
let resolver = def.id.resolver(self.db.upcast());
SemanticsScope { db: self.db, resolver }
}
@ -331,12 +512,13 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
ChildContainer::DefWithBodyId(def) => {
return SourceAnalyzer::new_for_body(self.db, def, src, offset)
}
ChildContainer::TraitId(it) => it.resolver(self.db),
ChildContainer::ImplId(it) => it.resolver(self.db),
ChildContainer::ModuleId(it) => it.resolver(self.db),
ChildContainer::EnumId(it) => it.resolver(self.db),
ChildContainer::VariantId(it) => it.resolver(self.db),
ChildContainer::GenericDefId(it) => it.resolver(self.db),
ChildContainer::TraitId(it) => it.resolver(self.db.upcast()),
ChildContainer::ImplId(it) => it.resolver(self.db.upcast()),
ChildContainer::ModuleId(it) => it.resolver(self.db.upcast()),
ChildContainer::EnumId(it) => it.resolver(self.db.upcast()),
ChildContainer::VariantId(it) => it.resolver(self.db.upcast()),
ChildContainer::TypeAliasId(it) => it.resolver(self.db.upcast()),
ChildContainer::GenericDefId(it) => it.resolver(self.db.upcast()),
};
SourceAnalyzer::new_for_resolver(resolver, src)
}
@ -348,7 +530,7 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
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());
}
@ -382,14 +564,14 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
pub trait ToDef: AstNode + Clone {
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 {
($(($def:path, $ast:path, $meth:ident)),* ,) => {$(
impl ToDef for $ast {
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)
}
}
@ -398,18 +580,18 @@ macro_rules! to_def_impls {
to_def_impls![
(crate::Module, ast::Module, module_to_def),
(crate::Struct, ast::StructDef, struct_to_def),
(crate::Enum, ast::EnumDef, enum_to_def),
(crate::Union, ast::UnionDef, union_to_def),
(crate::Trait, ast::TraitDef, trait_to_def),
(crate::ImplDef, ast::ImplDef, impl_to_def),
(crate::TypeAlias, ast::TypeAliasDef, type_alias_to_def),
(crate::Const, ast::ConstDef, const_to_def),
(crate::Static, ast::StaticDef, static_to_def),
(crate::Function, ast::FnDef, fn_to_def),
(crate::Field, ast::RecordFieldDef, record_field_to_def),
(crate::Field, ast::TupleFieldDef, tuple_field_to_def),
(crate::EnumVariant, ast::EnumVariant, enum_variant_to_def),
(crate::Struct, ast::Struct, struct_to_def),
(crate::Enum, ast::Enum, enum_to_def),
(crate::Union, ast::Union, union_to_def),
(crate::Trait, ast::Trait, trait_to_def),
(crate::ImplDef, ast::Impl, impl_to_def),
(crate::TypeAlias, ast::TypeAlias, type_alias_to_def),
(crate::Const, ast::Const, const_to_def),
(crate::Static, ast::Static, static_to_def),
(crate::Function, ast::Fn, fn_to_def),
(crate::Field, ast::RecordField, record_field_to_def),
(crate::Field, ast::TupleField, tuple_field_to_def),
(crate::EnumVariant, ast::Variant, enum_variant_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::Local, ast::BindPat, bind_pat_to_def),
@ -419,12 +601,13 @@ fn find_root(node: &SyntaxNode) -> SyntaxNode {
node.ancestors().last().unwrap()
}
pub struct SemanticsScope<'a, DB> {
pub db: &'a DB,
#[derive(Debug)]
pub struct SemanticsScope<'a> {
pub db: &'a dyn HirDatabase,
resolver: Resolver,
}
impl<'a, DB: HirDatabase> SemanticsScope<'a, DB> {
impl<'a> SemanticsScope<'a> {
pub fn module(&self) -> Option<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?
pub fn traits_in_scope(&self) -> FxHashSet<TraitId> {
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)) {
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 {
resolver::ScopeDef::PerNs(it) => {
let items = ScopeDef::all_items(it);

View file

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

View file

@ -14,11 +14,11 @@ use hir_def::{
},
expr::{ExprId, Pat, PatId},
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_ty::{
expr::{record_literal_missing_fields, record_pattern_missing_fields},
diagnostics::{record_literal_missing_fields, record_pattern_missing_fields},
InferenceResult, Substs, Ty,
};
use ra_syntax::{
@ -115,7 +115,7 @@ impl SourceAnalyzer {
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 ty = self.infer.as_ref()?[expr_id].clone();
Type::new_with_resolver(db, &self.resolver, ty)
@ -127,13 +127,24 @@ impl SourceAnalyzer {
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(
&self,
db: &dyn HirDatabase,
call: &ast::MethodCallExpr,
) -> Option<Function> {
) -> Option<FunctionId> {
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(
@ -148,7 +159,7 @@ impl SourceAnalyzer {
pub(crate) fn resolve_record_field(
&self,
db: &dyn HirDatabase,
field: &ast::RecordField,
field: &ast::RecordExprField,
) -> Option<(Field, Option<Local>)> {
let expr = field.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())?;
if let Some(VariantId::EnumVariantId(variant)) =
self.infer.as_ref()?.variant_resolution_for_expr(expr_id)
@ -273,7 +284,7 @@ impl SourceAnalyzer {
pub(crate) fn record_literal_missing_fields(
&self,
db: &dyn HirDatabase,
literal: &ast::RecordLit,
literal: &ast::RecordExpr,
) -> Option<Vec<(Field, Type)>> {
let krate = self.resolver.krate()?;
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| {
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(
&self,
db: &dyn HirDatabase,
record_lit: ast::RecordLit,
record_lit: ast::RecordExpr,
) -> Option<VariantId> {
let infer = self.infer.as_ref()?;
let expr_id = self.expr_id(db, &record_lit.into())?;
@ -394,8 +405,7 @@ fn scope_for_offset(
)
})
.map(|(expr_range, scope)| {
adjust(db, scopes, source_map, expr_range, offset.file_id, offset.value)
.unwrap_or(*scope)
adjust(db, scopes, source_map, expr_range, offset).unwrap_or(*scope)
})
}
@ -406,8 +416,7 @@ fn adjust(
scopes: &ExprScopes,
source_map: &BodySourceMap,
expr_range: TextRange,
file_id: HirFileId,
offset: TextSize,
offset: InFile<TextSize>,
) -> Option<ScopeId> {
let child_scopes = scopes
.scope_by_expr()
@ -415,7 +424,7 @@ fn adjust(
.filter_map(|(id, scope)| {
let source = source_map.expr_syntax(*id).ok()?;
// FIXME: correctly handle macro expansion
if source.file_id != file_id {
if source.file_id != offset.file_id {
return None;
}
let root = source.file_syntax(db.upcast());
@ -423,7 +432,7 @@ fn adjust(
Some((node.syntax().text_range(), scope))
})
.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

View file

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

View file

@ -8,7 +8,7 @@ use hir_expand::{
InFile,
};
use ra_arena::{map::ArenaMap, Arena};
use ra_syntax::ast::{self, NameOwner, TypeAscriptionOwner, VisibilityOwner};
use ra_syntax::ast::{self, NameOwner, VisibilityOwner};
use crate::{
body::{CfgExpander, LowerCtx},
@ -112,7 +112,7 @@ impl EnumData {
impl HasChildSource for EnumId {
type ChildId = LocalEnumVariantId;
type Value = ast::EnumVariant;
type Value = ast::Variant;
fn child_source(&self, db: &dyn DefDatabase) -> InFile<ArenaMap<Self::ChildId, Self::Value>> {
let src = self.lookup(db).source(db);
let mut trace = Trace::new_for_map();
@ -123,8 +123,8 @@ impl HasChildSource for EnumId {
fn lower_enum(
db: &dyn DefDatabase,
trace: &mut Trace<EnumVariantData, ast::EnumVariant>,
ast: &InFile<ast::EnumDef>,
trace: &mut Trace<EnumVariantData, ast::Variant>,
ast: &InFile<ast::Enum>,
module_id: ModuleId,
) {
let expander = CfgExpander::new(db, ast.file_id, module_id.krate);
@ -179,7 +179,7 @@ impl VariantData {
impl HasChildSource for VariantId {
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>> {
let (src, module_id) = match self {
@ -194,7 +194,7 @@ impl HasChildSource for VariantId {
}
VariantId::UnionId(it) => (
it.lookup(db).source(db).map(|it| {
it.record_field_def_list()
it.record_field_list()
.map(ast::StructKind::Record)
.unwrap_or(ast::StructKind::Unit)
}),
@ -218,7 +218,7 @@ pub enum StructKind {
fn lower_struct(
db: &dyn DefDatabase,
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>,
) -> StructKind {
let ctx = LowerCtx::new(db, ast.file_id);
@ -234,7 +234,7 @@ fn lower_struct(
|| Either::Left(fd.clone()),
|| FieldData {
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())),
},
);
@ -251,7 +251,7 @@ fn lower_struct(
|| Either::Right(fd.clone()),
|| FieldData {
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())),
},
);

View file

@ -5,7 +5,7 @@ use std::{ops, sync::Arc};
use either::Either;
use hir_expand::{hygiene::Hygiene, AstId, InFile};
use mbe::ast_to_token_tree;
use ra_cfg::CfgOptions;
use ra_cfg::{CfgExpr, CfgOptions};
use ra_syntax::{
ast::{self, AstNode, AttrsOwner},
SmolStr,
@ -125,9 +125,12 @@ impl Attrs {
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 :-)
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 {
fn from_src(ast: ast::Attr, hygiene: &Hygiene) -> Option<Attr> {
let path = ModPath::from_src(ast.path()?, hygiene)?;
let input = match ast.input() {
None => None,
Some(ast::AttrInput::Literal(lit)) => {
// FIXME: escape? raw string?
let value = lit.syntax().first_token()?.text().trim_matches('"').into();
Some(AttrInput::Literal(value))
}
Some(ast::AttrInput::TokenTree(tt)) => {
Some(AttrInput::TokenTree(ast_to_token_tree(&tt)?.0))
}
let input = if let Some(lit) = ast.literal() {
// FIXME: escape? raw string?
let value = lit.syntax().first_token()?.text().trim_matches('"').into();
Some(AttrInput::Literal(value))
} else if let Some(tt) = ast.token_tree() {
Some(AttrInput::TokenTree(ast_to_token_tree(&tt)?.0))
} else {
None
};
Some(Attr { path, input })
}
}

View file

@ -14,6 +14,7 @@ use ra_db::CrateId;
use ra_prof::profile;
use ra_syntax::{ast, AstNode, AstPtr};
use rustc_hash::FxHashMap;
use test_utils::mark;
pub(crate) use lower::LowerCtx;
@ -42,9 +43,15 @@ pub(crate) struct Expander {
current_file_id: HirFileId,
ast_id_map: Arc<AstIdMap>,
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 {
pub(crate) fn new(
db: &dyn DefDatabase,
@ -81,7 +88,7 @@ impl Expander {
current_file_id,
ast_id_map,
module,
recursive_limit: 0,
recursion_limit: 0,
}
}
@ -91,7 +98,9 @@ impl Expander {
local_scope: Option<&ItemScope>,
macro_call: ast::MacroCall,
) -> 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;
}
@ -118,8 +127,6 @@ impl Expander {
self.cfg_expander.hygiene = Hygiene::new(db.upcast(), file_id);
self.current_file_id = file_id;
self.ast_id_map = db.ast_id_map(file_id);
self.recursive_limit += 1;
return Some((mark, expr));
}
}
@ -134,7 +141,7 @@ impl Expander {
self.cfg_expander.hygiene = Hygiene::new(db.upcast(), mark.file_id);
self.current_file_id = mark.file_id;
self.ast_id_map = mem::take(&mut mark.ast_id_map);
self.recursive_limit -= 1;
self.recursion_limit -= 1;
mark.bomb.defuse();
}
@ -209,7 +216,7 @@ pub struct BodySourceMap {
expr_map_back: ArenaMap<ExprId, Result<ExprSource, SyntheticSyntax>>,
pat_map: FxHashMap<PatSource, PatId>,
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>,
}
@ -302,7 +309,53 @@ impl BodySourceMap {
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()
}
}
#[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::{
ast::{
self, ArgListOwner, ArrayExprKind, LiteralKind, LoopBodyOwner, ModuleItemOwner, NameOwner,
SlicePatComponents, TypeAscriptionOwner,
SlicePatComponents,
},
AstNode, AstPtr,
};
@ -379,10 +379,10 @@ impl ExprCollector<'_> {
let expr = e.expr().map(|e| self.collect_expr(e));
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 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
.fields()
.inspect(|field| field_ptrs.push(AstPtr::new(field)))
@ -432,7 +432,7 @@ impl ExprCollector<'_> {
}
ast::Expr::CastExpr(e) => {
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)
}
ast::Expr::RefExpr(e) => {
@ -466,16 +466,13 @@ impl ExprCollector<'_> {
if let Some(pl) = e.param_list() {
for param in pl.params() {
let pat = self.collect_pat_opt(param.pat());
let type_ref =
param.ascribed_type().map(|it| TypeRef::from_ast(&self.ctx(), it));
let type_ref = param.ty().map(|it| TypeRef::from_ast(&self.ctx(), it));
args.push(pat);
arg_types.push(type_ref);
}
}
let ret_type = e
.ret_type()
.and_then(|r| r.type_ref())
.map(|it| TypeRef::from_ast(&self.ctx(), it));
let ret_type =
e.ret_type().and_then(|r| r.ty()).map(|it| TypeRef::from_ast(&self.ctx(), it));
let body = self.collect_expr_opt(e.body());
self.alloc_expr(Expr::Lambda { args, arg_types, ret_type, body }, syntax_ptr)
}
@ -607,8 +604,7 @@ impl ExprCollector<'_> {
.map(|s| match s {
ast::Stmt::LetStmt(stmt) => {
let pat = self.collect_pat_opt(stmt.pat());
let type_ref =
stmt.ascribed_type().map(|it| TypeRef::from_ast(&self.ctx(), it));
let type_ref = stmt.ty().map(|it| TypeRef::from_ast(&self.ctx(), it));
let initializer = stmt.initializer().map(|e| self.collect_expr(e));
Statement::Let { pat, type_ref, initializer }
}
@ -627,53 +623,53 @@ impl ExprCollector<'_> {
.items()
.filter_map(|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)?;
(
FunctionLoc { container: container.into(), id }.intern(self.db).into(),
def.name(),
)
}
ast::ModuleItem::TypeAliasDef(def) => {
ast::Item::TypeAlias(def) => {
let id = self.find_inner_item(&def)?;
(
TypeAliasLoc { container: container.into(), id }.intern(self.db).into(),
def.name(),
)
}
ast::ModuleItem::ConstDef(def) => {
ast::Item::Const(def) => {
let id = self.find_inner_item(&def)?;
(
ConstLoc { container: container.into(), id }.intern(self.db).into(),
def.name(),
)
}
ast::ModuleItem::StaticDef(def) => {
ast::Item::Static(def) => {
let id = self.find_inner_item(&def)?;
(StaticLoc { container, id }.intern(self.db).into(), def.name())
}
ast::ModuleItem::StructDef(def) => {
ast::Item::Struct(def) => {
let id = self.find_inner_item(&def)?;
(StructLoc { container, id }.intern(self.db).into(), def.name())
}
ast::ModuleItem::EnumDef(def) => {
ast::Item::Enum(def) => {
let id = self.find_inner_item(&def)?;
(EnumLoc { container, id }.intern(self.db).into(), def.name())
}
ast::ModuleItem::UnionDef(def) => {
ast::Item::Union(def) => {
let id = self.find_inner_item(&def)?;
(UnionLoc { container, id }.intern(self.db).into(), def.name())
}
ast::ModuleItem::TraitDef(def) => {
ast::Item::Trait(def) => {
let id = self.find_inner_item(&def)?;
(TraitLoc { container, id }.intern(self.db).into(), def.name())
}
ast::ModuleItem::ExternBlock(_) => return None, // FIXME: collect from extern blocks
ast::ModuleItem::ImplDef(_)
| ast::ModuleItem::UseItem(_)
| ast::ModuleItem::ExternCrateItem(_)
| ast::ModuleItem::Module(_)
| ast::ModuleItem::MacroCall(_) => return None,
ast::Item::ExternBlock(_) => return None, // FIXME: collect from extern blocks
ast::Item::Impl(_)
| ast::Item::Use(_)
| ast::Item::ExternCrate(_)
| ast::Item::Module(_)
| ast::Item::MacroCall(_) => return None,
};
Some((def, name))

View file

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

View file

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

View file

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

View file

@ -5,14 +5,16 @@ use std::{cmp::Ordering, fmt, hash::BuildHasherDefault, sync::Arc};
use fst::{self, Streamer};
use indexmap::{map::Entry, IndexMap};
use ra_db::CrateId;
use rustc_hash::FxHasher;
use ra_syntax::SmolStr;
use rustc_hash::{FxHashMap, FxHasher};
use smallvec::SmallVec;
use crate::{
db::DefDatabase,
item_scope::ItemInNs,
path::{ModPath, PathKind},
visibility::Visibility,
ModuleDefId, ModuleId,
AssocItemId, ModuleDefId, ModuleId, TraitId,
};
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
/// to be prepended to the `ModPath` before the path is valid.
#[derive(Default)]
pub struct ImportMap {
map: FxIndexMap<ItemInNs, ImportInfo>,
@ -45,13 +48,17 @@ pub struct ImportMap {
/// the index of the first one.
importables: Vec<ItemInNs>,
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 {
pub fn import_map_query(db: &dyn DefDatabase, krate: CrateId) -> Arc<Self> {
let _p = ra_prof::profile("import_map_query");
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.
let empty = ModPath { kind: PathKind::Plain, segments: vec![] };
@ -85,7 +92,7 @@ impl ImportMap {
for item in per_ns.iter_items() {
let path = mk_path();
match import_map.entry(item) {
match import_map.map.entry(item) {
Entry::Vacant(entry) => {
entry.insert(ImportInfo { path, container: module });
}
@ -105,11 +112,16 @@ impl ImportMap {
if let Some(ModuleDefId::ModuleId(mod_id)) = item.as_module_def_id() {
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);
@ -133,10 +145,10 @@ impl ImportMap {
builder.insert(key, start as u64).unwrap();
}
let fst = fst::Map::new(builder.into_inner().unwrap()).unwrap();
let importables = importables.iter().map(|(item, _)| **item).collect();
import_map.fst = fst::Map::new(builder.into_inner().unwrap()).unwrap();
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.
@ -147,6 +159,13 @@ impl ImportMap {
pub fn import_info_for(&self, item: ItemInNs) -> Option<&ImportInfo> {
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 {
@ -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
}
#[cfg(test)]
mod tests {
use expect::{expect, Expect};
use ra_db::{fixture::WithFixture, SourceDatabase, Upcast};
use crate::{test_db::TestDB, AssocContainerId, Lookup};
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 {
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 {
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 krate = crate_graph
@ -331,7 +345,7 @@ mod tests {
})
.unwrap();
search_dependencies(db.upcast(), krate, query)
let actual = search_dependencies(db.upcast(), krate, query)
.into_iter()
.filter_map(|item| {
let mark = match item {
@ -339,23 +353,67 @@ mod tests {
ItemInNs::Values(_) => "v",
ItemInNs::Macros(_) => "m",
};
let item = assoc_to_trait(&db, item);
item.krate(db.upcast()).map(|krate| {
let map = db.import_map(krate);
let path = map.path_of(item).unwrap();
format!(
"{}::{} ({})",
"{}::{} ({})\n",
crate_graph[krate].display_name.as_ref().unwrap(),
path,
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]
fn smoke() {
let map = import_map(
check(
r"
//- /main.rs crate:main deps:lib
@ -380,24 +438,23 @@ mod tests {
pub struct Pub2; // t + v
struct Priv;
",
expect![[r#"
main:
- publ1 (t)
- real_pu2 (t)
- real_pub (t)
- real_pub::Pub (t)
lib:
- Pub (t)
- Pub2 (t)
- Pub2 (v)
"#]],
);
assert_snapshot!(map, @r###"
main:
- publ1 (t)
- real_pu2 (t)
- real_pub (t)
- real_pub::Pub (t)
lib:
- Pub (t)
- Pub2 (t)
- Pub2 (v)
"###);
}
#[test]
fn prefers_shortest_path() {
let map = import_map(
check(
r"
//- /main.rs crate:main
@ -409,21 +466,20 @@ mod tests {
pub use super::sub::subsub::Def;
}
",
expect![[r#"
main:
- sub (t)
- sub::Def (t)
- sub::subsub (t)
"#]],
);
assert_snapshot!(map, @r###"
main:
- sub (t)
- sub::Def (t)
- sub::subsub (t)
"###);
}
#[test]
fn type_reexport_cross_crate() {
// Reexports need to be visible from a crate, even if the original crate exports the item
// at a shorter path.
let map = import_map(
check(
r"
//- /main.rs crate:main deps:lib
pub mod m {
@ -432,22 +488,21 @@ mod tests {
//- /lib.rs crate:lib
pub struct S;
",
expect![[r#"
main:
- m (t)
- m::S (t)
- m::S (v)
lib:
- S (t)
- S (v)
"#]],
);
assert_snapshot!(map, @r###"
main:
- m (t)
- m::S (t)
- m::S (v)
lib:
- S (t)
- S (v)
"###);
}
#[test]
fn macro_reexport() {
let map = import_map(
check(
r"
//- /main.rs crate:main deps:lib
pub mod m {
@ -459,21 +514,20 @@ mod tests {
() => {};
}
",
expect![[r#"
main:
- m (t)
- m::pub_macro (m)
lib:
- pub_macro (m)
"#]],
);
assert_snapshot!(map, @r###"
main:
- m (t)
- m::pub_macro (m)
lib:
- pub_macro (m)
"###);
}
#[test]
fn module_reexport() {
// Reexporting modules from a dependency adds all contents to the import map.
let map = import_map(
check(
r"
//- /main.rs crate:main deps:lib
pub use lib::module as reexported_module;
@ -482,24 +536,23 @@ mod tests {
pub struct S;
}
",
expect![[r#"
main:
- reexported_module (t)
- reexported_module::S (t)
- reexported_module::S (v)
lib:
- module (t)
- module::S (t)
- module::S (v)
"#]],
);
assert_snapshot!(map, @r###"
main:
- reexported_module (t)
- reexported_module::S (t)
- reexported_module::S (v)
lib:
- module (t)
- module::S (t)
- module::S (v)
"###);
}
#[test]
fn cyclic_module_reexport() {
// A cyclic reexport does not hang.
let map = import_map(
check(
r"
//- /lib.rs crate:lib
pub mod module {
@ -511,36 +564,35 @@ mod tests {
pub use super::module;
}
",
expect![[r#"
lib:
- module (t)
- module::S (t)
- module::S (v)
- sub (t)
"#]],
);
assert_snapshot!(map, @r###"
lib:
- module (t)
- module::S (t)
- module::S (v)
- sub (t)
"###);
}
#[test]
fn private_macro() {
let map = import_map(
check(
r"
//- /lib.rs crate:lib
macro_rules! private_macro {
() => {};
}
",
);
expect![[r#"
lib:
assert_snapshot!(map, @r###"
lib:
"###);
"#]],
);
}
#[test]
fn namespacing() {
let map = import_map(
check(
r"
//- /lib.rs crate:lib
pub struct Thing; // t + v
@ -549,16 +601,15 @@ mod tests {
() => {};
}
",
expect![[r#"
lib:
- Thing (m)
- Thing (t)
- Thing (v)
"#]],
);
assert_snapshot!(map, @r###"
lib:
- Thing (m)
- Thing (t)
- Thing (v)
"###);
let map = import_map(
check(
r"
//- /lib.rs crate:lib
pub mod Thing {} // t
@ -567,13 +618,12 @@ mod tests {
() => {};
}
",
expect![[r#"
lib:
- Thing (m)
- Thing (t)
"#]],
);
assert_snapshot!(map, @r###"
lib:
- Thing (m)
- Thing (t)
"###);
}
#[test]
@ -602,23 +652,33 @@ mod tests {
}
"#;
let res = search_dependencies_of(ra_fixture, "main", Query::new("fmt"));
assert_snapshot!(res, @r###"
dep::fmt (t)
dep::Fmt (t)
dep::Fmt (v)
dep::Fmt (m)
dep::fmt::Display (t)
dep::format (v)
"###);
check_search(
ra_fixture,
"main",
Query::new("fmt"),
expect![[r#"
dep::fmt (t)
dep::Fmt (t)
dep::Fmt (v)
dep::Fmt (m)
dep::fmt::Display (t)
dep::format (v)
dep::fmt::Display (t)
"#]],
);
let res = search_dependencies_of(ra_fixture, "main", Query::new("fmt").anchor_end());
assert_snapshot!(res, @r###"
dep::fmt (t)
dep::Fmt (t)
dep::Fmt (v)
dep::Fmt (m)
"###);
check_search(
ra_fixture,
"main",
Query::new("fmt").anchor_end(),
expect![[r#"
dep::fmt (t)
dep::Fmt (t)
dep::Fmt (v)
dep::Fmt (m)
dep::fmt::Display (t)
"#]],
);
}
#[test]
@ -631,26 +691,32 @@ mod tests {
pub struct FMT;
"#;
let res = search_dependencies_of(ra_fixture, "main", Query::new("FMT"));
check_search(
ra_fixture,
"main",
Query::new("FMT"),
expect![[r#"
dep::fmt (t)
dep::fmt (v)
dep::FMT (t)
dep::FMT (v)
"#]],
);
assert_snapshot!(res, @r###"
dep::fmt (t)
dep::fmt (v)
dep::FMT (t)
dep::FMT (v)
"###);
let res = search_dependencies_of(ra_fixture, "main", Query::new("FMT").case_sensitive());
assert_snapshot!(res, @r###"
dep::FMT (t)
dep::FMT (v)
"###);
check_search(
ra_fixture,
"main",
Query::new("FMT").case_sensitive(),
expect![[r#"
dep::FMT (t)
dep::FMT (v)
"#]],
);
}
#[test]
fn search_limit() {
let res = search_dependencies_of(
check_search(
r#"
//- /main.rs crate:main deps:dep
//- /dep.rs crate:dep
@ -670,10 +736,10 @@ mod tests {
"#,
"main",
Query::new("").limit(2),
expect![[r#"
dep::fmt (t)
dep::Fmt (t)
"#]],
);
assert_snapshot!(res, @r###"
dep::fmt (t)
dep::Fmt (t)
"###);
}
}

View file

@ -1,6 +1,8 @@
//! Describes items defined or visible (ie, imported) in a certain scope.
//! This is shared between modules and blocks.
use std::collections::hash_map::Entry;
use hir_expand::name::Name;
use once_cell::sync::Lazy;
use ra_db::CrateId;
@ -27,9 +29,15 @@ pub struct PerNsGlobImports {
#[derive(Debug, Default, PartialEq, Eq)]
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>,
impls: Vec<ImplId>,
/// Traits imported via `use Trait as _;`.
unnamed_trait_imports: FxHashMap<TraitId, Visibility>,
/// 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.
@ -65,14 +73,16 @@ pub(crate) enum BuiltinShadowMode {
/// Other methods will only resolve values, types and module scoped macros only.
impl ItemScope {
pub fn entries<'a>(&'a self) -> impl Iterator<Item = (&'a Name, PerNs)> + 'a {
//FIXME: shadowing
self.visible.iter().map(|(n, def)| (n, *def))
}
// FIXME: shadowing
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>(
&'a self,
) -> impl Iterator<Item = (&'a Name, PerNs)> + 'a {
self.visible.iter().map(|(n, def)| (n, *def))
keys.into_iter().map(move |name| (name, self.get(name)))
}
pub fn declarations(&self) -> impl Iterator<Item = ModuleDefId> + '_ {
@ -91,7 +101,7 @@ impl ItemScope {
/// Iterate over all module scoped macros
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
@ -101,12 +111,16 @@ impl ItemScope {
/// Get a name from current module scope, legacy macros are not included
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)> {
for (name, per_ns) in &self.visible {
if let Some(vis) = item.match_with(*per_ns) {
for (name, per_ns) in self.entries() {
if let Some(vis) = item.match_with(per_ns) {
return Some((name, vis));
}
}
@ -114,10 +128,13 @@ impl ItemScope {
}
pub(crate) fn traits<'a>(&'a self) -> impl Iterator<Item = TraitId> + 'a {
self.visible.values().filter_map(|def| match def.take_types() {
Some(ModuleDefId::TraitId(t)) => Some(t),
_ => None,
})
self.types
.values()
.filter_map(|(def, _)| match def {
ModuleDefId::TraitId(t) => Some(*t),
_ => None,
})
.chain(self.unnamed_trait_imports.keys().copied())
}
pub(crate) fn define_def(&mut self, def: ModuleDefId) {
@ -136,23 +153,40 @@ impl ItemScope {
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 {
let mut changed = false;
let existing = self.visible.entry(name).or_default();
if existing.types.is_none() && def.types.is_some() {
existing.types = def.types;
changed = true;
if let Some(types) = def.types {
self.types.entry(name.clone()).or_insert_with(|| {
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() {
existing.values = def.values;
changed = true;
}
if existing.macros.is_none() && def.macros.is_some() {
existing.macros = def.macros;
changed = true;
if def.is_none() {
if self.unresolved.insert(name) {
changed = true;
}
}
changed
@ -166,17 +200,17 @@ impl ItemScope {
def_import_type: ImportType,
) -> bool {
let mut changed = false;
let existing = self.visible.entry(lookup.1.clone()).or_default();
macro_rules! check_changed {
(
$changed:ident,
( $existing:ident / $def:ident ) . $field:ident,
( $this:ident / $def:ident ) . $field:ident,
$glob_imports:ident [ $lookup:ident ],
$def_import_type:ident
) => {
match ($existing.$field, $def.$field) {
(None, Some(_)) => {
) => {{
let existing = $this.$field.entry($lookup.1.clone());
match (existing, $def.$field) {
(Entry::Vacant(entry), Some(_)) => {
match $def_import_type {
ImportType::Glob => {
$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;
}
(Some(_), Some(_))
(Entry::Occupied(mut entry), Some(_))
if $glob_imports.$field.contains(&$lookup)
&& matches!($def_import_type, ImportType::Named) =>
{
mark::hit!(import_shadowed);
$glob_imports.$field.remove(&$lookup);
$existing.$field = $def.$field;
if let Some(fld) = $def.$field {
entry.insert(fld);
}
$changed = true;
}
_ => {}
}
};
}};
}
check_changed!(changed, (existing / def).types, glob_imports[lookup], def_import_type);
check_changed!(changed, (existing / 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).types, glob_imports[lookup], def_import_type);
check_changed!(changed, (self / def).values, 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
}
pub(crate) fn resolutions<'a>(&'a self) -> impl Iterator<Item = (Name, PerNs)> + 'a {
self.visible.iter().map(|(name, res)| (name.clone(), *res))
pub(crate) fn resolutions<'a>(&'a self) -> impl Iterator<Item = (Option<Name>, PerNs)> + 'a {
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> {

View file

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

View file

@ -1,10 +1,7 @@
//! AST -> `ItemTree` lowering code.
use super::*;
use crate::{
attr::Attrs,
generics::{GenericParams, TypeParamData, TypeParamProvenance},
};
use std::{collections::hash_map::Entry, mem, sync::Arc};
use hir_expand::{ast_id_map::AstIdMap, hygiene::Hygiene, HirFileId};
use ra_arena::map::ArenaMap;
use ra_syntax::{
@ -12,7 +9,13 @@ use ra_syntax::{
SyntaxNode,
};
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> {
FileItemTreeId { index, _p: PhantomData }
@ -70,19 +73,19 @@ impl Ctx {
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());
// Collect inner items for 1-to-1-lowered items.
match item {
ast::ModuleItem::StructDef(_)
| ast::ModuleItem::UnionDef(_)
| ast::ModuleItem::EnumDef(_)
| ast::ModuleItem::FnDef(_)
| ast::ModuleItem::TypeAliasDef(_)
| ast::ModuleItem::ConstDef(_)
| ast::ModuleItem::StaticDef(_)
| ast::ModuleItem::MacroCall(_) => {
ast::Item::Struct(_)
| ast::Item::Union(_)
| ast::Item::Enum(_)
| ast::Item::Fn(_)
| ast::Item::TypeAlias(_)
| ast::Item::Const(_)
| ast::Item::Static(_)
| ast::Item::MacroCall(_) => {
// Skip this if we're already collecting inner items. We'll descend into all nodes
// already.
if !inner {
@ -92,34 +95,30 @@ impl Ctx {
// These are handled in their respective `lower_X` method (since we can't just blindly
// walk them).
ast::ModuleItem::TraitDef(_)
| ast::ModuleItem::ImplDef(_)
| ast::ModuleItem::ExternBlock(_) => {}
ast::Item::Trait(_) | ast::Item::Impl(_) | ast::Item::ExternBlock(_) => {}
// These don't have inner items.
ast::ModuleItem::Module(_)
| ast::ModuleItem::ExternCrateItem(_)
| ast::ModuleItem::UseItem(_) => {}
ast::Item::Module(_) | ast::Item::ExternCrate(_) | ast::Item::Use(_) => {}
};
let attrs = Attrs::new(item, &self.hygiene);
let items = match item {
ast::ModuleItem::StructDef(ast) => self.lower_struct(ast).map(Into::into),
ast::ModuleItem::UnionDef(ast) => self.lower_union(ast).map(Into::into),
ast::ModuleItem::EnumDef(ast) => self.lower_enum(ast).map(Into::into),
ast::ModuleItem::FnDef(ast) => self.lower_function(ast).map(Into::into),
ast::ModuleItem::TypeAliasDef(ast) => self.lower_type_alias(ast).map(Into::into),
ast::ModuleItem::StaticDef(ast) => self.lower_static(ast).map(Into::into),
ast::ModuleItem::ConstDef(ast) => Some(self.lower_const(ast).into()),
ast::ModuleItem::Module(ast) => self.lower_module(ast).map(Into::into),
ast::ModuleItem::TraitDef(ast) => self.lower_trait(ast).map(Into::into),
ast::ModuleItem::ImplDef(ast) => self.lower_impl(ast).map(Into::into),
ast::ModuleItem::UseItem(ast) => Some(ModItems(
ast::Item::Struct(ast) => self.lower_struct(ast).map(Into::into),
ast::Item::Union(ast) => self.lower_union(ast).map(Into::into),
ast::Item::Enum(ast) => self.lower_enum(ast).map(Into::into),
ast::Item::Fn(ast) => self.lower_function(ast).map(Into::into),
ast::Item::TypeAlias(ast) => self.lower_type_alias(ast).map(Into::into),
ast::Item::Static(ast) => self.lower_static(ast).map(Into::into),
ast::Item::Const(ast) => Some(self.lower_const(ast).into()),
ast::Item::Module(ast) => self.lower_module(ast).map(Into::into),
ast::Item::Trait(ast) => self.lower_trait(ast).map(Into::into),
ast::Item::Impl(ast) => self.lower_impl(ast).map(Into::into),
ast::Item::Use(ast) => Some(ModItems(
self.lower_use(ast).into_iter().map(Into::into).collect::<SmallVec<_>>(),
)),
ast::ModuleItem::ExternCrateItem(ast) => self.lower_extern_crate(ast).map(Into::into),
ast::ModuleItem::MacroCall(ast) => self.lower_macro_call(ast).map(Into::into),
ast::ModuleItem::ExternBlock(ast) => {
ast::Item::ExternCrate(ast) => self.lower_extern_crate(ast).map(Into::into),
ast::Item::MacroCall(ast) => self.lower_macro_call(ast).map(Into::into),
ast::Item::ExternBlock(ast) => {
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) {
let forced_vis = self.forced_visibility.take();
let mut inner_items = mem::take(&mut self.tree.inner_items);
inner_items.extend(
container.descendants().skip(1).filter_map(ast::ModuleItem::cast).filter_map(|item| {
inner_items.extend(container.descendants().skip(1).filter_map(ast::Item::cast).filter_map(
|item| {
let ast_id = self.source_ast_id_map.ast_id(&item);
Some((ast_id, self.lower_mod_item(&item, true)?.0))
}),
);
},
));
self.tree.inner_items = inner_items;
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 {
ast::ModuleItem::FnDef(ast) => self.lower_function(ast).map(Into::into),
ast::ModuleItem::TypeAliasDef(ast) => self.lower_type_alias(ast).map(Into::into),
ast::ModuleItem::ConstDef(ast) => Some(self.lower_const(ast).into()),
ast::ModuleItem::MacroCall(ast) => self.lower_macro_call(ast).map(Into::into),
_ => None,
ast::AssocItem::Fn(ast) => self.lower_function(ast).map(Into::into),
ast::AssocItem::TypeAlias(ast) => self.lower_type_alias(ast).map(Into::into),
ast::AssocItem::Const(ast) => Some(self.lower_const(ast).into()),
ast::AssocItem::MacroCall(ast) => self.lower_macro_call(ast).map(Into::into),
}
}
fn lower_struct(&mut self, strukt: &ast::StructDef) -> Option<FileItemTreeId<Struct>> {
fn lower_struct(&mut self, strukt: &ast::Struct) -> Option<FileItemTreeId<Struct>> {
let visibility = self.lower_visibility(strukt);
let name = strukt.name()?.as_name();
let generic_params = self.lower_generic_params(GenericsOwner::Struct, strukt);
@ -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();
for field in fields.fields() {
if let Some(data) = self.lower_record_field(&field) {
@ -208,42 +206,39 @@ impl Ctx {
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 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 };
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();
for (i, field) in fields.fields().enumerate() {
if let Some(data) = self.lower_tuple_field(i, &field) {
let idx = self.data().fields.alloc(data);
self.add_attrs(idx.into(), Attrs::new(&field, &self.hygiene));
}
let data = self.lower_tuple_field(i, &field);
let idx = self.data().fields.alloc(data);
self.add_attrs(idx.into(), Attrs::new(&field, &self.hygiene));
}
let end = self.next_field_idx();
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 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 };
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 name = union.name()?.as_name();
let generic_params = self.lower_generic_params(GenericsOwner::Union, union);
let fields = match union.record_field_def_list() {
Some(record_field_def_list) => {
self.lower_fields(&StructKind::Record(record_field_def_list))
}
let fields = match union.record_field_list() {
Some(record_field_list) => self.lower_fields(&StructKind::Record(record_field_list)),
None => Fields::Record(IdRange::new(self.next_field_idx()..self.next_field_idx())),
};
let ast_id = self.source_ast_id_map.ast_id(union);
@ -251,7 +246,7 @@ impl Ctx {
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 name = enum_.name()?.as_name();
let generic_params = self.lower_generic_params(GenericsOwner::Enum, enum_);
@ -264,7 +259,7 @@ impl Ctx {
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();
for variant in variants.variants() {
if let Some(data) = self.lower_variant(&variant) {
@ -276,14 +271,14 @@ impl Ctx {
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 fields = self.lower_fields(&variant.kind());
let res = Variant { name, fields };
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 name = func.name()?.as_name();
@ -291,7 +286,7 @@ impl Ctx {
let mut has_self_param = false;
if let Some(param_list) = func.param_list() {
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),
None => {
let self_type = TypeRef::Path(name![Self].into());
@ -310,11 +305,19 @@ impl Ctx {
has_self_param = true;
}
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);
}
}
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),
_ => TypeRef::unit(),
};
@ -335,6 +338,7 @@ impl Ctx {
has_self_param,
is_unsafe: func.unsafe_token().is_some(),
params: params.into_boxed_slice(),
is_varargs,
ret_type,
ast_id,
};
@ -345,10 +349,10 @@ impl Ctx {
fn lower_type_alias(
&mut self,
type_alias: &ast::TypeAliasDef,
type_alias: &ast::TypeAlias,
) -> Option<FileItemTreeId<TypeAlias>> {
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 bounds = self.lower_type_bounds(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)))
}
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 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 mutable = static_.mut_token().is_some();
let ast_id = self.source_ast_id_map.ast_id(static_);
@ -374,9 +378,9 @@ impl Ctx {
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 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 ast_id = self.source_ast_id_map.ast_id(konst);
let res = Const { name, visibility, type_ref, ast_id };
@ -409,15 +413,15 @@ impl Ctx {
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 visibility = self.lower_visibility(trait_def);
let generic_params =
self.lower_generic_params_and_inner_items(GenericsOwner::Trait(trait_def), trait_def);
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| {
list.items()
list.assoc_items()
.filter_map(|item| {
let attrs = Attrs::new(&item, &this.hygiene);
this.collect_inner_items(item.syntax());
@ -441,7 +445,7 @@ impl Ctx {
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 =
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));
@ -450,8 +454,9 @@ impl Ctx {
// We cannot use `assoc_items()` here as that does not include macro calls.
let items = impl_def
.item_list()?
.items()
.assoc_item_list()
.into_iter()
.flat_map(|it| it.assoc_items())
.filter_map(|item| {
self.collect_inner_items(item.syntax());
let assoc = self.lower_assoc_item(&item)?;
@ -465,7 +470,7 @@ impl Ctx {
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
let is_prelude = use_item.has_atom_attr("prelude_import");
let visibility = self.lower_visibility(use_item);
@ -494,10 +499,10 @@ impl Ctx {
fn lower_extern_crate(
&mut self,
extern_crate: &ast::ExternCrateItem,
extern_crate: &ast::ExternCrate,
) -> Option<FileItemTreeId<ExternCrate>> {
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)
});
let visibility = self.lower_visibility(extern_crate);
@ -543,14 +548,16 @@ impl Ctx {
self.collect_inner_items(item.syntax());
let attrs = Attrs::new(&item, &self.hygiene);
let id: ModItem = match item {
ast::ExternItem::FnDef(ast) => {
ast::ExternItem::Fn(ast) => {
let func = self.lower_function(&ast)?;
self.data().functions[func.index].is_unsafe = true;
func.into()
}
ast::ExternItem::StaticDef(ast) => {
ast::ExternItem::Static(ast) => {
let statik = self.lower_static(&ast)?;
statik.into()
}
ast::ExternItem::MacroCall(_) => return None,
};
self.add_attrs(id.into(), attrs);
Some(id)
@ -563,10 +570,10 @@ impl Ctx {
fn lower_generic_params_and_inner_items(
&mut self,
owner: GenericsOwner<'_>,
node: &impl ast::TypeParamsOwner,
node: &impl ast::GenericParamsOwner,
) -> GenericParamsId {
// 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());
}
if let Some(clause) = node.where_clause() {
@ -579,7 +586,7 @@ impl Ctx {
fn lower_generic_params(
&mut self,
owner: GenericsOwner<'_>,
node: &impl ast::TypeParamsOwner,
node: &impl ast::GenericParamsOwner,
) -> GenericParamsId {
let mut sm = &mut ArenaMap::default();
let mut generics = GenericParams::default();
@ -692,7 +699,7 @@ enum GenericsOwner<'a> {
Enum,
Union,
/// The `TraitDef` is needed to fill the source map for the implicit `Self` parameter.
Trait(&'a ast::TraitDef),
Trait(&'a ast::Trait),
TypeAlias,
Impl,
}

View file

@ -1,13 +1,15 @@
use super::{ItemTree, ModItem, ModKind};
use crate::{db::DefDatabase, test_db::TestDB};
use expect::{expect, Expect};
use hir_expand::{db::AstDatabase, HirFileId, InFile};
use insta::assert_snapshot;
use ra_db::fixture::WithFixture;
use ra_syntax::{ast, AstNode};
use rustc_hash::FxHashSet;
use std::sync::Arc;
use stdx::format_to;
use crate::{db::DefDatabase, test_db::TestDB};
use super::{ItemTree, ModItem, ModKind};
fn test_inner_items(ra_fixture: &str) {
let (db, file_id) = TestDB::with_single_file(ra_fixture);
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 worklist = tree.top_level_items().to_vec();
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::ExternCrate(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
// 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) {
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]
fn smoke() {
assert_snapshot!(print_item_tree(r"
check(
r"
#![attr]
#[attr_on_use]
@ -214,42 +222,44 @@ fn smoke() {
#[union_fld]
fld: u16,
}
"), @r###"
inner attrs: Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr"))] }, input: None }]) }
",
expect![[r##"
inner attrs: Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr"))] }, input: None }]) }
top-level items:
#[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) }
#[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) }
#[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) }
#[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) }
> #[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) }
> #[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) }
> #[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) }
> #[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) }
#[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 }
#[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 }
#[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 }
#[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) }
#[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) }
"###);
top-level items:
#[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::Use>(0) }
#[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::Use>(0) }
#[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::ExternCrate>(1) }
#[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::Trait>(2) }
> #[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::TypeAlias>(8) }
> #[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::Const>(9) }
> #[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)], 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 }]) }]
> 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 }]) }]
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 }]) }]
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 }]) }]
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 }]) }]
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 }]) }]
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]
fn simple_inner_items() {
let tree = print_item_tree(
check(
r"
impl<T:A> D for Response<T> {
fn foo() {
@ -260,26 +270,25 @@ fn simple_inner_items() {
}
}
",
expect![[r#"
inner attrs: Attrs { entries: None }
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::Impl>(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>(1) }
inner items:
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: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<ra_syntax::ast::generated::nodes::Fn>(2) }
"#]],
);
assert_snapshot!(tree, @r###"
inner attrs: Attrs { entries: None }
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) }
> 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) }
inner items:
for AST FileAstId::<ra_syntax::ast::generated::nodes::ModuleItem>(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) }
"###);
}
#[test]
fn extern_attrs() {
let tree = print_item_tree(
check(
r#"
#[block_attr]
extern "C" {
@ -289,22 +298,21 @@ fn extern_attrs() {
fn b() {}
}
"#,
expect![[r##"
inner attrs: Attrs { entries: None }
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 }]) }]
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 }]) }]
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) }
"##]],
);
assert_snapshot!(tree, @r###"
inner attrs: Attrs { entries: None }
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 }]) }]
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) }
#[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) }
"###);
}
#[test]
fn trait_attrs() {
let tree = print_item_tree(
check(
r#"
#[trait_attr]
trait Tr {
@ -314,24 +322,23 @@ fn trait_attrs() {
fn b() {}
}
"#,
expect![[r##"
inner attrs: Attrs { entries: None }
top-level items:
#[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::Trait>(0) }
> #[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: [], 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 }]) }]
> 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) }
"##]],
);
assert_snapshot!(tree, @r###"
inner attrs: Attrs { entries: None }
top-level items:
#[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) }
> #[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) }
> #[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) }
"###);
}
#[test]
fn impl_attrs() {
let tree = print_item_tree(
check(
r#"
#[impl_attr]
impl Ty {
@ -341,19 +348,18 @@ fn impl_attrs() {
fn b() {}
}
"#,
expect![[r##"
inner attrs: Attrs { entries: None }
top-level items:
#[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::Impl>(0) }
> #[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: [], 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 }]) }]
> 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) }
"##]],
);
assert_snapshot!(tree, @r###"
inner attrs: Attrs { entries: None }
top-level items:
#[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) }
> #[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) }
> #[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) }
"###);
}
#[test]
@ -391,45 +397,43 @@ fn cursed_inner_items() {
#[test]
fn inner_item_attrs() {
let tree = print_item_tree(
check(
r"
fn foo() {
#[on_inner]
fn inner() {}
}
",
expect![[r##"
inner attrs: Attrs { entries: None }
top-level items:
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:
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 }]) }]
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) }
"##]],
);
assert_snapshot!(tree, @r###"
inner attrs: Attrs { entries: None }
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) }
inner items:
for AST FileAstId::<ra_syntax::ast::generated::nodes::ModuleItem>(1):
#[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) }
"###);
}
#[test]
fn assoc_item_macros() {
let tree = print_item_tree(
check(
r"
impl S {
items!();
}
",
expect![[r#"
inner attrs: Attrs { entries: None }
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::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) }
"#]],
);
assert_snapshot!(tree, @r###"
inner attrs: Attrs { entries: None }
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) }
> 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 const FUNCTION: Key<ast::FnDef, FunctionId> = Key::new();
pub const CONST: Key<ast::ConstDef, ConstId> = Key::new();
pub const STATIC: Key<ast::StaticDef, StaticId> = Key::new();
pub const TYPE_ALIAS: Key<ast::TypeAliasDef, TypeAliasId> = Key::new();
pub const IMPL: Key<ast::ImplDef, ImplId> = Key::new();
pub const TRAIT: Key<ast::TraitDef, TraitId> = Key::new();
pub const STRUCT: Key<ast::StructDef, StructId> = Key::new();
pub const UNION: Key<ast::UnionDef, UnionId> = Key::new();
pub const ENUM: Key<ast::EnumDef, EnumId> = Key::new();
pub const FUNCTION: Key<ast::Fn, FunctionId> = Key::new();
pub const CONST: Key<ast::Const, ConstId> = Key::new();
pub const STATIC: Key<ast::Static, StaticId> = Key::new();
pub const TYPE_ALIAS: Key<ast::TypeAlias, TypeAliasId> = Key::new();
pub const IMPL: Key<ast::Impl, ImplId> = Key::new();
pub const TRAIT: Key<ast::Trait, TraitId> = Key::new();
pub const STRUCT: Key<ast::Struct, StructId> = Key::new();
pub const UNION: Key<ast::Union, UnionId> = Key::new();
pub const ENUM: Key<ast::Enum, EnumId> = Key::new();
pub const ENUM_VARIANT: Key<ast::EnumVariant, EnumVariantId> = Key::new();
pub const TUPLE_FIELD: Key<ast::TupleFieldDef, FieldId> = Key::new();
pub const RECORD_FIELD: Key<ast::RecordFieldDef, FieldId> = Key::new();
pub const VARIANT: Key<ast::Variant, EnumVariantId> = Key::new();
pub const TUPLE_FIELD: Key<ast::TupleField, 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 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,
TypeAlias, Union,
};
use stdx::impl_from;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ModuleId {
@ -158,17 +159,17 @@ pub struct FunctionId(salsa::InternId);
type FunctionLoc = AssocItemLoc<Function>;
impl_intern!(FunctionId, FunctionLoc, intern_function, lookup_intern_function);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct StructId(salsa::InternId);
type StructLoc = ItemLoc<Struct>;
impl_intern!(StructId, StructLoc, intern_struct, lookup_intern_struct);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct UnionId(salsa::InternId);
pub type UnionLoc = ItemLoc<Union>;
impl_intern!(UnionId, UnionLoc, intern_union, lookup_intern_union);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct EnumId(salsa::InternId);
pub type EnumLoc = ItemLoc<Enum>;
impl_intern!(EnumId, EnumLoc, intern_enum, lookup_intern_enum);
@ -223,25 +224,6 @@ pub struct TypeParamId {
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)]
pub enum ContainerId {
ModuleId(ModuleId),
@ -254,16 +236,16 @@ pub enum AssocContainerId {
ImplId(ImplId),
TraitId(TraitId),
}
impl_froms!(AssocContainerId: ContainerId);
impl_from!(ContainerId for AssocContainerId);
/// A Data Type
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum AdtId {
StructId(StructId),
UnionId(UnionId),
EnumId(EnumId),
}
impl_froms!(AdtId: StructId, UnionId, EnumId);
impl_from!(StructId, UnionId, EnumId for AdtId);
/// The defs which can be visible in the module.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@ -279,8 +261,8 @@ pub enum ModuleDefId {
TypeAliasId(TypeAliasId),
BuiltinType(BuiltinType),
}
impl_froms!(
ModuleDefId: ModuleId,
impl_from!(
ModuleId,
FunctionId,
AdtId(StructId, EnumId, UnionId),
EnumVariantId,
@ -289,6 +271,7 @@ impl_froms!(
TraitId,
TypeAliasId,
BuiltinType
for ModuleDefId
);
/// The defs which have a body.
@ -299,7 +282,7 @@ pub enum DefWithBodyId {
ConstId(ConstId),
}
impl_froms!(DefWithBodyId: FunctionId, ConstId, StaticId);
impl_from!(FunctionId, ConstId, StaticId for DefWithBodyId);
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum AssocItemId {
@ -311,7 +294,7 @@ pub enum AssocItemId {
// sure that you can only turn actual assoc items into AssocItemIds. This would
// require not implementing From, and instead having some checked way of
// casting them, and somehow making the constructors private, which would be annoying.
impl_froms!(AssocItemId: FunctionId, ConstId, TypeAliasId);
impl_from!(FunctionId, ConstId, TypeAliasId for AssocItemId);
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
pub enum GenericDefId {
@ -326,14 +309,15 @@ pub enum GenericDefId {
// consts can have type parameters from their parents (i.e. associated consts of traits)
ConstId(ConstId),
}
impl_froms!(
GenericDefId: FunctionId,
impl_from!(
FunctionId,
AdtId(StructId, EnumId, UnionId),
TraitId,
TypeAliasId,
ImplId,
EnumVariantId,
ConstId
for GenericDefId
);
impl From<AssocItemId> for GenericDefId {
@ -361,8 +345,8 @@ pub enum AttrDefId {
ImplId(ImplId),
}
impl_froms!(
AttrDefId: ModuleId,
impl_from!(
ModuleId,
FieldId,
AdtId(StructId, EnumId, UnionId),
EnumVariantId,
@ -373,6 +357,7 @@ impl_froms!(
TypeAliasId,
MacroDefId,
ImplId
for AttrDefId
);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@ -381,7 +366,7 @@ pub enum VariantId {
StructId(StructId),
UnionId(UnionId),
}
impl_froms!(VariantId: EnumVariantId, StructId, UnionId);
impl_from!(EnumVariantId, StructId, UnionId for VariantId);
trait Intern {
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(
&self,
db: &dyn db::DefDatabase,

View file

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

View file

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

View file

@ -1,23 +1,24 @@
//! This module resolves `mod foo;` declaration to file.
use hir_expand::name::Name;
use ra_db::{FileId, RelativePathBuf};
use ra_db::FileId;
use ra_syntax::SmolStr;
use crate::{db::DefDatabase, HirFileId};
#[derive(Clone, Debug)]
pub(super) struct ModDir {
/// `.` for `mod.rs`, `lib.rs`
/// `./foo` for `foo.rs`
/// `./foo/bar` for `mod bar { mod x; }` nested in `foo.rs`
path: RelativePathBuf,
/// `` for `mod.rs`, `lib.rs`
/// `foo/` for `foo.rs`
/// `foo/bar/` for `mod bar { mod x; }` nested in `foo.rs`
/// Invariant: path.is_empty() || path.ends_with('/')
dir_path: DirPath,
/// inside `./foo.rs`, mods with `#[path]` should *not* be relative to `./foo/`
root_non_dir_owner: bool,
}
impl 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(
@ -25,17 +26,21 @@ impl ModDir {
name: &Name,
attr_path: Option<&SmolStr>,
) -> ModDir {
let mut path = self.path.clone();
match attr_to_path(attr_path) {
None => path.push(&name.to_string()),
Some(attr_path) => {
if self.root_non_dir_owner {
assert!(path.pop());
}
path.push(attr_path);
let path = match attr_path.map(|it| it.as_str()) {
None => {
let mut path = self.dir_path.clone();
path.push(&name.to_string());
path
}
}
ModDir { path, root_non_dir_owner: false }
Some(attr_path) => {
let mut path = self.dir_path.join_attr(attr_path, self.root_non_dir_owner);
if !(path.is_empty() || path.ends_with('/')) {
path.push('/')
}
DirPath::new(path)
}
};
ModDir { dir_path: path, root_non_dir_owner: false }
}
pub(super) fn resolve_declaration(
@ -48,34 +53,87 @@ impl ModDir {
let file_id = file_id.original_file(db.upcast());
let mut candidate_files = Vec::new();
match attr_to_path(attr_path) {
match attr_path {
Some(attr_path) => {
let base =
if self.root_non_dir_owner { self.path.parent().unwrap() } else { &self.path };
candidate_files.push(base.join(attr_path).to_string())
candidate_files.push(self.dir_path.join_attr(attr_path, self.root_non_dir_owner))
}
None => {
candidate_files.push(self.path.join(&format!("{}.rs", name)).to_string());
candidate_files.push(self.path.join(&format!("{}/mod.rs", name)).to_string());
candidate_files.push(format!("{}{}.rs", self.dir_path.0, name));
candidate_files.push(format!("{}{}/mod.rs", self.dir_path.0, name));
}
};
for candidate in candidate_files.iter() {
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");
if !(is_mod_rs || attr_path.is_some()) {
root_non_dir_owner = true;
mod_path.push(&name.to_string());
}
return Ok((file_id, is_mod_rs, ModDir { path: mod_path, root_non_dir_owner }));
let (dir_path, root_non_dir_owner) = if is_mod_rs || attr_path.is_some() {
(DirPath::empty(), false)
} else {
(DirPath::new(format!("{}/", name)), true)
};
return Ok((file_id, is_mod_rs, ModDir { dir_path, root_non_dir_owner }));
}
}
Err(candidate_files.remove(0))
}
}
fn attr_to_path(attr: Option<&SmolStr>) -> Option<RelativePathBuf> {
attr.and_then(|it| RelativePathBuf::from_path(&it.replace("\\", "/")).ok())
#[derive(Clone, Debug)]
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,7 +226,15 @@ impl CrateDefMap {
match enum_data.variant(&segment) {
Some(local_id) => {
let variant = EnumVariantId { parent: e, local_id };
PerNs::both(variant.into(), variant.into(), Visibility::Public)
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)
}
}
}
None => {
return ResolvePathResult::with(

File diff suppressed because it is too large Load diff

View file

@ -2,367 +2,337 @@ use super::*;
#[test]
fn glob_1() {
let map = def_map(
r"
//- /lib.rs
mod foo;
use foo::*;
check(
r#"
//- /lib.rs
mod foo;
use foo::*;
//- /foo/mod.rs
pub mod bar;
pub use self::bar::Baz;
pub struct Foo;
//- /foo/mod.rs
pub mod bar;
pub use self::bar::Baz;
pub struct Foo;
//- /foo/bar.rs
pub struct Baz;
",
);
assert_snapshot!(map, @r###"
crate
Baz: t v
Foo: t v
bar: t
foo: t
crate::foo
Baz: t v
Foo: t v
bar: t
crate::foo::bar
Baz: t v
"###
//- /foo/bar.rs
pub struct Baz;
"#,
expect![[r#"
crate
Baz: t v
Foo: t v
bar: t
foo: t
crate::foo
Baz: t v
Foo: t v
bar: t
crate::foo::bar
Baz: t v
"#]],
);
}
#[test]
fn glob_2() {
let map = def_map(
"
//- /lib.rs
mod foo;
use foo::*;
check(
r#"
//- /lib.rs
mod foo;
use foo::*;
//- /foo/mod.rs
pub mod bar;
pub use self::bar::*;
pub struct Foo;
//- /foo/mod.rs
pub mod bar;
pub use self::bar::*;
pub struct Foo;
//- /foo/bar.rs
pub struct Baz;
pub use super::*;
",
);
assert_snapshot!(map, @r###"
crate
Baz: t v
Foo: t v
bar: t
foo: t
crate::foo
Baz: t v
Foo: t v
bar: t
crate::foo::bar
Baz: t v
Foo: t v
bar: t
"###
//- /foo/bar.rs
pub struct Baz;
pub use super::*;
"#,
expect![[r#"
crate
Baz: t v
Foo: t v
bar: t
foo: t
crate::foo
Baz: t v
Foo: t v
bar: t
crate::foo::bar
Baz: t v
Foo: t v
bar: t
"#]],
);
}
#[test]
fn glob_privacy_1() {
let map = def_map(
check(
r"
//- /lib.rs
mod foo;
use foo::*;
//- /lib.rs
mod foo;
use foo::*;
//- /foo/mod.rs
pub mod bar;
pub use self::bar::*;
struct PrivateStructFoo;
//- /foo/mod.rs
pub mod bar;
pub use self::bar::*;
struct PrivateStructFoo;
//- /foo/bar.rs
pub struct Baz;
struct PrivateStructBar;
pub use super::*;
",
);
assert_snapshot!(map, @r###"
crate
Baz: t v
bar: t
foo: t
crate::foo
Baz: t v
PrivateStructFoo: t v
bar: t
crate::foo::bar
Baz: t v
PrivateStructBar: t v
PrivateStructFoo: t v
bar: t
"###
//- /foo/bar.rs
pub struct Baz;
struct PrivateStructBar;
pub use super::*;
",
expect![[r#"
crate
Baz: t v
bar: t
foo: t
crate::foo
Baz: t v
PrivateStructFoo: t v
bar: t
crate::foo::bar
Baz: t v
PrivateStructBar: t v
PrivateStructFoo: t v
bar: t
"#]],
);
}
#[test]
fn glob_privacy_2() {
let map = def_map(
check(
r"
//- /lib.rs
mod foo;
use foo::*;
use foo::bar::*;
//- /lib.rs
mod foo;
use foo::*;
use foo::bar::*;
//- /foo/mod.rs
mod bar;
fn Foo() {};
pub struct Foo {};
//- /foo/mod.rs
mod bar;
fn Foo() {};
pub struct Foo {};
//- /foo/bar.rs
pub(super) struct PrivateBaz;
struct PrivateBar;
pub(crate) struct PubCrateStruct;
",
);
assert_snapshot!(map, @r###"
crate
Foo: t
PubCrateStruct: t v
foo: t
crate::foo
Foo: t v
bar: t
crate::foo::bar
PrivateBar: t v
PrivateBaz: t v
PubCrateStruct: t v
"###
//- /foo/bar.rs
pub(super) struct PrivateBaz;
struct PrivateBar;
pub(crate) struct PubCrateStruct;
",
expect![[r#"
crate
Foo: t
PubCrateStruct: t v
foo: t
crate::foo
Foo: t v
bar: t
crate::foo::bar
PrivateBar: t v
PrivateBaz: t v
PubCrateStruct: t v
"#]],
);
}
#[test]
fn glob_across_crates() {
mark::check!(glob_across_crates);
let map = def_map(
r"
//- /main.rs crate:main deps:test_crate
use test_crate::*;
check(
r#"
//- /main.rs crate:main deps:test_crate
use test_crate::*;
//- /lib.rs crate:test_crate
pub struct Baz;
",
);
assert_snapshot!(map, @r###"
crate
Baz: t v
"###
//- /lib.rs crate:test_crate
pub struct Baz;
"#,
expect![[r#"
crate
Baz: t v
"#]],
);
}
#[test]
fn glob_privacy_across_crates() {
let map = def_map(
r"
//- /main.rs crate:main deps:test_crate
use test_crate::*;
check(
r#"
//- /main.rs crate:main deps:test_crate
use test_crate::*;
//- /lib.rs crate:test_crate
pub struct Baz;
struct Foo;
",
);
assert_snapshot!(map, @r###"
crate
Baz: t v
"###
//- /lib.rs crate:test_crate
pub struct Baz;
struct Foo;
"#,
expect![[r#"
crate
Baz: t v
"#]],
);
}
#[test]
fn glob_enum() {
mark::check!(glob_enum);
let map = def_map(
"
//- /lib.rs
enum Foo {
Bar, Baz
}
use self::Foo::*;
",
);
assert_snapshot!(map, @r###"
crate
Bar: t v
Baz: t v
Foo: t
"###
check(
r#"
enum Foo { Bar, Baz }
use self::Foo::*;
"#,
expect![[r#"
crate
Bar: t v
Baz: t v
Foo: t
"#]],
);
}
#[test]
fn glob_enum_group() {
mark::check!(glob_enum_group);
let map = def_map(
r"
//- /lib.rs
enum Foo {
Bar, Baz
}
use self::Foo::{*};
",
);
assert_snapshot!(map, @r###"
crate
Bar: t v
Baz: t v
Foo: t
"###
check(
r#"
enum Foo { Bar, Baz }
use self::Foo::{*};
"#,
expect![[r#"
crate
Bar: t v
Baz: t v
Foo: t
"#]],
);
}
#[test]
fn glob_shadowed_def() {
mark::check!(import_shadowed);
let map = def_map(
r###"
//- /lib.rs
mod foo;
mod bar;
check(
r#"
//- /lib.rs
mod foo;
mod bar;
use foo::*;
use bar::baz;
use baz::Bar;
use foo::*;
use bar::baz;
//- /foo.rs
pub mod baz { pub struct Foo; }
use baz::Bar;
//- /bar.rs
pub mod baz { pub struct Bar; }
"#,
expect![[r#"
crate
Bar: t v
bar: t
baz: t
foo: t
//- /foo.rs
pub mod baz {
pub struct Foo;
}
crate::bar
baz: t
//- /bar.rs
pub mod baz {
pub struct Bar;
}
"###,
);
assert_snapshot!(map, @r###"
crate
Bar: t v
bar: t
baz: t
foo: t
crate::bar
baz: t
crate::bar::baz
Bar: t v
crate::foo
baz: t
crate::foo::baz
Foo: t v
"###
crate::bar::baz
Bar: t v
crate::foo
baz: t
crate::foo::baz
Foo: t v
"#]],
);
}
#[test]
fn glob_shadowed_def_reversed() {
let map = def_map(
r###"
//- /lib.rs
mod foo;
mod bar;
check(
r#"
//- /lib.rs
mod foo;
mod bar;
use bar::baz;
use foo::*;
use baz::Bar;
use bar::baz;
use foo::*;
//- /foo.rs
pub mod baz { pub struct Foo; }
use baz::Bar;
//- /bar.rs
pub mod baz { pub struct Bar; }
"#,
expect![[r#"
crate
Bar: t v
bar: t
baz: t
foo: t
//- /foo.rs
pub mod baz {
pub struct Foo;
}
crate::bar
baz: t
//- /bar.rs
pub mod baz {
pub struct Bar;
}
"###,
);
assert_snapshot!(map, @r###"
crate
Bar: t v
bar: t
baz: t
foo: t
crate::bar
baz: t
crate::bar::baz
Bar: t v
crate::foo
baz: t
crate::foo::baz
Foo: t v
"###
crate::bar::baz
Bar: t v
crate::foo
baz: t
crate::foo::baz
Foo: t v
"#]],
);
}
#[test]
fn glob_shadowed_def_dependencies() {
let map = def_map(
r###"
//- /lib.rs
mod a { pub mod foo { pub struct X; } }
mod b { pub use super::a::foo; }
mod c { pub mod foo { pub struct Y; } }
mod d {
use super::c::foo;
use super::b::*;
use foo::Y;
}
"###,
);
assert_snapshot!(map, @r###"
crate
a: t
b: t
c: t
d: t
crate::d
Y: t v
foo: t
crate::c
foo: t
crate::c::foo
Y: t v
crate::b
foo: t
crate::a
foo: t
crate::a::foo
X: t v
"###
check(
r#"
mod a { pub mod foo { pub struct X; } }
mod b { pub use super::a::foo; }
mod c { pub mod foo { pub struct Y; } }
mod d {
use super::c::foo;
use super::b::*;
use foo::Y;
}
"#,
expect![[r#"
crate
a: t
b: t
c: t
d: t
crate::d
Y: t v
foo: t
crate::c
foo: t
crate::c::foo
Y: t v
crate::b
foo: t
crate::a
foo: t
crate::a::foo
X: t v
"#]],
);
}

File diff suppressed because it is too large Load diff

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