mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-26 04:53:34 +00:00
Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
178c3e135a
1454 changed files with 98279 additions and 97700 deletions
2
.gitattributes
vendored
2
.gitattributes
vendored
|
@ -1,5 +1,5 @@
|
|||
* text=auto eol=lf
|
||||
crates/ra_syntax/test_data/** -text eof=LF
|
||||
crates/syntax/test_data/** -text eof=LF
|
||||
# Older git versions try to fix line endings on images, this prevents it.
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
|
|
36
.github/workflows/ci.yaml
vendored
36
.github/workflows/ci.yaml
vendored
|
@ -16,20 +16,6 @@ env:
|
|||
RUSTUP_MAX_RETRIES: 10
|
||||
|
||||
jobs:
|
||||
# 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
|
||||
|
||||
# - run: cargo audit
|
||||
|
||||
rust:
|
||||
name: Rust
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
@ -39,7 +25,7 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
os: [ubuntu-latest, windows-latest] #, macos-latest]
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
|
@ -88,11 +74,14 @@ 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
|
||||
# Weird targets to catch non-portable code
|
||||
rust-cross:
|
||||
name: Rust Cross
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
targets: "powerpc-unknown-linux-gnu x86_64-unknown-linux-musl"
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
@ -103,7 +92,9 @@ jobs:
|
|||
toolchain: stable
|
||||
profile: minimal
|
||||
override: true
|
||||
target: 'powerpc-unknown-linux-gnu'
|
||||
|
||||
- name: Install Rust targets
|
||||
run: rustup target add ${{ env.targets }}
|
||||
|
||||
- name: Cache cargo directories
|
||||
uses: actions/cache@v2
|
||||
|
@ -114,14 +105,17 @@ jobs:
|
|||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
|
||||
- name: Check
|
||||
run: cargo check --target=powerpc-unknown-linux-gnu --all-targets
|
||||
run: |
|
||||
for target in ${{ env.targets }}; do
|
||||
cargo check --target=$target --all-targets
|
||||
done
|
||||
|
||||
typescript:
|
||||
name: TypeScript
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
|
|
763
Cargo.lock
generated
763
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -39,7 +39,7 @@ https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Frls-2.2E0
|
|||
|
||||
* Website: https://rust-analyzer.github.io/
|
||||
* Metrics: https://rust-analyzer.github.io/metrics/
|
||||
* API docs: https://rust-analyzer.github.io/rust-analyzer/ra_ide/
|
||||
* API docs: https://rust-analyzer.github.io/rust-analyzer/ide/
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
status = [
|
||||
"Rust (ubuntu-latest)",
|
||||
"Rust (windows-latest)",
|
||||
"Rust (macos-latest)",
|
||||
# "Rust (macos-latest)",
|
||||
"TypeScript (ubuntu-latest)",
|
||||
"TypeScript (windows-latest)",
|
||||
"TypeScript (macos-latest)",
|
||||
]
|
||||
delete_merged_branches = true
|
||||
|
|
9
crates/arena/Cargo.toml
Normal file
9
crates/arena/Cargo.toml
Normal file
|
@ -0,0 +1,9 @@
|
|||
[package]
|
||||
name = "arena"
|
||||
version = "0.0.0"
|
||||
license = "MIT OR Apache-2.0"
|
||||
authors = ["rust-analyzer developers"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
23
crates/assists/Cargo.toml
Normal file
23
crates/assists/Cargo.toml
Normal file
|
@ -0,0 +1,23 @@
|
|||
[package]
|
||||
name = "assists"
|
||||
version = "0.0.0"
|
||||
license = "MIT OR Apache-2.0"
|
||||
authors = ["rust-analyzer developers"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
rustc-hash = "1.1.0"
|
||||
itertools = "0.9.0"
|
||||
either = "1.5.3"
|
||||
|
||||
stdx = { path = "../stdx" }
|
||||
syntax = { path = "../syntax" }
|
||||
text_edit = { path = "../text_edit" }
|
||||
profile = { path = "../profile" }
|
||||
base_db = { path = "../base_db" }
|
||||
ide_db = { path = "../ide_db" }
|
||||
hir = { path = "../hir" }
|
||||
test_utils = { path = "../test_utils" }
|
291
crates/assists/src/assist_context.rs
Normal file
291
crates/assists/src/assist_context.rs
Normal file
|
@ -0,0 +1,291 @@
|
|||
//! See `AssistContext`
|
||||
|
||||
use std::mem;
|
||||
|
||||
use algo::find_covering_element;
|
||||
use base_db::{FileId, FileRange};
|
||||
use hir::Semantics;
|
||||
use ide_db::{
|
||||
source_change::{SourceChange, SourceFileEdit},
|
||||
RootDatabase,
|
||||
};
|
||||
use syntax::{
|
||||
algo::{self, find_node_at_offset, SyntaxRewriter},
|
||||
AstNode, SourceFile, SyntaxElement, SyntaxKind, SyntaxToken, TextRange, TextSize,
|
||||
TokenAtOffset,
|
||||
};
|
||||
use text_edit::{TextEdit, TextEditBuilder};
|
||||
|
||||
use crate::{
|
||||
assist_config::{AssistConfig, SnippetCap},
|
||||
Assist, AssistId, AssistKind, GroupLabel, ResolvedAssist,
|
||||
};
|
||||
|
||||
/// `AssistContext` allows to apply an assist or check if it could be applied.
|
||||
///
|
||||
/// Assists use a somewhat over-engineered approach, given the current needs.
|
||||
/// The assists workflow consists of two phases. In the first phase, a user asks
|
||||
/// for the list of available assists. In the second phase, the user picks a
|
||||
/// particular assist and it gets applied.
|
||||
///
|
||||
/// There are two peculiarities here:
|
||||
///
|
||||
/// * first, we ideally avoid computing more things then necessary to answer "is
|
||||
/// assist applicable" in the first phase.
|
||||
/// * second, when we are applying assist, we don't have a guarantee that there
|
||||
/// weren't any changes between the point when user asked for assists and when
|
||||
/// they applied a particular assist. So, when applying assist, we need to do
|
||||
/// all the checks from scratch.
|
||||
///
|
||||
/// To avoid repeating the same code twice for both "check" and "apply"
|
||||
/// functions, we use an approach reminiscent of that of Django's function based
|
||||
/// views dealing with forms. Each assist receives a runtime parameter,
|
||||
/// `resolve`. It first check if an edit is applicable (potentially computing
|
||||
/// info required to compute the actual edit). If it is applicable, and
|
||||
/// `resolve` is `true`, it then computes the actual edit.
|
||||
///
|
||||
/// So, to implement the original assists workflow, we can first apply each edit
|
||||
/// with `resolve = false`, and then applying the selected edit again, with
|
||||
/// `resolve = true` this time.
|
||||
///
|
||||
/// Note, however, that we don't actually use such two-phase logic at the
|
||||
/// moment, because the LSP API is pretty awkward in this place, and it's much
|
||||
/// easier to just compute the edit eagerly :-)
|
||||
pub(crate) struct AssistContext<'a> {
|
||||
pub(crate) config: &'a AssistConfig,
|
||||
pub(crate) sema: Semantics<'a, RootDatabase>,
|
||||
pub(crate) frange: FileRange,
|
||||
source_file: SourceFile,
|
||||
}
|
||||
|
||||
impl<'a> AssistContext<'a> {
|
||||
pub(crate) fn new(
|
||||
sema: Semantics<'a, RootDatabase>,
|
||||
config: &'a AssistConfig,
|
||||
frange: FileRange,
|
||||
) -> AssistContext<'a> {
|
||||
let source_file = sema.parse(frange.file_id);
|
||||
AssistContext { config, sema, frange, source_file }
|
||||
}
|
||||
|
||||
pub(crate) fn db(&self) -> &RootDatabase {
|
||||
self.sema.db
|
||||
}
|
||||
|
||||
pub(crate) fn source_file(&self) -> &SourceFile {
|
||||
&self.source_file
|
||||
}
|
||||
|
||||
// NB, this ignores active selection.
|
||||
pub(crate) fn offset(&self) -> TextSize {
|
||||
self.frange.range.start()
|
||||
}
|
||||
|
||||
pub(crate) fn token_at_offset(&self) -> TokenAtOffset<SyntaxToken> {
|
||||
self.source_file.syntax().token_at_offset(self.offset())
|
||||
}
|
||||
pub(crate) fn find_token_at_offset(&self, kind: SyntaxKind) -> Option<SyntaxToken> {
|
||||
self.token_at_offset().find(|it| it.kind() == kind)
|
||||
}
|
||||
pub(crate) fn find_node_at_offset<N: AstNode>(&self) -> Option<N> {
|
||||
find_node_at_offset(self.source_file.syntax(), self.offset())
|
||||
}
|
||||
pub(crate) fn find_node_at_offset_with_descend<N: AstNode>(&self) -> Option<N> {
|
||||
self.sema.find_node_at_offset_with_descend(self.source_file.syntax(), self.offset())
|
||||
}
|
||||
pub(crate) fn covering_element(&self) -> SyntaxElement {
|
||||
find_covering_element(self.source_file.syntax(), self.frange.range)
|
||||
}
|
||||
// FIXME: remove
|
||||
pub(crate) fn covering_node_for_range(&self, range: TextRange) -> SyntaxElement {
|
||||
find_covering_element(self.source_file.syntax(), range)
|
||||
}
|
||||
}
|
||||
|
||||
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(),
|
||||
allowed: ctx.config.allowed.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new_unresolved(ctx: &AssistContext) -> Assists {
|
||||
Assists {
|
||||
resolve: false,
|
||||
file: ctx.frange.file_id,
|
||||
buf: Vec::new(),
|
||||
allowed: ctx.config.allowed.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn finish_unresolved(self) -> Vec<Assist> {
|
||||
assert!(!self.resolve);
|
||||
self.finish()
|
||||
.into_iter()
|
||||
.map(|(label, edit)| {
|
||||
assert!(edit.is_none());
|
||||
label
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn finish_resolved(self) -> Vec<ResolvedAssist> {
|
||||
assert!(self.resolve);
|
||||
self.finish()
|
||||
.into_iter()
|
||||
.map(|(label, edit)| ResolvedAssist { assist: label, source_change: edit.unwrap() })
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn add(
|
||||
&mut self,
|
||||
id: AssistId,
|
||||
label: impl Into<String>,
|
||||
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,
|
||||
id: AssistId,
|
||||
label: impl Into<String>,
|
||||
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);
|
||||
f(&mut builder);
|
||||
Some(builder.finish())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
self.buf.push((label, source_change));
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn finish(mut self) -> Vec<(Assist, Option<SourceChange>)> {
|
||||
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,
|
||||
change: SourceChange,
|
||||
}
|
||||
|
||||
impl AssistBuilder {
|
||||
pub(crate) fn new(file_id: FileId) -> AssistBuilder {
|
||||
AssistBuilder {
|
||||
edit: TextEdit::builder(),
|
||||
file_id,
|
||||
is_snippet: false,
|
||||
change: SourceChange::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn edit_file(&mut self, file_id: FileId) {
|
||||
self.file_id = file_id;
|
||||
}
|
||||
|
||||
fn commit(&mut self) {
|
||||
let edit = mem::take(&mut self.edit).finish();
|
||||
if !edit.is_empty() {
|
||||
let new_edit = SourceFileEdit { file_id: self.file_id, 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove specified `range` of text.
|
||||
pub(crate) fn delete(&mut self, range: TextRange) {
|
||||
self.edit.delete(range)
|
||||
}
|
||||
/// Append specified `text` at the given `offset`
|
||||
pub(crate) fn insert(&mut self, offset: TextSize, text: impl Into<String>) {
|
||||
self.edit.insert(offset, text.into())
|
||||
}
|
||||
/// Append specified `snippet` at the given `offset`
|
||||
pub(crate) fn insert_snippet(
|
||||
&mut self,
|
||||
_cap: SnippetCap,
|
||||
offset: TextSize,
|
||||
snippet: impl Into<String>,
|
||||
) {
|
||||
self.is_snippet = true;
|
||||
self.insert(offset, snippet);
|
||||
}
|
||||
/// Replaces specified `range` of text with a given string.
|
||||
pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) {
|
||||
self.edit.replace(range, replace_with.into())
|
||||
}
|
||||
/// Replaces specified `range` of text with a given `snippet`.
|
||||
pub(crate) fn replace_snippet(
|
||||
&mut self,
|
||||
_cap: SnippetCap,
|
||||
range: TextRange,
|
||||
snippet: impl Into<String>,
|
||||
) {
|
||||
self.is_snippet = true;
|
||||
self.replace(range, snippet);
|
||||
}
|
||||
pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) {
|
||||
algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit)
|
||||
}
|
||||
pub(crate) fn rewrite(&mut self, rewriter: SyntaxRewriter) {
|
||||
let node = rewriter.rewrite_root().unwrap();
|
||||
let new = rewriter.rewrite(&node);
|
||||
algo::diff(&node, &new).into_text_edit(&mut self.edit);
|
||||
}
|
||||
|
||||
// FIXME: kill this API
|
||||
/// Get access to the raw `TextEditBuilder`.
|
||||
pub(crate) fn text_edit_builder(&mut self) -> &mut TextEditBuilder {
|
||||
&mut self.edit
|
||||
}
|
||||
|
||||
fn finish(mut self) -> SourceChange {
|
||||
self.commit();
|
||||
let mut change = mem::take(&mut self.change);
|
||||
if self.is_snippet {
|
||||
change.is_snippet = true;
|
||||
}
|
||||
change
|
||||
}
|
||||
}
|
206
crates/assists/src/ast_transform.rs
Normal file
206
crates/assists/src/ast_transform.rs
Normal file
|
@ -0,0 +1,206 @@
|
|||
//! `AstTransformer`s are functions that replace nodes in an AST and can be easily combined.
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use hir::{HirDisplay, PathResolution, SemanticsScope};
|
||||
use syntax::{
|
||||
algo::SyntaxRewriter,
|
||||
ast::{self, AstNode},
|
||||
};
|
||||
|
||||
pub trait AstTransform<'a> {
|
||||
fn get_substitution(&self, node: &syntax::SyntaxNode) -> Option<syntax::SyntaxNode>;
|
||||
|
||||
fn chain_before(self, other: Box<dyn AstTransform<'a> + 'a>) -> Box<dyn AstTransform<'a> + 'a>;
|
||||
fn or<T: AstTransform<'a> + 'a>(self, other: T) -> Box<dyn AstTransform<'a> + 'a>
|
||||
where
|
||||
Self: Sized + 'a,
|
||||
{
|
||||
self.chain_before(Box::new(other))
|
||||
}
|
||||
}
|
||||
|
||||
struct NullTransformer;
|
||||
|
||||
impl<'a> AstTransform<'a> for NullTransformer {
|
||||
fn get_substitution(&self, _node: &syntax::SyntaxNode) -> Option<syntax::SyntaxNode> {
|
||||
None
|
||||
}
|
||||
fn chain_before(self, other: Box<dyn AstTransform<'a> + 'a>) -> Box<dyn AstTransform<'a> + 'a> {
|
||||
other
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SubstituteTypeParams<'a> {
|
||||
source_scope: &'a SemanticsScope<'a>,
|
||||
substs: FxHashMap<hir::TypeParam, ast::Type>,
|
||||
previous: Box<dyn AstTransform<'a> + 'a>,
|
||||
}
|
||||
|
||||
impl<'a> SubstituteTypeParams<'a> {
|
||||
pub fn for_trait_impl(
|
||||
source_scope: &'a SemanticsScope<'a>,
|
||||
// FIXME: there's implicit invariant that `trait_` and `source_scope` match...
|
||||
trait_: hir::Trait,
|
||||
impl_def: ast::Impl,
|
||||
) -> SubstituteTypeParams<'a> {
|
||||
let substs = get_syntactic_substs(impl_def).unwrap_or_default();
|
||||
let generic_def: hir::GenericDef = trait_.into();
|
||||
let substs_by_param: FxHashMap<_, _> = generic_def
|
||||
.params(source_scope.db)
|
||||
.into_iter()
|
||||
// this is a trait impl, so we need to skip the first type parameter -- this is a bit hacky
|
||||
.skip(1)
|
||||
// The actual list of trait type parameters may be longer than the one
|
||||
// used in the `impl` block due to trailing default type parameters.
|
||||
// For that case we extend the `substs` with an empty iterator so we
|
||||
// can still hit those trailing values and check if they actually have
|
||||
// a default type. If they do, go for that type from `hir` to `ast` so
|
||||
// the resulting change can be applied correctly.
|
||||
.zip(substs.into_iter().map(Some).chain(std::iter::repeat(None)))
|
||||
.filter_map(|(k, v)| match v {
|
||||
Some(v) => Some((k, v)),
|
||||
None => {
|
||||
let default = k.default(source_scope.db)?;
|
||||
Some((
|
||||
k,
|
||||
ast::make::ty(
|
||||
&default
|
||||
.display_source_code(source_scope.db, source_scope.module()?.into())
|
||||
.ok()?,
|
||||
),
|
||||
))
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
return SubstituteTypeParams {
|
||||
source_scope,
|
||||
substs: substs_by_param,
|
||||
previous: Box::new(NullTransformer),
|
||||
};
|
||||
|
||||
// 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::Impl) -> Option<Vec<ast::Type>> {
|
||||
let target_trait = impl_def.trait_()?;
|
||||
let path_type = match target_trait {
|
||||
ast::Type::PathType(path) => path,
|
||||
_ => return None,
|
||||
};
|
||||
let generic_arg_list = path_type.path()?.segment()?.generic_arg_list()?;
|
||||
|
||||
let mut result = Vec::new();
|
||||
for generic_arg in generic_arg_list.generic_args() {
|
||||
match generic_arg {
|
||||
ast::GenericArg::TypeArg(type_arg) => result.push(type_arg.ty()?),
|
||||
ast::GenericArg::AssocTypeArg(_)
|
||||
| ast::GenericArg::LifetimeArg(_)
|
||||
| ast::GenericArg::ConstArg(_) => (),
|
||||
}
|
||||
}
|
||||
|
||||
Some(result)
|
||||
}
|
||||
}
|
||||
fn get_substitution_inner(&self, node: &syntax::SyntaxNode) -> Option<syntax::SyntaxNode> {
|
||||
let type_ref = ast::Type::cast(node.clone())?;
|
||||
let path = match &type_ref {
|
||||
ast::Type::PathType(path_type) => path_type.path()?,
|
||||
_ => return None,
|
||||
};
|
||||
// FIXME: use `hir::Path::from_src` instead.
|
||||
#[allow(deprecated)]
|
||||
let path = hir::Path::from_ast(path)?;
|
||||
let resolution = self.source_scope.resolve_hir_path(&path)?;
|
||||
match resolution {
|
||||
hir::PathResolution::TypeParam(tp) => Some(self.substs.get(&tp)?.syntax().clone()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AstTransform<'a> for SubstituteTypeParams<'a> {
|
||||
fn get_substitution(&self, node: &syntax::SyntaxNode) -> Option<syntax::SyntaxNode> {
|
||||
self.get_substitution_inner(node).or_else(|| self.previous.get_substitution(node))
|
||||
}
|
||||
fn chain_before(self, other: Box<dyn AstTransform<'a> + 'a>) -> Box<dyn AstTransform<'a> + 'a> {
|
||||
Box::new(SubstituteTypeParams { previous: other, ..self })
|
||||
}
|
||||
}
|
||||
|
||||
pub struct QualifyPaths<'a> {
|
||||
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>, source_scope: &'a SemanticsScope<'a>) -> Self {
|
||||
Self { target_scope, source_scope, previous: Box::new(NullTransformer) }
|
||||
}
|
||||
|
||||
fn get_substitution_inner(&self, node: &syntax::SyntaxNode) -> Option<syntax::SyntaxNode> {
|
||||
// FIXME handle value ns?
|
||||
let from = self.target_scope.module()?;
|
||||
let p = ast::Path::cast(node.clone())?;
|
||||
if p.segment().and_then(|s| s.param_list()).is_some() {
|
||||
// don't try to qualify `Fn(Foo) -> Bar` paths, they are in prelude anyway
|
||||
return None;
|
||||
}
|
||||
// FIXME: use `hir::Path::from_src` instead.
|
||||
#[allow(deprecated)]
|
||||
let hir_path = hir::Path::from_ast(p.clone());
|
||||
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.upcast(), def)?;
|
||||
let mut path = path_to_ast(found_path);
|
||||
|
||||
let type_args = p
|
||||
.segment()
|
||||
.and_then(|s| s.generic_arg_list())
|
||||
.map(|arg_list| apply(self, arg_list));
|
||||
if let Some(type_args) = type_args {
|
||||
let last_segment = path.segment().unwrap();
|
||||
path = path.with_segment(last_segment.with_type_args(type_args))
|
||||
}
|
||||
|
||||
Some(path.syntax().clone())
|
||||
}
|
||||
PathResolution::Local(_)
|
||||
| PathResolution::TypeParam(_)
|
||||
| PathResolution::SelfType(_) => None,
|
||||
PathResolution::Macro(_) => None,
|
||||
PathResolution::AssocItem(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: N) -> N {
|
||||
SyntaxRewriter::from_fn(|element| match element {
|
||||
syntax::SyntaxElement::Node(n) => {
|
||||
let replacement = transformer.get_substitution(&n)?;
|
||||
Some(replacement.into())
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.rewrite_ast(&node)
|
||||
}
|
||||
|
||||
impl<'a> AstTransform<'a> for QualifyPaths<'a> {
|
||||
fn get_substitution(&self, node: &syntax::SyntaxNode) -> Option<syntax::SyntaxNode> {
|
||||
self.get_substitution_inner(node).or_else(|| self.previous.get_substitution(node))
|
||||
}
|
||||
fn chain_before(self, other: Box<dyn AstTransform<'a> + 'a>) -> Box<dyn AstTransform<'a> + 'a> {
|
||||
Box::new(QualifyPaths { previous: other, ..self })
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn path_to_ast(path: hir::ModPath) -> ast::Path {
|
||||
let parse = ast::SourceFile::parse(&path.to_string());
|
||||
parse
|
||||
.tree()
|
||||
.syntax()
|
||||
.descendants()
|
||||
.find_map(ast::Path::cast)
|
||||
.unwrap_or_else(|| panic!("failed to parse path {:?}, `{}`", path, path))
|
||||
}
|
208
crates/assists/src/handlers/add_custom_impl.rs
Normal file
208
crates/assists/src/handlers/add_custom_impl.rs
Normal file
|
@ -0,0 +1,208 @@
|
|||
use itertools::Itertools;
|
||||
use syntax::{
|
||||
ast::{self, AstNode},
|
||||
Direction, SmolStr,
|
||||
SyntaxKind::{IDENT, WHITESPACE},
|
||||
TextRange, TextSize,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
assist_context::{AssistContext, Assists},
|
||||
AssistId, AssistKind,
|
||||
};
|
||||
|
||||
// Assist: add_custom_impl
|
||||
//
|
||||
// Adds impl block for derived trait.
|
||||
//
|
||||
// ```
|
||||
// #[derive(Deb<|>ug, Display)]
|
||||
// struct S;
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// #[derive(Display)]
|
||||
// struct S;
|
||||
//
|
||||
// impl Debug for S {
|
||||
// $0
|
||||
// }
|
||||
// ```
|
||||
pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
let attr = ctx.find_node_at_offset::<ast::Attr>()?;
|
||||
let input = attr.token_tree()?;
|
||||
|
||||
let attr_name = attr
|
||||
.syntax()
|
||||
.descendants_with_tokens()
|
||||
.filter(|t| t.kind() == IDENT)
|
||||
.find_map(|i| i.into_token())
|
||||
.filter(|t| *t.text() == "derive")?
|
||||
.text()
|
||||
.clone();
|
||||
|
||||
let trait_token =
|
||||
ctx.token_at_offset().find(|t| t.kind() == IDENT && *t.text() != attr_name)?;
|
||||
|
||||
let annotated = attr.syntax().siblings(Direction::Next).find_map(ast::Name::cast)?;
|
||||
let annotated_name = annotated.syntax().text().to_string();
|
||||
let start_offset = annotated.syntax().parent()?.text_range().end();
|
||||
|
||||
let label =
|
||||
format!("Add custom impl `{}` for `{}`", trait_token.text().as_str(), annotated_name);
|
||||
|
||||
let target = attr.syntax().text_range();
|
||||
acc.add(AssistId("add_custom_impl", AssistKind::Refactor), label, target, |builder| {
|
||||
let new_attr_input = input
|
||||
.syntax()
|
||||
.descendants_with_tokens()
|
||||
.filter(|t| t.kind() == IDENT)
|
||||
.filter_map(|t| t.into_token().map(|t| t.text().clone()))
|
||||
.filter(|t| t != trait_token.text())
|
||||
.collect::<Vec<SmolStr>>();
|
||||
let has_more_derives = !new_attr_input.is_empty();
|
||||
|
||||
if has_more_derives {
|
||||
let new_attr_input = format!("({})", new_attr_input.iter().format(", "));
|
||||
builder.replace(input.syntax().text_range(), new_attr_input);
|
||||
} else {
|
||||
let attr_range = attr.syntax().text_range();
|
||||
builder.delete(attr_range);
|
||||
|
||||
let line_break_range = attr
|
||||
.syntax()
|
||||
.next_sibling_or_token()
|
||||
.filter(|t| t.kind() == WHITESPACE)
|
||||
.map(|t| t.text_range())
|
||||
.unwrap_or_else(|| TextRange::new(TextSize::from(0), TextSize::from(0)));
|
||||
builder.delete(line_break_range);
|
||||
}
|
||||
|
||||
match ctx.config.snippet_cap {
|
||||
Some(cap) => {
|
||||
builder.insert_snippet(
|
||||
cap,
|
||||
start_offset,
|
||||
format!("\n\nimpl {} for {} {{\n $0\n}}", trait_token, annotated_name),
|
||||
);
|
||||
}
|
||||
None => {
|
||||
builder.insert(
|
||||
start_offset,
|
||||
format!("\n\nimpl {} for {} {{\n\n}}", trait_token, annotated_name),
|
||||
);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests::{check_assist, check_assist_not_applicable};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn add_custom_impl_for_unique_input() {
|
||||
check_assist(
|
||||
add_custom_impl,
|
||||
"
|
||||
#[derive(Debu<|>g)]
|
||||
struct Foo {
|
||||
bar: String,
|
||||
}
|
||||
",
|
||||
"
|
||||
struct Foo {
|
||||
bar: String,
|
||||
}
|
||||
|
||||
impl Debug for Foo {
|
||||
$0
|
||||
}
|
||||
",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_custom_impl_for_with_visibility_modifier() {
|
||||
check_assist(
|
||||
add_custom_impl,
|
||||
"
|
||||
#[derive(Debug<|>)]
|
||||
pub struct Foo {
|
||||
bar: String,
|
||||
}
|
||||
",
|
||||
"
|
||||
pub struct Foo {
|
||||
bar: String,
|
||||
}
|
||||
|
||||
impl Debug for Foo {
|
||||
$0
|
||||
}
|
||||
",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_custom_impl_when_multiple_inputs() {
|
||||
check_assist(
|
||||
add_custom_impl,
|
||||
"
|
||||
#[derive(Display, Debug<|>, Serialize)]
|
||||
struct Foo {}
|
||||
",
|
||||
"
|
||||
#[derive(Display, Serialize)]
|
||||
struct Foo {}
|
||||
|
||||
impl Debug for Foo {
|
||||
$0
|
||||
}
|
||||
",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ignore_derive_macro_without_input() {
|
||||
check_assist_not_applicable(
|
||||
add_custom_impl,
|
||||
"
|
||||
#[derive(<|>)]
|
||||
struct Foo {}
|
||||
",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ignore_if_cursor_on_param() {
|
||||
check_assist_not_applicable(
|
||||
add_custom_impl,
|
||||
"
|
||||
#[derive<|>(Debug)]
|
||||
struct Foo {}
|
||||
",
|
||||
);
|
||||
|
||||
check_assist_not_applicable(
|
||||
add_custom_impl,
|
||||
"
|
||||
#[derive(Debug)<|>]
|
||||
struct Foo {}
|
||||
",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ignore_if_not_derive() {
|
||||
check_assist_not_applicable(
|
||||
add_custom_impl,
|
||||
"
|
||||
#[allow(non_camel_<|>case_types)]
|
||||
struct Foo {}
|
||||
",
|
||||
)
|
||||
}
|
||||
}
|
211
crates/assists/src/handlers/add_explicit_type.rs
Normal file
211
crates/assists/src/handlers/add_explicit_type.rs
Normal file
|
@ -0,0 +1,211 @@
|
|||
use hir::HirDisplay;
|
||||
use syntax::{
|
||||
ast::{self, AstNode, LetStmt, NameOwner},
|
||||
TextRange,
|
||||
};
|
||||
|
||||
use crate::{AssistContext, AssistId, AssistKind, Assists};
|
||||
|
||||
// Assist: add_explicit_type
|
||||
//
|
||||
// Specify type for a let binding.
|
||||
//
|
||||
// ```
|
||||
// fn main() {
|
||||
// let x<|> = 92;
|
||||
// }
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// fn main() {
|
||||
// let x: i32 = 92;
|
||||
// }
|
||||
// ```
|
||||
pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
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 let_stmt.pat()? {
|
||||
ast::Pat::IdentPat(bind_pat) => bind_pat,
|
||||
_ => return None,
|
||||
};
|
||||
let pat_range = pat.syntax().text_range();
|
||||
// The binding must have a name
|
||||
let name = pat.name()?;
|
||||
let name_range = name.syntax().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);
|
||||
if !cursor_in_range {
|
||||
return None;
|
||||
}
|
||||
// Assist not applicable if the type has already been specified
|
||||
// and it has no placeholders
|
||||
let ascribed_ty = let_stmt.ty();
|
||||
if let Some(ty) = &ascribed_ty {
|
||||
if ty.syntax().descendants().find_map(ast::InferType::cast).is_none() {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
// Infer type
|
||||
let ty = ctx.sema.type_of_expr(&expr)?;
|
||||
|
||||
if ty.contains_unknown() || ty.is_closure() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let inferred_type = ty.display_source_code(ctx.db(), module.into()).ok()?;
|
||||
acc.add(
|
||||
AssistId("add_explicit_type", AssistKind::RefactorRewrite),
|
||||
format!("Insert explicit type `{}`", inferred_type),
|
||||
pat_range,
|
||||
|builder| match ascribed_ty {
|
||||
Some(ascribed_ty) => {
|
||||
builder.replace(ascribed_ty.syntax().text_range(), inferred_type);
|
||||
}
|
||||
None => {
|
||||
builder.insert(name_range.end(), format!(": {}", inferred_type));
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
|
||||
|
||||
#[test]
|
||||
fn add_explicit_type_target() {
|
||||
check_assist_target(add_explicit_type, "fn f() { let a<|> = 1; }", "a");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_explicit_type_works_for_simple_expr() {
|
||||
check_assist(add_explicit_type, "fn f() { let a<|> = 1; }", "fn f() { let a: i32 = 1; }");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_explicit_type_works_for_underscore() {
|
||||
check_assist(
|
||||
add_explicit_type,
|
||||
"fn f() { let a<|>: _ = 1; }",
|
||||
"fn f() { let a: i32 = 1; }",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_explicit_type_works_for_nested_underscore() {
|
||||
check_assist(
|
||||
add_explicit_type,
|
||||
r#"
|
||||
enum Option<T> {
|
||||
Some(T),
|
||||
None
|
||||
}
|
||||
|
||||
fn f() {
|
||||
let a<|>: Option<_> = Option::Some(1);
|
||||
}"#,
|
||||
r#"
|
||||
enum Option<T> {
|
||||
Some(T),
|
||||
None
|
||||
}
|
||||
|
||||
fn f() {
|
||||
let a: Option<i32> = Option::Some(1);
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_explicit_type_works_for_macro_call() {
|
||||
check_assist(
|
||||
add_explicit_type,
|
||||
r"macro_rules! v { () => {0u64} } fn f() { let a<|> = v!(); }",
|
||||
r"macro_rules! v { () => {0u64} } fn f() { let a: u64 = v!(); }",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_explicit_type_works_for_macro_call_recursive() {
|
||||
check_assist(
|
||||
add_explicit_type,
|
||||
r#"macro_rules! u { () => {0u64} } macro_rules! v { () => {u!()} } fn f() { let a<|> = v!(); }"#,
|
||||
r#"macro_rules! u { () => {0u64} } macro_rules! v { () => {u!()} } fn f() { let a: u64 = v!(); }"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_explicit_type_not_applicable_if_ty_not_inferred() {
|
||||
check_assist_not_applicable(add_explicit_type, "fn f() { let a<|> = None; }");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_explicit_type_not_applicable_if_ty_already_specified() {
|
||||
check_assist_not_applicable(add_explicit_type, "fn f() { let a<|>: i32 = 1; }");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_explicit_type_not_applicable_if_specified_ty_is_tuple() {
|
||||
check_assist_not_applicable(add_explicit_type, "fn f() { let a<|>: (i32, i32) = (3, 4); }");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_explicit_type_not_applicable_if_cursor_after_equals() {
|
||||
check_assist_not_applicable(
|
||||
add_explicit_type,
|
||||
"fn f() {let a =<|> match 1 {2 => 3, 3 => 5};}",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_explicit_type_not_applicable_if_cursor_before_let() {
|
||||
check_assist_not_applicable(
|
||||
add_explicit_type,
|
||||
"fn f() <|>{let a = match 1 {2 => 3, 3 => 5};}",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closure_parameters_are_not_added() {
|
||||
check_assist_not_applicable(
|
||||
add_explicit_type,
|
||||
r#"
|
||||
fn main() {
|
||||
let multiply_by_two<|> = |i| i * 3;
|
||||
let six = multiply_by_two(2);
|
||||
}"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_generics_should_not_be_added() {
|
||||
check_assist(
|
||||
add_explicit_type,
|
||||
r#"
|
||||
struct Test<K, T = u8> {
|
||||
k: K,
|
||||
t: T,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let test<|> = Test { t: 23u8, k: 33 };
|
||||
}"#,
|
||||
r#"
|
||||
struct Test<K, T = u8> {
|
||||
k: K,
|
||||
t: T,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let test: Test<i32> = Test { t: 23u8, k: 33 };
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
}
|
711
crates/assists/src/handlers/add_missing_impl_members.rs
Normal file
711
crates/assists/src/handlers/add_missing_impl_members.rs
Normal file
|
@ -0,0 +1,711 @@
|
|||
use hir::HasSource;
|
||||
use syntax::{
|
||||
ast::{
|
||||
self,
|
||||
edit::{self, AstNodeEdit, IndentLevel},
|
||||
make, AstNode, NameOwner,
|
||||
},
|
||||
SmolStr,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
assist_context::{AssistContext, Assists},
|
||||
ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams},
|
||||
utils::{get_missing_assoc_items, render_snippet, resolve_target_trait, Cursor},
|
||||
AssistId, AssistKind,
|
||||
};
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum AddMissingImplMembersMode {
|
||||
DefaultMethodsOnly,
|
||||
NoDefaultMethods,
|
||||
}
|
||||
|
||||
// Assist: add_impl_missing_members
|
||||
//
|
||||
// Adds scaffold for required impl members.
|
||||
//
|
||||
// ```
|
||||
// trait Trait<T> {
|
||||
// Type X;
|
||||
// fn foo(&self) -> T;
|
||||
// fn bar(&self) {}
|
||||
// }
|
||||
//
|
||||
// impl Trait<u32> for () {<|>
|
||||
//
|
||||
// }
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// trait Trait<T> {
|
||||
// Type X;
|
||||
// fn foo(&self) -> T;
|
||||
// fn bar(&self) {}
|
||||
// }
|
||||
//
|
||||
// impl Trait<u32> for () {
|
||||
// fn foo(&self) -> u32 {
|
||||
// ${0:todo!()}
|
||||
// }
|
||||
//
|
||||
// }
|
||||
// ```
|
||||
pub(crate) fn add_missing_impl_members(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
add_missing_impl_members_inner(
|
||||
acc,
|
||||
ctx,
|
||||
AddMissingImplMembersMode::NoDefaultMethods,
|
||||
"add_impl_missing_members",
|
||||
"Implement missing members",
|
||||
)
|
||||
}
|
||||
|
||||
// Assist: add_impl_default_members
|
||||
//
|
||||
// Adds scaffold for overriding default impl members.
|
||||
//
|
||||
// ```
|
||||
// trait Trait {
|
||||
// Type X;
|
||||
// fn foo(&self);
|
||||
// fn bar(&self) {}
|
||||
// }
|
||||
//
|
||||
// impl Trait for () {
|
||||
// Type X = ();
|
||||
// fn foo(&self) {}<|>
|
||||
//
|
||||
// }
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// trait Trait {
|
||||
// Type X;
|
||||
// fn foo(&self);
|
||||
// fn bar(&self) {}
|
||||
// }
|
||||
//
|
||||
// impl Trait for () {
|
||||
// Type X = ();
|
||||
// fn foo(&self) {}
|
||||
// $0fn bar(&self) {}
|
||||
//
|
||||
// }
|
||||
// ```
|
||||
pub(crate) fn add_missing_default_members(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
add_missing_impl_members_inner(
|
||||
acc,
|
||||
ctx,
|
||||
AddMissingImplMembersMode::DefaultMethodsOnly,
|
||||
"add_impl_default_members",
|
||||
"Implement default members",
|
||||
)
|
||||
}
|
||||
|
||||
fn add_missing_impl_members_inner(
|
||||
acc: &mut Assists,
|
||||
ctx: &AssistContext,
|
||||
mode: AddMissingImplMembersMode,
|
||||
assist_id: &'static str,
|
||||
label: &'static str,
|
||||
) -> Option<()> {
|
||||
let _p = profile::span("add_missing_impl_members_inner");
|
||||
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::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())
|
||||
};
|
||||
|
||||
let missing_items = get_missing_assoc_items(&ctx.sema, &impl_def)
|
||||
.iter()
|
||||
.map(|i| match i {
|
||||
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::Fn(def) => match mode {
|
||||
AddMissingImplMembersMode::DefaultMethodsOnly => def.body().is_some(),
|
||||
AddMissingImplMembersMode::NoDefaultMethods => def.body().is_none(),
|
||||
},
|
||||
_ => mode == AddMissingImplMembersMode::NoDefaultMethods,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if missing_items.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let target = impl_def.syntax().text_range();
|
||||
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());
|
||||
let ast_transform = QualifyPaths::new(&target_scope, &source_scope)
|
||||
.or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def));
|
||||
let items = missing_items
|
||||
.into_iter()
|
||||
.map(|it| ast_transform::apply(&*ast_transform, it))
|
||||
.map(|it| match it {
|
||||
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));
|
||||
let new_impl_item_list = impl_item_list.append_items(items);
|
||||
let first_new_item = new_impl_item_list.assoc_items().nth(n_existing_items).unwrap();
|
||||
|
||||
let original_range = impl_item_list.syntax().text_range();
|
||||
match ctx.config.snippet_cap {
|
||||
None => builder.replace(original_range, new_impl_item_list.to_string()),
|
||||
Some(cap) => {
|
||||
let mut cursor = Cursor::Before(first_new_item.syntax());
|
||||
let placeholder;
|
||||
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;
|
||||
cursor = Cursor::Replace(placeholder.syntax());
|
||||
}
|
||||
}
|
||||
}
|
||||
builder.replace_snippet(
|
||||
cap,
|
||||
original_range,
|
||||
render_snippet(cap, new_impl_item_list.syntax(), cursor),
|
||||
)
|
||||
}
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
fn add_body(fn_def: ast::Fn) -> ast::Fn {
|
||||
if fn_def.body().is_some() {
|
||||
return fn_def;
|
||||
}
|
||||
let body = make::block_expr(None, Some(make::expr_todo())).indent(IndentLevel(1));
|
||||
fn_def.with_body(body)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests::{check_assist, check_assist_not_applicable};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_add_missing_impl_members() {
|
||||
check_assist(
|
||||
add_missing_impl_members,
|
||||
r#"
|
||||
trait Foo {
|
||||
type Output;
|
||||
|
||||
const CONST: usize = 42;
|
||||
|
||||
fn foo(&self);
|
||||
fn bar(&self);
|
||||
fn baz(&self);
|
||||
}
|
||||
|
||||
struct S;
|
||||
|
||||
impl Foo for S {
|
||||
fn bar(&self) {}
|
||||
<|>
|
||||
}"#,
|
||||
r#"
|
||||
trait Foo {
|
||||
type Output;
|
||||
|
||||
const CONST: usize = 42;
|
||||
|
||||
fn foo(&self);
|
||||
fn bar(&self);
|
||||
fn baz(&self);
|
||||
}
|
||||
|
||||
struct S;
|
||||
|
||||
impl Foo for S {
|
||||
fn bar(&self) {}
|
||||
$0type Output;
|
||||
const CONST: usize = 42;
|
||||
fn foo(&self) {
|
||||
todo!()
|
||||
}
|
||||
fn baz(&self) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_copied_overriden_members() {
|
||||
check_assist(
|
||||
add_missing_impl_members,
|
||||
r#"
|
||||
trait Foo {
|
||||
fn foo(&self);
|
||||
fn bar(&self) -> bool { true }
|
||||
fn baz(&self) -> u32 { 42 }
|
||||
}
|
||||
|
||||
struct S;
|
||||
|
||||
impl Foo for S {
|
||||
fn bar(&self) {}
|
||||
<|>
|
||||
}"#,
|
||||
r#"
|
||||
trait Foo {
|
||||
fn foo(&self);
|
||||
fn bar(&self) -> bool { true }
|
||||
fn baz(&self) -> u32 { 42 }
|
||||
}
|
||||
|
||||
struct S;
|
||||
|
||||
impl Foo for S {
|
||||
fn bar(&self) {}
|
||||
fn foo(&self) {
|
||||
${0:todo!()}
|
||||
}
|
||||
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_impl_def() {
|
||||
check_assist(
|
||||
add_missing_impl_members,
|
||||
r#"
|
||||
trait Foo { fn foo(&self); }
|
||||
struct S;
|
||||
impl Foo for S { <|> }"#,
|
||||
r#"
|
||||
trait Foo { fn foo(&self); }
|
||||
struct S;
|
||||
impl Foo for S {
|
||||
fn foo(&self) {
|
||||
${0:todo!()}
|
||||
}
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fill_in_type_params_1() {
|
||||
check_assist(
|
||||
add_missing_impl_members,
|
||||
r#"
|
||||
trait Foo<T> { fn foo(&self, t: T) -> &T; }
|
||||
struct S;
|
||||
impl Foo<u32> for S { <|> }"#,
|
||||
r#"
|
||||
trait Foo<T> { fn foo(&self, t: T) -> &T; }
|
||||
struct S;
|
||||
impl Foo<u32> for S {
|
||||
fn foo(&self, t: u32) -> &u32 {
|
||||
${0:todo!()}
|
||||
}
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fill_in_type_params_2() {
|
||||
check_assist(
|
||||
add_missing_impl_members,
|
||||
r#"
|
||||
trait Foo<T> { fn foo(&self, t: T) -> &T; }
|
||||
struct S;
|
||||
impl<U> Foo<U> for S { <|> }"#,
|
||||
r#"
|
||||
trait Foo<T> { fn foo(&self, t: T) -> &T; }
|
||||
struct S;
|
||||
impl<U> Foo<U> for S {
|
||||
fn foo(&self, t: U) -> &U {
|
||||
${0:todo!()}
|
||||
}
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cursor_after_empty_impl_def() {
|
||||
check_assist(
|
||||
add_missing_impl_members,
|
||||
r#"
|
||||
trait Foo { fn foo(&self); }
|
||||
struct S;
|
||||
impl Foo for S {}<|>"#,
|
||||
r#"
|
||||
trait Foo { fn foo(&self); }
|
||||
struct S;
|
||||
impl Foo for S {
|
||||
fn foo(&self) {
|
||||
${0:todo!()}
|
||||
}
|
||||
}"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_qualify_path_1() {
|
||||
check_assist(
|
||||
add_missing_impl_members,
|
||||
r#"
|
||||
mod foo {
|
||||
pub struct Bar;
|
||||
trait Foo { fn foo(&self, bar: Bar); }
|
||||
}
|
||||
struct S;
|
||||
impl foo::Foo for S { <|> }"#,
|
||||
r#"
|
||||
mod foo {
|
||||
pub struct Bar;
|
||||
trait Foo { fn foo(&self, bar: Bar); }
|
||||
}
|
||||
struct S;
|
||||
impl foo::Foo for S {
|
||||
fn foo(&self, bar: foo::Bar) {
|
||||
${0:todo!()}
|
||||
}
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_qualify_path_generic() {
|
||||
check_assist(
|
||||
add_missing_impl_members,
|
||||
r#"
|
||||
mod foo {
|
||||
pub struct Bar<T>;
|
||||
trait Foo { fn foo(&self, bar: Bar<u32>); }
|
||||
}
|
||||
struct S;
|
||||
impl foo::Foo for S { <|> }"#,
|
||||
r#"
|
||||
mod foo {
|
||||
pub struct Bar<T>;
|
||||
trait Foo { fn foo(&self, bar: Bar<u32>); }
|
||||
}
|
||||
struct S;
|
||||
impl foo::Foo for S {
|
||||
fn foo(&self, bar: foo::Bar<u32>) {
|
||||
${0:todo!()}
|
||||
}
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_qualify_path_and_substitute_param() {
|
||||
check_assist(
|
||||
add_missing_impl_members,
|
||||
r#"
|
||||
mod foo {
|
||||
pub struct Bar<T>;
|
||||
trait Foo<T> { fn foo(&self, bar: Bar<T>); }
|
||||
}
|
||||
struct S;
|
||||
impl foo::Foo<u32> for S { <|> }"#,
|
||||
r#"
|
||||
mod foo {
|
||||
pub struct Bar<T>;
|
||||
trait Foo<T> { fn foo(&self, bar: Bar<T>); }
|
||||
}
|
||||
struct S;
|
||||
impl foo::Foo<u32> for S {
|
||||
fn foo(&self, bar: foo::Bar<u32>) {
|
||||
${0:todo!()}
|
||||
}
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_substitute_param_no_qualify() {
|
||||
// when substituting params, the substituted param should not be qualified!
|
||||
check_assist(
|
||||
add_missing_impl_members,
|
||||
r#"
|
||||
mod foo {
|
||||
trait Foo<T> { fn foo(&self, bar: T); }
|
||||
pub struct Param;
|
||||
}
|
||||
struct Param;
|
||||
struct S;
|
||||
impl foo::Foo<Param> for S { <|> }"#,
|
||||
r#"
|
||||
mod foo {
|
||||
trait Foo<T> { fn foo(&self, bar: T); }
|
||||
pub struct Param;
|
||||
}
|
||||
struct Param;
|
||||
struct S;
|
||||
impl foo::Foo<Param> for S {
|
||||
fn foo(&self, bar: Param) {
|
||||
${0:todo!()}
|
||||
}
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_qualify_path_associated_item() {
|
||||
check_assist(
|
||||
add_missing_impl_members,
|
||||
r#"
|
||||
mod foo {
|
||||
pub struct Bar<T>;
|
||||
impl Bar<T> { type Assoc = u32; }
|
||||
trait Foo { fn foo(&self, bar: Bar<u32>::Assoc); }
|
||||
}
|
||||
struct S;
|
||||
impl foo::Foo for S { <|> }"#,
|
||||
r#"
|
||||
mod foo {
|
||||
pub struct Bar<T>;
|
||||
impl Bar<T> { type Assoc = u32; }
|
||||
trait Foo { fn foo(&self, bar: Bar<u32>::Assoc); }
|
||||
}
|
||||
struct S;
|
||||
impl foo::Foo for S {
|
||||
fn foo(&self, bar: foo::Bar<u32>::Assoc) {
|
||||
${0:todo!()}
|
||||
}
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_qualify_path_nested() {
|
||||
check_assist(
|
||||
add_missing_impl_members,
|
||||
r#"
|
||||
mod foo {
|
||||
pub struct Bar<T>;
|
||||
pub struct Baz;
|
||||
trait Foo { fn foo(&self, bar: Bar<Baz>); }
|
||||
}
|
||||
struct S;
|
||||
impl foo::Foo for S { <|> }"#,
|
||||
r#"
|
||||
mod foo {
|
||||
pub struct Bar<T>;
|
||||
pub struct Baz;
|
||||
trait Foo { fn foo(&self, bar: Bar<Baz>); }
|
||||
}
|
||||
struct S;
|
||||
impl foo::Foo for S {
|
||||
fn foo(&self, bar: foo::Bar<foo::Baz>) {
|
||||
${0:todo!()}
|
||||
}
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_qualify_path_fn_trait_notation() {
|
||||
check_assist(
|
||||
add_missing_impl_members,
|
||||
r#"
|
||||
mod foo {
|
||||
pub trait Fn<Args> { type Output; }
|
||||
trait Foo { fn foo(&self, bar: dyn Fn(u32) -> i32); }
|
||||
}
|
||||
struct S;
|
||||
impl foo::Foo for S { <|> }"#,
|
||||
r#"
|
||||
mod foo {
|
||||
pub trait Fn<Args> { type Output; }
|
||||
trait Foo { fn foo(&self, bar: dyn Fn(u32) -> i32); }
|
||||
}
|
||||
struct S;
|
||||
impl foo::Foo for S {
|
||||
fn foo(&self, bar: dyn Fn(u32) -> i32) {
|
||||
${0:todo!()}
|
||||
}
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_trait() {
|
||||
check_assist_not_applicable(
|
||||
add_missing_impl_members,
|
||||
r#"
|
||||
trait Foo;
|
||||
struct S;
|
||||
impl Foo for S { <|> }"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ignore_unnamed_trait_members_and_default_methods() {
|
||||
check_assist_not_applicable(
|
||||
add_missing_impl_members,
|
||||
r#"
|
||||
trait Foo {
|
||||
fn (arg: u32);
|
||||
fn valid(some: u32) -> bool { false }
|
||||
}
|
||||
struct S;
|
||||
impl Foo for S { <|> }"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_docstring_and_attrs() {
|
||||
check_assist(
|
||||
add_missing_impl_members,
|
||||
r#"
|
||||
#[doc(alias = "test alias")]
|
||||
trait Foo {
|
||||
/// doc string
|
||||
type Output;
|
||||
|
||||
#[must_use]
|
||||
fn foo(&self);
|
||||
}
|
||||
struct S;
|
||||
impl Foo for S {}<|>"#,
|
||||
r#"
|
||||
#[doc(alias = "test alias")]
|
||||
trait Foo {
|
||||
/// doc string
|
||||
type Output;
|
||||
|
||||
#[must_use]
|
||||
fn foo(&self);
|
||||
}
|
||||
struct S;
|
||||
impl Foo for S {
|
||||
$0type Output;
|
||||
fn foo(&self) {
|
||||
todo!()
|
||||
}
|
||||
}"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_methods() {
|
||||
check_assist(
|
||||
add_missing_default_members,
|
||||
r#"
|
||||
trait Foo {
|
||||
type Output;
|
||||
|
||||
const CONST: usize = 42;
|
||||
|
||||
fn valid(some: u32) -> bool { false }
|
||||
fn foo(some: u32) -> bool;
|
||||
}
|
||||
struct S;
|
||||
impl Foo for S { <|> }"#,
|
||||
r#"
|
||||
trait Foo {
|
||||
type Output;
|
||||
|
||||
const CONST: usize = 42;
|
||||
|
||||
fn valid(some: u32) -> bool { false }
|
||||
fn foo(some: u32) -> bool;
|
||||
}
|
||||
struct S;
|
||||
impl Foo for S {
|
||||
$0fn valid(some: u32) -> bool { false }
|
||||
}"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generic_single_default_parameter() {
|
||||
check_assist(
|
||||
add_missing_impl_members,
|
||||
r#"
|
||||
trait Foo<T = Self> {
|
||||
fn bar(&self, other: &T);
|
||||
}
|
||||
|
||||
struct S;
|
||||
impl Foo for S { <|> }"#,
|
||||
r#"
|
||||
trait Foo<T = Self> {
|
||||
fn bar(&self, other: &T);
|
||||
}
|
||||
|
||||
struct S;
|
||||
impl Foo for S {
|
||||
fn bar(&self, other: &Self) {
|
||||
${0:todo!()}
|
||||
}
|
||||
}"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generic_default_parameter_is_second() {
|
||||
check_assist(
|
||||
add_missing_impl_members,
|
||||
r#"
|
||||
trait Foo<T1, T2 = Self> {
|
||||
fn bar(&self, this: &T1, that: &T2);
|
||||
}
|
||||
|
||||
struct S<T>;
|
||||
impl Foo<T> for S<T> { <|> }"#,
|
||||
r#"
|
||||
trait Foo<T1, T2 = Self> {
|
||||
fn bar(&self, this: &T1, that: &T2);
|
||||
}
|
||||
|
||||
struct S<T>;
|
||||
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;
|
||||
}"#,
|
||||
)
|
||||
}
|
||||
}
|
164
crates/assists/src/handlers/add_turbo_fish.rs
Normal file
164
crates/assists/src/handlers/add_turbo_fish.rs
Normal file
|
@ -0,0 +1,164 @@
|
|||
use ide_db::defs::{classify_name_ref, Definition, NameRefClass};
|
||||
use syntax::{ast, AstNode, SyntaxKind, T};
|
||||
use test_utils::mark;
|
||||
|
||||
use crate::{
|
||||
assist_context::{AssistContext, Assists},
|
||||
AssistId, AssistKind,
|
||||
};
|
||||
|
||||
// Assist: add_turbo_fish
|
||||
//
|
||||
// Adds `::<_>` to a call of a generic method or function.
|
||||
//
|
||||
// ```
|
||||
// fn make<T>() -> T { todo!() }
|
||||
// fn main() {
|
||||
// let x = make<|>();
|
||||
// }
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// fn make<T>() -> T { todo!() }
|
||||
// fn main() {
|
||||
// let x = make::<${0:_}>();
|
||||
// }
|
||||
// ```
|
||||
pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
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);
|
||||
return None;
|
||||
}
|
||||
let name_ref = ast::NameRef::cast(ident.parent())?;
|
||||
let def = match classify_name_ref(&ctx.sema, &name_ref)? {
|
||||
NameRefClass::Definition(def) => def,
|
||||
NameRefClass::ExternCrate(_) | NameRefClass::FieldShorthand { .. } => return None,
|
||||
};
|
||||
let fun = match def {
|
||||
Definition::ModuleDef(hir::ModuleDef::Function(it)) => it,
|
||||
_ => return None,
|
||||
};
|
||||
let generics = hir::GenericDef::Function(fun).params(ctx.sema.db);
|
||||
if generics.is_empty() {
|
||||
mark::hit!(add_turbo_fish_non_generic);
|
||||
return None;
|
||||
}
|
||||
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)]
|
||||
mod tests {
|
||||
use crate::tests::{check_assist, check_assist_not_applicable};
|
||||
|
||||
use super::*;
|
||||
use test_utils::mark;
|
||||
|
||||
#[test]
|
||||
fn add_turbo_fish_function() {
|
||||
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_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(
|
||||
add_turbo_fish,
|
||||
r#"
|
||||
struct S;
|
||||
impl S {
|
||||
fn make<T>(&self) -> T {}
|
||||
}
|
||||
fn main() {
|
||||
S.make<|>();
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
struct S;
|
||||
impl S {
|
||||
fn make<T>(&self) -> T {}
|
||||
}
|
||||
fn main() {
|
||||
S.make::<${0:_}>();
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_turbo_fish_one_fish_is_enough() {
|
||||
mark::check!(add_turbo_fish_one_fish_is_enough);
|
||||
check_assist_not_applicable(
|
||||
add_turbo_fish,
|
||||
r#"
|
||||
fn make<T>() -> T {}
|
||||
fn main() {
|
||||
make<|>::<()>();
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_turbo_fish_non_generic() {
|
||||
mark::check!(add_turbo_fish_non_generic);
|
||||
check_assist_not_applicable(
|
||||
add_turbo_fish,
|
||||
r#"
|
||||
fn make() -> () {}
|
||||
fn main() {
|
||||
make<|>();
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
}
|
93
crates/assists/src/handlers/apply_demorgan.rs
Normal file
93
crates/assists/src/handlers/apply_demorgan.rs
Normal file
|
@ -0,0 +1,93 @@
|
|||
use syntax::ast::{self, AstNode};
|
||||
|
||||
use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKind, Assists};
|
||||
|
||||
// Assist: apply_demorgan
|
||||
//
|
||||
// Apply https://en.wikipedia.org/wiki/De_Morgan%27s_laws[De Morgan's law].
|
||||
// This transforms expressions of the form `!l || !r` into `!(l && r)`.
|
||||
// This also works with `&&`. This assist can only be applied with the cursor
|
||||
// on either `||` or `&&`, with both operands being a negation of some kind.
|
||||
// This means something of the form `!x` or `x != y`.
|
||||
//
|
||||
// ```
|
||||
// fn main() {
|
||||
// if x != 4 ||<|> !y {}
|
||||
// }
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// fn main() {
|
||||
// if !(x == 4 && y) {}
|
||||
// }
|
||||
// ```
|
||||
pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
let expr = ctx.find_node_at_offset::<ast::BinExpr>()?;
|
||||
let op = expr.op_kind()?;
|
||||
let op_range = expr.op_token()?.text_range();
|
||||
let opposite_op = opposite_logic_op(op)?;
|
||||
let cursor_in_range = op_range.contains_range(ctx.frange.range);
|
||||
if !cursor_in_range {
|
||||
return None;
|
||||
}
|
||||
|
||||
let lhs = expr.lhs()?;
|
||||
let lhs_range = lhs.syntax().text_range();
|
||||
let not_lhs = invert_boolean_expression(lhs);
|
||||
|
||||
let rhs = expr.rhs()?;
|
||||
let rhs_range = rhs.syntax().text_range();
|
||||
let not_rhs = invert_boolean_expression(rhs);
|
||||
|
||||
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
|
||||
fn opposite_logic_op(kind: ast::BinOp) -> Option<&'static str> {
|
||||
match kind {
|
||||
ast::BinOp::BooleanOr => Some("&&"),
|
||||
ast::BinOp::BooleanAnd => Some("||"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::tests::{check_assist, check_assist_not_applicable};
|
||||
|
||||
#[test]
|
||||
fn demorgan_turns_and_into_or() {
|
||||
check_assist(apply_demorgan, "fn f() { !x &&<|> !x }", "fn f() { !(x || x) }")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn demorgan_turns_or_into_and() {
|
||||
check_assist(apply_demorgan, "fn f() { !x ||<|> !x }", "fn f() { !(x && x) }")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn demorgan_removes_inequality() {
|
||||
check_assist(apply_demorgan, "fn f() { x != x ||<|> !x }", "fn f() { !(x == x && x) }")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn demorgan_general_case() {
|
||||
check_assist(apply_demorgan, "fn f() { x ||<|> x }", "fn f() { !(!x && !x) }")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn demorgan_doesnt_apply_with_cursor_not_on_op() {
|
||||
check_assist_not_applicable(apply_demorgan, "fn f() { <|> !x || !x }")
|
||||
}
|
||||
}
|
1088
crates/assists/src/handlers/auto_import.rs
Normal file
1088
crates/assists/src/handlers/auto_import.rs
Normal file
File diff suppressed because it is too large
Load diff
998
crates/assists/src/handlers/change_return_type_to_result.rs
Normal file
998
crates/assists/src/handlers/change_return_type_to_result.rs
Normal file
|
@ -0,0 +1,998 @@
|
|||
use std::iter;
|
||||
|
||||
use syntax::{
|
||||
ast::{self, make, BlockExpr, Expr, LoopBodyOwner},
|
||||
AstNode, SyntaxNode,
|
||||
};
|
||||
use test_utils::mark;
|
||||
|
||||
use crate::{AssistContext, AssistId, AssistKind, Assists};
|
||||
|
||||
// Assist: change_return_type_to_result
|
||||
//
|
||||
// Change the function's return type to Result.
|
||||
//
|
||||
// ```
|
||||
// fn foo() -> i32<|> { 42i32 }
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// fn foo() -> Result<i32, ${0:_}> { Ok(42i32) }
|
||||
// ```
|
||||
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::Fn::cast)?;
|
||||
|
||||
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 {
|
||||
if ret_type_first_part.ends_with("Result") {
|
||||
mark::hit!(change_return_type_to_result_simple_return_type_already_result);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
let block_expr = &fn_def.body()?;
|
||||
|
||||
acc.add(
|
||||
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();
|
||||
tail_return_expr_collector.collect_jump_exprs(block_expr, false);
|
||||
tail_return_expr_collector.collect_tail_exprs(block_expr);
|
||||
|
||||
for ret_expr_arg in tail_return_expr_collector.exprs_to_wrap {
|
||||
let ok_wrapped = make::expr_call(
|
||||
make::expr_path(make::path_unqualified(make::path_segment(make::name_ref(
|
||||
"Ok",
|
||||
)))),
|
||||
make::arg_list(iter::once(ret_expr_arg.clone())),
|
||||
);
|
||||
builder.replace_ast(ret_expr_arg, ok_wrapped);
|
||||
}
|
||||
|
||||
match ctx.config.snippet_cap {
|
||||
Some(cap) => {
|
||||
let snippet = format!("Result<{}, ${{0:_}}>", type_ref);
|
||||
builder.replace_snippet(cap, type_ref.syntax().text_range(), snippet)
|
||||
}
|
||||
None => builder
|
||||
.replace(type_ref.syntax().text_range(), format!("Result<{}, _>", type_ref)),
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
struct TailReturnCollector {
|
||||
exprs_to_wrap: Vec<ast::Expr>,
|
||||
}
|
||||
|
||||
impl TailReturnCollector {
|
||||
fn new() -> Self {
|
||||
Self { exprs_to_wrap: vec![] }
|
||||
}
|
||||
/// Collect all`return` expression
|
||||
fn collect_jump_exprs(&mut self, block_expr: &BlockExpr, collect_break: bool) {
|
||||
let statements = block_expr.statements();
|
||||
for stmt in statements {
|
||||
let expr = match &stmt {
|
||||
ast::Stmt::ExprStmt(stmt) => stmt.expr(),
|
||||
ast::Stmt::LetStmt(stmt) => stmt.initializer(),
|
||||
ast::Stmt::Item(_) => continue,
|
||||
};
|
||||
if let Some(expr) = &expr {
|
||||
self.handle_exprs(expr, collect_break);
|
||||
}
|
||||
}
|
||||
|
||||
// Browse tail expressions for each block
|
||||
if let Some(expr) = block_expr.expr() {
|
||||
if let Some(last_exprs) = get_tail_expr_from_block(&expr) {
|
||||
for last_expr in last_exprs {
|
||||
let last_expr = match last_expr {
|
||||
NodeType::Node(expr) => expr,
|
||||
NodeType::Leaf(expr) => expr.syntax().clone(),
|
||||
};
|
||||
|
||||
if let Some(last_expr) = Expr::cast(last_expr.clone()) {
|
||||
self.handle_exprs(&last_expr, collect_break);
|
||||
} else if let Some(expr_stmt) = ast::Stmt::cast(last_expr) {
|
||||
let expr_stmt = match &expr_stmt {
|
||||
ast::Stmt::ExprStmt(stmt) => stmt.expr(),
|
||||
ast::Stmt::LetStmt(stmt) => stmt.initializer(),
|
||||
ast::Stmt::Item(_) => None,
|
||||
};
|
||||
if let Some(expr) = &expr_stmt {
|
||||
self.handle_exprs(expr, collect_break);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_exprs(&mut self, expr: &Expr, collect_break: bool) {
|
||||
match expr {
|
||||
Expr::BlockExpr(block_expr) => {
|
||||
self.collect_jump_exprs(&block_expr, collect_break);
|
||||
}
|
||||
Expr::ReturnExpr(ret_expr) => {
|
||||
if let Some(ret_expr_arg) = &ret_expr.expr() {
|
||||
self.exprs_to_wrap.push(ret_expr_arg.clone());
|
||||
}
|
||||
}
|
||||
Expr::BreakExpr(break_expr) if collect_break => {
|
||||
if let Some(break_expr_arg) = &break_expr.expr() {
|
||||
self.exprs_to_wrap.push(break_expr_arg.clone());
|
||||
}
|
||||
}
|
||||
Expr::IfExpr(if_expr) => {
|
||||
for block in if_expr.blocks() {
|
||||
self.collect_jump_exprs(&block, collect_break);
|
||||
}
|
||||
}
|
||||
Expr::LoopExpr(loop_expr) => {
|
||||
if let Some(block_expr) = loop_expr.loop_body() {
|
||||
self.collect_jump_exprs(&block_expr, collect_break);
|
||||
}
|
||||
}
|
||||
Expr::ForExpr(for_expr) => {
|
||||
if let Some(block_expr) = for_expr.loop_body() {
|
||||
self.collect_jump_exprs(&block_expr, collect_break);
|
||||
}
|
||||
}
|
||||
Expr::WhileExpr(while_expr) => {
|
||||
if let Some(block_expr) = while_expr.loop_body() {
|
||||
self.collect_jump_exprs(&block_expr, collect_break);
|
||||
}
|
||||
}
|
||||
Expr::MatchExpr(match_expr) => {
|
||||
if let Some(arm_list) = match_expr.match_arm_list() {
|
||||
arm_list.arms().filter_map(|match_arm| match_arm.expr()).for_each(|expr| {
|
||||
self.handle_exprs(&expr, collect_break);
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_tail_exprs(&mut self, block: &BlockExpr) {
|
||||
if let Some(expr) = block.expr() {
|
||||
self.handle_exprs(&expr, true);
|
||||
self.fetch_tail_exprs(&expr);
|
||||
}
|
||||
}
|
||||
|
||||
fn fetch_tail_exprs(&mut self, expr: &Expr) {
|
||||
if let Some(exprs) = get_tail_expr_from_block(expr) {
|
||||
for node_type in &exprs {
|
||||
match node_type {
|
||||
NodeType::Leaf(expr) => {
|
||||
self.exprs_to_wrap.push(expr.clone());
|
||||
}
|
||||
NodeType::Node(expr) => {
|
||||
if let Some(last_expr) = Expr::cast(expr.clone()) {
|
||||
self.fetch_tail_exprs(&last_expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum NodeType {
|
||||
Leaf(ast::Expr),
|
||||
Node(SyntaxNode),
|
||||
}
|
||||
|
||||
/// Get a tail expression inside a block
|
||||
fn get_tail_expr_from_block(expr: &Expr) -> Option<Vec<NodeType>> {
|
||||
match expr {
|
||||
Expr::IfExpr(if_expr) => {
|
||||
let mut nodes = vec![];
|
||||
for block in if_expr.blocks() {
|
||||
if let Some(block_expr) = block.expr() {
|
||||
if let Some(tail_exprs) = get_tail_expr_from_block(&block_expr) {
|
||||
nodes.extend(tail_exprs);
|
||||
}
|
||||
} else if let Some(last_expr) = block.syntax().last_child() {
|
||||
nodes.push(NodeType::Node(last_expr));
|
||||
} else {
|
||||
nodes.push(NodeType::Node(block.syntax().clone()));
|
||||
}
|
||||
}
|
||||
Some(nodes)
|
||||
}
|
||||
Expr::LoopExpr(loop_expr) => {
|
||||
loop_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)])
|
||||
}
|
||||
Expr::ForExpr(for_expr) => {
|
||||
for_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)])
|
||||
}
|
||||
Expr::WhileExpr(while_expr) => {
|
||||
while_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)])
|
||||
}
|
||||
Expr::BlockExpr(block_expr) => {
|
||||
block_expr.expr().map(|lc| vec![NodeType::Node(lc.syntax().clone())])
|
||||
}
|
||||
Expr::MatchExpr(match_expr) => {
|
||||
let arm_list = match_expr.match_arm_list()?;
|
||||
let arms: Vec<NodeType> = arm_list
|
||||
.arms()
|
||||
.filter_map(|match_arm| match_arm.expr())
|
||||
.map(|expr| match expr {
|
||||
Expr::ReturnExpr(ret_expr) => NodeType::Node(ret_expr.syntax().clone()),
|
||||
Expr::BreakExpr(break_expr) => NodeType::Node(break_expr.syntax().clone()),
|
||||
_ => match expr.syntax().last_child() {
|
||||
Some(last_expr) => NodeType::Node(last_expr),
|
||||
None => NodeType::Node(expr.syntax().clone()),
|
||||
},
|
||||
})
|
||||
.collect();
|
||||
|
||||
Some(arms)
|
||||
}
|
||||
Expr::BreakExpr(expr) => expr.expr().map(|e| vec![NodeType::Leaf(e)]),
|
||||
Expr::ReturnExpr(ret_expr) => Some(vec![NodeType::Node(ret_expr.syntax().clone())]),
|
||||
|
||||
Expr::CallExpr(_)
|
||||
| Expr::Literal(_)
|
||||
| Expr::TupleExpr(_)
|
||||
| Expr::ArrayExpr(_)
|
||||
| Expr::ParenExpr(_)
|
||||
| Expr::PathExpr(_)
|
||||
| Expr::RecordExpr(_)
|
||||
| Expr::IndexExpr(_)
|
||||
| Expr::MethodCallExpr(_)
|
||||
| Expr::AwaitExpr(_)
|
||||
| Expr::CastExpr(_)
|
||||
| Expr::RefExpr(_)
|
||||
| Expr::PrefixExpr(_)
|
||||
| Expr::RangeExpr(_)
|
||||
| Expr::BinExpr(_)
|
||||
| Expr::MacroCall(_)
|
||||
| Expr::BoxExpr(_) => Some(vec![NodeType::Leaf(expr.clone())]),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests::{check_assist, check_assist_not_applicable};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn change_return_type_to_result_simple() {
|
||||
check_assist(
|
||||
change_return_type_to_result,
|
||||
r#"fn foo() -> i3<|>2 {
|
||||
let test = "test";
|
||||
return 42i32;
|
||||
}"#,
|
||||
r#"fn foo() -> Result<i32, ${0:_}> {
|
||||
let test = "test";
|
||||
return Ok(42i32);
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_return_type_to_result_simple_return_type() {
|
||||
check_assist(
|
||||
change_return_type_to_result,
|
||||
r#"fn foo() -> i32<|> {
|
||||
let test = "test";
|
||||
return 42i32;
|
||||
}"#,
|
||||
r#"fn foo() -> Result<i32, ${0:_}> {
|
||||
let test = "test";
|
||||
return Ok(42i32);
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_return_type_to_result_simple_return_type_bad_cursor() {
|
||||
check_assist_not_applicable(
|
||||
change_return_type_to_result,
|
||||
r#"fn foo() -> i32 {
|
||||
let test = "test";<|>
|
||||
return 42i32;
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_return_type_to_result_simple_return_type_already_result_std() {
|
||||
check_assist_not_applicable(
|
||||
change_return_type_to_result,
|
||||
r#"fn foo() -> std::result::Result<i32<|>, String> {
|
||||
let test = "test";
|
||||
return 42i32;
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_return_type_to_result_simple_return_type_already_result() {
|
||||
mark::check!(change_return_type_to_result_simple_return_type_already_result);
|
||||
check_assist_not_applicable(
|
||||
change_return_type_to_result,
|
||||
r#"fn foo() -> Result<i32<|>, String> {
|
||||
let test = "test";
|
||||
return 42i32;
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_return_type_to_result_simple_with_cursor() {
|
||||
check_assist(
|
||||
change_return_type_to_result,
|
||||
r#"fn foo() -> <|>i32 {
|
||||
let test = "test";
|
||||
return 42i32;
|
||||
}"#,
|
||||
r#"fn foo() -> Result<i32, ${0:_}> {
|
||||
let test = "test";
|
||||
return Ok(42i32);
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_return_type_to_result_simple_with_tail() {
|
||||
check_assist(
|
||||
change_return_type_to_result,
|
||||
r#"fn foo() -><|> i32 {
|
||||
let test = "test";
|
||||
42i32
|
||||
}"#,
|
||||
r#"fn foo() -> Result<i32, ${0:_}> {
|
||||
let test = "test";
|
||||
Ok(42i32)
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_return_type_to_result_simple_with_tail_only() {
|
||||
check_assist(
|
||||
change_return_type_to_result,
|
||||
r#"fn foo() -> i32<|> {
|
||||
42i32
|
||||
}"#,
|
||||
r#"fn foo() -> Result<i32, ${0:_}> {
|
||||
Ok(42i32)
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn change_return_type_to_result_simple_with_tail_block_like() {
|
||||
check_assist(
|
||||
change_return_type_to_result,
|
||||
r#"fn foo() -> i32<|> {
|
||||
if true {
|
||||
42i32
|
||||
} else {
|
||||
24i32
|
||||
}
|
||||
}"#,
|
||||
r#"fn foo() -> Result<i32, ${0:_}> {
|
||||
if true {
|
||||
Ok(42i32)
|
||||
} else {
|
||||
Ok(24i32)
|
||||
}
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_return_type_to_result_simple_with_nested_if() {
|
||||
check_assist(
|
||||
change_return_type_to_result,
|
||||
r#"fn foo() -> i32<|> {
|
||||
if true {
|
||||
if false {
|
||||
1
|
||||
} else {
|
||||
2
|
||||
}
|
||||
} else {
|
||||
24i32
|
||||
}
|
||||
}"#,
|
||||
r#"fn foo() -> Result<i32, ${0:_}> {
|
||||
if true {
|
||||
if false {
|
||||
Ok(1)
|
||||
} else {
|
||||
Ok(2)
|
||||
}
|
||||
} else {
|
||||
Ok(24i32)
|
||||
}
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_return_type_to_result_simple_with_await() {
|
||||
check_assist(
|
||||
change_return_type_to_result,
|
||||
r#"async fn foo() -> i<|>32 {
|
||||
if true {
|
||||
if false {
|
||||
1.await
|
||||
} else {
|
||||
2.await
|
||||
}
|
||||
} else {
|
||||
24i32.await
|
||||
}
|
||||
}"#,
|
||||
r#"async fn foo() -> Result<i32, ${0:_}> {
|
||||
if true {
|
||||
if false {
|
||||
Ok(1.await)
|
||||
} else {
|
||||
Ok(2.await)
|
||||
}
|
||||
} else {
|
||||
Ok(24i32.await)
|
||||
}
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_return_type_to_result_simple_with_array() {
|
||||
check_assist(
|
||||
change_return_type_to_result,
|
||||
r#"fn foo() -> [i32;<|> 3] {
|
||||
[1, 2, 3]
|
||||
}"#,
|
||||
r#"fn foo() -> Result<[i32; 3], ${0:_}> {
|
||||
Ok([1, 2, 3])
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_return_type_to_result_simple_with_cast() {
|
||||
check_assist(
|
||||
change_return_type_to_result,
|
||||
r#"fn foo() -<|>> i32 {
|
||||
if true {
|
||||
if false {
|
||||
1 as i32
|
||||
} else {
|
||||
2 as i32
|
||||
}
|
||||
} else {
|
||||
24 as i32
|
||||
}
|
||||
}"#,
|
||||
r#"fn foo() -> Result<i32, ${0:_}> {
|
||||
if true {
|
||||
if false {
|
||||
Ok(1 as i32)
|
||||
} else {
|
||||
Ok(2 as i32)
|
||||
}
|
||||
} else {
|
||||
Ok(24 as i32)
|
||||
}
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_return_type_to_result_simple_with_tail_block_like_match() {
|
||||
check_assist(
|
||||
change_return_type_to_result,
|
||||
r#"fn foo() -> i32<|> {
|
||||
let my_var = 5;
|
||||
match my_var {
|
||||
5 => 42i32,
|
||||
_ => 24i32,
|
||||
}
|
||||
}"#,
|
||||
r#"fn foo() -> Result<i32, ${0:_}> {
|
||||
let my_var = 5;
|
||||
match my_var {
|
||||
5 => Ok(42i32),
|
||||
_ => Ok(24i32),
|
||||
}
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_return_type_to_result_simple_with_loop_with_tail() {
|
||||
check_assist(
|
||||
change_return_type_to_result,
|
||||
r#"fn foo() -> i32<|> {
|
||||
let my_var = 5;
|
||||
loop {
|
||||
println!("test");
|
||||
5
|
||||
}
|
||||
|
||||
my_var
|
||||
}"#,
|
||||
r#"fn foo() -> Result<i32, ${0:_}> {
|
||||
let my_var = 5;
|
||||
loop {
|
||||
println!("test");
|
||||
5
|
||||
}
|
||||
|
||||
Ok(my_var)
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_return_type_to_result_simple_with_loop_in_let_stmt() {
|
||||
check_assist(
|
||||
change_return_type_to_result,
|
||||
r#"fn foo() -> i32<|> {
|
||||
let my_var = let x = loop {
|
||||
break 1;
|
||||
};
|
||||
|
||||
my_var
|
||||
}"#,
|
||||
r#"fn foo() -> Result<i32, ${0:_}> {
|
||||
let my_var = let x = loop {
|
||||
break 1;
|
||||
};
|
||||
|
||||
Ok(my_var)
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_return_type_to_result_simple_with_tail_block_like_match_return_expr() {
|
||||
check_assist(
|
||||
change_return_type_to_result,
|
||||
r#"fn foo() -> i32<|> {
|
||||
let my_var = 5;
|
||||
let res = match my_var {
|
||||
5 => 42i32,
|
||||
_ => return 24i32,
|
||||
};
|
||||
|
||||
res
|
||||
}"#,
|
||||
r#"fn foo() -> Result<i32, ${0:_}> {
|
||||
let my_var = 5;
|
||||
let res = match my_var {
|
||||
5 => 42i32,
|
||||
_ => return Ok(24i32),
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
}"#,
|
||||
);
|
||||
|
||||
check_assist(
|
||||
change_return_type_to_result,
|
||||
r#"fn foo() -> i32<|> {
|
||||
let my_var = 5;
|
||||
let res = if my_var == 5 {
|
||||
42i32
|
||||
} else {
|
||||
return 24i32;
|
||||
};
|
||||
|
||||
res
|
||||
}"#,
|
||||
r#"fn foo() -> Result<i32, ${0:_}> {
|
||||
let my_var = 5;
|
||||
let res = if my_var == 5 {
|
||||
42i32
|
||||
} else {
|
||||
return Ok(24i32);
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_return_type_to_result_simple_with_tail_block_like_match_deeper() {
|
||||
check_assist(
|
||||
change_return_type_to_result,
|
||||
r#"fn foo() -> i32<|> {
|
||||
let my_var = 5;
|
||||
match my_var {
|
||||
5 => {
|
||||
if true {
|
||||
42i32
|
||||
} else {
|
||||
25i32
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
let test = "test";
|
||||
if test == "test" {
|
||||
return bar();
|
||||
}
|
||||
53i32
|
||||
},
|
||||
}
|
||||
}"#,
|
||||
r#"fn foo() -> Result<i32, ${0:_}> {
|
||||
let my_var = 5;
|
||||
match my_var {
|
||||
5 => {
|
||||
if true {
|
||||
Ok(42i32)
|
||||
} else {
|
||||
Ok(25i32)
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
let test = "test";
|
||||
if test == "test" {
|
||||
return Ok(bar());
|
||||
}
|
||||
Ok(53i32)
|
||||
},
|
||||
}
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_return_type_to_result_simple_with_tail_block_like_early_return() {
|
||||
check_assist(
|
||||
change_return_type_to_result,
|
||||
r#"fn foo() -> i<|>32 {
|
||||
let test = "test";
|
||||
if test == "test" {
|
||||
return 24i32;
|
||||
}
|
||||
53i32
|
||||
}"#,
|
||||
r#"fn foo() -> Result<i32, ${0:_}> {
|
||||
let test = "test";
|
||||
if test == "test" {
|
||||
return Ok(24i32);
|
||||
}
|
||||
Ok(53i32)
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_return_type_to_result_simple_with_closure() {
|
||||
check_assist(
|
||||
change_return_type_to_result,
|
||||
r#"fn foo(the_field: u32) -><|> u32 {
|
||||
let true_closure = || {
|
||||
return true;
|
||||
};
|
||||
if the_field < 5 {
|
||||
let mut i = 0;
|
||||
|
||||
|
||||
if true_closure() {
|
||||
return 99;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
the_field
|
||||
}"#,
|
||||
r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
|
||||
let true_closure = || {
|
||||
return true;
|
||||
};
|
||||
if the_field < 5 {
|
||||
let mut i = 0;
|
||||
|
||||
|
||||
if true_closure() {
|
||||
return Ok(99);
|
||||
} else {
|
||||
return Ok(0);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(the_field)
|
||||
}"#,
|
||||
);
|
||||
|
||||
check_assist(
|
||||
change_return_type_to_result,
|
||||
r#"fn foo(the_field: u32) -> u32<|> {
|
||||
let true_closure = || {
|
||||
return true;
|
||||
};
|
||||
if the_field < 5 {
|
||||
let mut i = 0;
|
||||
|
||||
|
||||
if true_closure() {
|
||||
return 99;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
let t = None;
|
||||
|
||||
t.unwrap_or_else(|| the_field)
|
||||
}"#,
|
||||
r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
|
||||
let true_closure = || {
|
||||
return true;
|
||||
};
|
||||
if the_field < 5 {
|
||||
let mut i = 0;
|
||||
|
||||
|
||||
if true_closure() {
|
||||
return Ok(99);
|
||||
} else {
|
||||
return Ok(0);
|
||||
}
|
||||
}
|
||||
let t = None;
|
||||
|
||||
Ok(t.unwrap_or_else(|| the_field))
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_return_type_to_result_simple_with_weird_forms() {
|
||||
check_assist(
|
||||
change_return_type_to_result,
|
||||
r#"fn foo() -> i32<|> {
|
||||
let test = "test";
|
||||
if test == "test" {
|
||||
return 24i32;
|
||||
}
|
||||
let mut i = 0;
|
||||
loop {
|
||||
if i == 1 {
|
||||
break 55;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}"#,
|
||||
r#"fn foo() -> Result<i32, ${0:_}> {
|
||||
let test = "test";
|
||||
if test == "test" {
|
||||
return Ok(24i32);
|
||||
}
|
||||
let mut i = 0;
|
||||
loop {
|
||||
if i == 1 {
|
||||
break Ok(55);
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}"#,
|
||||
);
|
||||
|
||||
check_assist(
|
||||
change_return_type_to_result,
|
||||
r#"fn foo() -> i32<|> {
|
||||
let test = "test";
|
||||
if test == "test" {
|
||||
return 24i32;
|
||||
}
|
||||
let mut i = 0;
|
||||
loop {
|
||||
loop {
|
||||
if i == 1 {
|
||||
break 55;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}"#,
|
||||
r#"fn foo() -> Result<i32, ${0:_}> {
|
||||
let test = "test";
|
||||
if test == "test" {
|
||||
return Ok(24i32);
|
||||
}
|
||||
let mut i = 0;
|
||||
loop {
|
||||
loop {
|
||||
if i == 1 {
|
||||
break Ok(55);
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}"#,
|
||||
);
|
||||
|
||||
check_assist(
|
||||
change_return_type_to_result,
|
||||
r#"fn foo() -> i3<|>2 {
|
||||
let test = "test";
|
||||
let other = 5;
|
||||
if test == "test" {
|
||||
let res = match other {
|
||||
5 => 43,
|
||||
_ => return 56,
|
||||
};
|
||||
}
|
||||
let mut i = 0;
|
||||
loop {
|
||||
loop {
|
||||
if i == 1 {
|
||||
break 55;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}"#,
|
||||
r#"fn foo() -> Result<i32, ${0:_}> {
|
||||
let test = "test";
|
||||
let other = 5;
|
||||
if test == "test" {
|
||||
let res = match other {
|
||||
5 => 43,
|
||||
_ => return Ok(56),
|
||||
};
|
||||
}
|
||||
let mut i = 0;
|
||||
loop {
|
||||
loop {
|
||||
if i == 1 {
|
||||
break Ok(55);
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}"#,
|
||||
);
|
||||
|
||||
check_assist(
|
||||
change_return_type_to_result,
|
||||
r#"fn foo(the_field: u32) -> u32<|> {
|
||||
if the_field < 5 {
|
||||
let mut i = 0;
|
||||
loop {
|
||||
if i > 5 {
|
||||
return 55u32;
|
||||
}
|
||||
i += 3;
|
||||
}
|
||||
|
||||
match i {
|
||||
5 => return 99,
|
||||
_ => return 0,
|
||||
};
|
||||
}
|
||||
|
||||
the_field
|
||||
}"#,
|
||||
r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
|
||||
if the_field < 5 {
|
||||
let mut i = 0;
|
||||
loop {
|
||||
if i > 5 {
|
||||
return Ok(55u32);
|
||||
}
|
||||
i += 3;
|
||||
}
|
||||
|
||||
match i {
|
||||
5 => return Ok(99),
|
||||
_ => return Ok(0),
|
||||
};
|
||||
}
|
||||
|
||||
Ok(the_field)
|
||||
}"#,
|
||||
);
|
||||
|
||||
check_assist(
|
||||
change_return_type_to_result,
|
||||
r#"fn foo(the_field: u32) -> u3<|>2 {
|
||||
if the_field < 5 {
|
||||
let mut i = 0;
|
||||
|
||||
match i {
|
||||
5 => return 99,
|
||||
_ => return 0,
|
||||
}
|
||||
}
|
||||
|
||||
the_field
|
||||
}"#,
|
||||
r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
|
||||
if the_field < 5 {
|
||||
let mut i = 0;
|
||||
|
||||
match i {
|
||||
5 => return Ok(99),
|
||||
_ => return Ok(0),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(the_field)
|
||||
}"#,
|
||||
);
|
||||
|
||||
check_assist(
|
||||
change_return_type_to_result,
|
||||
r#"fn foo(the_field: u32) -> u32<|> {
|
||||
if the_field < 5 {
|
||||
let mut i = 0;
|
||||
|
||||
if i == 5 {
|
||||
return 99
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
the_field
|
||||
}"#,
|
||||
r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
|
||||
if the_field < 5 {
|
||||
let mut i = 0;
|
||||
|
||||
if i == 5 {
|
||||
return Ok(99)
|
||||
} else {
|
||||
return Ok(0)
|
||||
}
|
||||
}
|
||||
|
||||
Ok(the_field)
|
||||
}"#,
|
||||
);
|
||||
|
||||
check_assist(
|
||||
change_return_type_to_result,
|
||||
r#"fn foo(the_field: u32) -> <|>u32 {
|
||||
if the_field < 5 {
|
||||
let mut i = 0;
|
||||
|
||||
if i == 5 {
|
||||
return 99;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
the_field
|
||||
}"#,
|
||||
r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
|
||||
if the_field < 5 {
|
||||
let mut i = 0;
|
||||
|
||||
if i == 5 {
|
||||
return Ok(99);
|
||||
} else {
|
||||
return Ok(0);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(the_field)
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
}
|
200
crates/assists/src/handlers/change_visibility.rs
Normal file
200
crates/assists/src/handlers/change_visibility.rs
Normal file
|
@ -0,0 +1,200 @@
|
|||
use syntax::{
|
||||
ast::{self, NameOwner, VisibilityOwner},
|
||||
AstNode,
|
||||
SyntaxKind::{CONST, ENUM, FN, MODULE, STATIC, STRUCT, TRAIT, VISIBILITY},
|
||||
T,
|
||||
};
|
||||
use test_utils::mark;
|
||||
|
||||
use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists};
|
||||
|
||||
// Assist: change_visibility
|
||||
//
|
||||
// Adds or changes existing visibility specifier.
|
||||
//
|
||||
// ```
|
||||
// <|>fn frobnicate() {}
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// pub(crate) fn frobnicate() {}
|
||||
// ```
|
||||
pub(crate) fn change_visibility(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
if let Some(vis) = ctx.find_node_at_offset::<ast::Visibility>() {
|
||||
return change_vis(acc, vis);
|
||||
}
|
||||
add_vis(acc, ctx)
|
||||
}
|
||||
|
||||
fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
let item_keyword = ctx.token_at_offset().find(|leaf| {
|
||||
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, 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;
|
||||
}
|
||||
// Already have visibility, do nothing
|
||||
if parent.children().any(|child| child.kind() == VISIBILITY) {
|
||||
return None;
|
||||
}
|
||||
(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::RecordField::cast)?;
|
||||
if field.name()? != field_name {
|
||||
mark::hit!(change_visibility_field_false_positive);
|
||||
return None;
|
||||
}
|
||||
if field.visibility().is_some() {
|
||||
return None;
|
||||
}
|
||||
(vis_offset(field.syntax()), field_name.syntax().text_range())
|
||||
} else if let Some(field) = ctx.find_node_at_offset::<ast::TupleField>() {
|
||||
if field.visibility().is_some() {
|
||||
return None;
|
||||
}
|
||||
(vis_offset(field.syntax()), field.syntax().text_range())
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
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", AssistKind::RefactorRewrite),
|
||||
"Change Visibility to pub(crate)",
|
||||
target,
|
||||
|edit| {
|
||||
edit.replace(vis.syntax().text_range(), "pub(crate)");
|
||||
},
|
||||
);
|
||||
}
|
||||
if vis.syntax().text() == "pub(crate)" {
|
||||
let target = vis.syntax().text_range();
|
||||
return acc.add(
|
||||
AssistId("change_visibility", AssistKind::RefactorRewrite),
|
||||
"Change visibility to pub",
|
||||
target,
|
||||
|edit| {
|
||||
edit.replace(vis.syntax().text_range(), "pub");
|
||||
},
|
||||
);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use test_utils::mark;
|
||||
|
||||
use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn change_visibility_adds_pub_crate_to_items() {
|
||||
check_assist(change_visibility, "<|>fn foo() {}", "pub(crate) fn foo() {}");
|
||||
check_assist(change_visibility, "f<|>n foo() {}", "pub(crate) fn foo() {}");
|
||||
check_assist(change_visibility, "<|>struct Foo {}", "pub(crate) struct Foo {}");
|
||||
check_assist(change_visibility, "<|>mod foo {}", "pub(crate) mod foo {}");
|
||||
check_assist(change_visibility, "<|>trait Foo {}", "pub(crate) trait Foo {}");
|
||||
check_assist(change_visibility, "m<|>od {}", "pub(crate) mod {}");
|
||||
check_assist(change_visibility, "unsafe f<|>n foo() {}", "pub(crate) unsafe fn foo() {}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_visibility_works_with_struct_fields() {
|
||||
check_assist(
|
||||
change_visibility,
|
||||
r"struct S { <|>field: u32 }",
|
||||
r"struct S { pub(crate) field: u32 }",
|
||||
);
|
||||
check_assist(change_visibility, r"struct S ( <|>u32 )", r"struct S ( pub(crate) u32 )");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_visibility_field_false_positive() {
|
||||
mark::check!(change_visibility_field_false_positive);
|
||||
check_assist_not_applicable(
|
||||
change_visibility,
|
||||
r"struct S { field: [(); { let <|>x = ();}] }",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_visibility_pub_to_pub_crate() {
|
||||
check_assist(change_visibility, "<|>pub fn foo() {}", "pub(crate) fn foo() {}")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_visibility_pub_crate_to_pub() {
|
||||
check_assist(change_visibility, "<|>pub(crate) fn foo() {}", "pub fn foo() {}")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_visibility_const() {
|
||||
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(
|
||||
change_visibility,
|
||||
r"
|
||||
/// docs
|
||||
|
||||
// comments
|
||||
|
||||
#[derive(Debug)]
|
||||
<|>struct Foo;
|
||||
",
|
||||
r"
|
||||
/// docs
|
||||
|
||||
// comments
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Foo;
|
||||
",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_applicable_for_enum_variants() {
|
||||
check_assist_not_applicable(
|
||||
change_visibility,
|
||||
r"mod foo { pub enum Foo {Foo1} }
|
||||
fn main() { foo::Foo::Foo1<|> } ",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_visibility_target() {
|
||||
check_assist_target(change_visibility, "<|>fn foo() {}", "fn");
|
||||
check_assist_target(change_visibility, "pub(crate)<|> fn foo() {}", "pub(crate)");
|
||||
check_assist_target(change_visibility, "struct S { <|>field: u32 }", "field");
|
||||
}
|
||||
}
|
515
crates/assists/src/handlers/early_return.rs
Normal file
515
crates/assists/src/handlers/early_return.rs
Normal file
|
@ -0,0 +1,515 @@
|
|||
use std::{iter::once, ops::RangeInclusive};
|
||||
|
||||
use syntax::{
|
||||
algo::replace_children,
|
||||
ast::{
|
||||
self,
|
||||
edit::{AstNodeEdit, IndentLevel},
|
||||
make,
|
||||
},
|
||||
AstNode,
|
||||
SyntaxKind::{FN, LOOP_EXPR, L_CURLY, R_CURLY, WHILE_EXPR, WHITESPACE},
|
||||
SyntaxNode,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
assist_context::{AssistContext, Assists},
|
||||
utils::invert_boolean_expression,
|
||||
AssistId, AssistKind,
|
||||
};
|
||||
|
||||
// Assist: convert_to_guarded_return
|
||||
//
|
||||
// Replace a large conditional with a guarded return.
|
||||
//
|
||||
// ```
|
||||
// fn main() {
|
||||
// <|>if cond {
|
||||
// foo();
|
||||
// bar();
|
||||
// }
|
||||
// }
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// fn main() {
|
||||
// if !cond {
|
||||
// return;
|
||||
// }
|
||||
// foo();
|
||||
// bar();
|
||||
// }
|
||||
// ```
|
||||
pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
let if_expr: ast::IfExpr = ctx.find_node_at_offset()?;
|
||||
if if_expr.else_branch().is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let cond = if_expr.condition()?;
|
||||
|
||||
// Check if there is an IfLet that we can handle.
|
||||
let if_let_pat = match cond.pat() {
|
||||
None => None, // No IfLet, supported.
|
||||
Some(ast::Pat::TupleStructPat(pat)) if pat.fields().count() == 1 => {
|
||||
let path = pat.path()?;
|
||||
match path.qualifier() {
|
||||
None => {
|
||||
let bound_ident = pat.fields().next().unwrap();
|
||||
Some((path, bound_ident))
|
||||
}
|
||||
Some(_) => return None,
|
||||
}
|
||||
}
|
||||
Some(_) => return None, // Unsupported IfLet.
|
||||
};
|
||||
|
||||
let cond_expr = cond.expr()?;
|
||||
let then_block = if_expr.then_branch()?;
|
||||
|
||||
let parent_block = if_expr.syntax().parent()?.ancestors().find_map(ast::BlockExpr::cast)?;
|
||||
|
||||
if parent_block.expr()? != if_expr.clone().into() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// check for early return and continue
|
||||
let first_in_then_block = then_block.syntax().first_child()?;
|
||||
if ast::ReturnExpr::can_cast(first_in_then_block.kind())
|
||||
|| ast::ContinueExpr::can_cast(first_in_then_block.kind())
|
||||
|| first_in_then_block
|
||||
.children()
|
||||
.any(|x| ast::ReturnExpr::can_cast(x.kind()) || ast::ContinueExpr::can_cast(x.kind()))
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let parent_container = parent_block.syntax().parent()?;
|
||||
|
||||
let early_expression: ast::Expr = match parent_container.kind() {
|
||||
WHILE_EXPR | LOOP_EXPR => make::expr_continue(),
|
||||
FN => make::expr_return(),
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
if then_block.syntax().first_child_or_token().map(|t| t.kind() == L_CURLY).is_none() {
|
||||
return None;
|
||||
}
|
||||
|
||||
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", 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::ident_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)
|
||||
};
|
||||
|
||||
let sad_arm = make::match_arm(
|
||||
// FIXME: would be cool to use `None` or `Err(_)` if appropriate
|
||||
once(make::wildcard_pat().into()),
|
||||
early_expression,
|
||||
);
|
||||
|
||||
make::expr_match(cond_expr, make::match_arm_list(vec![happy_arm, sad_arm]))
|
||||
};
|
||||
|
||||
let let_stmt = make::let_stmt(
|
||||
make::ident_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());
|
||||
|
||||
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)]
|
||||
mod tests {
|
||||
use crate::tests::{check_assist, check_assist_not_applicable};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn convert_inside_fn() {
|
||||
check_assist(
|
||||
convert_to_guarded_return,
|
||||
r#"
|
||||
fn main() {
|
||||
bar();
|
||||
if<|> true {
|
||||
foo();
|
||||
|
||||
//comment
|
||||
bar();
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn main() {
|
||||
bar();
|
||||
if !true {
|
||||
return;
|
||||
}
|
||||
foo();
|
||||
|
||||
//comment
|
||||
bar();
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_let_inside_fn() {
|
||||
check_assist(
|
||||
convert_to_guarded_return,
|
||||
r#"
|
||||
fn main(n: Option<String>) {
|
||||
bar();
|
||||
if<|> let Some(n) = n {
|
||||
foo(n);
|
||||
|
||||
//comment
|
||||
bar();
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn main(n: Option<String>) {
|
||||
bar();
|
||||
let n = match n {
|
||||
Some(it) => it,
|
||||
_ => return,
|
||||
};
|
||||
foo(n);
|
||||
|
||||
//comment
|
||||
bar();
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_if_let_result() {
|
||||
check_assist(
|
||||
convert_to_guarded_return,
|
||||
r#"
|
||||
fn main() {
|
||||
if<|> let Ok(x) = Err(92) {
|
||||
foo(x);
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn main() {
|
||||
let x = match Err(92) {
|
||||
Ok(it) => it,
|
||||
_ => return,
|
||||
};
|
||||
foo(x);
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_let_ok_inside_fn() {
|
||||
check_assist(
|
||||
convert_to_guarded_return,
|
||||
r#"
|
||||
fn main(n: Option<String>) {
|
||||
bar();
|
||||
if<|> let Ok(n) = n {
|
||||
foo(n);
|
||||
|
||||
//comment
|
||||
bar();
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn main(n: Option<String>) {
|
||||
bar();
|
||||
let n = match n {
|
||||
Ok(it) => it,
|
||||
_ => return,
|
||||
};
|
||||
foo(n);
|
||||
|
||||
//comment
|
||||
bar();
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_inside_while() {
|
||||
check_assist(
|
||||
convert_to_guarded_return,
|
||||
r#"
|
||||
fn main() {
|
||||
while true {
|
||||
if<|> true {
|
||||
foo();
|
||||
bar();
|
||||
}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn main() {
|
||||
while true {
|
||||
if !true {
|
||||
continue;
|
||||
}
|
||||
foo();
|
||||
bar();
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_let_inside_while() {
|
||||
check_assist(
|
||||
convert_to_guarded_return,
|
||||
r#"
|
||||
fn main() {
|
||||
while true {
|
||||
if<|> let Some(n) = n {
|
||||
foo(n);
|
||||
bar();
|
||||
}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn main() {
|
||||
while true {
|
||||
let n = match n {
|
||||
Some(it) => it,
|
||||
_ => continue,
|
||||
};
|
||||
foo(n);
|
||||
bar();
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_inside_loop() {
|
||||
check_assist(
|
||||
convert_to_guarded_return,
|
||||
r#"
|
||||
fn main() {
|
||||
loop {
|
||||
if<|> true {
|
||||
foo();
|
||||
bar();
|
||||
}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn main() {
|
||||
loop {
|
||||
if !true {
|
||||
continue;
|
||||
}
|
||||
foo();
|
||||
bar();
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_let_inside_loop() {
|
||||
check_assist(
|
||||
convert_to_guarded_return,
|
||||
r#"
|
||||
fn main() {
|
||||
loop {
|
||||
if<|> let Some(n) = n {
|
||||
foo(n);
|
||||
bar();
|
||||
}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn main() {
|
||||
loop {
|
||||
let n = match n {
|
||||
Some(it) => it,
|
||||
_ => continue,
|
||||
};
|
||||
foo(n);
|
||||
bar();
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignore_already_converted_if() {
|
||||
check_assist_not_applicable(
|
||||
convert_to_guarded_return,
|
||||
r#"
|
||||
fn main() {
|
||||
if<|> true {
|
||||
return;
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignore_already_converted_loop() {
|
||||
check_assist_not_applicable(
|
||||
convert_to_guarded_return,
|
||||
r#"
|
||||
fn main() {
|
||||
loop {
|
||||
if<|> true {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignore_return() {
|
||||
check_assist_not_applicable(
|
||||
convert_to_guarded_return,
|
||||
r#"
|
||||
fn main() {
|
||||
if<|> true {
|
||||
return
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignore_else_branch() {
|
||||
check_assist_not_applicable(
|
||||
convert_to_guarded_return,
|
||||
r#"
|
||||
fn main() {
|
||||
if<|> true {
|
||||
foo();
|
||||
} else {
|
||||
bar()
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignore_statements_aftert_if() {
|
||||
check_assist_not_applicable(
|
||||
convert_to_guarded_return,
|
||||
r#"
|
||||
fn main() {
|
||||
if<|> true {
|
||||
foo();
|
||||
}
|
||||
bar();
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignore_statements_inside_if() {
|
||||
check_assist_not_applicable(
|
||||
convert_to_guarded_return,
|
||||
r#"
|
||||
fn main() {
|
||||
if false {
|
||||
if<|> true {
|
||||
foo();
|
||||
}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
}
|
391
crates/assists/src/handlers/expand_glob_import.rs
Normal file
391
crates/assists/src/handlers/expand_glob_import.rs
Normal file
|
@ -0,0 +1,391 @@
|
|||
use hir::{AssocItem, MacroDef, ModuleDef, Name, PathResolution, ScopeDef, SemanticsScope};
|
||||
use ide_db::{
|
||||
defs::{classify_name_ref, Definition, NameRefClass},
|
||||
RootDatabase,
|
||||
};
|
||||
use syntax::{algo, ast, match_ast, AstNode, SyntaxNode, SyntaxToken, T};
|
||||
|
||||
use crate::{
|
||||
assist_context::{AssistBuilder, AssistContext, Assists},
|
||||
AssistId, AssistKind,
|
||||
};
|
||||
|
||||
use either::Either;
|
||||
|
||||
// Assist: expand_glob_import
|
||||
//
|
||||
// Expands glob imports.
|
||||
//
|
||||
// ```
|
||||
// mod foo {
|
||||
// pub struct Bar;
|
||||
// pub struct Baz;
|
||||
// }
|
||||
//
|
||||
// use foo::*<|>;
|
||||
//
|
||||
// fn qux(bar: Bar, baz: Baz) {}
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// mod foo {
|
||||
// pub struct Bar;
|
||||
// pub struct Baz;
|
||||
// }
|
||||
//
|
||||
// use foo::{Baz, Bar};
|
||||
//
|
||||
// fn qux(bar: Bar, baz: Baz) {}
|
||||
// ```
|
||||
pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
let star = ctx.find_token_at_offset(T![*])?;
|
||||
let mod_path = find_mod_path(&star)?;
|
||||
|
||||
let source_file = ctx.source_file();
|
||||
let scope = ctx.sema.scope_at_offset(source_file.syntax(), ctx.offset());
|
||||
|
||||
let defs_in_mod = find_defs_in_mod(ctx, scope, &mod_path)?;
|
||||
let name_refs_in_source_file =
|
||||
source_file.syntax().descendants().filter_map(ast::NameRef::cast).collect();
|
||||
let used_names = find_used_names(ctx, defs_in_mod, name_refs_in_source_file);
|
||||
|
||||
let parent = star.parent().parent()?;
|
||||
acc.add(
|
||||
AssistId("expand_glob_import", AssistKind::RefactorRewrite),
|
||||
"Expand glob import",
|
||||
parent.text_range(),
|
||||
|builder| {
|
||||
replace_ast(builder, &parent, mod_path, used_names);
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn find_mod_path(star: &SyntaxToken) -> Option<ast::Path> {
|
||||
star.ancestors().find_map(|n| ast::UseTree::cast(n).and_then(|u| u.path()))
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum Def {
|
||||
ModuleDef(ModuleDef),
|
||||
MacroDef(MacroDef),
|
||||
}
|
||||
|
||||
impl Def {
|
||||
fn name(&self, db: &RootDatabase) -> Option<Name> {
|
||||
match self {
|
||||
Def::ModuleDef(def) => def.name(db),
|
||||
Def::MacroDef(def) => def.name(db),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn find_defs_in_mod(
|
||||
ctx: &AssistContext,
|
||||
from: SemanticsScope<'_>,
|
||||
path: &ast::Path,
|
||||
) -> Option<Vec<Def>> {
|
||||
let hir_path = ctx.sema.lower_path(&path)?;
|
||||
let module = if let Some(PathResolution::Def(ModuleDef::Module(module))) =
|
||||
from.resolve_hir_path_qualifier(&hir_path)
|
||||
{
|
||||
module
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let module_scope = module.scope(ctx.db(), from.module());
|
||||
|
||||
let mut defs = vec![];
|
||||
for (_, def) in module_scope {
|
||||
match def {
|
||||
ScopeDef::ModuleDef(def) => defs.push(Def::ModuleDef(def)),
|
||||
ScopeDef::MacroDef(def) => defs.push(Def::MacroDef(def)),
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
|
||||
Some(defs)
|
||||
}
|
||||
|
||||
fn find_used_names(
|
||||
ctx: &AssistContext,
|
||||
defs_in_mod: Vec<Def>,
|
||||
name_refs_in_source_file: Vec<ast::NameRef>,
|
||||
) -> Vec<Name> {
|
||||
let defs_in_source_file = name_refs_in_source_file
|
||||
.iter()
|
||||
.filter_map(|r| classify_name_ref(&ctx.sema, r))
|
||||
.filter_map(|rc| match rc {
|
||||
NameRefClass::Definition(Definition::ModuleDef(def)) => Some(Def::ModuleDef(def)),
|
||||
NameRefClass::Definition(Definition::Macro(def)) => Some(Def::MacroDef(def)),
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<Def>>();
|
||||
|
||||
defs_in_mod
|
||||
.iter()
|
||||
.filter(|def| {
|
||||
if let Def::ModuleDef(ModuleDef::Trait(tr)) = def {
|
||||
for item in tr.items(ctx.db()) {
|
||||
if let AssocItem::Function(f) = item {
|
||||
if defs_in_source_file.contains(&Def::ModuleDef(ModuleDef::Function(f))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defs_in_source_file.contains(def)
|
||||
})
|
||||
.filter_map(|d| d.name(ctx.db()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn replace_ast(
|
||||
builder: &mut AssistBuilder,
|
||||
node: &SyntaxNode,
|
||||
path: ast::Path,
|
||||
used_names: Vec<Name>,
|
||||
) {
|
||||
let replacement: Either<ast::UseTree, ast::UseTreeList> = match used_names.as_slice() {
|
||||
[name] => Either::Left(ast::make::use_tree(
|
||||
ast::make::path_from_text(&format!("{}::{}", path, name)),
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
)),
|
||||
names => Either::Right(ast::make::use_tree_list(names.iter().map(|n| {
|
||||
ast::make::use_tree(ast::make::path_from_text(&n.to_string()), None, None, false)
|
||||
}))),
|
||||
};
|
||||
|
||||
let mut replace_node = |replacement: Either<ast::UseTree, ast::UseTreeList>| {
|
||||
algo::diff(node, &replacement.either(|u| u.syntax().clone(), |ut| ut.syntax().clone()))
|
||||
.into_text_edit(builder.text_edit_builder());
|
||||
};
|
||||
|
||||
match_ast! {
|
||||
match node {
|
||||
ast::UseTree(use_tree) => {
|
||||
replace_node(replacement);
|
||||
},
|
||||
ast::UseTreeList(use_tree_list) => {
|
||||
replace_node(replacement);
|
||||
},
|
||||
ast::Use(use_item) => {
|
||||
builder.replace_ast(use_item, ast::make::use_(replacement.left_or_else(|ut| ast::make::use_tree(path, Some(ut), None, false))));
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests::{check_assist, check_assist_not_applicable};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn expanding_glob_import() {
|
||||
check_assist(
|
||||
expand_glob_import,
|
||||
r"
|
||||
mod foo {
|
||||
pub struct Bar;
|
||||
pub struct Baz;
|
||||
pub struct Qux;
|
||||
|
||||
pub fn f() {}
|
||||
}
|
||||
|
||||
use foo::*<|>;
|
||||
|
||||
fn qux(bar: Bar, baz: Baz) {
|
||||
f();
|
||||
}
|
||||
",
|
||||
r"
|
||||
mod foo {
|
||||
pub struct Bar;
|
||||
pub struct Baz;
|
||||
pub struct Qux;
|
||||
|
||||
pub fn f() {}
|
||||
}
|
||||
|
||||
use foo::{Baz, Bar, f};
|
||||
|
||||
fn qux(bar: Bar, baz: Baz) {
|
||||
f();
|
||||
}
|
||||
",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expanding_glob_import_with_existing_explicit_names() {
|
||||
check_assist(
|
||||
expand_glob_import,
|
||||
r"
|
||||
mod foo {
|
||||
pub struct Bar;
|
||||
pub struct Baz;
|
||||
pub struct Qux;
|
||||
|
||||
pub fn f() {}
|
||||
}
|
||||
|
||||
use foo::{*<|>, f};
|
||||
|
||||
fn qux(bar: Bar, baz: Baz) {
|
||||
f();
|
||||
}
|
||||
",
|
||||
r"
|
||||
mod foo {
|
||||
pub struct Bar;
|
||||
pub struct Baz;
|
||||
pub struct Qux;
|
||||
|
||||
pub fn f() {}
|
||||
}
|
||||
|
||||
use foo::{Baz, Bar, f};
|
||||
|
||||
fn qux(bar: Bar, baz: Baz) {
|
||||
f();
|
||||
}
|
||||
",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expanding_nested_glob_import() {
|
||||
check_assist(
|
||||
expand_glob_import,
|
||||
r"
|
||||
mod foo {
|
||||
mod bar {
|
||||
pub struct Bar;
|
||||
pub struct Baz;
|
||||
pub struct Qux;
|
||||
|
||||
pub fn f() {}
|
||||
}
|
||||
|
||||
mod baz {
|
||||
pub fn g() {}
|
||||
}
|
||||
}
|
||||
|
||||
use foo::{bar::{*<|>, f}, baz::*};
|
||||
|
||||
fn qux(bar: Bar, baz: Baz) {
|
||||
f();
|
||||
g();
|
||||
}
|
||||
",
|
||||
r"
|
||||
mod foo {
|
||||
mod bar {
|
||||
pub struct Bar;
|
||||
pub struct Baz;
|
||||
pub struct Qux;
|
||||
|
||||
pub fn f() {}
|
||||
}
|
||||
|
||||
mod baz {
|
||||
pub fn g() {}
|
||||
}
|
||||
}
|
||||
|
||||
use foo::{bar::{Baz, Bar, f}, baz::*};
|
||||
|
||||
fn qux(bar: Bar, baz: Baz) {
|
||||
f();
|
||||
g();
|
||||
}
|
||||
",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expanding_glob_import_with_macro_defs() {
|
||||
check_assist(
|
||||
expand_glob_import,
|
||||
r"
|
||||
//- /lib.rs crate:foo
|
||||
#[macro_export]
|
||||
macro_rules! bar {
|
||||
() => ()
|
||||
}
|
||||
|
||||
pub fn baz() {}
|
||||
|
||||
//- /main.rs crate:main deps:foo
|
||||
use foo::*<|>;
|
||||
|
||||
fn main() {
|
||||
bar!();
|
||||
baz();
|
||||
}
|
||||
",
|
||||
r"
|
||||
use foo::{bar, baz};
|
||||
|
||||
fn main() {
|
||||
bar!();
|
||||
baz();
|
||||
}
|
||||
",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expanding_glob_import_with_trait_method_uses() {
|
||||
check_assist(
|
||||
expand_glob_import,
|
||||
r"
|
||||
//- /lib.rs crate:foo
|
||||
pub trait Tr {
|
||||
fn method(&self) {}
|
||||
}
|
||||
impl Tr for () {}
|
||||
|
||||
//- /main.rs crate:main deps:foo
|
||||
use foo::*<|>;
|
||||
|
||||
fn main() {
|
||||
().method();
|
||||
}
|
||||
",
|
||||
r"
|
||||
use foo::Tr;
|
||||
|
||||
fn main() {
|
||||
().method();
|
||||
}
|
||||
",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expanding_is_not_applicable_if_cursor_is_not_in_star_token() {
|
||||
check_assist_not_applicable(
|
||||
expand_glob_import,
|
||||
r"
|
||||
mod foo {
|
||||
pub struct Bar;
|
||||
pub struct Baz;
|
||||
pub struct Qux;
|
||||
}
|
||||
|
||||
use foo::Bar<|>;
|
||||
|
||||
fn qux(bar: Bar, baz: Baz) {}
|
||||
",
|
||||
)
|
||||
}
|
||||
}
|
317
crates/assists/src/handlers/extract_struct_from_enum_variant.rs
Normal file
317
crates/assists/src/handlers/extract_struct_from_enum_variant.rs
Normal file
|
@ -0,0 +1,317 @@
|
|||
use base_db::FileId;
|
||||
use hir::{EnumVariant, Module, ModuleDef, Name};
|
||||
use ide_db::{defs::Definition, search::Reference, RootDatabase};
|
||||
use rustc_hash::FxHashSet;
|
||||
use syntax::{
|
||||
algo::find_node_at_offset,
|
||||
ast::{self, edit::IndentLevel, ArgListOwner, AstNode, NameOwner, VisibilityOwner},
|
||||
SourceFile, TextRange, TextSize,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
assist_context::AssistBuilder, utils::insert_use_statement, AssistContext, AssistId,
|
||||
AssistKind, Assists,
|
||||
};
|
||||
|
||||
// Assist: extract_struct_from_enum_variant
|
||||
//
|
||||
// Extracts a struct from enum variant.
|
||||
//
|
||||
// ```
|
||||
// enum A { <|>One(u32, u32) }
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// struct One(pub u32, pub u32);
|
||||
//
|
||||
// enum A { One(One) }
|
||||
// ```
|
||||
pub(crate) fn extract_struct_from_enum_variant(
|
||||
acc: &mut Assists,
|
||||
ctx: &AssistContext,
|
||||
) -> Option<()> {
|
||||
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) {
|
||||
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 enum_module_def = ModuleDef::from(enum_hir);
|
||||
let current_module = enum_hir.module(ctx.db());
|
||||
let target = variant.syntax().text_range();
|
||||
acc.add(
|
||||
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.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);
|
||||
for reference in res {
|
||||
let source_file = ctx.sema.parse(reference.file_range.file_id);
|
||||
update_reference(
|
||||
ctx,
|
||||
builder,
|
||||
reference,
|
||||
&source_file,
|
||||
&enum_module_def,
|
||||
&variant_hir_name,
|
||||
&mut visited_modules_set,
|
||||
);
|
||||
}
|
||||
extract_struct_def(
|
||||
builder,
|
||||
&enum_ast,
|
||||
&variant_name,
|
||||
&field_list.to_string(),
|
||||
start_offset,
|
||||
ctx.frange.file_id,
|
||||
&visibility,
|
||||
);
|
||||
let list_range = field_list.syntax().text_range();
|
||||
update_variant(builder, &variant_name, ctx.frange.file_id, list_range);
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn existing_struct_def(db: &RootDatabase, variant_name: &str, variant: &EnumVariant) -> bool {
|
||||
variant
|
||||
.parent_enum(db)
|
||||
.module(db)
|
||||
.scope(db, None)
|
||||
.into_iter()
|
||||
.any(|(name, _)| name.to_string() == variant_name.to_string())
|
||||
}
|
||||
|
||||
fn insert_import(
|
||||
ctx: &AssistContext,
|
||||
builder: &mut AssistBuilder,
|
||||
path: &ast::PathExpr,
|
||||
module: &Module,
|
||||
enum_module_def: &ModuleDef,
|
||||
variant_hir_name: &Name,
|
||||
) -> Option<()> {
|
||||
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();
|
||||
mod_path.segments.push(variant_hir_name.clone());
|
||||
insert_use_statement(path.syntax(), &mod_path, ctx, builder.text_edit_builder());
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
|
||||
// FIXME: this should use strongly-typed `make`, rather than string manipulation.
|
||||
fn extract_struct_def(
|
||||
builder: &mut AssistBuilder,
|
||||
enum_: &ast::Enum,
|
||||
variant_name: &str,
|
||||
variant_list: &str,
|
||||
start_offset: TextSize,
|
||||
file_id: FileId,
|
||||
visibility: &Option<ast::Visibility>,
|
||||
) -> Option<()> {
|
||||
let visibility_string = if let Some(visibility) = visibility {
|
||||
format!("{} ", visibility.to_string())
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
let indent = IndentLevel::from_node(enum_.syntax());
|
||||
let struct_def = format!(
|
||||
r#"{}struct {}{};
|
||||
|
||||
{}"#,
|
||||
visibility_string,
|
||||
variant_name,
|
||||
list_with_visibility(variant_list),
|
||||
indent
|
||||
);
|
||||
builder.edit_file(file_id);
|
||||
builder.insert(start_offset, struct_def);
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn update_variant(
|
||||
builder: &mut AssistBuilder,
|
||||
variant_name: &str,
|
||||
file_id: FileId,
|
||||
list_range: TextRange,
|
||||
) -> Option<()> {
|
||||
let inside_variant_range = TextRange::new(
|
||||
list_range.start().checked_add(TextSize::from(1))?,
|
||||
list_range.end().checked_sub(TextSize::from(1))?,
|
||||
);
|
||||
builder.edit_file(file_id);
|
||||
builder.replace(inside_variant_range, variant_name);
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn update_reference(
|
||||
ctx: &AssistContext,
|
||||
builder: &mut AssistBuilder,
|
||||
reference: Reference,
|
||||
source_file: &SourceFile,
|
||||
enum_module_def: &ModuleDef,
|
||||
variant_hir_name: &Name,
|
||||
visited_modules_set: &mut FxHashSet<Module>,
|
||||
) -> Option<()> {
|
||||
let path_expr: ast::PathExpr = find_node_at_offset::<ast::PathExpr>(
|
||||
source_file.syntax(),
|
||||
reference.file_range.range.start(),
|
||||
)?;
|
||||
let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
|
||||
let list = call.arg_list()?;
|
||||
let segment = path_expr.path()?.segment()?;
|
||||
let module = ctx.sema.scope(&path_expr.syntax()).module()?;
|
||||
let list_range = list.syntax().text_range();
|
||||
let inside_list_range = TextRange::new(
|
||||
list_range.start().checked_add(TextSize::from(1))?,
|
||||
list_range.end().checked_sub(TextSize::from(1))?,
|
||||
);
|
||||
builder.edit_file(reference.file_range.file_id);
|
||||
if !visited_modules_set.contains(&module) {
|
||||
if insert_import(ctx, builder, &path_expr, &module, enum_module_def, variant_hir_name)
|
||||
.is_some()
|
||||
{
|
||||
visited_modules_set.insert(module);
|
||||
}
|
||||
}
|
||||
builder.replace(inside_list_range, format!("{}{}", segment, list));
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn list_with_visibility(list: &str) -> String {
|
||||
list.split(',')
|
||||
.map(|part| {
|
||||
let index = if part.chars().next().unwrap() == '(' { 1usize } else { 0 };
|
||||
let mut mod_part = part.trim().to_string();
|
||||
mod_part.insert_str(index, "pub ");
|
||||
mod_part
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use crate::{
|
||||
tests::{check_assist, check_assist_not_applicable},
|
||||
utils::FamousDefs,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_extract_struct_several_fields() {
|
||||
check_assist(
|
||||
extract_struct_from_enum_variant,
|
||||
"enum A { <|>One(u32, u32) }",
|
||||
r#"struct One(pub u32, pub u32);
|
||||
|
||||
enum A { One(One) }"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_struct_one_field() {
|
||||
check_assist(
|
||||
extract_struct_from_enum_variant,
|
||||
"enum A { <|>One(u32) }",
|
||||
r#"struct One(pub u32);
|
||||
|
||||
enum A { One(One) }"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_struct_pub_visibility() {
|
||||
check_assist(
|
||||
extract_struct_from_enum_variant,
|
||||
"pub enum A { <|>One(u32, u32) }",
|
||||
r#"pub struct One(pub u32, pub u32);
|
||||
|
||||
pub enum A { One(One) }"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_struct_with_complex_imports() {
|
||||
check_assist(
|
||||
extract_struct_from_enum_variant,
|
||||
r#"mod my_mod {
|
||||
fn another_fn() {
|
||||
let m = my_other_mod::MyEnum::MyField(1, 1);
|
||||
}
|
||||
|
||||
pub mod my_other_mod {
|
||||
fn another_fn() {
|
||||
let m = MyEnum::MyField(1, 1);
|
||||
}
|
||||
|
||||
pub enum MyEnum {
|
||||
<|>MyField(u8, u8),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn another_fn() {
|
||||
let m = my_mod::my_other_mod::MyEnum::MyField(1, 1);
|
||||
}"#,
|
||||
r#"use my_mod::my_other_mod::MyField;
|
||||
|
||||
mod my_mod {
|
||||
use my_other_mod::MyField;
|
||||
|
||||
fn another_fn() {
|
||||
let m = my_other_mod::MyEnum::MyField(MyField(1, 1));
|
||||
}
|
||||
|
||||
pub mod my_other_mod {
|
||||
fn another_fn() {
|
||||
let m = MyEnum::MyField(MyField(1, 1));
|
||||
}
|
||||
|
||||
pub struct MyField(pub u8, pub u8);
|
||||
|
||||
pub enum MyEnum {
|
||||
MyField(MyField),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn another_fn() {
|
||||
let m = my_mod::my_other_mod::MyEnum::MyField(MyField(1, 1));
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
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(extract_struct_from_enum_variant, &fixture)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_enum_not_applicable_for_element_with_no_fields() {
|
||||
check_not_applicable("enum A { <|>One }");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_enum_not_applicable_if_struct_exists() {
|
||||
check_not_applicable(
|
||||
r#"struct One;
|
||||
enum A { <|>One(u8) }"#,
|
||||
);
|
||||
}
|
||||
}
|
588
crates/assists/src/handlers/extract_variable.rs
Normal file
588
crates/assists/src/handlers/extract_variable.rs
Normal file
|
@ -0,0 +1,588 @@
|
|||
use stdx::format_to;
|
||||
use syntax::{
|
||||
ast::{self, AstNode},
|
||||
SyntaxKind::{
|
||||
BLOCK_EXPR, BREAK_EXPR, CLOSURE_EXPR, COMMENT, LOOP_EXPR, MATCH_ARM, PATH_EXPR, RETURN_EXPR,
|
||||
},
|
||||
SyntaxNode,
|
||||
};
|
||||
use test_utils::mark;
|
||||
|
||||
use crate::{AssistContext, AssistId, AssistKind, Assists};
|
||||
|
||||
// Assist: extract_variable
|
||||
//
|
||||
// Extracts subexpression into a variable.
|
||||
//
|
||||
// ```
|
||||
// fn main() {
|
||||
// <|>(1 + 2)<|> * 4;
|
||||
// }
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// fn main() {
|
||||
// let $0var_name = (1 + 2);
|
||||
// var_name * 4;
|
||||
// }
|
||||
// ```
|
||||
pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
if ctx.frange.range.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let node = ctx.covering_element();
|
||||
if node.kind() == COMMENT {
|
||||
mark::hit!(extract_var_in_comment_is_not_applicable);
|
||||
return 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 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 let Anchor::WrapInBlock(_) = anchor {
|
||||
format_to!(buf, "{{ let {} = ", var_name);
|
||||
} else {
|
||||
format_to!(buf, "let {} = ", var_name);
|
||||
};
|
||||
format_to!(buf, "{}", to_extract.syntax());
|
||||
|
||||
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.insert_snippet(cap, offset, snip)
|
||||
}
|
||||
None => edit.insert(offset, buf),
|
||||
}
|
||||
|
||||
if let Anchor::WrapInBlock(_) = anchor {
|
||||
edit.insert(anchor.syntax().text_range().end(), " }");
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Check whether the node is a valid expression which can be extracted to a variable.
|
||||
/// In general that's true for any expression, but in some cases that would produce invalid code.
|
||||
fn valid_target_expr(node: SyntaxNode) -> Option<ast::Expr> {
|
||||
match node.kind() {
|
||||
PATH_EXPR | LOOP_EXPR => None,
|
||||
BREAK_EXPR => ast::BreakExpr::cast(node).and_then(|e| e.expr()),
|
||||
RETURN_EXPR => ast::ReturnExpr::cast(node).and_then(|e| e.expr()),
|
||||
BLOCK_EXPR => {
|
||||
ast::BlockExpr::cast(node).filter(|it| it.is_standalone()).map(ast::Expr::from)
|
||||
}
|
||||
_ => ast::Expr::cast(node),
|
||||
}
|
||||
}
|
||||
|
||||
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() == CLOSURE_EXPR {
|
||||
return Some(Anchor::WrapInBlock(node));
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
fn syntax(&self) -> &SyntaxNode {
|
||||
match self {
|
||||
Anchor::Before(it) | Anchor::WrapInBlock(it) => it,
|
||||
Anchor::Replace(stmt) => stmt.syntax(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use test_utils::mark;
|
||||
|
||||
use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_extract_var_simple() {
|
||||
check_assist(
|
||||
extract_variable,
|
||||
r#"
|
||||
fn foo() {
|
||||
foo(<|>1 + 1<|>);
|
||||
}"#,
|
||||
r#"
|
||||
fn foo() {
|
||||
let $0var_name = 1 + 1;
|
||||
foo(var_name);
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_var_in_comment_is_not_applicable() {
|
||||
mark::check!(extract_var_in_comment_is_not_applicable);
|
||||
check_assist_not_applicable(extract_variable, "fn main() { 1 + /* <|>comment<|> */ 1; }");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_var_expr_stmt() {
|
||||
mark::check!(test_extract_var_expr_stmt);
|
||||
check_assist(
|
||||
extract_variable,
|
||||
r#"
|
||||
fn foo() {
|
||||
<|>1 + 1<|>;
|
||||
}"#,
|
||||
r#"
|
||||
fn foo() {
|
||||
let $0var_name = 1 + 1;
|
||||
}"#,
|
||||
);
|
||||
check_assist(
|
||||
extract_variable,
|
||||
"
|
||||
fn foo() {
|
||||
<|>{ let x = 0; x }<|>
|
||||
something_else();
|
||||
}",
|
||||
"
|
||||
fn foo() {
|
||||
let $0var_name = { let x = 0; x };
|
||||
something_else();
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_var_part_of_expr_stmt() {
|
||||
check_assist(
|
||||
extract_variable,
|
||||
"
|
||||
fn foo() {
|
||||
<|>1<|> + 1;
|
||||
}",
|
||||
"
|
||||
fn foo() {
|
||||
let $0var_name = 1;
|
||||
var_name + 1;
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_var_last_expr() {
|
||||
mark::check!(test_extract_var_last_expr);
|
||||
check_assist(
|
||||
extract_variable,
|
||||
r#"
|
||||
fn foo() {
|
||||
bar(<|>1 + 1<|>)
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn foo() {
|
||||
let $0var_name = 1 + 1;
|
||||
bar(var_name)
|
||||
}
|
||||
"#,
|
||||
);
|
||||
check_assist(
|
||||
extract_variable,
|
||||
r#"
|
||||
fn foo() {
|
||||
<|>bar(1 + 1)<|>
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn foo() {
|
||||
let $0var_name = bar(1 + 1);
|
||||
var_name
|
||||
}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_var_in_match_arm_no_block() {
|
||||
check_assist(
|
||||
extract_variable,
|
||||
"
|
||||
fn main() {
|
||||
let x = true;
|
||||
let tuple = match x {
|
||||
true => (<|>2 + 2<|>, true)
|
||||
_ => (0, false)
|
||||
};
|
||||
}
|
||||
",
|
||||
"
|
||||
fn main() {
|
||||
let x = true;
|
||||
let tuple = match x {
|
||||
true => { let $0var_name = 2 + 2; (var_name, true) }
|
||||
_ => (0, false)
|
||||
};
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_var_in_match_arm_with_block() {
|
||||
check_assist(
|
||||
extract_variable,
|
||||
"
|
||||
fn main() {
|
||||
let x = true;
|
||||
let tuple = match x {
|
||||
true => {
|
||||
let y = 1;
|
||||
(<|>2 + y<|>, true)
|
||||
}
|
||||
_ => (0, false)
|
||||
};
|
||||
}
|
||||
",
|
||||
"
|
||||
fn main() {
|
||||
let x = true;
|
||||
let tuple = match x {
|
||||
true => {
|
||||
let y = 1;
|
||||
let $0var_name = 2 + y;
|
||||
(var_name, true)
|
||||
}
|
||||
_ => (0, false)
|
||||
};
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_var_in_closure_no_block() {
|
||||
check_assist(
|
||||
extract_variable,
|
||||
"
|
||||
fn main() {
|
||||
let lambda = |x: u32| <|>x * 2<|>;
|
||||
}
|
||||
",
|
||||
"
|
||||
fn main() {
|
||||
let lambda = |x: u32| { let $0var_name = x * 2; var_name };
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_var_in_closure_with_block() {
|
||||
check_assist(
|
||||
extract_variable,
|
||||
"
|
||||
fn main() {
|
||||
let lambda = |x: u32| { <|>x * 2<|> };
|
||||
}
|
||||
",
|
||||
"
|
||||
fn main() {
|
||||
let lambda = |x: u32| { let $0var_name = x * 2; var_name };
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_var_path_simple() {
|
||||
check_assist(
|
||||
extract_variable,
|
||||
"
|
||||
fn main() {
|
||||
let o = <|>Some(true)<|>;
|
||||
}
|
||||
",
|
||||
"
|
||||
fn main() {
|
||||
let $0var_name = Some(true);
|
||||
let o = var_name;
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_var_path_method() {
|
||||
check_assist(
|
||||
extract_variable,
|
||||
"
|
||||
fn main() {
|
||||
let v = <|>bar.foo()<|>;
|
||||
}
|
||||
",
|
||||
"
|
||||
fn main() {
|
||||
let $0var_name = bar.foo();
|
||||
let v = var_name;
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_var_return() {
|
||||
check_assist(
|
||||
extract_variable,
|
||||
"
|
||||
fn foo() -> u32 {
|
||||
<|>return 2 + 2<|>;
|
||||
}
|
||||
",
|
||||
"
|
||||
fn foo() -> u32 {
|
||||
let $0var_name = 2 + 2;
|
||||
return var_name;
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_var_does_not_add_extra_whitespace() {
|
||||
check_assist(
|
||||
extract_variable,
|
||||
"
|
||||
fn foo() -> u32 {
|
||||
|
||||
|
||||
<|>return 2 + 2<|>;
|
||||
}
|
||||
",
|
||||
"
|
||||
fn foo() -> u32 {
|
||||
|
||||
|
||||
let $0var_name = 2 + 2;
|
||||
return var_name;
|
||||
}
|
||||
",
|
||||
);
|
||||
|
||||
check_assist(
|
||||
extract_variable,
|
||||
"
|
||||
fn foo() -> u32 {
|
||||
|
||||
<|>return 2 + 2<|>;
|
||||
}
|
||||
",
|
||||
"
|
||||
fn foo() -> u32 {
|
||||
|
||||
let $0var_name = 2 + 2;
|
||||
return var_name;
|
||||
}
|
||||
",
|
||||
);
|
||||
|
||||
check_assist(
|
||||
extract_variable,
|
||||
"
|
||||
fn foo() -> u32 {
|
||||
let foo = 1;
|
||||
|
||||
// bar
|
||||
|
||||
|
||||
<|>return 2 + 2<|>;
|
||||
}
|
||||
",
|
||||
"
|
||||
fn foo() -> u32 {
|
||||
let foo = 1;
|
||||
|
||||
// bar
|
||||
|
||||
|
||||
let $0var_name = 2 + 2;
|
||||
return var_name;
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_var_break() {
|
||||
check_assist(
|
||||
extract_variable,
|
||||
"
|
||||
fn main() {
|
||||
let result = loop {
|
||||
<|>break 2 + 2<|>;
|
||||
};
|
||||
}
|
||||
",
|
||||
"
|
||||
fn main() {
|
||||
let result = loop {
|
||||
let $0var_name = 2 + 2;
|
||||
break var_name;
|
||||
};
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_var_for_cast() {
|
||||
check_assist(
|
||||
extract_variable,
|
||||
"
|
||||
fn main() {
|
||||
let v = <|>0f32 as u32<|>;
|
||||
}
|
||||
",
|
||||
"
|
||||
fn main() {
|
||||
let $0var_name = 0f32 as u32;
|
||||
let v = var_name;
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_var_field_shorthand() {
|
||||
check_assist(
|
||||
extract_variable,
|
||||
r#"
|
||||
struct S {
|
||||
foo: i32
|
||||
}
|
||||
|
||||
fn main() {
|
||||
S { foo: <|>1 + 1<|> }
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
struct S {
|
||||
foo: i32
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let $0foo = 1 + 1;
|
||||
S { foo }
|
||||
}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_var_for_return_not_applicable() {
|
||||
check_assist_not_applicable(extract_variable, "fn foo() { <|>return<|>; } ");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_var_for_break_not_applicable() {
|
||||
check_assist_not_applicable(extract_variable, "fn main() { loop { <|>break<|>; }; }");
|
||||
}
|
||||
|
||||
// FIXME: This is not quite correct, but good enough(tm) for the sorting heuristic
|
||||
#[test]
|
||||
fn extract_var_target() {
|
||||
check_assist_target(extract_variable, "fn foo() -> u32 { <|>return 2 + 2<|>; }", "2 + 2");
|
||||
|
||||
check_assist_target(
|
||||
extract_variable,
|
||||
"
|
||||
fn main() {
|
||||
let x = true;
|
||||
let tuple = match x {
|
||||
true => (<|>2 + 2<|>, true)
|
||||
_ => (0, false)
|
||||
};
|
||||
}
|
||||
",
|
||||
"2 + 2",
|
||||
);
|
||||
}
|
||||
}
|
747
crates/assists/src/handlers/fill_match_arms.rs
Normal file
747
crates/assists/src/handlers/fill_match_arms.rs
Normal file
|
@ -0,0 +1,747 @@
|
|||
use std::iter;
|
||||
|
||||
use hir::{Adt, HasSource, ModuleDef, Semantics};
|
||||
use ide_db::RootDatabase;
|
||||
use itertools::Itertools;
|
||||
use syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat};
|
||||
use test_utils::mark;
|
||||
|
||||
use crate::{
|
||||
utils::{render_snippet, Cursor, FamousDefs},
|
||||
AssistContext, AssistId, AssistKind, Assists,
|
||||
};
|
||||
|
||||
// Assist: fill_match_arms
|
||||
//
|
||||
// Adds missing clauses to a `match` expression.
|
||||
//
|
||||
// ```
|
||||
// enum Action { Move { distance: u32 }, Stop }
|
||||
//
|
||||
// fn handle(action: Action) {
|
||||
// match action {
|
||||
// <|>
|
||||
// }
|
||||
// }
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// enum Action { Move { distance: u32 }, Stop }
|
||||
//
|
||||
// fn handle(action: Action) {
|
||||
// match action {
|
||||
// $0Action::Move { distance } => {}
|
||||
// Action::Stop => {}
|
||||
// }
|
||||
// }
|
||||
// ```
|
||||
pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
let match_expr = ctx.find_node_at_offset::<ast::MatchExpr>()?;
|
||||
let match_arm_list = match_expr.match_arm_list()?;
|
||||
|
||||
let expr = match_expr.expr()?;
|
||||
|
||||
let mut arms: Vec<MatchArm> = match_arm_list.arms().collect();
|
||||
if arms.len() == 1 {
|
||||
if let Some(Pat::WildcardPat(..)) = arms[0].pat() {
|
||||
arms.clear();
|
||||
}
|
||||
}
|
||||
|
||||
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 mut variants = variants
|
||||
.into_iter()
|
||||
.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<_>>();
|
||||
if Some(enum_def) == FamousDefs(&ctx.sema, module.krate()).core_option_Option() {
|
||||
// Match `Some` variant first.
|
||||
mark::hit!(option_order);
|
||||
variants.reverse()
|
||||
}
|
||||
variants
|
||||
} else if let Some(enum_defs) = resolve_tuple_of_enum_def(&ctx.sema, &expr) {
|
||||
// Partial fill not currently supported for tuple of enums.
|
||||
if !arms.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// We do not currently support filling match arms for a tuple
|
||||
// containing a single enum.
|
||||
if enum_defs.len() < 2 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// When calculating the match arms for a tuple of enums, we want
|
||||
// to create a match arm for each possible combination of enum
|
||||
// values. The `multi_cartesian_product` method transforms
|
||||
// Vec<Vec<EnumVariant>> into Vec<(EnumVariant, .., EnumVariant)>
|
||||
// where each tuple represents a proposed match arm.
|
||||
enum_defs
|
||||
.into_iter()
|
||||
.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));
|
||||
ast::Pat::from(make::tuple_pat(patterns))
|
||||
})
|
||||
.filter(|variant_pat| is_variant_missing(&mut arms, variant_pat))
|
||||
.map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block()))
|
||||
.collect()
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
if missing_arms.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let target = match_expr.syntax().text_range();
|
||||
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::WildcardPat::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()),
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn is_variant_missing(existing_arms: &mut Vec<MatchArm>, var: &Pat) -> bool {
|
||||
existing_arms.iter().filter_map(|arm| arm.pat()).all(|pat| {
|
||||
// Special casee OrPat as separate top-level pats
|
||||
let top_level_pats: Vec<Pat> = match pat {
|
||||
Pat::OrPat(pats) => pats.pats().collect::<Vec<_>>(),
|
||||
_ => vec![pat],
|
||||
};
|
||||
|
||||
!top_level_pats.iter().any(|pat| does_pat_match_variant(pat, var))
|
||||
})
|
||||
}
|
||||
|
||||
fn does_pat_match_variant(pat: &Pat, var: &Pat) -> bool {
|
||||
let first_node_text = |pat: &Pat| pat.syntax().first_child().map(|node| node.text());
|
||||
|
||||
let pat_head = match pat {
|
||||
Pat::IdentPat(bind_pat) => {
|
||||
if let Some(p) = bind_pat.pat() {
|
||||
first_node_text(&p)
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
pat => first_node_text(pat),
|
||||
};
|
||||
|
||||
let var_head = first_node_text(var);
|
||||
|
||||
pat_head == var_head
|
||||
}
|
||||
|
||||
fn resolve_enum_def(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<hir::Enum> {
|
||||
sema.type_of_expr(&expr)?.autoderef(sema.db).find_map(|ty| match ty.as_adt() {
|
||||
Some(Adt::Enum(e)) => Some(e),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
fn resolve_tuple_of_enum_def(
|
||||
sema: &Semantics<RootDatabase>,
|
||||
expr: &ast::Expr,
|
||||
) -> Option<Vec<hir::Enum>> {
|
||||
sema.type_of_expr(&expr)?
|
||||
.tuple_fields(sema.db)
|
||||
.iter()
|
||||
.map(|ty| {
|
||||
ty.autoderef(sema.db).find_map(|ty| match ty.as_adt() {
|
||||
Some(Adt::Enum(e)) => Some(e),
|
||||
// For now we only handle expansion for a tuple of enums. Here
|
||||
// we map non-enum items to None and rely on `collect` to
|
||||
// convert Vec<Option<hir::Enum>> into Option<Vec<hir::Enum>>.
|
||||
_ => None,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn build_pat(db: &RootDatabase, module: hir::Module, var: hir::EnumVariant) -> Option<ast::Pat> {
|
||||
let path = crate::ast_transform::path_to_ast(module.find_use_path(db, ModuleDef::from(var))?);
|
||||
|
||||
// FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though
|
||||
let pat: ast::Pat = match var.source(db).value.kind() {
|
||||
ast::StructKind::Tuple(field_list) => {
|
||||
let pats = iter::repeat(make::wildcard_pat().into()).take(field_list.fields().count());
|
||||
make::tuple_struct_pat(path, pats).into()
|
||||
}
|
||||
ast::StructKind::Record(field_list) => {
|
||||
let pats = field_list.fields().map(|f| make::ident_pat(f.name().unwrap()).into());
|
||||
make::record_pat(path, pats).into()
|
||||
}
|
||||
ast::StructKind::Unit => make::path_pat(path),
|
||||
};
|
||||
|
||||
Some(pat)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use test_utils::mark;
|
||||
|
||||
use crate::{
|
||||
tests::{check_assist, check_assist_not_applicable, check_assist_target},
|
||||
utils::FamousDefs,
|
||||
};
|
||||
|
||||
use super::fill_match_arms;
|
||||
|
||||
#[test]
|
||||
fn all_match_arms_provided() {
|
||||
check_assist_not_applicable(
|
||||
fill_match_arms,
|
||||
r#"
|
||||
enum A {
|
||||
As,
|
||||
Bs{x:i32, y:Option<i32>},
|
||||
Cs(i32, Option<i32>),
|
||||
}
|
||||
fn main() {
|
||||
match A::As<|> {
|
||||
A::As,
|
||||
A::Bs{x,y:Some(_)} => {}
|
||||
A::Cs(_, Some(_)) => {}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tuple_of_non_enum() {
|
||||
// for now this case is not handled, although it potentially could be
|
||||
// in the future
|
||||
check_assist_not_applicable(
|
||||
fill_match_arms,
|
||||
r#"
|
||||
fn main() {
|
||||
match (0, false)<|> {
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_fill_record_tuple() {
|
||||
check_assist(
|
||||
fill_match_arms,
|
||||
r#"
|
||||
enum A {
|
||||
As,
|
||||
Bs { x: i32, y: Option<i32> },
|
||||
Cs(i32, Option<i32>),
|
||||
}
|
||||
fn main() {
|
||||
match A::As<|> {
|
||||
A::Bs { x, y: Some(_) } => {}
|
||||
A::Cs(_, Some(_)) => {}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
enum A {
|
||||
As,
|
||||
Bs { x: i32, y: Option<i32> },
|
||||
Cs(i32, Option<i32>),
|
||||
}
|
||||
fn main() {
|
||||
match A::As {
|
||||
A::Bs { x, y: Some(_) } => {}
|
||||
A::Cs(_, Some(_)) => {}
|
||||
$0A::As => {}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_fill_or_pat() {
|
||||
check_assist(
|
||||
fill_match_arms,
|
||||
r#"
|
||||
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 => {}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_fill() {
|
||||
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) => (),
|
||||
}
|
||||
}
|
||||
"#,
|
||||
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 => {}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_fill_bind_pat() {
|
||||
check_assist(
|
||||
fill_match_arms,
|
||||
r#"
|
||||
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(_) => {}
|
||||
A::Cs(${0:_}) => {}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fill_match_arms_empty_body() {
|
||||
check_assist(
|
||||
fill_match_arms,
|
||||
r#"
|
||||
enum A { As, Bs, Cs(String), Ds(String, String), Es { x: usize, y: usize } }
|
||||
|
||||
fn main() {
|
||||
let a = A::As;
|
||||
match a<|> {}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
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 } => {}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fill_match_arms_tuple_of_enum() {
|
||||
check_assist(
|
||||
fill_match_arms,
|
||||
r#"
|
||||
enum A { One, Two }
|
||||
enum B { One, Two }
|
||||
|
||||
fn main() {
|
||||
let a = A::One;
|
||||
let b = B::One;
|
||||
match (a<|>, b) {}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
enum A { One, Two }
|
||||
enum B { One, Two }
|
||||
|
||||
fn main() {
|
||||
let a = A::One;
|
||||
let b = B::One;
|
||||
match (a, b) {
|
||||
$0(A::One, B::One) => {}
|
||||
(A::One, B::Two) => {}
|
||||
(A::Two, B::One) => {}
|
||||
(A::Two, B::Two) => {}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fill_match_arms_tuple_of_enum_ref() {
|
||||
check_assist(
|
||||
fill_match_arms,
|
||||
r#"
|
||||
enum A { One, Two }
|
||||
enum B { One, Two }
|
||||
|
||||
fn main() {
|
||||
let a = A::One;
|
||||
let b = B::One;
|
||||
match (&a<|>, &b) {}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
enum A { One, Two }
|
||||
enum B { One, Two }
|
||||
|
||||
fn main() {
|
||||
let a = A::One;
|
||||
let b = B::One;
|
||||
match (&a, &b) {
|
||||
$0(A::One, B::One) => {}
|
||||
(A::One, B::Two) => {}
|
||||
(A::Two, B::One) => {}
|
||||
(A::Two, B::Two) => {}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fill_match_arms_tuple_of_enum_partial() {
|
||||
check_assist_not_applicable(
|
||||
fill_match_arms,
|
||||
r#"
|
||||
enum A { One, Two }
|
||||
enum B { One, Two }
|
||||
|
||||
fn main() {
|
||||
let a = A::One;
|
||||
let b = B::One;
|
||||
match (a<|>, b) {
|
||||
(A::Two, B::One) => {}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fill_match_arms_tuple_of_enum_not_applicable() {
|
||||
check_assist_not_applicable(
|
||||
fill_match_arms,
|
||||
r#"
|
||||
enum A { One, Two }
|
||||
enum B { One, Two }
|
||||
|
||||
fn main() {
|
||||
let a = A::One;
|
||||
let b = B::One;
|
||||
match (a<|>, b) {
|
||||
(A::Two, B::One) => {}
|
||||
(A::One, B::One) => {}
|
||||
(A::One, B::Two) => {}
|
||||
(A::Two, B::Two) => {}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fill_match_arms_single_element_tuple_of_enum() {
|
||||
// For now we don't hande the case of a single element tuple, but
|
||||
// we could handle this in the future if `make::tuple_pat` allowed
|
||||
// creating a tuple with a single pattern.
|
||||
check_assist_not_applicable(
|
||||
fill_match_arms,
|
||||
r#"
|
||||
enum A { One, Two }
|
||||
|
||||
fn main() {
|
||||
let a = A::One;
|
||||
match (a<|>, ) {
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fill_match_arm_refs() {
|
||||
check_assist(
|
||||
fill_match_arms,
|
||||
r#"
|
||||
enum A { As }
|
||||
|
||||
fn foo(a: &A) {
|
||||
match a<|> {
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
enum A { As }
|
||||
|
||||
fn foo(a: &A) {
|
||||
match a {
|
||||
$0A::As => {}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
|
||||
check_assist(
|
||||
fill_match_arms,
|
||||
r#"
|
||||
enum A {
|
||||
Es { x: usize, y: usize }
|
||||
}
|
||||
|
||||
fn foo(a: &mut A) {
|
||||
match a<|> {
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
enum A {
|
||||
Es { x: usize, y: usize }
|
||||
}
|
||||
|
||||
fn foo(a: &mut A) {
|
||||
match a {
|
||||
$0A::Es { x, y } => {}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fill_match_arms_target() {
|
||||
check_assist_target(
|
||||
fill_match_arms,
|
||||
r#"
|
||||
enum E { X, Y }
|
||||
|
||||
fn main() {
|
||||
match E::X<|> {}
|
||||
}
|
||||
"#,
|
||||
"match E::X {}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fill_match_arms_trivial_arm() {
|
||||
check_assist(
|
||||
fill_match_arms,
|
||||
r#"
|
||||
enum E { X, Y }
|
||||
|
||||
fn main() {
|
||||
match E::X {
|
||||
<|>_ => {}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
enum E { X, Y }
|
||||
|
||||
fn main() {
|
||||
match E::X {
|
||||
$0E::X => {}
|
||||
E::Y => {}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fill_match_arms_qualifies_path() {
|
||||
check_assist(
|
||||
fill_match_arms,
|
||||
r#"
|
||||
mod foo { pub enum E { X, Y } }
|
||||
use foo::E::X;
|
||||
|
||||
fn main() {
|
||||
match X {
|
||||
<|>
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
mod foo { pub enum E { X, Y } }
|
||||
use foo::E::X;
|
||||
|
||||
fn main() {
|
||||
match X {
|
||||
$0X => {}
|
||||
foo::E::Y => {}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fill_match_arms_preserves_comments() {
|
||||
check_assist(
|
||||
fill_match_arms,
|
||||
r#"
|
||||
enum A { One, Two }
|
||||
fn foo(a: A) {
|
||||
match a {
|
||||
// foo bar baz<|>
|
||||
A::One => {}
|
||||
// This is where the rest should be
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
enum A { One, Two }
|
||||
fn foo(a: A) {
|
||||
match a {
|
||||
// foo bar baz
|
||||
A::One => {}
|
||||
// This is where the rest should be
|
||||
$0A::Two => {}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fill_match_arms_preserves_comments_empty() {
|
||||
check_assist(
|
||||
fill_match_arms,
|
||||
r#"
|
||||
enum A { One, Two }
|
||||
fn foo(a: A) {
|
||||
match a {
|
||||
// foo bar baz<|>
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
enum A { One, Two }
|
||||
fn foo(a: A) {
|
||||
match a {
|
||||
// foo bar baz
|
||||
$0A::One => {}
|
||||
A::Two => {}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fill_match_arms_placeholder() {
|
||||
check_assist(
|
||||
fill_match_arms,
|
||||
r#"
|
||||
enum A { One, Two, }
|
||||
fn foo(a: A) {
|
||||
match a<|> {
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
enum A { One, Two, }
|
||||
fn foo(a: A) {
|
||||
match a {
|
||||
$0A::One => {}
|
||||
A::Two => {}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn option_order() {
|
||||
mark::check!(option_order);
|
||||
let before = r#"
|
||||
fn foo(opt: Option<i32>) {
|
||||
match opt<|> {
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let before = &format!("//- /main.rs crate:main deps:core{}{}", before, FamousDefs::FIXTURE);
|
||||
|
||||
check_assist(
|
||||
fill_match_arms,
|
||||
before,
|
||||
r#"
|
||||
fn foo(opt: Option<i32>) {
|
||||
match opt {
|
||||
Some(${0:_}) => {}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
}
|
607
crates/assists/src/handlers/fix_visibility.rs
Normal file
607
crates/assists/src/handlers/fix_visibility.rs
Normal file
|
@ -0,0 +1,607 @@
|
|||
use base_db::FileId;
|
||||
use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution};
|
||||
use syntax::{ast, AstNode, TextRange, TextSize};
|
||||
|
||||
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.
|
||||
|
||||
// Assist: fix_visibility
|
||||
//
|
||||
// Makes inaccessible item public.
|
||||
//
|
||||
// ```
|
||||
// mod m {
|
||||
// fn frobnicate() {}
|
||||
// }
|
||||
// fn main() {
|
||||
// m::frobnicate<|>() {}
|
||||
// }
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// mod m {
|
||||
// $0pub(crate) fn frobnicate() {}
|
||||
// }
|
||||
// fn main() {
|
||||
// m::frobnicate() {}
|
||||
// }
|
||||
// ```
|
||||
pub(crate) fn fix_visibility(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
add_vis_to_referenced_module_def(acc, ctx)
|
||||
.or_else(|| add_vis_to_referenced_record_field(acc, ctx))
|
||||
}
|
||||
|
||||
fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
let path: ast::Path = ctx.find_node_at_offset()?;
|
||||
let path_res = ctx.sema.resolve_path(&path)?;
|
||||
let def = match path_res {
|
||||
PathResolution::Def(def) => def,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let current_module = ctx.sema.scope(&path.syntax()).module()?;
|
||||
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()) {
|
||||
return None;
|
||||
};
|
||||
|
||||
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" };
|
||||
|
||||
let assist_label = match target_name {
|
||||
None => format!("Change visibility to {}", missing_visibility),
|
||||
Some(name) => format!("Change visibility of {} to {}", name, missing_visibility),
|
||||
};
|
||||
|
||||
acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |builder| {
|
||||
builder.edit_file(target_file);
|
||||
match ctx.config.snippet_cap {
|
||||
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::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()) {
|
||||
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 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), it.visibility(), s.text_range())
|
||||
}
|
||||
hir::FieldSource::Pos(it) => {
|
||||
let s = it.syntax();
|
||||
(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_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", AssistKind::QuickFix), assist_label, target, |builder| {
|
||||
builder.edit_file(target_file);
|
||||
match ctx.config.snippet_cap {
|
||||
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 target_data_for_def(
|
||||
db: &dyn HirDatabase,
|
||||
def: hir::ModuleDef,
|
||||
) -> Option<(TextSize, Option<ast::Visibility>, TextRange, FileId, Option<hir::Name>)> {
|
||||
fn offset_target_and_file_id<S, Ast>(
|
||||
db: &dyn HirDatabase,
|
||||
x: S,
|
||||
) -> (TextSize, Option<ast::Visibility>, TextRange, FileId)
|
||||
where
|
||||
S: HasSource<Ast = Ast>,
|
||||
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;
|
||||
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, current_visibility, target, target_file) = match def {
|
||||
hir::ModuleDef::Function(f) => {
|
||||
target_name = Some(f.name(db));
|
||||
offset_target_and_file_id(db, f)
|
||||
}
|
||||
hir::ModuleDef::Adt(adt) => {
|
||||
target_name = Some(adt.name(db));
|
||||
match adt {
|
||||
hir::Adt::Struct(s) => offset_target_and_file_id(db, s),
|
||||
hir::Adt::Union(u) => offset_target_and_file_id(db, u),
|
||||
hir::Adt::Enum(e) => offset_target_and_file_id(db, e),
|
||||
}
|
||||
}
|
||||
hir::ModuleDef::Const(c) => {
|
||||
target_name = c.name(db);
|
||||
offset_target_and_file_id(db, c)
|
||||
}
|
||||
hir::ModuleDef::Static(s) => {
|
||||
target_name = s.name(db);
|
||||
offset_target_and_file_id(db, s)
|
||||
}
|
||||
hir::ModuleDef::Trait(t) => {
|
||||
target_name = Some(t.name(db));
|
||||
offset_target_and_file_id(db, t)
|
||||
}
|
||||
hir::ModuleDef::TypeAlias(t) => {
|
||||
target_name = Some(t.name(db));
|
||||
offset_target_and_file_id(db, t)
|
||||
}
|
||||
hir::ModuleDef::Module(m) => {
|
||||
target_name = m.name(db);
|
||||
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), 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, current_visibility, target, target_file, target_name))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests::{check_assist, check_assist_not_applicable};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn fix_visibility_of_fn() {
|
||||
check_assist(
|
||||
fix_visibility,
|
||||
r"mod foo { fn foo() {} }
|
||||
fn main() { foo::foo<|>() } ",
|
||||
r"mod foo { $0pub(crate) fn foo() {} }
|
||||
fn main() { foo::foo() } ",
|
||||
);
|
||||
check_assist_not_applicable(
|
||||
fix_visibility,
|
||||
r"mod foo { pub fn foo() {} }
|
||||
fn main() { foo::foo<|>() } ",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fix_visibility_of_adt_in_submodule() {
|
||||
check_assist(
|
||||
fix_visibility,
|
||||
r"mod foo { struct Foo; }
|
||||
fn main() { foo::Foo<|> } ",
|
||||
r"mod foo { $0pub(crate) struct Foo; }
|
||||
fn main() { foo::Foo } ",
|
||||
);
|
||||
check_assist_not_applicable(
|
||||
fix_visibility,
|
||||
r"mod foo { pub struct Foo; }
|
||||
fn main() { foo::Foo<|> } ",
|
||||
);
|
||||
check_assist(
|
||||
fix_visibility,
|
||||
r"mod foo { enum Foo; }
|
||||
fn main() { foo::Foo<|> } ",
|
||||
r"mod foo { $0pub(crate) enum Foo; }
|
||||
fn main() { foo::Foo } ",
|
||||
);
|
||||
check_assist_not_applicable(
|
||||
fix_visibility,
|
||||
r"mod foo { pub enum Foo; }
|
||||
fn main() { foo::Foo<|> } ",
|
||||
);
|
||||
check_assist(
|
||||
fix_visibility,
|
||||
r"mod foo { union Foo; }
|
||||
fn main() { foo::Foo<|> } ",
|
||||
r"mod foo { $0pub(crate) union Foo; }
|
||||
fn main() { foo::Foo } ",
|
||||
);
|
||||
check_assist_not_applicable(
|
||||
fix_visibility,
|
||||
r"mod foo { pub union Foo; }
|
||||
fn main() { foo::Foo<|> } ",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fix_visibility_of_adt_in_other_file() {
|
||||
check_assist(
|
||||
fix_visibility,
|
||||
r"
|
||||
//- /main.rs
|
||||
mod foo;
|
||||
fn main() { foo::Foo<|> }
|
||||
|
||||
//- /foo.rs
|
||||
struct Foo;
|
||||
",
|
||||
r"$0pub(crate) struct Foo;
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fix_visibility_of_struct_field() {
|
||||
check_assist(
|
||||
fix_visibility,
|
||||
r"mod foo { pub struct Foo { bar: (), } }
|
||||
fn main() { foo::Foo { <|>bar: () }; } ",
|
||||
r"mod foo { pub struct Foo { $0pub(crate) bar: (), } }
|
||||
fn main() { foo::Foo { bar: () }; } ",
|
||||
);
|
||||
check_assist(
|
||||
fix_visibility,
|
||||
r"
|
||||
//- /lib.rs
|
||||
mod foo;
|
||||
fn main() { foo::Foo { <|>bar: () }; }
|
||||
//- /foo.rs
|
||||
pub struct Foo { bar: () }
|
||||
",
|
||||
r"pub struct Foo { $0pub(crate) bar: () }
|
||||
",
|
||||
);
|
||||
check_assist_not_applicable(
|
||||
fix_visibility,
|
||||
r"mod foo { pub struct Foo { pub bar: (), } }
|
||||
fn main() { foo::Foo { <|>bar: () }; } ",
|
||||
);
|
||||
check_assist_not_applicable(
|
||||
fix_visibility,
|
||||
r"
|
||||
//- /lib.rs
|
||||
mod foo;
|
||||
fn main() { foo::Foo { <|>bar: () }; }
|
||||
//- /foo.rs
|
||||
pub struct Foo { pub bar: () }
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fix_visibility_of_enum_variant_field() {
|
||||
check_assist(
|
||||
fix_visibility,
|
||||
r"mod foo { pub enum Foo { Bar { bar: () } } }
|
||||
fn main() { foo::Foo::Bar { <|>bar: () }; } ",
|
||||
r"mod foo { pub enum Foo { Bar { $0pub(crate) bar: () } } }
|
||||
fn main() { foo::Foo::Bar { bar: () }; } ",
|
||||
);
|
||||
check_assist(
|
||||
fix_visibility,
|
||||
r"
|
||||
//- /lib.rs
|
||||
mod foo;
|
||||
fn main() { foo::Foo::Bar { <|>bar: () }; }
|
||||
//- /foo.rs
|
||||
pub enum Foo { Bar { bar: () } }
|
||||
",
|
||||
r"pub enum Foo { Bar { $0pub(crate) bar: () } }
|
||||
",
|
||||
);
|
||||
check_assist_not_applicable(
|
||||
fix_visibility,
|
||||
r"mod foo { pub struct Foo { pub bar: (), } }
|
||||
fn main() { foo::Foo { <|>bar: () }; } ",
|
||||
);
|
||||
check_assist_not_applicable(
|
||||
fix_visibility,
|
||||
r"
|
||||
//- /lib.rs
|
||||
mod foo;
|
||||
fn main() { foo::Foo { <|>bar: () }; }
|
||||
//- /foo.rs
|
||||
pub struct Foo { pub bar: () }
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
// FIXME reenable this test when `Semantics::resolve_record_field` works with union fields
|
||||
fn fix_visibility_of_union_field() {
|
||||
check_assist(
|
||||
fix_visibility,
|
||||
r"mod foo { pub union Foo { bar: (), } }
|
||||
fn main() { foo::Foo { <|>bar: () }; } ",
|
||||
r"mod foo { pub union Foo { $0pub(crate) bar: (), } }
|
||||
fn main() { foo::Foo { bar: () }; } ",
|
||||
);
|
||||
check_assist(
|
||||
fix_visibility,
|
||||
r"
|
||||
//- /lib.rs
|
||||
mod foo;
|
||||
fn main() { foo::Foo { <|>bar: () }; }
|
||||
//- /foo.rs
|
||||
pub union Foo { bar: () }
|
||||
",
|
||||
r"pub union Foo { $0pub(crate) bar: () }
|
||||
",
|
||||
);
|
||||
check_assist_not_applicable(
|
||||
fix_visibility,
|
||||
r"mod foo { pub union Foo { pub bar: (), } }
|
||||
fn main() { foo::Foo { <|>bar: () }; } ",
|
||||
);
|
||||
check_assist_not_applicable(
|
||||
fix_visibility,
|
||||
r"
|
||||
//- /lib.rs
|
||||
mod foo;
|
||||
fn main() { foo::Foo { <|>bar: () }; }
|
||||
//- /foo.rs
|
||||
pub union Foo { pub bar: () }
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fix_visibility_of_const() {
|
||||
check_assist(
|
||||
fix_visibility,
|
||||
r"mod foo { const FOO: () = (); }
|
||||
fn main() { foo::FOO<|> } ",
|
||||
r"mod foo { $0pub(crate) const FOO: () = (); }
|
||||
fn main() { foo::FOO } ",
|
||||
);
|
||||
check_assist_not_applicable(
|
||||
fix_visibility,
|
||||
r"mod foo { pub const FOO: () = (); }
|
||||
fn main() { foo::FOO<|> } ",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fix_visibility_of_static() {
|
||||
check_assist(
|
||||
fix_visibility,
|
||||
r"mod foo { static FOO: () = (); }
|
||||
fn main() { foo::FOO<|> } ",
|
||||
r"mod foo { $0pub(crate) static FOO: () = (); }
|
||||
fn main() { foo::FOO } ",
|
||||
);
|
||||
check_assist_not_applicable(
|
||||
fix_visibility,
|
||||
r"mod foo { pub static FOO: () = (); }
|
||||
fn main() { foo::FOO<|> } ",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fix_visibility_of_trait() {
|
||||
check_assist(
|
||||
fix_visibility,
|
||||
r"mod foo { trait Foo { fn foo(&self) {} } }
|
||||
fn main() { let x: &dyn foo::<|>Foo; } ",
|
||||
r"mod foo { $0pub(crate) trait Foo { fn foo(&self) {} } }
|
||||
fn main() { let x: &dyn foo::Foo; } ",
|
||||
);
|
||||
check_assist_not_applicable(
|
||||
fix_visibility,
|
||||
r"mod foo { pub trait Foo { fn foo(&self) {} } }
|
||||
fn main() { let x: &dyn foo::Foo<|>; } ",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fix_visibility_of_type_alias() {
|
||||
check_assist(
|
||||
fix_visibility,
|
||||
r"mod foo { type Foo = (); }
|
||||
fn main() { let x: foo::Foo<|>; } ",
|
||||
r"mod foo { $0pub(crate) type Foo = (); }
|
||||
fn main() { let x: foo::Foo; } ",
|
||||
);
|
||||
check_assist_not_applicable(
|
||||
fix_visibility,
|
||||
r"mod foo { pub type Foo = (); }
|
||||
fn main() { let x: foo::Foo<|>; } ",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fix_visibility_of_module() {
|
||||
check_assist(
|
||||
fix_visibility,
|
||||
r"mod foo { mod bar { fn bar() {} } }
|
||||
fn main() { foo::bar<|>::bar(); } ",
|
||||
r"mod foo { $0pub(crate) mod bar { fn bar() {} } }
|
||||
fn main() { foo::bar::bar(); } ",
|
||||
);
|
||||
|
||||
check_assist(
|
||||
fix_visibility,
|
||||
r"
|
||||
//- /main.rs
|
||||
mod foo;
|
||||
fn main() { foo::bar<|>::baz(); }
|
||||
|
||||
//- /foo.rs
|
||||
mod bar {
|
||||
pub fn baz() {}
|
||||
}
|
||||
",
|
||||
r"$0pub(crate) mod bar {
|
||||
pub fn baz() {}
|
||||
}
|
||||
",
|
||||
);
|
||||
|
||||
check_assist_not_applicable(
|
||||
fix_visibility,
|
||||
r"mod foo { pub mod bar { pub fn bar() {} } }
|
||||
fn main() { foo::bar<|>::bar(); } ",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fix_visibility_of_inline_module_in_other_file() {
|
||||
check_assist(
|
||||
fix_visibility,
|
||||
r"
|
||||
//- /main.rs
|
||||
mod foo;
|
||||
fn main() { foo::bar<|>::baz(); }
|
||||
|
||||
//- /foo.rs
|
||||
mod bar;
|
||||
//- /foo/bar.rs
|
||||
pub fn baz() {}
|
||||
",
|
||||
r"$0pub(crate) mod bar;
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fix_visibility_of_module_declaration_in_other_file() {
|
||||
check_assist(
|
||||
fix_visibility,
|
||||
r"
|
||||
//- /main.rs
|
||||
mod foo;
|
||||
fn main() { foo::bar<|>>::baz(); }
|
||||
|
||||
//- /foo.rs
|
||||
mod bar {
|
||||
pub fn baz() {}
|
||||
}
|
||||
",
|
||||
r"$0pub(crate) mod bar {
|
||||
pub fn baz() {}
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adds_pub_when_target_is_in_another_crate() {
|
||||
check_assist(
|
||||
fix_visibility,
|
||||
r"
|
||||
//- /main.rs crate:a deps:foo
|
||||
foo::Bar<|>
|
||||
//- /lib.rs crate:foo
|
||||
struct Bar;
|
||||
",
|
||||
r"$0pub 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
|
||||
fn fix_visibility_of_reexport() {
|
||||
check_assist(
|
||||
fix_visibility,
|
||||
r"
|
||||
mod foo {
|
||||
use bar::Baz;
|
||||
mod bar { pub(super) struct Baz; }
|
||||
}
|
||||
foo::Baz<|>
|
||||
",
|
||||
r"
|
||||
mod foo {
|
||||
$0pub(crate) use bar::Baz;
|
||||
mod bar { pub(super) struct Baz; }
|
||||
}
|
||||
foo::Baz
|
||||
",
|
||||
)
|
||||
}
|
||||
}
|
142
crates/assists/src/handlers/flip_binexpr.rs
Normal file
142
crates/assists/src/handlers/flip_binexpr.rs
Normal file
|
@ -0,0 +1,142 @@
|
|||
use syntax::ast::{AstNode, BinExpr, BinOp};
|
||||
|
||||
use crate::{AssistContext, AssistId, AssistKind, Assists};
|
||||
|
||||
// Assist: flip_binexpr
|
||||
//
|
||||
// Flips operands of a binary expression.
|
||||
//
|
||||
// ```
|
||||
// fn main() {
|
||||
// let _ = 90 +<|> 2;
|
||||
// }
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// fn main() {
|
||||
// let _ = 2 + 90;
|
||||
// }
|
||||
// ```
|
||||
pub(crate) fn flip_binexpr(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
let expr = ctx.find_node_at_offset::<BinExpr>()?;
|
||||
let lhs = expr.lhs()?.syntax().clone();
|
||||
let rhs = expr.rhs()?.syntax().clone();
|
||||
let op_range = expr.op_token()?.text_range();
|
||||
// The assist should be applied only if the cursor is on the operator
|
||||
let cursor_in_range = op_range.contains_range(ctx.frange.range);
|
||||
if !cursor_in_range {
|
||||
return None;
|
||||
}
|
||||
let action: FlipAction = expr.op_kind()?.into();
|
||||
// The assist should not be applied for certain operators
|
||||
if let FlipAction::DontFlip = action {
|
||||
return None;
|
||||
}
|
||||
|
||||
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 {
|
||||
// Flip the expression
|
||||
Flip,
|
||||
// Flip the expression and replace the operator with this string
|
||||
FlipAndReplaceOp(&'static str),
|
||||
// Do not flip the expression
|
||||
DontFlip,
|
||||
}
|
||||
|
||||
impl From<BinOp> for FlipAction {
|
||||
fn from(op_kind: BinOp) -> Self {
|
||||
match op_kind {
|
||||
kind if kind.is_assignment() => FlipAction::DontFlip,
|
||||
BinOp::GreaterTest => FlipAction::FlipAndReplaceOp("<"),
|
||||
BinOp::GreaterEqualTest => FlipAction::FlipAndReplaceOp("<="),
|
||||
BinOp::LesserTest => FlipAction::FlipAndReplaceOp(">"),
|
||||
BinOp::LesserEqualTest => FlipAction::FlipAndReplaceOp(">="),
|
||||
_ => FlipAction::Flip,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
|
||||
|
||||
#[test]
|
||||
fn flip_binexpr_target_is_the_op() {
|
||||
check_assist_target(flip_binexpr, "fn f() { let res = 1 ==<|> 2; }", "==")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flip_binexpr_not_applicable_for_assignment() {
|
||||
check_assist_not_applicable(flip_binexpr, "fn f() { let mut _x = 1; _x +=<|> 2 }")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flip_binexpr_works_for_eq() {
|
||||
check_assist(
|
||||
flip_binexpr,
|
||||
"fn f() { let res = 1 ==<|> 2; }",
|
||||
"fn f() { let res = 2 == 1; }",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flip_binexpr_works_for_gt() {
|
||||
check_assist(flip_binexpr, "fn f() { let res = 1 ><|> 2; }", "fn f() { let res = 2 < 1; }")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flip_binexpr_works_for_lteq() {
|
||||
check_assist(
|
||||
flip_binexpr,
|
||||
"fn f() { let res = 1 <=<|> 2; }",
|
||||
"fn f() { let res = 2 >= 1; }",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flip_binexpr_works_for_complex_expr() {
|
||||
check_assist(
|
||||
flip_binexpr,
|
||||
"fn f() { let res = (1 + 1) ==<|> (2 + 2); }",
|
||||
"fn f() { let res = (2 + 2) == (1 + 1); }",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flip_binexpr_works_inside_match() {
|
||||
check_assist(
|
||||
flip_binexpr,
|
||||
r#"
|
||||
fn dyn_eq(&self, other: &dyn Diagnostic) -> bool {
|
||||
match other.downcast_ref::<Self>() {
|
||||
None => false,
|
||||
Some(it) => it ==<|> self,
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn dyn_eq(&self, other: &dyn Diagnostic) -> bool {
|
||||
match other.downcast_ref::<Self>() {
|
||||
None => false,
|
||||
Some(it) => self == it,
|
||||
}
|
||||
}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
}
|
84
crates/assists/src/handlers/flip_comma.rs
Normal file
84
crates/assists/src/handlers/flip_comma.rs
Normal file
|
@ -0,0 +1,84 @@
|
|||
use syntax::{algo::non_trivia_sibling, Direction, T};
|
||||
|
||||
use crate::{AssistContext, AssistId, AssistKind, Assists};
|
||||
|
||||
// Assist: flip_comma
|
||||
//
|
||||
// Flips two comma-separated items.
|
||||
//
|
||||
// ```
|
||||
// fn main() {
|
||||
// ((1, 2),<|> (3, 4));
|
||||
// }
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// fn main() {
|
||||
// ((3, 4), (1, 2));
|
||||
// }
|
||||
// ```
|
||||
pub(crate) fn flip_comma(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
let comma = ctx.find_token_at_offset(T![,])?;
|
||||
let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?;
|
||||
let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?;
|
||||
|
||||
// Don't apply a "flip" in case of a last comma
|
||||
// that typically comes before punctuation
|
||||
if next.kind().is_punct() {
|
||||
return None;
|
||||
}
|
||||
|
||||
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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::tests::{check_assist, check_assist_target};
|
||||
|
||||
#[test]
|
||||
fn flip_comma_works_for_function_parameters() {
|
||||
check_assist(
|
||||
flip_comma,
|
||||
"fn foo(x: i32,<|> y: Result<(), ()>) {}",
|
||||
"fn foo(y: Result<(), ()>, x: i32) {}",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flip_comma_target() {
|
||||
check_assist_target(flip_comma, "fn foo(x: i32,<|> y: Result<(), ()>) {}", ",")
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn flip_comma_before_punct() {
|
||||
// See https://github.com/rust-analyzer/rust-analyzer/issues/1619
|
||||
// "Flip comma" assist shouldn't be applicable to the last comma in enum or struct
|
||||
// declaration body.
|
||||
check_assist_target(
|
||||
flip_comma,
|
||||
"pub enum Test { \
|
||||
A,<|> \
|
||||
}",
|
||||
",",
|
||||
);
|
||||
|
||||
check_assist_target(
|
||||
flip_comma,
|
||||
"pub struct Test { \
|
||||
foo: usize,<|> \
|
||||
}",
|
||||
",",
|
||||
);
|
||||
}
|
||||
}
|
121
crates/assists/src/handlers/flip_trait_bound.rs
Normal file
121
crates/assists/src/handlers/flip_trait_bound.rs
Normal file
|
@ -0,0 +1,121 @@
|
|||
use syntax::{
|
||||
algo::non_trivia_sibling,
|
||||
ast::{self, AstNode},
|
||||
Direction, T,
|
||||
};
|
||||
|
||||
use crate::{AssistContext, AssistId, AssistKind, Assists};
|
||||
|
||||
// Assist: flip_trait_bound
|
||||
//
|
||||
// Flips two trait bounds.
|
||||
//
|
||||
// ```
|
||||
// fn foo<T: Clone +<|> Copy>() { }
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// fn foo<T: Copy + Clone>() { }
|
||||
// ```
|
||||
pub(crate) fn flip_trait_bound(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
// We want to replicate the behavior of `flip_binexpr` by only suggesting
|
||||
// the assist when the cursor is on a `+`
|
||||
let plus = ctx.find_token_at_offset(T![+])?;
|
||||
|
||||
// Make sure we're in a `TypeBoundList`
|
||||
if ast::TypeBoundList::cast(plus.parent()).is_none() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let (before, after) = (
|
||||
non_trivia_sibling(plus.clone().into(), Direction::Prev)?,
|
||||
non_trivia_sibling(plus.clone().into(), Direction::Next)?,
|
||||
);
|
||||
|
||||
let target = plus.text_range();
|
||||
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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
|
||||
|
||||
#[test]
|
||||
fn flip_trait_bound_assist_available() {
|
||||
check_assist_target(flip_trait_bound, "struct S<T> where T: A <|>+ B + C { }", "+")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flip_trait_bound_not_applicable_for_single_trait_bound() {
|
||||
check_assist_not_applicable(flip_trait_bound, "struct S<T> where T: <|>A { }")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flip_trait_bound_works_for_struct() {
|
||||
check_assist(
|
||||
flip_trait_bound,
|
||||
"struct S<T> where T: A <|>+ B { }",
|
||||
"struct S<T> where T: B + A { }",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flip_trait_bound_works_for_trait_impl() {
|
||||
check_assist(
|
||||
flip_trait_bound,
|
||||
"impl X for S<T> where T: A +<|> B { }",
|
||||
"impl X for S<T> where T: B + A { }",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flip_trait_bound_works_for_fn() {
|
||||
check_assist(flip_trait_bound, "fn f<T: A <|>+ B>(t: T) { }", "fn f<T: B + A>(t: T) { }")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flip_trait_bound_works_for_fn_where_clause() {
|
||||
check_assist(
|
||||
flip_trait_bound,
|
||||
"fn f<T>(t: T) where T: A +<|> B { }",
|
||||
"fn f<T>(t: T) where T: B + A { }",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flip_trait_bound_works_for_lifetime() {
|
||||
check_assist(
|
||||
flip_trait_bound,
|
||||
"fn f<T>(t: T) where T: A <|>+ 'static { }",
|
||||
"fn f<T>(t: T) where T: 'static + A { }",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flip_trait_bound_works_for_complex_bounds() {
|
||||
check_assist(
|
||||
flip_trait_bound,
|
||||
"struct S<T> where T: A<T> <|>+ b_mod::B<T> + C<T> { }",
|
||||
"struct S<T> where T: b_mod::B<T> + A<T> + C<T> { }",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flip_trait_bound_works_for_long_bounds() {
|
||||
check_assist(
|
||||
flip_trait_bound,
|
||||
"struct S<T> where T: A + B + C + D + E + F +<|> G + H + I + J { }",
|
||||
"struct S<T> where T: A + B + C + D + E + G + F + H + I + J { }",
|
||||
)
|
||||
}
|
||||
}
|
132
crates/assists/src/handlers/generate_derive.rs
Normal file
132
crates/assists/src/handlers/generate_derive.rs
Normal file
|
@ -0,0 +1,132 @@
|
|||
use syntax::{
|
||||
ast::{self, AstNode, AttrsOwner},
|
||||
SyntaxKind::{COMMENT, WHITESPACE},
|
||||
TextSize,
|
||||
};
|
||||
|
||||
use crate::{AssistContext, AssistId, AssistKind, Assists};
|
||||
|
||||
// Assist: generate_derive
|
||||
//
|
||||
// Adds a new `#[derive()]` clause to a struct or enum.
|
||||
//
|
||||
// ```
|
||||
// struct Point {
|
||||
// x: u32,
|
||||
// y: u32,<|>
|
||||
// }
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// #[derive($0)]
|
||||
// struct Point {
|
||||
// x: u32,
|
||||
// y: u32,
|
||||
// }
|
||||
// ```
|
||||
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::AdtDef>()?;
|
||||
let node_start = derive_insertion_offset(&nominal)?;
|
||||
let target = nominal.syntax().text_range();
|
||||
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::AdtDef) -> Option<TextSize> {
|
||||
let non_ws_child = nominal
|
||||
.syntax()
|
||||
.children_with_tokens()
|
||||
.find(|it| it.kind() != COMMENT && it.kind() != WHITESPACE)?;
|
||||
Some(non_ws_child.text_range().start())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests::{check_assist, check_assist_target};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn add_derive_new() {
|
||||
check_assist(
|
||||
generate_derive,
|
||||
"struct Foo { a: i32, <|>}",
|
||||
"#[derive($0)]\nstruct Foo { a: i32, }",
|
||||
);
|
||||
check_assist(
|
||||
generate_derive,
|
||||
"struct Foo { <|> a: i32, }",
|
||||
"#[derive($0)]\nstruct Foo { a: i32, }",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_derive_existing() {
|
||||
check_assist(
|
||||
generate_derive,
|
||||
"#[derive(Clone)]\nstruct Foo { a: i32<|>, }",
|
||||
"#[derive(Clone$0)]\nstruct Foo { a: i32, }",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_derive_new_with_doc_comment() {
|
||||
check_assist(
|
||||
generate_derive,
|
||||
"
|
||||
/// `Foo` is a pretty important struct.
|
||||
/// It does stuff.
|
||||
struct Foo { a: i32<|>, }
|
||||
",
|
||||
"
|
||||
/// `Foo` is a pretty important struct.
|
||||
/// It does stuff.
|
||||
#[derive($0)]
|
||||
struct Foo { a: i32, }
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_derive_target() {
|
||||
check_assist_target(
|
||||
generate_derive,
|
||||
"
|
||||
struct SomeThingIrrelevant;
|
||||
/// `Foo` is a pretty important struct.
|
||||
/// It does stuff.
|
||||
struct Foo { a: i32<|>, }
|
||||
struct EvenMoreIrrelevant;
|
||||
",
|
||||
"/// `Foo` is a pretty important struct.
|
||||
/// It does stuff.
|
||||
struct Foo { a: i32, }",
|
||||
);
|
||||
}
|
||||
}
|
200
crates/assists/src/handlers/generate_from_impl_for_enum.rs
Normal file
200
crates/assists/src/handlers/generate_from_impl_for_enum.rs
Normal file
|
@ -0,0 +1,200 @@
|
|||
use ide_db::RootDatabase;
|
||||
use syntax::ast::{self, AstNode, NameOwner};
|
||||
use test_utils::mark;
|
||||
|
||||
use crate::{utils::FamousDefs, AssistContext, AssistId, AssistKind, Assists};
|
||||
|
||||
// Assist: generate_from_impl_for_enum
|
||||
//
|
||||
// Adds a From impl for an enum variant with one tuple field.
|
||||
//
|
||||
// ```
|
||||
// enum A { <|>One(u32) }
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// enum A { One(u32) }
|
||||
//
|
||||
// impl From<u32> for A {
|
||||
// fn from(v: u32) -> Self {
|
||||
// A::One(v)
|
||||
// }
|
||||
// }
|
||||
// ```
|
||||
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() {
|
||||
ast::StructKind::Tuple(field_list) => field_list,
|
||||
_ => return None,
|
||||
};
|
||||
if field_list.fields().count() != 1 {
|
||||
return None;
|
||||
}
|
||||
let field_type = field_list.fields().next()?.ty()?;
|
||||
let path = match field_type {
|
||||
ast::Type::PathType(it) => it,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
if existing_from_impl(&ctx.sema, &variant).is_some() {
|
||||
mark::hit!(test_add_from_impl_already_exists);
|
||||
return None;
|
||||
}
|
||||
|
||||
let target = variant.syntax().text_range();
|
||||
acc.add(
|
||||
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();
|
||||
let buf = format!(
|
||||
r#"
|
||||
|
||||
impl From<{0}> for {1} {{
|
||||
fn from(v: {0}) -> Self {{
|
||||
{1}::{2}(v)
|
||||
}}
|
||||
}}"#,
|
||||
path.syntax(),
|
||||
enum_name,
|
||||
variant_name
|
||||
);
|
||||
edit.insert(start_offset, buf);
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn existing_from_impl(
|
||||
sema: &'_ hir::Semantics<'_, RootDatabase>,
|
||||
variant: &ast::Variant,
|
||||
) -> Option<()> {
|
||||
let variant = sema.to_def(variant)?;
|
||||
let enum_ = variant.parent_enum(sema.db);
|
||||
let krate = enum_.module(sema.db).krate();
|
||||
|
||||
let from_trait = FamousDefs(sema, krate).core_convert_From()?;
|
||||
|
||||
let enum_type = enum_.ty(sema.db);
|
||||
|
||||
let wrapped_type = variant.fields(sema.db).get(0)?.signature_ty(sema.db);
|
||||
|
||||
if enum_type.impls_trait(sema.db, from_trait, &[wrapped_type]) {
|
||||
Some(())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use test_utils::mark;
|
||||
|
||||
use crate::tests::{check_assist, check_assist_not_applicable};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_generate_from_impl_for_enum() {
|
||||
check_assist(
|
||||
generate_from_impl_for_enum,
|
||||
"enum A { <|>One(u32) }",
|
||||
r#"enum A { One(u32) }
|
||||
|
||||
impl From<u32> for A {
|
||||
fn from(v: u32) -> Self {
|
||||
A::One(v)
|
||||
}
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_from_impl_for_enum_complicated_path() {
|
||||
check_assist(
|
||||
generate_from_impl_for_enum,
|
||||
r#"enum A { <|>One(foo::bar::baz::Boo) }"#,
|
||||
r#"enum A { One(foo::bar::baz::Boo) }
|
||||
|
||||
impl From<foo::bar::baz::Boo> for A {
|
||||
fn from(v: foo::bar::baz::Boo) -> Self {
|
||||
A::One(v)
|
||||
}
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
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(generate_from_impl_for_enum, &fixture)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_from_impl_no_element() {
|
||||
check_not_applicable("enum A { <|>One }");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_from_impl_more_than_one_element_in_tuple() {
|
||||
check_not_applicable("enum A { <|>One(u32, String) }");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_from_impl_struct_variant() {
|
||||
check_not_applicable("enum A { <|>One { x: u32 } }");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_from_impl_already_exists() {
|
||||
mark::check!(test_add_from_impl_already_exists);
|
||||
check_not_applicable(
|
||||
r#"
|
||||
enum A { <|>One(u32), }
|
||||
|
||||
impl From<u32> for A {
|
||||
fn from(v: u32) -> Self {
|
||||
A::One(v)
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_from_impl_different_variant_impl_exists() {
|
||||
check_assist(
|
||||
generate_from_impl_for_enum,
|
||||
r#"enum A { <|>One(u32), Two(String), }
|
||||
|
||||
impl From<String> for A {
|
||||
fn from(v: String) -> Self {
|
||||
A::Two(v)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait From<T> {
|
||||
fn from(T) -> Self;
|
||||
}"#,
|
||||
r#"enum A { One(u32), Two(String), }
|
||||
|
||||
impl From<u32> for A {
|
||||
fn from(v: u32) -> Self {
|
||||
A::One(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for A {
|
||||
fn from(v: String) -> Self {
|
||||
A::Two(v)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait From<T> {
|
||||
fn from(T) -> Self;
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
}
|
1058
crates/assists/src/handlers/generate_function.rs
Normal file
1058
crates/assists/src/handlers/generate_function.rs
Normal file
File diff suppressed because it is too large
Load diff
110
crates/assists/src/handlers/generate_impl.rs
Normal file
110
crates/assists/src/handlers/generate_impl.rs
Normal file
|
@ -0,0 +1,110 @@
|
|||
use itertools::Itertools;
|
||||
use stdx::format_to;
|
||||
use syntax::ast::{self, AstNode, GenericParamsOwner, NameOwner};
|
||||
|
||||
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).format(", ");
|
||||
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>> {}",
|
||||
);
|
||||
}
|
||||
}
|
421
crates/assists/src/handlers/generate_new.rs
Normal file
421
crates/assists/src/handlers/generate_new.rs
Normal file
|
@ -0,0 +1,421 @@
|
|||
use hir::Adt;
|
||||
use itertools::Itertools;
|
||||
use stdx::format_to;
|
||||
use syntax::{
|
||||
ast::{self, AstNode, GenericParamsOwner, NameOwner, StructKind, VisibilityOwner},
|
||||
T,
|
||||
};
|
||||
|
||||
use crate::{AssistContext, AssistId, AssistKind, Assists};
|
||||
|
||||
// Assist: generate_new
|
||||
//
|
||||
// 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> {
|
||||
// fn $0new(data: T) -> Self { Self { data } }
|
||||
// }
|
||||
//
|
||||
// ```
|
||||
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() {
|
||||
StructKind::Record(named) => named,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
// Return early if we've found an existing new fn
|
||||
let impl_def = find_struct_impl(&ctx, &strukt)?;
|
||||
|
||||
let target = strukt.syntax().text_range();
|
||||
acc.add(AssistId("generate_new", AssistKind::Generate), "Generate `new`", target, |builder| {
|
||||
let mut buf = String::with_capacity(512);
|
||||
|
||||
if impl_def.is_some() {
|
||||
buf.push('\n');
|
||||
}
|
||||
|
||||
let vis = strukt.visibility().map_or(String::new(), |v| format!("{} ", v));
|
||||
|
||||
let params = field_list
|
||||
.fields()
|
||||
.filter_map(|f| Some(format!("{}: {}", f.name()?.syntax(), f.ty()?.syntax())))
|
||||
.format(", ");
|
||||
let fields = field_list.fields().filter_map(|f| f.name()).format(", ");
|
||||
|
||||
format_to!(buf, " {}fn new({}) -> Self {{ Self {{ {} }} }}", vis, params, fields);
|
||||
|
||||
let start_offset = impl_def
|
||||
.and_then(|impl_def| {
|
||||
buf.push('\n');
|
||||
let start = impl_def
|
||||
.syntax()
|
||||
.descendants_with_tokens()
|
||||
.find(|t| t.kind() == T!['{'])?
|
||||
.text_range()
|
||||
.end();
|
||||
|
||||
Some(start)
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
buf = generate_impl_text(&strukt, &buf);
|
||||
strukt.syntax().text_range().end()
|
||||
});
|
||||
|
||||
match ctx.config.snippet_cap {
|
||||
None => builder.insert(start_offset, buf),
|
||||
Some(cap) => {
|
||||
buf = buf.replace("fn new", "fn $0new");
|
||||
builder.insert_snippet(cap, start_offset, buf);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Generates the surrounding `impl Type { <code> }` including type and lifetime
|
||||
// parameters
|
||||
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 {
|
||||
format_to!(buf, "{}", type_params.syntax());
|
||||
}
|
||||
buf.push_str(" ");
|
||||
buf.push_str(strukt.name().unwrap().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());
|
||||
format_to!(buf, "<{}>", lifetime_params.chain(type_params).format(", "))
|
||||
}
|
||||
|
||||
format_to!(buf, " {{\n{}\n}}\n", code);
|
||||
|
||||
buf
|
||||
}
|
||||
|
||||
// Uses a syntax-driven approach to find any impl blocks for the struct that
|
||||
// exist within the module/file
|
||||
//
|
||||
// Returns `None` if we've found an existing `new` fn
|
||||
//
|
||||
// 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::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::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> {}`
|
||||
// (we currently use the wrong type parameter)
|
||||
// also we wouldn't want to use e.g. `impl S<u32>`
|
||||
let same_ty = match blk.target_ty(db).as_adt() {
|
||||
Some(def) => def == Adt::Struct(struct_def),
|
||||
None => false,
|
||||
};
|
||||
let not_trait_impl = blk.target_trait(db).is_none();
|
||||
|
||||
if !(same_ty && not_trait_impl) {
|
||||
None
|
||||
} else {
|
||||
Some(impl_blk)
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(ref impl_blk) = block {
|
||||
if has_new_fn(impl_blk) {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
Some(block)
|
||||
}
|
||||
|
||||
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::Fn(f) = item {
|
||||
if let Some(name) = f.name() {
|
||||
if name.text().eq_ignore_ascii_case("new") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
#[rustfmt::skip]
|
||||
fn test_generate_new() {
|
||||
// Check output of generation
|
||||
check_assist(
|
||||
generate_new,
|
||||
"struct Foo {<|>}",
|
||||
"struct Foo {}
|
||||
|
||||
impl Foo {
|
||||
fn $0new() -> Self { Self { } }
|
||||
}
|
||||
",
|
||||
);
|
||||
check_assist(
|
||||
generate_new,
|
||||
"struct Foo<T: Clone> {<|>}",
|
||||
"struct Foo<T: Clone> {}
|
||||
|
||||
impl<T: Clone> Foo<T> {
|
||||
fn $0new() -> Self { Self { } }
|
||||
}
|
||||
",
|
||||
);
|
||||
check_assist(
|
||||
generate_new,
|
||||
"struct Foo<'a, T: Foo<'a>> {<|>}",
|
||||
"struct Foo<'a, T: Foo<'a>> {}
|
||||
|
||||
impl<'a, T: Foo<'a>> Foo<'a, T> {
|
||||
fn $0new() -> Self { Self { } }
|
||||
}
|
||||
",
|
||||
);
|
||||
check_assist(
|
||||
generate_new,
|
||||
"struct Foo { baz: String <|>}",
|
||||
"struct Foo { baz: String }
|
||||
|
||||
impl Foo {
|
||||
fn $0new(baz: String) -> Self { Self { baz } }
|
||||
}
|
||||
",
|
||||
);
|
||||
check_assist(
|
||||
generate_new,
|
||||
"struct Foo { baz: String, qux: Vec<i32> <|>}",
|
||||
"struct Foo { baz: String, qux: Vec<i32> }
|
||||
|
||||
impl Foo {
|
||||
fn $0new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }
|
||||
}
|
||||
",
|
||||
);
|
||||
|
||||
// Check that visibility modifiers don't get brought in for fields
|
||||
check_assist(
|
||||
generate_new,
|
||||
"struct Foo { pub baz: String, pub qux: Vec<i32> <|>}",
|
||||
"struct Foo { pub baz: String, pub qux: Vec<i32> }
|
||||
|
||||
impl Foo {
|
||||
fn $0new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }
|
||||
}
|
||||
",
|
||||
);
|
||||
|
||||
// Check that it reuses existing impls
|
||||
check_assist(
|
||||
generate_new,
|
||||
"struct Foo {<|>}
|
||||
|
||||
impl Foo {}
|
||||
",
|
||||
"struct Foo {}
|
||||
|
||||
impl Foo {
|
||||
fn $0new() -> Self { Self { } }
|
||||
}
|
||||
",
|
||||
);
|
||||
check_assist(
|
||||
generate_new,
|
||||
"struct Foo {<|>}
|
||||
|
||||
impl Foo {
|
||||
fn qux(&self) {}
|
||||
}
|
||||
",
|
||||
"struct Foo {}
|
||||
|
||||
impl Foo {
|
||||
fn $0new() -> Self { Self { } }
|
||||
|
||||
fn qux(&self) {}
|
||||
}
|
||||
",
|
||||
);
|
||||
|
||||
check_assist(
|
||||
generate_new,
|
||||
"struct Foo {<|>}
|
||||
|
||||
impl Foo {
|
||||
fn qux(&self) {}
|
||||
fn baz() -> i32 {
|
||||
5
|
||||
}
|
||||
}
|
||||
",
|
||||
"struct Foo {}
|
||||
|
||||
impl Foo {
|
||||
fn $0new() -> Self { Self { } }
|
||||
|
||||
fn qux(&self) {}
|
||||
fn baz() -> i32 {
|
||||
5
|
||||
}
|
||||
}
|
||||
",
|
||||
);
|
||||
|
||||
// Check visibility of new fn based on struct
|
||||
check_assist(
|
||||
generate_new,
|
||||
"pub struct Foo {<|>}",
|
||||
"pub struct Foo {}
|
||||
|
||||
impl Foo {
|
||||
pub fn $0new() -> Self { Self { } }
|
||||
}
|
||||
",
|
||||
);
|
||||
check_assist(
|
||||
generate_new,
|
||||
"pub(crate) struct Foo {<|>}",
|
||||
"pub(crate) struct Foo {}
|
||||
|
||||
impl Foo {
|
||||
pub(crate) fn $0new() -> Self { Self { } }
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generate_new_not_applicable_if_fn_exists() {
|
||||
check_assist_not_applicable(
|
||||
generate_new,
|
||||
"
|
||||
struct Foo {<|>}
|
||||
|
||||
impl Foo {
|
||||
fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}",
|
||||
);
|
||||
|
||||
check_assist_not_applicable(
|
||||
generate_new,
|
||||
"
|
||||
struct Foo {<|>}
|
||||
|
||||
impl Foo {
|
||||
fn New() -> Self {
|
||||
Self
|
||||
}
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generate_new_target() {
|
||||
check_assist_target(
|
||||
generate_new,
|
||||
"
|
||||
struct SomeThingIrrelevant;
|
||||
/// Has a lifetime parameter
|
||||
struct Foo<'a, T: Foo<'a>> {<|>}
|
||||
struct EvenMoreIrrelevant;
|
||||
",
|
||||
"/// Has a lifetime parameter
|
||||
struct Foo<'a, T: Foo<'a>> {}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unrelated_new() {
|
||||
check_assist(
|
||||
generate_new,
|
||||
r##"
|
||||
pub struct AstId<N: AstNode> {
|
||||
file_id: HirFileId,
|
||||
file_ast_id: FileAstId<N>,
|
||||
}
|
||||
|
||||
impl<N: AstNode> AstId<N> {
|
||||
pub fn new(file_id: HirFileId, file_ast_id: FileAstId<N>) -> AstId<N> {
|
||||
AstId { file_id, file_ast_id }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Source<T> {
|
||||
pub file_id: HirFileId,<|>
|
||||
pub ast: T,
|
||||
}
|
||||
|
||||
impl<T> Source<T> {
|
||||
pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> {
|
||||
Source { file_id: self.file_id, ast: f(self.ast) }
|
||||
}
|
||||
}
|
||||
"##,
|
||||
r##"
|
||||
pub struct AstId<N: AstNode> {
|
||||
file_id: HirFileId,
|
||||
file_ast_id: FileAstId<N>,
|
||||
}
|
||||
|
||||
impl<N: AstNode> AstId<N> {
|
||||
pub fn new(file_id: HirFileId, file_ast_id: FileAstId<N>) -> AstId<N> {
|
||||
AstId { file_id, file_ast_id }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Source<T> {
|
||||
pub file_id: HirFileId,
|
||||
pub ast: T,
|
||||
}
|
||||
|
||||
impl<T> Source<T> {
|
||||
pub fn $0new(file_id: HirFileId, ast: T) -> Self { Self { file_id, ast } }
|
||||
|
||||
pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> {
|
||||
Source { file_id: self.file_id, ast: f(self.ast) }
|
||||
}
|
||||
}
|
||||
"##,
|
||||
);
|
||||
}
|
||||
}
|
695
crates/assists/src/handlers/inline_local_variable.rs
Normal file
695
crates/assists/src/handlers/inline_local_variable.rs
Normal file
|
@ -0,0 +1,695 @@
|
|||
use ide_db::defs::Definition;
|
||||
use syntax::{
|
||||
ast::{self, AstNode, AstToken},
|
||||
TextRange,
|
||||
};
|
||||
use test_utils::mark;
|
||||
|
||||
use crate::{
|
||||
assist_context::{AssistContext, Assists},
|
||||
AssistId, AssistKind,
|
||||
};
|
||||
|
||||
// Assist: inline_local_variable
|
||||
//
|
||||
// Inlines local variable.
|
||||
//
|
||||
// ```
|
||||
// fn main() {
|
||||
// let x<|> = 1 + 2;
|
||||
// x * 4;
|
||||
// }
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// fn main() {
|
||||
// (1 + 2) * 4;
|
||||
// }
|
||||
// ```
|
||||
pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
let let_stmt = ctx.find_node_at_offset::<ast::LetStmt>()?;
|
||||
let bind_pat = match let_stmt.pat()? {
|
||||
ast::Pat::IdentPat(pat) => pat,
|
||||
_ => return None,
|
||||
};
|
||||
if bind_pat.mut_token().is_some() {
|
||||
mark::hit!(test_not_inline_mut_variable);
|
||||
return None;
|
||||
}
|
||||
if !bind_pat.syntax().text_range().contains_inclusive(ctx.offset()) {
|
||||
mark::hit!(not_applicable_outside_of_bind_pat);
|
||||
return None;
|
||||
}
|
||||
let initializer_expr = let_stmt.initializer()?;
|
||||
|
||||
let def = ctx.sema.to_def(&bind_pat)?;
|
||||
let def = Definition::Local(def);
|
||||
let refs = def.find_usages(&ctx.sema, None);
|
||||
if refs.is_empty() {
|
||||
mark::hit!(test_not_applicable_if_variable_unused);
|
||||
return None;
|
||||
};
|
||||
|
||||
let delete_range = if let Some(whitespace) = let_stmt
|
||||
.syntax()
|
||||
.next_sibling_or_token()
|
||||
.and_then(|it| ast::Whitespace::cast(it.as_token()?.clone()))
|
||||
{
|
||||
TextRange::new(
|
||||
let_stmt.syntax().text_range().start(),
|
||||
whitespace.syntax().text_range().end(),
|
||||
)
|
||||
} else {
|
||||
let_stmt.syntax().text_range()
|
||||
};
|
||||
|
||||
let mut wrap_in_parens = vec![true; refs.len()];
|
||||
|
||||
for (i, desc) in refs.iter().enumerate() {
|
||||
let usage_node = ctx
|
||||
.covering_node_for_range(desc.file_range.range)
|
||||
.ancestors()
|
||||
.find_map(ast::PathExpr::cast)?;
|
||||
let usage_parent_option = usage_node.syntax().parent().and_then(ast::Expr::cast);
|
||||
let usage_parent = match usage_parent_option {
|
||||
Some(u) => u,
|
||||
None => {
|
||||
wrap_in_parens[i] = false;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
wrap_in_parens[i] = match (&initializer_expr, usage_parent) {
|
||||
(ast::Expr::CallExpr(_), _)
|
||||
| (ast::Expr::IndexExpr(_), _)
|
||||
| (ast::Expr::MethodCallExpr(_), _)
|
||||
| (ast::Expr::FieldExpr(_), _)
|
||||
| (ast::Expr::TryExpr(_), _)
|
||||
| (ast::Expr::RefExpr(_), _)
|
||||
| (ast::Expr::Literal(_), _)
|
||||
| (ast::Expr::TupleExpr(_), _)
|
||||
| (ast::Expr::ArrayExpr(_), _)
|
||||
| (ast::Expr::ParenExpr(_), _)
|
||||
| (ast::Expr::PathExpr(_), _)
|
||||
| (ast::Expr::BlockExpr(_), _)
|
||||
| (ast::Expr::EffectExpr(_), _)
|
||||
| (_, ast::Expr::CallExpr(_))
|
||||
| (_, ast::Expr::TupleExpr(_))
|
||||
| (_, ast::Expr::ArrayExpr(_))
|
||||
| (_, ast::Expr::ParenExpr(_))
|
||||
| (_, ast::Expr::ForExpr(_))
|
||||
| (_, ast::Expr::WhileExpr(_))
|
||||
| (_, ast::Expr::BreakExpr(_))
|
||||
| (_, ast::Expr::ReturnExpr(_))
|
||||
| (_, ast::Expr::MatchExpr(_)) => false,
|
||||
_ => true,
|
||||
};
|
||||
}
|
||||
|
||||
let init_str = initializer_expr.syntax().text().to_string();
|
||||
let init_in_paren = format!("({})", &init_str);
|
||||
|
||||
let target = bind_pat.syntax().text_range();
|
||||
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)]
|
||||
mod tests {
|
||||
use test_utils::mark;
|
||||
|
||||
use crate::tests::{check_assist, check_assist_not_applicable};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_inline_let_bind_literal_expr() {
|
||||
check_assist(
|
||||
inline_local_variable,
|
||||
r"
|
||||
fn bar(a: usize) {}
|
||||
fn foo() {
|
||||
let a<|> = 1;
|
||||
a + 1;
|
||||
if a > 10 {
|
||||
}
|
||||
|
||||
while a > 10 {
|
||||
|
||||
}
|
||||
let b = a * 10;
|
||||
bar(a);
|
||||
}",
|
||||
r"
|
||||
fn bar(a: usize) {}
|
||||
fn foo() {
|
||||
1 + 1;
|
||||
if 1 > 10 {
|
||||
}
|
||||
|
||||
while 1 > 10 {
|
||||
|
||||
}
|
||||
let b = 1 * 10;
|
||||
bar(1);
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inline_let_bind_bin_expr() {
|
||||
check_assist(
|
||||
inline_local_variable,
|
||||
r"
|
||||
fn bar(a: usize) {}
|
||||
fn foo() {
|
||||
let a<|> = 1 + 1;
|
||||
a + 1;
|
||||
if a > 10 {
|
||||
}
|
||||
|
||||
while a > 10 {
|
||||
|
||||
}
|
||||
let b = a * 10;
|
||||
bar(a);
|
||||
}",
|
||||
r"
|
||||
fn bar(a: usize) {}
|
||||
fn foo() {
|
||||
(1 + 1) + 1;
|
||||
if (1 + 1) > 10 {
|
||||
}
|
||||
|
||||
while (1 + 1) > 10 {
|
||||
|
||||
}
|
||||
let b = (1 + 1) * 10;
|
||||
bar(1 + 1);
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inline_let_bind_function_call_expr() {
|
||||
check_assist(
|
||||
inline_local_variable,
|
||||
r"
|
||||
fn bar(a: usize) {}
|
||||
fn foo() {
|
||||
let a<|> = bar(1);
|
||||
a + 1;
|
||||
if a > 10 {
|
||||
}
|
||||
|
||||
while a > 10 {
|
||||
|
||||
}
|
||||
let b = a * 10;
|
||||
bar(a);
|
||||
}",
|
||||
r"
|
||||
fn bar(a: usize) {}
|
||||
fn foo() {
|
||||
bar(1) + 1;
|
||||
if bar(1) > 10 {
|
||||
}
|
||||
|
||||
while bar(1) > 10 {
|
||||
|
||||
}
|
||||
let b = bar(1) * 10;
|
||||
bar(bar(1));
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inline_let_bind_cast_expr() {
|
||||
check_assist(
|
||||
inline_local_variable,
|
||||
r"
|
||||
fn bar(a: usize): usize { a }
|
||||
fn foo() {
|
||||
let a<|> = bar(1) as u64;
|
||||
a + 1;
|
||||
if a > 10 {
|
||||
}
|
||||
|
||||
while a > 10 {
|
||||
|
||||
}
|
||||
let b = a * 10;
|
||||
bar(a);
|
||||
}",
|
||||
r"
|
||||
fn bar(a: usize): usize { a }
|
||||
fn foo() {
|
||||
(bar(1) as u64) + 1;
|
||||
if (bar(1) as u64) > 10 {
|
||||
}
|
||||
|
||||
while (bar(1) as u64) > 10 {
|
||||
|
||||
}
|
||||
let b = (bar(1) as u64) * 10;
|
||||
bar(bar(1) as u64);
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inline_let_bind_block_expr() {
|
||||
check_assist(
|
||||
inline_local_variable,
|
||||
r"
|
||||
fn foo() {
|
||||
let a<|> = { 10 + 1 };
|
||||
a + 1;
|
||||
if a > 10 {
|
||||
}
|
||||
|
||||
while a > 10 {
|
||||
|
||||
}
|
||||
let b = a * 10;
|
||||
bar(a);
|
||||
}",
|
||||
r"
|
||||
fn foo() {
|
||||
{ 10 + 1 } + 1;
|
||||
if { 10 + 1 } > 10 {
|
||||
}
|
||||
|
||||
while { 10 + 1 } > 10 {
|
||||
|
||||
}
|
||||
let b = { 10 + 1 } * 10;
|
||||
bar({ 10 + 1 });
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inline_let_bind_paren_expr() {
|
||||
check_assist(
|
||||
inline_local_variable,
|
||||
r"
|
||||
fn foo() {
|
||||
let a<|> = ( 10 + 1 );
|
||||
a + 1;
|
||||
if a > 10 {
|
||||
}
|
||||
|
||||
while a > 10 {
|
||||
|
||||
}
|
||||
let b = a * 10;
|
||||
bar(a);
|
||||
}",
|
||||
r"
|
||||
fn foo() {
|
||||
( 10 + 1 ) + 1;
|
||||
if ( 10 + 1 ) > 10 {
|
||||
}
|
||||
|
||||
while ( 10 + 1 ) > 10 {
|
||||
|
||||
}
|
||||
let b = ( 10 + 1 ) * 10;
|
||||
bar(( 10 + 1 ));
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_not_inline_mut_variable() {
|
||||
mark::check!(test_not_inline_mut_variable);
|
||||
check_assist_not_applicable(
|
||||
inline_local_variable,
|
||||
r"
|
||||
fn foo() {
|
||||
let mut a<|> = 1 + 1;
|
||||
a + 1;
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_call_expr() {
|
||||
check_assist(
|
||||
inline_local_variable,
|
||||
r"
|
||||
fn foo() {
|
||||
let a<|> = bar(10 + 1);
|
||||
let b = a * 10;
|
||||
let c = a as usize;
|
||||
}",
|
||||
r"
|
||||
fn foo() {
|
||||
let b = bar(10 + 1) * 10;
|
||||
let c = bar(10 + 1) as usize;
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_index_expr() {
|
||||
check_assist(
|
||||
inline_local_variable,
|
||||
r"
|
||||
fn foo() {
|
||||
let x = vec![1, 2, 3];
|
||||
let a<|> = x[0];
|
||||
let b = a * 10;
|
||||
let c = a as usize;
|
||||
}",
|
||||
r"
|
||||
fn foo() {
|
||||
let x = vec![1, 2, 3];
|
||||
let b = x[0] * 10;
|
||||
let c = x[0] as usize;
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_method_call_expr() {
|
||||
check_assist(
|
||||
inline_local_variable,
|
||||
r"
|
||||
fn foo() {
|
||||
let bar = vec![1];
|
||||
let a<|> = bar.len();
|
||||
let b = a * 10;
|
||||
let c = a as usize;
|
||||
}",
|
||||
r"
|
||||
fn foo() {
|
||||
let bar = vec![1];
|
||||
let b = bar.len() * 10;
|
||||
let c = bar.len() as usize;
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_field_expr() {
|
||||
check_assist(
|
||||
inline_local_variable,
|
||||
r"
|
||||
struct Bar {
|
||||
foo: usize
|
||||
}
|
||||
|
||||
fn foo() {
|
||||
let bar = Bar { foo: 1 };
|
||||
let a<|> = bar.foo;
|
||||
let b = a * 10;
|
||||
let c = a as usize;
|
||||
}",
|
||||
r"
|
||||
struct Bar {
|
||||
foo: usize
|
||||
}
|
||||
|
||||
fn foo() {
|
||||
let bar = Bar { foo: 1 };
|
||||
let b = bar.foo * 10;
|
||||
let c = bar.foo as usize;
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_try_expr() {
|
||||
check_assist(
|
||||
inline_local_variable,
|
||||
r"
|
||||
fn foo() -> Option<usize> {
|
||||
let bar = Some(1);
|
||||
let a<|> = bar?;
|
||||
let b = a * 10;
|
||||
let c = a as usize;
|
||||
None
|
||||
}",
|
||||
r"
|
||||
fn foo() -> Option<usize> {
|
||||
let bar = Some(1);
|
||||
let b = bar? * 10;
|
||||
let c = bar? as usize;
|
||||
None
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ref_expr() {
|
||||
check_assist(
|
||||
inline_local_variable,
|
||||
r"
|
||||
fn foo() {
|
||||
let bar = 10;
|
||||
let a<|> = &bar;
|
||||
let b = a * 10;
|
||||
}",
|
||||
r"
|
||||
fn foo() {
|
||||
let bar = 10;
|
||||
let b = &bar * 10;
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tuple_expr() {
|
||||
check_assist(
|
||||
inline_local_variable,
|
||||
r"
|
||||
fn foo() {
|
||||
let a<|> = (10, 20);
|
||||
let b = a[0];
|
||||
}",
|
||||
r"
|
||||
fn foo() {
|
||||
let b = (10, 20)[0];
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_array_expr() {
|
||||
check_assist(
|
||||
inline_local_variable,
|
||||
r"
|
||||
fn foo() {
|
||||
let a<|> = [1, 2, 3];
|
||||
let b = a.len();
|
||||
}",
|
||||
r"
|
||||
fn foo() {
|
||||
let b = [1, 2, 3].len();
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_paren() {
|
||||
check_assist(
|
||||
inline_local_variable,
|
||||
r"
|
||||
fn foo() {
|
||||
let a<|> = (10 + 20);
|
||||
let b = a * 10;
|
||||
let c = a as usize;
|
||||
}",
|
||||
r"
|
||||
fn foo() {
|
||||
let b = (10 + 20) * 10;
|
||||
let c = (10 + 20) as usize;
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_expr() {
|
||||
check_assist(
|
||||
inline_local_variable,
|
||||
r"
|
||||
fn foo() {
|
||||
let d = 10;
|
||||
let a<|> = d;
|
||||
let b = a * 10;
|
||||
let c = a as usize;
|
||||
}",
|
||||
r"
|
||||
fn foo() {
|
||||
let d = 10;
|
||||
let b = d * 10;
|
||||
let c = d as usize;
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_block_expr() {
|
||||
check_assist(
|
||||
inline_local_variable,
|
||||
r"
|
||||
fn foo() {
|
||||
let a<|> = { 10 };
|
||||
let b = a * 10;
|
||||
let c = a as usize;
|
||||
}",
|
||||
r"
|
||||
fn foo() {
|
||||
let b = { 10 } * 10;
|
||||
let c = { 10 } as usize;
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_used_in_different_expr1() {
|
||||
check_assist(
|
||||
inline_local_variable,
|
||||
r"
|
||||
fn foo() {
|
||||
let a<|> = 10 + 20;
|
||||
let b = a * 10;
|
||||
let c = (a, 20);
|
||||
let d = [a, 10];
|
||||
let e = (a);
|
||||
}",
|
||||
r"
|
||||
fn foo() {
|
||||
let b = (10 + 20) * 10;
|
||||
let c = (10 + 20, 20);
|
||||
let d = [10 + 20, 10];
|
||||
let e = (10 + 20);
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_used_in_for_expr() {
|
||||
check_assist(
|
||||
inline_local_variable,
|
||||
r"
|
||||
fn foo() {
|
||||
let a<|> = vec![10, 20];
|
||||
for i in a {}
|
||||
}",
|
||||
r"
|
||||
fn foo() {
|
||||
for i in vec![10, 20] {}
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_used_in_while_expr() {
|
||||
check_assist(
|
||||
inline_local_variable,
|
||||
r"
|
||||
fn foo() {
|
||||
let a<|> = 1 > 0;
|
||||
while a {}
|
||||
}",
|
||||
r"
|
||||
fn foo() {
|
||||
while 1 > 0 {}
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_used_in_break_expr() {
|
||||
check_assist(
|
||||
inline_local_variable,
|
||||
r"
|
||||
fn foo() {
|
||||
let a<|> = 1 + 1;
|
||||
loop {
|
||||
break a;
|
||||
}
|
||||
}",
|
||||
r"
|
||||
fn foo() {
|
||||
loop {
|
||||
break 1 + 1;
|
||||
}
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_used_in_return_expr() {
|
||||
check_assist(
|
||||
inline_local_variable,
|
||||
r"
|
||||
fn foo() {
|
||||
let a<|> = 1 > 0;
|
||||
return a;
|
||||
}",
|
||||
r"
|
||||
fn foo() {
|
||||
return 1 > 0;
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_used_in_match_expr() {
|
||||
check_assist(
|
||||
inline_local_variable,
|
||||
r"
|
||||
fn foo() {
|
||||
let a<|> = 1 > 0;
|
||||
match a {}
|
||||
}",
|
||||
r"
|
||||
fn foo() {
|
||||
match 1 > 0 {}
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_not_applicable_if_variable_unused() {
|
||||
mark::check!(test_not_applicable_if_variable_unused);
|
||||
check_assist_not_applicable(
|
||||
inline_local_variable,
|
||||
r"
|
||||
fn foo() {
|
||||
let <|>a = 0;
|
||||
}
|
||||
",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_applicable_outside_of_bind_pat() {
|
||||
mark::check!(not_applicable_outside_of_bind_pat);
|
||||
check_assist_not_applicable(
|
||||
inline_local_variable,
|
||||
r"
|
||||
fn main() {
|
||||
let x = <|>1 + 2;
|
||||
x * 4;
|
||||
}
|
||||
",
|
||||
)
|
||||
}
|
||||
}
|
318
crates/assists/src/handlers/introduce_named_lifetime.rs
Normal file
318
crates/assists/src/handlers/introduce_named_lifetime.rs
Normal file
|
@ -0,0 +1,318 @@
|
|||
use rustc_hash::FxHashSet;
|
||||
use syntax::{
|
||||
ast::{self, GenericParamsOwner, NameOwner},
|
||||
AstNode, SyntaxKind, TextRange, TextSize,
|
||||
};
|
||||
|
||||
use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists};
|
||||
|
||||
static ASSIST_NAME: &str = "introduce_named_lifetime";
|
||||
static ASSIST_LABEL: &str = "Introduce named lifetime";
|
||||
|
||||
// Assist: introduce_named_lifetime
|
||||
//
|
||||
// Change an anonymous lifetime to a named lifetime.
|
||||
//
|
||||
// ```
|
||||
// impl Cursor<'_<|>> {
|
||||
// fn node(self) -> &SyntaxNode {
|
||||
// match self {
|
||||
// Cursor::Replace(node) | Cursor::Before(node) => node,
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// impl<'a> Cursor<'a> {
|
||||
// fn node(self) -> &SyntaxNode {
|
||||
// match self {
|
||||
// Cursor::Replace(node) | Cursor::Before(node) => node,
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// ```
|
||||
// FIXME: How can we handle renaming any one of multiple anonymous lifetimes?
|
||||
// FIXME: should also add support for the case fun(f: &Foo) -> &<|>Foo
|
||||
pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
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::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::Impl::cast) {
|
||||
generate_impl_def_assist(acc, &impl_def, lifetime_token.text_range())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate the assist for the fn def case
|
||||
fn generate_fn_def_assist(
|
||||
acc: &mut Assists,
|
||||
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.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
|
||||
param_list.self_param().filter(|p| p.lifetime_token().is_none() && p.amp_token().is_some());
|
||||
// compute the location which implicitly has the same lifetime as the anonymous lifetime
|
||||
let loc_needing_lifetime = if let Some(self_param) = self_param {
|
||||
// if we have a self reference, use that
|
||||
Some(self_param.self_token()?.text_range().start())
|
||||
} else {
|
||||
// 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.ty() {
|
||||
Some(ast::Type::RefType(ascribed_type))
|
||||
if ascribed_type.lifetime_token() == None =>
|
||||
{
|
||||
Some(ascribed_type.amp_token()?.text_range().end())
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
match fn_params_without_lifetime.len() {
|
||||
1 => Some(fn_params_without_lifetime.into_iter().nth(0)?),
|
||||
0 => None,
|
||||
// multiple unnnamed is invalid. assist is not applicable
|
||||
_ => return None,
|
||||
}
|
||||
};
|
||||
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)));
|
||||
})
|
||||
}
|
||||
|
||||
/// Generate the assist for the impl def case
|
||||
fn generate_impl_def_assist(
|
||||
acc: &mut Assists,
|
||||
impl_def: &ast::Impl,
|
||||
lifetime_loc: TextRange,
|
||||
) -> Option<()> {
|
||||
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, 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));
|
||||
})
|
||||
}
|
||||
|
||||
/// 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::GenericParamList>,
|
||||
) -> Option<char> {
|
||||
match existing_type_param_list {
|
||||
Some(type_params) => {
|
||||
let used_lifetime_params: FxHashSet<_> = type_params
|
||||
.lifetime_params()
|
||||
.map(|p| p.syntax().text().to_string()[1..].to_owned())
|
||||
.collect();
|
||||
(b'a'..=b'z').map(char::from).find(|c| !used_lifetime_params.contains(&c.to_string()))
|
||||
}
|
||||
None => Some('a'),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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::GenericParamsOwner>(
|
||||
type_params_owner: &TypeParamsOwner,
|
||||
builder: &mut AssistBuilder,
|
||||
new_type_params_loc: TextSize,
|
||||
new_lifetime_param: char,
|
||||
) {
|
||||
match type_params_owner.generic_param_list() {
|
||||
// add the new lifetime parameter to an existing type param list
|
||||
Some(type_params) => {
|
||||
builder.insert(
|
||||
(u32::from(type_params.syntax().text_range().end()) - 1).into(),
|
||||
format!(", '{}", new_lifetime_param),
|
||||
);
|
||||
}
|
||||
// create a new type param list containing only the new lifetime parameter
|
||||
None => {
|
||||
builder.insert(new_type_params_loc, format!("<'{}>", new_lifetime_param));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::tests::{check_assist, check_assist_not_applicable};
|
||||
|
||||
#[test]
|
||||
fn test_example_case() {
|
||||
check_assist(
|
||||
introduce_named_lifetime,
|
||||
r#"impl Cursor<'_<|>> {
|
||||
fn node(self) -> &SyntaxNode {
|
||||
match self {
|
||||
Cursor::Replace(node) | Cursor::Before(node) => node,
|
||||
}
|
||||
}
|
||||
}"#,
|
||||
r#"impl<'a> Cursor<'a> {
|
||||
fn node(self) -> &SyntaxNode {
|
||||
match self {
|
||||
Cursor::Replace(node) | Cursor::Before(node) => node,
|
||||
}
|
||||
}
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_example_case_simplified() {
|
||||
check_assist(
|
||||
introduce_named_lifetime,
|
||||
r#"impl Cursor<'_<|>> {"#,
|
||||
r#"impl<'a> Cursor<'a> {"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_example_case_cursor_after_tick() {
|
||||
check_assist(
|
||||
introduce_named_lifetime,
|
||||
r#"impl Cursor<'<|>_> {"#,
|
||||
r#"impl<'a> Cursor<'a> {"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_impl_with_other_type_param() {
|
||||
check_assist(
|
||||
introduce_named_lifetime,
|
||||
"impl<I> fmt::Display for SepByBuilder<'_<|>, I>
|
||||
where
|
||||
I: Iterator,
|
||||
I::Item: fmt::Display,
|
||||
{",
|
||||
"impl<I, 'a> fmt::Display for SepByBuilder<'a, I>
|
||||
where
|
||||
I: Iterator,
|
||||
I::Item: fmt::Display,
|
||||
{",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_example_case_cursor_before_tick() {
|
||||
check_assist(
|
||||
introduce_named_lifetime,
|
||||
r#"impl Cursor<<|>'_> {"#,
|
||||
r#"impl<'a> Cursor<'a> {"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_not_applicable_cursor_position() {
|
||||
check_assist_not_applicable(introduce_named_lifetime, r#"impl Cursor<'_><|> {"#);
|
||||
check_assist_not_applicable(introduce_named_lifetime, r#"impl Cursor<|><'_> {"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_not_applicable_lifetime_already_name() {
|
||||
check_assist_not_applicable(introduce_named_lifetime, r#"impl Cursor<'a<|>> {"#);
|
||||
check_assist_not_applicable(introduce_named_lifetime, r#"fn my_fun<'a>() -> X<'a<|>>"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_type_parameter() {
|
||||
check_assist(
|
||||
introduce_named_lifetime,
|
||||
r#"impl<T> Cursor<T, '_<|>>"#,
|
||||
r#"impl<T, 'a> Cursor<T, 'a>"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_existing_lifetime_name_conflict() {
|
||||
check_assist(
|
||||
introduce_named_lifetime,
|
||||
r#"impl<'a, 'b> Cursor<'a, 'b, '_<|>>"#,
|
||||
r#"impl<'a, 'b, 'c> Cursor<'a, 'b, 'c>"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_function_return_value_anon_lifetime_param() {
|
||||
check_assist(
|
||||
introduce_named_lifetime,
|
||||
r#"fn my_fun() -> X<'_<|>>"#,
|
||||
r#"fn my_fun<'a>() -> X<'a>"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_function_return_value_anon_reference_lifetime() {
|
||||
check_assist(
|
||||
introduce_named_lifetime,
|
||||
r#"fn my_fun() -> &'_<|> X"#,
|
||||
r#"fn my_fun<'a>() -> &'a X"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_function_param_anon_lifetime() {
|
||||
check_assist(
|
||||
introduce_named_lifetime,
|
||||
r#"fn my_fun(x: X<'_<|>>)"#,
|
||||
r#"fn my_fun<'a>(x: X<'a>)"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_function_add_lifetime_to_params() {
|
||||
check_assist(
|
||||
introduce_named_lifetime,
|
||||
r#"fn my_fun(f: &Foo) -> X<'_<|>>"#,
|
||||
r#"fn my_fun<'a>(f: &'a Foo) -> X<'a>"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_function_add_lifetime_to_params_in_presence_of_other_lifetime() {
|
||||
check_assist(
|
||||
introduce_named_lifetime,
|
||||
r#"fn my_fun<'other>(f: &Foo, b: &'other Bar) -> X<'_<|>>"#,
|
||||
r#"fn my_fun<'other, 'a>(f: &'a Foo, b: &'other Bar) -> X<'a>"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_function_not_applicable_without_self_and_multiple_unnamed_param_lifetimes() {
|
||||
// this is not permitted under lifetime elision rules
|
||||
check_assist_not_applicable(
|
||||
introduce_named_lifetime,
|
||||
r#"fn my_fun(f: &Foo, b: &Bar) -> X<'_<|>>"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_function_add_lifetime_to_self_ref_param() {
|
||||
check_assist(
|
||||
introduce_named_lifetime,
|
||||
r#"fn my_fun<'other>(&self, f: &Foo, b: &'other Bar) -> X<'_<|>>"#,
|
||||
r#"fn my_fun<'other, 'a>(&'a self, f: &Foo, b: &'other Bar) -> X<'a>"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_function_add_lifetime_to_param_with_non_ref_self() {
|
||||
check_assist(
|
||||
introduce_named_lifetime,
|
||||
r#"fn my_fun<'other>(self, f: &Foo, b: &'other Bar) -> X<'_<|>>"#,
|
||||
r#"fn my_fun<'other, 'a>(self, f: &'a Foo, b: &'other Bar) -> X<'a>"#,
|
||||
);
|
||||
}
|
||||
}
|
109
crates/assists/src/handlers/invert_if.rs
Normal file
109
crates/assists/src/handlers/invert_if.rs
Normal file
|
@ -0,0 +1,109 @@
|
|||
use syntax::{
|
||||
ast::{self, AstNode},
|
||||
T,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
assist_context::{AssistContext, Assists},
|
||||
utils::invert_boolean_expression,
|
||||
AssistId, AssistKind,
|
||||
};
|
||||
|
||||
// Assist: invert_if
|
||||
//
|
||||
// Apply invert_if
|
||||
// This transforms if expressions of the form `if !x {A} else {B}` into `if x {B} else {A}`
|
||||
// This also works with `!=`. This assist can only be applied with the cursor
|
||||
// on `if`.
|
||||
//
|
||||
// ```
|
||||
// fn main() {
|
||||
// if<|> !y { A } else { B }
|
||||
// }
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// fn main() {
|
||||
// if y { B } else { A }
|
||||
// }
|
||||
// ```
|
||||
|
||||
pub(crate) fn invert_if(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
let if_keyword = ctx.find_token_at_offset(T![if])?;
|
||||
let expr = ast::IfExpr::cast(if_keyword.parent())?;
|
||||
let if_range = if_keyword.text_range();
|
||||
let cursor_in_range = if_range.contains_range(ctx.frange.range);
|
||||
if !cursor_in_range {
|
||||
return None;
|
||||
}
|
||||
|
||||
// This assist should not apply for if-let.
|
||||
if expr.condition()?.pat().is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let cond = expr.condition()?.expr()?;
|
||||
let then_node = expr.then_branch()?.syntax().clone();
|
||||
let else_block = match expr.else_branch()? {
|
||||
ast::ElseBranch::Block(it) => it,
|
||||
ast::ElseBranch::IfExpr(_) => return None,
|
||||
};
|
||||
|
||||
let cond_range = cond.syntax().text_range();
|
||||
let flip_cond = invert_boolean_expression(cond);
|
||||
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", 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());
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::tests::{check_assist, check_assist_not_applicable};
|
||||
|
||||
#[test]
|
||||
fn invert_if_remove_inequality() {
|
||||
check_assist(
|
||||
invert_if,
|
||||
"fn f() { i<|>f x != 3 { 1 } else { 3 + 2 } }",
|
||||
"fn f() { if x == 3 { 3 + 2 } else { 1 } }",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invert_if_remove_not() {
|
||||
check_assist(
|
||||
invert_if,
|
||||
"fn f() { <|>if !cond { 3 * 2 } else { 1 } }",
|
||||
"fn f() { if cond { 1 } else { 3 * 2 } }",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invert_if_general_case() {
|
||||
check_assist(
|
||||
invert_if,
|
||||
"fn f() { i<|>f cond { 3 * 2 } else { 1 } }",
|
||||
"fn f() { if !cond { 1 } else { 3 * 2 } }",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invert_if_doesnt_apply_with_cursor_not_on_if() {
|
||||
check_assist_not_applicable(invert_if, "fn f() { if !<|>cond { 3 * 2 } else { 1 } }")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invert_if_doesnt_apply_with_if_let() {
|
||||
check_assist_not_applicable(
|
||||
invert_if,
|
||||
"fn f() { i<|>f let Some(_) = Some(1) { 1 } else { 0 } }",
|
||||
)
|
||||
}
|
||||
}
|
321
crates/assists/src/handlers/merge_imports.rs
Normal file
321
crates/assists/src/handlers/merge_imports.rs
Normal file
|
@ -0,0 +1,321 @@
|
|||
use std::iter::successors;
|
||||
|
||||
use syntax::{
|
||||
algo::{neighbor, skip_trivia_token, SyntaxRewriter},
|
||||
ast::{self, edit::AstNodeEdit, make},
|
||||
AstNode, Direction, InsertPosition, SyntaxElement, T,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
assist_context::{AssistContext, Assists},
|
||||
AssistId, AssistKind,
|
||||
};
|
||||
|
||||
// Assist: merge_imports
|
||||
//
|
||||
// Merges two imports with a common prefix.
|
||||
//
|
||||
// ```
|
||||
// use std::<|>fmt::Formatter;
|
||||
// use std::io;
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// use std::{fmt::Formatter, io};
|
||||
// ```
|
||||
pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
let tree: ast::UseTree = ctx.find_node_at_offset()?;
|
||||
let mut rewriter = SyntaxRewriter::default();
|
||||
let mut offset = ctx.offset();
|
||||
|
||||
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()?)))
|
||||
.find_map(|(use_item, use_tree)| {
|
||||
Some((try_merge_trees(&tree, &use_tree)?, use_item))
|
||||
})?;
|
||||
|
||||
rewriter.replace_ast(&tree, &merged);
|
||||
rewriter += to_delete.remove();
|
||||
|
||||
if to_delete.syntax().text_range().end() < offset {
|
||||
offset -= to_delete.syntax().text_range().len();
|
||||
}
|
||||
} else {
|
||||
let (merged, to_delete) = next_prev()
|
||||
.filter_map(|dir| neighbor(&tree, dir))
|
||||
.find_map(|use_tree| Some((try_merge_trees(&tree, &use_tree)?, use_tree.clone())))?;
|
||||
|
||||
rewriter.replace_ast(&tree, &merged);
|
||||
rewriter += to_delete.remove();
|
||||
|
||||
if to_delete.syntax().text_range().end() < offset {
|
||||
offset -= to_delete.syntax().text_range().len();
|
||||
}
|
||||
};
|
||||
|
||||
let target = tree.syntax().text_range();
|
||||
acc.add(
|
||||
AssistId("merge_imports", AssistKind::RefactorRewrite),
|
||||
"Merge imports",
|
||||
target,
|
||||
|builder| {
|
||||
builder.rewrite(rewriter);
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn next_prev() -> impl Iterator<Item = Direction> {
|
||||
[Direction::Next, Direction::Prev].iter().copied()
|
||||
}
|
||||
|
||||
fn try_merge_trees(old: &ast::UseTree, new: &ast::UseTree) -> Option<ast::UseTree> {
|
||||
let lhs_path = old.path()?;
|
||||
let rhs_path = new.path()?;
|
||||
|
||||
let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?;
|
||||
|
||||
let lhs = old.split_prefix(&lhs_prefix);
|
||||
let rhs = new.split_prefix(&rhs_prefix);
|
||||
|
||||
let should_insert_comma = lhs
|
||||
.use_tree_list()?
|
||||
.r_curly_token()
|
||||
.and_then(|it| skip_trivia_token(it.prev_token()?, Direction::Prev))
|
||||
.map(|it| it.kind() != T![,])
|
||||
.unwrap_or(true);
|
||||
|
||||
let mut to_insert: Vec<SyntaxElement> = Vec::new();
|
||||
if should_insert_comma {
|
||||
to_insert.push(make::token(T![,]).into());
|
||||
to_insert.push(make::tokens::single_space().into());
|
||||
}
|
||||
to_insert.extend(
|
||||
rhs.use_tree_list()?
|
||||
.syntax()
|
||||
.children_with_tokens()
|
||||
.filter(|it| it.kind() != T!['{'] && it.kind() != T!['}']),
|
||||
);
|
||||
let use_tree_list = lhs.use_tree_list()?;
|
||||
let pos = InsertPosition::Before(use_tree_list.r_curly_token()?.into());
|
||||
let use_tree_list = use_tree_list.insert_children(pos, to_insert);
|
||||
Some(lhs.with_use_tree_list(use_tree_list))
|
||||
}
|
||||
|
||||
fn common_prefix(lhs: &ast::Path, rhs: &ast::Path) -> Option<(ast::Path, ast::Path)> {
|
||||
let mut res = None;
|
||||
let mut lhs_curr = first_path(&lhs);
|
||||
let mut rhs_curr = first_path(&rhs);
|
||||
loop {
|
||||
match (lhs_curr.segment(), rhs_curr.segment()) {
|
||||
(Some(lhs), Some(rhs)) if lhs.syntax().text() == rhs.syntax().text() => (),
|
||||
_ => break,
|
||||
}
|
||||
res = Some((lhs_curr.clone(), rhs_curr.clone()));
|
||||
|
||||
match (lhs_curr.parent_path(), rhs_curr.parent_path()) {
|
||||
(Some(lhs), Some(rhs)) => {
|
||||
lhs_curr = lhs;
|
||||
rhs_curr = rhs;
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
fn first_path(path: &ast::Path) -> ast::Path {
|
||||
successors(Some(path.clone()), |it| it.qualifier()).last().unwrap()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests::{check_assist, check_assist_not_applicable};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_merge_first() {
|
||||
check_assist(
|
||||
merge_imports,
|
||||
r"
|
||||
use std::fmt<|>::Debug;
|
||||
use std::fmt::Display;
|
||||
",
|
||||
r"
|
||||
use std::fmt::{Debug, Display};
|
||||
",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merge_second() {
|
||||
check_assist(
|
||||
merge_imports,
|
||||
r"
|
||||
use std::fmt::Debug;
|
||||
use std::fmt<|>::Display;
|
||||
",
|
||||
r"
|
||||
use std::fmt::{Display, Debug};
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_self1() {
|
||||
check_assist(
|
||||
merge_imports,
|
||||
r"
|
||||
use std::fmt<|>;
|
||||
use std::fmt::Display;
|
||||
",
|
||||
r"
|
||||
use std::fmt::{self, Display};
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_self2() {
|
||||
check_assist(
|
||||
merge_imports,
|
||||
r"
|
||||
use std::{fmt, <|>fmt::Display};
|
||||
",
|
||||
r"
|
||||
use std::{fmt::{Display, self}};
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merge_nested() {
|
||||
check_assist(
|
||||
merge_imports,
|
||||
r"
|
||||
use std::{fmt<|>::Debug, fmt::Display};
|
||||
",
|
||||
r"
|
||||
use std::{fmt::{Debug, Display}};
|
||||
",
|
||||
);
|
||||
check_assist(
|
||||
merge_imports,
|
||||
r"
|
||||
use std::{fmt::Debug, fmt<|>::Display};
|
||||
",
|
||||
r"
|
||||
use std::{fmt::{Display, Debug}};
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merge_single_wildcard_diff_prefixes() {
|
||||
check_assist(
|
||||
merge_imports,
|
||||
r"
|
||||
use std<|>::cell::*;
|
||||
use std::str;
|
||||
",
|
||||
r"
|
||||
use std::{cell::*, str};
|
||||
",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merge_both_wildcard_diff_prefixes() {
|
||||
check_assist(
|
||||
merge_imports,
|
||||
r"
|
||||
use std<|>::cell::*;
|
||||
use std::str::*;
|
||||
",
|
||||
r"
|
||||
use std::{cell::*, str::*};
|
||||
",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn removes_just_enough_whitespace() {
|
||||
check_assist(
|
||||
merge_imports,
|
||||
r"
|
||||
use foo<|>::bar;
|
||||
use foo::baz;
|
||||
|
||||
/// Doc comment
|
||||
",
|
||||
r"
|
||||
use foo::{bar, baz};
|
||||
|
||||
/// Doc comment
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn works_with_trailing_comma() {
|
||||
check_assist(
|
||||
merge_imports,
|
||||
r"
|
||||
use {
|
||||
foo<|>::bar,
|
||||
foo::baz,
|
||||
};
|
||||
",
|
||||
r"
|
||||
use {
|
||||
foo::{bar, baz},
|
||||
};
|
||||
",
|
||||
);
|
||||
check_assist(
|
||||
merge_imports,
|
||||
r"
|
||||
use {
|
||||
foo::baz,
|
||||
foo<|>::bar,
|
||||
};
|
||||
",
|
||||
r"
|
||||
use {
|
||||
foo::{bar, baz},
|
||||
};
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_double_comma() {
|
||||
check_assist(
|
||||
merge_imports,
|
||||
r"
|
||||
use foo::bar::baz;
|
||||
use foo::<|>{
|
||||
FooBar,
|
||||
};
|
||||
",
|
||||
r"
|
||||
use foo::{
|
||||
FooBar,
|
||||
bar::baz};
|
||||
",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_use() {
|
||||
check_assist_not_applicable(
|
||||
merge_imports,
|
||||
r"
|
||||
use std::<|>
|
||||
fn main() {}",
|
||||
);
|
||||
}
|
||||
}
|
248
crates/assists/src/handlers/merge_match_arms.rs
Normal file
248
crates/assists/src/handlers/merge_match_arms.rs
Normal file
|
@ -0,0 +1,248 @@
|
|||
use std::iter::successors;
|
||||
|
||||
use syntax::{
|
||||
algo::neighbor,
|
||||
ast::{self, AstNode},
|
||||
Direction,
|
||||
};
|
||||
|
||||
use crate::{AssistContext, AssistId, AssistKind, Assists, TextRange};
|
||||
|
||||
// Assist: merge_match_arms
|
||||
//
|
||||
// Merges identical match arms.
|
||||
//
|
||||
// ```
|
||||
// enum Action { Move { distance: u32 }, Stop }
|
||||
//
|
||||
// fn handle(action: Action) {
|
||||
// match action {
|
||||
// <|>Action::Move(..) => foo(),
|
||||
// Action::Stop => foo(),
|
||||
// }
|
||||
// }
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// enum Action { Move { distance: u32 }, Stop }
|
||||
//
|
||||
// fn handle(action: Action) {
|
||||
// match action {
|
||||
// Action::Move(..) | Action::Stop => foo(),
|
||||
// }
|
||||
// }
|
||||
// ```
|
||||
pub(crate) fn merge_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
let current_arm = ctx.find_node_at_offset::<ast::MatchArm>()?;
|
||||
// Don't try to handle arms with guards for now - can add support for this later
|
||||
if current_arm.guard().is_some() {
|
||||
return None;
|
||||
}
|
||||
let current_expr = current_arm.expr()?;
|
||||
let current_text_range = current_arm.syntax().text_range();
|
||||
|
||||
// We check if the following match arms match this one. We could, but don't,
|
||||
// compare to the previous match arm as well.
|
||||
let arms_to_merge = successors(Some(current_arm), |it| neighbor(it, Direction::Next))
|
||||
.take_while(|arm| {
|
||||
if arm.guard().is_some() {
|
||||
return false;
|
||||
}
|
||||
match arm.expr() {
|
||||
Some(expr) => expr.syntax().text() == current_expr.syntax().text(),
|
||||
None => false,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if arms_to_merge.len() <= 1 {
|
||||
return None;
|
||||
}
|
||||
|
||||
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 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);
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn contains_placeholder(a: &ast::MatchArm) -> bool {
|
||||
matches!(a.pat(), Some(ast::Pat::WildcardPat(..)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests::{check_assist, check_assist_not_applicable};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn merge_match_arms_single_patterns() {
|
||||
check_assist(
|
||||
merge_match_arms,
|
||||
r#"
|
||||
#[derive(Debug)]
|
||||
enum X { A, B, C }
|
||||
|
||||
fn main() {
|
||||
let x = X::A;
|
||||
let y = match x {
|
||||
X::A => { 1i32<|> }
|
||||
X::B => { 1i32 }
|
||||
X::C => { 2i32 }
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
#[derive(Debug)]
|
||||
enum X { A, B, C }
|
||||
|
||||
fn main() {
|
||||
let x = X::A;
|
||||
let y = match x {
|
||||
X::A | X::B => { 1i32 }
|
||||
X::C => { 2i32 }
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_match_arms_multiple_patterns() {
|
||||
check_assist(
|
||||
merge_match_arms,
|
||||
r#"
|
||||
#[derive(Debug)]
|
||||
enum X { A, B, C, D, E }
|
||||
|
||||
fn main() {
|
||||
let x = X::A;
|
||||
let y = match x {
|
||||
X::A | X::B => {<|> 1i32 },
|
||||
X::C | X::D => { 1i32 },
|
||||
X::E => { 2i32 },
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
#[derive(Debug)]
|
||||
enum X { A, B, C, D, E }
|
||||
|
||||
fn main() {
|
||||
let x = X::A;
|
||||
let y = match x {
|
||||
X::A | X::B | X::C | X::D => { 1i32 },
|
||||
X::E => { 2i32 },
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_match_arms_placeholder_pattern() {
|
||||
check_assist(
|
||||
merge_match_arms,
|
||||
r#"
|
||||
#[derive(Debug)]
|
||||
enum X { A, B, C, D, E }
|
||||
|
||||
fn main() {
|
||||
let x = X::A;
|
||||
let y = match x {
|
||||
X::A => { 1i32 },
|
||||
X::B => { 2i<|>32 },
|
||||
_ => { 2i32 }
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
#[derive(Debug)]
|
||||
enum X { A, B, C, D, E }
|
||||
|
||||
fn main() {
|
||||
let x = X::A;
|
||||
let y = match x {
|
||||
X::A => { 1i32 },
|
||||
_ => { 2i32 }
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merges_all_subsequent_arms() {
|
||||
check_assist(
|
||||
merge_match_arms,
|
||||
r#"
|
||||
enum X { A, B, C, D, E }
|
||||
|
||||
fn main() {
|
||||
match X::A {
|
||||
X::A<|> => 92,
|
||||
X::B => 92,
|
||||
X::C => 92,
|
||||
X::D => 62,
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
enum X { A, B, C, D, E }
|
||||
|
||||
fn main() {
|
||||
match X::A {
|
||||
X::A | X::B | X::C => 92,
|
||||
X::D => 62,
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_match_arms_rejects_guards() {
|
||||
check_assist_not_applicable(
|
||||
merge_match_arms,
|
||||
r#"
|
||||
#[derive(Debug)]
|
||||
enum X {
|
||||
A(i32),
|
||||
B,
|
||||
C
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let x = X::A;
|
||||
let y = match x {
|
||||
X::A(a) if a > 5 => { <|>1i32 },
|
||||
X::B => { 1i32 },
|
||||
X::C => { 2i32 }
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
}
|
152
crates/assists/src/handlers/move_bounds.rs
Normal file
152
crates/assists/src/handlers/move_bounds.rs
Normal file
|
@ -0,0 +1,152 @@
|
|||
use syntax::{
|
||||
ast::{self, edit::AstNodeEdit, make, AstNode, NameOwner, TypeBoundsOwner},
|
||||
match_ast,
|
||||
SyntaxKind::*,
|
||||
T,
|
||||
};
|
||||
|
||||
use crate::{AssistContext, AssistId, AssistKind, Assists};
|
||||
|
||||
// Assist: move_bounds_to_where_clause
|
||||
//
|
||||
// Moves inline type bounds to a where clause.
|
||||
//
|
||||
// ```
|
||||
// fn apply<T, U, <|>F: FnOnce(T) -> U>(f: F, x: T) -> U {
|
||||
// f(x)
|
||||
// }
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// fn apply<T, U, F>(f: F, x: T) -> U where F: FnOnce(T) -> U {
|
||||
// f(x)
|
||||
// }
|
||||
// ```
|
||||
pub(crate) fn move_bounds_to_where_clause(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
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()) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let parent = type_param_list.syntax().parent()?;
|
||||
if parent.children_with_tokens().any(|it| it.kind() == WHERE_CLAUSE) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let anchor = match_ast! {
|
||||
match parent {
|
||||
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_LIST || it.kind() == T![;])?
|
||||
},
|
||||
_ => return None
|
||||
}
|
||||
};
|
||||
|
||||
let target = type_param_list.syntax().text_range();
|
||||
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 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);
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn build_predicate(param: ast::TypeParam) -> Option<ast::WherePred> {
|
||||
let path = {
|
||||
let name_ref = make::name_ref(¶m.name()?.syntax().to_string());
|
||||
let segment = make::path_segment(name_ref);
|
||||
make::path_unqualified(segment)
|
||||
};
|
||||
let predicate = make::where_pred(path, param.type_bound_list()?.bounds());
|
||||
Some(predicate)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::tests::check_assist;
|
||||
|
||||
#[test]
|
||||
fn move_bounds_to_where_clause_fn() {
|
||||
check_assist(
|
||||
move_bounds_to_where_clause,
|
||||
r#"
|
||||
fn foo<T: u32, <|>F: FnOnce(T) -> T>() {}
|
||||
"#,
|
||||
r#"
|
||||
fn foo<T, F>() where T: u32, F: FnOnce(T) -> T {}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn move_bounds_to_where_clause_impl() {
|
||||
check_assist(
|
||||
move_bounds_to_where_clause,
|
||||
r#"
|
||||
impl<U: u32, <|>T> A<U, T> {}
|
||||
"#,
|
||||
r#"
|
||||
impl<U, T> A<U, T> where U: u32 {}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn move_bounds_to_where_clause_struct() {
|
||||
check_assist(
|
||||
move_bounds_to_where_clause,
|
||||
r#"
|
||||
struct A<<|>T: Iterator<Item = u32>> {}
|
||||
"#,
|
||||
r#"
|
||||
struct A<T> where T: Iterator<Item = u32> {}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn move_bounds_to_where_clause_tuple_struct() {
|
||||
check_assist(
|
||||
move_bounds_to_where_clause,
|
||||
r#"
|
||||
struct Pair<<|>T: u32>(T, T);
|
||||
"#,
|
||||
r#"
|
||||
struct Pair<T>(T, T) where T: u32;
|
||||
"#,
|
||||
);
|
||||
}
|
||||
}
|
293
crates/assists/src/handlers/move_guard.rs
Normal file
293
crates/assists/src/handlers/move_guard.rs
Normal file
|
@ -0,0 +1,293 @@
|
|||
use syntax::{
|
||||
ast::{edit::AstNodeEdit, make, AstNode, IfExpr, MatchArm},
|
||||
SyntaxKind::WHITESPACE,
|
||||
};
|
||||
|
||||
use crate::{AssistContext, AssistId, AssistKind, Assists};
|
||||
|
||||
// Assist: move_guard_to_arm_body
|
||||
//
|
||||
// Moves match guard into match arm body.
|
||||
//
|
||||
// ```
|
||||
// enum Action { Move { distance: u32 }, Stop }
|
||||
//
|
||||
// fn handle(action: Action) {
|
||||
// match action {
|
||||
// Action::Move { distance } <|>if distance > 10 => foo(),
|
||||
// _ => (),
|
||||
// }
|
||||
// }
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// enum Action { Move { distance: u32 }, Stop }
|
||||
//
|
||||
// fn handle(action: Action) {
|
||||
// match action {
|
||||
// Action::Move { distance } => if distance > 10 {
|
||||
// foo()
|
||||
// },
|
||||
// _ => (),
|
||||
// }
|
||||
// }
|
||||
// ```
|
||||
pub(crate) fn move_guard_to_arm_body(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
let match_arm = ctx.find_node_at_offset::<MatchArm>()?;
|
||||
let guard = match_arm.guard()?;
|
||||
let space_before_guard = guard.syntax().prev_sibling_or_token();
|
||||
|
||||
let guard_condition = guard.expr()?;
|
||||
let arm_expr = match_arm.expr()?;
|
||||
let if_expr = make::expr_if(
|
||||
make::condition(guard_condition, None),
|
||||
make::block_expr(None, Some(arm_expr.clone())),
|
||||
)
|
||||
.indent(arm_expr.indent_level());
|
||||
|
||||
let target = guard.syntax().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_ast(arm_expr, if_expr);
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Assist: move_arm_cond_to_match_guard
|
||||
//
|
||||
// Moves if expression from match arm body into a guard.
|
||||
//
|
||||
// ```
|
||||
// enum Action { Move { distance: u32 }, Stop }
|
||||
//
|
||||
// fn handle(action: Action) {
|
||||
// match action {
|
||||
// Action::Move { distance } => <|>if distance > 10 { foo() },
|
||||
// _ => (),
|
||||
// }
|
||||
// }
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// enum Action { Move { distance: u32 }, Stop }
|
||||
//
|
||||
// fn handle(action: Action) {
|
||||
// match action {
|
||||
// Action::Move { distance } if distance > 10 => foo(),
|
||||
// _ => (),
|
||||
// }
|
||||
// }
|
||||
// ```
|
||||
pub(crate) fn move_arm_cond_to_match_guard(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
let match_arm: MatchArm = ctx.find_node_at_offset::<MatchArm>()?;
|
||||
let match_pat = match_arm.pat()?;
|
||||
|
||||
let arm_body = match_arm.expr()?;
|
||||
let if_expr: IfExpr = IfExpr::cast(arm_body.syntax().clone())?;
|
||||
let cond = if_expr.condition()?;
|
||||
let then_block = if_expr.then_branch()?;
|
||||
|
||||
// Not support if with else branch
|
||||
if if_expr.else_branch().is_some() {
|
||||
return None;
|
||||
}
|
||||
// Not support moving if let to arm guard
|
||||
if cond.pat().is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let buf = format!(" if {}", cond.syntax().text());
|
||||
|
||||
let target = if_expr.syntax().text_range();
|
||||
acc.add(
|
||||
AssistId("move_arm_cond_to_match_guard", AssistKind::RefactorRewrite),
|
||||
"Move condition to match guard",
|
||||
target,
|
||||
|edit| {
|
||||
let then_only_expr = then_block.statements().next().is_none();
|
||||
|
||||
match &then_block.expr() {
|
||||
Some(then_expr) if then_only_expr => {
|
||||
edit.replace(if_expr.syntax().text_range(), then_expr.syntax().text())
|
||||
}
|
||||
_ => edit.replace(if_expr.syntax().text_range(), then_block.syntax().text()),
|
||||
}
|
||||
|
||||
edit.insert(match_pat.syntax().text_range().end(), buf);
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
|
||||
|
||||
#[test]
|
||||
fn move_guard_to_arm_body_target() {
|
||||
check_assist_target(
|
||||
move_guard_to_arm_body,
|
||||
r#"
|
||||
fn main() {
|
||||
match 92 {
|
||||
x <|>if x > 10 => false,
|
||||
_ => true
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"if x > 10"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn move_guard_to_arm_body_works() {
|
||||
check_assist(
|
||||
move_guard_to_arm_body,
|
||||
r#"
|
||||
fn main() {
|
||||
match 92 {
|
||||
x <|>if x > 10 => false,
|
||||
_ => true
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn main() {
|
||||
match 92 {
|
||||
x => if x > 10 {
|
||||
false
|
||||
},
|
||||
_ => true
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn move_guard_to_arm_body_works_complex_match() {
|
||||
check_assist(
|
||||
move_guard_to_arm_body,
|
||||
r#"
|
||||
fn main() {
|
||||
match 92 {
|
||||
<|>x @ 4 | x @ 5 if x > 5 => true,
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn main() {
|
||||
match 92 {
|
||||
x @ 4 | x @ 5 => if x > 5 {
|
||||
true
|
||||
},
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn move_arm_cond_to_match_guard_works() {
|
||||
check_assist(
|
||||
move_arm_cond_to_match_guard,
|
||||
r#"
|
||||
fn main() {
|
||||
match 92 {
|
||||
x => if x > 10 { <|>false },
|
||||
_ => true
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn main() {
|
||||
match 92 {
|
||||
x if x > 10 => false,
|
||||
_ => true
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn move_arm_cond_to_match_guard_if_let_not_works() {
|
||||
check_assist_not_applicable(
|
||||
move_arm_cond_to_match_guard,
|
||||
r#"
|
||||
fn main() {
|
||||
match 92 {
|
||||
x => if let 62 = x { <|>false },
|
||||
_ => true
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn move_arm_cond_to_match_guard_if_empty_body_works() {
|
||||
check_assist(
|
||||
move_arm_cond_to_match_guard,
|
||||
r#"
|
||||
fn main() {
|
||||
match 92 {
|
||||
x => if x > 10 { <|> },
|
||||
_ => true
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn main() {
|
||||
match 92 {
|
||||
x if x > 10 => { },
|
||||
_ => true
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn move_arm_cond_to_match_guard_if_multiline_body_works() {
|
||||
check_assist(
|
||||
move_arm_cond_to_match_guard,
|
||||
r#"
|
||||
fn main() {
|
||||
match 92 {
|
||||
x => if x > 10 {
|
||||
92;<|>
|
||||
false
|
||||
},
|
||||
_ => true
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn main() {
|
||||
match 92 {
|
||||
x if x > 10 => {
|
||||
92;
|
||||
false
|
||||
},
|
||||
_ => true
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
}
|
504
crates/assists/src/handlers/raw_string.rs
Normal file
504
crates/assists/src/handlers/raw_string.rs
Normal file
|
@ -0,0 +1,504 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use syntax::{
|
||||
ast::{self, HasQuotes, HasStringValue},
|
||||
AstToken,
|
||||
SyntaxKind::{RAW_STRING, STRING},
|
||||
TextRange, TextSize,
|
||||
};
|
||||
use test_utils::mark;
|
||||
|
||||
use crate::{AssistContext, AssistId, AssistKind, Assists};
|
||||
|
||||
// Assist: make_raw_string
|
||||
//
|
||||
// Adds `r#` to a plain string literal.
|
||||
//
|
||||
// ```
|
||||
// fn main() {
|
||||
// "Hello,<|> World!";
|
||||
// }
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// fn main() {
|
||||
// r#"Hello, World!"#;
|
||||
// }
|
||||
// ```
|
||||
pub(crate) fn make_raw_string(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", 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
|
||||
//
|
||||
// Turns a raw string into a plain string.
|
||||
//
|
||||
// ```
|
||||
// fn main() {
|
||||
// r#"Hello,<|> "World!""#;
|
||||
// }
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// fn main() {
|
||||
// "Hello, \"World!\"";
|
||||
// }
|
||||
// ```
|
||||
pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
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", 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
|
||||
//
|
||||
// Adds a hash to a raw string literal.
|
||||
//
|
||||
// ```
|
||||
// fn main() {
|
||||
// r#"Hello,<|> World!"#;
|
||||
// }
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// fn main() {
|
||||
// r##"Hello, World!"##;
|
||||
// }
|
||||
// ```
|
||||
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", AssistKind::Refactor), "Add #", target, |edit| {
|
||||
edit.insert(token.text_range().start() + TextSize::of('r'), "#");
|
||||
edit.insert(token.text_range().end(), "#");
|
||||
})
|
||||
}
|
||||
|
||||
// Assist: remove_hash
|
||||
//
|
||||
// Removes a hash from a raw string literal.
|
||||
//
|
||||
// ```
|
||||
// fn main() {
|
||||
// r#"Hello,<|> World!"#;
|
||||
// }
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// fn main() {
|
||||
// r"Hello, World!";
|
||||
// }
|
||||
// ```
|
||||
pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
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#") && text.ends_with('#') {
|
||||
return None;
|
||||
}
|
||||
|
||||
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 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 n_hashes = sub.chars().take_while(|c| *c == '#').count();
|
||||
res = res.max(n_hashes + 1)
|
||||
}
|
||||
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 tests {
|
||||
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(
|
||||
make_raw_string,
|
||||
r#"
|
||||
fn f() {
|
||||
let s = <|>"random\nstring";
|
||||
}
|
||||
"#,
|
||||
r#""random\nstring""#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn make_raw_string_works() {
|
||||
check_assist(
|
||||
make_raw_string,
|
||||
r#"
|
||||
fn f() {
|
||||
let s = <|>"random\nstring";
|
||||
}
|
||||
"#,
|
||||
r##"
|
||||
fn f() {
|
||||
let s = r#"random
|
||||
string"#;
|
||||
}
|
||||
"##,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn make_raw_string_works_inside_macros() {
|
||||
check_assist(
|
||||
make_raw_string,
|
||||
r#"
|
||||
fn f() {
|
||||
format!(<|>"x = {}", 92)
|
||||
}
|
||||
"#,
|
||||
r##"
|
||||
fn f() {
|
||||
format!(r#"x = {}"#, 92)
|
||||
}
|
||||
"##,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn make_raw_string_hashes_inside_works() {
|
||||
check_assist(
|
||||
make_raw_string,
|
||||
r###"
|
||||
fn f() {
|
||||
let s = <|>"#random##\nstring";
|
||||
}
|
||||
"###,
|
||||
r####"
|
||||
fn f() {
|
||||
let s = r#"#random##
|
||||
string"#;
|
||||
}
|
||||
"####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn make_raw_string_closing_hashes_inside_works() {
|
||||
check_assist(
|
||||
make_raw_string,
|
||||
r###"
|
||||
fn f() {
|
||||
let s = <|>"#random\"##\nstring";
|
||||
}
|
||||
"###,
|
||||
r####"
|
||||
fn f() {
|
||||
let s = r###"#random"##
|
||||
string"###;
|
||||
}
|
||||
"####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn make_raw_string_nothing_to_unescape_works() {
|
||||
check_assist(
|
||||
make_raw_string,
|
||||
r#"
|
||||
fn f() {
|
||||
let s = <|>"random string";
|
||||
}
|
||||
"#,
|
||||
r##"
|
||||
fn f() {
|
||||
let s = r#"random string"#;
|
||||
}
|
||||
"##,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn make_raw_string_not_works_on_partial_string() {
|
||||
check_assist_not_applicable(
|
||||
make_raw_string,
|
||||
r#"
|
||||
fn f() {
|
||||
let s = "foo<|>
|
||||
}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn make_usual_string_not_works_on_partial_string() {
|
||||
check_assist_not_applicable(
|
||||
make_usual_string,
|
||||
r#"
|
||||
fn main() {
|
||||
let s = r#"bar<|>
|
||||
}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_hash_target() {
|
||||
check_assist_target(
|
||||
add_hash,
|
||||
r#"
|
||||
fn f() {
|
||||
let s = <|>r"random string";
|
||||
}
|
||||
"#,
|
||||
r#"r"random string""#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_hash_works() {
|
||||
check_assist(
|
||||
add_hash,
|
||||
r#"
|
||||
fn f() {
|
||||
let s = <|>r"random string";
|
||||
}
|
||||
"#,
|
||||
r##"
|
||||
fn f() {
|
||||
let s = r#"random string"#;
|
||||
}
|
||||
"##,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_more_hash_works() {
|
||||
check_assist(
|
||||
add_hash,
|
||||
r##"
|
||||
fn f() {
|
||||
let s = <|>r#"random"string"#;
|
||||
}
|
||||
"##,
|
||||
r###"
|
||||
fn f() {
|
||||
let s = r##"random"string"##;
|
||||
}
|
||||
"###,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_hash_not_works() {
|
||||
check_assist_not_applicable(
|
||||
add_hash,
|
||||
r#"
|
||||
fn f() {
|
||||
let s = <|>"random string";
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_hash_target() {
|
||||
check_assist_target(
|
||||
remove_hash,
|
||||
r##"
|
||||
fn f() {
|
||||
let s = <|>r#"random string"#;
|
||||
}
|
||||
"##,
|
||||
r##"r#"random string"#"##,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_hash_works() {
|
||||
check_assist(
|
||||
remove_hash,
|
||||
r##"fn f() { let s = <|>r#"random string"#; }"##,
|
||||
r#"fn f() { let s = r"random string"; }"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
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"#;
|
||||
}
|
||||
"##,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_more_hash_works() {
|
||||
check_assist(
|
||||
remove_hash,
|
||||
r###"
|
||||
fn f() {
|
||||
let s = <|>r##"random string"##;
|
||||
}
|
||||
"###,
|
||||
r##"
|
||||
fn f() {
|
||||
let s = r#"random string"#;
|
||||
}
|
||||
"##,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_hash_doesnt_work() {
|
||||
check_assist_not_applicable(remove_hash, r#"fn f() { let s = <|>"random string"; }"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_hash_no_hash_doesnt_work() {
|
||||
check_assist_not_applicable(remove_hash, r#"fn f() { let s = <|>r"random string"; }"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn make_usual_string_target() {
|
||||
check_assist_target(
|
||||
make_usual_string,
|
||||
r##"
|
||||
fn f() {
|
||||
let s = <|>r#"random string"#;
|
||||
}
|
||||
"##,
|
||||
r##"r#"random string"#"##,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn make_usual_string_works() {
|
||||
check_assist(
|
||||
make_usual_string,
|
||||
r##"
|
||||
fn f() {
|
||||
let s = <|>r#"random string"#;
|
||||
}
|
||||
"##,
|
||||
r#"
|
||||
fn f() {
|
||||
let s = "random string";
|
||||
}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn make_usual_string_with_quote_works() {
|
||||
check_assist(
|
||||
make_usual_string,
|
||||
r##"
|
||||
fn f() {
|
||||
let s = <|>r#"random"str"ing"#;
|
||||
}
|
||||
"##,
|
||||
r#"
|
||||
fn f() {
|
||||
let s = "random\"str\"ing";
|
||||
}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn make_usual_string_more_hash_works() {
|
||||
check_assist(
|
||||
make_usual_string,
|
||||
r###"
|
||||
fn f() {
|
||||
let s = <|>r##"random string"##;
|
||||
}
|
||||
"###,
|
||||
r##"
|
||||
fn f() {
|
||||
let s = "random string";
|
||||
}
|
||||
"##,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn make_usual_string_not_works() {
|
||||
check_assist_not_applicable(
|
||||
make_usual_string,
|
||||
r#"
|
||||
fn f() {
|
||||
let s = <|>"random string";
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
}
|
205
crates/assists/src/handlers/remove_dbg.rs
Normal file
205
crates/assists/src/handlers/remove_dbg.rs
Normal file
|
@ -0,0 +1,205 @@
|
|||
use syntax::{
|
||||
ast::{self, AstNode},
|
||||
TextRange, TextSize, T,
|
||||
};
|
||||
|
||||
use crate::{AssistContext, AssistId, AssistKind, Assists};
|
||||
|
||||
// Assist: remove_dbg
|
||||
//
|
||||
// Removes `dbg!()` macro call.
|
||||
//
|
||||
// ```
|
||||
// fn main() {
|
||||
// <|>dbg!(92);
|
||||
// }
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// fn main() {
|
||||
// 92;
|
||||
// }
|
||||
// ```
|
||||
pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?;
|
||||
|
||||
if !is_valid_macrocall(¯o_call, "dbg")? {
|
||||
return None;
|
||||
}
|
||||
|
||||
let is_leaf = macro_call.syntax().next_sibling().is_none();
|
||||
|
||||
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()
|
||||
};
|
||||
|
||||
// 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", AssistKind::Refactor), "Remove dbg!()", target, |builder| {
|
||||
builder.replace(macro_range, paste_instead_of_dbg);
|
||||
})
|
||||
}
|
||||
|
||||
/// Verifies that the given macro_call actually matches the given name
|
||||
/// and contains proper ending tokens
|
||||
fn is_valid_macrocall(macro_call: &ast::MacroCall, macro_name: &str) -> Option<bool> {
|
||||
let path = macro_call.path()?;
|
||||
let name_ref = path.segment()?.name_ref()?;
|
||||
|
||||
// Make sure it is actually a dbg-macro call, dbg followed by !
|
||||
let excl = path.syntax().next_sibling_or_token()?;
|
||||
|
||||
if name_ref.text() != macro_name || excl.kind() != T![!] {
|
||||
return None;
|
||||
}
|
||||
|
||||
let node = macro_call.token_tree()?.syntax().clone();
|
||||
let first_child = node.first_child_or_token()?;
|
||||
let last_child = node.last_child_or_token()?;
|
||||
|
||||
match (first_child.kind(), last_child.kind()) {
|
||||
(T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) => Some(true),
|
||||
_ => Some(false),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
|
||||
|
||||
#[test]
|
||||
fn test_remove_dbg() {
|
||||
check_assist(remove_dbg, "<|>dbg!(1 + 1)", "1 + 1");
|
||||
|
||||
check_assist(remove_dbg, "dbg!<|>((1 + 1))", "(1 + 1)");
|
||||
|
||||
check_assist(remove_dbg, "dbg!(1 <|>+ 1)", "1 + 1");
|
||||
|
||||
check_assist(remove_dbg, "let _ = <|>dbg!(1 + 1)", "let _ = 1 + 1");
|
||||
|
||||
check_assist(
|
||||
remove_dbg,
|
||||
"
|
||||
fn foo(n: usize) {
|
||||
if let Some(_) = dbg!(n.<|>checked_sub(4)) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
",
|
||||
"
|
||||
fn foo(n: usize) {
|
||||
if let Some(_) = n.checked_sub(4) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_dbg_with_brackets_and_braces() {
|
||||
check_assist(remove_dbg, "dbg![<|>1 + 1]", "1 + 1");
|
||||
check_assist(remove_dbg, "dbg!{<|>1 + 1}", "1 + 1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_dbg_not_applicable() {
|
||||
check_assist_not_applicable(remove_dbg, "<|>vec![1, 2, 3]");
|
||||
check_assist_not_applicable(remove_dbg, "<|>dbg(5, 6, 7)");
|
||||
check_assist_not_applicable(remove_dbg, "<|>dbg!(5, 6, 7");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_dbg_target() {
|
||||
check_assist_target(
|
||||
remove_dbg,
|
||||
"
|
||||
fn foo(n: usize) {
|
||||
if let Some(_) = dbg!(n.<|>checked_sub(4)) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
",
|
||||
"dbg!(n.checked_sub(4))",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_dbg_keep_semicolon() {
|
||||
// https://github.com/rust-analyzer/rust-analyzer/issues/5129#issuecomment-651399779
|
||||
// not quite though
|
||||
// adding a comment at the end of the line makes
|
||||
// the ast::MacroCall to include the semicolon at the end
|
||||
check_assist(
|
||||
remove_dbg,
|
||||
r#"let res = <|>dbg!(1 * 20); // needless comment"#,
|
||||
r#"let res = 1 * 20; // needless comment"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_dbg_keep_expression() {
|
||||
check_assist(
|
||||
remove_dbg,
|
||||
r#"let res = <|>dbg!(a + b).foo();"#,
|
||||
r#"let res = (a + b).foo();"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_dbg_from_inside_fn() {
|
||||
check_assist_target(
|
||||
remove_dbg,
|
||||
r#"
|
||||
fn square(x: u32) -> u32 {
|
||||
x * x
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let x = square(dbg<|>!(5 + 10));
|
||||
println!("{}", x);
|
||||
}"#,
|
||||
"dbg!(5 + 10)",
|
||||
);
|
||||
|
||||
check_assist(
|
||||
remove_dbg,
|
||||
r#"
|
||||
fn square(x: u32) -> u32 {
|
||||
x * x
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let x = square(dbg<|>!(5 + 10));
|
||||
println!("{}", x);
|
||||
}"#,
|
||||
r#"
|
||||
fn square(x: u32) -> u32 {
|
||||
x * x
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let x = square(5 + 10);
|
||||
println!("{}", x);
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
}
|
37
crates/assists/src/handlers/remove_mut.rs
Normal file
37
crates/assists/src/handlers/remove_mut.rs
Normal file
|
@ -0,0 +1,37 @@
|
|||
use syntax::{SyntaxKind, TextRange, T};
|
||||
|
||||
use crate::{AssistContext, AssistId, AssistKind, Assists};
|
||||
|
||||
// Assist: remove_mut
|
||||
//
|
||||
// Removes the `mut` keyword.
|
||||
//
|
||||
// ```
|
||||
// impl Walrus {
|
||||
// fn feed(&mut<|> self, amount: u32) {}
|
||||
// }
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// impl Walrus {
|
||||
// fn feed(&self, amount: u32) {}
|
||||
// }
|
||||
// ```
|
||||
pub(crate) fn remove_mut(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
let mut_token = ctx.find_token_at_offset(T![mut])?;
|
||||
let delete_from = mut_token.text_range().start();
|
||||
let delete_to = match mut_token.next_token() {
|
||||
Some(it) if it.kind() == SyntaxKind::WHITESPACE => it.text_range().end(),
|
||||
_ => mut_token.text_range().end(),
|
||||
};
|
||||
|
||||
let target = mut_token.text_range();
|
||||
acc.add(
|
||||
AssistId("remove_mut", AssistKind::Refactor),
|
||||
"Remove `mut` keyword",
|
||||
target,
|
||||
|builder| {
|
||||
builder.delete(TextRange::new(delete_from, delete_to));
|
||||
},
|
||||
)
|
||||
}
|
220
crates/assists/src/handlers/reorder_fields.rs
Normal file
220
crates/assists/src/handlers/reorder_fields.rs
Normal file
|
@ -0,0 +1,220 @@
|
|||
use itertools::Itertools;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use hir::{Adt, ModuleDef, PathResolution, Semantics, Struct};
|
||||
use ide_db::RootDatabase;
|
||||
use syntax::{algo, ast, match_ast, AstNode, SyntaxKind, SyntaxKind::*, SyntaxNode};
|
||||
|
||||
use crate::{AssistContext, AssistId, AssistKind, Assists};
|
||||
|
||||
// Assist: reorder_fields
|
||||
//
|
||||
// Reorder the fields of record literals and record patterns in the same order as in
|
||||
// the definition.
|
||||
//
|
||||
// ```
|
||||
// struct Foo {foo: i32, bar: i32};
|
||||
// const test: Foo = <|>Foo {bar: 0, foo: 1}
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// struct Foo {foo: i32, bar: i32};
|
||||
// const test: Foo = Foo {foo: 1, bar: 0}
|
||||
// ```
|
||||
//
|
||||
pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
reorder::<ast::RecordExpr>(acc, ctx).or_else(|| reorder::<ast::RecordPat>(acc, ctx))
|
||||
}
|
||||
|
||||
fn reorder<R: AstNode>(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
let record = ctx.find_node_at_offset::<R>()?;
|
||||
let path = record.syntax().children().find_map(ast::Path::cast)?;
|
||||
|
||||
let ranks = compute_fields_ranks(&path, &ctx)?;
|
||||
|
||||
let fields = get_fields(&record.syntax());
|
||||
let sorted_fields = sorted_by_rank(&fields, |node| {
|
||||
*ranks.get(&get_field_name(node)).unwrap_or(&usize::max_value())
|
||||
});
|
||||
|
||||
if sorted_fields == fields {
|
||||
return None;
|
||||
}
|
||||
|
||||
let target = record.syntax().text_range();
|
||||
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_EXPR => vec![RECORD_EXPR_FIELD],
|
||||
RECORD_PAT => vec![RECORD_PAT_FIELD, IDENT_PAT],
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn get_field_name(node: &SyntaxNode) -> String {
|
||||
let res = match_ast! {
|
||||
match node {
|
||||
ast::RecordExprField(field) => field.field_name().map(|it| it.to_string()),
|
||||
ast::RecordPatField(field) => field.field_name().map(|it| it.to_string()),
|
||||
_ => None,
|
||||
}
|
||||
};
|
||||
res.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn get_fields(record: &SyntaxNode) -> Vec<SyntaxNode> {
|
||||
let kinds = get_fields_kind(record);
|
||||
record.children().flat_map(|n| n.children()).filter(|n| kinds.contains(&n.kind())).collect()
|
||||
}
|
||||
|
||||
fn sorted_by_rank(
|
||||
fields: &[SyntaxNode],
|
||||
get_rank: impl Fn(&SyntaxNode) -> usize,
|
||||
) -> Vec<SyntaxNode> {
|
||||
fields.iter().cloned().sorted_by_key(get_rank).collect()
|
||||
}
|
||||
|
||||
fn struct_definition(path: &ast::Path, sema: &Semantics<RootDatabase>) -> Option<Struct> {
|
||||
match sema.resolve_path(path) {
|
||||
Some(PathResolution::Def(ModuleDef::Adt(Adt::Struct(s)))) => Some(s),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> {
|
||||
Some(
|
||||
struct_definition(path, &ctx.sema)?
|
||||
.fields(ctx.db())
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, field)| (field.name(ctx.db()).to_string(), idx))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests::{check_assist, check_assist_not_applicable};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn not_applicable_if_sorted() {
|
||||
check_assist_not_applicable(
|
||||
reorder_fields,
|
||||
r#"
|
||||
struct Foo {
|
||||
foo: i32,
|
||||
bar: i32,
|
||||
}
|
||||
|
||||
const test: Foo = <|>Foo { foo: 0, bar: 0 };
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trivial_empty_fields() {
|
||||
check_assist_not_applicable(
|
||||
reorder_fields,
|
||||
r#"
|
||||
struct Foo {};
|
||||
const test: Foo = <|>Foo {}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reorder_struct_fields() {
|
||||
check_assist(
|
||||
reorder_fields,
|
||||
r#"
|
||||
struct Foo {foo: i32, bar: i32};
|
||||
const test: Foo = <|>Foo {bar: 0, foo: 1}
|
||||
"#,
|
||||
r#"
|
||||
struct Foo {foo: i32, bar: i32};
|
||||
const test: Foo = Foo {foo: 1, bar: 0}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reorder_struct_pattern() {
|
||||
check_assist(
|
||||
reorder_fields,
|
||||
r#"
|
||||
struct Foo { foo: i64, bar: i64, baz: i64 }
|
||||
|
||||
fn f(f: Foo) -> {
|
||||
match f {
|
||||
<|>Foo { baz: 0, ref mut bar, .. } => (),
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
struct Foo { foo: i64, bar: i64, baz: i64 }
|
||||
|
||||
fn f(f: Foo) -> {
|
||||
match f {
|
||||
Foo { ref mut bar, baz: 0, .. } => (),
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reorder_with_extra_field() {
|
||||
check_assist(
|
||||
reorder_fields,
|
||||
r#"
|
||||
struct Foo {
|
||||
foo: String,
|
||||
bar: String,
|
||||
}
|
||||
|
||||
impl Foo {
|
||||
fn new() -> Foo {
|
||||
let foo = String::new();
|
||||
<|>Foo {
|
||||
bar: foo.clone(),
|
||||
extra: "Extra field",
|
||||
foo,
|
||||
}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
struct Foo {
|
||||
foo: String,
|
||||
bar: String,
|
||||
}
|
||||
|
||||
impl Foo {
|
||||
fn new() -> Foo {
|
||||
let foo = String::new();
|
||||
Foo {
|
||||
foo,
|
||||
bar: foo.clone(),
|
||||
extra: "Extra field",
|
||||
}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
}
|
257
crates/assists/src/handlers/replace_if_let_with_match.rs
Normal file
257
crates/assists/src/handlers/replace_if_let_with_match.rs
Normal file
|
@ -0,0 +1,257 @@
|
|||
use syntax::{
|
||||
ast::{
|
||||
self,
|
||||
edit::{AstNodeEdit, IndentLevel},
|
||||
make,
|
||||
},
|
||||
AstNode,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
utils::{unwrap_trivial_block, TryEnum},
|
||||
AssistContext, AssistId, AssistKind, Assists,
|
||||
};
|
||||
|
||||
// Assist: replace_if_let_with_match
|
||||
//
|
||||
// Replaces `if let` with an else branch with a `match` expression.
|
||||
//
|
||||
// ```
|
||||
// enum Action { Move { distance: u32 }, Stop }
|
||||
//
|
||||
// fn handle(action: Action) {
|
||||
// <|>if let Action::Move { distance } = action {
|
||||
// foo(distance)
|
||||
// } else {
|
||||
// bar()
|
||||
// }
|
||||
// }
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// enum Action { Move { distance: u32 }, Stop }
|
||||
//
|
||||
// fn handle(action: Action) {
|
||||
// match action {
|
||||
// Action::Move { distance } => foo(distance),
|
||||
// _ => bar(),
|
||||
// }
|
||||
// }
|
||||
// ```
|
||||
pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
let if_expr: ast::IfExpr = ctx.find_node_at_offset()?;
|
||||
let cond = if_expr.condition()?;
|
||||
let pat = cond.pat()?;
|
||||
let expr = cond.expr()?;
|
||||
let then_block = if_expr.then_branch()?;
|
||||
let else_block = match if_expr.else_branch()? {
|
||||
ast::ElseBranch::Block(it) => it,
|
||||
ast::ElseBranch::IfExpr(_) => return None,
|
||||
};
|
||||
|
||||
let target = if_expr.syntax().text_range();
|
||||
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::wildcard_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);
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::tests::{check_assist, check_assist_target};
|
||||
|
||||
#[test]
|
||||
fn test_replace_if_let_with_match_unwraps_simple_expressions() {
|
||||
check_assist(
|
||||
replace_if_let_with_match,
|
||||
r#"
|
||||
impl VariantData {
|
||||
pub fn is_struct(&self) -> bool {
|
||||
if <|>let VariantData::Struct(..) = *self {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
} "#,
|
||||
r#"
|
||||
impl VariantData {
|
||||
pub fn is_struct(&self) -> bool {
|
||||
match *self {
|
||||
VariantData::Struct(..) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
} "#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replace_if_let_with_match_doesnt_unwrap_multiline_expressions() {
|
||||
check_assist(
|
||||
replace_if_let_with_match,
|
||||
r#"
|
||||
fn foo() {
|
||||
if <|>let VariantData::Struct(..) = a {
|
||||
bar(
|
||||
123
|
||||
)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} "#,
|
||||
r#"
|
||||
fn foo() {
|
||||
match a {
|
||||
VariantData::Struct(..) => {
|
||||
bar(
|
||||
123
|
||||
)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
} "#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replace_if_let_with_match_target() {
|
||||
check_assist_target(
|
||||
replace_if_let_with_match,
|
||||
r#"
|
||||
impl VariantData {
|
||||
pub fn is_struct(&self) -> bool {
|
||||
if <|>let VariantData::Struct(..) = *self {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
} "#,
|
||||
"if let VariantData::Struct(..) = *self {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn special_case_option() {
|
||||
check_assist(
|
||||
replace_if_let_with_match,
|
||||
r#"
|
||||
enum Option<T> { Some(T), None }
|
||||
use Option::*;
|
||||
|
||||
fn foo(x: Option<i32>) {
|
||||
<|>if let Some(x) = x {
|
||||
println!("{}", x)
|
||||
} else {
|
||||
println!("none")
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
enum Option<T> { Some(T), None }
|
||||
use Option::*;
|
||||
|
||||
fn foo(x: Option<i32>) {
|
||||
match x {
|
||||
Some(x) => println!("{}", x),
|
||||
None => println!("none"),
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn special_case_result() {
|
||||
check_assist(
|
||||
replace_if_let_with_match,
|
||||
r#"
|
||||
enum Result<T, E> { Ok(T), Err(E) }
|
||||
use Result::*;
|
||||
|
||||
fn foo(x: Result<i32, ()>) {
|
||||
<|>if let Ok(x) = x {
|
||||
println!("{}", x)
|
||||
} else {
|
||||
println!("none")
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
enum Result<T, E> { Ok(T), Err(E) }
|
||||
use Result::*;
|
||||
|
||||
fn foo(x: Result<i32, ()>) {
|
||||
match x {
|
||||
Ok(x) => println!("{}", x),
|
||||
Err(_) => println!("none"),
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_indent() {
|
||||
check_assist(
|
||||
replace_if_let_with_match,
|
||||
r#"
|
||||
fn main() {
|
||||
if true {
|
||||
<|>if let Ok(rel_path) = path.strip_prefix(root_path) {
|
||||
let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
|
||||
Some((*id, rel_path))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn main() {
|
||||
if true {
|
||||
match path.strip_prefix(root_path) {
|
||||
Ok(rel_path) => {
|
||||
let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
|
||||
Some((*id, rel_path))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
}
|
100
crates/assists/src/handlers/replace_let_with_if_let.rs
Normal file
100
crates/assists/src/handlers/replace_let_with_if_let.rs
Normal file
|
@ -0,0 +1,100 @@
|
|||
use std::iter::once;
|
||||
|
||||
use syntax::{
|
||||
ast::{
|
||||
self,
|
||||
edit::{AstNodeEdit, IndentLevel},
|
||||
make,
|
||||
},
|
||||
AstNode, T,
|
||||
};
|
||||
|
||||
use crate::{utils::TryEnum, AssistContext, AssistId, AssistKind, Assists};
|
||||
|
||||
// Assist: replace_let_with_if_let
|
||||
//
|
||||
// Replaces `let` with an `if-let`.
|
||||
//
|
||||
// ```
|
||||
// # enum Option<T> { Some(T), None }
|
||||
//
|
||||
// fn main(action: Action) {
|
||||
// <|>let x = compute();
|
||||
// }
|
||||
//
|
||||
// fn compute() -> Option<i32> { None }
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// # enum Option<T> { Some(T), None }
|
||||
//
|
||||
// fn main(action: Action) {
|
||||
// if let Some(x) = compute() {
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// fn compute() -> Option<i32> { None }
|
||||
// ```
|
||||
pub(crate) fn replace_let_with_if_let(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
let let_kw = ctx.find_token_at_offset(T![let])?;
|
||||
let let_stmt = let_kw.ancestors().find_map(ast::LetStmt::cast)?;
|
||||
let init = let_stmt.initializer()?;
|
||||
let original_pat = let_stmt.pat()?;
|
||||
let ty = ctx.sema.type_of_expr(&init)?;
|
||||
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", AssistKind::RefactorRewrite),
|
||||
"Replace with if-let",
|
||||
target,
|
||||
|edit| {
|
||||
let with_placeholder: ast::Pat = match happy_variant {
|
||||
None => make::wildcard_pat().into(),
|
||||
Some(var_name) => make::tuple_struct_pat(
|
||||
make::path_unqualified(make::path_segment(make::name_ref(var_name))),
|
||||
once(make::wildcard_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::WildcardPat::cast).unwrap();
|
||||
let stmt = stmt.replace_descendant(placeholder.into(), original_pat);
|
||||
|
||||
edit.replace_ast(ast::Stmt::from(let_stmt), ast::Stmt::from(stmt));
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests::check_assist;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn replace_let_unknown_enum() {
|
||||
check_assist(
|
||||
replace_let_with_if_let,
|
||||
r"
|
||||
enum E<T> { X(T), Y(T) }
|
||||
|
||||
fn main() {
|
||||
<|>let x = E::X(92);
|
||||
}
|
||||
",
|
||||
r"
|
||||
enum E<T> { X(T), Y(T) }
|
||||
|
||||
fn main() {
|
||||
if let x = E::X(92) {
|
||||
}
|
||||
}
|
||||
",
|
||||
)
|
||||
}
|
||||
}
|
688
crates/assists/src/handlers/replace_qualified_name_with_use.rs
Normal file
688
crates/assists/src/handlers/replace_qualified_name_with_use.rs
Normal file
|
@ -0,0 +1,688 @@
|
|||
use hir;
|
||||
use syntax::{algo::SyntaxRewriter, ast, match_ast, AstNode, SmolStr, SyntaxNode};
|
||||
|
||||
use crate::{
|
||||
utils::{find_insert_use_container, insert_use_statement},
|
||||
AssistContext, AssistId, AssistKind, Assists,
|
||||
};
|
||||
|
||||
// Assist: replace_qualified_name_with_use
|
||||
//
|
||||
// Adds a use statement for a given fully-qualified name.
|
||||
//
|
||||
// ```
|
||||
// fn process(map: std::collections::<|>HashMap<String, String>) {}
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// use std::collections::HashMap;
|
||||
//
|
||||
// fn process(map: HashMap<String, String>) {}
|
||||
// ```
|
||||
pub(crate) fn replace_qualified_name_with_use(
|
||||
acc: &mut Assists,
|
||||
ctx: &AssistContext,
|
||||
) -> 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::Use::cast).is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let hir_path = ctx.sema.lower_path(&path)?;
|
||||
let segments = collect_hir_path_segments(&hir_path)?;
|
||||
if segments.len() < 2 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let target = path.syntax().text_range();
|
||||
acc.add(
|
||||
AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite),
|
||||
"Replace qualified path with use",
|
||||
target,
|
||||
|builder| {
|
||||
let path_to_import = hir_path.mod_path().clone();
|
||||
let container = match find_insert_use_container(path.syntax(), ctx) {
|
||||
Some(c) => c,
|
||||
None => return,
|
||||
};
|
||||
insert_use_statement(path.syntax(), &path_to_import, ctx, builder.text_edit_builder());
|
||||
|
||||
// Now that we've brought the name into scope, re-qualify all paths that could be
|
||||
// affected (that is, all paths inside the node we added the `use` to).
|
||||
let mut rewriter = SyntaxRewriter::default();
|
||||
let syntax = container.either(|l| l.syntax().clone(), |r| r.syntax().clone());
|
||||
shorten_paths(&mut rewriter, syntax, path);
|
||||
builder.rewrite(rewriter);
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn collect_hir_path_segments(path: &hir::Path) -> Option<Vec<SmolStr>> {
|
||||
let mut ps = Vec::<SmolStr>::with_capacity(10);
|
||||
match path.kind() {
|
||||
hir::PathKind::Abs => ps.push("".into()),
|
||||
hir::PathKind::Crate => ps.push("crate".into()),
|
||||
hir::PathKind::Plain => {}
|
||||
hir::PathKind::Super(0) => ps.push("self".into()),
|
||||
hir::PathKind::Super(lvl) => {
|
||||
let mut chain = "super".to_string();
|
||||
for _ in 0..*lvl {
|
||||
chain += "::super";
|
||||
}
|
||||
ps.push(chain.into());
|
||||
}
|
||||
hir::PathKind::DollarCrate(_) => return None,
|
||||
}
|
||||
ps.extend(path.segments().iter().map(|it| it.name.to_string().into()));
|
||||
Some(ps)
|
||||
}
|
||||
|
||||
/// Adds replacements to `re` that shorten `path` in all descendants of `node`.
|
||||
fn shorten_paths(rewriter: &mut SyntaxRewriter<'static>, node: SyntaxNode, path: ast::Path) {
|
||||
for child in node.children() {
|
||||
match_ast! {
|
||||
match child {
|
||||
// Don't modify `use` items, as this can break the `use` item when injecting a new
|
||||
// import into the use tree.
|
||||
ast::Use(_it) => continue,
|
||||
// Don't descend into submodules, they don't have the same `use` items in scope.
|
||||
ast::Module(_it) => continue,
|
||||
|
||||
ast::Path(p) => {
|
||||
match maybe_replace_path(rewriter, p.clone(), path.clone()) {
|
||||
Some(()) => {},
|
||||
None => shorten_paths(rewriter, p.syntax().clone(), path.clone()),
|
||||
}
|
||||
},
|
||||
_ => shorten_paths(rewriter, child, path.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn maybe_replace_path(
|
||||
rewriter: &mut SyntaxRewriter<'static>,
|
||||
path: ast::Path,
|
||||
target: ast::Path,
|
||||
) -> Option<()> {
|
||||
if !path_eq(path.clone(), target) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Shorten `path`, leaving only its last segment.
|
||||
if let Some(parent) = path.qualifier() {
|
||||
rewriter.delete(parent.syntax());
|
||||
}
|
||||
if let Some(double_colon) = path.coloncolon_token() {
|
||||
rewriter.delete(&double_colon);
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn path_eq(lhs: ast::Path, rhs: ast::Path) -> bool {
|
||||
let mut lhs_curr = lhs;
|
||||
let mut rhs_curr = rhs;
|
||||
loop {
|
||||
match (lhs_curr.segment(), rhs_curr.segment()) {
|
||||
(Some(lhs), Some(rhs)) if lhs.syntax().text() == rhs.syntax().text() => (),
|
||||
_ => return false,
|
||||
}
|
||||
|
||||
match (lhs_curr.qualifier(), rhs_curr.qualifier()) {
|
||||
(Some(lhs), Some(rhs)) => {
|
||||
lhs_curr = lhs;
|
||||
rhs_curr = rhs;
|
||||
}
|
||||
(None, None) => return true,
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests::{check_assist, check_assist_not_applicable};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_replace_add_use_no_anchor() {
|
||||
check_assist(
|
||||
replace_qualified_name_with_use,
|
||||
r"
|
||||
std::fmt::Debug<|>
|
||||
",
|
||||
r"
|
||||
use std::fmt::Debug;
|
||||
|
||||
Debug
|
||||
",
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn test_replace_add_use_no_anchor_with_item_below() {
|
||||
check_assist(
|
||||
replace_qualified_name_with_use,
|
||||
r"
|
||||
std::fmt::Debug<|>
|
||||
|
||||
fn main() {
|
||||
}
|
||||
",
|
||||
r"
|
||||
use std::fmt::Debug;
|
||||
|
||||
Debug
|
||||
|
||||
fn main() {
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replace_add_use_no_anchor_with_item_above() {
|
||||
check_assist(
|
||||
replace_qualified_name_with_use,
|
||||
r"
|
||||
fn main() {
|
||||
}
|
||||
|
||||
std::fmt::Debug<|>
|
||||
",
|
||||
r"
|
||||
use std::fmt::Debug;
|
||||
|
||||
fn main() {
|
||||
}
|
||||
|
||||
Debug
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replace_add_use_no_anchor_2seg() {
|
||||
check_assist(
|
||||
replace_qualified_name_with_use,
|
||||
r"
|
||||
std::fmt<|>::Debug
|
||||
",
|
||||
r"
|
||||
use std::fmt;
|
||||
|
||||
fmt::Debug
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replace_add_use() {
|
||||
check_assist(
|
||||
replace_qualified_name_with_use,
|
||||
r"
|
||||
use stdx;
|
||||
|
||||
impl std::fmt::Debug<|> for Foo {
|
||||
}
|
||||
",
|
||||
r"
|
||||
use stdx;
|
||||
use std::fmt::Debug;
|
||||
|
||||
impl Debug for Foo {
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replace_file_use_other_anchor() {
|
||||
check_assist(
|
||||
replace_qualified_name_with_use,
|
||||
r"
|
||||
impl std::fmt::Debug<|> for Foo {
|
||||
}
|
||||
",
|
||||
r"
|
||||
use std::fmt::Debug;
|
||||
|
||||
impl Debug for Foo {
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replace_add_use_other_anchor_indent() {
|
||||
check_assist(
|
||||
replace_qualified_name_with_use,
|
||||
r"
|
||||
impl std::fmt::Debug<|> for Foo {
|
||||
}
|
||||
",
|
||||
r"
|
||||
use std::fmt::Debug;
|
||||
|
||||
impl Debug for Foo {
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replace_split_different() {
|
||||
check_assist(
|
||||
replace_qualified_name_with_use,
|
||||
r"
|
||||
use std::fmt;
|
||||
|
||||
impl std::io<|> for Foo {
|
||||
}
|
||||
",
|
||||
r"
|
||||
use std::{io, fmt};
|
||||
|
||||
impl io for Foo {
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replace_split_self_for_use() {
|
||||
check_assist(
|
||||
replace_qualified_name_with_use,
|
||||
r"
|
||||
use std::fmt;
|
||||
|
||||
impl std::fmt::Debug<|> for Foo {
|
||||
}
|
||||
",
|
||||
r"
|
||||
use std::fmt::{self, Debug, };
|
||||
|
||||
impl Debug for Foo {
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replace_split_self_for_target() {
|
||||
check_assist(
|
||||
replace_qualified_name_with_use,
|
||||
r"
|
||||
use std::fmt::Debug;
|
||||
|
||||
impl std::fmt<|> for Foo {
|
||||
}
|
||||
",
|
||||
r"
|
||||
use std::fmt::{self, Debug};
|
||||
|
||||
impl fmt for Foo {
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replace_add_to_nested_self_nested() {
|
||||
check_assist(
|
||||
replace_qualified_name_with_use,
|
||||
r"
|
||||
use std::fmt::{Debug, nested::{Display}};
|
||||
|
||||
impl std::fmt::nested<|> for Foo {
|
||||
}
|
||||
",
|
||||
r"
|
||||
use std::fmt::{Debug, nested::{Display, self}};
|
||||
|
||||
impl nested for Foo {
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replace_add_to_nested_self_already_included() {
|
||||
check_assist(
|
||||
replace_qualified_name_with_use,
|
||||
r"
|
||||
use std::fmt::{Debug, nested::{self, Display}};
|
||||
|
||||
impl std::fmt::nested<|> for Foo {
|
||||
}
|
||||
",
|
||||
r"
|
||||
use std::fmt::{Debug, nested::{self, Display}};
|
||||
|
||||
impl nested for Foo {
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replace_add_to_nested_nested() {
|
||||
check_assist(
|
||||
replace_qualified_name_with_use,
|
||||
r"
|
||||
use std::fmt::{Debug, nested::{Display}};
|
||||
|
||||
impl std::fmt::nested::Debug<|> for Foo {
|
||||
}
|
||||
",
|
||||
r"
|
||||
use std::fmt::{Debug, nested::{Display, Debug}};
|
||||
|
||||
impl Debug for Foo {
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replace_split_common_target_longer() {
|
||||
check_assist(
|
||||
replace_qualified_name_with_use,
|
||||
r"
|
||||
use std::fmt::Debug;
|
||||
|
||||
impl std::fmt::nested::Display<|> for Foo {
|
||||
}
|
||||
",
|
||||
r"
|
||||
use std::fmt::{nested::Display, Debug};
|
||||
|
||||
impl Display for Foo {
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replace_split_common_use_longer() {
|
||||
check_assist(
|
||||
replace_qualified_name_with_use,
|
||||
r"
|
||||
use std::fmt::nested::Debug;
|
||||
|
||||
impl std::fmt::Display<|> for Foo {
|
||||
}
|
||||
",
|
||||
r"
|
||||
use std::fmt::{Display, nested::Debug};
|
||||
|
||||
impl Display for Foo {
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replace_use_nested_import() {
|
||||
check_assist(
|
||||
replace_qualified_name_with_use,
|
||||
r"
|
||||
use crate::{
|
||||
ty::{Substs, Ty},
|
||||
AssocItem,
|
||||
};
|
||||
|
||||
fn foo() { crate::ty::lower<|>::trait_env() }
|
||||
",
|
||||
r"
|
||||
use crate::{
|
||||
ty::{Substs, Ty, lower},
|
||||
AssocItem,
|
||||
};
|
||||
|
||||
fn foo() { lower::trait_env() }
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replace_alias() {
|
||||
check_assist(
|
||||
replace_qualified_name_with_use,
|
||||
r"
|
||||
use std::fmt as foo;
|
||||
|
||||
impl foo::Debug<|> for Foo {
|
||||
}
|
||||
",
|
||||
r"
|
||||
use std::fmt as foo;
|
||||
|
||||
impl Debug for Foo {
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replace_not_applicable_one_segment() {
|
||||
check_assist_not_applicable(
|
||||
replace_qualified_name_with_use,
|
||||
r"
|
||||
impl foo<|> for Foo {
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replace_not_applicable_in_use() {
|
||||
check_assist_not_applicable(
|
||||
replace_qualified_name_with_use,
|
||||
r"
|
||||
use std::fmt<|>;
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replace_add_use_no_anchor_in_mod_mod() {
|
||||
check_assist(
|
||||
replace_qualified_name_with_use,
|
||||
r"
|
||||
mod foo {
|
||||
mod bar {
|
||||
std::fmt::Debug<|>
|
||||
}
|
||||
}
|
||||
",
|
||||
r"
|
||||
mod foo {
|
||||
mod bar {
|
||||
use std::fmt::Debug;
|
||||
|
||||
Debug
|
||||
}
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inserts_imports_after_inner_attributes() {
|
||||
check_assist(
|
||||
replace_qualified_name_with_use,
|
||||
r"
|
||||
#![allow(dead_code)]
|
||||
|
||||
fn main() {
|
||||
std::fmt::Debug<|>
|
||||
}
|
||||
",
|
||||
r"
|
||||
#![allow(dead_code)]
|
||||
use std::fmt::Debug;
|
||||
|
||||
fn main() {
|
||||
Debug
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replaces_all_affected_paths() {
|
||||
check_assist(
|
||||
replace_qualified_name_with_use,
|
||||
r"
|
||||
fn main() {
|
||||
std::fmt::Debug<|>;
|
||||
let x: std::fmt::Debug = std::fmt::Debug;
|
||||
}
|
||||
",
|
||||
r"
|
||||
use std::fmt::Debug;
|
||||
|
||||
fn main() {
|
||||
Debug;
|
||||
let x: Debug = Debug;
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replaces_all_affected_paths_mod() {
|
||||
check_assist(
|
||||
replace_qualified_name_with_use,
|
||||
r"
|
||||
mod m {
|
||||
fn f() {
|
||||
std::fmt::Debug<|>;
|
||||
let x: std::fmt::Debug = std::fmt::Debug;
|
||||
}
|
||||
fn g() {
|
||||
std::fmt::Debug;
|
||||
}
|
||||
}
|
||||
|
||||
fn f() {
|
||||
std::fmt::Debug;
|
||||
}
|
||||
",
|
||||
r"
|
||||
mod m {
|
||||
use std::fmt::Debug;
|
||||
|
||||
fn f() {
|
||||
Debug;
|
||||
let x: Debug = Debug;
|
||||
}
|
||||
fn g() {
|
||||
Debug;
|
||||
}
|
||||
}
|
||||
|
||||
fn f() {
|
||||
std::fmt::Debug;
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_replace_in_submodules() {
|
||||
check_assist(
|
||||
replace_qualified_name_with_use,
|
||||
r"
|
||||
fn main() {
|
||||
std::fmt::Debug<|>;
|
||||
}
|
||||
|
||||
mod sub {
|
||||
fn f() {
|
||||
std::fmt::Debug;
|
||||
}
|
||||
}
|
||||
",
|
||||
r"
|
||||
use std::fmt::Debug;
|
||||
|
||||
fn main() {
|
||||
Debug;
|
||||
}
|
||||
|
||||
mod sub {
|
||||
fn f() {
|
||||
std::fmt::Debug;
|
||||
}
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_replace_in_use() {
|
||||
check_assist(
|
||||
replace_qualified_name_with_use,
|
||||
r"
|
||||
use std::fmt::Display;
|
||||
|
||||
fn main() {
|
||||
std::fmt<|>;
|
||||
}
|
||||
",
|
||||
r"
|
||||
use std::fmt::{self, Display};
|
||||
|
||||
fn main() {
|
||||
fmt;
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_replace_pub_use() {
|
||||
check_assist(
|
||||
replace_qualified_name_with_use,
|
||||
r"
|
||||
pub use std::fmt;
|
||||
|
||||
impl std::io<|> for Foo {
|
||||
}
|
||||
",
|
||||
r"
|
||||
use std::io;
|
||||
|
||||
pub use std::fmt;
|
||||
|
||||
impl io for Foo {
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_replace_pub_crate_use() {
|
||||
check_assist(
|
||||
replace_qualified_name_with_use,
|
||||
r"
|
||||
pub(crate) use std::fmt;
|
||||
|
||||
impl std::io<|> for Foo {
|
||||
}
|
||||
",
|
||||
r"
|
||||
use std::io;
|
||||
|
||||
pub(crate) use std::fmt;
|
||||
|
||||
impl io for Foo {
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
}
|
187
crates/assists/src/handlers/replace_unwrap_with_match.rs
Normal file
187
crates/assists/src/handlers/replace_unwrap_with_match.rs
Normal file
|
@ -0,0 +1,187 @@
|
|||
use std::iter;
|
||||
|
||||
use syntax::{
|
||||
ast::{
|
||||
self,
|
||||
edit::{AstNodeEdit, IndentLevel},
|
||||
make,
|
||||
},
|
||||
AstNode,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
utils::{render_snippet, Cursor, TryEnum},
|
||||
AssistContext, AssistId, AssistKind, Assists,
|
||||
};
|
||||
|
||||
// Assist: replace_unwrap_with_match
|
||||
//
|
||||
// Replaces `unwrap` a `match` expression. Works for Result and Option.
|
||||
//
|
||||
// ```
|
||||
// enum Result<T, E> { Ok(T), Err(E) }
|
||||
// fn main() {
|
||||
// let x: Result<i32, i32> = Result::Ok(92);
|
||||
// let y = x.<|>unwrap();
|
||||
// }
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// enum Result<T, E> { Ok(T), Err(E) }
|
||||
// fn main() {
|
||||
// let x: Result<i32, i32> = Result::Ok(92);
|
||||
// let y = match x {
|
||||
// Ok(a) => a,
|
||||
// $0_ => unreachable!(),
|
||||
// };
|
||||
// }
|
||||
// ```
|
||||
pub(crate) fn replace_unwrap_with_match(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
let method_call: ast::MethodCallExpr = ctx.find_node_at_offset()?;
|
||||
let name = method_call.name_ref()?;
|
||||
if name.text() != "unwrap" {
|
||||
return None;
|
||||
}
|
||||
let caller = method_call.expr()?;
|
||||
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", 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::ident_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 unreachable_call = make::expr_unreachable();
|
||||
let err_arm =
|
||||
make::match_arm(iter::once(make::wildcard_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 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()),
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests::{check_assist, check_assist_target};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_replace_result_unwrap_with_match() {
|
||||
check_assist(
|
||||
replace_unwrap_with_match,
|
||||
r"
|
||||
enum Result<T, E> { Ok(T), Err(E) }
|
||||
fn i<T>(a: T) -> T { a }
|
||||
fn main() {
|
||||
let x: Result<i32, i32> = Result::Ok(92);
|
||||
let y = i(x).<|>unwrap();
|
||||
}
|
||||
",
|
||||
r"
|
||||
enum Result<T, E> { Ok(T), Err(E) }
|
||||
fn i<T>(a: T) -> T { a }
|
||||
fn main() {
|
||||
let x: Result<i32, i32> = Result::Ok(92);
|
||||
let y = match i(x) {
|
||||
Ok(a) => a,
|
||||
$0_ => unreachable!(),
|
||||
};
|
||||
}
|
||||
",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replace_option_unwrap_with_match() {
|
||||
check_assist(
|
||||
replace_unwrap_with_match,
|
||||
r"
|
||||
enum Option<T> { Some(T), None }
|
||||
fn i<T>(a: T) -> T { a }
|
||||
fn main() {
|
||||
let x = Option::Some(92);
|
||||
let y = i(x).<|>unwrap();
|
||||
}
|
||||
",
|
||||
r"
|
||||
enum Option<T> { Some(T), None }
|
||||
fn i<T>(a: T) -> T { a }
|
||||
fn main() {
|
||||
let x = Option::Some(92);
|
||||
let y = match i(x) {
|
||||
Some(a) => a,
|
||||
$0_ => unreachable!(),
|
||||
};
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replace_result_unwrap_with_match_chaining() {
|
||||
check_assist(
|
||||
replace_unwrap_with_match,
|
||||
r"
|
||||
enum Result<T, E> { Ok(T), Err(E) }
|
||||
fn i<T>(a: T) -> T { a }
|
||||
fn main() {
|
||||
let x: Result<i32, i32> = Result::Ok(92);
|
||||
let y = i(x).<|>unwrap().count_zeroes();
|
||||
}
|
||||
",
|
||||
r"
|
||||
enum Result<T, E> { Ok(T), Err(E) }
|
||||
fn i<T>(a: T) -> T { a }
|
||||
fn main() {
|
||||
let x: Result<i32, i32> = Result::Ok(92);
|
||||
let y = match i(x) {
|
||||
Ok(a) => a,
|
||||
$0_ => unreachable!(),
|
||||
}.count_zeroes();
|
||||
}
|
||||
",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replace_unwrap_with_match_target() {
|
||||
check_assist_target(
|
||||
replace_unwrap_with_match,
|
||||
r"
|
||||
enum Option<T> { Some(T), None }
|
||||
fn i<T>(a: T) -> T { a }
|
||||
fn main() {
|
||||
let x = Option::Some(92);
|
||||
let y = i(x).<|>unwrap();
|
||||
}
|
||||
",
|
||||
r"i(x).unwrap()",
|
||||
);
|
||||
}
|
||||
}
|
79
crates/assists/src/handlers/split_import.rs
Normal file
79
crates/assists/src/handlers/split_import.rs
Normal file
|
@ -0,0 +1,79 @@
|
|||
use std::iter::successors;
|
||||
|
||||
use syntax::{ast, AstNode, T};
|
||||
|
||||
use crate::{AssistContext, AssistId, AssistKind, Assists};
|
||||
|
||||
// Assist: split_import
|
||||
//
|
||||
// Wraps the tail of import into braces.
|
||||
//
|
||||
// ```
|
||||
// use std::<|>collections::HashMap;
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// use std::{collections::HashMap};
|
||||
// ```
|
||||
pub(crate) fn split_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
let colon_colon = ctx.find_token_at_offset(T![::])?;
|
||||
let path = ast::Path::cast(colon_colon.parent())?.qualifier()?;
|
||||
let top_path = successors(Some(path.clone()), |it| it.parent_path()).last()?;
|
||||
|
||||
let use_tree = top_path.syntax().ancestors().find_map(ast::UseTree::cast)?;
|
||||
|
||||
let new_tree = use_tree.split_prefix(&path);
|
||||
if new_tree == use_tree {
|
||||
return None;
|
||||
}
|
||||
|
||||
let target = colon_colon.text_range();
|
||||
acc.add(AssistId("split_import", AssistKind::RefactorRewrite), "Split import", target, |edit| {
|
||||
edit.replace_ast(use_tree, new_tree);
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_split_import() {
|
||||
check_assist(
|
||||
split_import,
|
||||
"use crate::<|>db::RootDatabase;",
|
||||
"use crate::{db::RootDatabase};",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn split_import_works_with_trees() {
|
||||
check_assist(
|
||||
split_import,
|
||||
"use crate:<|>:db::{RootDatabase, FileSymbol}",
|
||||
"use crate::{db::{RootDatabase, FileSymbol}}",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn split_import_target() {
|
||||
check_assist_target(split_import, "use crate::<|>db::{RootDatabase, FileSymbol}", "::");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn issue4044() {
|
||||
check_assist_not_applicable(split_import, "use crate::<|>:::self;")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_use() {
|
||||
check_assist_not_applicable(
|
||||
split_import,
|
||||
r"
|
||||
use std::<|>
|
||||
fn main() {}",
|
||||
);
|
||||
}
|
||||
}
|
517
crates/assists/src/handlers/unwrap_block.rs
Normal file
517
crates/assists/src/handlers/unwrap_block.rs
Normal file
|
@ -0,0 +1,517 @@
|
|||
use syntax::{
|
||||
ast::{
|
||||
self,
|
||||
edit::{AstNodeEdit, IndentLevel},
|
||||
},
|
||||
AstNode, TextRange, T,
|
||||
};
|
||||
|
||||
use crate::{utils::unwrap_trivial_block, AssistContext, AssistId, AssistKind, Assists};
|
||||
|
||||
// Assist: unwrap_block
|
||||
//
|
||||
// This assist removes if...else, for, while and loop control statements to just keep the body.
|
||||
//
|
||||
// ```
|
||||
// fn foo() {
|
||||
// if true {<|>
|
||||
// println!("foo");
|
||||
// }
|
||||
// }
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// fn foo() {
|
||||
// println!("foo");
|
||||
// }
|
||||
// ```
|
||||
pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
let assist_id = AssistId("unwrap_block", AssistKind::RefactorRewrite);
|
||||
let assist_label = "Unwrap block";
|
||||
|
||||
let l_curly_token = ctx.find_token_at_offset(T!['{'])?;
|
||||
let mut block = ast::BlockExpr::cast(l_curly_token.parent())?;
|
||||
let mut parent = block.syntax().parent()?;
|
||||
if ast::MatchArm::can_cast(parent.kind()) {
|
||||
parent = parent.ancestors().find(|it| ast::MatchExpr::can_cast(it.kind()))?
|
||||
}
|
||||
|
||||
let parent = ast::Expr::cast(parent)?;
|
||||
|
||||
match parent.clone() {
|
||||
ast::Expr::ForExpr(_) | ast::Expr::WhileExpr(_) | ast::Expr::LoopExpr(_) => (),
|
||||
ast::Expr::MatchExpr(_) => block = block.dedent(IndentLevel(1)),
|
||||
ast::Expr::IfExpr(if_expr) => {
|
||||
let then_branch = if_expr.then_branch()?;
|
||||
if then_branch == block {
|
||||
if let Some(ancestor) = if_expr.syntax().parent().and_then(ast::IfExpr::cast) {
|
||||
// For `else if` blocks
|
||||
let ancestor_then_branch = ancestor.then_branch()?;
|
||||
|
||||
let target = then_branch.syntax().text_range();
|
||||
return acc.add(assist_id, assist_label, target, |edit| {
|
||||
let range_to_del_else_if = TextRange::new(
|
||||
ancestor_then_branch.syntax().text_range().end(),
|
||||
l_curly_token.text_range().start(),
|
||||
);
|
||||
let range_to_del_rest = TextRange::new(
|
||||
then_branch.syntax().text_range().end(),
|
||||
if_expr.syntax().text_range().end(),
|
||||
);
|
||||
|
||||
edit.delete(range_to_del_rest);
|
||||
edit.delete(range_to_del_else_if);
|
||||
edit.replace(
|
||||
target,
|
||||
update_expr_string(then_branch.to_string(), &[' ', '{']),
|
||||
);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
let target = block.syntax().text_range();
|
||||
return acc.add(assist_id, assist_label, target, |edit| {
|
||||
let range_to_del = TextRange::new(
|
||||
then_branch.syntax().text_range().end(),
|
||||
l_curly_token.text_range().start(),
|
||||
);
|
||||
|
||||
edit.delete(range_to_del);
|
||||
edit.replace(target, update_expr_string(block.to_string(), &[' ', '{']));
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let unwrapped = unwrap_trivial_block(block);
|
||||
let target = unwrapped.syntax().text_range();
|
||||
acc.add(assist_id, assist_label, target, |builder| {
|
||||
builder.replace(
|
||||
parent.syntax().text_range(),
|
||||
update_expr_string(unwrapped.to_string(), &[' ', '{', '\n']),
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
fn update_expr_string(expr_str: String, trim_start_pat: &[char]) -> String {
|
||||
let expr_string = expr_str.trim_start_matches(trim_start_pat);
|
||||
let mut expr_string_lines: Vec<&str> = expr_string.lines().collect();
|
||||
expr_string_lines.pop(); // Delete last line
|
||||
|
||||
expr_string_lines
|
||||
.into_iter()
|
||||
.map(|line| line.replacen(" ", "", 1)) // Delete indentation
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests::{check_assist, check_assist_not_applicable};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn simple_if() {
|
||||
check_assist(
|
||||
unwrap_block,
|
||||
r#"
|
||||
fn main() {
|
||||
bar();
|
||||
if true {<|>
|
||||
foo();
|
||||
|
||||
//comment
|
||||
bar();
|
||||
} else {
|
||||
println!("bar");
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn main() {
|
||||
bar();
|
||||
foo();
|
||||
|
||||
//comment
|
||||
bar();
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_if_else() {
|
||||
check_assist(
|
||||
unwrap_block,
|
||||
r#"
|
||||
fn main() {
|
||||
bar();
|
||||
if true {
|
||||
foo();
|
||||
|
||||
//comment
|
||||
bar();
|
||||
} else {<|>
|
||||
println!("bar");
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn main() {
|
||||
bar();
|
||||
if true {
|
||||
foo();
|
||||
|
||||
//comment
|
||||
bar();
|
||||
}
|
||||
println!("bar");
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_if_else_if() {
|
||||
check_assist(
|
||||
unwrap_block,
|
||||
r#"
|
||||
fn main() {
|
||||
//bar();
|
||||
if true {
|
||||
println!("true");
|
||||
|
||||
//comment
|
||||
//bar();
|
||||
} else if false {<|>
|
||||
println!("bar");
|
||||
} else {
|
||||
println!("foo");
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn main() {
|
||||
//bar();
|
||||
if true {
|
||||
println!("true");
|
||||
|
||||
//comment
|
||||
//bar();
|
||||
}
|
||||
println!("bar");
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_if_else_if_nested() {
|
||||
check_assist(
|
||||
unwrap_block,
|
||||
r#"
|
||||
fn main() {
|
||||
//bar();
|
||||
if true {
|
||||
println!("true");
|
||||
|
||||
//comment
|
||||
//bar();
|
||||
} else if false {
|
||||
println!("bar");
|
||||
} else if true {<|>
|
||||
println!("foo");
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn main() {
|
||||
//bar();
|
||||
if true {
|
||||
println!("true");
|
||||
|
||||
//comment
|
||||
//bar();
|
||||
} else if false {
|
||||
println!("bar");
|
||||
}
|
||||
println!("foo");
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_if_else_if_nested_else() {
|
||||
check_assist(
|
||||
unwrap_block,
|
||||
r#"
|
||||
fn main() {
|
||||
//bar();
|
||||
if true {
|
||||
println!("true");
|
||||
|
||||
//comment
|
||||
//bar();
|
||||
} else if false {
|
||||
println!("bar");
|
||||
} else if true {
|
||||
println!("foo");
|
||||
} else {<|>
|
||||
println!("else");
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn main() {
|
||||
//bar();
|
||||
if true {
|
||||
println!("true");
|
||||
|
||||
//comment
|
||||
//bar();
|
||||
} else if false {
|
||||
println!("bar");
|
||||
} else if true {
|
||||
println!("foo");
|
||||
}
|
||||
println!("else");
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_if_else_if_nested_middle() {
|
||||
check_assist(
|
||||
unwrap_block,
|
||||
r#"
|
||||
fn main() {
|
||||
//bar();
|
||||
if true {
|
||||
println!("true");
|
||||
|
||||
//comment
|
||||
//bar();
|
||||
} else if false {
|
||||
println!("bar");
|
||||
} else if true {<|>
|
||||
println!("foo");
|
||||
} else {
|
||||
println!("else");
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn main() {
|
||||
//bar();
|
||||
if true {
|
||||
println!("true");
|
||||
|
||||
//comment
|
||||
//bar();
|
||||
} else if false {
|
||||
println!("bar");
|
||||
}
|
||||
println!("foo");
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_if_bad_cursor_position() {
|
||||
check_assist_not_applicable(
|
||||
unwrap_block,
|
||||
r#"
|
||||
fn main() {
|
||||
bar();<|>
|
||||
if true {
|
||||
foo();
|
||||
|
||||
//comment
|
||||
bar();
|
||||
} else {
|
||||
println!("bar");
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_for() {
|
||||
check_assist(
|
||||
unwrap_block,
|
||||
r#"
|
||||
fn main() {
|
||||
for i in 0..5 {<|>
|
||||
if true {
|
||||
foo();
|
||||
|
||||
//comment
|
||||
bar();
|
||||
} else {
|
||||
println!("bar");
|
||||
}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn main() {
|
||||
if true {
|
||||
foo();
|
||||
|
||||
//comment
|
||||
bar();
|
||||
} else {
|
||||
println!("bar");
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_if_in_for() {
|
||||
check_assist(
|
||||
unwrap_block,
|
||||
r#"
|
||||
fn main() {
|
||||
for i in 0..5 {
|
||||
if true {<|>
|
||||
foo();
|
||||
|
||||
//comment
|
||||
bar();
|
||||
} else {
|
||||
println!("bar");
|
||||
}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn main() {
|
||||
for i in 0..5 {
|
||||
foo();
|
||||
|
||||
//comment
|
||||
bar();
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_loop() {
|
||||
check_assist(
|
||||
unwrap_block,
|
||||
r#"
|
||||
fn main() {
|
||||
loop {<|>
|
||||
if true {
|
||||
foo();
|
||||
|
||||
//comment
|
||||
bar();
|
||||
} else {
|
||||
println!("bar");
|
||||
}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn main() {
|
||||
if true {
|
||||
foo();
|
||||
|
||||
//comment
|
||||
bar();
|
||||
} else {
|
||||
println!("bar");
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_while() {
|
||||
check_assist(
|
||||
unwrap_block,
|
||||
r#"
|
||||
fn main() {
|
||||
while true {<|>
|
||||
if true {
|
||||
foo();
|
||||
|
||||
//comment
|
||||
bar();
|
||||
} else {
|
||||
println!("bar");
|
||||
}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn main() {
|
||||
if true {
|
||||
foo();
|
||||
|
||||
//comment
|
||||
bar();
|
||||
} else {
|
||||
println!("bar");
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unwrap_match_arm() {
|
||||
check_assist(
|
||||
unwrap_block,
|
||||
r#"
|
||||
fn main() {
|
||||
match rel_path {
|
||||
Ok(rel_path) => {<|>
|
||||
let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
|
||||
Some((*id, rel_path))
|
||||
}
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn main() {
|
||||
let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
|
||||
Some((*id, rel_path))
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_if_in_while_bad_cursor_position() {
|
||||
check_assist_not_applicable(
|
||||
unwrap_block,
|
||||
r#"
|
||||
fn main() {
|
||||
while true {
|
||||
if true {
|
||||
foo();<|>
|
||||
|
||||
//comment
|
||||
bar();
|
||||
} else {
|
||||
println!("bar");
|
||||
}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
}
|
241
crates/assists/src/lib.rs
Normal file
241
crates/assists/src/lib.rs
Normal file
|
@ -0,0 +1,241 @@
|
|||
//! `assists` crate provides a bunch of code assists, also known as code
|
||||
//! actions (in LSP) or intentions (in IntelliJ).
|
||||
//!
|
||||
//! An assist is a micro-refactoring, which is automatically activated in
|
||||
//! certain context. For example, if the cursor is over `,`, a "swap `,`" assist
|
||||
//! becomes available.
|
||||
|
||||
#[allow(unused)]
|
||||
macro_rules! eprintln {
|
||||
($($tt:tt)*) => { stdx::eprintln!($($tt)*) };
|
||||
}
|
||||
|
||||
mod assist_config;
|
||||
mod assist_context;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
pub mod utils;
|
||||
pub mod ast_transform;
|
||||
|
||||
use base_db::FileRange;
|
||||
use hir::Semantics;
|
||||
use ide_db::{source_change::SourceChange, RootDatabase};
|
||||
use syntax::TextRange;
|
||||
|
||||
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 AssistKind);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GroupLabel(pub String);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Assist {
|
||||
id: AssistId,
|
||||
/// Short description of the assist, as shown in the UI.
|
||||
label: String,
|
||||
group: Option<GroupLabel>,
|
||||
/// Target ranges are used to sort assists: the smaller the target range,
|
||||
/// the more specific assist is, and so it should be sorted first.
|
||||
target: TextRange,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ResolvedAssist {
|
||||
pub assist: Assist,
|
||||
pub source_change: SourceChange,
|
||||
}
|
||||
|
||||
impl Assist {
|
||||
/// Return all the assists applicable at the given position.
|
||||
///
|
||||
/// Assists are returned in the "unresolved" state, that is only labels are
|
||||
/// returned, without actual edits.
|
||||
pub fn unresolved(db: &RootDatabase, config: &AssistConfig, range: FileRange) -> Vec<Assist> {
|
||||
let sema = Semantics::new(db);
|
||||
let ctx = AssistContext::new(sema, config, range);
|
||||
let mut acc = Assists::new_unresolved(&ctx);
|
||||
handlers::all().iter().for_each(|handler| {
|
||||
handler(&mut acc, &ctx);
|
||||
});
|
||||
acc.finish_unresolved()
|
||||
}
|
||||
|
||||
/// Return all the assists applicable at the given position.
|
||||
///
|
||||
/// Assists are returned in the "resolved" state, that is with edit fully
|
||||
/// computed.
|
||||
pub fn resolved(
|
||||
db: &RootDatabase,
|
||||
config: &AssistConfig,
|
||||
range: FileRange,
|
||||
) -> Vec<ResolvedAssist> {
|
||||
let sema = Semantics::new(db);
|
||||
let ctx = AssistContext::new(sema, config, range);
|
||||
let mut acc = Assists::new_resolved(&ctx);
|
||||
handlers::all().iter().for_each(|handler| {
|
||||
handler(&mut acc, &ctx);
|
||||
});
|
||||
acc.finish_resolved()
|
||||
}
|
||||
|
||||
pub(crate) fn new(
|
||||
id: AssistId,
|
||||
label: String,
|
||||
group: Option<GroupLabel>,
|
||||
target: TextRange,
|
||||
) -> Assist {
|
||||
assert!(label.starts_with(|c: char| c.is_uppercase()));
|
||||
Assist { id, label, group, target }
|
||||
}
|
||||
|
||||
pub fn id(&self) -> AssistId {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn label(&self) -> String {
|
||||
self.label.clone()
|
||||
}
|
||||
|
||||
pub fn group(&self) -> Option<GroupLabel> {
|
||||
self.group.clone()
|
||||
}
|
||||
|
||||
pub fn target(&self) -> TextRange {
|
||||
self.target
|
||||
}
|
||||
}
|
||||
|
||||
mod handlers {
|
||||
use crate::{AssistContext, Assists};
|
||||
|
||||
pub(crate) type Handler = fn(&mut Assists, &AssistContext) -> Option<()>;
|
||||
|
||||
mod add_custom_impl;
|
||||
mod add_explicit_type;
|
||||
mod add_missing_impl_members;
|
||||
mod add_turbo_fish;
|
||||
mod apply_demorgan;
|
||||
mod auto_import;
|
||||
mod change_return_type_to_result;
|
||||
mod change_visibility;
|
||||
mod early_return;
|
||||
mod expand_glob_import;
|
||||
mod extract_struct_from_enum_variant;
|
||||
mod extract_variable;
|
||||
mod fill_match_arms;
|
||||
mod fix_visibility;
|
||||
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;
|
||||
mod merge_imports;
|
||||
mod merge_match_arms;
|
||||
mod move_bounds;
|
||||
mod move_guard;
|
||||
mod raw_string;
|
||||
mod remove_dbg;
|
||||
mod remove_mut;
|
||||
mod reorder_fields;
|
||||
mod replace_if_let_with_match;
|
||||
mod replace_let_with_if_let;
|
||||
mod replace_qualified_name_with_use;
|
||||
mod replace_unwrap_with_match;
|
||||
mod split_import;
|
||||
mod unwrap_block;
|
||||
|
||||
pub(crate) fn all() -> &'static [Handler] {
|
||||
&[
|
||||
// These are alphabetic for the foolish consistency
|
||||
add_custom_impl::add_custom_impl,
|
||||
add_explicit_type::add_explicit_type,
|
||||
add_turbo_fish::add_turbo_fish,
|
||||
apply_demorgan::apply_demorgan,
|
||||
auto_import::auto_import,
|
||||
change_return_type_to_result::change_return_type_to_result,
|
||||
change_visibility::change_visibility,
|
||||
early_return::convert_to_guarded_return,
|
||||
expand_glob_import::expand_glob_import,
|
||||
extract_struct_from_enum_variant::extract_struct_from_enum_variant,
|
||||
extract_variable::extract_variable,
|
||||
fill_match_arms::fill_match_arms,
|
||||
fix_visibility::fix_visibility,
|
||||
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,
|
||||
merge_imports::merge_imports,
|
||||
merge_match_arms::merge_match_arms,
|
||||
move_bounds::move_bounds_to_where_clause,
|
||||
move_guard::move_arm_cond_to_match_guard,
|
||||
move_guard::move_guard_to_arm_body,
|
||||
raw_string::add_hash,
|
||||
raw_string::make_raw_string,
|
||||
raw_string::make_usual_string,
|
||||
raw_string::remove_hash,
|
||||
remove_dbg::remove_dbg,
|
||||
remove_mut::remove_mut,
|
||||
reorder_fields::reorder_fields,
|
||||
replace_if_let_with_match::replace_if_let_with_match,
|
||||
replace_let_with_if_let::replace_let_with_if_let,
|
||||
replace_qualified_name_with_use::replace_qualified_name_with_use,
|
||||
replace_unwrap_with_match::replace_unwrap_with_match,
|
||||
split_import::split_import,
|
||||
unwrap_block::unwrap_block,
|
||||
// These are manually sorted for better priorities
|
||||
add_missing_impl_members::add_missing_impl_members,
|
||||
add_missing_impl_members::add_missing_default_members,
|
||||
// Are you sure you want to add new assist here, and not to the
|
||||
// sorted list above?
|
||||
]
|
||||
}
|
||||
}
|
179
crates/assists/src/tests.rs
Normal file
179
crates/assists/src/tests.rs
Normal file
|
@ -0,0 +1,179 @@
|
|||
mod generated;
|
||||
|
||||
use base_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt};
|
||||
use hir::Semantics;
|
||||
use ide_db::RootDatabase;
|
||||
use syntax::TextRange;
|
||||
use test_utils::{assert_eq_text, extract_offset, extract_range};
|
||||
|
||||
use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, AssistKind, Assists};
|
||||
use stdx::trim_indent;
|
||||
|
||||
pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) {
|
||||
RootDatabase::with_single_file(text)
|
||||
}
|
||||
|
||||
pub(crate) fn check_assist(assist: Handler, ra_fixture_before: &str, ra_fixture_after: &str) {
|
||||
let ra_fixture_after = trim_indent(ra_fixture_after);
|
||||
check(assist, ra_fixture_before, ExpectedResult::After(&ra_fixture_after));
|
||||
}
|
||||
|
||||
// FIXME: instead of having a separate function here, maybe use
|
||||
// `extract_ranges` and mark the target as `<target> </target>` in the
|
||||
// fixture?
|
||||
pub(crate) fn check_assist_target(assist: Handler, ra_fixture: &str, target: &str) {
|
||||
check(assist, ra_fixture, ExpectedResult::Target(target));
|
||||
}
|
||||
|
||||
pub(crate) fn check_assist_not_applicable(assist: Handler, ra_fixture: &str) {
|
||||
check(assist, ra_fixture, ExpectedResult::NotApplicable);
|
||||
}
|
||||
|
||||
fn check_doc_test(assist_id: &str, before: &str, after: &str) {
|
||||
let after = trim_indent(after);
|
||||
let (db, file_id, selection) = RootDatabase::with_range_or_offset(&before);
|
||||
let before = db.file_text(file_id).to_string();
|
||||
let frange = FileRange { file_id, range: selection.into() };
|
||||
|
||||
let mut assist = Assist::resolved(&db, &AssistConfig::default(), frange)
|
||||
.into_iter()
|
||||
.find(|assist| assist.assist.id.0 == assist_id)
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"\n\nAssist is not applicable: {}\nAvailable assists: {}",
|
||||
assist_id,
|
||||
Assist::resolved(&db, &AssistConfig::default(), frange)
|
||||
.into_iter()
|
||||
.map(|assist| assist.assist.id.0)
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
)
|
||||
});
|
||||
|
||||
let actual = {
|
||||
let change = assist.source_change.source_file_edits.pop().unwrap();
|
||||
let mut actual = before;
|
||||
change.edit.apply(&mut actual);
|
||||
actual
|
||||
};
|
||||
assert_eq_text!(&after, &actual);
|
||||
}
|
||||
|
||||
enum ExpectedResult<'a> {
|
||||
NotApplicable,
|
||||
After(&'a str),
|
||||
Target(&'a str),
|
||||
}
|
||||
|
||||
fn check(handler: Handler, before: &str, expected: ExpectedResult) {
|
||||
let (db, file_with_caret_id, range_or_offset) = RootDatabase::with_range_or_offset(before);
|
||||
let text_without_caret = db.file_text(file_with_caret_id).to_string();
|
||||
|
||||
let frange = FileRange { file_id: file_with_caret_id, range: range_or_offset.into() };
|
||||
|
||||
let sema = Semantics::new(&db);
|
||||
let config = AssistConfig::default();
|
||||
let ctx = AssistContext::new(sema, &config, frange);
|
||||
let mut acc = Assists::new_resolved(&ctx);
|
||||
handler(&mut acc, &ctx);
|
||||
let mut res = acc.finish_resolved();
|
||||
let assist = res.pop();
|
||||
match (assist, expected) {
|
||||
(Some(assist), ExpectedResult::After(after)) => {
|
||||
let mut source_change = assist.source_change;
|
||||
let change = source_change.source_file_edits.pop().unwrap();
|
||||
|
||||
let mut actual = db.file_text(change.file_id).as_ref().to_owned();
|
||||
change.edit.apply(&mut actual);
|
||||
assert_eq_text!(after, &actual);
|
||||
}
|
||||
(Some(assist), ExpectedResult::Target(target)) => {
|
||||
let range = assist.assist.target;
|
||||
assert_eq_text!(&text_without_caret[range], target);
|
||||
}
|
||||
(Some(_), ExpectedResult::NotApplicable) => panic!("assist should not be applicable!"),
|
||||
(None, ExpectedResult::After(_)) | (None, ExpectedResult::Target(_)) => {
|
||||
panic!("code action is not applicable")
|
||||
}
|
||||
(None, ExpectedResult::NotApplicable) => (),
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assist_order_field_struct() {
|
||||
let before = "struct Foo { <|>bar: u32 }";
|
||||
let (before_cursor_pos, before) = extract_offset(before);
|
||||
let (db, file_id) = with_single_file(&before);
|
||||
let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) };
|
||||
let assists = Assist::resolved(&db, &AssistConfig::default(), frange);
|
||||
let mut assists = assists.iter();
|
||||
|
||||
assert_eq!(
|
||||
assists.next().expect("expected assist").assist.label,
|
||||
"Change visibility to pub(crate)"
|
||||
);
|
||||
assert_eq!(assists.next().expect("expected assist").assist.label, "Add `#[derive]`");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assist_order_if_expr() {
|
||||
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 assists = Assist::resolved(&db, &AssistConfig::default(), 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");
|
||||
}
|
||||
|
||||
#[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");
|
||||
}
|
||||
}
|
892
crates/assists/src/tests/generated.rs
Normal file
892
crates/assists/src/tests/generated.rs
Normal file
|
@ -0,0 +1,892 @@
|
|||
//! Generated file, do not edit by hand, see `xtask/src/codegen`
|
||||
|
||||
use super::check_doc_test;
|
||||
|
||||
#[test]
|
||||
fn doctest_add_custom_impl() {
|
||||
check_doc_test(
|
||||
"add_custom_impl",
|
||||
r#####"
|
||||
#[derive(Deb<|>ug, Display)]
|
||||
struct S;
|
||||
"#####,
|
||||
r#####"
|
||||
#[derive(Display)]
|
||||
struct S;
|
||||
|
||||
impl Debug for S {
|
||||
$0
|
||||
}
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_add_explicit_type() {
|
||||
check_doc_test(
|
||||
"add_explicit_type",
|
||||
r#####"
|
||||
fn main() {
|
||||
let x<|> = 92;
|
||||
}
|
||||
"#####,
|
||||
r#####"
|
||||
fn main() {
|
||||
let x: i32 = 92;
|
||||
}
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_add_hash() {
|
||||
check_doc_test(
|
||||
"add_hash",
|
||||
r#####"
|
||||
fn main() {
|
||||
r#"Hello,<|> World!"#;
|
||||
}
|
||||
"#####,
|
||||
r#####"
|
||||
fn main() {
|
||||
r##"Hello, World!"##;
|
||||
}
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_add_impl_default_members() {
|
||||
check_doc_test(
|
||||
"add_impl_default_members",
|
||||
r#####"
|
||||
trait Trait {
|
||||
Type X;
|
||||
fn foo(&self);
|
||||
fn bar(&self) {}
|
||||
}
|
||||
|
||||
impl Trait for () {
|
||||
Type X = ();
|
||||
fn foo(&self) {}<|>
|
||||
|
||||
}
|
||||
"#####,
|
||||
r#####"
|
||||
trait Trait {
|
||||
Type X;
|
||||
fn foo(&self);
|
||||
fn bar(&self) {}
|
||||
}
|
||||
|
||||
impl Trait for () {
|
||||
Type X = ();
|
||||
fn foo(&self) {}
|
||||
$0fn bar(&self) {}
|
||||
|
||||
}
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_add_impl_missing_members() {
|
||||
check_doc_test(
|
||||
"add_impl_missing_members",
|
||||
r#####"
|
||||
trait Trait<T> {
|
||||
Type X;
|
||||
fn foo(&self) -> T;
|
||||
fn bar(&self) {}
|
||||
}
|
||||
|
||||
impl Trait<u32> for () {<|>
|
||||
|
||||
}
|
||||
"#####,
|
||||
r#####"
|
||||
trait Trait<T> {
|
||||
Type X;
|
||||
fn foo(&self) -> T;
|
||||
fn bar(&self) {}
|
||||
}
|
||||
|
||||
impl Trait<u32> for () {
|
||||
fn foo(&self) -> u32 {
|
||||
${0:todo!()}
|
||||
}
|
||||
|
||||
}
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_add_turbo_fish() {
|
||||
check_doc_test(
|
||||
"add_turbo_fish",
|
||||
r#####"
|
||||
fn make<T>() -> T { todo!() }
|
||||
fn main() {
|
||||
let x = make<|>();
|
||||
}
|
||||
"#####,
|
||||
r#####"
|
||||
fn make<T>() -> T { todo!() }
|
||||
fn main() {
|
||||
let x = make::<${0:_}>();
|
||||
}
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_apply_demorgan() {
|
||||
check_doc_test(
|
||||
"apply_demorgan",
|
||||
r#####"
|
||||
fn main() {
|
||||
if x != 4 ||<|> !y {}
|
||||
}
|
||||
"#####,
|
||||
r#####"
|
||||
fn main() {
|
||||
if !(x == 4 && y) {}
|
||||
}
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_auto_import() {
|
||||
check_doc_test(
|
||||
"auto_import",
|
||||
r#####"
|
||||
fn main() {
|
||||
let map = HashMap<|>::new();
|
||||
}
|
||||
pub mod std { pub mod collections { pub struct HashMap { } } }
|
||||
"#####,
|
||||
r#####"
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn main() {
|
||||
let map = HashMap::new();
|
||||
}
|
||||
pub mod std { pub mod collections { pub struct HashMap { } } }
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_change_return_type_to_result() {
|
||||
check_doc_test(
|
||||
"change_return_type_to_result",
|
||||
r#####"
|
||||
fn foo() -> i32<|> { 42i32 }
|
||||
"#####,
|
||||
r#####"
|
||||
fn foo() -> Result<i32, ${0:_}> { Ok(42i32) }
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_change_visibility() {
|
||||
check_doc_test(
|
||||
"change_visibility",
|
||||
r#####"
|
||||
<|>fn frobnicate() {}
|
||||
"#####,
|
||||
r#####"
|
||||
pub(crate) fn frobnicate() {}
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_convert_to_guarded_return() {
|
||||
check_doc_test(
|
||||
"convert_to_guarded_return",
|
||||
r#####"
|
||||
fn main() {
|
||||
<|>if cond {
|
||||
foo();
|
||||
bar();
|
||||
}
|
||||
}
|
||||
"#####,
|
||||
r#####"
|
||||
fn main() {
|
||||
if !cond {
|
||||
return;
|
||||
}
|
||||
foo();
|
||||
bar();
|
||||
}
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_expand_glob_import() {
|
||||
check_doc_test(
|
||||
"expand_glob_import",
|
||||
r#####"
|
||||
mod foo {
|
||||
pub struct Bar;
|
||||
pub struct Baz;
|
||||
}
|
||||
|
||||
use foo::*<|>;
|
||||
|
||||
fn qux(bar: Bar, baz: Baz) {}
|
||||
"#####,
|
||||
r#####"
|
||||
mod foo {
|
||||
pub struct Bar;
|
||||
pub struct Baz;
|
||||
}
|
||||
|
||||
use foo::{Baz, Bar};
|
||||
|
||||
fn qux(bar: Bar, baz: Baz) {}
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_extract_struct_from_enum_variant() {
|
||||
check_doc_test(
|
||||
"extract_struct_from_enum_variant",
|
||||
r#####"
|
||||
enum A { <|>One(u32, u32) }
|
||||
"#####,
|
||||
r#####"
|
||||
struct One(pub u32, pub u32);
|
||||
|
||||
enum A { One(One) }
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_extract_variable() {
|
||||
check_doc_test(
|
||||
"extract_variable",
|
||||
r#####"
|
||||
fn main() {
|
||||
<|>(1 + 2)<|> * 4;
|
||||
}
|
||||
"#####,
|
||||
r#####"
|
||||
fn main() {
|
||||
let $0var_name = (1 + 2);
|
||||
var_name * 4;
|
||||
}
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_fill_match_arms() {
|
||||
check_doc_test(
|
||||
"fill_match_arms",
|
||||
r#####"
|
||||
enum Action { Move { distance: u32 }, Stop }
|
||||
|
||||
fn handle(action: Action) {
|
||||
match action {
|
||||
<|>
|
||||
}
|
||||
}
|
||||
"#####,
|
||||
r#####"
|
||||
enum Action { Move { distance: u32 }, Stop }
|
||||
|
||||
fn handle(action: Action) {
|
||||
match action {
|
||||
$0Action::Move { distance } => {}
|
||||
Action::Stop => {}
|
||||
}
|
||||
}
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_fix_visibility() {
|
||||
check_doc_test(
|
||||
"fix_visibility",
|
||||
r#####"
|
||||
mod m {
|
||||
fn frobnicate() {}
|
||||
}
|
||||
fn main() {
|
||||
m::frobnicate<|>() {}
|
||||
}
|
||||
"#####,
|
||||
r#####"
|
||||
mod m {
|
||||
$0pub(crate) fn frobnicate() {}
|
||||
}
|
||||
fn main() {
|
||||
m::frobnicate() {}
|
||||
}
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_flip_binexpr() {
|
||||
check_doc_test(
|
||||
"flip_binexpr",
|
||||
r#####"
|
||||
fn main() {
|
||||
let _ = 90 +<|> 2;
|
||||
}
|
||||
"#####,
|
||||
r#####"
|
||||
fn main() {
|
||||
let _ = 2 + 90;
|
||||
}
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_flip_comma() {
|
||||
check_doc_test(
|
||||
"flip_comma",
|
||||
r#####"
|
||||
fn main() {
|
||||
((1, 2),<|> (3, 4));
|
||||
}
|
||||
"#####,
|
||||
r#####"
|
||||
fn main() {
|
||||
((3, 4), (1, 2));
|
||||
}
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_flip_trait_bound() {
|
||||
check_doc_test(
|
||||
"flip_trait_bound",
|
||||
r#####"
|
||||
fn foo<T: Clone +<|> Copy>() { }
|
||||
"#####,
|
||||
r#####"
|
||||
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(
|
||||
"inline_local_variable",
|
||||
r#####"
|
||||
fn main() {
|
||||
let x<|> = 1 + 2;
|
||||
x * 4;
|
||||
}
|
||||
"#####,
|
||||
r#####"
|
||||
fn main() {
|
||||
(1 + 2) * 4;
|
||||
}
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_introduce_named_lifetime() {
|
||||
check_doc_test(
|
||||
"introduce_named_lifetime",
|
||||
r#####"
|
||||
impl Cursor<'_<|>> {
|
||||
fn node(self) -> &SyntaxNode {
|
||||
match self {
|
||||
Cursor::Replace(node) | Cursor::Before(node) => node,
|
||||
}
|
||||
}
|
||||
}
|
||||
"#####,
|
||||
r#####"
|
||||
impl<'a> Cursor<'a> {
|
||||
fn node(self) -> &SyntaxNode {
|
||||
match self {
|
||||
Cursor::Replace(node) | Cursor::Before(node) => node,
|
||||
}
|
||||
}
|
||||
}
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_invert_if() {
|
||||
check_doc_test(
|
||||
"invert_if",
|
||||
r#####"
|
||||
fn main() {
|
||||
if<|> !y { A } else { B }
|
||||
}
|
||||
"#####,
|
||||
r#####"
|
||||
fn main() {
|
||||
if y { B } else { A }
|
||||
}
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_make_raw_string() {
|
||||
check_doc_test(
|
||||
"make_raw_string",
|
||||
r#####"
|
||||
fn main() {
|
||||
"Hello,<|> World!";
|
||||
}
|
||||
"#####,
|
||||
r#####"
|
||||
fn main() {
|
||||
r#"Hello, World!"#;
|
||||
}
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_make_usual_string() {
|
||||
check_doc_test(
|
||||
"make_usual_string",
|
||||
r#####"
|
||||
fn main() {
|
||||
r#"Hello,<|> "World!""#;
|
||||
}
|
||||
"#####,
|
||||
r#####"
|
||||
fn main() {
|
||||
"Hello, \"World!\"";
|
||||
}
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_merge_imports() {
|
||||
check_doc_test(
|
||||
"merge_imports",
|
||||
r#####"
|
||||
use std::<|>fmt::Formatter;
|
||||
use std::io;
|
||||
"#####,
|
||||
r#####"
|
||||
use std::{fmt::Formatter, io};
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_merge_match_arms() {
|
||||
check_doc_test(
|
||||
"merge_match_arms",
|
||||
r#####"
|
||||
enum Action { Move { distance: u32 }, Stop }
|
||||
|
||||
fn handle(action: Action) {
|
||||
match action {
|
||||
<|>Action::Move(..) => foo(),
|
||||
Action::Stop => foo(),
|
||||
}
|
||||
}
|
||||
"#####,
|
||||
r#####"
|
||||
enum Action { Move { distance: u32 }, Stop }
|
||||
|
||||
fn handle(action: Action) {
|
||||
match action {
|
||||
Action::Move(..) | Action::Stop => foo(),
|
||||
}
|
||||
}
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_move_arm_cond_to_match_guard() {
|
||||
check_doc_test(
|
||||
"move_arm_cond_to_match_guard",
|
||||
r#####"
|
||||
enum Action { Move { distance: u32 }, Stop }
|
||||
|
||||
fn handle(action: Action) {
|
||||
match action {
|
||||
Action::Move { distance } => <|>if distance > 10 { foo() },
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
"#####,
|
||||
r#####"
|
||||
enum Action { Move { distance: u32 }, Stop }
|
||||
|
||||
fn handle(action: Action) {
|
||||
match action {
|
||||
Action::Move { distance } if distance > 10 => foo(),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_move_bounds_to_where_clause() {
|
||||
check_doc_test(
|
||||
"move_bounds_to_where_clause",
|
||||
r#####"
|
||||
fn apply<T, U, <|>F: FnOnce(T) -> U>(f: F, x: T) -> U {
|
||||
f(x)
|
||||
}
|
||||
"#####,
|
||||
r#####"
|
||||
fn apply<T, U, F>(f: F, x: T) -> U where F: FnOnce(T) -> U {
|
||||
f(x)
|
||||
}
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_move_guard_to_arm_body() {
|
||||
check_doc_test(
|
||||
"move_guard_to_arm_body",
|
||||
r#####"
|
||||
enum Action { Move { distance: u32 }, Stop }
|
||||
|
||||
fn handle(action: Action) {
|
||||
match action {
|
||||
Action::Move { distance } <|>if distance > 10 => foo(),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
"#####,
|
||||
r#####"
|
||||
enum Action { Move { distance: u32 }, Stop }
|
||||
|
||||
fn handle(action: Action) {
|
||||
match action {
|
||||
Action::Move { distance } => if distance > 10 {
|
||||
foo()
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_remove_dbg() {
|
||||
check_doc_test(
|
||||
"remove_dbg",
|
||||
r#####"
|
||||
fn main() {
|
||||
<|>dbg!(92);
|
||||
}
|
||||
"#####,
|
||||
r#####"
|
||||
fn main() {
|
||||
92;
|
||||
}
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_remove_hash() {
|
||||
check_doc_test(
|
||||
"remove_hash",
|
||||
r#####"
|
||||
fn main() {
|
||||
r#"Hello,<|> World!"#;
|
||||
}
|
||||
"#####,
|
||||
r#####"
|
||||
fn main() {
|
||||
r"Hello, World!";
|
||||
}
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_remove_mut() {
|
||||
check_doc_test(
|
||||
"remove_mut",
|
||||
r#####"
|
||||
impl Walrus {
|
||||
fn feed(&mut<|> self, amount: u32) {}
|
||||
}
|
||||
"#####,
|
||||
r#####"
|
||||
impl Walrus {
|
||||
fn feed(&self, amount: u32) {}
|
||||
}
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_reorder_fields() {
|
||||
check_doc_test(
|
||||
"reorder_fields",
|
||||
r#####"
|
||||
struct Foo {foo: i32, bar: i32};
|
||||
const test: Foo = <|>Foo {bar: 0, foo: 1}
|
||||
"#####,
|
||||
r#####"
|
||||
struct Foo {foo: i32, bar: i32};
|
||||
const test: Foo = Foo {foo: 1, bar: 0}
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_replace_if_let_with_match() {
|
||||
check_doc_test(
|
||||
"replace_if_let_with_match",
|
||||
r#####"
|
||||
enum Action { Move { distance: u32 }, Stop }
|
||||
|
||||
fn handle(action: Action) {
|
||||
<|>if let Action::Move { distance } = action {
|
||||
foo(distance)
|
||||
} else {
|
||||
bar()
|
||||
}
|
||||
}
|
||||
"#####,
|
||||
r#####"
|
||||
enum Action { Move { distance: u32 }, Stop }
|
||||
|
||||
fn handle(action: Action) {
|
||||
match action {
|
||||
Action::Move { distance } => foo(distance),
|
||||
_ => bar(),
|
||||
}
|
||||
}
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_replace_let_with_if_let() {
|
||||
check_doc_test(
|
||||
"replace_let_with_if_let",
|
||||
r#####"
|
||||
enum Option<T> { Some(T), None }
|
||||
|
||||
fn main(action: Action) {
|
||||
<|>let x = compute();
|
||||
}
|
||||
|
||||
fn compute() -> Option<i32> { None }
|
||||
"#####,
|
||||
r#####"
|
||||
enum Option<T> { Some(T), None }
|
||||
|
||||
fn main(action: Action) {
|
||||
if let Some(x) = compute() {
|
||||
}
|
||||
}
|
||||
|
||||
fn compute() -> Option<i32> { None }
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_replace_qualified_name_with_use() {
|
||||
check_doc_test(
|
||||
"replace_qualified_name_with_use",
|
||||
r#####"
|
||||
fn process(map: std::collections::<|>HashMap<String, String>) {}
|
||||
"#####,
|
||||
r#####"
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn process(map: HashMap<String, String>) {}
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_replace_unwrap_with_match() {
|
||||
check_doc_test(
|
||||
"replace_unwrap_with_match",
|
||||
r#####"
|
||||
enum Result<T, E> { Ok(T), Err(E) }
|
||||
fn main() {
|
||||
let x: Result<i32, i32> = Result::Ok(92);
|
||||
let y = x.<|>unwrap();
|
||||
}
|
||||
"#####,
|
||||
r#####"
|
||||
enum Result<T, E> { Ok(T), Err(E) }
|
||||
fn main() {
|
||||
let x: Result<i32, i32> = Result::Ok(92);
|
||||
let y = match x {
|
||||
Ok(a) => a,
|
||||
$0_ => unreachable!(),
|
||||
};
|
||||
}
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_split_import() {
|
||||
check_doc_test(
|
||||
"split_import",
|
||||
r#####"
|
||||
use std::<|>collections::HashMap;
|
||||
"#####,
|
||||
r#####"
|
||||
use std::{collections::HashMap};
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_unwrap_block() {
|
||||
check_doc_test(
|
||||
"unwrap_block",
|
||||
r#####"
|
||||
fn foo() {
|
||||
if true {<|>
|
||||
println!("foo");
|
||||
}
|
||||
}
|
||||
"#####,
|
||||
r#####"
|
||||
fn foo() {
|
||||
println!("foo");
|
||||
}
|
||||
"#####,
|
||||
)
|
||||
}
|
313
crates/assists/src/utils.rs
Normal file
313
crates/assists/src/utils.rs
Normal file
|
@ -0,0 +1,313 @@
|
|||
//! Assorted functions shared by several assists.
|
||||
pub(crate) mod insert_use;
|
||||
|
||||
use std::{iter, ops};
|
||||
|
||||
use hir::{Adt, Crate, Enum, ScopeDef, Semantics, Trait, Type};
|
||||
use ide_db::RootDatabase;
|
||||
use itertools::Itertools;
|
||||
use rustc_hash::FxHashSet;
|
||||
use syntax::{
|
||||
ast::{self, make, NameOwner},
|
||||
AstNode,
|
||||
SyntaxKind::*,
|
||||
SyntaxNode, TextSize, T,
|
||||
};
|
||||
|
||||
use crate::assist_config::SnippetCap;
|
||||
|
||||
pub(crate) use insert_use::{find_insert_use_container, insert_use_statement};
|
||||
|
||||
pub(crate) fn unwrap_trivial_block(block: ast::BlockExpr) -> ast::Expr {
|
||||
extract_trivial_expression(&block)
|
||||
.filter(|expr| !expr.syntax().text().contains_char('\n'))
|
||||
.unwrap_or_else(|| block.into())
|
||||
}
|
||||
|
||||
pub fn extract_trivial_expression(block: &ast::BlockExpr) -> Option<ast::Expr> {
|
||||
let has_anything_else = |thing: &SyntaxNode| -> bool {
|
||||
let mut non_trivial_children =
|
||||
block.syntax().children_with_tokens().filter(|it| match it.kind() {
|
||||
WHITESPACE | T!['{'] | T!['}'] => false,
|
||||
_ => it.as_node() != Some(thing),
|
||||
});
|
||||
non_trivial_children.next().is_some()
|
||||
};
|
||||
|
||||
if let Some(expr) = block.expr() {
|
||||
if has_anything_else(expr.syntax()) {
|
||||
return None;
|
||||
}
|
||||
return Some(expr);
|
||||
}
|
||||
// Unwrap `{ continue; }`
|
||||
let (stmt,) = block.statements().next_tuple()?;
|
||||
if let ast::Stmt::ExprStmt(expr_stmt) = stmt {
|
||||
if has_anything_else(expr_stmt.syntax()) {
|
||||
return None;
|
||||
}
|
||||
let expr = expr_stmt.expr()?;
|
||||
match expr.syntax().kind() {
|
||||
CONTINUE_EXPR | BREAK_EXPR | RETURN_EXPR => return Some(expr),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) enum Cursor<'a> {
|
||||
Replace(&'a SyntaxNode),
|
||||
Before(&'a SyntaxNode),
|
||||
}
|
||||
|
||||
impl<'a> Cursor<'a> {
|
||||
fn node(self) -> &'a SyntaxNode {
|
||||
match self {
|
||||
Cursor::Replace(node) | Cursor::Before(node) => node,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn render_snippet(_cap: SnippetCap, node: &SyntaxNode, cursor: Cursor) -> String {
|
||||
assert!(cursor.node().ancestors().any(|it| it == *node));
|
||||
let range = cursor.node().text_range() - node.text_range().start();
|
||||
let range: ops::Range<usize> = range.into();
|
||||
|
||||
let mut placeholder = cursor.node().to_string();
|
||||
escape(&mut placeholder);
|
||||
let tab_stop = match cursor {
|
||||
Cursor::Replace(placeholder) => format!("${{0:{}}}", placeholder),
|
||||
Cursor::Before(placeholder) => format!("$0{}", placeholder),
|
||||
};
|
||||
|
||||
let mut buf = node.to_string();
|
||||
buf.replace_range(range, &tab_stop);
|
||||
return buf;
|
||||
|
||||
fn escape(buf: &mut String) {
|
||||
stdx::replace(buf, '{', r"\{");
|
||||
stdx::replace(buf, '}', r"\}");
|
||||
stdx::replace(buf, '$', r"\$");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_missing_assoc_items(
|
||||
sema: &Semantics<RootDatabase>,
|
||||
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.assoc_item_list() {
|
||||
for item in item_list.assoc_items() {
|
||||
match item {
|
||||
ast::AssocItem::Fn(f) => {
|
||||
if let Some(n) = f.name() {
|
||||
impl_fns_consts.insert(n.syntax().to_string());
|
||||
}
|
||||
}
|
||||
|
||||
ast::AssocItem::TypeAlias(t) => {
|
||||
if let Some(n) = t.name() {
|
||||
impl_type.insert(n.syntax().to_string());
|
||||
}
|
||||
}
|
||||
|
||||
ast::AssocItem::Const(c) => {
|
||||
if let Some(n) = c.name() {
|
||||
impl_fns_consts.insert(n.syntax().to_string());
|
||||
}
|
||||
}
|
||||
ast::AssocItem::MacroCall(_) => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resolve_target_trait(sema, impl_def).map_or(vec![], |target_trait| {
|
||||
target_trait
|
||||
.items(sema.db)
|
||||
.iter()
|
||||
.filter(|i| match i {
|
||||
hir::AssocItem::Function(f) => {
|
||||
!impl_fns_consts.contains(&f.name(sema.db).to_string())
|
||||
}
|
||||
hir::AssocItem::TypeAlias(t) => !impl_type.contains(&t.name(sema.db).to_string()),
|
||||
hir::AssocItem::Const(c) => c
|
||||
.name(sema.db)
|
||||
.map(|n| !impl_fns_consts.contains(&n.to_string()))
|
||||
.unwrap_or_default(),
|
||||
})
|
||||
.cloned()
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_target_trait(
|
||||
sema: &Semantics<RootDatabase>,
|
||||
impl_def: &ast::Impl,
|
||||
) -> Option<hir::Trait> {
|
||||
let ast_path =
|
||||
impl_def.trait_().map(|it| it.syntax().clone()).and_then(ast::PathType::cast)?.path()?;
|
||||
|
||||
match sema.resolve_path(&ast_path) {
|
||||
Some(hir::PathResolution::Def(hir::ModuleDef::Trait(def))) => Some(def),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn vis_offset(node: &SyntaxNode) -> TextSize {
|
||||
node.children_with_tokens()
|
||||
.find(|it| !matches!(it.kind(), WHITESPACE | COMMENT | ATTR))
|
||||
.map(|it| it.text_range().start())
|
||||
.unwrap_or_else(|| node.text_range().start())
|
||||
}
|
||||
|
||||
pub(crate) fn invert_boolean_expression(expr: ast::Expr) -> ast::Expr {
|
||||
if let Some(expr) = invert_special_case(&expr) {
|
||||
return expr;
|
||||
}
|
||||
make::expr_prefix(T![!], expr)
|
||||
}
|
||||
|
||||
fn invert_special_case(expr: &ast::Expr) -> Option<ast::Expr> {
|
||||
match expr {
|
||||
ast::Expr::BinExpr(bin) => match bin.op_kind()? {
|
||||
ast::BinOp::NegatedEqualityTest => bin.replace_op(T![==]).map(|it| it.into()),
|
||||
ast::BinOp::EqualityTest => bin.replace_op(T![!=]).map(|it| it.into()),
|
||||
_ => None,
|
||||
},
|
||||
ast::Expr::PrefixExpr(pe) if pe.op_kind()? == ast::PrefixOp::Not => pe.expr(),
|
||||
// FIXME:
|
||||
// ast::Expr::Literal(true | false )
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum TryEnum {
|
||||
Result,
|
||||
Option,
|
||||
}
|
||||
|
||||
impl TryEnum {
|
||||
const ALL: [TryEnum; 2] = [TryEnum::Option, TryEnum::Result];
|
||||
|
||||
pub fn from_ty(sema: &Semantics<RootDatabase>, ty: &Type) -> Option<TryEnum> {
|
||||
let enum_ = match ty.as_adt() {
|
||||
Some(Adt::Enum(it)) => it,
|
||||
_ => return None,
|
||||
};
|
||||
TryEnum::ALL.iter().find_map(|&var| {
|
||||
if &enum_.name(sema.db).to_string() == var.type_name() {
|
||||
return Some(var);
|
||||
}
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn happy_case(self) -> &'static str {
|
||||
match self {
|
||||
TryEnum::Result => "Ok",
|
||||
TryEnum::Option => "Some",
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn sad_pattern(self) -> ast::Pat {
|
||||
match self {
|
||||
TryEnum::Result => make::tuple_struct_pat(
|
||||
make::path_unqualified(make::path_segment(make::name_ref("Err"))),
|
||||
iter::once(make::wildcard_pat().into()),
|
||||
)
|
||||
.into(),
|
||||
TryEnum::Option => make::ident_pat(make::name("None")).into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn type_name(self) -> &'static str {
|
||||
match self {
|
||||
TryEnum::Result => "Result",
|
||||
TryEnum::Option => "Option",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helps with finding well-know things inside the standard library. This is
|
||||
/// somewhat similar to the known paths infra inside hir, but it different; We
|
||||
/// want to make sure that IDE specific paths don't become interesting inside
|
||||
/// the compiler itself as well.
|
||||
pub(crate) struct FamousDefs<'a, 'b>(pub(crate) &'a Semantics<'b, RootDatabase>, pub(crate) Crate);
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
impl FamousDefs<'_, '_> {
|
||||
#[cfg(test)]
|
||||
pub(crate) const FIXTURE: &'static str = r#"//- /libcore.rs crate:core
|
||||
pub mod convert {
|
||||
pub trait From<T> {
|
||||
fn from(T) -> Self;
|
||||
}
|
||||
}
|
||||
|
||||
pub mod option {
|
||||
pub enum Option<T> { None, Some(T)}
|
||||
}
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::{convert::From, option::Option::{self, *}};
|
||||
}
|
||||
#[prelude_import]
|
||||
pub use prelude::*;
|
||||
"#;
|
||||
|
||||
pub(crate) fn core_convert_From(&self) -> Option<Trait> {
|
||||
self.find_trait("core:convert:From")
|
||||
}
|
||||
|
||||
pub(crate) fn core_option_Option(&self) -> Option<Enum> {
|
||||
self.find_enum("core:option:Option")
|
||||
}
|
||||
|
||||
fn find_trait(&self, path: &str) -> Option<Trait> {
|
||||
match self.find_def(path)? {
|
||||
hir::ScopeDef::ModuleDef(hir::ModuleDef::Trait(it)) => Some(it),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn find_enum(&self, path: &str) -> Option<Enum> {
|
||||
match self.find_def(path)? {
|
||||
hir::ScopeDef::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Enum(it))) => Some(it),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn find_def(&self, path: &str) -> Option<ScopeDef> {
|
||||
let db = self.0.db;
|
||||
let mut path = path.split(':');
|
||||
let trait_ = path.next_back()?;
|
||||
let std_crate = path.next()?;
|
||||
let std_crate = self
|
||||
.1
|
||||
.dependencies(db)
|
||||
.into_iter()
|
||||
.find(|dep| &dep.name.to_string() == std_crate)?
|
||||
.krate;
|
||||
|
||||
let mut module = std_crate.root_module(db);
|
||||
for segment in path {
|
||||
module = module.children(db).find_map(|child| {
|
||||
let name = child.name(db)?;
|
||||
if &name.to_string() == segment {
|
||||
Some(child)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})?;
|
||||
}
|
||||
let def =
|
||||
module.scope(db, None).into_iter().find(|(name, _def)| &name.to_string() == trait_)?.1;
|
||||
Some(def)
|
||||
}
|
||||
}
|
547
crates/assists/src/utils/insert_use.rs
Normal file
547
crates/assists/src/utils/insert_use.rs
Normal file
|
@ -0,0 +1,547 @@
|
|||
//! Handle syntactic aspects of inserting a new `use`.
|
||||
// FIXME: rewrite according to the plan, outlined in
|
||||
// https://github.com/rust-analyzer/rust-analyzer/issues/3301#issuecomment-592931553
|
||||
|
||||
use std::iter::successors;
|
||||
|
||||
use either::Either;
|
||||
use hir::{self, ModPath};
|
||||
use syntax::{
|
||||
ast::{self, NameOwner, VisibilityOwner},
|
||||
AstNode, AstToken, Direction, SmolStr,
|
||||
SyntaxKind::{PATH, PATH_SEGMENT},
|
||||
SyntaxNode, SyntaxToken, T,
|
||||
};
|
||||
use text_edit::TextEditBuilder;
|
||||
|
||||
use crate::assist_context::AssistContext;
|
||||
|
||||
/// Determines the containing syntax node in which to insert a `use` statement affecting `position`.
|
||||
pub(crate) fn find_insert_use_container(
|
||||
position: &SyntaxNode,
|
||||
ctx: &AssistContext,
|
||||
) -> Option<Either<ast::ItemList, ast::SourceFile>> {
|
||||
ctx.sema.ancestors_with_macros(position.clone()).find_map(|n| {
|
||||
if let Some(module) = ast::Module::cast(n.clone()) {
|
||||
return module.item_list().map(|it| Either::Left(it));
|
||||
}
|
||||
Some(Either::Right(ast::SourceFile::cast(n)?))
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates and inserts a use statement for the given path to import.
|
||||
/// The use statement is inserted in the scope most appropriate to the
|
||||
/// the cursor position given, additionally merged with the existing use imports.
|
||||
pub(crate) fn insert_use_statement(
|
||||
// Ideally the position of the cursor, used to
|
||||
position: &SyntaxNode,
|
||||
path_to_import: &ModPath,
|
||||
ctx: &AssistContext,
|
||||
builder: &mut TextEditBuilder,
|
||||
) {
|
||||
let target = path_to_import.to_string().split("::").map(SmolStr::new).collect::<Vec<_>>();
|
||||
let container = find_insert_use_container(position, ctx);
|
||||
|
||||
if let Some(container) = container {
|
||||
let syntax = container.either(|l| l.syntax().clone(), |r| r.syntax().clone());
|
||||
let action = best_action_for_target(syntax, position.clone(), &target);
|
||||
make_assist(&action, &target, builder);
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_path_segments_raw(
|
||||
segments: &mut Vec<ast::PathSegment>,
|
||||
mut path: ast::Path,
|
||||
) -> Option<usize> {
|
||||
let oldlen = segments.len();
|
||||
loop {
|
||||
let mut children = path.syntax().children_with_tokens();
|
||||
let (first, second, third) = (
|
||||
children.next().map(|n| (n.clone(), n.kind())),
|
||||
children.next().map(|n| (n.clone(), n.kind())),
|
||||
children.next().map(|n| (n.clone(), n.kind())),
|
||||
);
|
||||
match (first, second, third) {
|
||||
(Some((subpath, PATH)), Some((_, T![::])), Some((segment, PATH_SEGMENT))) => {
|
||||
path = ast::Path::cast(subpath.as_node()?.clone())?;
|
||||
segments.push(ast::PathSegment::cast(segment.as_node()?.clone())?);
|
||||
}
|
||||
(Some((segment, PATH_SEGMENT)), _, _) => {
|
||||
segments.push(ast::PathSegment::cast(segment.as_node()?.clone())?);
|
||||
break;
|
||||
}
|
||||
(_, _, _) => return None,
|
||||
}
|
||||
}
|
||||
// We need to reverse only the new added segments
|
||||
let only_new_segments = segments.split_at_mut(oldlen).1;
|
||||
only_new_segments.reverse();
|
||||
Some(segments.len() - oldlen)
|
||||
}
|
||||
|
||||
fn fmt_segments_raw(segments: &[SmolStr], buf: &mut String) {
|
||||
let mut iter = segments.iter();
|
||||
if let Some(s) = iter.next() {
|
||||
buf.push_str(s);
|
||||
}
|
||||
for s in iter {
|
||||
buf.push_str("::");
|
||||
buf.push_str(s);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of common segments.
|
||||
fn compare_path_segments(left: &[SmolStr], right: &[ast::PathSegment]) -> usize {
|
||||
left.iter().zip(right).take_while(|(l, r)| compare_path_segment(l, r)).count()
|
||||
}
|
||||
|
||||
fn compare_path_segment(a: &SmolStr, b: &ast::PathSegment) -> bool {
|
||||
if let Some(kb) = b.kind() {
|
||||
match kb {
|
||||
ast::PathSegmentKind::Name(nameref_b) => a == nameref_b.text(),
|
||||
ast::PathSegmentKind::SelfKw => a == "self",
|
||||
ast::PathSegmentKind::SuperKw => a == "super",
|
||||
ast::PathSegmentKind::CrateKw => a == "crate",
|
||||
ast::PathSegmentKind::Type { .. } => false, // not allowed in imports
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn compare_path_segment_with_name(a: &SmolStr, b: &ast::Name) -> bool {
|
||||
a == b.text()
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum ImportAction {
|
||||
Nothing,
|
||||
// Add a brand new use statement.
|
||||
AddNewUse {
|
||||
anchor: Option<SyntaxNode>, // anchor node
|
||||
add_after_anchor: bool,
|
||||
},
|
||||
|
||||
// To split an existing use statement creating a nested import.
|
||||
AddNestedImport {
|
||||
// how may segments matched with the target path
|
||||
common_segments: usize,
|
||||
path_to_split: ast::Path,
|
||||
// the first segment of path_to_split we want to add into the new nested list
|
||||
first_segment_to_split: Option<ast::PathSegment>,
|
||||
// Wether to add 'self' in addition to the target path
|
||||
add_self: bool,
|
||||
},
|
||||
// To add the target path to an existing nested import tree list.
|
||||
AddInTreeList {
|
||||
common_segments: usize,
|
||||
// The UseTreeList where to add the target path
|
||||
tree_list: ast::UseTreeList,
|
||||
add_self: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl ImportAction {
|
||||
fn add_new_use(anchor: Option<SyntaxNode>, add_after_anchor: bool) -> Self {
|
||||
ImportAction::AddNewUse { anchor, add_after_anchor }
|
||||
}
|
||||
|
||||
fn add_nested_import(
|
||||
common_segments: usize,
|
||||
path_to_split: ast::Path,
|
||||
first_segment_to_split: Option<ast::PathSegment>,
|
||||
add_self: bool,
|
||||
) -> Self {
|
||||
ImportAction::AddNestedImport {
|
||||
common_segments,
|
||||
path_to_split,
|
||||
first_segment_to_split,
|
||||
add_self,
|
||||
}
|
||||
}
|
||||
|
||||
fn add_in_tree_list(
|
||||
common_segments: usize,
|
||||
tree_list: ast::UseTreeList,
|
||||
add_self: bool,
|
||||
) -> Self {
|
||||
ImportAction::AddInTreeList { common_segments, tree_list, add_self }
|
||||
}
|
||||
|
||||
fn better(left: ImportAction, right: ImportAction) -> ImportAction {
|
||||
if left.is_better(&right) {
|
||||
left
|
||||
} else {
|
||||
right
|
||||
}
|
||||
}
|
||||
|
||||
fn is_better(&self, other: &ImportAction) -> bool {
|
||||
match (self, other) {
|
||||
(ImportAction::Nothing, _) => true,
|
||||
(ImportAction::AddInTreeList { .. }, ImportAction::Nothing) => false,
|
||||
(
|
||||
ImportAction::AddNestedImport { common_segments: n, .. },
|
||||
ImportAction::AddInTreeList { common_segments: m, .. },
|
||||
)
|
||||
| (
|
||||
ImportAction::AddInTreeList { common_segments: n, .. },
|
||||
ImportAction::AddNestedImport { common_segments: m, .. },
|
||||
)
|
||||
| (
|
||||
ImportAction::AddInTreeList { common_segments: n, .. },
|
||||
ImportAction::AddInTreeList { common_segments: m, .. },
|
||||
)
|
||||
| (
|
||||
ImportAction::AddNestedImport { common_segments: n, .. },
|
||||
ImportAction::AddNestedImport { common_segments: m, .. },
|
||||
) => n > m,
|
||||
(ImportAction::AddInTreeList { .. }, _) => true,
|
||||
(ImportAction::AddNestedImport { .. }, ImportAction::Nothing) => false,
|
||||
(ImportAction::AddNestedImport { .. }, _) => true,
|
||||
(ImportAction::AddNewUse { .. }, _) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find out the best ImportAction to import target path against current_use_tree.
|
||||
// If current_use_tree has a nested import the function gets called recursively on every UseTree inside a UseTreeList.
|
||||
fn walk_use_tree_for_best_action(
|
||||
current_path_segments: &mut Vec<ast::PathSegment>, // buffer containing path segments
|
||||
current_parent_use_tree_list: Option<ast::UseTreeList>, // will be Some value if we are in a nested import
|
||||
current_use_tree: ast::UseTree, // the use tree we are currently examinating
|
||||
target: &[SmolStr], // the path we want to import
|
||||
) -> ImportAction {
|
||||
// We save the number of segments in the buffer so we can restore the correct segments
|
||||
// before returning. Recursive call will add segments so we need to delete them.
|
||||
let prev_len = current_path_segments.len();
|
||||
|
||||
let tree_list = current_use_tree.use_tree_list();
|
||||
let alias = current_use_tree.rename();
|
||||
|
||||
let path = match current_use_tree.path() {
|
||||
Some(path) => path,
|
||||
None => {
|
||||
// If the use item don't have a path, it means it's broken (syntax error)
|
||||
return ImportAction::add_new_use(
|
||||
current_use_tree
|
||||
.syntax()
|
||||
.ancestors()
|
||||
.find_map(ast::Use::cast)
|
||||
.map(|it| it.syntax().clone()),
|
||||
true,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// This can happen only if current_use_tree is a direct child of a UseItem
|
||||
if let Some(name) = alias.and_then(|it| it.name()) {
|
||||
if compare_path_segment_with_name(&target[0], &name) {
|
||||
return ImportAction::Nothing;
|
||||
}
|
||||
}
|
||||
|
||||
collect_path_segments_raw(current_path_segments, path.clone());
|
||||
|
||||
// We compare only the new segments added in the line just above.
|
||||
// The first prev_len segments were already compared in 'parent' recursive calls.
|
||||
let left = target.split_at(prev_len).1;
|
||||
let right = current_path_segments.split_at(prev_len).1;
|
||||
let common = compare_path_segments(left, &right);
|
||||
let mut action = match common {
|
||||
0 => ImportAction::add_new_use(
|
||||
// e.g: target is std::fmt and we can have
|
||||
// use foo::bar
|
||||
// We add a brand new use statement
|
||||
current_use_tree
|
||||
.syntax()
|
||||
.ancestors()
|
||||
.find_map(ast::Use::cast)
|
||||
.map(|it| it.syntax().clone()),
|
||||
true,
|
||||
),
|
||||
common if common == left.len() && left.len() == right.len() => {
|
||||
// e.g: target is std::fmt and we can have
|
||||
// 1- use std::fmt;
|
||||
// 2- use std::fmt::{ ... }
|
||||
if let Some(list) = tree_list {
|
||||
// In case 2 we need to add self to the nested list
|
||||
// unless it's already there
|
||||
let has_self = list.use_trees().map(|it| it.path()).any(|p| {
|
||||
p.and_then(|it| it.segment())
|
||||
.and_then(|it| it.kind())
|
||||
.filter(|k| *k == ast::PathSegmentKind::SelfKw)
|
||||
.is_some()
|
||||
});
|
||||
|
||||
if has_self {
|
||||
ImportAction::Nothing
|
||||
} else {
|
||||
ImportAction::add_in_tree_list(current_path_segments.len(), list, true)
|
||||
}
|
||||
} else {
|
||||
// Case 1
|
||||
ImportAction::Nothing
|
||||
}
|
||||
}
|
||||
common if common != left.len() && left.len() == right.len() => {
|
||||
// e.g: target is std::fmt and we have
|
||||
// use std::io;
|
||||
// We need to split.
|
||||
let segments_to_split = current_path_segments.split_at(prev_len + common).1;
|
||||
ImportAction::add_nested_import(
|
||||
prev_len + common,
|
||||
path,
|
||||
Some(segments_to_split[0].clone()),
|
||||
false,
|
||||
)
|
||||
}
|
||||
common if common == right.len() && left.len() > right.len() => {
|
||||
// e.g: target is std::fmt and we can have
|
||||
// 1- use std;
|
||||
// 2- use std::{ ... };
|
||||
|
||||
// fallback action
|
||||
let mut better_action = ImportAction::add_new_use(
|
||||
current_use_tree
|
||||
.syntax()
|
||||
.ancestors()
|
||||
.find_map(ast::Use::cast)
|
||||
.map(|it| it.syntax().clone()),
|
||||
true,
|
||||
);
|
||||
if let Some(list) = tree_list {
|
||||
// Case 2, check recursively if the path is already imported in the nested list
|
||||
for u in list.use_trees() {
|
||||
let child_action = walk_use_tree_for_best_action(
|
||||
current_path_segments,
|
||||
Some(list.clone()),
|
||||
u,
|
||||
target,
|
||||
);
|
||||
if child_action.is_better(&better_action) {
|
||||
better_action = child_action;
|
||||
if let ImportAction::Nothing = better_action {
|
||||
return better_action;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Case 1, split adding self
|
||||
better_action = ImportAction::add_nested_import(prev_len + common, path, None, true)
|
||||
}
|
||||
better_action
|
||||
}
|
||||
common if common == left.len() && left.len() < right.len() => {
|
||||
// e.g: target is std::fmt and we can have
|
||||
// use std::fmt::Debug;
|
||||
let segments_to_split = current_path_segments.split_at(prev_len + common).1;
|
||||
ImportAction::add_nested_import(
|
||||
prev_len + common,
|
||||
path,
|
||||
Some(segments_to_split[0].clone()),
|
||||
true,
|
||||
)
|
||||
}
|
||||
common if common < left.len() && common < right.len() => {
|
||||
// e.g: target is std::fmt::nested::Debug
|
||||
// use std::fmt::Display
|
||||
let segments_to_split = current_path_segments.split_at(prev_len + common).1;
|
||||
ImportAction::add_nested_import(
|
||||
prev_len + common,
|
||||
path,
|
||||
Some(segments_to_split[0].clone()),
|
||||
false,
|
||||
)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
// If we are inside a UseTreeList adding a use statement become adding to the existing
|
||||
// tree list.
|
||||
action = match (current_parent_use_tree_list, action.clone()) {
|
||||
(Some(use_tree_list), ImportAction::AddNewUse { .. }) => {
|
||||
ImportAction::add_in_tree_list(prev_len, use_tree_list, false)
|
||||
}
|
||||
(_, _) => action,
|
||||
};
|
||||
|
||||
// We remove the segments added
|
||||
current_path_segments.truncate(prev_len);
|
||||
action
|
||||
}
|
||||
|
||||
fn best_action_for_target(
|
||||
container: SyntaxNode,
|
||||
anchor: SyntaxNode,
|
||||
target: &[SmolStr],
|
||||
) -> ImportAction {
|
||||
let mut storage = Vec::with_capacity(16); // this should be the only allocation
|
||||
let best_action = container
|
||||
.children()
|
||||
.filter_map(ast::Use::cast)
|
||||
.filter(|u| u.visibility().is_none())
|
||||
.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 {
|
||||
Some(best) => Some(ImportAction::better(best, a)),
|
||||
None => Some(a),
|
||||
});
|
||||
|
||||
match best_action {
|
||||
Some(action) => action,
|
||||
None => {
|
||||
// We have no action and no UseItem was found in container so we find
|
||||
// another item and we use it as anchor.
|
||||
// If there are no items above, we choose the target path itself as anchor.
|
||||
// todo: we should include even whitespace blocks as anchor candidates
|
||||
let anchor = container.children().next().or_else(|| Some(anchor));
|
||||
|
||||
let add_after_anchor = anchor
|
||||
.clone()
|
||||
.and_then(ast::Attr::cast)
|
||||
.map(|attr| attr.kind() == ast::AttrKind::Inner)
|
||||
.unwrap_or(false);
|
||||
ImportAction::add_new_use(anchor, add_after_anchor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn make_assist(action: &ImportAction, target: &[SmolStr], edit: &mut TextEditBuilder) {
|
||||
match action {
|
||||
ImportAction::AddNewUse { anchor, add_after_anchor } => {
|
||||
make_assist_add_new_use(anchor, *add_after_anchor, target, edit)
|
||||
}
|
||||
ImportAction::AddInTreeList { common_segments, tree_list, add_self } => {
|
||||
// We know that the fist n segments already exists in the use statement we want
|
||||
// to modify, so we want to add only the last target.len() - n segments.
|
||||
let segments_to_add = target.split_at(*common_segments).1;
|
||||
make_assist_add_in_tree_list(tree_list, segments_to_add, *add_self, edit)
|
||||
}
|
||||
ImportAction::AddNestedImport {
|
||||
common_segments,
|
||||
path_to_split,
|
||||
first_segment_to_split,
|
||||
add_self,
|
||||
} => {
|
||||
let segments_to_add = target.split_at(*common_segments).1;
|
||||
make_assist_add_nested_import(
|
||||
path_to_split,
|
||||
first_segment_to_split,
|
||||
segments_to_add,
|
||||
*add_self,
|
||||
edit,
|
||||
)
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn make_assist_add_new_use(
|
||||
anchor: &Option<SyntaxNode>,
|
||||
after: bool,
|
||||
target: &[SmolStr],
|
||||
edit: &mut TextEditBuilder,
|
||||
) {
|
||||
if let Some(anchor) = anchor {
|
||||
let indent = leading_indent(anchor);
|
||||
let mut buf = String::new();
|
||||
if after {
|
||||
buf.push_str("\n");
|
||||
if let Some(spaces) = &indent {
|
||||
buf.push_str(spaces);
|
||||
}
|
||||
}
|
||||
buf.push_str("use ");
|
||||
fmt_segments_raw(target, &mut buf);
|
||||
buf.push_str(";");
|
||||
if !after {
|
||||
buf.push_str("\n\n");
|
||||
if let Some(spaces) = &indent {
|
||||
buf.push_str(&spaces);
|
||||
}
|
||||
}
|
||||
let position = if after { anchor.text_range().end() } else { anchor.text_range().start() };
|
||||
edit.insert(position, buf);
|
||||
}
|
||||
}
|
||||
|
||||
fn make_assist_add_in_tree_list(
|
||||
tree_list: &ast::UseTreeList,
|
||||
target: &[SmolStr],
|
||||
add_self: bool,
|
||||
edit: &mut TextEditBuilder,
|
||||
) {
|
||||
let last = tree_list.use_trees().last();
|
||||
if let Some(last) = last {
|
||||
let mut buf = String::new();
|
||||
let comma = last.syntax().siblings(Direction::Next).find(|n| n.kind() == T![,]);
|
||||
let offset = if let Some(comma) = comma {
|
||||
comma.text_range().end()
|
||||
} else {
|
||||
buf.push_str(",");
|
||||
last.syntax().text_range().end()
|
||||
};
|
||||
if add_self {
|
||||
buf.push_str(" self")
|
||||
} else {
|
||||
buf.push_str(" ");
|
||||
}
|
||||
fmt_segments_raw(target, &mut buf);
|
||||
edit.insert(offset, buf);
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
fn make_assist_add_nested_import(
|
||||
path: &ast::Path,
|
||||
first_segment_to_split: &Option<ast::PathSegment>,
|
||||
target: &[SmolStr],
|
||||
add_self: bool,
|
||||
edit: &mut TextEditBuilder,
|
||||
) {
|
||||
let use_tree = path.syntax().ancestors().find_map(ast::UseTree::cast);
|
||||
if let Some(use_tree) = use_tree {
|
||||
let (start, add_colon_colon) = if let Some(first_segment_to_split) = first_segment_to_split
|
||||
{
|
||||
(first_segment_to_split.syntax().text_range().start(), false)
|
||||
} else {
|
||||
(use_tree.syntax().text_range().end(), true)
|
||||
};
|
||||
let end = use_tree.syntax().text_range().end();
|
||||
|
||||
let mut buf = String::new();
|
||||
if add_colon_colon {
|
||||
buf.push_str("::");
|
||||
}
|
||||
buf.push_str("{");
|
||||
if add_self {
|
||||
buf.push_str("self, ");
|
||||
}
|
||||
fmt_segments_raw(target, &mut buf);
|
||||
if !target.is_empty() {
|
||||
buf.push_str(", ");
|
||||
}
|
||||
edit.insert(start, buf);
|
||||
edit.insert(end, "}".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
/// If the node is on the beginning of the line, calculate indent.
|
||||
fn leading_indent(node: &SyntaxNode) -> Option<SmolStr> {
|
||||
for token in prev_tokens(node.first_token()?) {
|
||||
if let Some(ws) = ast::Whitespace::cast(token.clone()) {
|
||||
let ws_text = ws.text();
|
||||
if let Some(pos) = ws_text.rfind('\n') {
|
||||
return Some(ws_text[pos + 1..].into());
|
||||
}
|
||||
}
|
||||
if token.text().contains('\n') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return None;
|
||||
fn prev_tokens(token: SyntaxToken) -> impl Iterator<Item = SyntaxToken> {
|
||||
successors(token.prev_token(), |token| token.prev_token())
|
||||
}
|
||||
}
|
21
crates/base_db/Cargo.toml
Normal file
21
crates/base_db/Cargo.toml
Normal file
|
@ -0,0 +1,21 @@
|
|||
[package]
|
||||
name = "base_db"
|
||||
version = "0.0.0"
|
||||
license = "MIT OR Apache-2.0"
|
||||
authors = ["rust-analyzer developers"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
salsa = "0.15.2"
|
||||
rustc-hash = "1.1.0"
|
||||
|
||||
syntax = { path = "../syntax" }
|
||||
cfg = { path = "../cfg" }
|
||||
profile = { path = "../profile" }
|
||||
tt = { path = "../tt" }
|
||||
test_utils = { path = "../test_utils" }
|
||||
vfs = { path = "../vfs" }
|
||||
stdx = { path = "../stdx" }
|
228
crates/base_db/src/fixture.rs
Normal file
228
crates/base_db/src/fixture.rs
Normal file
|
@ -0,0 +1,228 @@
|
|||
//! Fixtures are strings containing rust source code with optional metadata.
|
||||
//! A fixture without metadata is parsed into a single source file.
|
||||
//! Use this to test functionality local to one file.
|
||||
//!
|
||||
//! Simple Example:
|
||||
//! ```
|
||||
//! r#"
|
||||
//! fn main() {
|
||||
//! println!("Hello World")
|
||||
//! }
|
||||
//! "#
|
||||
//! ```
|
||||
//!
|
||||
//! Metadata can be added to a fixture after a `//-` comment.
|
||||
//! The basic form is specifying filenames,
|
||||
//! which is also how to define multiple files in a single test fixture
|
||||
//!
|
||||
//! Example using two files in the same crate:
|
||||
//! ```
|
||||
//! "
|
||||
//! //- /main.rs
|
||||
//! mod foo;
|
||||
//! fn main() {
|
||||
//! foo::bar();
|
||||
//! }
|
||||
//!
|
||||
//! //- /foo.rs
|
||||
//! pub fn bar() {}
|
||||
//! "
|
||||
//! ```
|
||||
//!
|
||||
//! Example using two crates with one file each, with one crate depending on the other:
|
||||
//! ```
|
||||
//! r#"
|
||||
//! //- /main.rs crate:a deps:b
|
||||
//! fn main() {
|
||||
//! b::foo();
|
||||
//! }
|
||||
//! //- /lib.rs crate:b
|
||||
//! pub fn b() {
|
||||
//! println!("Hello World")
|
||||
//! }
|
||||
//! "#
|
||||
//! ```
|
||||
//!
|
||||
//! Metadata allows specifying all settings and variables
|
||||
//! that are available in a real rust project:
|
||||
//! - crate names via `crate:cratename`
|
||||
//! - dependencies via `deps:dep1,dep2`
|
||||
//! - configuration settings via `cfg:dbg=false,opt_level=2`
|
||||
//! - environment variables via `env:PATH=/bin,RUST_LOG=debug`
|
||||
//!
|
||||
//! Example using all available metadata:
|
||||
//! ```
|
||||
//! "
|
||||
//! //- /lib.rs crate:foo deps:bar,baz cfg:foo=a,bar=b env:OUTDIR=path/to,OTHER=foo
|
||||
//! fn insert_source_code_here() {}
|
||||
//! "
|
||||
//! ```
|
||||
use std::{str::FromStr, sync::Arc};
|
||||
|
||||
use cfg::CfgOptions;
|
||||
use rustc_hash::FxHashMap;
|
||||
use test_utils::{extract_range_or_offset, Fixture, RangeOrOffset, CURSOR_MARKER};
|
||||
use vfs::{file_set::FileSet, VfsPath};
|
||||
|
||||
use crate::{
|
||||
input::CrateName, CrateGraph, CrateId, Edition, Env, FileId, FilePosition, SourceDatabaseExt,
|
||||
SourceRoot, SourceRootId,
|
||||
};
|
||||
|
||||
pub const WORKSPACE: SourceRootId = SourceRootId(0);
|
||||
|
||||
pub trait WithFixture: Default + SourceDatabaseExt + 'static {
|
||||
fn with_single_file(text: &str) -> (Self, FileId) {
|
||||
let mut db = Self::default();
|
||||
let (_, files) = with_files(&mut db, text);
|
||||
assert_eq!(files.len(), 1);
|
||||
(db, files[0])
|
||||
}
|
||||
|
||||
fn with_files(ra_fixture: &str) -> Self {
|
||||
let mut db = Self::default();
|
||||
let (pos, _) = with_files(&mut db, ra_fixture);
|
||||
assert!(pos.is_none());
|
||||
db
|
||||
}
|
||||
|
||||
fn with_position(ra_fixture: &str) -> (Self, FilePosition) {
|
||||
let (db, file_id, range_or_offset) = Self::with_range_or_offset(ra_fixture);
|
||||
let offset = match range_or_offset {
|
||||
RangeOrOffset::Range(_) => panic!(),
|
||||
RangeOrOffset::Offset(it) => it,
|
||||
};
|
||||
(db, FilePosition { file_id, offset })
|
||||
}
|
||||
|
||||
fn with_range_or_offset(ra_fixture: &str) -> (Self, FileId, RangeOrOffset) {
|
||||
let mut db = Self::default();
|
||||
let (pos, _) = with_files(&mut db, ra_fixture);
|
||||
let (file_id, range_or_offset) = pos.unwrap();
|
||||
(db, file_id, range_or_offset)
|
||||
}
|
||||
|
||||
fn test_crate(&self) -> CrateId {
|
||||
let crate_graph = self.crate_graph();
|
||||
let mut it = crate_graph.iter();
|
||||
let res = it.next().unwrap();
|
||||
assert!(it.next().is_none());
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl<DB: SourceDatabaseExt + Default + 'static> WithFixture for DB {}
|
||||
|
||||
fn with_files(
|
||||
db: &mut dyn SourceDatabaseExt,
|
||||
fixture: &str,
|
||||
) -> (Option<(FileId, RangeOrOffset)>, Vec<FileId>) {
|
||||
let fixture = Fixture::parse(fixture);
|
||||
|
||||
let mut files = Vec::new();
|
||||
let mut crate_graph = CrateGraph::default();
|
||||
let mut crates = FxHashMap::default();
|
||||
let mut crate_deps = Vec::new();
|
||||
let mut default_crate_root: Option<FileId> = None;
|
||||
|
||||
let mut file_set = FileSet::default();
|
||||
let source_root_id = WORKSPACE;
|
||||
let source_root_prefix = "/".to_string();
|
||||
let mut file_id = FileId(0);
|
||||
|
||||
let mut file_position = None;
|
||||
|
||||
for entry in fixture {
|
||||
let text = if entry.text.contains(CURSOR_MARKER) {
|
||||
let (range_or_offset, text) = extract_range_or_offset(&entry.text);
|
||||
assert!(file_position.is_none());
|
||||
file_position = Some((file_id, range_or_offset));
|
||||
text.to_string()
|
||||
} else {
|
||||
entry.text.clone()
|
||||
};
|
||||
|
||||
let meta = FileMeta::from(entry);
|
||||
assert!(meta.path.starts_with(&source_root_prefix));
|
||||
|
||||
if let Some(krate) = meta.krate {
|
||||
let crate_id = crate_graph.add_crate_root(
|
||||
file_id,
|
||||
meta.edition,
|
||||
Some(krate.clone()),
|
||||
meta.cfg,
|
||||
meta.env,
|
||||
Default::default(),
|
||||
);
|
||||
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 {
|
||||
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());
|
||||
default_crate_root = Some(file_id);
|
||||
}
|
||||
|
||||
db.set_file_text(file_id, Arc::new(text));
|
||||
db.set_file_source_root(file_id, source_root_id);
|
||||
let path = VfsPath::new_virtual_path(meta.path);
|
||||
file_set.insert(file_id, path.into());
|
||||
files.push(file_id);
|
||||
file_id.0 += 1;
|
||||
}
|
||||
|
||||
if crates.is_empty() {
|
||||
let crate_root = default_crate_root.unwrap();
|
||||
crate_graph.add_crate_root(
|
||||
crate_root,
|
||||
Edition::Edition2018,
|
||||
None,
|
||||
CfgOptions::default(),
|
||||
Env::default(),
|
||||
Default::default(),
|
||||
);
|
||||
} else {
|
||||
for (from, to) in crate_deps {
|
||||
let from_id = crates[&from];
|
||||
let to_id = crates[&to];
|
||||
crate_graph.add_dep(from_id, CrateName::new(&to).unwrap(), to_id).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
db.set_source_root(source_root_id, Arc::new(SourceRoot::new_local(file_set)));
|
||||
db.set_crate_graph(Arc::new(crate_graph));
|
||||
|
||||
(file_position, files)
|
||||
}
|
||||
|
||||
struct FileMeta {
|
||||
path: String,
|
||||
krate: Option<String>,
|
||||
deps: Vec<String>,
|
||||
cfg: CfgOptions,
|
||||
edition: Edition,
|
||||
env: Env,
|
||||
}
|
||||
|
||||
impl From<Fixture> for FileMeta {
|
||||
fn from(f: Fixture) -> FileMeta {
|
||||
let mut cfg = CfgOptions::default();
|
||||
f.cfg_atoms.iter().for_each(|it| cfg.insert_atom(it.into()));
|
||||
f.cfg_key_values.iter().for_each(|(k, v)| cfg.insert_key_value(k.into(), v.into()));
|
||||
|
||||
FileMeta {
|
||||
path: f.path,
|
||||
krate: f.krate,
|
||||
deps: f.deps,
|
||||
cfg,
|
||||
edition: f
|
||||
.edition
|
||||
.as_ref()
|
||||
.map_or(Edition::Edition2018, |v| Edition::from_str(&v).unwrap()),
|
||||
env: f.env.into_iter().collect(),
|
||||
}
|
||||
}
|
||||
}
|
453
crates/base_db/src/input.rs
Normal file
453
crates/base_db/src/input.rs
Normal file
|
@ -0,0 +1,453 @@
|
|||
//! This module specifies the input to rust-analyzer. In some sense, this is
|
||||
//! **the** most important module, because all other fancy stuff is strictly
|
||||
//! derived from this input.
|
||||
//!
|
||||
//! Note that neither this module, nor any other part of the analyzer's core do
|
||||
//! actual IO. See `vfs` and `project_model` in the `rust-analyzer` crate for how
|
||||
//! actual IO is done and lowered to input.
|
||||
|
||||
use std::{fmt, iter::FromIterator, ops, str::FromStr, sync::Arc};
|
||||
|
||||
use cfg::CfgOptions;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use syntax::SmolStr;
|
||||
use tt::TokenExpander;
|
||||
use vfs::file_set::FileSet;
|
||||
|
||||
pub use vfs::FileId;
|
||||
|
||||
/// Files are grouped into source roots. A source root is a directory on the
|
||||
/// file systems which is watched for changes. Typically it corresponds to a
|
||||
/// Rust crate. Source roots *might* be nested: in this case, a file belongs to
|
||||
/// the nearest enclosing source root. Paths to files are always relative to a
|
||||
/// source root, and the analyzer does not know the root path of the source root at
|
||||
/// all. So, a file from one source root can't refer to a file in another source
|
||||
/// root by path.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct SourceRootId(pub u32);
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct SourceRoot {
|
||||
/// Sysroot or crates.io library.
|
||||
///
|
||||
/// Libraries are considered mostly immutable, this assumption is used to
|
||||
/// optimize salsa's query structure
|
||||
pub is_library: bool,
|
||||
pub(crate) file_set: FileSet,
|
||||
}
|
||||
|
||||
impl SourceRoot {
|
||||
pub fn new_local(file_set: FileSet) -> SourceRoot {
|
||||
SourceRoot { is_library: false, file_set }
|
||||
}
|
||||
pub fn new_library(file_set: FileSet) -> SourceRoot {
|
||||
SourceRoot { is_library: true, file_set }
|
||||
}
|
||||
pub fn iter(&self) -> impl Iterator<Item = FileId> + '_ {
|
||||
self.file_set.iter()
|
||||
}
|
||||
}
|
||||
|
||||
/// `CrateGraph` is a bit of information which turns a set of text files into a
|
||||
/// number of Rust crates. Each crate is defined by the `FileId` of its root module,
|
||||
/// the set of cfg flags (not yet implemented) and the set of dependencies. Note
|
||||
/// that, due to cfg's, there might be several crates for a single `FileId`! As
|
||||
/// in the rust-lang proper, a crate does not have a name. Instead, names are
|
||||
/// specified on dependency edges. That is, a crate might be known under
|
||||
/// different names in different dependent crates.
|
||||
///
|
||||
/// Note that `CrateGraph` is build-system agnostic: it's a concept of the Rust
|
||||
/// language proper, not a concept of the build system. In practice, we get
|
||||
/// `CrateGraph` by lowering `cargo metadata` output.
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct CrateGraph {
|
||||
arena: FxHashMap<CrateId, CrateData>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct CrateId(pub u32);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct CrateName(SmolStr);
|
||||
|
||||
impl CrateName {
|
||||
/// Creates a crate name, checking for dashes in the string provided.
|
||||
/// Dashes are not allowed in the crate names,
|
||||
/// hence the input string is returned as `Err` for those cases.
|
||||
pub fn new(name: &str) -> Result<CrateName, &str> {
|
||||
if name.contains('-') {
|
||||
Err(name)
|
||||
} else {
|
||||
Ok(Self(SmolStr::new(name)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a crate name, unconditionally replacing the dashes with underscores.
|
||||
pub fn normalize_dashes(name: &str) -> CrateName {
|
||||
Self(SmolStr::new(name.replace('-', "_")))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CrateName {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ProcMacro {
|
||||
pub name: SmolStr,
|
||||
pub expander: Arc<dyn TokenExpander>,
|
||||
}
|
||||
|
||||
impl Eq for ProcMacro {}
|
||||
impl PartialEq for ProcMacro {
|
||||
fn eq(&self, other: &ProcMacro) -> bool {
|
||||
self.name == other.name && Arc::ptr_eq(&self.expander, &other.expander)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CrateData {
|
||||
pub root_file_id: FileId,
|
||||
pub edition: Edition,
|
||||
/// 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<String>,
|
||||
pub cfg_options: CfgOptions,
|
||||
pub env: Env,
|
||||
pub dependencies: Vec<Dependency>,
|
||||
pub proc_macro: Vec<ProcMacro>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum Edition {
|
||||
Edition2018,
|
||||
Edition2015,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Env {
|
||||
entries: FxHashMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Dependency {
|
||||
pub crate_id: CrateId,
|
||||
pub name: CrateName,
|
||||
}
|
||||
|
||||
impl CrateGraph {
|
||||
pub fn add_crate_root(
|
||||
&mut self,
|
||||
file_id: FileId,
|
||||
edition: Edition,
|
||||
display_name: Option<String>,
|
||||
cfg_options: CfgOptions,
|
||||
env: Env,
|
||||
proc_macro: Vec<(SmolStr, Arc<dyn tt::TokenExpander>)>,
|
||||
) -> CrateId {
|
||||
let proc_macro =
|
||||
proc_macro.into_iter().map(|(name, it)| ProcMacro { name, expander: it }).collect();
|
||||
|
||||
let data = CrateData {
|
||||
root_file_id: file_id,
|
||||
edition,
|
||||
display_name,
|
||||
cfg_options,
|
||||
env,
|
||||
proc_macro,
|
||||
dependencies: Vec::new(),
|
||||
};
|
||||
let crate_id = CrateId(self.arena.len() as u32);
|
||||
let prev = self.arena.insert(crate_id, data);
|
||||
assert!(prev.is_none());
|
||||
crate_id
|
||||
}
|
||||
|
||||
pub fn add_dep(
|
||||
&mut self,
|
||||
from: CrateId,
|
||||
name: CrateName,
|
||||
to: CrateId,
|
||||
) -> Result<(), CyclicDependenciesError> {
|
||||
if self.dfs_find(from, to, &mut FxHashSet::default()) {
|
||||
return Err(CyclicDependenciesError);
|
||||
}
|
||||
self.arena.get_mut(&from).unwrap().add_dep(name, to);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.arena.is_empty()
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = CrateId> + '_ {
|
||||
self.arena.keys().copied()
|
||||
}
|
||||
|
||||
/// Returns an iterator over all transitive dependencies of the given crate.
|
||||
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, _) =
|
||||
self.arena.iter().find(|(_crate_id, data)| data.root_file_id == file_id)?;
|
||||
Some(crate_id)
|
||||
}
|
||||
|
||||
/// Extends this crate graph by adding a complete disjoint second crate
|
||||
/// graph.
|
||||
///
|
||||
/// The ids of the crates in the `other` graph are shifted by the return
|
||||
/// amount.
|
||||
pub fn extend(&mut self, other: CrateGraph) -> u32 {
|
||||
let start = self.arena.len() as u32;
|
||||
self.arena.extend(other.arena.into_iter().map(|(id, mut data)| {
|
||||
let new_id = id.shift(start);
|
||||
for dep in &mut data.dependencies {
|
||||
dep.crate_id = dep.crate_id.shift(start);
|
||||
}
|
||||
(new_id, data)
|
||||
}));
|
||||
start
|
||||
}
|
||||
|
||||
fn dfs_find(&self, target: CrateId, from: CrateId, visited: &mut FxHashSet<CrateId>) -> bool {
|
||||
if !visited.insert(from) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if target == from {
|
||||
return true;
|
||||
}
|
||||
|
||||
for dep in &self[from].dependencies {
|
||||
let crate_id = dep.crate_id;
|
||||
if self.dfs_find(target, crate_id, visited) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Index<CrateId> for CrateGraph {
|
||||
type Output = CrateData;
|
||||
fn index(&self, crate_id: CrateId) -> &CrateData {
|
||||
&self.arena[&crate_id]
|
||||
}
|
||||
}
|
||||
|
||||
impl CrateId {
|
||||
pub fn shift(self, amount: u32) -> CrateId {
|
||||
CrateId(self.0 + amount)
|
||||
}
|
||||
}
|
||||
|
||||
impl CrateData {
|
||||
fn add_dep(&mut self, name: CrateName, crate_id: CrateId) {
|
||||
self.dependencies.push(Dependency { name, crate_id })
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Edition {
|
||||
type Err = ParseEditionError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let res = match s {
|
||||
"2015" => Edition::Edition2015,
|
||||
"2018" => Edition::Edition2018,
|
||||
_ => return Err(ParseEditionError { invalid_input: s.to_string() }),
|
||||
};
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Edition {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(match self {
|
||||
Edition::Edition2015 => "2015",
|
||||
Edition::Edition2018 => "2018",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<(String, String)> for Env {
|
||||
fn from_iter<T: IntoIterator<Item = (String, String)>>(iter: T) -> Self {
|
||||
Env { entries: FromIterator::from_iter(iter) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Env {
|
||||
pub fn set(&mut self, env: &str, value: String) {
|
||||
self.entries.insert(env.to_owned(), value);
|
||||
}
|
||||
|
||||
pub fn get(&self, env: &str) -> Option<String> {
|
||||
self.entries.get(env).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ParseEditionError {
|
||||
invalid_input: String,
|
||||
}
|
||||
|
||||
impl fmt::Display for ParseEditionError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "invalid edition: {:?}", self.invalid_input)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for ParseEditionError {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CyclicDependenciesError;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{CfgOptions, CrateGraph, CrateName, Dependency, Edition::Edition2018, Env, FileId};
|
||||
|
||||
#[test]
|
||||
fn detect_cyclic_dependency_indirect() {
|
||||
let mut graph = CrateGraph::default();
|
||||
let crate1 = graph.add_crate_root(
|
||||
FileId(1u32),
|
||||
Edition2018,
|
||||
None,
|
||||
CfgOptions::default(),
|
||||
Env::default(),
|
||||
Default::default(),
|
||||
);
|
||||
let crate2 = graph.add_crate_root(
|
||||
FileId(2u32),
|
||||
Edition2018,
|
||||
None,
|
||||
CfgOptions::default(),
|
||||
Env::default(),
|
||||
Default::default(),
|
||||
);
|
||||
let crate3 = graph.add_crate_root(
|
||||
FileId(3u32),
|
||||
Edition2018,
|
||||
None,
|
||||
CfgOptions::default(),
|
||||
Env::default(),
|
||||
Default::default(),
|
||||
);
|
||||
assert!(graph.add_dep(crate1, CrateName::new("crate2").unwrap(), crate2).is_ok());
|
||||
assert!(graph.add_dep(crate2, CrateName::new("crate3").unwrap(), crate3).is_ok());
|
||||
assert!(graph.add_dep(crate3, CrateName::new("crate1").unwrap(), crate1).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detect_cyclic_dependency_direct() {
|
||||
let mut graph = CrateGraph::default();
|
||||
let crate1 = graph.add_crate_root(
|
||||
FileId(1u32),
|
||||
Edition2018,
|
||||
None,
|
||||
CfgOptions::default(),
|
||||
Env::default(),
|
||||
Default::default(),
|
||||
);
|
||||
let crate2 = graph.add_crate_root(
|
||||
FileId(2u32),
|
||||
Edition2018,
|
||||
None,
|
||||
CfgOptions::default(),
|
||||
Env::default(),
|
||||
Default::default(),
|
||||
);
|
||||
assert!(graph.add_dep(crate1, CrateName::new("crate2").unwrap(), crate2).is_ok());
|
||||
assert!(graph.add_dep(crate2, CrateName::new("crate2").unwrap(), crate2).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let mut graph = CrateGraph::default();
|
||||
let crate1 = graph.add_crate_root(
|
||||
FileId(1u32),
|
||||
Edition2018,
|
||||
None,
|
||||
CfgOptions::default(),
|
||||
Env::default(),
|
||||
Default::default(),
|
||||
);
|
||||
let crate2 = graph.add_crate_root(
|
||||
FileId(2u32),
|
||||
Edition2018,
|
||||
None,
|
||||
CfgOptions::default(),
|
||||
Env::default(),
|
||||
Default::default(),
|
||||
);
|
||||
let crate3 = graph.add_crate_root(
|
||||
FileId(3u32),
|
||||
Edition2018,
|
||||
None,
|
||||
CfgOptions::default(),
|
||||
Env::default(),
|
||||
Default::default(),
|
||||
);
|
||||
assert!(graph.add_dep(crate1, CrateName::new("crate2").unwrap(), crate2).is_ok());
|
||||
assert!(graph.add_dep(crate2, CrateName::new("crate3").unwrap(), crate3).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dashes_are_normalized() {
|
||||
let mut graph = CrateGraph::default();
|
||||
let crate1 = graph.add_crate_root(
|
||||
FileId(1u32),
|
||||
Edition2018,
|
||||
None,
|
||||
CfgOptions::default(),
|
||||
Env::default(),
|
||||
Default::default(),
|
||||
);
|
||||
let crate2 = graph.add_crate_root(
|
||||
FileId(2u32),
|
||||
Edition2018,
|
||||
None,
|
||||
CfgOptions::default(),
|
||||
Env::default(),
|
||||
Default::default(),
|
||||
);
|
||||
assert!(graph
|
||||
.add_dep(crate1, CrateName::normalize_dashes("crate-name-with-dashes"), crate2)
|
||||
.is_ok());
|
||||
assert_eq!(
|
||||
graph[crate1].dependencies,
|
||||
vec![Dependency {
|
||||
crate_id: crate2,
|
||||
name: CrateName::new("crate_name_with_dashes").unwrap()
|
||||
}]
|
||||
);
|
||||
}
|
||||
}
|
167
crates/base_db/src/lib.rs
Normal file
167
crates/base_db/src/lib.rs
Normal file
|
@ -0,0 +1,167 @@
|
|||
//! base_db defines basic database traits. The concrete DB is defined by ide.
|
||||
mod cancellation;
|
||||
mod input;
|
||||
pub mod fixture;
|
||||
|
||||
use std::{panic, sync::Arc};
|
||||
|
||||
use rustc_hash::FxHashSet;
|
||||
use syntax::{ast, Parse, SourceFile, TextRange, TextSize};
|
||||
|
||||
pub use crate::{
|
||||
cancellation::Canceled,
|
||||
input::{
|
||||
CrateData, CrateGraph, CrateId, CrateName, Dependency, Edition, Env, FileId, ProcMacroId,
|
||||
SourceRoot, SourceRootId,
|
||||
},
|
||||
};
|
||||
pub use salsa;
|
||||
pub use vfs::{file_set::FileSet, VfsPath};
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! impl_intern_key {
|
||||
($name:ident) => {
|
||||
impl $crate::salsa::InternKey for $name {
|
||||
fn from_intern_id(v: $crate::salsa::InternId) -> Self {
|
||||
$name(v)
|
||||
}
|
||||
fn as_intern_id(&self) -> $crate::salsa::InternId {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub trait Upcast<T: ?Sized> {
|
||||
fn upcast(&self) -> &T;
|
||||
}
|
||||
|
||||
pub trait CheckCanceled {
|
||||
/// Aborts current query if there are pending changes.
|
||||
///
|
||||
/// rust-analyzer needs to be able to answer semantic questions about the
|
||||
/// code while the code is being modified. A common problem is that a
|
||||
/// long-running query is being calculated when a new change arrives.
|
||||
///
|
||||
/// We can't just apply the change immediately: this will cause the pending
|
||||
/// query to see inconsistent state (it will observe an absence of
|
||||
/// repeatable read). So what we do is we **cancel** all pending queries
|
||||
/// before applying the change.
|
||||
///
|
||||
/// We implement cancellation by panicking with a special value and catching
|
||||
/// it on the API boundary. Salsa explicitly supports this use-case.
|
||||
fn check_canceled(&self);
|
||||
|
||||
fn catch_canceled<F, T>(&self, f: F) -> Result<T, Canceled>
|
||||
where
|
||||
Self: Sized + panic::RefUnwindSafe,
|
||||
F: FnOnce(&Self) -> T + panic::UnwindSafe,
|
||||
{
|
||||
panic::catch_unwind(|| f(self)).map_err(|err| match err.downcast::<Canceled>() {
|
||||
Ok(canceled) => *canceled,
|
||||
Err(payload) => panic::resume_unwind(payload),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: salsa::Database> CheckCanceled for T {
|
||||
fn check_canceled(&self) {
|
||||
if self.salsa_runtime().is_current_revision_canceled() {
|
||||
Canceled::throw()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct FilePosition {
|
||||
pub file_id: FileId,
|
||||
pub offset: TextSize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct FileRange {
|
||||
pub file_id: FileId,
|
||||
pub range: TextRange,
|
||||
}
|
||||
|
||||
pub const DEFAULT_LRU_CAP: usize = 128;
|
||||
|
||||
pub trait FileLoader {
|
||||
/// Text of the file.
|
||||
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. 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>>;
|
||||
}
|
||||
|
||||
/// Database which stores all significant input facts: source code and project
|
||||
/// model. Everything else in rust-analyzer is derived from these queries.
|
||||
#[salsa::query_group(SourceDatabaseStorage)]
|
||||
pub trait SourceDatabase: CheckCanceled + FileLoader + std::fmt::Debug {
|
||||
// Parses the file into the syntax tree.
|
||||
#[salsa::invoke(parse_query)]
|
||||
fn parse(&self, file_id: FileId) -> Parse<ast::SourceFile>;
|
||||
|
||||
/// The crate graph.
|
||||
#[salsa::input]
|
||||
fn crate_graph(&self) -> Arc<CrateGraph>;
|
||||
}
|
||||
|
||||
fn parse_query(db: &dyn SourceDatabase, file_id: FileId) -> Parse<ast::SourceFile> {
|
||||
let _p = profile::span("parse_query").detail(|| format!("{:?}", file_id));
|
||||
let text = db.file_text(file_id);
|
||||
SourceFile::parse(&*text)
|
||||
}
|
||||
|
||||
/// We don't want to give HIR knowledge of source roots, hence we extract these
|
||||
/// methods into a separate DB.
|
||||
#[salsa::query_group(SourceDatabaseExtStorage)]
|
||||
pub trait SourceDatabaseExt: SourceDatabase {
|
||||
#[salsa::input]
|
||||
fn file_text(&self, file_id: FileId) -> Arc<String>;
|
||||
/// Path to a file, relative to the root of its source root.
|
||||
/// Source root of the file.
|
||||
#[salsa::input]
|
||||
fn file_source_root(&self, file_id: FileId) -> SourceRootId;
|
||||
/// Contents of the source root.
|
||||
#[salsa::input]
|
||||
fn source_root(&self, id: SourceRootId) -> Arc<SourceRoot>;
|
||||
|
||||
fn source_root_crates(&self, 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()
|
||||
.filter(|&krate| {
|
||||
let root_file = graph[krate].root_file_id;
|
||||
db.file_source_root(root_file) == id
|
||||
})
|
||||
.collect::<FxHashSet<_>>();
|
||||
Arc::new(res)
|
||||
}
|
||||
|
||||
/// Silly workaround for cyclic deps between the traits
|
||||
pub struct FileLoaderDelegate<T>(pub T);
|
||||
|
||||
impl<T: SourceDatabaseExt> FileLoader for FileLoaderDelegate<&'_ T> {
|
||||
fn file_text(&self, file_id: FileId) -> Arc<String> {
|
||||
SourceDatabaseExt::file_text(self.0, file_id)
|
||||
}
|
||||
fn resolve_path(&self, anchor: FileId, path: &str) -> Option<FileId> {
|
||||
// FIXME: this *somehow* should be platform agnostic...
|
||||
let source_root = self.0.file_source_root(anchor);
|
||||
let source_root = self.0.source_root(source_root);
|
||||
source_root.file_set.resolve_path(anchor, path)
|
||||
}
|
||||
|
||||
fn relevant_crates(&self, file_id: FileId) -> Arc<FxHashSet<CrateId>> {
|
||||
let source_root = self.0.file_source_root(file_id);
|
||||
self.0.source_root_crates(source_root)
|
||||
}
|
||||
}
|
18
crates/cfg/Cargo.toml
Normal file
18
crates/cfg/Cargo.toml
Normal file
|
@ -0,0 +1,18 @@
|
|||
[package]
|
||||
name = "cfg"
|
||||
version = "0.0.0"
|
||||
license = "MIT OR Apache-2.0"
|
||||
authors = ["rust-analyzer developers"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
rustc-hash = "1.1.0"
|
||||
|
||||
tt = { path = "../tt" }
|
||||
|
||||
[dev-dependencies]
|
||||
mbe = { path = "../mbe" }
|
||||
syntax = { path = "../syntax" }
|
133
crates/cfg/src/cfg_expr.rs
Normal file
133
crates/cfg/src/cfg_expr.rs
Normal file
|
@ -0,0 +1,133 @@
|
|||
//! The condition expression used in `#[cfg(..)]` attributes.
|
||||
//!
|
||||
//! See: https://doc.rust-lang.org/reference/conditional-compilation.html#conditional-compilation
|
||||
|
||||
use std::slice::Iter as SliceIter;
|
||||
|
||||
use tt::SmolStr;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum CfgExpr {
|
||||
Invalid,
|
||||
Atom(SmolStr),
|
||||
KeyValue { key: SmolStr, value: SmolStr },
|
||||
All(Vec<CfgExpr>),
|
||||
Any(Vec<CfgExpr>),
|
||||
Not(Box<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 {
|
||||
CfgExpr::Invalid => None,
|
||||
CfgExpr::Atom(name) => Some(query(name, None)),
|
||||
CfgExpr::KeyValue { key, value } => Some(query(key, Some(value))),
|
||||
CfgExpr::All(preds) => {
|
||||
preds.iter().try_fold(true, |s, pred| Some(s && pred.fold(query)?))
|
||||
}
|
||||
CfgExpr::Any(preds) => {
|
||||
preds.iter().try_fold(false, |s, pred| Some(s || pred.fold(query)?))
|
||||
}
|
||||
CfgExpr::Not(pred) => pred.fold(query).map(|s| !s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn next_cfg_expr(it: &mut SliceIter<tt::TokenTree>) -> Option<CfgExpr> {
|
||||
let name = match it.next() {
|
||||
None => return None,
|
||||
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(tt::TokenTree::Leaf(tt::Leaf::Punct(punct))) if punct.char == '=' => {
|
||||
match it.as_slice().get(1) {
|
||||
Some(tt::TokenTree::Leaf(tt::Leaf::Literal(literal))) => {
|
||||
it.next();
|
||||
it.next();
|
||||
// FIXME: escape? raw string?
|
||||
let value =
|
||||
SmolStr::new(literal.text.trim_start_matches('"').trim_end_matches('"'));
|
||||
CfgExpr::KeyValue { key: name, value }
|
||||
}
|
||||
_ => return Some(CfgExpr::Invalid),
|
||||
}
|
||||
}
|
||||
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();
|
||||
match name.as_str() {
|
||||
"all" => CfgExpr::All(subs),
|
||||
"any" => CfgExpr::Any(subs),
|
||||
"not" => CfgExpr::Not(Box::new(subs.pop().unwrap_or(CfgExpr::Invalid))),
|
||||
_ => CfgExpr::Invalid,
|
||||
}
|
||||
}
|
||||
_ => CfgExpr::Atom(name),
|
||||
};
|
||||
|
||||
// Eat comma separator
|
||||
if let Some(tt::TokenTree::Leaf(tt::Leaf::Punct(punct))) = it.as_slice().first() {
|
||||
if punct.char == ',' {
|
||||
it.next();
|
||||
}
|
||||
}
|
||||
Some(ret)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use mbe::ast_to_token_tree;
|
||||
use syntax::ast::{self, AstNode};
|
||||
|
||||
fn assert_parse_result(input: &str, expected: CfgExpr) {
|
||||
let (tt, _) = {
|
||||
let source_file = ast::SourceFile::parse(input).ok().unwrap();
|
||||
let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
|
||||
ast_to_token_tree(&tt).unwrap()
|
||||
};
|
||||
let cfg = CfgExpr::parse(&tt);
|
||||
assert_eq!(cfg, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cfg_expr_parser() {
|
||||
assert_parse_result("#![cfg(foo)]", CfgExpr::Atom("foo".into()));
|
||||
assert_parse_result("#![cfg(foo,)]", CfgExpr::Atom("foo".into()));
|
||||
assert_parse_result(
|
||||
"#![cfg(not(foo))]",
|
||||
CfgExpr::Not(Box::new(CfgExpr::Atom("foo".into()))),
|
||||
);
|
||||
assert_parse_result("#![cfg(foo(bar))]", CfgExpr::Invalid);
|
||||
|
||||
// Only take the first
|
||||
assert_parse_result(r#"#![cfg(foo, bar = "baz")]"#, CfgExpr::Atom("foo".into()));
|
||||
|
||||
assert_parse_result(
|
||||
r#"#![cfg(all(foo, bar = "baz"))]"#,
|
||||
CfgExpr::All(vec![
|
||||
CfgExpr::Atom("foo".into()),
|
||||
CfgExpr::KeyValue { key: "bar".into(), value: "baz".into() },
|
||||
]),
|
||||
);
|
||||
|
||||
assert_parse_result(
|
||||
r#"#![cfg(any(not(), all(), , bar = "baz",))]"#,
|
||||
CfgExpr::Any(vec![
|
||||
CfgExpr::Not(Box::new(CfgExpr::Invalid)),
|
||||
CfgExpr::All(vec![]),
|
||||
CfgExpr::Invalid,
|
||||
CfgExpr::KeyValue { key: "bar".into(), value: "baz".into() },
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
51
crates/cfg/src/lib.rs
Normal file
51
crates/cfg/src/lib.rs
Normal file
|
@ -0,0 +1,51 @@
|
|||
//! cfg defines conditional compiling options, `cfg` attibute parser and evaluator
|
||||
|
||||
mod cfg_expr;
|
||||
|
||||
use rustc_hash::FxHashSet;
|
||||
use tt::SmolStr;
|
||||
|
||||
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
|
||||
/// key-value options like `target_arch="x86"`.
|
||||
///
|
||||
/// Note that for key-value options, one key can have multiple values (but not none).
|
||||
/// `feature` is an example. We have both `feature="foo"` and `feature="bar"` if features
|
||||
/// `foo` and `bar` are both enabled. And here, we store key-value options as a set of tuple
|
||||
/// of key and value in `key_values`.
|
||||
///
|
||||
/// See: https://doc.rust-lang.org/reference/conditional-compilation.html#set-configuration-options
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||
pub struct CfgOptions {
|
||||
atoms: FxHashSet<SmolStr>,
|
||||
key_values: FxHashSet<(SmolStr, SmolStr)>,
|
||||
}
|
||||
|
||||
impl CfgOptions {
|
||||
pub fn check(&self, cfg: &CfgExpr) -> Option<bool> {
|
||||
cfg.fold(&|key, value| match value {
|
||||
None => self.atoms.contains(key),
|
||||
Some(value) => self.key_values.contains(&(key.clone(), value.clone())),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn insert_atom(&mut self, key: SmolStr) {
|
||||
self.atoms.insert(key);
|
||||
}
|
||||
|
||||
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()));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
[package]
|
||||
name = "expect"
|
||||
version = "0.1.0"
|
||||
version = "0.0.0"
|
||||
license = "MIT OR Apache-2.0"
|
||||
authors = ["rust-analyzer developers"]
|
||||
edition = "2018"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
@ -11,4 +11,5 @@ doctest = false
|
|||
[dependencies]
|
||||
once_cell = "1"
|
||||
difference = "2"
|
||||
|
||||
stdx = { path = "../stdx" }
|
||||
|
|
|
@ -74,7 +74,7 @@ impl fmt::Display for Position {
|
|||
impl Expect {
|
||||
pub fn assert_eq(&self, actual: &str) {
|
||||
let trimmed = self.trimmed();
|
||||
if &trimmed == actual {
|
||||
if trimmed == actual {
|
||||
return;
|
||||
}
|
||||
Runtime::fail_expect(self, &trimmed, actual);
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
[package]
|
||||
edition = "2018"
|
||||
name = "flycheck"
|
||||
version = "0.1.0"
|
||||
authors = ["rust-analyzer developers"]
|
||||
version = "0.0.0"
|
||||
license = "MIT OR Apache-2.0"
|
||||
authors = ["rust-analyzer developers"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
@ -14,4 +14,5 @@ log = "0.4.8"
|
|||
cargo_metadata = "0.11.1"
|
||||
serde_json = "1.0.48"
|
||||
jod-thread = "0.1.1"
|
||||
ra_toolchain = { path = "../ra_toolchain" }
|
||||
|
||||
toolchain = { path = "../toolchain" }
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//! cargo_check provides the functionality needed to run `cargo check` or
|
||||
//! Flycheck provides the functionality needed to run `cargo check` or
|
||||
//! another compatible command (f.x. clippy) in a background thread and provide
|
||||
//! LSP diagnostics based on the output of the command.
|
||||
|
||||
|
@ -147,6 +147,12 @@ impl FlycheckActor {
|
|||
// avoid busy-waiting.
|
||||
let cargo_handle = self.cargo_handle.take().unwrap();
|
||||
let res = cargo_handle.join();
|
||||
if res.is_err() {
|
||||
log::error!(
|
||||
"Flycheck failed to run the following command: {:?}",
|
||||
self.check_command()
|
||||
)
|
||||
}
|
||||
self.send(Message::Progress(Progress::DidFinish(res)));
|
||||
}
|
||||
Event::CheckEvent(Some(message)) => match message {
|
||||
|
@ -187,7 +193,7 @@ impl FlycheckActor {
|
|||
extra_args,
|
||||
features,
|
||||
} => {
|
||||
let mut cmd = Command::new(ra_toolchain::cargo());
|
||||
let mut cmd = Command::new(toolchain::cargo());
|
||||
cmd.arg(command);
|
||||
cmd.args(&["--workspace", "--message-format=json", "--manifest-path"])
|
||||
.arg(self.workspace_root.join("Cargo.toml"));
|
||||
|
@ -253,7 +259,7 @@ impl CargoHandle {
|
|||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!(
|
||||
"Cargo watcher failed,the command produced no valid metadata (exit code: {:?})",
|
||||
"Cargo watcher failed, the command produced no valid metadata (exit code: {:?})",
|
||||
exit_status
|
||||
),
|
||||
));
|
||||
|
|
24
crates/hir/Cargo.toml
Normal file
24
crates/hir/Cargo.toml
Normal file
|
@ -0,0 +1,24 @@
|
|||
[package]
|
||||
name = "hir"
|
||||
version = "0.0.0"
|
||||
license = "MIT OR Apache-2.0"
|
||||
authors = ["rust-analyzer developers"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
log = "0.4.8"
|
||||
rustc-hash = "1.1.0"
|
||||
either = "1.5.3"
|
||||
arrayvec = "0.5.1"
|
||||
itertools = "0.9.0"
|
||||
|
||||
stdx = { path = "../stdx" }
|
||||
syntax = { path = "../syntax" }
|
||||
base_db = { path = "../base_db" }
|
||||
profile = { path = "../profile" }
|
||||
hir_expand = { path = "../hir_expand" }
|
||||
hir_def = { path = "../hir_def" }
|
||||
hir_ty = { path = "../hir_ty" }
|
1719
crates/hir/src/code_model.rs
Normal file
1719
crates/hir/src/code_model.rs
Normal file
File diff suppressed because it is too large
Load diff
6
crates/hir/src/diagnostics.rs
Normal file
6
crates/hir/src/diagnostics.rs
Normal file
|
@ -0,0 +1,6 @@
|
|||
//! FIXME: write short doc here
|
||||
pub use hir_def::diagnostics::UnresolvedModule;
|
||||
pub use hir_expand::diagnostics::{Diagnostic, DiagnosticSink, DiagnosticSinkBuilder};
|
||||
pub use hir_ty::diagnostics::{
|
||||
MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkInTailExpr, NoSuchField,
|
||||
};
|
247
crates/hir/src/from_id.rs
Normal file
247
crates/hir/src/from_id.rs
Normal file
|
@ -0,0 +1,247 @@
|
|||
//! Utility module for converting between hir_def ids and code_model wrappers.
|
||||
//!
|
||||
//! It's unclear if we need this long-term, but it's definitelly useful while we
|
||||
//! are splitting the hir.
|
||||
|
||||
use hir_def::{
|
||||
expr::PatId, AdtId, AssocItemId, AttrDefId, DefWithBodyId, EnumVariantId, FieldId,
|
||||
GenericDefId, ModuleDefId, VariantId,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
code_model::ItemInNs, Adt, AssocItem, AttrDef, DefWithBody, EnumVariant, Field, GenericDef,
|
||||
Local, MacroDef, ModuleDef, VariantDef,
|
||||
};
|
||||
|
||||
macro_rules! from_id {
|
||||
($(($id:path, $ty:path)),*) => {$(
|
||||
impl From<$id> for $ty {
|
||||
fn from(id: $id) -> $ty {
|
||||
$ty { id }
|
||||
}
|
||||
}
|
||||
impl From<$ty> for $id {
|
||||
fn from(ty: $ty) -> $id {
|
||||
ty.id
|
||||
}
|
||||
}
|
||||
)*}
|
||||
}
|
||||
|
||||
from_id![
|
||||
(base_db::CrateId, crate::Crate),
|
||||
(hir_def::ModuleId, crate::Module),
|
||||
(hir_def::StructId, crate::Struct),
|
||||
(hir_def::UnionId, crate::Union),
|
||||
(hir_def::EnumId, crate::Enum),
|
||||
(hir_def::TypeAliasId, crate::TypeAlias),
|
||||
(hir_def::TraitId, crate::Trait),
|
||||
(hir_def::StaticId, crate::Static),
|
||||
(hir_def::ConstId, crate::Const),
|
||||
(hir_def::FunctionId, crate::Function),
|
||||
(hir_def::ImplId, crate::ImplDef),
|
||||
(hir_def::TypeParamId, crate::TypeParam),
|
||||
(hir_expand::MacroDefId, crate::MacroDef)
|
||||
];
|
||||
|
||||
impl From<AdtId> for Adt {
|
||||
fn from(id: AdtId) -> Self {
|
||||
match id {
|
||||
AdtId::StructId(it) => Adt::Struct(it.into()),
|
||||
AdtId::UnionId(it) => Adt::Union(it.into()),
|
||||
AdtId::EnumId(it) => Adt::Enum(it.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Adt> for AdtId {
|
||||
fn from(id: Adt) -> Self {
|
||||
match id {
|
||||
Adt::Struct(it) => AdtId::StructId(it.id),
|
||||
Adt::Union(it) => AdtId::UnionId(it.id),
|
||||
Adt::Enum(it) => AdtId::EnumId(it.id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EnumVariantId> for EnumVariant {
|
||||
fn from(id: EnumVariantId) -> Self {
|
||||
EnumVariant { parent: id.parent.into(), id: id.local_id }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EnumVariant> for EnumVariantId {
|
||||
fn from(def: EnumVariant) -> Self {
|
||||
EnumVariantId { parent: def.parent.id, local_id: def.id }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ModuleDefId> for ModuleDef {
|
||||
fn from(id: ModuleDefId) -> Self {
|
||||
match id {
|
||||
ModuleDefId::ModuleId(it) => ModuleDef::Module(it.into()),
|
||||
ModuleDefId::FunctionId(it) => ModuleDef::Function(it.into()),
|
||||
ModuleDefId::AdtId(it) => ModuleDef::Adt(it.into()),
|
||||
ModuleDefId::EnumVariantId(it) => ModuleDef::EnumVariant(it.into()),
|
||||
ModuleDefId::ConstId(it) => ModuleDef::Const(it.into()),
|
||||
ModuleDefId::StaticId(it) => ModuleDef::Static(it.into()),
|
||||
ModuleDefId::TraitId(it) => ModuleDef::Trait(it.into()),
|
||||
ModuleDefId::TypeAliasId(it) => ModuleDef::TypeAlias(it.into()),
|
||||
ModuleDefId::BuiltinType(it) => ModuleDef::BuiltinType(it),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ModuleDef> for ModuleDefId {
|
||||
fn from(id: ModuleDef) -> Self {
|
||||
match id {
|
||||
ModuleDef::Module(it) => ModuleDefId::ModuleId(it.into()),
|
||||
ModuleDef::Function(it) => ModuleDefId::FunctionId(it.into()),
|
||||
ModuleDef::Adt(it) => ModuleDefId::AdtId(it.into()),
|
||||
ModuleDef::EnumVariant(it) => ModuleDefId::EnumVariantId(it.into()),
|
||||
ModuleDef::Const(it) => ModuleDefId::ConstId(it.into()),
|
||||
ModuleDef::Static(it) => ModuleDefId::StaticId(it.into()),
|
||||
ModuleDef::Trait(it) => ModuleDefId::TraitId(it.into()),
|
||||
ModuleDef::TypeAlias(it) => ModuleDefId::TypeAliasId(it.into()),
|
||||
ModuleDef::BuiltinType(it) => ModuleDefId::BuiltinType(it),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DefWithBody> for DefWithBodyId {
|
||||
fn from(def: DefWithBody) -> Self {
|
||||
match def {
|
||||
DefWithBody::Function(it) => DefWithBodyId::FunctionId(it.id),
|
||||
DefWithBody::Static(it) => DefWithBodyId::StaticId(it.id),
|
||||
DefWithBody::Const(it) => DefWithBodyId::ConstId(it.id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DefWithBodyId> for DefWithBody {
|
||||
fn from(def: DefWithBodyId) -> Self {
|
||||
match def {
|
||||
DefWithBodyId::FunctionId(it) => DefWithBody::Function(it.into()),
|
||||
DefWithBodyId::StaticId(it) => DefWithBody::Static(it.into()),
|
||||
DefWithBodyId::ConstId(it) => DefWithBody::Const(it.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AssocItemId> for AssocItem {
|
||||
fn from(def: AssocItemId) -> Self {
|
||||
match def {
|
||||
AssocItemId::FunctionId(it) => AssocItem::Function(it.into()),
|
||||
AssocItemId::TypeAliasId(it) => AssocItem::TypeAlias(it.into()),
|
||||
AssocItemId::ConstId(it) => AssocItem::Const(it.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GenericDef> for GenericDefId {
|
||||
fn from(def: GenericDef) -> Self {
|
||||
match def {
|
||||
GenericDef::Function(it) => GenericDefId::FunctionId(it.id),
|
||||
GenericDef::Adt(it) => GenericDefId::AdtId(it.into()),
|
||||
GenericDef::Trait(it) => GenericDefId::TraitId(it.id),
|
||||
GenericDef::TypeAlias(it) => GenericDefId::TypeAliasId(it.id),
|
||||
GenericDef::ImplDef(it) => GenericDefId::ImplId(it.id),
|
||||
GenericDef::EnumVariant(it) => {
|
||||
GenericDefId::EnumVariantId(EnumVariantId { parent: it.parent.id, local_id: it.id })
|
||||
}
|
||||
GenericDef::Const(it) => GenericDefId::ConstId(it.id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Adt> for GenericDefId {
|
||||
fn from(id: Adt) -> Self {
|
||||
match id {
|
||||
Adt::Struct(it) => it.id.into(),
|
||||
Adt::Union(it) => it.id.into(),
|
||||
Adt::Enum(it) => it.id.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<VariantId> for VariantDef {
|
||||
fn from(def: VariantId) -> Self {
|
||||
match def {
|
||||
VariantId::StructId(it) => VariantDef::Struct(it.into()),
|
||||
VariantId::EnumVariantId(it) => VariantDef::EnumVariant(it.into()),
|
||||
VariantId::UnionId(it) => VariantDef::Union(it.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<VariantDef> for VariantId {
|
||||
fn from(def: VariantDef) -> Self {
|
||||
match def {
|
||||
VariantDef::Struct(it) => VariantId::StructId(it.id),
|
||||
VariantDef::EnumVariant(it) => VariantId::EnumVariantId(it.into()),
|
||||
VariantDef::Union(it) => VariantId::UnionId(it.id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Field> for FieldId {
|
||||
fn from(def: Field) -> Self {
|
||||
FieldId { parent: def.parent.into(), local_id: def.id }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FieldId> for Field {
|
||||
fn from(def: FieldId) -> Self {
|
||||
Field { parent: def.parent.into(), id: def.local_id }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AttrDef> for AttrDefId {
|
||||
fn from(def: AttrDef) -> Self {
|
||||
match def {
|
||||
AttrDef::Module(it) => AttrDefId::ModuleId(it.id),
|
||||
AttrDef::Field(it) => AttrDefId::FieldId(it.into()),
|
||||
AttrDef::Adt(it) => AttrDefId::AdtId(it.into()),
|
||||
AttrDef::Function(it) => AttrDefId::FunctionId(it.id),
|
||||
AttrDef::EnumVariant(it) => AttrDefId::EnumVariantId(it.into()),
|
||||
AttrDef::Static(it) => AttrDefId::StaticId(it.id),
|
||||
AttrDef::Const(it) => AttrDefId::ConstId(it.id),
|
||||
AttrDef::Trait(it) => AttrDefId::TraitId(it.id),
|
||||
AttrDef::TypeAlias(it) => AttrDefId::TypeAliasId(it.id),
|
||||
AttrDef::MacroDef(it) => AttrDefId::MacroDefId(it.id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AssocItem> for GenericDefId {
|
||||
fn from(item: AssocItem) -> Self {
|
||||
match item {
|
||||
AssocItem::Function(f) => f.id.into(),
|
||||
AssocItem::Const(c) => c.id.into(),
|
||||
AssocItem::TypeAlias(t) => t.id.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(DefWithBodyId, PatId)> for Local {
|
||||
fn from((parent, pat_id): (DefWithBodyId, PatId)) -> Self {
|
||||
Local { parent, pat_id }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MacroDef> for ItemInNs {
|
||||
fn from(macro_def: MacroDef) -> Self {
|
||||
ItemInNs::Macros(macro_def.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ModuleDef> for ItemInNs {
|
||||
fn from(module_def: ModuleDef) -> Self {
|
||||
match module_def {
|
||||
ModuleDef::Static(_) | ModuleDef::Const(_) | ModuleDef::Function(_) => {
|
||||
ItemInNs::Values(module_def.into())
|
||||
}
|
||||
_ => ItemInNs::Types(module_def.into()),
|
||||
}
|
||||
}
|
||||
}
|
135
crates/hir/src/has_source.rs
Normal file
135
crates/hir/src/has_source.rs
Normal file
|
@ -0,0 +1,135 @@
|
|||
//! FIXME: write short doc here
|
||||
|
||||
use either::Either;
|
||||
use hir_def::{
|
||||
nameres::{ModuleOrigin, ModuleSource},
|
||||
src::{HasChildSource, HasSource as _},
|
||||
Lookup, VariantId,
|
||||
};
|
||||
use syntax::ast;
|
||||
|
||||
use crate::{
|
||||
db::HirDatabase, Const, Enum, EnumVariant, Field, FieldSource, Function, ImplDef, MacroDef,
|
||||
Module, Static, Struct, Trait, TypeAlias, TypeParam, Union,
|
||||
};
|
||||
|
||||
pub use hir_expand::InFile;
|
||||
|
||||
pub trait HasSource {
|
||||
type Ast;
|
||||
fn source(self, db: &dyn HirDatabase) -> InFile<Self::Ast>;
|
||||
}
|
||||
|
||||
/// NB: Module is !HasSource, because it has two source nodes at the same time:
|
||||
/// definition and declaration.
|
||||
impl Module {
|
||||
/// Returns a node which defines this module. That is, a file or a `mod foo {}` with items.
|
||||
pub fn definition_source(self, db: &dyn HirDatabase) -> InFile<ModuleSource> {
|
||||
let def_map = db.crate_def_map(self.id.krate);
|
||||
def_map[self.id.local_id].definition_source(db.upcast())
|
||||
}
|
||||
|
||||
pub fn is_mod_rs(self, db: &dyn HirDatabase) -> bool {
|
||||
let def_map = db.crate_def_map(self.id.krate);
|
||||
match def_map[self.id.local_id].origin {
|
||||
ModuleOrigin::File { is_mod_rs, .. } => is_mod_rs,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a node which declares this module, either a `mod foo;` or a `mod foo {}`.
|
||||
/// `None` for the crate root.
|
||||
pub fn declaration_source(self, db: &dyn HirDatabase) -> Option<InFile<ast::Module>> {
|
||||
let def_map = db.crate_def_map(self.id.krate);
|
||||
def_map[self.id.local_id].declaration_source(db.upcast())
|
||||
}
|
||||
}
|
||||
|
||||
impl HasSource for Field {
|
||||
type Ast = FieldSource;
|
||||
fn source(self, db: &dyn HirDatabase) -> InFile<FieldSource> {
|
||||
let var = VariantId::from(self.parent);
|
||||
let src = var.child_source(db.upcast());
|
||||
src.map(|it| match it[self.id].clone() {
|
||||
Either::Left(it) => FieldSource::Pos(it),
|
||||
Either::Right(it) => FieldSource::Named(it),
|
||||
})
|
||||
}
|
||||
}
|
||||
impl HasSource for Struct {
|
||||
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::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::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::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::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::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::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::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::TypeAlias;
|
||||
fn source(self, db: &dyn HirDatabase) -> InFile<ast::TypeAlias> {
|
||||
self.id.lookup(db.upcast()).source(db.upcast())
|
||||
}
|
||||
}
|
||||
impl HasSource for MacroDef {
|
||||
type Ast = ast::MacroCall;
|
||||
fn source(self, db: &dyn HirDatabase) -> InFile<ast::MacroCall> {
|
||||
InFile {
|
||||
file_id: self.id.ast_id.expect("MacroDef without ast_id").file_id,
|
||||
value: self.id.ast_id.expect("MacroDef without ast_id").to_node(db.upcast()),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl HasSource for 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::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())
|
||||
}
|
||||
}
|
63
crates/hir/src/lib.rs
Normal file
63
crates/hir/src/lib.rs
Normal file
|
@ -0,0 +1,63 @@
|
|||
//! HIR (previously known as descriptors) provides a high-level object oriented
|
||||
//! access to Rust code.
|
||||
//!
|
||||
//! The principal difference between HIR and syntax trees is that HIR is bound
|
||||
//! to a particular crate instance. That is, it has cfg flags and features
|
||||
//! applied. So, the relation between syntax and HIR is many-to-one.
|
||||
//!
|
||||
//! HIR is the public API of the all of the compiler logic above syntax trees.
|
||||
//! It is written in "OO" style. Each type is self contained (as in, it knows it's
|
||||
//! parents and full context). It should be "clean code".
|
||||
//!
|
||||
//! `hir_*` crates are the implementation of the compiler logic.
|
||||
//! They are written in "ECS" style, with relatively little abstractions.
|
||||
//! Many types are not self-contained, and explicitly use local indexes, arenas, etc.
|
||||
//!
|
||||
//! `hir` is what insulates the "we don't know how to actually write an incremental compiler"
|
||||
//! from the ide with completions, hovers, etc. It is a (soft, internal) boundary:
|
||||
//! https://www.tedinski.com/2018/02/06/system-boundaries.html.
|
||||
|
||||
#![recursion_limit = "512"]
|
||||
|
||||
mod semantics;
|
||||
pub mod db;
|
||||
mod source_analyzer;
|
||||
|
||||
pub mod diagnostics;
|
||||
|
||||
mod from_id;
|
||||
mod code_model;
|
||||
|
||||
mod has_source;
|
||||
|
||||
pub use crate::{
|
||||
code_model::{
|
||||
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},
|
||||
};
|
||||
|
||||
pub use hir_def::{
|
||||
adt::StructKind,
|
||||
attr::Attrs,
|
||||
body::scope::ExprScopes,
|
||||
builtin_type::BuiltinType,
|
||||
docs::Documentation,
|
||||
nameres::ModuleSource,
|
||||
path::{ModPath, Path, PathKind},
|
||||
type_ref::{Mutability, TypeRef},
|
||||
};
|
||||
pub use hir_expand::{
|
||||
name::Name, HirFileId, InFile, MacroCallId, MacroCallLoc, /* FIXME */ MacroDefId,
|
||||
MacroFile, Origin,
|
||||
};
|
||||
pub use hir_ty::display::HirDisplay;
|
||||
|
||||
// These are negative re-exports: pub using these names is forbidden, they
|
||||
// should remain private to hir internals.
|
||||
#[allow(unused)]
|
||||
use hir_expand::hygiene::Hygiene;
|
849
crates/hir/src/semantics.rs
Normal file
849
crates/hir/src/semantics.rs
Normal file
|
@ -0,0 +1,849 @@
|
|||
//! See `Semantics`.
|
||||
|
||||
mod source_to_def;
|
||||
|
||||
use std::{cell::RefCell, fmt, iter::successors};
|
||||
|
||||
use base_db::{FileId, FileRange};
|
||||
use hir_def::{
|
||||
resolver::{self, HasResolver, Resolver},
|
||||
AsMacroCall, FunctionId, TraitId, VariantId,
|
||||
};
|
||||
use hir_expand::{hygiene::Hygiene, name::AsName, ExpansionInfo};
|
||||
use hir_ty::associated_type_shorthand_candidates;
|
||||
use itertools::Itertools;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use syntax::{
|
||||
algo::{find_node_at_offset, skip_trivia_token},
|
||||
ast, AstNode, Direction, SyntaxNode, SyntaxToken, TextRange, TextSize,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
db::HirDatabase,
|
||||
diagnostics::Diagnostic,
|
||||
semantics::source_to_def::{ChildContainer, SourceToDefCache, SourceToDefCtx},
|
||||
source_analyzer::{resolve_hir_path, resolve_hir_path_qualifier, SourceAnalyzer},
|
||||
AssocItem, Callable, Crate, Field, Function, HirFileId, ImplDef, InFile, Local, MacroDef,
|
||||
Module, ModuleDef, Name, Origin, Path, ScopeDef, Trait, Type, TypeAlias, TypeParam, TypeRef,
|
||||
VariantDef,
|
||||
};
|
||||
use resolver::TypeNs;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum PathResolution {
|
||||
/// An item
|
||||
Def(ModuleDef),
|
||||
/// A local binding (only value namespace)
|
||||
Local(Local),
|
||||
/// A generic parameter
|
||||
TypeParam(TypeParam),
|
||||
SelfType(ImplDef),
|
||||
Macro(MacroDef),
|
||||
AssocItem(AssocItem),
|
||||
}
|
||||
|
||||
impl PathResolution {
|
||||
fn in_type_ns(&self) -> Option<TypeNs> {
|
||||
match self {
|
||||
PathResolution::Def(ModuleDef::Adt(adt)) => Some(TypeNs::AdtId((*adt).into())),
|
||||
PathResolution::Def(ModuleDef::BuiltinType(builtin)) => {
|
||||
Some(TypeNs::BuiltinType(*builtin))
|
||||
}
|
||||
PathResolution::Def(ModuleDef::Const(_))
|
||||
| PathResolution::Def(ModuleDef::EnumVariant(_))
|
||||
| PathResolution::Def(ModuleDef::Function(_))
|
||||
| PathResolution::Def(ModuleDef::Module(_))
|
||||
| PathResolution::Def(ModuleDef::Static(_))
|
||||
| PathResolution::Def(ModuleDef::Trait(_)) => None,
|
||||
PathResolution::Def(ModuleDef::TypeAlias(alias)) => {
|
||||
Some(TypeNs::TypeAliasId((*alias).into()))
|
||||
}
|
||||
PathResolution::Local(_) | PathResolution::Macro(_) => None,
|
||||
PathResolution::TypeParam(param) => Some(TypeNs::GenericParam((*param).into())),
|
||||
PathResolution::SelfType(impl_def) => Some(TypeNs::SelfType((*impl_def).into())),
|
||||
PathResolution::AssocItem(AssocItem::Const(_))
|
||||
| PathResolution::AssocItem(AssocItem::Function(_)) => None,
|
||||
PathResolution::AssocItem(AssocItem::TypeAlias(alias)) => {
|
||||
Some(TypeNs::TypeAliasId((*alias).into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator over associated types that may be specified after this path (using
|
||||
/// `Ty::Assoc` syntax).
|
||||
pub fn assoc_type_shorthand_candidates<R>(
|
||||
&self,
|
||||
db: &dyn HirDatabase,
|
||||
mut cb: impl FnMut(TypeAlias) -> Option<R>,
|
||||
) -> Option<R> {
|
||||
associated_type_shorthand_candidates(db, self.in_type_ns()?, |_, _, id| cb(id.into()))
|
||||
}
|
||||
}
|
||||
|
||||
/// 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>>,
|
||||
}
|
||||
|
||||
impl<DB> fmt::Debug for Semantics<'_, DB> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Semantics {{ ... }}")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db, DB: HirDatabase> Semantics<'db, DB> {
|
||||
pub fn new(db: &DB) -> Semantics<DB> {
|
||||
let impl_ = SemanticsImpl::new(db);
|
||||
Semantics { db, imp: impl_ }
|
||||
}
|
||||
|
||||
pub fn parse(&self, file_id: FileId) -> ast::SourceFile {
|
||||
self.imp.parse(file_id)
|
||||
}
|
||||
|
||||
pub fn expand(&self, macro_call: &ast::MacroCall) -> Option<SyntaxNode> {
|
||||
self.imp.expand(macro_call)
|
||||
}
|
||||
pub fn speculative_expand(
|
||||
&self,
|
||||
actual_macro_call: &ast::MacroCall,
|
||||
hypothetical_args: &ast::TokenTree,
|
||||
token_to_map: SyntaxToken,
|
||||
) -> Option<(SyntaxNode, SyntaxToken)> {
|
||||
self.imp.speculative_expand(actual_macro_call, hypothetical_args, token_to_map)
|
||||
}
|
||||
|
||||
pub fn descend_into_macros(&self, token: SyntaxToken) -> SyntaxToken {
|
||||
self.imp.descend_into_macros(token)
|
||||
}
|
||||
|
||||
pub fn descend_node_at_offset<N: ast::AstNode>(
|
||||
&self,
|
||||
node: &SyntaxNode,
|
||||
offset: TextSize,
|
||||
) -> Option<N> {
|
||||
self.imp.descend_node_at_offset(node, offset).find_map(N::cast)
|
||||
}
|
||||
|
||||
pub fn original_range(&self, node: &SyntaxNode) -> FileRange {
|
||||
self.imp.original_range(node)
|
||||
}
|
||||
|
||||
pub fn diagnostics_display_range(&self, diagnostics: &dyn Diagnostic) -> FileRange {
|
||||
self.imp.diagnostics_display_range(diagnostics)
|
||||
}
|
||||
|
||||
pub fn ancestors_with_macros(&self, node: SyntaxNode) -> impl Iterator<Item = SyntaxNode> + '_ {
|
||||
self.imp.ancestors_with_macros(node)
|
||||
}
|
||||
|
||||
pub fn ancestors_at_offset_with_macros(
|
||||
&self,
|
||||
node: &SyntaxNode,
|
||||
offset: TextSize,
|
||||
) -> impl Iterator<Item = SyntaxNode> + '_ {
|
||||
self.imp.ancestors_at_offset_with_macros(node, offset)
|
||||
}
|
||||
|
||||
/// Find a AstNode by offset inside SyntaxNode, if it is inside *Macrofile*,
|
||||
/// search up until it is of the target AstNode type
|
||||
pub fn find_node_at_offset_with_macros<N: AstNode>(
|
||||
&self,
|
||||
node: &SyntaxNode,
|
||||
offset: TextSize,
|
||||
) -> Option<N> {
|
||||
self.imp.ancestors_at_offset_with_macros(node, offset).find_map(N::cast)
|
||||
}
|
||||
|
||||
/// Find a AstNode by offset inside SyntaxNode, if it is inside *MacroCall*,
|
||||
/// descend it and find again
|
||||
pub fn find_node_at_offset_with_descend<N: AstNode>(
|
||||
&self,
|
||||
node: &SyntaxNode,
|
||||
offset: TextSize,
|
||||
) -> Option<N> {
|
||||
if let Some(it) = find_node_at_offset(&node, offset) {
|
||||
return Some(it);
|
||||
}
|
||||
|
||||
self.imp.descend_node_at_offset(node, offset).find_map(N::cast)
|
||||
}
|
||||
|
||||
pub fn type_of_expr(&self, expr: &ast::Expr) -> Option<Type> {
|
||||
self.imp.type_of_expr(expr)
|
||||
}
|
||||
|
||||
pub fn type_of_pat(&self, pat: &ast::Pat) -> Option<Type> {
|
||||
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.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.imp.resolve_field(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::RecordPatField) -> Option<Field> {
|
||||
self.imp.resolve_record_field_pat(field)
|
||||
}
|
||||
|
||||
pub fn resolve_macro_call(&self, macro_call: &ast::MacroCall) -> Option<MacroDef> {
|
||||
self.imp.resolve_macro_call(macro_call)
|
||||
}
|
||||
|
||||
pub fn resolve_path(&self, path: &ast::Path) -> Option<PathResolution> {
|
||||
self.imp.resolve_path(path)
|
||||
}
|
||||
|
||||
pub fn resolve_extern_crate(&self, extern_crate: &ast::ExternCrate) -> Option<Crate> {
|
||||
self.imp.resolve_extern_crate(extern_crate)
|
||||
}
|
||||
|
||||
pub fn resolve_variant(&self, record_lit: ast::RecordExpr) -> Option<VariantDef> {
|
||||
self.imp.resolve_variant(record_lit).map(VariantDef::from)
|
||||
}
|
||||
|
||||
pub fn lower_path(&self, path: &ast::Path) -> Option<Path> {
|
||||
self.imp.lower_path(path)
|
||||
}
|
||||
|
||||
pub fn resolve_bind_pat_to_const(&self, pat: &ast::IdentPat) -> Option<ModuleDef> {
|
||||
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::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)
|
||||
}
|
||||
|
||||
pub fn is_unsafe_method_call(&self, method_call_expr: ast::MethodCallExpr) -> bool {
|
||||
self.imp.is_unsafe_method_call(method_call_expr)
|
||||
}
|
||||
|
||||
pub fn is_unsafe_ref_expr(&self, ref_expr: &ast::RefExpr) -> bool {
|
||||
self.imp.is_unsafe_ref_expr(ref_expr)
|
||||
}
|
||||
|
||||
pub fn is_unsafe_ident_pat(&self, ident_pat: &ast::IdentPat) -> bool {
|
||||
self.imp.is_unsafe_ident_pat(ident_pat)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> SemanticsImpl<'db> {
|
||||
fn new(db: &'db dyn HirDatabase) -> Self {
|
||||
SemanticsImpl {
|
||||
db,
|
||||
s2d_cache: Default::default(),
|
||||
cache: Default::default(),
|
||||
expansion_info_cache: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
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 speculative_expand(
|
||||
&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::span("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(¯o_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_display_range(&self, diagnostics: &dyn Diagnostic) -> FileRange {
|
||||
let src = diagnostics.display_source();
|
||||
let root = self.db.parse_or_expand(src.file_id).unwrap();
|
||||
let node = src.value.to_node(&root);
|
||||
self.cache(root, src.file_id);
|
||||
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, ¶m)
|
||||
}
|
||||
|
||||
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::RecordPatField) -> 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_extern_crate(&self, extern_crate: &ast::ExternCrate) -> Option<Crate> {
|
||||
let krate = self.scope(extern_crate.syntax()).krate()?;
|
||||
krate.dependencies(self.db).into_iter().find_map(|dep| {
|
||||
if dep.name == extern_crate.name_ref()?.as_name() {
|
||||
Some(dep.krate)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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::IdentPat) -> Option<ModuleDef> {
|
||||
self.analyze(pat.syntax()).resolve_bind_pat_to_const(self.db, pat)
|
||||
}
|
||||
|
||||
fn record_literal_missing_fields(&self, literal: &ast::RecordExpr) -> Vec<(Field, Type)> {
|
||||
self.analyze(literal.syntax())
|
||||
.record_literal_missing_fields(self.db, literal)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn record_pattern_missing_fields(&self, pattern: &ast::RecordPat) -> Vec<(Field, Type)> {
|
||||
self.analyze(pattern.syntax())
|
||||
.record_pattern_missing_fields(self.db, pattern)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn with_ctx<F: FnOnce(&mut SourceToDefCtx) -> T, T>(&self, f: F) -> T {
|
||||
let mut cache = self.s2d_cache.borrow_mut();
|
||||
let mut ctx = SourceToDefCtx { db: self.db, cache: &mut *cache };
|
||||
f(&mut ctx)
|
||||
}
|
||||
|
||||
fn to_module_def(&self, file: FileId) -> Option<Module> {
|
||||
self.with_ctx(|ctx| ctx.file_to_def(file)).map(Module::from)
|
||||
}
|
||||
|
||||
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, file_id: node.file_id, resolver }
|
||||
}
|
||||
|
||||
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, file_id: node.file_id, resolver }
|
||||
}
|
||||
|
||||
fn scope_for_def(&self, def: Trait) -> SemanticsScope<'db> {
|
||||
let file_id = self.db.lookup_intern_trait(def.id).id.file_id;
|
||||
let resolver = def.id.resolver(self.db.upcast());
|
||||
SemanticsScope { db: self.db, file_id, resolver }
|
||||
}
|
||||
|
||||
fn analyze(&self, node: &SyntaxNode) -> SourceAnalyzer {
|
||||
let src = self.find_file(node.clone());
|
||||
self.analyze2(src.as_ref(), None)
|
||||
}
|
||||
|
||||
fn analyze2(&self, src: InFile<&SyntaxNode>, offset: Option<TextSize>) -> SourceAnalyzer {
|
||||
let _p = profile::span("Semantics::analyze2");
|
||||
|
||||
let container = match self.with_ctx(|ctx| ctx.find_container(src)) {
|
||||
Some(it) => it,
|
||||
None => return SourceAnalyzer::new_for_resolver(Resolver::default(), src),
|
||||
};
|
||||
|
||||
let resolver = match container {
|
||||
ChildContainer::DefWithBodyId(def) => {
|
||||
return SourceAnalyzer::new_for_body(self.db, def, src, offset)
|
||||
}
|
||||
ChildContainer::TraitId(it) => it.resolver(self.db.upcast()),
|
||||
ChildContainer::ImplId(it) => it.resolver(self.db.upcast()),
|
||||
ChildContainer::ModuleId(it) => it.resolver(self.db.upcast()),
|
||||
ChildContainer::EnumId(it) => it.resolver(self.db.upcast()),
|
||||
ChildContainer::VariantId(it) => it.resolver(self.db.upcast()),
|
||||
ChildContainer::TypeAliasId(it) => it.resolver(self.db.upcast()),
|
||||
ChildContainer::GenericDefId(it) => it.resolver(self.db.upcast()),
|
||||
};
|
||||
SourceAnalyzer::new_for_resolver(resolver, src)
|
||||
}
|
||||
|
||||
fn cache(&self, root_node: SyntaxNode, file_id: HirFileId) {
|
||||
assert!(root_node.parent().is_none());
|
||||
let mut cache = self.cache.borrow_mut();
|
||||
let prev = cache.insert(root_node, file_id);
|
||||
assert!(prev == None || prev == Some(file_id))
|
||||
}
|
||||
|
||||
fn assert_contains_node(&self, node: &SyntaxNode) {
|
||||
self.find_file(node.clone());
|
||||
}
|
||||
|
||||
fn lookup(&self, root_node: &SyntaxNode) -> Option<HirFileId> {
|
||||
let cache = self.cache.borrow();
|
||||
cache.get(root_node).copied()
|
||||
}
|
||||
|
||||
fn find_file(&self, node: SyntaxNode) -> InFile<SyntaxNode> {
|
||||
let root_node = find_root(&node);
|
||||
let file_id = self.lookup(&root_node).unwrap_or_else(|| {
|
||||
panic!(
|
||||
"\n\nFailed to lookup {:?} in this Semantics.\n\
|
||||
Make sure to use only query nodes, derived from this instance of Semantics.\n\
|
||||
root node: {:?}\n\
|
||||
known nodes: {}\n\n",
|
||||
node,
|
||||
root_node,
|
||||
self.cache
|
||||
.borrow()
|
||||
.keys()
|
||||
.map(|it| format!("{:?}", it))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
)
|
||||
});
|
||||
InFile::new(file_id, node)
|
||||
}
|
||||
|
||||
pub fn is_unsafe_method_call(&self, method_call_expr: ast::MethodCallExpr) -> bool {
|
||||
method_call_expr
|
||||
.expr()
|
||||
.and_then(|expr| {
|
||||
let field_expr = if let ast::Expr::FieldExpr(field_expr) = expr {
|
||||
field_expr
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
let ty = self.type_of_expr(&field_expr.expr()?)?;
|
||||
if !ty.is_packed(self.db) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let func = self.resolve_method_call(&method_call_expr).map(Function::from)?;
|
||||
let is_unsafe = func.has_self_param(self.db)
|
||||
&& matches!(func.params(self.db).first(), Some(TypeRef::Reference(..)));
|
||||
Some(is_unsafe)
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn is_unsafe_ref_expr(&self, ref_expr: &ast::RefExpr) -> bool {
|
||||
ref_expr
|
||||
.expr()
|
||||
.and_then(|expr| {
|
||||
let field_expr = match expr {
|
||||
ast::Expr::FieldExpr(field_expr) => field_expr,
|
||||
_ => return None,
|
||||
};
|
||||
let expr = field_expr.expr()?;
|
||||
self.type_of_expr(&expr)
|
||||
})
|
||||
// Binding a reference to a packed type is possibly unsafe.
|
||||
.map(|ty| ty.is_packed(self.db))
|
||||
.unwrap_or(false)
|
||||
|
||||
// FIXME This needs layout computation to be correct. It will highlight
|
||||
// more than it should with the current implementation.
|
||||
}
|
||||
|
||||
pub fn is_unsafe_ident_pat(&self, ident_pat: &ast::IdentPat) -> bool {
|
||||
if !ident_pat.ref_token().is_some() {
|
||||
return false;
|
||||
}
|
||||
|
||||
ident_pat
|
||||
.syntax()
|
||||
.parent()
|
||||
.and_then(|parent| {
|
||||
// `IdentPat` can live under `RecordPat` directly under `RecordPatField` or
|
||||
// `RecordPatFieldList`. `RecordPatField` also lives under `RecordPatFieldList`,
|
||||
// so this tries to lookup the `IdentPat` anywhere along that structure to the
|
||||
// `RecordPat` so we can get the containing type.
|
||||
let record_pat = ast::RecordPatField::cast(parent.clone())
|
||||
.and_then(|record_pat| record_pat.syntax().parent())
|
||||
.or_else(|| Some(parent.clone()))
|
||||
.and_then(|parent| {
|
||||
ast::RecordPatFieldList::cast(parent)?
|
||||
.syntax()
|
||||
.parent()
|
||||
.and_then(ast::RecordPat::cast)
|
||||
});
|
||||
|
||||
// If this doesn't match a `RecordPat`, fallback to a `LetStmt` to see if
|
||||
// this is initialized from a `FieldExpr`.
|
||||
if let Some(record_pat) = record_pat {
|
||||
self.type_of_pat(&ast::Pat::RecordPat(record_pat))
|
||||
} else if let Some(let_stmt) = ast::LetStmt::cast(parent) {
|
||||
let field_expr = match let_stmt.initializer()? {
|
||||
ast::Expr::FieldExpr(field_expr) => field_expr,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
self.type_of_expr(&field_expr.expr()?)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
// Binding a reference to a packed type is possibly unsafe.
|
||||
.map(|ty| ty.is_packed(self.db))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ToDef: AstNode + Clone {
|
||||
type Def;
|
||||
|
||||
fn to_def(sema: &SemanticsImpl, src: InFile<Self>) -> Option<Self::Def>;
|
||||
}
|
||||
|
||||
macro_rules! to_def_impls {
|
||||
($(($def:path, $ast:path, $meth:ident)),* ,) => {$(
|
||||
impl ToDef for $ast {
|
||||
type Def = $def;
|
||||
fn to_def(sema: &SemanticsImpl, src: InFile<Self>) -> Option<Self::Def> {
|
||||
sema.with_ctx(|ctx| ctx.$meth(src)).map(<$def>::from)
|
||||
}
|
||||
}
|
||||
)*}
|
||||
}
|
||||
|
||||
to_def_impls![
|
||||
(crate::Module, ast::Module, module_to_def),
|
||||
(crate::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::IdentPat, bind_pat_to_def),
|
||||
];
|
||||
|
||||
fn find_root(node: &SyntaxNode) -> SyntaxNode {
|
||||
node.ancestors().last().unwrap()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SemanticsScope<'a> {
|
||||
pub db: &'a dyn HirDatabase,
|
||||
file_id: HirFileId,
|
||||
resolver: Resolver,
|
||||
}
|
||||
|
||||
impl<'a> SemanticsScope<'a> {
|
||||
pub fn module(&self) -> Option<Module> {
|
||||
Some(Module { id: self.resolver.module()? })
|
||||
}
|
||||
|
||||
pub fn krate(&self) -> Option<Crate> {
|
||||
Some(Crate { id: self.resolver.krate()? })
|
||||
}
|
||||
|
||||
/// Note: `FxHashSet<TraitId>` should be treated as an opaque type, passed into `Type
|
||||
// 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.upcast())
|
||||
}
|
||||
|
||||
pub fn process_all_names(&self, f: &mut dyn FnMut(Name, ScopeDef)) {
|
||||
let resolver = &self.resolver;
|
||||
|
||||
resolver.process_all_names(self.db.upcast(), &mut |name, def| {
|
||||
let def = match def {
|
||||
resolver::ScopeDef::PerNs(it) => {
|
||||
let items = ScopeDef::all_items(it);
|
||||
for item in items {
|
||||
f(name.clone(), item);
|
||||
}
|
||||
return;
|
||||
}
|
||||
resolver::ScopeDef::ImplSelfType(it) => ScopeDef::ImplSelfType(it.into()),
|
||||
resolver::ScopeDef::AdtSelfType(it) => ScopeDef::AdtSelfType(it.into()),
|
||||
resolver::ScopeDef::GenericParam(id) => ScopeDef::GenericParam(TypeParam { id }),
|
||||
resolver::ScopeDef::Local(pat_id) => {
|
||||
let parent = resolver.body_owner().unwrap().into();
|
||||
ScopeDef::Local(Local { parent, pat_id })
|
||||
}
|
||||
};
|
||||
f(name, def)
|
||||
})
|
||||
}
|
||||
|
||||
/// Resolve a path as-if it was written at the given scope. This is
|
||||
/// necessary a heuristic, as it doesn't take hygiene into account.
|
||||
pub fn speculative_resolve(&self, path: &ast::Path) -> Option<PathResolution> {
|
||||
let hygiene = Hygiene::new(self.db.upcast(), self.file_id);
|
||||
let path = Path::from_src(path.clone(), &hygiene)?;
|
||||
self.resolve_hir_path(&path)
|
||||
}
|
||||
|
||||
pub fn resolve_hir_path(&self, path: &Path) -> Option<PathResolution> {
|
||||
resolve_hir_path(self.db, &self.resolver, path)
|
||||
}
|
||||
|
||||
/// Resolves a path where we know it is a qualifier of another path.
|
||||
///
|
||||
/// For example, if we have:
|
||||
/// ```
|
||||
/// mod my {
|
||||
/// pub mod foo {
|
||||
/// struct Bar;
|
||||
/// }
|
||||
///
|
||||
/// pub fn foo() {}
|
||||
/// }
|
||||
/// ```
|
||||
/// then we know that `foo` in `my::foo::Bar` refers to the module, not the function.
|
||||
pub fn resolve_hir_path_qualifier(&self, path: &Path) -> Option<PathResolution> {
|
||||
resolve_hir_path_qualifier(self.db, &self.resolver, path)
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Change `HasSource` trait to work with `Semantics` and remove this?
|
||||
pub fn original_range(db: &dyn HirDatabase, node: InFile<&SyntaxNode>) -> FileRange {
|
||||
if let Some(range) = original_range_opt(db, node) {
|
||||
let original_file = range.file_id.original_file(db.upcast());
|
||||
if range.file_id == original_file.into() {
|
||||
return FileRange { file_id: original_file, range: range.value };
|
||||
}
|
||||
|
||||
log::error!("Fail to mapping up more for {:?}", range);
|
||||
return FileRange { file_id: range.file_id.original_file(db.upcast()), range: range.value };
|
||||
}
|
||||
|
||||
// Fall back to whole macro call
|
||||
if let Some(expansion) = node.file_id.expansion_info(db.upcast()) {
|
||||
if let Some(call_node) = expansion.call_node() {
|
||||
return FileRange {
|
||||
file_id: call_node.file_id.original_file(db.upcast()),
|
||||
range: call_node.value.text_range(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
FileRange { file_id: node.file_id.original_file(db.upcast()), range: node.value.text_range() }
|
||||
}
|
||||
|
||||
fn original_range_opt(
|
||||
db: &dyn HirDatabase,
|
||||
node: InFile<&SyntaxNode>,
|
||||
) -> Option<InFile<TextRange>> {
|
||||
let expansion = node.file_id.expansion_info(db.upcast())?;
|
||||
|
||||
// the input node has only one token ?
|
||||
let single = skip_trivia_token(node.value.first_token()?, Direction::Next)?
|
||||
== skip_trivia_token(node.value.last_token()?, Direction::Prev)?;
|
||||
|
||||
Some(node.value.descendants().find_map(|it| {
|
||||
let first = skip_trivia_token(it.first_token()?, Direction::Next)?;
|
||||
let first = ascend_call_token(db, &expansion, node.with_value(first))?;
|
||||
|
||||
let last = skip_trivia_token(it.last_token()?, Direction::Prev)?;
|
||||
let last = ascend_call_token(db, &expansion, node.with_value(last))?;
|
||||
|
||||
if (!single && first == last) || (first.file_id != last.file_id) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(first.with_value(first.value.text_range().cover(last.value.text_range())))
|
||||
})?)
|
||||
}
|
||||
|
||||
fn ascend_call_token(
|
||||
db: &dyn HirDatabase,
|
||||
expansion: &ExpansionInfo,
|
||||
token: InFile<SyntaxToken>,
|
||||
) -> Option<InFile<SyntaxToken>> {
|
||||
let (mapped, origin) = expansion.map_token_up(token.as_ref())?;
|
||||
if origin != Origin::Call {
|
||||
return None;
|
||||
}
|
||||
if let Some(info) = mapped.file_id.expansion_info(db.upcast()) {
|
||||
return ascend_call_token(db, &info, mapped);
|
||||
}
|
||||
Some(mapped)
|
||||
}
|
275
crates/hir/src/semantics/source_to_def.rs
Normal file
275
crates/hir/src/semantics/source_to_def.rs
Normal file
|
@ -0,0 +1,275 @@
|
|||
//! Maps *syntax* of various definitions to their semantic ids.
|
||||
|
||||
use base_db::FileId;
|
||||
use hir_def::{
|
||||
child_by_source::ChildBySource,
|
||||
dyn_map::DynMap,
|
||||
expr::PatId,
|
||||
keys::{self, Key},
|
||||
ConstId, DefWithBodyId, EnumId, EnumVariantId, FieldId, FunctionId, GenericDefId, ImplId,
|
||||
ModuleId, StaticId, StructId, TraitId, TypeAliasId, TypeParamId, UnionId, VariantId,
|
||||
};
|
||||
use hir_expand::{name::AsName, AstId, MacroDefKind};
|
||||
use rustc_hash::FxHashMap;
|
||||
use stdx::impl_from;
|
||||
use syntax::{
|
||||
ast::{self, NameOwner},
|
||||
match_ast, AstNode, SyntaxNode,
|
||||
};
|
||||
|
||||
use crate::{db::HirDatabase, InFile, MacroDefId};
|
||||
|
||||
pub(super) type SourceToDefCache = FxHashMap<ChildContainer, DynMap>;
|
||||
|
||||
pub(super) struct SourceToDefCtx<'a, 'b> {
|
||||
pub(super) db: &'b dyn HirDatabase,
|
||||
pub(super) cache: &'a mut SourceToDefCache,
|
||||
}
|
||||
|
||||
impl SourceToDefCtx<'_, '_> {
|
||||
pub(super) fn file_to_def(&mut self, file: FileId) -> Option<ModuleId> {
|
||||
let _p = profile::span("SourceBinder::to_module_def");
|
||||
let (krate, local_id) = self.db.relevant_crates(file).iter().find_map(|&crate_id| {
|
||||
let crate_def_map = self.db.crate_def_map(crate_id);
|
||||
let local_id = crate_def_map.modules_for_file(file).next()?;
|
||||
Some((crate_id, local_id))
|
||||
})?;
|
||||
Some(ModuleId { krate, local_id })
|
||||
}
|
||||
|
||||
pub(super) fn module_to_def(&mut self, src: InFile<ast::Module>) -> Option<ModuleId> {
|
||||
let _p = profile::span("module_to_def");
|
||||
let parent_declaration = src
|
||||
.as_ref()
|
||||
.map(|it| it.syntax())
|
||||
.cloned()
|
||||
.ancestors_with_macros(self.db.upcast())
|
||||
.skip(1)
|
||||
.find_map(|it| {
|
||||
let m = ast::Module::cast(it.value.clone())?;
|
||||
Some(it.with_value(m))
|
||||
});
|
||||
|
||||
let parent_module = match parent_declaration {
|
||||
Some(parent_declaration) => self.module_to_def(parent_declaration),
|
||||
None => {
|
||||
let file_id = src.file_id.original_file(self.db.upcast());
|
||||
self.file_to_def(file_id)
|
||||
}
|
||||
}?;
|
||||
|
||||
let child_name = src.value.name()?.as_name();
|
||||
let def_map = self.db.crate_def_map(parent_module.krate);
|
||||
let child_id = *def_map[parent_module.local_id].children.get(&child_name)?;
|
||||
Some(ModuleId { krate: parent_module.krate, local_id: child_id })
|
||||
}
|
||||
|
||||
pub(super) fn trait_to_def(&mut self, src: InFile<ast::Trait>) -> Option<TraitId> {
|
||||
self.to_def(src, keys::TRAIT)
|
||||
}
|
||||
pub(super) fn impl_to_def(&mut self, src: InFile<ast::Impl>) -> Option<ImplId> {
|
||||
self.to_def(src, keys::IMPL)
|
||||
}
|
||||
pub(super) fn fn_to_def(&mut self, src: InFile<ast::Fn>) -> Option<FunctionId> {
|
||||
self.to_def(src, keys::FUNCTION)
|
||||
}
|
||||
pub(super) fn struct_to_def(&mut self, src: InFile<ast::Struct>) -> Option<StructId> {
|
||||
self.to_def(src, keys::STRUCT)
|
||||
}
|
||||
pub(super) fn enum_to_def(&mut self, src: InFile<ast::Enum>) -> Option<EnumId> {
|
||||
self.to_def(src, keys::ENUM)
|
||||
}
|
||||
pub(super) fn union_to_def(&mut self, src: InFile<ast::Union>) -> Option<UnionId> {
|
||||
self.to_def(src, keys::UNION)
|
||||
}
|
||||
pub(super) fn static_to_def(&mut self, src: InFile<ast::Static>) -> Option<StaticId> {
|
||||
self.to_def(src, keys::STATIC)
|
||||
}
|
||||
pub(super) fn const_to_def(&mut self, src: InFile<ast::Const>) -> Option<ConstId> {
|
||||
self.to_def(src, keys::CONST)
|
||||
}
|
||||
pub(super) fn type_alias_to_def(&mut self, src: InFile<ast::TypeAlias>) -> Option<TypeAliasId> {
|
||||
self.to_def(src, keys::TYPE_ALIAS)
|
||||
}
|
||||
pub(super) fn record_field_to_def(&mut self, src: InFile<ast::RecordField>) -> Option<FieldId> {
|
||||
self.to_def(src, keys::RECORD_FIELD)
|
||||
}
|
||||
pub(super) fn tuple_field_to_def(&mut self, src: InFile<ast::TupleField>) -> Option<FieldId> {
|
||||
self.to_def(src, keys::TUPLE_FIELD)
|
||||
}
|
||||
pub(super) fn enum_variant_to_def(
|
||||
&mut self,
|
||||
src: InFile<ast::Variant>,
|
||||
) -> Option<EnumVariantId> {
|
||||
self.to_def(src, keys::VARIANT)
|
||||
}
|
||||
pub(super) fn bind_pat_to_def(
|
||||
&mut self,
|
||||
src: InFile<ast::IdentPat>,
|
||||
) -> Option<(DefWithBodyId, PatId)> {
|
||||
let container = self.find_pat_container(src.as_ref().map(|it| it.syntax()))?;
|
||||
let (_body, source_map) = self.db.body_with_source_map(container);
|
||||
let src = src.map(ast::Pat::from);
|
||||
let pat_id = source_map.node_pat(src.as_ref())?;
|
||||
Some((container, pat_id))
|
||||
}
|
||||
|
||||
fn to_def<Ast: AstNode + 'static, ID: Copy + 'static>(
|
||||
&mut self,
|
||||
src: InFile<Ast>,
|
||||
key: Key<Ast, ID>,
|
||||
) -> Option<ID> {
|
||||
let container = self.find_container(src.as_ref().map(|it| it.syntax()))?;
|
||||
let db = self.db;
|
||||
let dyn_map =
|
||||
&*self.cache.entry(container).or_insert_with(|| container.child_by_source(db));
|
||||
dyn_map[key].get(&src).copied()
|
||||
}
|
||||
|
||||
pub(super) fn type_param_to_def(&mut self, src: InFile<ast::TypeParam>) -> Option<TypeParamId> {
|
||||
let container: ChildContainer =
|
||||
self.find_type_param_container(src.as_ref().map(|it| it.syntax()))?.into();
|
||||
let db = self.db;
|
||||
let dyn_map =
|
||||
&*self.cache.entry(container).or_insert_with(|| container.child_by_source(db));
|
||||
dyn_map[keys::TYPE_PARAM].get(&src).copied()
|
||||
}
|
||||
|
||||
// FIXME: use DynMap as well?
|
||||
pub(super) fn macro_call_to_def(&mut self, src: InFile<ast::MacroCall>) -> Option<MacroDefId> {
|
||||
let kind = MacroDefKind::Declarative;
|
||||
let file_id = src.file_id.original_file(self.db.upcast());
|
||||
let krate = self.file_to_def(file_id)?.krate;
|
||||
let file_ast_id = self.db.ast_id_map(src.file_id).ast_id(&src.value);
|
||||
let ast_id = Some(AstId::new(src.file_id, file_ast_id));
|
||||
Some(MacroDefId { krate: Some(krate), ast_id, kind, local_inner: false })
|
||||
}
|
||||
|
||||
pub(super) fn find_container(&mut self, src: InFile<&SyntaxNode>) -> Option<ChildContainer> {
|
||||
for container in src.cloned().ancestors_with_macros(self.db.upcast()).skip(1) {
|
||||
let res: ChildContainer = match_ast! {
|
||||
match (container.value) {
|
||||
ast::Module(it) => {
|
||||
let def = self.module_to_def(container.with_value(it))?;
|
||||
def.into()
|
||||
},
|
||||
ast::Trait(it) => {
|
||||
let def = self.trait_to_def(container.with_value(it))?;
|
||||
def.into()
|
||||
},
|
||||
ast::Impl(it) => {
|
||||
let def = self.impl_to_def(container.with_value(it))?;
|
||||
def.into()
|
||||
},
|
||||
ast::Fn(it) => {
|
||||
let def = self.fn_to_def(container.with_value(it))?;
|
||||
DefWithBodyId::from(def).into()
|
||||
},
|
||||
ast::Struct(it) => {
|
||||
let def = self.struct_to_def(container.with_value(it))?;
|
||||
VariantId::from(def).into()
|
||||
},
|
||||
ast::Enum(it) => {
|
||||
let def = self.enum_to_def(container.with_value(it))?;
|
||||
def.into()
|
||||
},
|
||||
ast::Union(it) => {
|
||||
let def = self.union_to_def(container.with_value(it))?;
|
||||
VariantId::from(def).into()
|
||||
},
|
||||
ast::Static(it) => {
|
||||
let def = self.static_to_def(container.with_value(it))?;
|
||||
DefWithBodyId::from(def).into()
|
||||
},
|
||||
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,
|
||||
}
|
||||
};
|
||||
return Some(res);
|
||||
}
|
||||
|
||||
let def = self.file_to_def(src.file_id.original_file(self.db.upcast()))?;
|
||||
Some(def.into())
|
||||
}
|
||||
|
||||
fn find_type_param_container(&mut self, src: InFile<&SyntaxNode>) -> Option<GenericDefId> {
|
||||
for container in src.cloned().ancestors_with_macros(self.db.upcast()).skip(1) {
|
||||
let res: GenericDefId = match_ast! {
|
||||
match (container.value) {
|
||||
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,
|
||||
}
|
||||
};
|
||||
return Some(res);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn find_pat_container(&mut self, src: InFile<&SyntaxNode>) -> Option<DefWithBodyId> {
|
||||
for container in src.cloned().ancestors_with_macros(self.db.upcast()).skip(1) {
|
||||
let res: DefWithBodyId = match_ast! {
|
||||
match (container.value) {
|
||||
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,
|
||||
}
|
||||
};
|
||||
return Some(res);
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||
pub(crate) enum ChildContainer {
|
||||
DefWithBodyId(DefWithBodyId),
|
||||
ModuleId(ModuleId),
|
||||
TraitId(TraitId),
|
||||
ImplId(ImplId),
|
||||
EnumId(EnumId),
|
||||
VariantId(VariantId),
|
||||
TypeAliasId(TypeAliasId),
|
||||
/// XXX: this might be the same def as, for example an `EnumId`. However,
|
||||
/// here the children generic parameters, and not, eg enum variants.
|
||||
GenericDefId(GenericDefId),
|
||||
}
|
||||
impl_from! {
|
||||
DefWithBodyId,
|
||||
ModuleId,
|
||||
TraitId,
|
||||
ImplId,
|
||||
EnumId,
|
||||
VariantId,
|
||||
TypeAliasId,
|
||||
GenericDefId
|
||||
for ChildContainer
|
||||
}
|
||||
|
||||
impl ChildContainer {
|
||||
fn child_by_source(self, db: &dyn HirDatabase) -> DynMap {
|
||||
let db = db.upcast();
|
||||
match self {
|
||||
ChildContainer::DefWithBodyId(it) => it.child_by_source(db),
|
||||
ChildContainer::ModuleId(it) => it.child_by_source(db),
|
||||
ChildContainer::TraitId(it) => it.child_by_source(db),
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
534
crates/hir/src/source_analyzer.rs
Normal file
534
crates/hir/src/source_analyzer.rs
Normal file
|
@ -0,0 +1,534 @@
|
|||
//! Lookup hir elements using positions in the source code. This is a lossy
|
||||
//! transformation: in general, a single source might correspond to several
|
||||
//! modules, functions, etc, due to macros, cfgs and `#[path=]` attributes on
|
||||
//! modules.
|
||||
//!
|
||||
//! So, this modules should not be used during hir construction, it exists
|
||||
//! purely for "IDE needs".
|
||||
use std::{iter::once, sync::Arc};
|
||||
|
||||
use hir_def::{
|
||||
body::{
|
||||
scope::{ExprScopes, ScopeId},
|
||||
Body, BodySourceMap,
|
||||
},
|
||||
expr::{ExprId, Pat, PatId},
|
||||
resolver::{resolver_for_scope, Resolver, TypeNs, ValueNs},
|
||||
AsMacroCall, DefWithBodyId, FieldId, FunctionId, LocalFieldId, VariantId,
|
||||
};
|
||||
use hir_expand::{hygiene::Hygiene, name::AsName, HirFileId, InFile};
|
||||
use hir_ty::{
|
||||
diagnostics::{record_literal_missing_fields, record_pattern_missing_fields},
|
||||
InferenceResult, Substs, Ty,
|
||||
};
|
||||
use syntax::{
|
||||
ast::{self, AstNode},
|
||||
SyntaxNode, TextRange, TextSize,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
db::HirDatabase, semantics::PathResolution, Adt, Const, EnumVariant, Field, Function, Local,
|
||||
MacroDef, ModPath, ModuleDef, Path, PathKind, Static, Struct, Trait, Type, TypeAlias,
|
||||
TypeParam,
|
||||
};
|
||||
use base_db::CrateId;
|
||||
|
||||
/// `SourceAnalyzer` is a convenience wrapper which exposes HIR API in terms of
|
||||
/// original source files. It should not be used inside the HIR itself.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct SourceAnalyzer {
|
||||
file_id: HirFileId,
|
||||
pub(crate) resolver: Resolver,
|
||||
body: Option<Arc<Body>>,
|
||||
body_source_map: Option<Arc<BodySourceMap>>,
|
||||
infer: Option<Arc<InferenceResult>>,
|
||||
scopes: Option<Arc<ExprScopes>>,
|
||||
}
|
||||
|
||||
impl SourceAnalyzer {
|
||||
pub(crate) fn new_for_body(
|
||||
db: &dyn HirDatabase,
|
||||
def: DefWithBodyId,
|
||||
node: InFile<&SyntaxNode>,
|
||||
offset: Option<TextSize>,
|
||||
) -> SourceAnalyzer {
|
||||
let (body, source_map) = db.body_with_source_map(def);
|
||||
let scopes = db.expr_scopes(def);
|
||||
let scope = match offset {
|
||||
None => scope_for(&scopes, &source_map, node),
|
||||
Some(offset) => scope_for_offset(db, &scopes, &source_map, node.with_value(offset)),
|
||||
};
|
||||
let resolver = resolver_for_scope(db.upcast(), def, scope);
|
||||
SourceAnalyzer {
|
||||
resolver,
|
||||
body: Some(body),
|
||||
body_source_map: Some(source_map),
|
||||
infer: Some(db.infer(def)),
|
||||
scopes: Some(scopes),
|
||||
file_id: node.file_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new_for_resolver(
|
||||
resolver: Resolver,
|
||||
node: InFile<&SyntaxNode>,
|
||||
) -> SourceAnalyzer {
|
||||
SourceAnalyzer {
|
||||
resolver,
|
||||
body: None,
|
||||
body_source_map: None,
|
||||
infer: None,
|
||||
scopes: None,
|
||||
file_id: node.file_id,
|
||||
}
|
||||
}
|
||||
|
||||
fn expr_id(&self, db: &dyn HirDatabase, expr: &ast::Expr) -> Option<ExprId> {
|
||||
let src = match expr {
|
||||
ast::Expr::MacroCall(call) => {
|
||||
self.expand_expr(db, InFile::new(self.file_id, call.clone()))?
|
||||
}
|
||||
_ => InFile::new(self.file_id, expr.clone()),
|
||||
};
|
||||
let sm = self.body_source_map.as_ref()?;
|
||||
sm.node_expr(src.as_ref())
|
||||
}
|
||||
|
||||
fn pat_id(&self, pat: &ast::Pat) -> Option<PatId> {
|
||||
// FIXME: macros, see `expr_id`
|
||||
let src = InFile { file_id: self.file_id, value: pat };
|
||||
self.body_source_map.as_ref()?.node_pat(src)
|
||||
}
|
||||
|
||||
fn expand_expr(
|
||||
&self,
|
||||
db: &dyn HirDatabase,
|
||||
expr: InFile<ast::MacroCall>,
|
||||
) -> Option<InFile<ast::Expr>> {
|
||||
let macro_file = self.body_source_map.as_ref()?.node_macro_file(expr.as_ref())?;
|
||||
let expanded = db.parse_or_expand(macro_file)?;
|
||||
|
||||
let res = match ast::MacroCall::cast(expanded.clone()) {
|
||||
Some(call) => self.expand_expr(db, InFile::new(macro_file, call))?,
|
||||
_ => InFile::new(macro_file, ast::Expr::cast(expanded)?),
|
||||
};
|
||||
Some(res)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
pub(crate) fn type_of_pat(&self, db: &dyn HirDatabase, pat: &ast::Pat) -> Option<Type> {
|
||||
let pat_id = self.pat_id(pat)?;
|
||||
let ty = self.infer.as_ref()?[pat_id].clone();
|
||||
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<FunctionId> {
|
||||
let expr_id = self.expr_id(db, &call.clone().into())?;
|
||||
self.infer.as_ref()?.method_resolution(expr_id)
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_field(
|
||||
&self,
|
||||
db: &dyn HirDatabase,
|
||||
field: &ast::FieldExpr,
|
||||
) -> Option<Field> {
|
||||
let expr_id = self.expr_id(db, &field.clone().into())?;
|
||||
self.infer.as_ref()?.field_resolution(expr_id).map(|it| it.into())
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_record_field(
|
||||
&self,
|
||||
db: &dyn HirDatabase,
|
||||
field: &ast::RecordExprField,
|
||||
) -> Option<(Field, Option<Local>)> {
|
||||
let expr = field.expr()?;
|
||||
let expr_id = self.expr_id(db, &expr)?;
|
||||
let local = if field.name_ref().is_some() {
|
||||
None
|
||||
} else {
|
||||
let local_name = field.field_name()?.as_name();
|
||||
let path = ModPath::from_segments(PathKind::Plain, once(local_name));
|
||||
match self.resolver.resolve_path_in_value_ns_fully(db.upcast(), &path) {
|
||||
Some(ValueNs::LocalBinding(pat_id)) => {
|
||||
Some(Local { pat_id, parent: self.resolver.body_owner()? })
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
};
|
||||
let struct_field = self.infer.as_ref()?.record_field_resolution(expr_id)?;
|
||||
Some((struct_field.into(), local))
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_record_field_pat(
|
||||
&self,
|
||||
_db: &dyn HirDatabase,
|
||||
field: &ast::RecordPatField,
|
||||
) -> Option<Field> {
|
||||
let pat_id = self.pat_id(&field.pat()?)?;
|
||||
let struct_field = self.infer.as_ref()?.record_field_pat_resolution(pat_id)?;
|
||||
Some(struct_field.into())
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_macro_call(
|
||||
&self,
|
||||
db: &dyn HirDatabase,
|
||||
macro_call: InFile<&ast::MacroCall>,
|
||||
) -> Option<MacroDef> {
|
||||
let hygiene = Hygiene::new(db.upcast(), macro_call.file_id);
|
||||
let path = macro_call.value.path().and_then(|ast| Path::from_src(ast, &hygiene))?;
|
||||
self.resolver.resolve_path_as_macro(db.upcast(), path.mod_path()).map(|it| it.into())
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_bind_pat_to_const(
|
||||
&self,
|
||||
db: &dyn HirDatabase,
|
||||
pat: &ast::IdentPat,
|
||||
) -> Option<ModuleDef> {
|
||||
let pat_id = self.pat_id(&pat.clone().into())?;
|
||||
let body = self.body.as_ref()?;
|
||||
let path = match &body[pat_id] {
|
||||
Pat::Path(path) => path,
|
||||
_ => return None,
|
||||
};
|
||||
let res = resolve_hir_path(db, &self.resolver, &path)?;
|
||||
match res {
|
||||
PathResolution::Def(def) => Some(def),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_path(
|
||||
&self,
|
||||
db: &dyn HirDatabase,
|
||||
path: &ast::Path,
|
||||
) -> Option<PathResolution> {
|
||||
if let Some(path_expr) = path.syntax().parent().and_then(ast::PathExpr::cast) {
|
||||
let expr_id = self.expr_id(db, &path_expr.into())?;
|
||||
if let Some(assoc) = self.infer.as_ref()?.assoc_resolutions_for_expr(expr_id) {
|
||||
return Some(PathResolution::AssocItem(assoc.into()));
|
||||
}
|
||||
if let Some(VariantId::EnumVariantId(variant)) =
|
||||
self.infer.as_ref()?.variant_resolution_for_expr(expr_id)
|
||||
{
|
||||
return Some(PathResolution::Def(ModuleDef::EnumVariant(variant.into())));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(path_pat) = path.syntax().parent().and_then(ast::PathPat::cast) {
|
||||
let pat_id = self.pat_id(&path_pat.into())?;
|
||||
if let Some(assoc) = self.infer.as_ref()?.assoc_resolutions_for_pat(pat_id) {
|
||||
return Some(PathResolution::AssocItem(assoc.into()));
|
||||
}
|
||||
if let Some(VariantId::EnumVariantId(variant)) =
|
||||
self.infer.as_ref()?.variant_resolution_for_pat(pat_id)
|
||||
{
|
||||
return Some(PathResolution::Def(ModuleDef::EnumVariant(variant.into())));
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
return Some(PathResolution::Def(ModuleDef::EnumVariant(variant.into())));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(rec_pat) = path.syntax().parent().and_then(ast::RecordPat::cast) {
|
||||
let pat_id = self.pat_id(&rec_pat.into())?;
|
||||
if let Some(VariantId::EnumVariantId(variant)) =
|
||||
self.infer.as_ref()?.variant_resolution_for_pat(pat_id)
|
||||
{
|
||||
return Some(PathResolution::Def(ModuleDef::EnumVariant(variant.into())));
|
||||
}
|
||||
}
|
||||
|
||||
// This must be a normal source file rather than macro file.
|
||||
let hir_path = Path::from_src(path.clone(), &Hygiene::new(db.upcast(), self.file_id))?;
|
||||
|
||||
// Case where path is a qualifier of another path, e.g. foo::bar::Baz where we
|
||||
// trying to resolve foo::bar.
|
||||
if let Some(outer_path) = path.syntax().parent().and_then(ast::Path::cast) {
|
||||
if let Some(qualifier) = outer_path.qualifier() {
|
||||
if path == &qualifier {
|
||||
return resolve_hir_path_qualifier(db, &self.resolver, &hir_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resolve_hir_path(db, &self.resolver, &hir_path)
|
||||
}
|
||||
|
||||
pub(crate) fn record_literal_missing_fields(
|
||||
&self,
|
||||
db: &dyn HirDatabase,
|
||||
literal: &ast::RecordExpr,
|
||||
) -> Option<Vec<(Field, Type)>> {
|
||||
let krate = self.resolver.krate()?;
|
||||
let body = self.body.as_ref()?;
|
||||
let infer = self.infer.as_ref()?;
|
||||
|
||||
let expr_id = self.expr_id(db, &literal.clone().into())?;
|
||||
let substs = match &infer.type_of_expr[expr_id] {
|
||||
Ty::Apply(a_ty) => &a_ty.parameters,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let (variant, missing_fields, _exhaustive) =
|
||||
record_literal_missing_fields(db, infer, expr_id, &body[expr_id])?;
|
||||
let res = self.missing_fields(db, krate, substs, variant, missing_fields);
|
||||
Some(res)
|
||||
}
|
||||
|
||||
pub(crate) fn record_pattern_missing_fields(
|
||||
&self,
|
||||
db: &dyn HirDatabase,
|
||||
pattern: &ast::RecordPat,
|
||||
) -> Option<Vec<(Field, Type)>> {
|
||||
let krate = self.resolver.krate()?;
|
||||
let body = self.body.as_ref()?;
|
||||
let infer = self.infer.as_ref()?;
|
||||
|
||||
let pat_id = self.pat_id(&pattern.clone().into())?;
|
||||
let substs = match &infer.type_of_pat[pat_id] {
|
||||
Ty::Apply(a_ty) => &a_ty.parameters,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let (variant, missing_fields, _exhaustive) =
|
||||
record_pattern_missing_fields(db, infer, pat_id, &body[pat_id])?;
|
||||
let res = self.missing_fields(db, krate, substs, variant, missing_fields);
|
||||
Some(res)
|
||||
}
|
||||
|
||||
fn missing_fields(
|
||||
&self,
|
||||
db: &dyn HirDatabase,
|
||||
krate: CrateId,
|
||||
substs: &Substs,
|
||||
variant: VariantId,
|
||||
missing_fields: Vec<LocalFieldId>,
|
||||
) -> Vec<(Field, Type)> {
|
||||
let field_types = db.field_types(variant);
|
||||
|
||||
missing_fields
|
||||
.into_iter()
|
||||
.map(|local_id| {
|
||||
let field = FieldId { parent: variant, local_id };
|
||||
let ty = field_types[local_id].clone().subst(substs);
|
||||
(field.into(), Type::new_with_resolver_inner(db, krate, &self.resolver, ty))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn expand(
|
||||
&self,
|
||||
db: &dyn HirDatabase,
|
||||
macro_call: InFile<&ast::MacroCall>,
|
||||
) -> Option<HirFileId> {
|
||||
let krate = self.resolver.krate()?;
|
||||
let macro_call_id = macro_call.as_call_id(db.upcast(), krate, |path| {
|
||||
self.resolver.resolve_path_as_macro(db.upcast(), &path)
|
||||
})?;
|
||||
Some(macro_call_id.as_file()).filter(|it| it.expansion_level(db.upcast()) < 64)
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_variant(
|
||||
&self,
|
||||
db: &dyn HirDatabase,
|
||||
record_lit: ast::RecordExpr,
|
||||
) -> Option<VariantId> {
|
||||
let infer = self.infer.as_ref()?;
|
||||
let expr_id = self.expr_id(db, &record_lit.into())?;
|
||||
infer.variant_resolution_for_expr(expr_id)
|
||||
}
|
||||
}
|
||||
|
||||
fn scope_for(
|
||||
scopes: &ExprScopes,
|
||||
source_map: &BodySourceMap,
|
||||
node: InFile<&SyntaxNode>,
|
||||
) -> Option<ScopeId> {
|
||||
node.value
|
||||
.ancestors()
|
||||
.filter_map(ast::Expr::cast)
|
||||
.filter_map(|it| source_map.node_expr(InFile::new(node.file_id, &it)))
|
||||
.find_map(|it| scopes.scope_for(it))
|
||||
}
|
||||
|
||||
fn scope_for_offset(
|
||||
db: &dyn HirDatabase,
|
||||
scopes: &ExprScopes,
|
||||
source_map: &BodySourceMap,
|
||||
offset: InFile<TextSize>,
|
||||
) -> Option<ScopeId> {
|
||||
scopes
|
||||
.scope_by_expr()
|
||||
.iter()
|
||||
.filter_map(|(id, scope)| {
|
||||
let source = source_map.expr_syntax(*id).ok()?;
|
||||
// FIXME: correctly handle macro expansion
|
||||
if source.file_id != offset.file_id {
|
||||
return None;
|
||||
}
|
||||
let root = source.file_syntax(db.upcast());
|
||||
let node = source.value.to_node(&root);
|
||||
Some((node.syntax().text_range(), scope))
|
||||
})
|
||||
// find containing scope
|
||||
.min_by_key(|(expr_range, _scope)| {
|
||||
(
|
||||
!(expr_range.start() <= offset.value && offset.value <= expr_range.end()),
|
||||
expr_range.len(),
|
||||
)
|
||||
})
|
||||
.map(|(expr_range, scope)| {
|
||||
adjust(db, scopes, source_map, expr_range, offset).unwrap_or(*scope)
|
||||
})
|
||||
}
|
||||
|
||||
// XXX: during completion, cursor might be outside of any particular
|
||||
// expression. Try to figure out the correct scope...
|
||||
fn adjust(
|
||||
db: &dyn HirDatabase,
|
||||
scopes: &ExprScopes,
|
||||
source_map: &BodySourceMap,
|
||||
expr_range: TextRange,
|
||||
offset: InFile<TextSize>,
|
||||
) -> Option<ScopeId> {
|
||||
let child_scopes = scopes
|
||||
.scope_by_expr()
|
||||
.iter()
|
||||
.filter_map(|(id, scope)| {
|
||||
let source = source_map.expr_syntax(*id).ok()?;
|
||||
// FIXME: correctly handle macro expansion
|
||||
if source.file_id != offset.file_id {
|
||||
return None;
|
||||
}
|
||||
let root = source.file_syntax(db.upcast());
|
||||
let node = source.value.to_node(&root);
|
||||
Some((node.syntax().text_range(), scope))
|
||||
})
|
||||
.filter(|&(range, _)| {
|
||||
range.start() <= offset.value && expr_range.contains_range(range) && range != expr_range
|
||||
});
|
||||
|
||||
child_scopes
|
||||
.max_by(|&(r1, _), &(r2, _)| {
|
||||
if r1.contains_range(r2) {
|
||||
std::cmp::Ordering::Greater
|
||||
} else if r2.contains_range(r1) {
|
||||
std::cmp::Ordering::Less
|
||||
} else {
|
||||
r1.start().cmp(&r2.start())
|
||||
}
|
||||
})
|
||||
.map(|(_ptr, scope)| *scope)
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_hir_path(
|
||||
db: &dyn HirDatabase,
|
||||
resolver: &Resolver,
|
||||
path: &Path,
|
||||
) -> Option<PathResolution> {
|
||||
let types =
|
||||
resolver.resolve_path_in_type_ns_fully(db.upcast(), path.mod_path()).map(|ty| match ty {
|
||||
TypeNs::SelfType(it) => PathResolution::SelfType(it.into()),
|
||||
TypeNs::GenericParam(id) => PathResolution::TypeParam(TypeParam { id }),
|
||||
TypeNs::AdtSelfType(it) | TypeNs::AdtId(it) => {
|
||||
PathResolution::Def(Adt::from(it).into())
|
||||
}
|
||||
TypeNs::EnumVariantId(it) => PathResolution::Def(EnumVariant::from(it).into()),
|
||||
TypeNs::TypeAliasId(it) => PathResolution::Def(TypeAlias::from(it).into()),
|
||||
TypeNs::BuiltinType(it) => PathResolution::Def(it.into()),
|
||||
TypeNs::TraitId(it) => PathResolution::Def(Trait::from(it).into()),
|
||||
});
|
||||
|
||||
let body_owner = resolver.body_owner();
|
||||
let values =
|
||||
resolver.resolve_path_in_value_ns_fully(db.upcast(), path.mod_path()).and_then(|val| {
|
||||
let res = match val {
|
||||
ValueNs::LocalBinding(pat_id) => {
|
||||
let var = Local { parent: body_owner?.into(), pat_id };
|
||||
PathResolution::Local(var)
|
||||
}
|
||||
ValueNs::FunctionId(it) => PathResolution::Def(Function::from(it).into()),
|
||||
ValueNs::ConstId(it) => PathResolution::Def(Const::from(it).into()),
|
||||
ValueNs::StaticId(it) => PathResolution::Def(Static::from(it).into()),
|
||||
ValueNs::StructId(it) => PathResolution::Def(Struct::from(it).into()),
|
||||
ValueNs::EnumVariantId(it) => PathResolution::Def(EnumVariant::from(it).into()),
|
||||
ValueNs::ImplSelf(impl_id) => PathResolution::SelfType(impl_id.into()),
|
||||
};
|
||||
Some(res)
|
||||
});
|
||||
|
||||
let items = resolver
|
||||
.resolve_module_path_in_items(db.upcast(), path.mod_path())
|
||||
.take_types()
|
||||
.map(|it| PathResolution::Def(it.into()));
|
||||
|
||||
types.or(values).or(items).or_else(|| {
|
||||
resolver
|
||||
.resolve_path_as_macro(db.upcast(), path.mod_path())
|
||||
.map(|def| PathResolution::Macro(def.into()))
|
||||
})
|
||||
}
|
||||
|
||||
/// Resolves a path where we know it is a qualifier of another path.
|
||||
///
|
||||
/// For example, if we have:
|
||||
/// ```
|
||||
/// mod my {
|
||||
/// pub mod foo {
|
||||
/// struct Bar;
|
||||
/// }
|
||||
///
|
||||
/// pub fn foo() {}
|
||||
/// }
|
||||
/// ```
|
||||
/// then we know that `foo` in `my::foo::Bar` refers to the module, not the function.
|
||||
pub(crate) fn resolve_hir_path_qualifier(
|
||||
db: &dyn HirDatabase,
|
||||
resolver: &Resolver,
|
||||
path: &Path,
|
||||
) -> Option<PathResolution> {
|
||||
let items = resolver
|
||||
.resolve_module_path_in_items(db.upcast(), path.mod_path())
|
||||
.take_types()
|
||||
.map(|it| PathResolution::Def(it.into()));
|
||||
|
||||
if items.is_some() {
|
||||
return items;
|
||||
}
|
||||
|
||||
resolver.resolve_path_in_type_ns_fully(db.upcast(), path.mod_path()).map(|ty| match ty {
|
||||
TypeNs::SelfType(it) => PathResolution::SelfType(it.into()),
|
||||
TypeNs::GenericParam(id) => PathResolution::TypeParam(TypeParam { id }),
|
||||
TypeNs::AdtSelfType(it) | TypeNs::AdtId(it) => PathResolution::Def(Adt::from(it).into()),
|
||||
TypeNs::EnumVariantId(it) => PathResolution::Def(EnumVariant::from(it).into()),
|
||||
TypeNs::TypeAliasId(it) => PathResolution::Def(TypeAlias::from(it).into()),
|
||||
TypeNs::BuiltinType(it) => PathResolution::Def(it.into()),
|
||||
TypeNs::TraitId(it) => PathResolution::Def(Trait::from(it).into()),
|
||||
})
|
||||
}
|
35
crates/hir_def/Cargo.toml
Normal file
35
crates/hir_def/Cargo.toml
Normal file
|
@ -0,0 +1,35 @@
|
|||
[package]
|
||||
name = "hir_def"
|
||||
version = "0.0.0"
|
||||
license = "MIT OR Apache-2.0"
|
||||
authors = ["rust-analyzer developers"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
log = "0.4.8"
|
||||
once_cell = "1.3.1"
|
||||
rustc-hash = "1.1.0"
|
||||
either = "1.5.3"
|
||||
anymap = "0.12.1"
|
||||
drop_bomb = "0.1.4"
|
||||
fst = { version = "0.4", default-features = false }
|
||||
itertools = "0.9.0"
|
||||
indexmap = "1.4.0"
|
||||
smallvec = "1.4.0"
|
||||
|
||||
stdx = { path = "../stdx" }
|
||||
arena = { path = "../arena" }
|
||||
base_db = { path = "../base_db" }
|
||||
syntax = { path = "../syntax" }
|
||||
profile = { path = "../profile" }
|
||||
hir_expand = { path = "../hir_expand" }
|
||||
test_utils = { path = "../test_utils" }
|
||||
mbe = { path = "../mbe" }
|
||||
cfg = { path = "../cfg" }
|
||||
tt = { path = "../tt" }
|
||||
|
||||
[dev-dependencies]
|
||||
expect = { path = "../expect" }
|
329
crates/hir_def/src/adt.rs
Normal file
329
crates/hir_def/src/adt.rs
Normal file
|
@ -0,0 +1,329 @@
|
|||
//! Defines hir-level representation of structs, enums and unions
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use arena::{map::ArenaMap, Arena};
|
||||
use either::Either;
|
||||
use hir_expand::{
|
||||
name::{AsName, Name},
|
||||
InFile,
|
||||
};
|
||||
use syntax::ast::{self, NameOwner, VisibilityOwner};
|
||||
use tt::{Delimiter, DelimiterKind, Leaf, Subtree, TokenTree};
|
||||
|
||||
use crate::{
|
||||
body::{CfgExpander, LowerCtx},
|
||||
db::DefDatabase,
|
||||
item_tree::{AttrOwner, Field, Fields, ItemTree, ModItem},
|
||||
src::HasChildSource,
|
||||
src::HasSource,
|
||||
trace::Trace,
|
||||
type_ref::TypeRef,
|
||||
visibility::RawVisibility,
|
||||
EnumId, HasModule, LocalEnumVariantId, LocalFieldId, Lookup, ModuleId, StructId, UnionId,
|
||||
VariantId,
|
||||
};
|
||||
use cfg::CfgOptions;
|
||||
|
||||
/// Note that we use `StructData` for unions as well!
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct StructData {
|
||||
pub name: Name,
|
||||
pub variant_data: Arc<VariantData>,
|
||||
pub repr: Option<ReprKind>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct EnumData {
|
||||
pub name: Name,
|
||||
pub variants: Arena<EnumVariantData>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct EnumVariantData {
|
||||
pub name: Name,
|
||||
pub variant_data: Arc<VariantData>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum VariantData {
|
||||
Record(Arena<FieldData>),
|
||||
Tuple(Arena<FieldData>),
|
||||
Unit,
|
||||
}
|
||||
|
||||
/// A single field of an enum variant or struct
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct FieldData {
|
||||
pub name: Name,
|
||||
pub type_ref: TypeRef,
|
||||
pub visibility: RawVisibility,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ReprKind {
|
||||
Packed,
|
||||
Other,
|
||||
}
|
||||
|
||||
fn repr_from_value(item_tree: &ItemTree, of: AttrOwner) -> Option<ReprKind> {
|
||||
item_tree.attrs(of).by_key("repr").tt_values().find_map(parse_repr_tt)
|
||||
}
|
||||
|
||||
fn parse_repr_tt(tt: &Subtree) -> Option<ReprKind> {
|
||||
match tt.delimiter {
|
||||
Some(Delimiter { kind: DelimiterKind::Parenthesis, .. }) => {}
|
||||
_ => return None,
|
||||
}
|
||||
|
||||
let mut it = tt.token_trees.iter();
|
||||
match it.next()? {
|
||||
TokenTree::Leaf(Leaf::Ident(ident)) if ident.text == "packed" => Some(ReprKind::Packed),
|
||||
_ => Some(ReprKind::Other),
|
||||
}
|
||||
}
|
||||
|
||||
impl StructData {
|
||||
pub(crate) fn struct_data_query(db: &dyn DefDatabase, id: StructId) -> Arc<StructData> {
|
||||
let loc = id.lookup(db);
|
||||
let item_tree = db.item_tree(loc.id.file_id);
|
||||
let repr = repr_from_value(&item_tree, ModItem::from(loc.id.value).into());
|
||||
let cfg_options = db.crate_graph()[loc.container.module(db).krate].cfg_options.clone();
|
||||
|
||||
let strukt = &item_tree[loc.id.value];
|
||||
let variant_data = lower_fields(&item_tree, &cfg_options, &strukt.fields);
|
||||
Arc::new(StructData {
|
||||
name: strukt.name.clone(),
|
||||
variant_data: Arc::new(variant_data),
|
||||
repr,
|
||||
})
|
||||
}
|
||||
pub(crate) fn union_data_query(db: &dyn DefDatabase, id: UnionId) -> Arc<StructData> {
|
||||
let loc = id.lookup(db);
|
||||
let item_tree = db.item_tree(loc.id.file_id);
|
||||
let repr = repr_from_value(&item_tree, ModItem::from(loc.id.value).into());
|
||||
let cfg_options = db.crate_graph()[loc.container.module(db).krate].cfg_options.clone();
|
||||
|
||||
let union = &item_tree[loc.id.value];
|
||||
let variant_data = lower_fields(&item_tree, &cfg_options, &union.fields);
|
||||
|
||||
Arc::new(StructData {
|
||||
name: union.name.clone(),
|
||||
variant_data: Arc::new(variant_data),
|
||||
repr,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl EnumData {
|
||||
pub(crate) fn enum_data_query(db: &dyn DefDatabase, e: EnumId) -> Arc<EnumData> {
|
||||
let loc = e.lookup(db);
|
||||
let item_tree = db.item_tree(loc.id.file_id);
|
||||
let cfg_options = db.crate_graph()[loc.container.module(db).krate].cfg_options.clone();
|
||||
|
||||
let enum_ = &item_tree[loc.id.value];
|
||||
let mut variants = Arena::new();
|
||||
for var_id in enum_.variants.clone() {
|
||||
if item_tree.attrs(var_id.into()).is_cfg_enabled(&cfg_options) {
|
||||
let var = &item_tree[var_id];
|
||||
let var_data = lower_fields(&item_tree, &cfg_options, &var.fields);
|
||||
|
||||
variants.alloc(EnumVariantData {
|
||||
name: var.name.clone(),
|
||||
variant_data: Arc::new(var_data),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Arc::new(EnumData { name: enum_.name.clone(), variants })
|
||||
}
|
||||
|
||||
pub fn variant(&self, name: &Name) -> Option<LocalEnumVariantId> {
|
||||
let (id, _) = self.variants.iter().find(|(_id, data)| &data.name == name)?;
|
||||
Some(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl HasChildSource for EnumId {
|
||||
type ChildId = LocalEnumVariantId;
|
||||
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();
|
||||
lower_enum(db, &mut trace, &src, self.lookup(db).container.module(db));
|
||||
src.with_value(trace.into_map())
|
||||
}
|
||||
}
|
||||
|
||||
fn lower_enum(
|
||||
db: &dyn DefDatabase,
|
||||
trace: &mut Trace<EnumVariantData, ast::Variant>,
|
||||
ast: &InFile<ast::Enum>,
|
||||
module_id: ModuleId,
|
||||
) {
|
||||
let expander = CfgExpander::new(db, ast.file_id, module_id.krate);
|
||||
let variants = ast
|
||||
.value
|
||||
.variant_list()
|
||||
.into_iter()
|
||||
.flat_map(|it| it.variants())
|
||||
.filter(|var| expander.is_cfg_enabled(var));
|
||||
for var in variants {
|
||||
trace.alloc(
|
||||
|| var.clone(),
|
||||
|| EnumVariantData {
|
||||
name: var.name().map_or_else(Name::missing, |it| it.as_name()),
|
||||
variant_data: Arc::new(VariantData::new(db, ast.with_value(var.kind()), module_id)),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl VariantData {
|
||||
fn new(db: &dyn DefDatabase, flavor: InFile<ast::StructKind>, module_id: ModuleId) -> Self {
|
||||
let mut expander = CfgExpander::new(db, flavor.file_id, module_id.krate);
|
||||
let mut trace = Trace::new_for_arena();
|
||||
match lower_struct(db, &mut expander, &mut trace, &flavor) {
|
||||
StructKind::Tuple => VariantData::Tuple(trace.into_arena()),
|
||||
StructKind::Record => VariantData::Record(trace.into_arena()),
|
||||
StructKind::Unit => VariantData::Unit,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fields(&self) -> &Arena<FieldData> {
|
||||
const EMPTY: &Arena<FieldData> = &Arena::new();
|
||||
match &self {
|
||||
VariantData::Record(fields) | VariantData::Tuple(fields) => fields,
|
||||
_ => EMPTY,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn field(&self, name: &Name) -> Option<LocalFieldId> {
|
||||
self.fields().iter().find_map(|(id, data)| if &data.name == name { Some(id) } else { None })
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> StructKind {
|
||||
match self {
|
||||
VariantData::Record(_) => StructKind::Record,
|
||||
VariantData::Tuple(_) => StructKind::Tuple,
|
||||
VariantData::Unit => StructKind::Unit,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HasChildSource for VariantId {
|
||||
type ChildId = LocalFieldId;
|
||||
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 {
|
||||
VariantId::EnumVariantId(it) => {
|
||||
// I don't really like the fact that we call into parent source
|
||||
// here, this might add to more queries then necessary.
|
||||
let src = it.parent.child_source(db);
|
||||
(src.map(|map| map[it.local_id].kind()), it.parent.lookup(db).container.module(db))
|
||||
}
|
||||
VariantId::StructId(it) => {
|
||||
(it.lookup(db).source(db).map(|it| it.kind()), it.lookup(db).container.module(db))
|
||||
}
|
||||
VariantId::UnionId(it) => (
|
||||
it.lookup(db).source(db).map(|it| {
|
||||
it.record_field_list()
|
||||
.map(ast::StructKind::Record)
|
||||
.unwrap_or(ast::StructKind::Unit)
|
||||
}),
|
||||
it.lookup(db).container.module(db),
|
||||
),
|
||||
};
|
||||
let mut expander = CfgExpander::new(db, src.file_id, module_id.krate);
|
||||
let mut trace = Trace::new_for_map();
|
||||
lower_struct(db, &mut expander, &mut trace, &src);
|
||||
src.with_value(trace.into_map())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum StructKind {
|
||||
Tuple,
|
||||
Record,
|
||||
Unit,
|
||||
}
|
||||
|
||||
fn lower_struct(
|
||||
db: &dyn DefDatabase,
|
||||
expander: &mut CfgExpander,
|
||||
trace: &mut Trace<FieldData, Either<ast::TupleField, ast::RecordField>>,
|
||||
ast: &InFile<ast::StructKind>,
|
||||
) -> StructKind {
|
||||
let ctx = LowerCtx::new(db, ast.file_id);
|
||||
|
||||
match &ast.value {
|
||||
ast::StructKind::Tuple(fl) => {
|
||||
for (i, fd) in fl.fields().enumerate() {
|
||||
if !expander.is_cfg_enabled(&fd) {
|
||||
continue;
|
||||
}
|
||||
|
||||
trace.alloc(
|
||||
|| Either::Left(fd.clone()),
|
||||
|| FieldData {
|
||||
name: Name::new_tuple_field(i),
|
||||
type_ref: TypeRef::from_ast_opt(&ctx, fd.ty()),
|
||||
visibility: RawVisibility::from_ast(db, ast.with_value(fd.visibility())),
|
||||
},
|
||||
);
|
||||
}
|
||||
StructKind::Tuple
|
||||
}
|
||||
ast::StructKind::Record(fl) => {
|
||||
for fd in fl.fields() {
|
||||
if !expander.is_cfg_enabled(&fd) {
|
||||
continue;
|
||||
}
|
||||
|
||||
trace.alloc(
|
||||
|| 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.ty()),
|
||||
visibility: RawVisibility::from_ast(db, ast.with_value(fd.visibility())),
|
||||
},
|
||||
);
|
||||
}
|
||||
StructKind::Record
|
||||
}
|
||||
ast::StructKind::Unit => StructKind::Unit,
|
||||
}
|
||||
}
|
||||
|
||||
fn lower_fields(item_tree: &ItemTree, cfg_options: &CfgOptions, fields: &Fields) -> VariantData {
|
||||
match fields {
|
||||
Fields::Record(flds) => {
|
||||
let mut arena = Arena::new();
|
||||
for field_id in flds.clone() {
|
||||
if item_tree.attrs(field_id.into()).is_cfg_enabled(cfg_options) {
|
||||
arena.alloc(lower_field(item_tree, &item_tree[field_id]));
|
||||
}
|
||||
}
|
||||
VariantData::Record(arena)
|
||||
}
|
||||
Fields::Tuple(flds) => {
|
||||
let mut arena = Arena::new();
|
||||
for field_id in flds.clone() {
|
||||
if item_tree.attrs(field_id.into()).is_cfg_enabled(cfg_options) {
|
||||
arena.alloc(lower_field(item_tree, &item_tree[field_id]));
|
||||
}
|
||||
}
|
||||
VariantData::Tuple(arena)
|
||||
}
|
||||
Fields::Unit => VariantData::Unit,
|
||||
}
|
||||
}
|
||||
|
||||
fn lower_field(item_tree: &ItemTree, field: &Field) -> FieldData {
|
||||
FieldData {
|
||||
name: field.name.clone(),
|
||||
type_ref: field.type_ref.clone(),
|
||||
visibility: item_tree[field.visibility].clone(),
|
||||
}
|
||||
}
|
212
crates/hir_def/src/attr.rs
Normal file
212
crates/hir_def/src/attr.rs
Normal file
|
@ -0,0 +1,212 @@
|
|||
//! A higher level attributes based on TokenTree, with also some shortcuts.
|
||||
|
||||
use std::{ops, sync::Arc};
|
||||
|
||||
use cfg::{CfgExpr, CfgOptions};
|
||||
use either::Either;
|
||||
use hir_expand::{hygiene::Hygiene, AstId, InFile};
|
||||
use mbe::ast_to_token_tree;
|
||||
use syntax::{
|
||||
ast::{self, AstNode, AttrsOwner},
|
||||
SmolStr,
|
||||
};
|
||||
use tt::Subtree;
|
||||
|
||||
use crate::{
|
||||
db::DefDatabase,
|
||||
item_tree::{ItemTreeId, ItemTreeNode},
|
||||
nameres::ModuleSource,
|
||||
path::ModPath,
|
||||
src::HasChildSource,
|
||||
AdtId, AttrDefId, Lookup,
|
||||
};
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Attrs {
|
||||
entries: Option<Arc<[Attr]>>,
|
||||
}
|
||||
|
||||
impl ops::Deref for Attrs {
|
||||
type Target = [Attr];
|
||||
|
||||
fn deref(&self) -> &[Attr] {
|
||||
match &self.entries {
|
||||
Some(it) => &*it,
|
||||
None => &[],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Attrs {
|
||||
pub const EMPTY: Attrs = Attrs { entries: None };
|
||||
|
||||
pub(crate) fn attrs_query(db: &dyn DefDatabase, def: AttrDefId) -> Attrs {
|
||||
match def {
|
||||
AttrDefId::ModuleId(module) => {
|
||||
let def_map = db.crate_def_map(module.krate);
|
||||
let mod_data = &def_map[module.local_id];
|
||||
match mod_data.declaration_source(db) {
|
||||
Some(it) => {
|
||||
Attrs::from_attrs_owner(db, it.as_ref().map(|it| it as &dyn AttrsOwner))
|
||||
}
|
||||
None => Attrs::from_attrs_owner(
|
||||
db,
|
||||
mod_data.definition_source(db).as_ref().map(|src| match src {
|
||||
ModuleSource::SourceFile(file) => file as &dyn AttrsOwner,
|
||||
ModuleSource::Module(module) => module as &dyn AttrsOwner,
|
||||
}),
|
||||
),
|
||||
}
|
||||
}
|
||||
AttrDefId::FieldId(it) => {
|
||||
let src = it.parent.child_source(db);
|
||||
match &src.value[it.local_id] {
|
||||
Either::Left(_tuple) => Attrs::default(),
|
||||
Either::Right(record) => Attrs::from_attrs_owner(db, src.with_value(record)),
|
||||
}
|
||||
}
|
||||
AttrDefId::EnumVariantId(var_id) => {
|
||||
let src = var_id.parent.child_source(db);
|
||||
let src = src.as_ref().map(|it| &it[var_id.local_id]);
|
||||
Attrs::from_attrs_owner(db, src.map(|it| it as &dyn AttrsOwner))
|
||||
}
|
||||
AttrDefId::AdtId(it) => match it {
|
||||
AdtId::StructId(it) => attrs_from_item_tree(it.lookup(db).id, db),
|
||||
AdtId::EnumId(it) => attrs_from_item_tree(it.lookup(db).id, db),
|
||||
AdtId::UnionId(it) => attrs_from_item_tree(it.lookup(db).id, db),
|
||||
},
|
||||
AttrDefId::TraitId(it) => attrs_from_item_tree(it.lookup(db).id, db),
|
||||
AttrDefId::MacroDefId(it) => {
|
||||
it.ast_id.map_or_else(Default::default, |ast_id| attrs_from_ast(ast_id, db))
|
||||
}
|
||||
AttrDefId::ImplId(it) => attrs_from_item_tree(it.lookup(db).id, db),
|
||||
AttrDefId::ConstId(it) => attrs_from_item_tree(it.lookup(db).id, db),
|
||||
AttrDefId::StaticId(it) => attrs_from_item_tree(it.lookup(db).id, db),
|
||||
AttrDefId::FunctionId(it) => attrs_from_item_tree(it.lookup(db).id, db),
|
||||
AttrDefId::TypeAliasId(it) => attrs_from_item_tree(it.lookup(db).id, db),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_attrs_owner(db: &dyn DefDatabase, owner: InFile<&dyn AttrsOwner>) -> Attrs {
|
||||
let hygiene = Hygiene::new(db.upcast(), owner.file_id);
|
||||
Attrs::new(owner.value, &hygiene)
|
||||
}
|
||||
|
||||
pub(crate) fn new(owner: &dyn AttrsOwner, hygiene: &Hygiene) -> Attrs {
|
||||
let docs = ast::CommentIter::from_syntax_node(owner.syntax()).doc_comment_text().map(
|
||||
|docs_text| Attr {
|
||||
input: Some(AttrInput::Literal(SmolStr::new(docs_text))),
|
||||
path: ModPath::from(hir_expand::name!(doc)),
|
||||
},
|
||||
);
|
||||
let mut attrs = owner.attrs().peekable();
|
||||
let entries = if attrs.peek().is_none() {
|
||||
// Avoid heap allocation
|
||||
None
|
||||
} else {
|
||||
Some(attrs.flat_map(|ast| Attr::from_src(ast, hygiene)).chain(docs).collect())
|
||||
};
|
||||
Attrs { entries }
|
||||
}
|
||||
|
||||
pub fn merge(&self, other: Attrs) -> Attrs {
|
||||
match (&self.entries, &other.entries) {
|
||||
(None, None) => Attrs { entries: None },
|
||||
(Some(entries), None) | (None, Some(entries)) => {
|
||||
Attrs { entries: Some(entries.clone()) }
|
||||
}
|
||||
(Some(a), Some(b)) => {
|
||||
Attrs { entries: Some(a.iter().chain(b.iter()).cloned().collect()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn by_key(&self, key: &'static str) -> AttrQuery<'_> {
|
||||
AttrQuery { attrs: self, key }
|
||||
}
|
||||
|
||||
pub fn cfg(&self) -> impl Iterator<Item = CfgExpr> + '_ {
|
||||
// FIXME: handle cfg_attr :-)
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Attr {
|
||||
pub(crate) path: ModPath,
|
||||
pub(crate) input: Option<AttrInput>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum AttrInput {
|
||||
/// `#[attr = "string"]`
|
||||
Literal(SmolStr),
|
||||
/// `#[attr(subtree)]`
|
||||
TokenTree(Subtree),
|
||||
}
|
||||
|
||||
impl Attr {
|
||||
fn from_src(ast: ast::Attr, hygiene: &Hygiene) -> Option<Attr> {
|
||||
let path = ModPath::from_src(ast.path()?, hygiene)?;
|
||||
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 })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct AttrQuery<'a> {
|
||||
attrs: &'a Attrs,
|
||||
key: &'static str,
|
||||
}
|
||||
|
||||
impl<'a> AttrQuery<'a> {
|
||||
pub fn tt_values(self) -> impl Iterator<Item = &'a Subtree> {
|
||||
self.attrs().filter_map(|attr| match attr.input.as_ref()? {
|
||||
AttrInput::TokenTree(it) => Some(it),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn string_value(self) -> Option<&'a SmolStr> {
|
||||
self.attrs().find_map(|attr| match attr.input.as_ref()? {
|
||||
AttrInput::Literal(it) => Some(it),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn exists(self) -> bool {
|
||||
self.attrs().next().is_some()
|
||||
}
|
||||
|
||||
fn attrs(self) -> impl Iterator<Item = &'a Attr> {
|
||||
let key = self.key;
|
||||
self.attrs
|
||||
.iter()
|
||||
.filter(move |attr| attr.path.as_ident().map_or(false, |s| s.to_string() == key))
|
||||
}
|
||||
}
|
||||
|
||||
fn attrs_from_ast<N>(src: AstId<N>, db: &dyn DefDatabase) -> Attrs
|
||||
where
|
||||
N: ast::AttrsOwner,
|
||||
{
|
||||
let src = InFile::new(src.file_id, src.to_node(db.upcast()));
|
||||
Attrs::from_attrs_owner(db, src.as_ref().map(|it| it as &dyn AttrsOwner))
|
||||
}
|
||||
|
||||
fn attrs_from_item_tree<N: ItemTreeNode>(id: ItemTreeId<N>, db: &dyn DefDatabase) -> Attrs {
|
||||
let tree = db.item_tree(id.file_id);
|
||||
let mod_item = N::id_to_mod_item(id.value);
|
||||
tree.attrs(mod_item.into()).clone()
|
||||
}
|
360
crates/hir_def/src/body.rs
Normal file
360
crates/hir_def/src/body.rs
Normal file
|
@ -0,0 +1,360 @@
|
|||
//! Defines `Body`: a lowered representation of bodies of functions, statics and
|
||||
//! consts.
|
||||
mod lower;
|
||||
pub mod scope;
|
||||
|
||||
use std::{mem, ops::Index, sync::Arc};
|
||||
|
||||
use arena::{map::ArenaMap, Arena};
|
||||
use base_db::CrateId;
|
||||
use cfg::CfgOptions;
|
||||
use drop_bomb::DropBomb;
|
||||
use either::Either;
|
||||
use hir_expand::{ast_id_map::AstIdMap, hygiene::Hygiene, AstId, HirFileId, InFile, MacroDefId};
|
||||
use rustc_hash::FxHashMap;
|
||||
use syntax::{ast, AstNode, AstPtr};
|
||||
use test_utils::mark;
|
||||
|
||||
pub(crate) use lower::LowerCtx;
|
||||
|
||||
use crate::{
|
||||
attr::Attrs,
|
||||
db::DefDatabase,
|
||||
expr::{Expr, ExprId, Pat, PatId},
|
||||
item_scope::BuiltinShadowMode,
|
||||
item_scope::ItemScope,
|
||||
nameres::CrateDefMap,
|
||||
path::{ModPath, Path},
|
||||
src::HasSource,
|
||||
AsMacroCall, DefWithBodyId, HasModule, Lookup, ModuleId,
|
||||
};
|
||||
|
||||
/// A subset of Expander that only deals with cfg attributes. We only need it to
|
||||
/// avoid cyclic queries in crate def map during enum processing.
|
||||
pub(crate) struct CfgExpander {
|
||||
cfg_options: CfgOptions,
|
||||
hygiene: Hygiene,
|
||||
}
|
||||
|
||||
pub(crate) struct Expander {
|
||||
cfg_expander: CfgExpander,
|
||||
crate_def_map: Arc<CrateDefMap>,
|
||||
current_file_id: HirFileId,
|
||||
ast_id_map: Arc<AstIdMap>,
|
||||
module: ModuleId,
|
||||
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,
|
||||
current_file_id: HirFileId,
|
||||
krate: CrateId,
|
||||
) -> CfgExpander {
|
||||
let hygiene = Hygiene::new(db.upcast(), current_file_id);
|
||||
let cfg_options = db.crate_graph()[krate].cfg_options.clone();
|
||||
CfgExpander { cfg_options, hygiene }
|
||||
}
|
||||
|
||||
pub(crate) fn parse_attrs(&self, owner: &dyn ast::AttrsOwner) -> Attrs {
|
||||
Attrs::new(owner, &self.hygiene)
|
||||
}
|
||||
|
||||
pub(crate) fn is_cfg_enabled(&self, owner: &dyn ast::AttrsOwner) -> bool {
|
||||
let attrs = self.parse_attrs(owner);
|
||||
attrs.is_cfg_enabled(&self.cfg_options)
|
||||
}
|
||||
}
|
||||
|
||||
impl Expander {
|
||||
pub(crate) fn new(
|
||||
db: &dyn DefDatabase,
|
||||
current_file_id: HirFileId,
|
||||
module: ModuleId,
|
||||
) -> Expander {
|
||||
let cfg_expander = CfgExpander::new(db, current_file_id, module.krate);
|
||||
let crate_def_map = db.crate_def_map(module.krate);
|
||||
let ast_id_map = db.ast_id_map(current_file_id);
|
||||
Expander {
|
||||
cfg_expander,
|
||||
crate_def_map,
|
||||
current_file_id,
|
||||
ast_id_map,
|
||||
module,
|
||||
recursion_limit: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn enter_expand<T: ast::AstNode>(
|
||||
&mut self,
|
||||
db: &dyn DefDatabase,
|
||||
local_scope: Option<&ItemScope>,
|
||||
macro_call: ast::MacroCall,
|
||||
) -> Option<(Mark, T)> {
|
||||
self.recursion_limit += 1;
|
||||
if self.recursion_limit > EXPANSION_RECURSION_LIMIT {
|
||||
mark::hit!(your_stack_belongs_to_me);
|
||||
return None;
|
||||
}
|
||||
|
||||
let macro_call = InFile::new(self.current_file_id, ¯o_call);
|
||||
|
||||
if let Some(call_id) = macro_call.as_call_id(db, self.crate_def_map.krate, |path| {
|
||||
if let Some(local_scope) = local_scope {
|
||||
if let Some(def) = path.as_ident().and_then(|n| local_scope.get_legacy_macro(n)) {
|
||||
return Some(def);
|
||||
}
|
||||
}
|
||||
self.resolve_path_as_macro(db, &path)
|
||||
}) {
|
||||
let file_id = call_id.as_file();
|
||||
if let Some(node) = db.parse_or_expand(file_id) {
|
||||
if let Some(expr) = T::cast(node) {
|
||||
log::debug!("macro expansion {:#?}", expr.syntax());
|
||||
|
||||
let mark = Mark {
|
||||
file_id: self.current_file_id,
|
||||
ast_id_map: mem::take(&mut self.ast_id_map),
|
||||
bomb: DropBomb::new("expansion mark dropped"),
|
||||
};
|
||||
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);
|
||||
return Some((mark, expr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Instead of just dropping the error from expansion
|
||||
// report it
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn exit(&mut self, db: &dyn DefDatabase, mut mark: Mark) {
|
||||
self.cfg_expander.hygiene = Hygiene::new(db.upcast(), mark.file_id);
|
||||
self.current_file_id = mark.file_id;
|
||||
self.ast_id_map = mem::take(&mut mark.ast_id_map);
|
||||
self.recursion_limit -= 1;
|
||||
mark.bomb.defuse();
|
||||
}
|
||||
|
||||
pub(crate) fn to_source<T>(&self, value: T) -> InFile<T> {
|
||||
InFile { file_id: self.current_file_id, value }
|
||||
}
|
||||
|
||||
pub(crate) fn is_cfg_enabled(&self, owner: &dyn ast::AttrsOwner) -> bool {
|
||||
self.cfg_expander.is_cfg_enabled(owner)
|
||||
}
|
||||
|
||||
fn parse_path(&mut self, path: ast::Path) -> Option<Path> {
|
||||
Path::from_src(path, &self.cfg_expander.hygiene)
|
||||
}
|
||||
|
||||
fn resolve_path_as_macro(&self, db: &dyn DefDatabase, path: &ModPath) -> Option<MacroDefId> {
|
||||
self.crate_def_map
|
||||
.resolve_path(db, self.module.local_id, path, BuiltinShadowMode::Other)
|
||||
.0
|
||||
.take_macros()
|
||||
}
|
||||
|
||||
fn ast_id<N: AstNode>(&self, item: &N) -> AstId<N> {
|
||||
let file_local_id = self.ast_id_map.ast_id(item);
|
||||
AstId::new(self.current_file_id, file_local_id)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Mark {
|
||||
file_id: HirFileId,
|
||||
ast_id_map: Arc<AstIdMap>,
|
||||
bomb: DropBomb,
|
||||
}
|
||||
|
||||
/// The body of an item (function, const etc.).
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct Body {
|
||||
pub exprs: Arena<Expr>,
|
||||
pub pats: Arena<Pat>,
|
||||
/// The patterns for the function's parameters. While the parameter types are
|
||||
/// part of the function signature, the patterns are not (they don't change
|
||||
/// the external type of the function).
|
||||
///
|
||||
/// If this `Body` is for the body of a constant, this will just be
|
||||
/// empty.
|
||||
pub params: Vec<PatId>,
|
||||
/// The `ExprId` of the actual body expression.
|
||||
pub body_expr: ExprId,
|
||||
pub item_scope: ItemScope,
|
||||
}
|
||||
|
||||
pub type ExprPtr = AstPtr<ast::Expr>;
|
||||
pub type ExprSource = InFile<ExprPtr>;
|
||||
|
||||
pub type PatPtr = Either<AstPtr<ast::Pat>, AstPtr<ast::SelfParam>>;
|
||||
pub type PatSource = InFile<PatPtr>;
|
||||
|
||||
/// An item body together with the mapping from syntax nodes to HIR expression
|
||||
/// IDs. This is needed to go from e.g. a position in a file to the HIR
|
||||
/// expression containing it; but for type inference etc., we want to operate on
|
||||
/// a structure that is agnostic to the actual positions of expressions in the
|
||||
/// file, so that we don't recompute types whenever some whitespace is typed.
|
||||
///
|
||||
/// One complication here is that, due to macro expansion, a single `Body` might
|
||||
/// be spread across several files. So, for each ExprId and PatId, we record
|
||||
/// both the HirFileId and the position inside the file. However, we only store
|
||||
/// AST -> ExprId mapping for non-macro files, as it is not clear how to handle
|
||||
/// this properly for macros.
|
||||
#[derive(Default, Debug, Eq, PartialEq)]
|
||||
pub struct BodySourceMap {
|
||||
expr_map: FxHashMap<ExprSource, ExprId>,
|
||||
expr_map_back: ArenaMap<ExprId, Result<ExprSource, SyntheticSyntax>>,
|
||||
pat_map: FxHashMap<PatSource, PatId>,
|
||||
pat_map_back: ArenaMap<PatId, Result<PatSource, SyntheticSyntax>>,
|
||||
field_map: FxHashMap<(ExprId, usize), InFile<AstPtr<ast::RecordExprField>>>,
|
||||
expansions: FxHashMap<InFile<AstPtr<ast::MacroCall>>, HirFileId>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Eq, PartialEq, Clone, Copy)]
|
||||
pub struct SyntheticSyntax;
|
||||
|
||||
impl Body {
|
||||
pub(crate) fn body_with_source_map_query(
|
||||
db: &dyn DefDatabase,
|
||||
def: DefWithBodyId,
|
||||
) -> (Arc<Body>, Arc<BodySourceMap>) {
|
||||
let _p = profile::span("body_with_source_map_query");
|
||||
let mut params = None;
|
||||
|
||||
let (file_id, module, body) = match def {
|
||||
DefWithBodyId::FunctionId(f) => {
|
||||
let f = f.lookup(db);
|
||||
let src = f.source(db);
|
||||
params = src.value.param_list();
|
||||
(src.file_id, f.module(db), src.value.body().map(ast::Expr::from))
|
||||
}
|
||||
DefWithBodyId::ConstId(c) => {
|
||||
let c = c.lookup(db);
|
||||
let src = c.source(db);
|
||||
(src.file_id, c.module(db), src.value.body())
|
||||
}
|
||||
DefWithBodyId::StaticId(s) => {
|
||||
let s = s.lookup(db);
|
||||
let src = s.source(db);
|
||||
(src.file_id, s.module(db), src.value.body())
|
||||
}
|
||||
};
|
||||
let expander = Expander::new(db, file_id, module);
|
||||
let (body, source_map) = Body::new(db, def, expander, params, body);
|
||||
(Arc::new(body), Arc::new(source_map))
|
||||
}
|
||||
|
||||
pub(crate) fn body_query(db: &dyn DefDatabase, def: DefWithBodyId) -> Arc<Body> {
|
||||
db.body_with_source_map(def).0
|
||||
}
|
||||
|
||||
fn new(
|
||||
db: &dyn DefDatabase,
|
||||
def: DefWithBodyId,
|
||||
expander: Expander,
|
||||
params: Option<ast::ParamList>,
|
||||
body: Option<ast::Expr>,
|
||||
) -> (Body, BodySourceMap) {
|
||||
lower::lower(db, def, expander, params, body)
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<ExprId> for Body {
|
||||
type Output = Expr;
|
||||
|
||||
fn index(&self, expr: ExprId) -> &Expr {
|
||||
&self.exprs[expr]
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<PatId> for Body {
|
||||
type Output = Pat;
|
||||
|
||||
fn index(&self, pat: PatId) -> &Pat {
|
||||
&self.pats[pat]
|
||||
}
|
||||
}
|
||||
|
||||
impl BodySourceMap {
|
||||
pub fn expr_syntax(&self, expr: ExprId) -> Result<ExprSource, SyntheticSyntax> {
|
||||
self.expr_map_back[expr].clone()
|
||||
}
|
||||
|
||||
pub fn node_expr(&self, node: InFile<&ast::Expr>) -> Option<ExprId> {
|
||||
let src = node.map(|it| AstPtr::new(it));
|
||||
self.expr_map.get(&src).cloned()
|
||||
}
|
||||
|
||||
pub fn node_macro_file(&self, node: InFile<&ast::MacroCall>) -> Option<HirFileId> {
|
||||
let src = node.map(|it| AstPtr::new(it));
|
||||
self.expansions.get(&src).cloned()
|
||||
}
|
||||
|
||||
pub fn pat_syntax(&self, pat: PatId) -> Result<PatSource, SyntheticSyntax> {
|
||||
self.pat_map_back[pat].clone()
|
||||
}
|
||||
|
||||
pub fn node_pat(&self, node: InFile<&ast::Pat>) -> Option<PatId> {
|
||||
let src = node.map(|it| Either::Left(AstPtr::new(it)));
|
||||
self.pat_map.get(&src).cloned()
|
||||
}
|
||||
|
||||
pub fn node_self_param(&self, node: InFile<&ast::SelfParam>) -> Option<PatId> {
|
||||
let src = node.map(|it| Either::Right(AstPtr::new(it)));
|
||||
self.pat_map.get(&src).cloned()
|
||||
}
|
||||
|
||||
pub fn field_syntax(&self, expr: ExprId, field: usize) -> InFile<AstPtr<ast::RecordExprField>> {
|
||||
self.field_map[&(expr, field)].clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use base_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); }
|
||||
",
|
||||
);
|
||||
}
|
||||
}
|
931
crates/hir_def/src/body/lower.rs
Normal file
931
crates/hir_def/src/body/lower.rs
Normal file
|
@ -0,0 +1,931 @@
|
|||
//! Transforms `ast::Expr` into an equivalent `hir_def::expr::Expr`
|
||||
//! representation.
|
||||
|
||||
use std::{any::type_name, sync::Arc};
|
||||
|
||||
use arena::Arena;
|
||||
use either::Either;
|
||||
use hir_expand::{
|
||||
hygiene::Hygiene,
|
||||
name::{name, AsName, Name},
|
||||
HirFileId, MacroDefId, MacroDefKind,
|
||||
};
|
||||
use rustc_hash::FxHashMap;
|
||||
use syntax::{
|
||||
ast::{
|
||||
self, ArgListOwner, ArrayExprKind, AstChildren, LiteralKind, LoopBodyOwner, NameOwner,
|
||||
SlicePatComponents,
|
||||
},
|
||||
AstNode, AstPtr,
|
||||
};
|
||||
use test_utils::mark;
|
||||
|
||||
use crate::{
|
||||
adt::StructKind,
|
||||
body::{Body, BodySourceMap, Expander, PatPtr, SyntheticSyntax},
|
||||
builtin_type::{BuiltinFloat, BuiltinInt},
|
||||
db::DefDatabase,
|
||||
expr::{
|
||||
dummy_expr_id, ArithOp, Array, BinaryOp, BindingAnnotation, CmpOp, Expr, ExprId, Literal,
|
||||
LogicOp, MatchArm, Ordering, Pat, PatId, RecordFieldPat, RecordLitField, Statement,
|
||||
},
|
||||
item_scope::BuiltinShadowMode,
|
||||
item_tree::{ItemTree, ItemTreeId, ItemTreeNode},
|
||||
path::{GenericArgs, Path},
|
||||
type_ref::{Mutability, Rawness, TypeRef},
|
||||
AdtId, ConstLoc, ContainerId, DefWithBodyId, EnumLoc, FunctionLoc, Intern, ModuleDefId,
|
||||
StaticLoc, StructLoc, TraitLoc, TypeAliasLoc, UnionLoc,
|
||||
};
|
||||
|
||||
use super::{ExprSource, PatSource};
|
||||
|
||||
pub(crate) struct LowerCtx {
|
||||
hygiene: Hygiene,
|
||||
}
|
||||
|
||||
impl LowerCtx {
|
||||
pub fn new(db: &dyn DefDatabase, file_id: HirFileId) -> Self {
|
||||
LowerCtx { hygiene: Hygiene::new(db.upcast(), file_id) }
|
||||
}
|
||||
pub fn with_hygiene(hygiene: &Hygiene) -> Self {
|
||||
LowerCtx { hygiene: hygiene.clone() }
|
||||
}
|
||||
|
||||
pub fn lower_path(&self, ast: ast::Path) -> Option<Path> {
|
||||
Path::from_src(ast, &self.hygiene)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn lower(
|
||||
db: &dyn DefDatabase,
|
||||
def: DefWithBodyId,
|
||||
expander: Expander,
|
||||
params: Option<ast::ParamList>,
|
||||
body: Option<ast::Expr>,
|
||||
) -> (Body, BodySourceMap) {
|
||||
let item_tree = db.item_tree(expander.current_file_id);
|
||||
ExprCollector {
|
||||
db,
|
||||
def,
|
||||
source_map: BodySourceMap::default(),
|
||||
body: Body {
|
||||
exprs: Arena::default(),
|
||||
pats: Arena::default(),
|
||||
params: Vec::new(),
|
||||
body_expr: dummy_expr_id(),
|
||||
item_scope: Default::default(),
|
||||
},
|
||||
item_trees: {
|
||||
let mut map = FxHashMap::default();
|
||||
map.insert(expander.current_file_id, item_tree);
|
||||
map
|
||||
},
|
||||
expander,
|
||||
}
|
||||
.collect(params, body)
|
||||
}
|
||||
|
||||
struct ExprCollector<'a> {
|
||||
db: &'a dyn DefDatabase,
|
||||
def: DefWithBodyId,
|
||||
expander: Expander,
|
||||
body: Body,
|
||||
source_map: BodySourceMap,
|
||||
|
||||
item_trees: FxHashMap<HirFileId, Arc<ItemTree>>,
|
||||
}
|
||||
|
||||
impl ExprCollector<'_> {
|
||||
fn collect(
|
||||
mut self,
|
||||
param_list: Option<ast::ParamList>,
|
||||
body: Option<ast::Expr>,
|
||||
) -> (Body, BodySourceMap) {
|
||||
if let Some(param_list) = param_list {
|
||||
if let Some(self_param) = param_list.self_param() {
|
||||
let ptr = AstPtr::new(&self_param);
|
||||
let param_pat = self.alloc_pat(
|
||||
Pat::Bind {
|
||||
name: name![self],
|
||||
mode: BindingAnnotation::Unannotated,
|
||||
subpat: None,
|
||||
},
|
||||
Either::Right(ptr),
|
||||
);
|
||||
self.body.params.push(param_pat);
|
||||
}
|
||||
|
||||
for param in param_list.params() {
|
||||
let pat = match param.pat() {
|
||||
None => continue,
|
||||
Some(pat) => pat,
|
||||
};
|
||||
let param_pat = self.collect_pat(pat);
|
||||
self.body.params.push(param_pat);
|
||||
}
|
||||
};
|
||||
|
||||
self.body.body_expr = self.collect_expr_opt(body);
|
||||
(self.body, self.source_map)
|
||||
}
|
||||
|
||||
fn ctx(&self) -> LowerCtx {
|
||||
LowerCtx::new(self.db, self.expander.current_file_id)
|
||||
}
|
||||
|
||||
fn alloc_expr(&mut self, expr: Expr, ptr: AstPtr<ast::Expr>) -> ExprId {
|
||||
let src = self.expander.to_source(ptr);
|
||||
let id = self.make_expr(expr, Ok(src.clone()));
|
||||
self.source_map.expr_map.insert(src, id);
|
||||
id
|
||||
}
|
||||
// desugared exprs don't have ptr, that's wrong and should be fixed
|
||||
// somehow.
|
||||
fn alloc_expr_desugared(&mut self, expr: Expr) -> ExprId {
|
||||
self.make_expr(expr, Err(SyntheticSyntax))
|
||||
}
|
||||
fn empty_block(&mut self) -> ExprId {
|
||||
self.alloc_expr_desugared(Expr::Block { statements: Vec::new(), tail: None, label: None })
|
||||
}
|
||||
fn missing_expr(&mut self) -> ExprId {
|
||||
self.alloc_expr_desugared(Expr::Missing)
|
||||
}
|
||||
fn make_expr(&mut self, expr: Expr, src: Result<ExprSource, SyntheticSyntax>) -> ExprId {
|
||||
let id = self.body.exprs.alloc(expr);
|
||||
self.source_map.expr_map_back.insert(id, src);
|
||||
id
|
||||
}
|
||||
|
||||
fn alloc_pat(&mut self, pat: Pat, ptr: PatPtr) -> PatId {
|
||||
let src = self.expander.to_source(ptr);
|
||||
let id = self.make_pat(pat, Ok(src.clone()));
|
||||
self.source_map.pat_map.insert(src, id);
|
||||
id
|
||||
}
|
||||
fn missing_pat(&mut self) -> PatId {
|
||||
self.make_pat(Pat::Missing, Err(SyntheticSyntax))
|
||||
}
|
||||
fn make_pat(&mut self, pat: Pat, src: Result<PatSource, SyntheticSyntax>) -> PatId {
|
||||
let id = self.body.pats.alloc(pat);
|
||||
self.source_map.pat_map_back.insert(id, src);
|
||||
id
|
||||
}
|
||||
|
||||
fn collect_expr(&mut self, expr: ast::Expr) -> ExprId {
|
||||
let syntax_ptr = AstPtr::new(&expr);
|
||||
if !self.expander.is_cfg_enabled(&expr) {
|
||||
return self.missing_expr();
|
||||
}
|
||||
|
||||
match expr {
|
||||
ast::Expr::IfExpr(e) => {
|
||||
let then_branch = self.collect_block_opt(e.then_branch());
|
||||
|
||||
let else_branch = e.else_branch().map(|b| match b {
|
||||
ast::ElseBranch::Block(it) => self.collect_block(it),
|
||||
ast::ElseBranch::IfExpr(elif) => {
|
||||
let expr: ast::Expr = ast::Expr::cast(elif.syntax().clone()).unwrap();
|
||||
self.collect_expr(expr)
|
||||
}
|
||||
});
|
||||
|
||||
let condition = match e.condition() {
|
||||
None => self.missing_expr(),
|
||||
Some(condition) => match condition.pat() {
|
||||
None => self.collect_expr_opt(condition.expr()),
|
||||
// if let -- desugar to match
|
||||
Some(pat) => {
|
||||
let pat = self.collect_pat(pat);
|
||||
let match_expr = self.collect_expr_opt(condition.expr());
|
||||
let placeholder_pat = self.missing_pat();
|
||||
let arms = vec![
|
||||
MatchArm { pat, expr: then_branch, guard: None },
|
||||
MatchArm {
|
||||
pat: placeholder_pat,
|
||||
expr: else_branch.unwrap_or_else(|| self.empty_block()),
|
||||
guard: None,
|
||||
},
|
||||
];
|
||||
return self
|
||||
.alloc_expr(Expr::Match { expr: match_expr, arms }, syntax_ptr);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
self.alloc_expr(Expr::If { condition, then_branch, else_branch }, syntax_ptr)
|
||||
}
|
||||
ast::Expr::EffectExpr(e) => match e.effect() {
|
||||
ast::Effect::Try(_) => {
|
||||
let body = self.collect_block_opt(e.block_expr());
|
||||
self.alloc_expr(Expr::TryBlock { body }, syntax_ptr)
|
||||
}
|
||||
ast::Effect::Unsafe(_) => {
|
||||
let body = self.collect_block_opt(e.block_expr());
|
||||
self.alloc_expr(Expr::Unsafe { body }, syntax_ptr)
|
||||
}
|
||||
// FIXME: we need to record these effects somewhere...
|
||||
ast::Effect::Label(label) => match e.block_expr() {
|
||||
Some(block) => {
|
||||
let res = self.collect_block(block);
|
||||
match &mut self.body.exprs[res] {
|
||||
Expr::Block { label: block_label, .. } => {
|
||||
*block_label =
|
||||
label.lifetime_token().map(|t| Name::new_lifetime(&t))
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
res
|
||||
}
|
||||
None => self.missing_expr(),
|
||||
},
|
||||
// FIXME: we need to record these effects somewhere...
|
||||
ast::Effect::Async(_) => self.collect_block_opt(e.block_expr()),
|
||||
},
|
||||
ast::Expr::BlockExpr(e) => self.collect_block(e),
|
||||
ast::Expr::LoopExpr(e) => {
|
||||
let body = self.collect_block_opt(e.loop_body());
|
||||
self.alloc_expr(
|
||||
Expr::Loop {
|
||||
body,
|
||||
label: e
|
||||
.label()
|
||||
.and_then(|l| l.lifetime_token())
|
||||
.map(|l| Name::new_lifetime(&l)),
|
||||
},
|
||||
syntax_ptr,
|
||||
)
|
||||
}
|
||||
ast::Expr::WhileExpr(e) => {
|
||||
let body = self.collect_block_opt(e.loop_body());
|
||||
|
||||
let condition = match e.condition() {
|
||||
None => self.missing_expr(),
|
||||
Some(condition) => match condition.pat() {
|
||||
None => self.collect_expr_opt(condition.expr()),
|
||||
// if let -- desugar to match
|
||||
Some(pat) => {
|
||||
mark::hit!(infer_resolve_while_let);
|
||||
let pat = self.collect_pat(pat);
|
||||
let match_expr = self.collect_expr_opt(condition.expr());
|
||||
let placeholder_pat = self.missing_pat();
|
||||
let break_ =
|
||||
self.alloc_expr_desugared(Expr::Break { expr: None, label: None });
|
||||
let arms = vec![
|
||||
MatchArm { pat, expr: body, guard: None },
|
||||
MatchArm { pat: placeholder_pat, expr: break_, guard: None },
|
||||
];
|
||||
let match_expr =
|
||||
self.alloc_expr_desugared(Expr::Match { expr: match_expr, arms });
|
||||
return self.alloc_expr(
|
||||
Expr::Loop {
|
||||
body: match_expr,
|
||||
label: e
|
||||
.label()
|
||||
.and_then(|l| l.lifetime_token())
|
||||
.map(|l| Name::new_lifetime(&l)),
|
||||
},
|
||||
syntax_ptr,
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
self.alloc_expr(
|
||||
Expr::While {
|
||||
condition,
|
||||
body,
|
||||
label: e
|
||||
.label()
|
||||
.and_then(|l| l.lifetime_token())
|
||||
.map(|l| Name::new_lifetime(&l)),
|
||||
},
|
||||
syntax_ptr,
|
||||
)
|
||||
}
|
||||
ast::Expr::ForExpr(e) => {
|
||||
let iterable = self.collect_expr_opt(e.iterable());
|
||||
let pat = self.collect_pat_opt(e.pat());
|
||||
let body = self.collect_block_opt(e.loop_body());
|
||||
self.alloc_expr(
|
||||
Expr::For {
|
||||
iterable,
|
||||
pat,
|
||||
body,
|
||||
label: e
|
||||
.label()
|
||||
.and_then(|l| l.lifetime_token())
|
||||
.map(|l| Name::new_lifetime(&l)),
|
||||
},
|
||||
syntax_ptr,
|
||||
)
|
||||
}
|
||||
ast::Expr::CallExpr(e) => {
|
||||
let callee = self.collect_expr_opt(e.expr());
|
||||
let args = if let Some(arg_list) = e.arg_list() {
|
||||
arg_list.args().map(|e| self.collect_expr(e)).collect()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
self.alloc_expr(Expr::Call { callee, args }, syntax_ptr)
|
||||
}
|
||||
ast::Expr::MethodCallExpr(e) => {
|
||||
let receiver = self.collect_expr_opt(e.expr());
|
||||
let args = if let Some(arg_list) = e.arg_list() {
|
||||
arg_list.args().map(|e| self.collect_expr(e)).collect()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let method_name = e.name_ref().map(|nr| nr.as_name()).unwrap_or_else(Name::missing);
|
||||
let generic_args =
|
||||
e.generic_arg_list().and_then(|it| GenericArgs::from_ast(&self.ctx(), it));
|
||||
self.alloc_expr(
|
||||
Expr::MethodCall { receiver, method_name, args, generic_args },
|
||||
syntax_ptr,
|
||||
)
|
||||
}
|
||||
ast::Expr::MatchExpr(e) => {
|
||||
let expr = self.collect_expr_opt(e.expr());
|
||||
let arms = if let Some(match_arm_list) = e.match_arm_list() {
|
||||
match_arm_list
|
||||
.arms()
|
||||
.map(|arm| MatchArm {
|
||||
pat: self.collect_pat_opt(arm.pat()),
|
||||
expr: self.collect_expr_opt(arm.expr()),
|
||||
guard: arm
|
||||
.guard()
|
||||
.and_then(|guard| guard.expr())
|
||||
.map(|e| self.collect_expr(e)),
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
self.alloc_expr(Expr::Match { expr, arms }, syntax_ptr)
|
||||
}
|
||||
ast::Expr::PathExpr(e) => {
|
||||
let path = e
|
||||
.path()
|
||||
.and_then(|path| self.expander.parse_path(path))
|
||||
.map(Expr::Path)
|
||||
.unwrap_or(Expr::Missing);
|
||||
self.alloc_expr(path, syntax_ptr)
|
||||
}
|
||||
ast::Expr::ContinueExpr(e) => self.alloc_expr(
|
||||
Expr::Continue { label: e.lifetime_token().map(|l| Name::new_lifetime(&l)) },
|
||||
syntax_ptr,
|
||||
),
|
||||
ast::Expr::BreakExpr(e) => {
|
||||
let expr = e.expr().map(|e| self.collect_expr(e));
|
||||
self.alloc_expr(
|
||||
Expr::Break { expr, label: e.lifetime_token().map(|l| Name::new_lifetime(&l)) },
|
||||
syntax_ptr,
|
||||
)
|
||||
}
|
||||
ast::Expr::ParenExpr(e) => {
|
||||
let inner = self.collect_expr_opt(e.expr());
|
||||
// make the paren expr point to the inner expression as well
|
||||
let src = self.expander.to_source(syntax_ptr);
|
||||
self.source_map.expr_map.insert(src, inner);
|
||||
inner
|
||||
}
|
||||
ast::Expr::ReturnExpr(e) => {
|
||||
let expr = e.expr().map(|e| self.collect_expr(e));
|
||||
self.alloc_expr(Expr::Return { expr }, syntax_ptr)
|
||||
}
|
||||
ast::Expr::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_expr_field_list() {
|
||||
let fields = nfl
|
||||
.fields()
|
||||
.inspect(|field| field_ptrs.push(AstPtr::new(field)))
|
||||
.filter_map(|field| {
|
||||
if !self.expander.is_cfg_enabled(&field) {
|
||||
return None;
|
||||
}
|
||||
let name = field.field_name()?.as_name();
|
||||
|
||||
Some(RecordLitField {
|
||||
name,
|
||||
expr: match field.expr() {
|
||||
Some(e) => self.collect_expr(e),
|
||||
None => self.missing_expr(),
|
||||
},
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
let spread = nfl.spread().map(|s| self.collect_expr(s));
|
||||
Expr::RecordLit { path, fields, spread }
|
||||
} else {
|
||||
Expr::RecordLit { path, fields: Vec::new(), spread: None }
|
||||
};
|
||||
|
||||
let res = self.alloc_expr(record_lit, syntax_ptr);
|
||||
for (i, ptr) in field_ptrs.into_iter().enumerate() {
|
||||
let src = self.expander.to_source(ptr);
|
||||
self.source_map.field_map.insert((res, i), src);
|
||||
}
|
||||
res
|
||||
}
|
||||
ast::Expr::FieldExpr(e) => {
|
||||
let expr = self.collect_expr_opt(e.expr());
|
||||
let name = match e.field_access() {
|
||||
Some(kind) => kind.as_name(),
|
||||
_ => Name::missing(),
|
||||
};
|
||||
self.alloc_expr(Expr::Field { expr, name }, syntax_ptr)
|
||||
}
|
||||
ast::Expr::AwaitExpr(e) => {
|
||||
let expr = self.collect_expr_opt(e.expr());
|
||||
self.alloc_expr(Expr::Await { expr }, syntax_ptr)
|
||||
}
|
||||
ast::Expr::TryExpr(e) => {
|
||||
let expr = self.collect_expr_opt(e.expr());
|
||||
self.alloc_expr(Expr::Try { expr }, syntax_ptr)
|
||||
}
|
||||
ast::Expr::CastExpr(e) => {
|
||||
let expr = self.collect_expr_opt(e.expr());
|
||||
let type_ref = TypeRef::from_ast_opt(&self.ctx(), e.ty());
|
||||
self.alloc_expr(Expr::Cast { expr, type_ref }, syntax_ptr)
|
||||
}
|
||||
ast::Expr::RefExpr(e) => {
|
||||
let expr = self.collect_expr_opt(e.expr());
|
||||
let raw_tok = e.raw_token().is_some();
|
||||
let mutability = if raw_tok {
|
||||
if e.mut_token().is_some() {
|
||||
Mutability::Mut
|
||||
} else if e.const_token().is_some() {
|
||||
Mutability::Shared
|
||||
} else {
|
||||
unreachable!("parser only remaps to raw_token() if matching mutability token follows")
|
||||
}
|
||||
} else {
|
||||
Mutability::from_mutable(e.mut_token().is_some())
|
||||
};
|
||||
let rawness = Rawness::from_raw(raw_tok);
|
||||
self.alloc_expr(Expr::Ref { expr, rawness, mutability }, syntax_ptr)
|
||||
}
|
||||
ast::Expr::PrefixExpr(e) => {
|
||||
let expr = self.collect_expr_opt(e.expr());
|
||||
if let Some(op) = e.op_kind() {
|
||||
self.alloc_expr(Expr::UnaryOp { expr, op }, syntax_ptr)
|
||||
} else {
|
||||
self.alloc_expr(Expr::Missing, syntax_ptr)
|
||||
}
|
||||
}
|
||||
ast::Expr::ClosureExpr(e) => {
|
||||
let mut args = Vec::new();
|
||||
let mut arg_types = Vec::new();
|
||||
if let Some(pl) = e.param_list() {
|
||||
for param in pl.params() {
|
||||
let pat = self.collect_pat_opt(param.pat());
|
||||
let type_ref = param.ty().map(|it| TypeRef::from_ast(&self.ctx(), it));
|
||||
args.push(pat);
|
||||
arg_types.push(type_ref);
|
||||
}
|
||||
}
|
||||
let ret_type =
|
||||
e.ret_type().and_then(|r| r.ty()).map(|it| 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)
|
||||
}
|
||||
ast::Expr::BinExpr(e) => {
|
||||
let lhs = self.collect_expr_opt(e.lhs());
|
||||
let rhs = self.collect_expr_opt(e.rhs());
|
||||
let op = e.op_kind().map(BinaryOp::from);
|
||||
self.alloc_expr(Expr::BinaryOp { lhs, rhs, op }, syntax_ptr)
|
||||
}
|
||||
ast::Expr::TupleExpr(e) => {
|
||||
let exprs = e.fields().map(|expr| self.collect_expr(expr)).collect();
|
||||
self.alloc_expr(Expr::Tuple { exprs }, syntax_ptr)
|
||||
}
|
||||
ast::Expr::BoxExpr(e) => {
|
||||
let expr = self.collect_expr_opt(e.expr());
|
||||
self.alloc_expr(Expr::Box { expr }, syntax_ptr)
|
||||
}
|
||||
|
||||
ast::Expr::ArrayExpr(e) => {
|
||||
let kind = e.kind();
|
||||
|
||||
match kind {
|
||||
ArrayExprKind::ElementList(e) => {
|
||||
let exprs = e.map(|expr| self.collect_expr(expr)).collect();
|
||||
self.alloc_expr(Expr::Array(Array::ElementList(exprs)), syntax_ptr)
|
||||
}
|
||||
ArrayExprKind::Repeat { initializer, repeat } => {
|
||||
let initializer = self.collect_expr_opt(initializer);
|
||||
let repeat = self.collect_expr_opt(repeat);
|
||||
self.alloc_expr(
|
||||
Expr::Array(Array::Repeat { initializer, repeat }),
|
||||
syntax_ptr,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ast::Expr::Literal(e) => self.alloc_expr(Expr::Literal(e.kind().into()), syntax_ptr),
|
||||
ast::Expr::IndexExpr(e) => {
|
||||
let base = self.collect_expr_opt(e.base());
|
||||
let index = self.collect_expr_opt(e.index());
|
||||
self.alloc_expr(Expr::Index { base, index }, syntax_ptr)
|
||||
}
|
||||
ast::Expr::RangeExpr(e) => {
|
||||
let lhs = e.start().map(|lhs| self.collect_expr(lhs));
|
||||
let rhs = e.end().map(|rhs| self.collect_expr(rhs));
|
||||
match e.op_kind() {
|
||||
Some(range_type) => {
|
||||
self.alloc_expr(Expr::Range { lhs, rhs, range_type }, syntax_ptr)
|
||||
}
|
||||
None => self.alloc_expr(Expr::Missing, syntax_ptr),
|
||||
}
|
||||
}
|
||||
ast::Expr::MacroCall(e) => {
|
||||
if let Some(name) = e.is_macro_rules().map(|it| it.as_name()) {
|
||||
let mac = MacroDefId {
|
||||
krate: Some(self.expander.module.krate),
|
||||
ast_id: Some(self.expander.ast_id(&e)),
|
||||
kind: MacroDefKind::Declarative,
|
||||
local_inner: false,
|
||||
};
|
||||
self.body.item_scope.define_legacy_macro(name, mac);
|
||||
|
||||
// FIXME: do we still need to allocate this as missing ?
|
||||
self.alloc_expr(Expr::Missing, syntax_ptr)
|
||||
} else {
|
||||
let macro_call = self.expander.to_source(AstPtr::new(&e));
|
||||
match self.expander.enter_expand(self.db, Some(&self.body.item_scope), e) {
|
||||
Some((mark, expansion)) => {
|
||||
self.source_map
|
||||
.expansions
|
||||
.insert(macro_call, self.expander.current_file_id);
|
||||
|
||||
let item_tree = self.db.item_tree(self.expander.current_file_id);
|
||||
self.item_trees.insert(self.expander.current_file_id, item_tree);
|
||||
let id = self.collect_expr(expansion);
|
||||
self.expander.exit(self.db, mark);
|
||||
id
|
||||
}
|
||||
None => self.alloc_expr(Expr::Missing, syntax_ptr),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn find_inner_item<N: ItemTreeNode>(&self, ast: &N::Source) -> Option<ItemTreeId<N>> {
|
||||
let id = self.expander.ast_id(ast);
|
||||
let tree = &self.item_trees[&id.file_id];
|
||||
|
||||
// FIXME: This probably breaks with `use` items, since they produce multiple item tree nodes
|
||||
|
||||
// Root file (non-macro).
|
||||
let item_tree_id = tree
|
||||
.all_inner_items()
|
||||
.chain(tree.top_level_items().iter().copied())
|
||||
.filter_map(|mod_item| mod_item.downcast::<N>())
|
||||
.find(|tree_id| tree[*tree_id].ast_id().upcast() == id.value.upcast())
|
||||
.or_else(|| {
|
||||
log::debug!(
|
||||
"couldn't find inner {} item for {:?} (AST: `{}` - {:?})",
|
||||
type_name::<N>(),
|
||||
id,
|
||||
ast.syntax(),
|
||||
ast.syntax(),
|
||||
);
|
||||
None
|
||||
})?;
|
||||
|
||||
Some(ItemTreeId::new(id.file_id, item_tree_id))
|
||||
}
|
||||
|
||||
fn collect_expr_opt(&mut self, expr: Option<ast::Expr>) -> ExprId {
|
||||
if let Some(expr) = expr {
|
||||
self.collect_expr(expr)
|
||||
} else {
|
||||
self.missing_expr()
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_block(&mut self, block: ast::BlockExpr) -> ExprId {
|
||||
let syntax_node_ptr = AstPtr::new(&block.clone().into());
|
||||
self.collect_block_items(&block);
|
||||
let statements = block
|
||||
.statements()
|
||||
.filter_map(|s| {
|
||||
let stmt = match s {
|
||||
ast::Stmt::LetStmt(stmt) => {
|
||||
let pat = self.collect_pat_opt(stmt.pat());
|
||||
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 }
|
||||
}
|
||||
ast::Stmt::ExprStmt(stmt) => {
|
||||
Statement::Expr(self.collect_expr_opt(stmt.expr()))
|
||||
}
|
||||
ast::Stmt::Item(_) => return None,
|
||||
};
|
||||
Some(stmt)
|
||||
})
|
||||
.collect();
|
||||
let tail = block.expr().map(|e| self.collect_expr(e));
|
||||
self.alloc_expr(Expr::Block { statements, tail, label: None }, syntax_node_ptr)
|
||||
}
|
||||
|
||||
fn collect_block_items(&mut self, block: &ast::BlockExpr) {
|
||||
let container = ContainerId::DefWithBodyId(self.def);
|
||||
|
||||
let items = block
|
||||
.statements()
|
||||
.filter_map(|stmt| match stmt {
|
||||
ast::Stmt::Item(it) => Some(it),
|
||||
ast::Stmt::LetStmt(_) | ast::Stmt::ExprStmt(_) => None,
|
||||
})
|
||||
.filter_map(|item| {
|
||||
let (def, name): (ModuleDefId, Option<ast::Name>) = match item {
|
||||
ast::Item::Fn(def) => {
|
||||
let id = self.find_inner_item(&def)?;
|
||||
(
|
||||
FunctionLoc { container: container.into(), id }.intern(self.db).into(),
|
||||
def.name(),
|
||||
)
|
||||
}
|
||||
ast::Item::TypeAlias(def) => {
|
||||
let id = self.find_inner_item(&def)?;
|
||||
(
|
||||
TypeAliasLoc { container: container.into(), id }.intern(self.db).into(),
|
||||
def.name(),
|
||||
)
|
||||
}
|
||||
ast::Item::Const(def) => {
|
||||
let id = self.find_inner_item(&def)?;
|
||||
(
|
||||
ConstLoc { container: container.into(), id }.intern(self.db).into(),
|
||||
def.name(),
|
||||
)
|
||||
}
|
||||
ast::Item::Static(def) => {
|
||||
let id = self.find_inner_item(&def)?;
|
||||
(StaticLoc { container, id }.intern(self.db).into(), def.name())
|
||||
}
|
||||
ast::Item::Struct(def) => {
|
||||
let id = self.find_inner_item(&def)?;
|
||||
(StructLoc { container, id }.intern(self.db).into(), def.name())
|
||||
}
|
||||
ast::Item::Enum(def) => {
|
||||
let id = self.find_inner_item(&def)?;
|
||||
(EnumLoc { container, id }.intern(self.db).into(), def.name())
|
||||
}
|
||||
ast::Item::Union(def) => {
|
||||
let id = self.find_inner_item(&def)?;
|
||||
(UnionLoc { container, id }.intern(self.db).into(), def.name())
|
||||
}
|
||||
ast::Item::Trait(def) => {
|
||||
let id = self.find_inner_item(&def)?;
|
||||
(TraitLoc { container, id }.intern(self.db).into(), def.name())
|
||||
}
|
||||
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))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for (def, name) in items {
|
||||
self.body.item_scope.define_def(def);
|
||||
if let Some(name) = name {
|
||||
let vis = crate::visibility::Visibility::Public; // FIXME determine correctly
|
||||
let has_constructor = match def {
|
||||
ModuleDefId::AdtId(AdtId::StructId(s)) => {
|
||||
self.db.struct_data(s).variant_data.kind() != StructKind::Record
|
||||
}
|
||||
_ => true,
|
||||
};
|
||||
self.body.item_scope.push_res(
|
||||
name.as_name(),
|
||||
crate::per_ns::PerNs::from_def(def, vis, has_constructor),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_block_opt(&mut self, expr: Option<ast::BlockExpr>) -> ExprId {
|
||||
if let Some(block) = expr {
|
||||
self.collect_block(block)
|
||||
} else {
|
||||
self.missing_expr()
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_pat(&mut self, pat: ast::Pat) -> PatId {
|
||||
let pattern = match &pat {
|
||||
ast::Pat::IdentPat(bp) => {
|
||||
let name = bp.name().map(|nr| nr.as_name()).unwrap_or_else(Name::missing);
|
||||
let annotation =
|
||||
BindingAnnotation::new(bp.mut_token().is_some(), bp.ref_token().is_some());
|
||||
let subpat = bp.pat().map(|subpat| self.collect_pat(subpat));
|
||||
if annotation == BindingAnnotation::Unannotated && subpat.is_none() {
|
||||
// This could also be a single-segment path pattern. To
|
||||
// decide that, we need to try resolving the name.
|
||||
let (resolved, _) = self.expander.crate_def_map.resolve_path(
|
||||
self.db,
|
||||
self.expander.module.local_id,
|
||||
&name.clone().into(),
|
||||
BuiltinShadowMode::Other,
|
||||
);
|
||||
match resolved.take_values() {
|
||||
Some(ModuleDefId::ConstId(_)) => Pat::Path(name.into()),
|
||||
Some(ModuleDefId::EnumVariantId(_)) => {
|
||||
// this is only really valid for unit variants, but
|
||||
// shadowing other enum variants with a pattern is
|
||||
// an error anyway
|
||||
Pat::Path(name.into())
|
||||
}
|
||||
Some(ModuleDefId::AdtId(AdtId::StructId(s)))
|
||||
if self.db.struct_data(s).variant_data.kind() != StructKind::Record =>
|
||||
{
|
||||
// Funnily enough, record structs *can* be shadowed
|
||||
// by pattern bindings (but unit or tuple structs
|
||||
// can't).
|
||||
Pat::Path(name.into())
|
||||
}
|
||||
// shadowing statics is an error as well, so we just ignore that case here
|
||||
_ => Pat::Bind { name, mode: annotation, subpat },
|
||||
}
|
||||
} else {
|
||||
Pat::Bind { name, mode: annotation, subpat }
|
||||
}
|
||||
}
|
||||
ast::Pat::TupleStructPat(p) => {
|
||||
let path = p.path().and_then(|path| self.expander.parse_path(path));
|
||||
let (args, ellipsis) = self.collect_tuple_pat(p.fields());
|
||||
Pat::TupleStruct { path, args, ellipsis }
|
||||
}
|
||||
ast::Pat::RefPat(p) => {
|
||||
let pat = self.collect_pat_opt(p.pat());
|
||||
let mutability = Mutability::from_mutable(p.mut_token().is_some());
|
||||
Pat::Ref { pat, mutability }
|
||||
}
|
||||
ast::Pat::PathPat(p) => {
|
||||
let path = p.path().and_then(|path| self.expander.parse_path(path));
|
||||
path.map(Pat::Path).unwrap_or(Pat::Missing)
|
||||
}
|
||||
ast::Pat::OrPat(p) => {
|
||||
let pats = p.pats().map(|p| self.collect_pat(p)).collect();
|
||||
Pat::Or(pats)
|
||||
}
|
||||
ast::Pat::ParenPat(p) => return self.collect_pat_opt(p.pat()),
|
||||
ast::Pat::TuplePat(p) => {
|
||||
let (args, ellipsis) = self.collect_tuple_pat(p.fields());
|
||||
Pat::Tuple { args, ellipsis }
|
||||
}
|
||||
ast::Pat::WildcardPat(_) => Pat::Wild,
|
||||
ast::Pat::RecordPat(p) => {
|
||||
let path = p.path().and_then(|path| self.expander.parse_path(path));
|
||||
let args: Vec<_> = p
|
||||
.record_pat_field_list()
|
||||
.expect("every struct should have a field list")
|
||||
.fields()
|
||||
.filter_map(|f| {
|
||||
let ast_pat = f.pat()?;
|
||||
let pat = self.collect_pat(ast_pat);
|
||||
let name = f.field_name()?.as_name();
|
||||
Some(RecordFieldPat { name, pat })
|
||||
})
|
||||
.collect();
|
||||
|
||||
let ellipsis = p
|
||||
.record_pat_field_list()
|
||||
.expect("every struct should have a field list")
|
||||
.dotdot_token()
|
||||
.is_some();
|
||||
|
||||
Pat::Record { path, args, ellipsis }
|
||||
}
|
||||
ast::Pat::SlicePat(p) => {
|
||||
let SlicePatComponents { prefix, slice, suffix } = p.components();
|
||||
|
||||
// FIXME properly handle `RestPat`
|
||||
Pat::Slice {
|
||||
prefix: prefix.into_iter().map(|p| self.collect_pat(p)).collect(),
|
||||
slice: slice.map(|p| self.collect_pat(p)),
|
||||
suffix: suffix.into_iter().map(|p| self.collect_pat(p)).collect(),
|
||||
}
|
||||
}
|
||||
ast::Pat::LiteralPat(lit) => {
|
||||
if let Some(ast_lit) = lit.literal() {
|
||||
let expr = Expr::Literal(ast_lit.kind().into());
|
||||
let expr_ptr = AstPtr::new(&ast::Expr::Literal(ast_lit));
|
||||
let expr_id = self.alloc_expr(expr, expr_ptr);
|
||||
Pat::Lit(expr_id)
|
||||
} else {
|
||||
Pat::Missing
|
||||
}
|
||||
}
|
||||
ast::Pat::RestPat(_) => {
|
||||
// `RestPat` requires special handling and should not be mapped
|
||||
// to a Pat. Here we are using `Pat::Missing` as a fallback for
|
||||
// when `RestPat` is mapped to `Pat`, which can easily happen
|
||||
// when the source code being analyzed has a malformed pattern
|
||||
// which includes `..` in a place where it isn't valid.
|
||||
|
||||
Pat::Missing
|
||||
}
|
||||
// FIXME: implement
|
||||
ast::Pat::BoxPat(_) | ast::Pat::RangePat(_) | ast::Pat::MacroPat(_) => Pat::Missing,
|
||||
};
|
||||
let ptr = AstPtr::new(&pat);
|
||||
self.alloc_pat(pattern, Either::Left(ptr))
|
||||
}
|
||||
|
||||
fn collect_pat_opt(&mut self, pat: Option<ast::Pat>) -> PatId {
|
||||
if let Some(pat) = pat {
|
||||
self.collect_pat(pat)
|
||||
} else {
|
||||
self.missing_pat()
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_tuple_pat(&mut self, args: AstChildren<ast::Pat>) -> (Vec<PatId>, Option<usize>) {
|
||||
// Find the location of the `..`, if there is one. Note that we do not
|
||||
// consider the possiblity of there being multiple `..` here.
|
||||
let ellipsis = args.clone().position(|p| matches!(p, ast::Pat::RestPat(_)));
|
||||
// We want to skip the `..` pattern here, since we account for it above.
|
||||
let args = args
|
||||
.filter(|p| !matches!(p, ast::Pat::RestPat(_)))
|
||||
.map(|p| self.collect_pat(p))
|
||||
.collect();
|
||||
|
||||
(args, ellipsis)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ast::BinOp> for BinaryOp {
|
||||
fn from(ast_op: ast::BinOp) -> Self {
|
||||
match ast_op {
|
||||
ast::BinOp::BooleanOr => BinaryOp::LogicOp(LogicOp::Or),
|
||||
ast::BinOp::BooleanAnd => BinaryOp::LogicOp(LogicOp::And),
|
||||
ast::BinOp::EqualityTest => BinaryOp::CmpOp(CmpOp::Eq { negated: false }),
|
||||
ast::BinOp::NegatedEqualityTest => BinaryOp::CmpOp(CmpOp::Eq { negated: true }),
|
||||
ast::BinOp::LesserEqualTest => {
|
||||
BinaryOp::CmpOp(CmpOp::Ord { ordering: Ordering::Less, strict: false })
|
||||
}
|
||||
ast::BinOp::GreaterEqualTest => {
|
||||
BinaryOp::CmpOp(CmpOp::Ord { ordering: Ordering::Greater, strict: false })
|
||||
}
|
||||
ast::BinOp::LesserTest => {
|
||||
BinaryOp::CmpOp(CmpOp::Ord { ordering: Ordering::Less, strict: true })
|
||||
}
|
||||
ast::BinOp::GreaterTest => {
|
||||
BinaryOp::CmpOp(CmpOp::Ord { ordering: Ordering::Greater, strict: true })
|
||||
}
|
||||
ast::BinOp::Addition => BinaryOp::ArithOp(ArithOp::Add),
|
||||
ast::BinOp::Multiplication => BinaryOp::ArithOp(ArithOp::Mul),
|
||||
ast::BinOp::Subtraction => BinaryOp::ArithOp(ArithOp::Sub),
|
||||
ast::BinOp::Division => BinaryOp::ArithOp(ArithOp::Div),
|
||||
ast::BinOp::Remainder => BinaryOp::ArithOp(ArithOp::Rem),
|
||||
ast::BinOp::LeftShift => BinaryOp::ArithOp(ArithOp::Shl),
|
||||
ast::BinOp::RightShift => BinaryOp::ArithOp(ArithOp::Shr),
|
||||
ast::BinOp::BitwiseXor => BinaryOp::ArithOp(ArithOp::BitXor),
|
||||
ast::BinOp::BitwiseOr => BinaryOp::ArithOp(ArithOp::BitOr),
|
||||
ast::BinOp::BitwiseAnd => BinaryOp::ArithOp(ArithOp::BitAnd),
|
||||
ast::BinOp::Assignment => BinaryOp::Assignment { op: None },
|
||||
ast::BinOp::AddAssign => BinaryOp::Assignment { op: Some(ArithOp::Add) },
|
||||
ast::BinOp::DivAssign => BinaryOp::Assignment { op: Some(ArithOp::Div) },
|
||||
ast::BinOp::MulAssign => BinaryOp::Assignment { op: Some(ArithOp::Mul) },
|
||||
ast::BinOp::RemAssign => BinaryOp::Assignment { op: Some(ArithOp::Rem) },
|
||||
ast::BinOp::ShlAssign => BinaryOp::Assignment { op: Some(ArithOp::Shl) },
|
||||
ast::BinOp::ShrAssign => BinaryOp::Assignment { op: Some(ArithOp::Shr) },
|
||||
ast::BinOp::SubAssign => BinaryOp::Assignment { op: Some(ArithOp::Sub) },
|
||||
ast::BinOp::BitOrAssign => BinaryOp::Assignment { op: Some(ArithOp::BitOr) },
|
||||
ast::BinOp::BitAndAssign => BinaryOp::Assignment { op: Some(ArithOp::BitAnd) },
|
||||
ast::BinOp::BitXorAssign => BinaryOp::Assignment { op: Some(ArithOp::BitXor) },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ast::LiteralKind> for Literal {
|
||||
fn from(ast_lit_kind: ast::LiteralKind) -> Self {
|
||||
match ast_lit_kind {
|
||||
LiteralKind::IntNumber { suffix } => {
|
||||
let known_name = suffix.and_then(|it| BuiltinInt::from_suffix(&it));
|
||||
|
||||
Literal::Int(Default::default(), known_name)
|
||||
}
|
||||
LiteralKind::FloatNumber { suffix } => {
|
||||
let known_name = suffix.and_then(|it| BuiltinFloat::from_suffix(&it));
|
||||
|
||||
Literal::Float(Default::default(), known_name)
|
||||
}
|
||||
LiteralKind::ByteString => Literal::ByteString(Default::default()),
|
||||
LiteralKind::String => Literal::String(Default::default()),
|
||||
LiteralKind::Byte => Literal::Int(Default::default(), Some(BuiltinInt::U8)),
|
||||
LiteralKind::Bool(val) => Literal::Bool(val),
|
||||
LiteralKind::Char => Literal::Char(Default::default()),
|
||||
}
|
||||
}
|
||||
}
|
456
crates/hir_def/src/body/scope.rs
Normal file
456
crates/hir_def/src/body/scope.rs
Normal file
|
@ -0,0 +1,456 @@
|
|||
//! Name resolution for expressions.
|
||||
use std::sync::Arc;
|
||||
|
||||
use arena::{Arena, Idx};
|
||||
use hir_expand::name::Name;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::{
|
||||
body::Body,
|
||||
db::DefDatabase,
|
||||
expr::{Expr, ExprId, Pat, PatId, Statement},
|
||||
DefWithBodyId,
|
||||
};
|
||||
|
||||
pub type ScopeId = Idx<ScopeData>;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct ExprScopes {
|
||||
scopes: Arena<ScopeData>,
|
||||
scope_by_expr: FxHashMap<ExprId, ScopeId>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct ScopeEntry {
|
||||
name: Name,
|
||||
pat: PatId,
|
||||
}
|
||||
|
||||
impl ScopeEntry {
|
||||
pub fn name(&self) -> &Name {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn pat(&self) -> PatId {
|
||||
self.pat
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct ScopeData {
|
||||
parent: Option<ScopeId>,
|
||||
entries: Vec<ScopeEntry>,
|
||||
}
|
||||
|
||||
impl ExprScopes {
|
||||
pub(crate) fn expr_scopes_query(db: &dyn DefDatabase, def: DefWithBodyId) -> Arc<ExprScopes> {
|
||||
let body = db.body(def);
|
||||
Arc::new(ExprScopes::new(&*body))
|
||||
}
|
||||
|
||||
fn new(body: &Body) -> ExprScopes {
|
||||
let mut scopes =
|
||||
ExprScopes { scopes: Arena::default(), scope_by_expr: FxHashMap::default() };
|
||||
let root = scopes.root_scope();
|
||||
scopes.add_params_bindings(body, root, &body.params);
|
||||
compute_expr_scopes(body.body_expr, body, &mut scopes, root);
|
||||
scopes
|
||||
}
|
||||
|
||||
pub fn entries(&self, scope: ScopeId) -> &[ScopeEntry] {
|
||||
&self.scopes[scope].entries
|
||||
}
|
||||
|
||||
pub fn scope_chain(&self, scope: Option<ScopeId>) -> impl Iterator<Item = ScopeId> + '_ {
|
||||
std::iter::successors(scope, move |&scope| self.scopes[scope].parent)
|
||||
}
|
||||
|
||||
pub fn resolve_name_in_scope(&self, scope: ScopeId, name: &Name) -> Option<&ScopeEntry> {
|
||||
self.scope_chain(Some(scope))
|
||||
.find_map(|scope| self.entries(scope).iter().find(|it| it.name == *name))
|
||||
}
|
||||
|
||||
pub fn scope_for(&self, expr: ExprId) -> Option<ScopeId> {
|
||||
self.scope_by_expr.get(&expr).copied()
|
||||
}
|
||||
|
||||
pub fn scope_by_expr(&self) -> &FxHashMap<ExprId, ScopeId> {
|
||||
&self.scope_by_expr
|
||||
}
|
||||
|
||||
fn root_scope(&mut self) -> ScopeId {
|
||||
self.scopes.alloc(ScopeData { parent: None, entries: vec![] })
|
||||
}
|
||||
|
||||
fn new_scope(&mut self, parent: ScopeId) -> ScopeId {
|
||||
self.scopes.alloc(ScopeData { parent: Some(parent), entries: vec![] })
|
||||
}
|
||||
|
||||
fn add_bindings(&mut self, body: &Body, scope: ScopeId, pat: PatId) {
|
||||
let pattern = &body[pat];
|
||||
if let Pat::Bind { name, .. } = pattern {
|
||||
let entry = ScopeEntry { name: name.clone(), pat };
|
||||
self.scopes[scope].entries.push(entry);
|
||||
}
|
||||
|
||||
pattern.walk_child_pats(|pat| self.add_bindings(body, scope, pat));
|
||||
}
|
||||
|
||||
fn add_params_bindings(&mut self, body: &Body, scope: ScopeId, params: &[PatId]) {
|
||||
params.iter().for_each(|pat| self.add_bindings(body, scope, *pat));
|
||||
}
|
||||
|
||||
fn set_scope(&mut self, node: ExprId, scope: ScopeId) {
|
||||
self.scope_by_expr.insert(node, scope);
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_block_scopes(
|
||||
statements: &[Statement],
|
||||
tail: Option<ExprId>,
|
||||
body: &Body,
|
||||
scopes: &mut ExprScopes,
|
||||
mut scope: ScopeId,
|
||||
) {
|
||||
for stmt in statements {
|
||||
match stmt {
|
||||
Statement::Let { pat, initializer, .. } => {
|
||||
if let Some(expr) = initializer {
|
||||
scopes.set_scope(*expr, scope);
|
||||
compute_expr_scopes(*expr, body, scopes, scope);
|
||||
}
|
||||
scope = scopes.new_scope(scope);
|
||||
scopes.add_bindings(body, scope, *pat);
|
||||
}
|
||||
Statement::Expr(expr) => {
|
||||
scopes.set_scope(*expr, scope);
|
||||
compute_expr_scopes(*expr, body, scopes, scope);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(expr) = tail {
|
||||
compute_expr_scopes(expr, body, scopes, scope);
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope: ScopeId) {
|
||||
scopes.set_scope(expr, scope);
|
||||
match &body[expr] {
|
||||
Expr::Block { statements, tail, .. } => {
|
||||
compute_block_scopes(&statements, *tail, body, scopes, scope);
|
||||
}
|
||||
Expr::For { iterable, pat, body: body_expr, .. } => {
|
||||
compute_expr_scopes(*iterable, body, scopes, scope);
|
||||
let scope = scopes.new_scope(scope);
|
||||
scopes.add_bindings(body, scope, *pat);
|
||||
compute_expr_scopes(*body_expr, body, scopes, scope);
|
||||
}
|
||||
Expr::Lambda { args, body: body_expr, .. } => {
|
||||
let scope = scopes.new_scope(scope);
|
||||
scopes.add_params_bindings(body, scope, &args);
|
||||
compute_expr_scopes(*body_expr, body, scopes, scope);
|
||||
}
|
||||
Expr::Match { expr, arms } => {
|
||||
compute_expr_scopes(*expr, body, scopes, scope);
|
||||
for arm in arms {
|
||||
let scope = scopes.new_scope(scope);
|
||||
scopes.add_bindings(body, scope, arm.pat);
|
||||
if let Some(guard) = arm.guard {
|
||||
scopes.set_scope(guard, scope);
|
||||
compute_expr_scopes(guard, body, scopes, scope);
|
||||
}
|
||||
scopes.set_scope(arm.expr, scope);
|
||||
compute_expr_scopes(arm.expr, body, scopes, scope);
|
||||
}
|
||||
}
|
||||
e => e.walk_child_exprs(|e| compute_expr_scopes(e, body, scopes, scope)),
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use base_db::{fixture::WithFixture, FileId, SourceDatabase};
|
||||
use hir_expand::{name::AsName, InFile};
|
||||
use syntax::{algo::find_node_at_offset, ast, AstNode};
|
||||
use test_utils::{assert_eq_text, extract_offset, mark};
|
||||
|
||||
use crate::{db::DefDatabase, test_db::TestDB, FunctionId, ModuleDefId};
|
||||
|
||||
fn find_function(db: &TestDB, file_id: FileId) -> FunctionId {
|
||||
let krate = db.test_crate();
|
||||
let crate_def_map = db.crate_def_map(krate);
|
||||
|
||||
let module = crate_def_map.modules_for_file(file_id).next().unwrap();
|
||||
let (_, def) = crate_def_map[module].scope.entries().next().unwrap();
|
||||
match def.take_values().unwrap() {
|
||||
ModuleDefId::FunctionId(it) => it,
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn do_check(ra_fixture: &str, expected: &[&str]) {
|
||||
let (offset, code) = extract_offset(ra_fixture);
|
||||
let code = {
|
||||
let mut buf = String::new();
|
||||
let off: usize = offset.into();
|
||||
buf.push_str(&code[..off]);
|
||||
buf.push_str("<|>marker");
|
||||
buf.push_str(&code[off..]);
|
||||
buf
|
||||
};
|
||||
|
||||
let (db, position) = TestDB::with_position(&code);
|
||||
let file_id = position.file_id;
|
||||
let offset = position.offset;
|
||||
|
||||
let file_syntax = db.parse(file_id).syntax_node();
|
||||
let marker: ast::PathExpr = find_node_at_offset(&file_syntax, offset).unwrap();
|
||||
let function = find_function(&db, file_id);
|
||||
|
||||
let scopes = db.expr_scopes(function.into());
|
||||
let (_body, source_map) = db.body_with_source_map(function.into());
|
||||
|
||||
let expr_id = source_map
|
||||
.node_expr(InFile { file_id: file_id.into(), value: &marker.into() })
|
||||
.unwrap();
|
||||
let scope = scopes.scope_for(expr_id);
|
||||
|
||||
let actual = scopes
|
||||
.scope_chain(scope)
|
||||
.flat_map(|scope| scopes.entries(scope))
|
||||
.map(|it| it.name().to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
let expected = expected.join("\n");
|
||||
assert_eq_text!(&expected, &actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lambda_scope() {
|
||||
do_check(
|
||||
r"
|
||||
fn quux(foo: i32) {
|
||||
let f = |bar, baz: i32| {
|
||||
<|>
|
||||
};
|
||||
}",
|
||||
&["bar", "baz", "foo"],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_call_scope() {
|
||||
do_check(
|
||||
r"
|
||||
fn quux() {
|
||||
f(|x| <|> );
|
||||
}",
|
||||
&["x"],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_method_call_scope() {
|
||||
do_check(
|
||||
r"
|
||||
fn quux() {
|
||||
z.f(|x| <|> );
|
||||
}",
|
||||
&["x"],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_loop_scope() {
|
||||
do_check(
|
||||
r"
|
||||
fn quux() {
|
||||
loop {
|
||||
let x = ();
|
||||
<|>
|
||||
};
|
||||
}",
|
||||
&["x"],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_match() {
|
||||
do_check(
|
||||
r"
|
||||
fn quux() {
|
||||
match () {
|
||||
Some(x) => {
|
||||
<|>
|
||||
}
|
||||
};
|
||||
}",
|
||||
&["x"],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shadow_variable() {
|
||||
do_check(
|
||||
r"
|
||||
fn foo(x: String) {
|
||||
let x : &str = &x<|>;
|
||||
}",
|
||||
&["x"],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bindings_after_at() {
|
||||
do_check(
|
||||
r"
|
||||
fn foo() {
|
||||
match Some(()) {
|
||||
opt @ Some(unit) => {
|
||||
<|>
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
",
|
||||
&["opt", "unit"],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macro_inner_item() {
|
||||
do_check(
|
||||
r"
|
||||
macro_rules! mac {
|
||||
() => {{
|
||||
fn inner() {}
|
||||
inner();
|
||||
}};
|
||||
}
|
||||
|
||||
fn foo() {
|
||||
mac!();
|
||||
<|>
|
||||
}
|
||||
",
|
||||
&[],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn broken_inner_item() {
|
||||
do_check(
|
||||
r"
|
||||
fn foo() {
|
||||
trait {}
|
||||
<|>
|
||||
}
|
||||
",
|
||||
&[],
|
||||
);
|
||||
}
|
||||
|
||||
fn do_check_local_name(ra_fixture: &str, expected_offset: u32) {
|
||||
let (db, position) = TestDB::with_position(ra_fixture);
|
||||
let file_id = position.file_id;
|
||||
let offset = position.offset;
|
||||
|
||||
let file = db.parse(file_id).ok().unwrap();
|
||||
let expected_name = find_node_at_offset::<ast::Name>(file.syntax(), expected_offset.into())
|
||||
.expect("failed to find a name at the target offset");
|
||||
let name_ref: ast::NameRef = find_node_at_offset(file.syntax(), offset).unwrap();
|
||||
|
||||
let function = find_function(&db, file_id);
|
||||
|
||||
let scopes = db.expr_scopes(function.into());
|
||||
let (_body, source_map) = db.body_with_source_map(function.into());
|
||||
|
||||
let expr_scope = {
|
||||
let expr_ast = name_ref.syntax().ancestors().find_map(ast::Expr::cast).unwrap();
|
||||
let expr_id =
|
||||
source_map.node_expr(InFile { file_id: file_id.into(), value: &expr_ast }).unwrap();
|
||||
scopes.scope_for(expr_id).unwrap()
|
||||
};
|
||||
|
||||
let resolved = scopes.resolve_name_in_scope(expr_scope, &name_ref.as_name()).unwrap();
|
||||
let pat_src = source_map.pat_syntax(resolved.pat()).unwrap();
|
||||
|
||||
let local_name = pat_src.value.either(
|
||||
|it| it.syntax_node_ptr().to_node(file.syntax()),
|
||||
|it| it.syntax_node_ptr().to_node(file.syntax()),
|
||||
);
|
||||
assert_eq!(local_name.text_range(), expected_name.syntax().text_range());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_local_name() {
|
||||
do_check_local_name(
|
||||
r#"
|
||||
fn foo(x: i32, y: u32) {
|
||||
{
|
||||
let z = x * 2;
|
||||
}
|
||||
{
|
||||
let t = x<|> * 3;
|
||||
}
|
||||
}
|
||||
"#,
|
||||
7,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_local_name_declaration() {
|
||||
do_check_local_name(
|
||||
r#"
|
||||
fn foo(x: String) {
|
||||
let x : &str = &x<|>;
|
||||
}
|
||||
"#,
|
||||
7,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_local_name_shadow() {
|
||||
do_check_local_name(
|
||||
r"
|
||||
fn foo(x: String) {
|
||||
let x : &str = &x;
|
||||
x<|>
|
||||
}
|
||||
",
|
||||
28,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ref_patterns_contribute_bindings() {
|
||||
do_check_local_name(
|
||||
r"
|
||||
fn foo() {
|
||||
if let Some(&from) = bar() {
|
||||
from<|>;
|
||||
}
|
||||
}
|
||||
",
|
||||
28,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn while_let_desugaring() {
|
||||
mark::check!(infer_resolve_while_let);
|
||||
do_check_local_name(
|
||||
r#"
|
||||
fn test() {
|
||||
let foo: Option<f32> = None;
|
||||
while let Option::Some(spam) = foo {
|
||||
spam<|>
|
||||
}
|
||||
}
|
||||
"#,
|
||||
75,
|
||||
);
|
||||
}
|
||||
}
|
278
crates/hir_def/src/data.rs
Normal file
278
crates/hir_def/src/data.rs
Normal file
|
@ -0,0 +1,278 @@
|
|||
//! Contains basic data about various HIR declarations.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use hir_expand::{name::Name, InFile};
|
||||
use syntax::ast;
|
||||
|
||||
use crate::{
|
||||
attr::Attrs,
|
||||
body::Expander,
|
||||
db::DefDatabase,
|
||||
item_tree::{AssocItem, ItemTreeId, ModItem},
|
||||
type_ref::{TypeBound, TypeRef},
|
||||
visibility::RawVisibility,
|
||||
AssocContainerId, AssocItemId, ConstId, ConstLoc, FunctionId, FunctionLoc, HasModule, ImplId,
|
||||
Intern, Lookup, ModuleId, StaticId, TraitId, TypeAliasId, TypeAliasLoc,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct FunctionData {
|
||||
pub name: Name,
|
||||
pub params: Vec<TypeRef>,
|
||||
pub ret_type: TypeRef,
|
||||
pub attrs: Attrs,
|
||||
/// True if the first param is `self`. This is relevant to decide whether this
|
||||
/// 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: &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];
|
||||
|
||||
Arc::new(FunctionData {
|
||||
name: func.name.clone(),
|
||||
params: func.params.to_vec(),
|
||||
ret_type: func.ret_type.clone(),
|
||||
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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct TypeAliasData {
|
||||
pub name: Name,
|
||||
pub type_ref: Option<TypeRef>,
|
||||
pub visibility: RawVisibility,
|
||||
/// Bounds restricting the type alias itself (eg. `type Ty: Bound;` in a trait or impl).
|
||||
pub bounds: Vec<TypeBound>,
|
||||
}
|
||||
|
||||
impl TypeAliasData {
|
||||
pub(crate) fn type_alias_data_query(
|
||||
db: &dyn DefDatabase,
|
||||
typ: TypeAliasId,
|
||||
) -> Arc<TypeAliasData> {
|
||||
let loc = typ.lookup(db);
|
||||
let item_tree = db.item_tree(loc.id.file_id);
|
||||
let typ = &item_tree[loc.id.value];
|
||||
|
||||
Arc::new(TypeAliasData {
|
||||
name: typ.name.clone(),
|
||||
type_ref: typ.type_ref.clone(),
|
||||
visibility: item_tree[typ.visibility].clone(),
|
||||
bounds: typ.bounds.to_vec(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct TraitData {
|
||||
pub name: Name,
|
||||
pub items: Vec<(Name, AssocItemId)>,
|
||||
pub auto: bool,
|
||||
}
|
||||
|
||||
impl TraitData {
|
||||
pub(crate) fn trait_data_query(db: &dyn DefDatabase, tr: TraitId) -> Arc<TraitData> {
|
||||
let tr_loc = tr.lookup(db);
|
||||
let item_tree = db.item_tree(tr_loc.id.file_id);
|
||||
let tr_def = &item_tree[tr_loc.id.value];
|
||||
let name = tr_def.name.clone();
|
||||
let auto = tr_def.auto;
|
||||
let module_id = tr_loc.container.module(db);
|
||||
let container = AssocContainerId::TraitId(tr);
|
||||
let mut expander = Expander::new(db, tr_loc.id.file_id, module_id);
|
||||
|
||||
let items = collect_items(
|
||||
db,
|
||||
module_id,
|
||||
&mut expander,
|
||||
tr_def.items.iter().copied(),
|
||||
tr_loc.id.file_id,
|
||||
container,
|
||||
100,
|
||||
);
|
||||
|
||||
Arc::new(TraitData { name, items, auto })
|
||||
}
|
||||
|
||||
pub fn associated_types(&self) -> impl Iterator<Item = TypeAliasId> + '_ {
|
||||
self.items.iter().filter_map(|(_name, item)| match item {
|
||||
AssocItemId::TypeAliasId(t) => Some(*t),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn associated_type_by_name(&self, name: &Name) -> Option<TypeAliasId> {
|
||||
self.items.iter().find_map(|(item_name, item)| match item {
|
||||
AssocItemId::TypeAliasId(t) if item_name == name => Some(*t),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ImplData {
|
||||
pub target_trait: Option<TypeRef>,
|
||||
pub target_type: TypeRef,
|
||||
pub items: Vec<AssocItemId>,
|
||||
pub is_negative: bool,
|
||||
}
|
||||
|
||||
impl ImplData {
|
||||
pub(crate) fn impl_data_query(db: &dyn DefDatabase, id: ImplId) -> Arc<ImplData> {
|
||||
let _p = profile::span("impl_data_query");
|
||||
let impl_loc = id.lookup(db);
|
||||
|
||||
let item_tree = db.item_tree(impl_loc.id.file_id);
|
||||
let impl_def = &item_tree[impl_loc.id.value];
|
||||
let target_trait = impl_def.target_trait.clone();
|
||||
let target_type = impl_def.target_type.clone();
|
||||
let is_negative = impl_def.is_negative;
|
||||
let module_id = impl_loc.container.module(db);
|
||||
let container = AssocContainerId::ImplId(id);
|
||||
let mut expander = Expander::new(db, impl_loc.id.file_id, module_id);
|
||||
|
||||
let items = collect_items(
|
||||
db,
|
||||
module_id,
|
||||
&mut expander,
|
||||
impl_def.items.iter().copied(),
|
||||
impl_loc.id.file_id,
|
||||
container,
|
||||
100,
|
||||
);
|
||||
let items = items.into_iter().map(|(_, item)| item).collect();
|
||||
|
||||
Arc::new(ImplData { target_trait, target_type, items, is_negative })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ConstData {
|
||||
/// const _: () = ();
|
||||
pub name: Option<Name>,
|
||||
pub type_ref: TypeRef,
|
||||
pub visibility: RawVisibility,
|
||||
}
|
||||
|
||||
impl ConstData {
|
||||
pub(crate) fn const_data_query(db: &dyn DefDatabase, konst: ConstId) -> Arc<ConstData> {
|
||||
let loc = konst.lookup(db);
|
||||
let item_tree = db.item_tree(loc.id.file_id);
|
||||
let konst = &item_tree[loc.id.value];
|
||||
|
||||
Arc::new(ConstData {
|
||||
name: konst.name.clone(),
|
||||
type_ref: konst.type_ref.clone(),
|
||||
visibility: item_tree[konst.visibility].clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct StaticData {
|
||||
pub name: Option<Name>,
|
||||
pub type_ref: TypeRef,
|
||||
pub visibility: RawVisibility,
|
||||
pub mutable: bool,
|
||||
}
|
||||
|
||||
impl StaticData {
|
||||
pub(crate) fn static_data_query(db: &dyn DefDatabase, konst: StaticId) -> Arc<StaticData> {
|
||||
let node = konst.lookup(db);
|
||||
let item_tree = db.item_tree(node.id.file_id);
|
||||
let statik = &item_tree[node.id.value];
|
||||
|
||||
Arc::new(StaticData {
|
||||
name: Some(statik.name.clone()),
|
||||
type_ref: statik.type_ref.clone(),
|
||||
visibility: item_tree[statik.visibility].clone(),
|
||||
mutable: statik.mutable,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_items(
|
||||
db: &dyn DefDatabase,
|
||||
module: ModuleId,
|
||||
expander: &mut Expander,
|
||||
assoc_items: impl Iterator<Item = AssocItem>,
|
||||
file_id: crate::HirFileId,
|
||||
container: AssocContainerId,
|
||||
limit: usize,
|
||||
) -> Vec<(Name, AssocItemId)> {
|
||||
if limit == 0 {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let item_tree = db.item_tree(file_id);
|
||||
let cfg_options = db.crate_graph()[module.krate].cfg_options.clone();
|
||||
|
||||
let mut items = Vec::new();
|
||||
for item in assoc_items {
|
||||
match item {
|
||||
AssocItem::Function(id) => {
|
||||
let item = &item_tree[id];
|
||||
let attrs = item_tree.attrs(ModItem::from(id).into());
|
||||
if !attrs.is_cfg_enabled(&cfg_options) {
|
||||
continue;
|
||||
}
|
||||
let def = FunctionLoc { container, id: ItemTreeId::new(file_id, id) }.intern(db);
|
||||
items.push((item.name.clone(), def.into()));
|
||||
}
|
||||
// FIXME: cfg?
|
||||
AssocItem::Const(id) => {
|
||||
let item = &item_tree[id];
|
||||
let name = match item.name.clone() {
|
||||
Some(name) => name,
|
||||
None => continue,
|
||||
};
|
||||
let def = ConstLoc { container, id: ItemTreeId::new(file_id, id) }.intern(db);
|
||||
items.push((name, def.into()));
|
||||
}
|
||||
AssocItem::TypeAlias(id) => {
|
||||
let item = &item_tree[id];
|
||||
let def = TypeAliasLoc { container, id: ItemTreeId::new(file_id, id) }.intern(db);
|
||||
items.push((item.name.clone(), def.into()));
|
||||
}
|
||||
AssocItem::MacroCall(call) => {
|
||||
let call = &item_tree[call];
|
||||
let ast_id_map = db.ast_id_map(file_id);
|
||||
let root = db.parse_or_expand(file_id).unwrap();
|
||||
let call = ast_id_map.get(call.ast_id).to_node(&root);
|
||||
|
||||
if let Some((mark, mac)) = expander.enter_expand(db, None, call) {
|
||||
let src: InFile<ast::MacroItems> = expander.to_source(mac);
|
||||
let item_tree = db.item_tree(src.file_id);
|
||||
let iter =
|
||||
item_tree.top_level_items().iter().filter_map(ModItem::as_assoc_item);
|
||||
items.extend(collect_items(
|
||||
db,
|
||||
module,
|
||||
expander,
|
||||
iter,
|
||||
src.file_id,
|
||||
container,
|
||||
limit - 1,
|
||||
));
|
||||
|
||||
expander.exit(db, mark);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
items
|
||||
}
|
120
crates/hir_def/src/db.rs
Normal file
120
crates/hir_def/src/db.rs
Normal file
|
@ -0,0 +1,120 @@
|
|||
//! Defines database & queries for name resolution.
|
||||
use std::sync::Arc;
|
||||
|
||||
use base_db::{salsa, CrateId, SourceDatabase, Upcast};
|
||||
use hir_expand::{db::AstDatabase, HirFileId};
|
||||
use syntax::SmolStr;
|
||||
|
||||
use crate::{
|
||||
adt::{EnumData, StructData},
|
||||
attr::Attrs,
|
||||
body::{scope::ExprScopes, Body, BodySourceMap},
|
||||
data::{ConstData, FunctionData, ImplData, StaticData, TraitData, TypeAliasData},
|
||||
docs::Documentation,
|
||||
generics::GenericParams,
|
||||
import_map::ImportMap,
|
||||
item_tree::ItemTree,
|
||||
lang_item::{LangItemTarget, LangItems},
|
||||
nameres::CrateDefMap,
|
||||
AttrDefId, ConstId, ConstLoc, DefWithBodyId, EnumId, EnumLoc, FunctionId, FunctionLoc,
|
||||
GenericDefId, ImplId, ImplLoc, ModuleId, StaticId, StaticLoc, StructId, StructLoc, TraitId,
|
||||
TraitLoc, TypeAliasId, TypeAliasLoc, UnionId, UnionLoc,
|
||||
};
|
||||
|
||||
#[salsa::query_group(InternDatabaseStorage)]
|
||||
pub trait InternDatabase: SourceDatabase {
|
||||
#[salsa::interned]
|
||||
fn intern_function(&self, loc: FunctionLoc) -> FunctionId;
|
||||
#[salsa::interned]
|
||||
fn intern_struct(&self, loc: StructLoc) -> StructId;
|
||||
#[salsa::interned]
|
||||
fn intern_union(&self, loc: UnionLoc) -> UnionId;
|
||||
#[salsa::interned]
|
||||
fn intern_enum(&self, loc: EnumLoc) -> EnumId;
|
||||
#[salsa::interned]
|
||||
fn intern_const(&self, loc: ConstLoc) -> ConstId;
|
||||
#[salsa::interned]
|
||||
fn intern_static(&self, loc: StaticLoc) -> StaticId;
|
||||
#[salsa::interned]
|
||||
fn intern_trait(&self, loc: TraitLoc) -> TraitId;
|
||||
#[salsa::interned]
|
||||
fn intern_type_alias(&self, loc: TypeAliasLoc) -> TypeAliasId;
|
||||
#[salsa::interned]
|
||||
fn intern_impl(&self, loc: ImplLoc) -> ImplId;
|
||||
}
|
||||
|
||||
#[salsa::query_group(DefDatabaseStorage)]
|
||||
pub trait DefDatabase: InternDatabase + AstDatabase + Upcast<dyn AstDatabase> {
|
||||
#[salsa::invoke(ItemTree::item_tree_query)]
|
||||
fn item_tree(&self, file_id: HirFileId) -> Arc<ItemTree>;
|
||||
|
||||
#[salsa::invoke(crate_def_map_wait)]
|
||||
#[salsa::transparent]
|
||||
fn crate_def_map(&self, krate: CrateId) -> Arc<CrateDefMap>;
|
||||
|
||||
#[salsa::invoke(CrateDefMap::crate_def_map_query)]
|
||||
fn crate_def_map_query(&self, krate: CrateId) -> Arc<CrateDefMap>;
|
||||
|
||||
#[salsa::invoke(StructData::struct_data_query)]
|
||||
fn struct_data(&self, id: StructId) -> Arc<StructData>;
|
||||
#[salsa::invoke(StructData::union_data_query)]
|
||||
fn union_data(&self, id: UnionId) -> Arc<StructData>;
|
||||
|
||||
#[salsa::invoke(EnumData::enum_data_query)]
|
||||
fn enum_data(&self, e: EnumId) -> Arc<EnumData>;
|
||||
|
||||
#[salsa::invoke(ImplData::impl_data_query)]
|
||||
fn impl_data(&self, e: ImplId) -> Arc<ImplData>;
|
||||
|
||||
#[salsa::invoke(TraitData::trait_data_query)]
|
||||
fn trait_data(&self, e: TraitId) -> Arc<TraitData>;
|
||||
|
||||
#[salsa::invoke(TypeAliasData::type_alias_data_query)]
|
||||
fn type_alias_data(&self, e: TypeAliasId) -> Arc<TypeAliasData>;
|
||||
|
||||
#[salsa::invoke(FunctionData::fn_data_query)]
|
||||
fn function_data(&self, func: FunctionId) -> Arc<FunctionData>;
|
||||
|
||||
#[salsa::invoke(ConstData::const_data_query)]
|
||||
fn const_data(&self, konst: ConstId) -> Arc<ConstData>;
|
||||
|
||||
#[salsa::invoke(StaticData::static_data_query)]
|
||||
fn static_data(&self, konst: StaticId) -> Arc<StaticData>;
|
||||
|
||||
#[salsa::invoke(Body::body_with_source_map_query)]
|
||||
fn body_with_source_map(&self, def: DefWithBodyId) -> (Arc<Body>, Arc<BodySourceMap>);
|
||||
|
||||
#[salsa::invoke(Body::body_query)]
|
||||
fn body(&self, def: DefWithBodyId) -> Arc<Body>;
|
||||
|
||||
#[salsa::invoke(ExprScopes::expr_scopes_query)]
|
||||
fn expr_scopes(&self, def: DefWithBodyId) -> Arc<ExprScopes>;
|
||||
|
||||
#[salsa::invoke(GenericParams::generic_params_query)]
|
||||
fn generic_params(&self, def: GenericDefId) -> Arc<GenericParams>;
|
||||
|
||||
#[salsa::invoke(Attrs::attrs_query)]
|
||||
fn attrs(&self, def: AttrDefId) -> Attrs;
|
||||
|
||||
#[salsa::invoke(LangItems::module_lang_items_query)]
|
||||
fn module_lang_items(&self, module: ModuleId) -> Option<Arc<LangItems>>;
|
||||
|
||||
#[salsa::invoke(LangItems::crate_lang_items_query)]
|
||||
fn crate_lang_items(&self, krate: CrateId) -> Arc<LangItems>;
|
||||
|
||||
#[salsa::invoke(LangItems::lang_item_query)]
|
||||
fn lang_item(&self, start_crate: CrateId, item: SmolStr) -> Option<LangItemTarget>;
|
||||
|
||||
// FIXME(https://github.com/rust-analyzer/rust-analyzer/issues/2148#issuecomment-550519102)
|
||||
// Remove this query completely, in favor of `Attrs::docs` method
|
||||
#[salsa::invoke(Documentation::documentation_query)]
|
||||
fn documentation(&self, def: AttrDefId) -> Option<Documentation>;
|
||||
|
||||
#[salsa::invoke(ImportMap::import_map_query)]
|
||||
fn import_map(&self, krate: CrateId) -> Arc<ImportMap>;
|
||||
}
|
||||
|
||||
fn crate_def_map_wait(db: &impl DefDatabase, krate: CrateId) -> Arc<CrateDefMap> {
|
||||
let _p = profile::span("crate_def_map:wait");
|
||||
db.crate_def_map_query(krate)
|
||||
}
|
27
crates/hir_def/src/diagnostics.rs
Normal file
27
crates/hir_def/src/diagnostics.rs
Normal file
|
@ -0,0 +1,27 @@
|
|||
//! Diagnostics produced by `hir_def`.
|
||||
|
||||
use std::any::Any;
|
||||
|
||||
use hir_expand::diagnostics::Diagnostic;
|
||||
use syntax::{ast, AstPtr, SyntaxNodePtr};
|
||||
|
||||
use hir_expand::{HirFileId, InFile};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UnresolvedModule {
|
||||
pub file: HirFileId,
|
||||
pub decl: AstPtr<ast::Module>,
|
||||
pub candidate: String,
|
||||
}
|
||||
|
||||
impl Diagnostic for UnresolvedModule {
|
||||
fn message(&self) -> String {
|
||||
"unresolved module".to_string()
|
||||
}
|
||||
fn display_source(&self) -> InFile<SyntaxNodePtr> {
|
||||
InFile::new(self.file, self.decl.clone().into())
|
||||
}
|
||||
fn as_any(&self) -> &(dyn Any + Send + 'static) {
|
||||
self
|
||||
}
|
||||
}
|
121
crates/hir_def/src/docs.rs
Normal file
121
crates/hir_def/src/docs.rs
Normal file
|
@ -0,0 +1,121 @@
|
|||
//! Defines hir documentation.
|
||||
//!
|
||||
//! This really shouldn't exist, instead, we should deshugar doc comments into attributes, see
|
||||
//! https://github.com/rust-analyzer/rust-analyzer/issues/2148#issuecomment-550519102
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use either::Either;
|
||||
use syntax::ast;
|
||||
|
||||
use crate::{
|
||||
db::DefDatabase,
|
||||
src::{HasChildSource, HasSource},
|
||||
AdtId, AttrDefId, Lookup,
|
||||
};
|
||||
|
||||
/// Holds documentation
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Documentation(Arc<str>);
|
||||
|
||||
impl Into<String> for Documentation {
|
||||
fn into(self) -> String {
|
||||
self.as_str().to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
impl Documentation {
|
||||
fn new(s: &str) -> Documentation {
|
||||
Documentation(s.into())
|
||||
}
|
||||
|
||||
pub fn from_ast<N>(node: &N) -> Option<Documentation>
|
||||
where
|
||||
N: ast::DocCommentsOwner + ast::AttrsOwner,
|
||||
{
|
||||
docs_from_ast(node)
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
&*self.0
|
||||
}
|
||||
|
||||
pub(crate) fn documentation_query(
|
||||
db: &dyn DefDatabase,
|
||||
def: AttrDefId,
|
||||
) -> Option<Documentation> {
|
||||
match def {
|
||||
AttrDefId::ModuleId(module) => {
|
||||
let def_map = db.crate_def_map(module.krate);
|
||||
let src = def_map[module.local_id].declaration_source(db)?;
|
||||
docs_from_ast(&src.value)
|
||||
}
|
||||
AttrDefId::FieldId(it) => {
|
||||
let src = it.parent.child_source(db);
|
||||
match &src.value[it.local_id] {
|
||||
Either::Left(_tuple) => None,
|
||||
Either::Right(record) => docs_from_ast(record),
|
||||
}
|
||||
}
|
||||
AttrDefId::AdtId(it) => match it {
|
||||
AdtId::StructId(it) => docs_from_ast(&it.lookup(db).source(db).value),
|
||||
AdtId::EnumId(it) => docs_from_ast(&it.lookup(db).source(db).value),
|
||||
AdtId::UnionId(it) => docs_from_ast(&it.lookup(db).source(db).value),
|
||||
},
|
||||
AttrDefId::EnumVariantId(it) => {
|
||||
let src = it.parent.child_source(db);
|
||||
docs_from_ast(&src.value[it.local_id])
|
||||
}
|
||||
AttrDefId::TraitId(it) => docs_from_ast(&it.lookup(db).source(db).value),
|
||||
AttrDefId::MacroDefId(it) => docs_from_ast(&it.ast_id?.to_node(db.upcast())),
|
||||
AttrDefId::ConstId(it) => docs_from_ast(&it.lookup(db).source(db).value),
|
||||
AttrDefId::StaticId(it) => docs_from_ast(&it.lookup(db).source(db).value),
|
||||
AttrDefId::FunctionId(it) => docs_from_ast(&it.lookup(db).source(db).value),
|
||||
AttrDefId::TypeAliasId(it) => docs_from_ast(&it.lookup(db).source(db).value),
|
||||
AttrDefId::ImplId(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn docs_from_ast<N>(node: &N) -> Option<Documentation>
|
||||
where
|
||||
N: ast::DocCommentsOwner + ast::AttrsOwner,
|
||||
{
|
||||
let doc_comment_text = node.doc_comment_text();
|
||||
let doc_attr_text = expand_doc_attrs(node);
|
||||
let docs = merge_doc_comments_and_attrs(doc_comment_text, doc_attr_text);
|
||||
docs.map(|it| Documentation::new(&it))
|
||||
}
|
||||
|
||||
fn merge_doc_comments_and_attrs(
|
||||
doc_comment_text: Option<String>,
|
||||
doc_attr_text: Option<String>,
|
||||
) -> Option<String> {
|
||||
match (doc_comment_text, doc_attr_text) {
|
||||
(Some(mut comment_text), Some(attr_text)) => {
|
||||
comment_text.push_str("\n\n");
|
||||
comment_text.push_str(&attr_text);
|
||||
Some(comment_text)
|
||||
}
|
||||
(Some(comment_text), None) => Some(comment_text),
|
||||
(None, Some(attr_text)) => Some(attr_text),
|
||||
(None, None) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn expand_doc_attrs(owner: &dyn ast::AttrsOwner) -> Option<String> {
|
||||
let mut docs = String::new();
|
||||
for attr in owner.attrs() {
|
||||
if let Some(("doc", value)) =
|
||||
attr.as_simple_key_value().as_ref().map(|(k, v)| (k.as_str(), v.as_str()))
|
||||
{
|
||||
docs.push_str(value);
|
||||
docs.push_str("\n\n");
|
||||
}
|
||||
}
|
||||
if docs.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(docs.trim_end_matches("\n\n").to_owned())
|
||||
}
|
||||
}
|
420
crates/hir_def/src/expr.rs
Normal file
420
crates/hir_def/src/expr.rs
Normal file
|
@ -0,0 +1,420 @@
|
|||
//! This module describes hir-level representation of expressions.
|
||||
//!
|
||||
//! This representaion is:
|
||||
//!
|
||||
//! 1. Identity-based. Each expression has an `id`, so we can distinguish
|
||||
//! between different `1` in `1 + 1`.
|
||||
//! 2. Independent of syntax. Though syntactic provenance information can be
|
||||
//! attached separately via id-based side map.
|
||||
//! 3. Unresolved. Paths are stored as sequences of names, and not as defs the
|
||||
//! names refer to.
|
||||
//! 4. Desugared. There's no `if let`.
|
||||
//!
|
||||
//! See also a neighboring `body` module.
|
||||
|
||||
use arena::{Idx, RawId};
|
||||
use hir_expand::name::Name;
|
||||
use syntax::ast::RangeOp;
|
||||
|
||||
use crate::{
|
||||
builtin_type::{BuiltinFloat, BuiltinInt},
|
||||
path::{GenericArgs, Path},
|
||||
type_ref::{Mutability, Rawness, TypeRef},
|
||||
};
|
||||
|
||||
pub type ExprId = Idx<Expr>;
|
||||
pub(crate) fn dummy_expr_id() -> ExprId {
|
||||
ExprId::from_raw(RawId::from(!0))
|
||||
}
|
||||
|
||||
pub type PatId = Idx<Pat>;
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum Literal {
|
||||
String(String),
|
||||
ByteString(Vec<u8>),
|
||||
Char(char),
|
||||
Bool(bool),
|
||||
Int(u64, Option<BuiltinInt>),
|
||||
Float(u64, Option<BuiltinFloat>), // FIXME: f64 is not Eq
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum Expr {
|
||||
/// This is produced if the syntax tree does not have a required expression piece.
|
||||
Missing,
|
||||
Path(Path),
|
||||
If {
|
||||
condition: ExprId,
|
||||
then_branch: ExprId,
|
||||
else_branch: Option<ExprId>,
|
||||
},
|
||||
Block {
|
||||
statements: Vec<Statement>,
|
||||
tail: Option<ExprId>,
|
||||
label: Option<Name>,
|
||||
},
|
||||
Loop {
|
||||
body: ExprId,
|
||||
label: Option<Name>,
|
||||
},
|
||||
While {
|
||||
condition: ExprId,
|
||||
body: ExprId,
|
||||
label: Option<Name>,
|
||||
},
|
||||
For {
|
||||
iterable: ExprId,
|
||||
pat: PatId,
|
||||
body: ExprId,
|
||||
label: Option<Name>,
|
||||
},
|
||||
Call {
|
||||
callee: ExprId,
|
||||
args: Vec<ExprId>,
|
||||
},
|
||||
MethodCall {
|
||||
receiver: ExprId,
|
||||
method_name: Name,
|
||||
args: Vec<ExprId>,
|
||||
generic_args: Option<GenericArgs>,
|
||||
},
|
||||
Match {
|
||||
expr: ExprId,
|
||||
arms: Vec<MatchArm>,
|
||||
},
|
||||
Continue {
|
||||
label: Option<Name>,
|
||||
},
|
||||
Break {
|
||||
expr: Option<ExprId>,
|
||||
label: Option<Name>,
|
||||
},
|
||||
Return {
|
||||
expr: Option<ExprId>,
|
||||
},
|
||||
RecordLit {
|
||||
path: Option<Path>,
|
||||
fields: Vec<RecordLitField>,
|
||||
spread: Option<ExprId>,
|
||||
},
|
||||
Field {
|
||||
expr: ExprId,
|
||||
name: Name,
|
||||
},
|
||||
Await {
|
||||
expr: ExprId,
|
||||
},
|
||||
Try {
|
||||
expr: ExprId,
|
||||
},
|
||||
TryBlock {
|
||||
body: ExprId,
|
||||
},
|
||||
Cast {
|
||||
expr: ExprId,
|
||||
type_ref: TypeRef,
|
||||
},
|
||||
Ref {
|
||||
expr: ExprId,
|
||||
rawness: Rawness,
|
||||
mutability: Mutability,
|
||||
},
|
||||
Box {
|
||||
expr: ExprId,
|
||||
},
|
||||
UnaryOp {
|
||||
expr: ExprId,
|
||||
op: UnaryOp,
|
||||
},
|
||||
BinaryOp {
|
||||
lhs: ExprId,
|
||||
rhs: ExprId,
|
||||
op: Option<BinaryOp>,
|
||||
},
|
||||
Range {
|
||||
lhs: Option<ExprId>,
|
||||
rhs: Option<ExprId>,
|
||||
range_type: RangeOp,
|
||||
},
|
||||
Index {
|
||||
base: ExprId,
|
||||
index: ExprId,
|
||||
},
|
||||
Lambda {
|
||||
args: Vec<PatId>,
|
||||
arg_types: Vec<Option<TypeRef>>,
|
||||
ret_type: Option<TypeRef>,
|
||||
body: ExprId,
|
||||
},
|
||||
Tuple {
|
||||
exprs: Vec<ExprId>,
|
||||
},
|
||||
Unsafe {
|
||||
body: ExprId,
|
||||
},
|
||||
Array(Array),
|
||||
Literal(Literal),
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum BinaryOp {
|
||||
LogicOp(LogicOp),
|
||||
ArithOp(ArithOp),
|
||||
CmpOp(CmpOp),
|
||||
Assignment { op: Option<ArithOp> },
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum LogicOp {
|
||||
And,
|
||||
Or,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum CmpOp {
|
||||
Eq { negated: bool },
|
||||
Ord { ordering: Ordering, strict: bool },
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum Ordering {
|
||||
Less,
|
||||
Greater,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum ArithOp {
|
||||
Add,
|
||||
Mul,
|
||||
Sub,
|
||||
Div,
|
||||
Rem,
|
||||
Shl,
|
||||
Shr,
|
||||
BitXor,
|
||||
BitOr,
|
||||
BitAnd,
|
||||
}
|
||||
|
||||
pub use syntax::ast::PrefixOp as UnaryOp;
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum Array {
|
||||
ElementList(Vec<ExprId>),
|
||||
Repeat { initializer: ExprId, repeat: ExprId },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct MatchArm {
|
||||
pub pat: PatId,
|
||||
pub guard: Option<ExprId>,
|
||||
pub expr: ExprId,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct RecordLitField {
|
||||
pub name: Name,
|
||||
pub expr: ExprId,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum Statement {
|
||||
Let { pat: PatId, type_ref: Option<TypeRef>, initializer: Option<ExprId> },
|
||||
Expr(ExprId),
|
||||
}
|
||||
|
||||
impl Expr {
|
||||
pub fn walk_child_exprs(&self, mut f: impl FnMut(ExprId)) {
|
||||
match self {
|
||||
Expr::Missing => {}
|
||||
Expr::Path(_) => {}
|
||||
Expr::If { condition, then_branch, else_branch } => {
|
||||
f(*condition);
|
||||
f(*then_branch);
|
||||
if let Some(else_branch) = else_branch {
|
||||
f(*else_branch);
|
||||
}
|
||||
}
|
||||
Expr::Block { statements, tail, .. } => {
|
||||
for stmt in statements {
|
||||
match stmt {
|
||||
Statement::Let { initializer, .. } => {
|
||||
if let Some(expr) = initializer {
|
||||
f(*expr);
|
||||
}
|
||||
}
|
||||
Statement::Expr(e) => f(*e),
|
||||
}
|
||||
}
|
||||
if let Some(expr) = tail {
|
||||
f(*expr);
|
||||
}
|
||||
}
|
||||
Expr::TryBlock { body } | Expr::Unsafe { body } => f(*body),
|
||||
Expr::Loop { body, .. } => f(*body),
|
||||
Expr::While { condition, body, .. } => {
|
||||
f(*condition);
|
||||
f(*body);
|
||||
}
|
||||
Expr::For { iterable, body, .. } => {
|
||||
f(*iterable);
|
||||
f(*body);
|
||||
}
|
||||
Expr::Call { callee, args } => {
|
||||
f(*callee);
|
||||
for arg in args {
|
||||
f(*arg);
|
||||
}
|
||||
}
|
||||
Expr::MethodCall { receiver, args, .. } => {
|
||||
f(*receiver);
|
||||
for arg in args {
|
||||
f(*arg);
|
||||
}
|
||||
}
|
||||
Expr::Match { expr, arms } => {
|
||||
f(*expr);
|
||||
for arm in arms {
|
||||
f(arm.expr);
|
||||
}
|
||||
}
|
||||
Expr::Continue { .. } => {}
|
||||
Expr::Break { expr, .. } | Expr::Return { expr } => {
|
||||
if let Some(expr) = expr {
|
||||
f(*expr);
|
||||
}
|
||||
}
|
||||
Expr::RecordLit { fields, spread, .. } => {
|
||||
for field in fields {
|
||||
f(field.expr);
|
||||
}
|
||||
if let Some(expr) = spread {
|
||||
f(*expr);
|
||||
}
|
||||
}
|
||||
Expr::Lambda { body, .. } => {
|
||||
f(*body);
|
||||
}
|
||||
Expr::BinaryOp { lhs, rhs, .. } => {
|
||||
f(*lhs);
|
||||
f(*rhs);
|
||||
}
|
||||
Expr::Range { lhs, rhs, .. } => {
|
||||
if let Some(lhs) = rhs {
|
||||
f(*lhs);
|
||||
}
|
||||
if let Some(rhs) = lhs {
|
||||
f(*rhs);
|
||||
}
|
||||
}
|
||||
Expr::Index { base, index } => {
|
||||
f(*base);
|
||||
f(*index);
|
||||
}
|
||||
Expr::Field { expr, .. }
|
||||
| Expr::Await { expr }
|
||||
| Expr::Try { expr }
|
||||
| Expr::Cast { expr, .. }
|
||||
| Expr::Ref { expr, .. }
|
||||
| Expr::UnaryOp { expr, .. }
|
||||
| Expr::Box { expr } => {
|
||||
f(*expr);
|
||||
}
|
||||
Expr::Tuple { exprs } => {
|
||||
for expr in exprs {
|
||||
f(*expr);
|
||||
}
|
||||
}
|
||||
Expr::Array(a) => match a {
|
||||
Array::ElementList(exprs) => {
|
||||
for expr in exprs {
|
||||
f(*expr);
|
||||
}
|
||||
}
|
||||
Array::Repeat { initializer, repeat } => {
|
||||
f(*initializer);
|
||||
f(*repeat)
|
||||
}
|
||||
},
|
||||
Expr::Literal(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Explicit binding annotations given in the HIR for a binding. Note
|
||||
/// that this is not the final binding *mode* that we infer after type
|
||||
/// inference.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Copy)]
|
||||
pub enum BindingAnnotation {
|
||||
/// No binding annotation given: this means that the final binding mode
|
||||
/// will depend on whether we have skipped through a `&` reference
|
||||
/// when matching. For example, the `x` in `Some(x)` will have binding
|
||||
/// mode `None`; if you do `let Some(x) = &Some(22)`, it will
|
||||
/// ultimately be inferred to be by-reference.
|
||||
Unannotated,
|
||||
|
||||
/// Annotated with `mut x` -- could be either ref or not, similar to `None`.
|
||||
Mutable,
|
||||
|
||||
/// Annotated as `ref`, like `ref x`
|
||||
Ref,
|
||||
|
||||
/// Annotated as `ref mut x`.
|
||||
RefMut,
|
||||
}
|
||||
|
||||
impl BindingAnnotation {
|
||||
pub fn new(is_mutable: bool, is_ref: bool) -> Self {
|
||||
match (is_mutable, is_ref) {
|
||||
(true, true) => BindingAnnotation::RefMut,
|
||||
(false, true) => BindingAnnotation::Ref,
|
||||
(true, false) => BindingAnnotation::Mutable,
|
||||
(false, false) => BindingAnnotation::Unannotated,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct RecordFieldPat {
|
||||
pub name: Name,
|
||||
pub pat: PatId,
|
||||
}
|
||||
|
||||
/// Close relative to rustc's hir::PatKind
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum Pat {
|
||||
Missing,
|
||||
Wild,
|
||||
Tuple { args: Vec<PatId>, ellipsis: Option<usize> },
|
||||
Or(Vec<PatId>),
|
||||
Record { path: Option<Path>, args: Vec<RecordFieldPat>, ellipsis: bool },
|
||||
Range { start: ExprId, end: ExprId },
|
||||
Slice { prefix: Vec<PatId>, slice: Option<PatId>, suffix: Vec<PatId> },
|
||||
Path(Path),
|
||||
Lit(ExprId),
|
||||
Bind { mode: BindingAnnotation, name: Name, subpat: Option<PatId> },
|
||||
TupleStruct { path: Option<Path>, args: Vec<PatId>, ellipsis: Option<usize> },
|
||||
Ref { pat: PatId, mutability: Mutability },
|
||||
}
|
||||
|
||||
impl Pat {
|
||||
pub fn walk_child_pats(&self, mut f: impl FnMut(PatId)) {
|
||||
match self {
|
||||
Pat::Range { .. } | Pat::Lit(..) | Pat::Path(..) | Pat::Wild | Pat::Missing => {}
|
||||
Pat::Bind { subpat, .. } => {
|
||||
subpat.iter().copied().for_each(f);
|
||||
}
|
||||
Pat::Or(args) | Pat::Tuple { args, .. } | Pat::TupleStruct { args, .. } => {
|
||||
args.iter().copied().for_each(f);
|
||||
}
|
||||
Pat::Ref { pat, .. } => f(*pat),
|
||||
Pat::Slice { prefix, slice, suffix } => {
|
||||
let total_iter = prefix.iter().chain(slice.iter()).chain(suffix.iter());
|
||||
total_iter.copied().for_each(f);
|
||||
}
|
||||
Pat::Record { args, .. } => {
|
||||
args.iter().map(|f| f.pat).for_each(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
687
crates/hir_def/src/find_path.rs
Normal file
687
crates/hir_def/src/find_path.rs
Normal file
|
@ -0,0 +1,687 @@
|
|||
//! An algorithm to find a path to refer to a certain item.
|
||||
|
||||
use hir_expand::name::{known, AsName, Name};
|
||||
use rustc_hash::FxHashSet;
|
||||
use test_utils::mark;
|
||||
|
||||
use crate::{
|
||||
db::DefDatabase,
|
||||
item_scope::ItemInNs,
|
||||
path::{ModPath, PathKind},
|
||||
visibility::Visibility,
|
||||
ModuleDefId, ModuleId,
|
||||
};
|
||||
|
||||
// FIXME: handle local items
|
||||
|
||||
/// Find a path that can be used to refer to a certain item. This can depend on
|
||||
/// *from where* you're referring to the item, hence the `from` parameter.
|
||||
pub fn find_path(db: &dyn DefDatabase, item: ItemInNs, from: ModuleId) -> Option<ModPath> {
|
||||
let _p = profile::span("find_path");
|
||||
find_path_inner(db, item, from, MAX_PATH_LEN)
|
||||
}
|
||||
|
||||
const MAX_PATH_LEN: usize = 15;
|
||||
|
||||
impl ModPath {
|
||||
fn starts_with_std(&self) -> bool {
|
||||
self.segments.first() == Some(&known::std)
|
||||
}
|
||||
|
||||
// When std library is present, paths starting with `std::`
|
||||
// should be preferred over paths starting with `core::` and `alloc::`
|
||||
fn can_start_with_std(&self) -> bool {
|
||||
let first_segment = self.segments.first();
|
||||
first_segment == Some(&known::alloc) || first_segment == Some(&known::core)
|
||||
}
|
||||
}
|
||||
|
||||
fn find_path_inner(
|
||||
db: &dyn DefDatabase,
|
||||
item: ItemInNs,
|
||||
from: ModuleId,
|
||||
max_len: usize,
|
||||
) -> Option<ModPath> {
|
||||
if max_len == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Base cases:
|
||||
|
||||
// - if the item is already in scope, return the name under which it is
|
||||
let def_map = db.crate_def_map(from.krate);
|
||||
let from_scope: &crate::item_scope::ItemScope = &def_map.modules[from.local_id].scope;
|
||||
if let Some((name, _)) = from_scope.name_of(item) {
|
||||
return Some(ModPath::from_segments(PathKind::Plain, vec![name.clone()]));
|
||||
}
|
||||
|
||||
// - if the item is the crate root, return `crate`
|
||||
if item
|
||||
== ItemInNs::Types(ModuleDefId::ModuleId(ModuleId {
|
||||
krate: from.krate,
|
||||
local_id: def_map.root,
|
||||
}))
|
||||
{
|
||||
return Some(ModPath::from_segments(PathKind::Crate, Vec::new()));
|
||||
}
|
||||
|
||||
// - if the item is the module we're in, use `self`
|
||||
if item == ItemInNs::Types(from.into()) {
|
||||
return Some(ModPath::from_segments(PathKind::Super(0), Vec::new()));
|
||||
}
|
||||
|
||||
// - if the item is the parent module, use `super` (this is not used recursively, since `super::super` is ugly)
|
||||
if let Some(parent_id) = def_map.modules[from.local_id].parent {
|
||||
if item
|
||||
== ItemInNs::Types(ModuleDefId::ModuleId(ModuleId {
|
||||
krate: from.krate,
|
||||
local_id: parent_id,
|
||||
}))
|
||||
{
|
||||
return Some(ModPath::from_segments(PathKind::Super(1), Vec::new()));
|
||||
}
|
||||
}
|
||||
|
||||
// - if the item is the crate root of a dependency crate, return the name from the extern prelude
|
||||
for (name, def_id) in &def_map.extern_prelude {
|
||||
if item == ItemInNs::Types(*def_id) {
|
||||
return Some(ModPath::from_segments(PathKind::Plain, vec![name.clone()]));
|
||||
}
|
||||
}
|
||||
|
||||
// - if the item is in the prelude, return the name from there
|
||||
if let Some(prelude_module) = def_map.prelude {
|
||||
let prelude_def_map = db.crate_def_map(prelude_module.krate);
|
||||
let prelude_scope: &crate::item_scope::ItemScope =
|
||||
&prelude_def_map.modules[prelude_module.local_id].scope;
|
||||
if let Some((name, vis)) = prelude_scope.name_of(item) {
|
||||
if vis.is_visible_from(db, from) {
|
||||
return Some(ModPath::from_segments(PathKind::Plain, vec![name.clone()]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// - if the item is a builtin, it's in scope
|
||||
if let ItemInNs::Types(ModuleDefId::BuiltinType(builtin)) = item {
|
||||
return Some(ModPath::from_segments(PathKind::Plain, vec![builtin.as_name()]));
|
||||
}
|
||||
|
||||
// Recursive case:
|
||||
// - if the item is an enum variant, refer to it via the enum
|
||||
if let Some(ModuleDefId::EnumVariantId(variant)) = item.as_module_def_id() {
|
||||
if let Some(mut path) = find_path(db, ItemInNs::Types(variant.parent.into()), from) {
|
||||
let data = db.enum_data(variant.parent);
|
||||
path.segments.push(data.variants[variant.local_id].name.clone());
|
||||
return Some(path);
|
||||
}
|
||||
// If this doesn't work, it seems we have no way of referring to the
|
||||
// enum; that's very weird, but there might still be a reexport of the
|
||||
// variant somewhere
|
||||
}
|
||||
|
||||
// - otherwise, look for modules containing (reexporting) it and import it from one of those
|
||||
|
||||
let crate_root = ModuleId { local_id: def_map.root, krate: from.krate };
|
||||
let crate_attrs = db.attrs(crate_root.into());
|
||||
let prefer_no_std = crate_attrs.by_key("no_std").exists();
|
||||
let mut best_path = None;
|
||||
let mut best_path_len = max_len;
|
||||
|
||||
if item.krate(db) == Some(from.krate) {
|
||||
// Item was defined in the same crate that wants to import it. It cannot be found in any
|
||||
// dependency in this case.
|
||||
|
||||
let local_imports = find_local_import_locations(db, item, from);
|
||||
for (module_id, name) in local_imports {
|
||||
if let Some(mut path) = find_path_inner(
|
||||
db,
|
||||
ItemInNs::Types(ModuleDefId::ModuleId(module_id)),
|
||||
from,
|
||||
best_path_len - 1,
|
||||
) {
|
||||
path.segments.push(name);
|
||||
|
||||
let new_path = if let Some(best_path) = best_path {
|
||||
select_best_path(best_path, path, prefer_no_std)
|
||||
} else {
|
||||
path
|
||||
};
|
||||
best_path_len = new_path.len();
|
||||
best_path = Some(new_path);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Item was defined in some upstream crate. This means that it must be exported from one,
|
||||
// too (unless we can't name it at all). It could *also* be (re)exported by the same crate
|
||||
// that wants to import it here, but we always prefer to use the external path here.
|
||||
|
||||
let crate_graph = db.crate_graph();
|
||||
let extern_paths = crate_graph[from.krate].dependencies.iter().filter_map(|dep| {
|
||||
let import_map = db.import_map(dep.crate_id);
|
||||
import_map.import_info_for(item).and_then(|info| {
|
||||
// Determine best path for containing module and append last segment from `info`.
|
||||
let mut path = find_path_inner(
|
||||
db,
|
||||
ItemInNs::Types(ModuleDefId::ModuleId(info.container)),
|
||||
from,
|
||||
best_path_len - 1,
|
||||
)?;
|
||||
path.segments.push(info.path.segments.last().unwrap().clone());
|
||||
Some(path)
|
||||
})
|
||||
});
|
||||
|
||||
for path in extern_paths {
|
||||
let new_path = if let Some(best_path) = best_path {
|
||||
select_best_path(best_path, path, prefer_no_std)
|
||||
} else {
|
||||
path
|
||||
};
|
||||
best_path = Some(new_path);
|
||||
}
|
||||
}
|
||||
|
||||
best_path
|
||||
}
|
||||
|
||||
fn select_best_path(old_path: ModPath, new_path: ModPath, prefer_no_std: bool) -> ModPath {
|
||||
if old_path.starts_with_std() && new_path.can_start_with_std() {
|
||||
if prefer_no_std {
|
||||
mark::hit!(prefer_no_std_paths);
|
||||
new_path
|
||||
} else {
|
||||
mark::hit!(prefer_std_paths);
|
||||
old_path
|
||||
}
|
||||
} else if new_path.starts_with_std() && old_path.can_start_with_std() {
|
||||
if prefer_no_std {
|
||||
mark::hit!(prefer_no_std_paths);
|
||||
old_path
|
||||
} else {
|
||||
mark::hit!(prefer_std_paths);
|
||||
new_path
|
||||
}
|
||||
} else if new_path.len() < old_path.len() {
|
||||
new_path
|
||||
} else {
|
||||
old_path
|
||||
}
|
||||
}
|
||||
|
||||
/// Finds locations in `from.krate` from which `item` can be imported by `from`.
|
||||
fn find_local_import_locations(
|
||||
db: &dyn DefDatabase,
|
||||
item: ItemInNs,
|
||||
from: ModuleId,
|
||||
) -> Vec<(ModuleId, Name)> {
|
||||
let _p = profile::span("find_local_import_locations");
|
||||
|
||||
// `from` can import anything below `from` with visibility of at least `from`, and anything
|
||||
// above `from` with any visibility. That means we do not need to descend into private siblings
|
||||
// of `from` (and similar).
|
||||
|
||||
let def_map = db.crate_def_map(from.krate);
|
||||
|
||||
// Compute the initial worklist. We start with all direct child modules of `from` as well as all
|
||||
// of its (recursive) parent modules.
|
||||
let data = &def_map.modules[from.local_id];
|
||||
let mut worklist = data
|
||||
.children
|
||||
.values()
|
||||
.map(|child| ModuleId { krate: from.krate, local_id: *child })
|
||||
.collect::<Vec<_>>();
|
||||
let mut parent = data.parent;
|
||||
while let Some(p) = parent {
|
||||
worklist.push(ModuleId { krate: from.krate, local_id: p });
|
||||
parent = def_map.modules[p].parent;
|
||||
}
|
||||
|
||||
let mut seen: FxHashSet<_> = FxHashSet::default();
|
||||
|
||||
let mut locations = Vec::new();
|
||||
while let Some(module) = worklist.pop() {
|
||||
if !seen.insert(module) {
|
||||
continue; // already processed this module
|
||||
}
|
||||
|
||||
let ext_def_map;
|
||||
let data = if module.krate == from.krate {
|
||||
&def_map[module.local_id]
|
||||
} else {
|
||||
// The crate might reexport a module defined in another crate.
|
||||
ext_def_map = db.crate_def_map(module.krate);
|
||||
&ext_def_map[module.local_id]
|
||||
};
|
||||
|
||||
if let Some((name, vis)) = data.scope.name_of(item) {
|
||||
if vis.is_visible_from(db, from) {
|
||||
let is_private = if let Visibility::Module(private_to) = vis {
|
||||
private_to.local_id == module.local_id
|
||||
} else {
|
||||
false
|
||||
};
|
||||
let is_original_def = if let Some(module_def_id) = item.as_module_def_id() {
|
||||
data.scope.declarations().any(|it| it == module_def_id)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
// Ignore private imports. these could be used if we are
|
||||
// in a submodule of this module, but that's usually not
|
||||
// what the user wants; and if this module can import
|
||||
// the item and we're a submodule of it, so can we.
|
||||
// Also this keeps the cached data smaller.
|
||||
if !is_private || is_original_def {
|
||||
locations.push((module, name.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Descend into all modules visible from `from`.
|
||||
for (_, per_ns) in data.scope.entries() {
|
||||
if let Some((ModuleDefId::ModuleId(module), vis)) = per_ns.take_types_vis() {
|
||||
if vis.is_visible_from(db, from) {
|
||||
worklist.push(module);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
locations
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use base_db::fixture::WithFixture;
|
||||
use hir_expand::hygiene::Hygiene;
|
||||
use syntax::ast::AstNode;
|
||||
use test_utils::mark;
|
||||
|
||||
use crate::test_db::TestDB;
|
||||
|
||||
use super::*;
|
||||
|
||||
/// `code` needs to contain a cursor marker; checks that `find_path` for the
|
||||
/// item the `path` refers to returns that same path when called from the
|
||||
/// module the cursor is in.
|
||||
fn check_found_path(ra_fixture: &str, path: &str) {
|
||||
let (db, pos) = TestDB::with_position(ra_fixture);
|
||||
let module = db.module_for_file(pos.file_id);
|
||||
let parsed_path_file = syntax::SourceFile::parse(&format!("use {};", path));
|
||||
let ast_path =
|
||||
parsed_path_file.syntax_node().descendants().find_map(syntax::ast::Path::cast).unwrap();
|
||||
let mod_path = ModPath::from_src(ast_path, &Hygiene::new_unhygienic()).unwrap();
|
||||
|
||||
let crate_def_map = db.crate_def_map(module.krate);
|
||||
let resolved = crate_def_map
|
||||
.resolve_path(
|
||||
&db,
|
||||
module.local_id,
|
||||
&mod_path,
|
||||
crate::item_scope::BuiltinShadowMode::Module,
|
||||
)
|
||||
.0
|
||||
.take_types()
|
||||
.unwrap();
|
||||
|
||||
let found_path = find_path(&db, ItemInNs::Types(resolved), module);
|
||||
|
||||
assert_eq!(found_path, Some(mod_path));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_module() {
|
||||
let code = r#"
|
||||
//- /main.rs
|
||||
struct S;
|
||||
<|>
|
||||
"#;
|
||||
check_found_path(code, "S");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enum_variant() {
|
||||
let code = r#"
|
||||
//- /main.rs
|
||||
enum E { A }
|
||||
<|>
|
||||
"#;
|
||||
check_found_path(code, "E::A");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sub_module() {
|
||||
let code = r#"
|
||||
//- /main.rs
|
||||
mod foo {
|
||||
pub struct S;
|
||||
}
|
||||
<|>
|
||||
"#;
|
||||
check_found_path(code, "foo::S");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn super_module() {
|
||||
let code = r#"
|
||||
//- /main.rs
|
||||
mod foo;
|
||||
//- /foo.rs
|
||||
mod bar;
|
||||
struct S;
|
||||
//- /foo/bar.rs
|
||||
<|>
|
||||
"#;
|
||||
check_found_path(code, "super::S");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn self_module() {
|
||||
let code = r#"
|
||||
//- /main.rs
|
||||
mod foo;
|
||||
//- /foo.rs
|
||||
<|>
|
||||
"#;
|
||||
check_found_path(code, "self");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn crate_root() {
|
||||
let code = r#"
|
||||
//- /main.rs
|
||||
mod foo;
|
||||
//- /foo.rs
|
||||
<|>
|
||||
"#;
|
||||
check_found_path(code, "crate");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_crate() {
|
||||
let code = r#"
|
||||
//- /main.rs
|
||||
mod foo;
|
||||
struct S;
|
||||
//- /foo.rs
|
||||
<|>
|
||||
"#;
|
||||
check_found_path(code, "crate::S");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn different_crate() {
|
||||
let code = r#"
|
||||
//- /main.rs crate:main deps:std
|
||||
<|>
|
||||
//- /std.rs crate:std
|
||||
pub struct S;
|
||||
"#;
|
||||
check_found_path(code, "std::S");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn different_crate_renamed() {
|
||||
let code = r#"
|
||||
//- /main.rs crate:main deps:std
|
||||
extern crate std as std_renamed;
|
||||
<|>
|
||||
//- /std.rs crate:std
|
||||
pub struct S;
|
||||
"#;
|
||||
check_found_path(code, "std_renamed::S");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partially_imported() {
|
||||
// Tests that short paths are used even for external items, when parts of the path are
|
||||
// already in scope.
|
||||
check_found_path(
|
||||
r#"
|
||||
//- /main.rs crate:main deps:syntax
|
||||
|
||||
use syntax::ast;
|
||||
<|>
|
||||
|
||||
//- /lib.rs crate:syntax
|
||||
pub mod ast {
|
||||
pub enum ModuleItem {
|
||||
A, B, C,
|
||||
}
|
||||
}
|
||||
"#,
|
||||
"ast::ModuleItem",
|
||||
);
|
||||
|
||||
check_found_path(
|
||||
r#"
|
||||
//- /main.rs crate:main deps:syntax
|
||||
|
||||
<|>
|
||||
|
||||
//- /lib.rs crate:syntax
|
||||
pub mod ast {
|
||||
pub enum ModuleItem {
|
||||
A, B, C,
|
||||
}
|
||||
}
|
||||
"#,
|
||||
"syntax::ast::ModuleItem",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_crate_reexport() {
|
||||
let code = r#"
|
||||
//- /main.rs
|
||||
mod bar {
|
||||
mod foo { pub(super) struct S; }
|
||||
pub(crate) use foo::*;
|
||||
}
|
||||
<|>
|
||||
"#;
|
||||
check_found_path(code, "bar::S");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_crate_reexport_rename() {
|
||||
let code = r#"
|
||||
//- /main.rs
|
||||
mod bar {
|
||||
mod foo { pub(super) struct S; }
|
||||
pub(crate) use foo::S as U;
|
||||
}
|
||||
<|>
|
||||
"#;
|
||||
check_found_path(code, "bar::U");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn different_crate_reexport() {
|
||||
let code = r#"
|
||||
//- /main.rs crate:main deps:std
|
||||
<|>
|
||||
//- /std.rs crate:std deps:core
|
||||
pub use core::S;
|
||||
//- /core.rs crate:core
|
||||
pub struct S;
|
||||
"#;
|
||||
check_found_path(code, "std::S");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prelude() {
|
||||
let code = r#"
|
||||
//- /main.rs crate:main deps:std
|
||||
<|>
|
||||
//- /std.rs crate:std
|
||||
pub mod prelude { pub struct S; }
|
||||
#[prelude_import]
|
||||
pub use prelude::*;
|
||||
"#;
|
||||
check_found_path(code, "S");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enum_variant_from_prelude() {
|
||||
let code = r#"
|
||||
//- /main.rs crate:main deps:std
|
||||
<|>
|
||||
//- /std.rs crate:std
|
||||
pub mod prelude {
|
||||
pub enum Option<T> { Some(T), None }
|
||||
pub use Option::*;
|
||||
}
|
||||
#[prelude_import]
|
||||
pub use prelude::*;
|
||||
"#;
|
||||
check_found_path(code, "None");
|
||||
check_found_path(code, "Some");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shortest_path() {
|
||||
let code = r#"
|
||||
//- /main.rs
|
||||
pub mod foo;
|
||||
pub mod baz;
|
||||
struct S;
|
||||
<|>
|
||||
//- /foo.rs
|
||||
pub mod bar { pub struct S; }
|
||||
//- /baz.rs
|
||||
pub use crate::foo::bar::S;
|
||||
"#;
|
||||
check_found_path(code, "baz::S");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn discount_private_imports() {
|
||||
let code = r#"
|
||||
//- /main.rs
|
||||
mod foo;
|
||||
pub mod bar { pub struct S; }
|
||||
use bar::S;
|
||||
//- /foo.rs
|
||||
<|>
|
||||
"#;
|
||||
// crate::S would be shorter, but using private imports seems wrong
|
||||
check_found_path(code, "crate::bar::S");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn import_cycle() {
|
||||
let code = r#"
|
||||
//- /main.rs
|
||||
pub mod foo;
|
||||
pub mod bar;
|
||||
pub mod baz;
|
||||
//- /bar.rs
|
||||
<|>
|
||||
//- /foo.rs
|
||||
pub use super::baz;
|
||||
pub struct S;
|
||||
//- /baz.rs
|
||||
pub use super::foo;
|
||||
"#;
|
||||
check_found_path(code, "crate::foo::S");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prefer_std_paths_over_alloc() {
|
||||
mark::check!(prefer_std_paths);
|
||||
let code = r#"
|
||||
//- /main.rs crate:main deps:alloc,std
|
||||
<|>
|
||||
|
||||
//- /std.rs crate:std deps:alloc
|
||||
pub mod sync {
|
||||
pub use alloc::sync::Arc;
|
||||
}
|
||||
|
||||
//- /zzz.rs crate:alloc
|
||||
pub mod sync {
|
||||
pub struct Arc;
|
||||
}
|
||||
"#;
|
||||
check_found_path(code, "std::sync::Arc");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prefer_core_paths_over_std() {
|
||||
mark::check!(prefer_no_std_paths);
|
||||
let code = r#"
|
||||
//- /main.rs crate:main deps:core,std
|
||||
#![no_std]
|
||||
|
||||
<|>
|
||||
|
||||
//- /std.rs crate:std deps:core
|
||||
|
||||
pub mod fmt {
|
||||
pub use core::fmt::Error;
|
||||
}
|
||||
|
||||
//- /zzz.rs crate:core
|
||||
|
||||
pub mod fmt {
|
||||
pub struct Error;
|
||||
}
|
||||
"#;
|
||||
check_found_path(code, "core::fmt::Error");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prefer_alloc_paths_over_std() {
|
||||
let code = r#"
|
||||
//- /main.rs crate:main deps:alloc,std
|
||||
#![no_std]
|
||||
|
||||
<|>
|
||||
|
||||
//- /std.rs crate:std deps:alloc
|
||||
|
||||
pub mod sync {
|
||||
pub use alloc::sync::Arc;
|
||||
}
|
||||
|
||||
//- /zzz.rs crate:alloc
|
||||
|
||||
pub mod sync {
|
||||
pub struct Arc;
|
||||
}
|
||||
"#;
|
||||
check_found_path(code, "alloc::sync::Arc");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prefer_shorter_paths_if_not_alloc() {
|
||||
let code = r#"
|
||||
//- /main.rs crate:main deps:megaalloc,std
|
||||
<|>
|
||||
|
||||
//- /std.rs crate:std deps:megaalloc
|
||||
pub mod sync {
|
||||
pub use megaalloc::sync::Arc;
|
||||
}
|
||||
|
||||
//- /zzz.rs crate:megaalloc
|
||||
pub struct Arc;
|
||||
"#;
|
||||
check_found_path(code, "megaalloc::Arc");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builtins_are_in_scope() {
|
||||
let code = r#"
|
||||
//- /main.rs
|
||||
<|>
|
||||
|
||||
pub mod primitive {
|
||||
pub use u8;
|
||||
}
|
||||
"#;
|
||||
check_found_path(code, "u8");
|
||||
check_found_path(code, "u16");
|
||||
}
|
||||
}
|
339
crates/hir_def/src/generics.rs
Normal file
339
crates/hir_def/src/generics.rs
Normal file
|
@ -0,0 +1,339 @@
|
|||
//! Many kinds of items or constructs can have generic parameters: functions,
|
||||
//! structs, impls, traits, etc. This module provides a common HIR for these
|
||||
//! generic parameters. See also the `Generics` type and the `generics_of` query
|
||||
//! in rustc.
|
||||
use std::sync::Arc;
|
||||
|
||||
use arena::{map::ArenaMap, Arena};
|
||||
use base_db::FileId;
|
||||
use either::Either;
|
||||
use hir_expand::{
|
||||
name::{name, AsName, Name},
|
||||
InFile,
|
||||
};
|
||||
use syntax::ast::{self, GenericParamsOwner, NameOwner, TypeBoundsOwner};
|
||||
|
||||
use crate::{
|
||||
body::LowerCtx,
|
||||
child_by_source::ChildBySource,
|
||||
db::DefDatabase,
|
||||
dyn_map::DynMap,
|
||||
keys,
|
||||
src::HasChildSource,
|
||||
src::HasSource,
|
||||
type_ref::{TypeBound, TypeRef},
|
||||
AdtId, GenericDefId, LocalTypeParamId, Lookup, TypeParamId,
|
||||
};
|
||||
|
||||
/// Data about a generic parameter (to a function, struct, impl, ...).
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct TypeParamData {
|
||||
pub name: Option<Name>,
|
||||
pub default: Option<TypeRef>,
|
||||
pub provenance: TypeParamProvenance,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub enum TypeParamProvenance {
|
||||
TypeParamList,
|
||||
TraitSelf,
|
||||
ArgumentImplTrait,
|
||||
}
|
||||
|
||||
/// Data about the generic parameters of a function, struct, impl, etc.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Default)]
|
||||
pub struct GenericParams {
|
||||
pub types: Arena<TypeParamData>,
|
||||
// lifetimes: Arena<LocalLifetimeParamId, LifetimeParamData>,
|
||||
pub where_predicates: Vec<WherePredicate>,
|
||||
}
|
||||
|
||||
/// A single predicate from a where clause, i.e. `where Type: Trait`. Combined
|
||||
/// where clauses like `where T: Foo + Bar` are turned into multiple of these.
|
||||
/// It might still result in multiple actual predicates though, because of
|
||||
/// associated type bindings like `Iterator<Item = u32>`.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct WherePredicate {
|
||||
pub target: WherePredicateTarget,
|
||||
pub bound: TypeBound,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub enum WherePredicateTarget {
|
||||
TypeRef(TypeRef),
|
||||
/// For desugared where predicates that can directly refer to a type param.
|
||||
TypeParam(LocalTypeParamId),
|
||||
}
|
||||
|
||||
type SourceMap = ArenaMap<LocalTypeParamId, Either<ast::Trait, ast::TypeParam>>;
|
||||
|
||||
impl GenericParams {
|
||||
pub(crate) fn generic_params_query(
|
||||
db: &dyn DefDatabase,
|
||||
def: GenericDefId,
|
||||
) -> Arc<GenericParams> {
|
||||
let _p = profile::span("generic_params_query");
|
||||
|
||||
let generics = match def {
|
||||
GenericDefId::FunctionId(id) => {
|
||||
let id = id.lookup(db).id;
|
||||
let tree = db.item_tree(id.file_id);
|
||||
let item = &tree[id.value];
|
||||
tree[item.generic_params].clone()
|
||||
}
|
||||
GenericDefId::AdtId(AdtId::StructId(id)) => {
|
||||
let id = id.lookup(db).id;
|
||||
let tree = db.item_tree(id.file_id);
|
||||
let item = &tree[id.value];
|
||||
tree[item.generic_params].clone()
|
||||
}
|
||||
GenericDefId::AdtId(AdtId::EnumId(id)) => {
|
||||
let id = id.lookup(db).id;
|
||||
let tree = db.item_tree(id.file_id);
|
||||
let item = &tree[id.value];
|
||||
tree[item.generic_params].clone()
|
||||
}
|
||||
GenericDefId::AdtId(AdtId::UnionId(id)) => {
|
||||
let id = id.lookup(db).id;
|
||||
let tree = db.item_tree(id.file_id);
|
||||
let item = &tree[id.value];
|
||||
tree[item.generic_params].clone()
|
||||
}
|
||||
GenericDefId::TraitId(id) => {
|
||||
let id = id.lookup(db).id;
|
||||
let tree = db.item_tree(id.file_id);
|
||||
let item = &tree[id.value];
|
||||
tree[item.generic_params].clone()
|
||||
}
|
||||
GenericDefId::TypeAliasId(id) => {
|
||||
let id = id.lookup(db).id;
|
||||
let tree = db.item_tree(id.file_id);
|
||||
let item = &tree[id.value];
|
||||
tree[item.generic_params].clone()
|
||||
}
|
||||
GenericDefId::ImplId(id) => {
|
||||
let id = id.lookup(db).id;
|
||||
let tree = db.item_tree(id.file_id);
|
||||
let item = &tree[id.value];
|
||||
tree[item.generic_params].clone()
|
||||
}
|
||||
GenericDefId::EnumVariantId(_) | GenericDefId::ConstId(_) => GenericParams::default(),
|
||||
};
|
||||
Arc::new(generics)
|
||||
}
|
||||
|
||||
fn new(db: &dyn DefDatabase, def: GenericDefId) -> (GenericParams, InFile<SourceMap>) {
|
||||
let mut generics = GenericParams { types: Arena::default(), where_predicates: Vec::new() };
|
||||
let mut sm = ArenaMap::default();
|
||||
|
||||
// FIXME: add `: Sized` bound for everything except for `Self` in traits
|
||||
let file_id = match def {
|
||||
GenericDefId::FunctionId(it) => {
|
||||
let src = it.lookup(db).source(db);
|
||||
let lower_ctx = LowerCtx::new(db, src.file_id);
|
||||
generics.fill(&lower_ctx, &mut sm, &src.value);
|
||||
// lower `impl Trait` in arguments
|
||||
let data = db.function_data(it);
|
||||
for param in &data.params {
|
||||
generics.fill_implicit_impl_trait_args(param);
|
||||
}
|
||||
src.file_id
|
||||
}
|
||||
GenericDefId::AdtId(AdtId::StructId(it)) => {
|
||||
let src = it.lookup(db).source(db);
|
||||
let lower_ctx = LowerCtx::new(db, src.file_id);
|
||||
generics.fill(&lower_ctx, &mut sm, &src.value);
|
||||
src.file_id
|
||||
}
|
||||
GenericDefId::AdtId(AdtId::UnionId(it)) => {
|
||||
let src = it.lookup(db).source(db);
|
||||
let lower_ctx = LowerCtx::new(db, src.file_id);
|
||||
generics.fill(&lower_ctx, &mut sm, &src.value);
|
||||
src.file_id
|
||||
}
|
||||
GenericDefId::AdtId(AdtId::EnumId(it)) => {
|
||||
let src = it.lookup(db).source(db);
|
||||
let lower_ctx = LowerCtx::new(db, src.file_id);
|
||||
generics.fill(&lower_ctx, &mut sm, &src.value);
|
||||
src.file_id
|
||||
}
|
||||
GenericDefId::TraitId(it) => {
|
||||
let src = it.lookup(db).source(db);
|
||||
let lower_ctx = LowerCtx::new(db, src.file_id);
|
||||
|
||||
// traits get the Self type as an implicit first type parameter
|
||||
let self_param_id = generics.types.alloc(TypeParamData {
|
||||
name: Some(name![Self]),
|
||||
default: None,
|
||||
provenance: TypeParamProvenance::TraitSelf,
|
||||
});
|
||||
sm.insert(self_param_id, Either::Left(src.value.clone()));
|
||||
// add super traits as bounds on Self
|
||||
// i.e., trait Foo: Bar is equivalent to trait Foo where Self: Bar
|
||||
let self_param = TypeRef::Path(name![Self].into());
|
||||
generics.fill_bounds(&lower_ctx, &src.value, self_param);
|
||||
|
||||
generics.fill(&lower_ctx, &mut sm, &src.value);
|
||||
src.file_id
|
||||
}
|
||||
GenericDefId::TypeAliasId(it) => {
|
||||
let src = it.lookup(db).source(db);
|
||||
let lower_ctx = LowerCtx::new(db, src.file_id);
|
||||
|
||||
generics.fill(&lower_ctx, &mut sm, &src.value);
|
||||
src.file_id
|
||||
}
|
||||
// Note that we don't add `Self` here: in `impl`s, `Self` is not a
|
||||
// type-parameter, but rather is a type-alias for impl's target
|
||||
// type, so this is handled by the resolver.
|
||||
GenericDefId::ImplId(it) => {
|
||||
let src = it.lookup(db).source(db);
|
||||
let lower_ctx = LowerCtx::new(db, src.file_id);
|
||||
|
||||
generics.fill(&lower_ctx, &mut sm, &src.value);
|
||||
src.file_id
|
||||
}
|
||||
// We won't be using this ID anyway
|
||||
GenericDefId::EnumVariantId(_) | GenericDefId::ConstId(_) => FileId(!0).into(),
|
||||
};
|
||||
|
||||
(generics, InFile::new(file_id, sm))
|
||||
}
|
||||
|
||||
pub(crate) fn fill(
|
||||
&mut self,
|
||||
lower_ctx: &LowerCtx,
|
||||
sm: &mut SourceMap,
|
||||
node: &dyn GenericParamsOwner,
|
||||
) {
|
||||
if let Some(params) = node.generic_param_list() {
|
||||
self.fill_params(lower_ctx, sm, params)
|
||||
}
|
||||
if let Some(where_clause) = node.where_clause() {
|
||||
self.fill_where_predicates(lower_ctx, where_clause);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn fill_bounds(
|
||||
&mut self,
|
||||
lower_ctx: &LowerCtx,
|
||||
node: &dyn ast::TypeBoundsOwner,
|
||||
type_ref: TypeRef,
|
||||
) {
|
||||
for bound in
|
||||
node.type_bound_list().iter().flat_map(|type_bound_list| type_bound_list.bounds())
|
||||
{
|
||||
self.add_where_predicate_from_bound(lower_ctx, bound, type_ref.clone());
|
||||
}
|
||||
}
|
||||
|
||||
fn fill_params(
|
||||
&mut self,
|
||||
lower_ctx: &LowerCtx,
|
||||
sm: &mut SourceMap,
|
||||
params: ast::GenericParamList,
|
||||
) {
|
||||
for type_param in params.type_params() {
|
||||
let name = type_param.name().map_or_else(Name::missing, |it| it.as_name());
|
||||
// FIXME: Use `Path::from_src`
|
||||
let default = type_param.default_type().map(|it| TypeRef::from_ast(lower_ctx, it));
|
||||
let param = TypeParamData {
|
||||
name: Some(name.clone()),
|
||||
default,
|
||||
provenance: TypeParamProvenance::TypeParamList,
|
||||
};
|
||||
let param_id = self.types.alloc(param);
|
||||
sm.insert(param_id, Either::Right(type_param.clone()));
|
||||
|
||||
let type_ref = TypeRef::Path(name.into());
|
||||
self.fill_bounds(&lower_ctx, &type_param, type_ref);
|
||||
}
|
||||
}
|
||||
|
||||
fn fill_where_predicates(&mut self, lower_ctx: &LowerCtx, where_clause: ast::WhereClause) {
|
||||
for pred in where_clause.predicates() {
|
||||
let type_ref = match pred.ty() {
|
||||
Some(type_ref) => type_ref,
|
||||
None => continue,
|
||||
};
|
||||
let type_ref = TypeRef::from_ast(lower_ctx, type_ref);
|
||||
for bound in pred.type_bound_list().iter().flat_map(|l| l.bounds()) {
|
||||
self.add_where_predicate_from_bound(lower_ctx, bound, type_ref.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_where_predicate_from_bound(
|
||||
&mut self,
|
||||
lower_ctx: &LowerCtx,
|
||||
bound: ast::TypeBound,
|
||||
type_ref: TypeRef,
|
||||
) {
|
||||
if bound.question_mark_token().is_some() {
|
||||
// FIXME: remove this bound
|
||||
return;
|
||||
}
|
||||
let bound = TypeBound::from_ast(lower_ctx, bound);
|
||||
self.where_predicates
|
||||
.push(WherePredicate { target: WherePredicateTarget::TypeRef(type_ref), bound });
|
||||
}
|
||||
|
||||
pub(crate) fn fill_implicit_impl_trait_args(&mut self, type_ref: &TypeRef) {
|
||||
type_ref.walk(&mut |type_ref| {
|
||||
if let TypeRef::ImplTrait(bounds) = type_ref {
|
||||
let param = TypeParamData {
|
||||
name: None,
|
||||
default: None,
|
||||
provenance: TypeParamProvenance::ArgumentImplTrait,
|
||||
};
|
||||
let param_id = self.types.alloc(param);
|
||||
for bound in bounds {
|
||||
self.where_predicates.push(WherePredicate {
|
||||
target: WherePredicateTarget::TypeParam(param_id),
|
||||
bound: bound.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn find_by_name(&self, name: &Name) -> Option<LocalTypeParamId> {
|
||||
self.types
|
||||
.iter()
|
||||
.find_map(|(id, p)| if p.name.as_ref() == Some(name) { Some(id) } else { None })
|
||||
}
|
||||
|
||||
pub fn find_trait_self_param(&self) -> Option<LocalTypeParamId> {
|
||||
self.types.iter().find_map(|(id, p)| {
|
||||
if p.provenance == TypeParamProvenance::TraitSelf {
|
||||
Some(id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl HasChildSource for GenericDefId {
|
||||
type ChildId = LocalTypeParamId;
|
||||
type Value = Either<ast::Trait, ast::TypeParam>;
|
||||
fn child_source(&self, db: &dyn DefDatabase) -> InFile<SourceMap> {
|
||||
let (_, sm) = GenericParams::new(db, *self);
|
||||
sm
|
||||
}
|
||||
}
|
||||
|
||||
impl ChildBySource for GenericDefId {
|
||||
fn child_by_source(&self, db: &dyn DefDatabase) -> DynMap {
|
||||
let mut res = DynMap::default();
|
||||
let arena_map = self.child_source(db);
|
||||
let arena_map = arena_map.as_ref();
|
||||
for (local_id, src) in arena_map.value.iter() {
|
||||
let id = TypeParamId { parent: *self, local_id };
|
||||
if let Either::Right(type_param) = src {
|
||||
res[keys::TYPE_PARAM].insert(arena_map.with_value(type_param.clone()), id)
|
||||
}
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
745
crates/hir_def/src/import_map.rs
Normal file
745
crates/hir_def/src/import_map.rs
Normal file
|
@ -0,0 +1,745 @@
|
|||
//! A map of all publicly exported items in a crate.
|
||||
|
||||
use std::{cmp::Ordering, fmt, hash::BuildHasherDefault, sync::Arc};
|
||||
|
||||
use base_db::CrateId;
|
||||
use fst::{self, Streamer};
|
||||
use indexmap::{map::Entry, IndexMap};
|
||||
use rustc_hash::{FxHashMap, FxHasher};
|
||||
use smallvec::SmallVec;
|
||||
use syntax::SmolStr;
|
||||
|
||||
use crate::{
|
||||
db::DefDatabase,
|
||||
item_scope::ItemInNs,
|
||||
path::{ModPath, PathKind},
|
||||
visibility::Visibility,
|
||||
AssocItemId, ModuleDefId, ModuleId, TraitId,
|
||||
};
|
||||
|
||||
type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<FxHasher>>;
|
||||
|
||||
/// Item import details stored in the `ImportMap`.
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct ImportInfo {
|
||||
/// A path that can be used to import the item, relative to the crate's root.
|
||||
pub path: ModPath,
|
||||
/// The module containing this item.
|
||||
pub container: ModuleId,
|
||||
}
|
||||
|
||||
/// A map from publicly exported items to the path needed to import/name them from a downstream
|
||||
/// crate.
|
||||
///
|
||||
/// Reexports of items are taken into account, ie. if something is exported under multiple
|
||||
/// names, the one with the shortest import path will be used.
|
||||
///
|
||||
/// 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>,
|
||||
|
||||
/// List of keys stored in `map`, sorted lexicographically by their `ModPath`. Indexed by the
|
||||
/// values returned by running `fst`.
|
||||
///
|
||||
/// Since a path can refer to multiple items due to namespacing, we store all items with the
|
||||
/// same path right after each other. This allows us to find all items after the FST gives us
|
||||
/// 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 = profile::span("import_map_query");
|
||||
let def_map = db.crate_def_map(krate);
|
||||
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![] };
|
||||
let root = ModuleId { krate, local_id: def_map.root };
|
||||
let mut worklist = vec![(root, empty)];
|
||||
while let Some((module, mod_path)) = worklist.pop() {
|
||||
let ext_def_map;
|
||||
let mod_data = if module.krate == krate {
|
||||
&def_map[module.local_id]
|
||||
} else {
|
||||
// The crate might reexport a module defined in another crate.
|
||||
ext_def_map = db.crate_def_map(module.krate);
|
||||
&ext_def_map[module.local_id]
|
||||
};
|
||||
|
||||
let visible_items = mod_data.scope.entries().filter_map(|(name, per_ns)| {
|
||||
let per_ns = per_ns.filter_visibility(|vis| vis == Visibility::Public);
|
||||
if per_ns.is_none() {
|
||||
None
|
||||
} else {
|
||||
Some((name, per_ns))
|
||||
}
|
||||
});
|
||||
|
||||
for (name, per_ns) in visible_items {
|
||||
let mk_path = || {
|
||||
let mut path = mod_path.clone();
|
||||
path.segments.push(name.clone());
|
||||
path
|
||||
};
|
||||
|
||||
for item in per_ns.iter_items() {
|
||||
let path = mk_path();
|
||||
match import_map.map.entry(item) {
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert(ImportInfo { path, container: module });
|
||||
}
|
||||
Entry::Occupied(mut entry) => {
|
||||
// If the new path is shorter, prefer that one.
|
||||
if path.len() < entry.get().path.len() {
|
||||
*entry.get_mut() = ImportInfo { path, container: module };
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we've just added a path to a module, descend into it. We might traverse
|
||||
// modules multiple times, but only if the new path to it is shorter than the
|
||||
// first (else we `continue` above).
|
||||
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.map.iter().collect::<Vec<_>>();
|
||||
|
||||
importables.sort_by(cmp);
|
||||
|
||||
// Build the FST, taking care not to insert duplicate values.
|
||||
|
||||
let mut builder = fst::MapBuilder::memory();
|
||||
let mut last_batch_start = 0;
|
||||
|
||||
for idx in 0..importables.len() {
|
||||
if let Some(next_item) = importables.get(idx + 1) {
|
||||
if cmp(&importables[last_batch_start], next_item) == Ordering::Equal {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let start = last_batch_start;
|
||||
last_batch_start = idx + 1;
|
||||
|
||||
let key = fst_path(&importables[start].1.path);
|
||||
|
||||
builder.insert(key, start as u64).unwrap();
|
||||
}
|
||||
|
||||
import_map.fst = fst::Map::new(builder.into_inner().unwrap()).unwrap();
|
||||
import_map.importables = importables.iter().map(|(item, _)| **item).collect();
|
||||
|
||||
Arc::new(import_map)
|
||||
}
|
||||
|
||||
/// Returns the `ModPath` needed to import/mention `item`, relative to this crate's root.
|
||||
pub fn path_of(&self, item: ItemInNs) -> Option<&ModPath> {
|
||||
Some(&self.map.get(&item)?.path)
|
||||
}
|
||||
|
||||
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 {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
// `fst` and `importables` are built from `map`, so we don't need to compare them.
|
||||
self.map == other.map
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for ImportMap {}
|
||||
|
||||
impl fmt::Debug for ImportMap {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut importable_paths: Vec<_> = self
|
||||
.map
|
||||
.iter()
|
||||
.map(|(item, info)| {
|
||||
let ns = match item {
|
||||
ItemInNs::Types(_) => "t",
|
||||
ItemInNs::Values(_) => "v",
|
||||
ItemInNs::Macros(_) => "m",
|
||||
};
|
||||
format!("- {} ({})", info.path, ns)
|
||||
})
|
||||
.collect();
|
||||
|
||||
importable_paths.sort();
|
||||
f.write_str(&importable_paths.join("\n"))
|
||||
}
|
||||
}
|
||||
|
||||
fn fst_path(path: &ModPath) -> String {
|
||||
let mut s = path.to_string();
|
||||
s.make_ascii_lowercase();
|
||||
s
|
||||
}
|
||||
|
||||
fn cmp((_, lhs): &(&ItemInNs, &ImportInfo), (_, rhs): &(&ItemInNs, &ImportInfo)) -> Ordering {
|
||||
let lhs_str = fst_path(&lhs.path);
|
||||
let rhs_str = fst_path(&rhs.path);
|
||||
lhs_str.cmp(&rhs_str)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Query {
|
||||
query: String,
|
||||
lowercased: String,
|
||||
anchor_end: bool,
|
||||
case_sensitive: bool,
|
||||
limit: usize,
|
||||
}
|
||||
|
||||
impl Query {
|
||||
pub fn new(query: &str) -> Self {
|
||||
Self {
|
||||
lowercased: query.to_lowercase(),
|
||||
query: query.to_string(),
|
||||
anchor_end: false,
|
||||
case_sensitive: false,
|
||||
limit: usize::max_value(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Only returns items whose paths end with the (case-insensitive) query string as their last
|
||||
/// segment.
|
||||
pub fn anchor_end(self) -> Self {
|
||||
Self { anchor_end: true, ..self }
|
||||
}
|
||||
|
||||
/// Limits the returned number of items to `limit`.
|
||||
pub fn limit(self, limit: usize) -> Self {
|
||||
Self { limit, ..self }
|
||||
}
|
||||
|
||||
/// Respect casing of the query string when matching.
|
||||
pub fn case_sensitive(self) -> Self {
|
||||
Self { case_sensitive: true, ..self }
|
||||
}
|
||||
}
|
||||
|
||||
/// Searches dependencies of `krate` for an importable path matching `query`.
|
||||
///
|
||||
/// This returns a list of items that could be imported from dependencies of `krate`.
|
||||
pub fn search_dependencies<'a>(
|
||||
db: &'a dyn DefDatabase,
|
||||
krate: CrateId,
|
||||
query: Query,
|
||||
) -> Vec<ItemInNs> {
|
||||
let _p = profile::span("search_dependencies").detail(|| format!("{:?}", query));
|
||||
|
||||
let graph = db.crate_graph();
|
||||
let import_maps: Vec<_> =
|
||||
graph[krate].dependencies.iter().map(|dep| db.import_map(dep.crate_id)).collect();
|
||||
|
||||
let automaton = fst::automaton::Subsequence::new(&query.lowercased);
|
||||
|
||||
let mut op = fst::map::OpBuilder::new();
|
||||
for map in &import_maps {
|
||||
op = op.add(map.fst.search(&automaton));
|
||||
}
|
||||
|
||||
let mut stream = op.union();
|
||||
let mut res = Vec::new();
|
||||
while let Some((_, indexed_values)) = stream.next() {
|
||||
for indexed_value in indexed_values {
|
||||
let import_map = &import_maps[indexed_value.index];
|
||||
let importables = &import_map.importables[indexed_value.value as usize..];
|
||||
|
||||
// Path shared by the importable items in this group.
|
||||
let path = &import_map.map[&importables[0]].path;
|
||||
|
||||
if query.anchor_end {
|
||||
// Last segment must match query.
|
||||
let last = path.segments.last().unwrap().to_string();
|
||||
if last.to_lowercase() != query.lowercased {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the items from this `ModPath` group. Those are all subsequent items in
|
||||
// `importables` whose paths match `path`.
|
||||
let iter = importables.iter().copied().take_while(|item| {
|
||||
let item_path = &import_map.map[item].path;
|
||||
fst_path(item_path) == fst_path(path)
|
||||
});
|
||||
|
||||
if query.case_sensitive {
|
||||
// FIXME: This does not do a subsequence match.
|
||||
res.extend(iter.filter(|item| {
|
||||
let item_path = &import_map.map[item].path;
|
||||
item_path.to_string().contains(&query.query)
|
||||
}));
|
||||
} else {
|
||||
res.extend(iter);
|
||||
}
|
||||
|
||||
if res.len() >= query.limit {
|
||||
res.truncate(query.limit);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 base_db::{fixture::WithFixture, SourceDatabase, Upcast};
|
||||
use expect::{expect, Expect};
|
||||
|
||||
use crate::{test_db::TestDB, AssocContainerId, Lookup};
|
||||
|
||||
use super::*;
|
||||
|
||||
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
|
||||
.iter()
|
||||
.find(|krate| {
|
||||
crate_graph[*krate].display_name.as_ref().map(|n| n.to_string())
|
||||
== Some(krate_name.to_string())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let actual = search_dependencies(db.upcast(), krate, query)
|
||||
.into_iter()
|
||||
.filter_map(|item| {
|
||||
let mark = match item {
|
||||
ItemInNs::Types(_) => "t",
|
||||
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
|
||||
)
|
||||
})
|
||||
})
|
||||
.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() {
|
||||
check(
|
||||
r"
|
||||
//- /main.rs crate:main deps:lib
|
||||
|
||||
mod private {
|
||||
pub use lib::Pub;
|
||||
pub struct InPrivateModule;
|
||||
}
|
||||
|
||||
pub mod publ1 {
|
||||
use lib::Pub;
|
||||
}
|
||||
|
||||
pub mod real_pub {
|
||||
pub use lib::Pub;
|
||||
}
|
||||
pub mod real_pu2 { // same path length as above
|
||||
pub use lib::Pub;
|
||||
}
|
||||
|
||||
//- /lib.rs crate:lib
|
||||
pub struct Pub {}
|
||||
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)
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prefers_shortest_path() {
|
||||
check(
|
||||
r"
|
||||
//- /main.rs crate:main
|
||||
|
||||
pub mod sub {
|
||||
pub mod subsub {
|
||||
pub struct Def {}
|
||||
}
|
||||
|
||||
pub use super::sub::subsub::Def;
|
||||
}
|
||||
",
|
||||
expect![[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.
|
||||
check(
|
||||
r"
|
||||
//- /main.rs crate:main deps:lib
|
||||
pub mod m {
|
||||
pub use lib::S;
|
||||
}
|
||||
//- /lib.rs crate:lib
|
||||
pub struct S;
|
||||
",
|
||||
expect![[r#"
|
||||
main:
|
||||
- m (t)
|
||||
- m::S (t)
|
||||
- m::S (v)
|
||||
lib:
|
||||
- S (t)
|
||||
- S (v)
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macro_reexport() {
|
||||
check(
|
||||
r"
|
||||
//- /main.rs crate:main deps:lib
|
||||
pub mod m {
|
||||
pub use lib::pub_macro;
|
||||
}
|
||||
//- /lib.rs crate:lib
|
||||
#[macro_export]
|
||||
macro_rules! pub_macro {
|
||||
() => {};
|
||||
}
|
||||
",
|
||||
expect![[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.
|
||||
check(
|
||||
r"
|
||||
//- /main.rs crate:main deps:lib
|
||||
pub use lib::module as reexported_module;
|
||||
//- /lib.rs crate:lib
|
||||
pub mod module {
|
||||
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)
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cyclic_module_reexport() {
|
||||
// A cyclic reexport does not hang.
|
||||
check(
|
||||
r"
|
||||
//- /lib.rs crate:lib
|
||||
pub mod module {
|
||||
pub struct S;
|
||||
pub use super::sub::*;
|
||||
}
|
||||
|
||||
pub mod sub {
|
||||
pub use super::module;
|
||||
}
|
||||
",
|
||||
expect![[r#"
|
||||
lib:
|
||||
- module (t)
|
||||
- module::S (t)
|
||||
- module::S (v)
|
||||
- sub (t)
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_macro() {
|
||||
check(
|
||||
r"
|
||||
//- /lib.rs crate:lib
|
||||
macro_rules! private_macro {
|
||||
() => {};
|
||||
}
|
||||
",
|
||||
expect![[r#"
|
||||
lib:
|
||||
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn namespacing() {
|
||||
check(
|
||||
r"
|
||||
//- /lib.rs crate:lib
|
||||
pub struct Thing; // t + v
|
||||
#[macro_export]
|
||||
macro_rules! Thing { // m
|
||||
() => {};
|
||||
}
|
||||
",
|
||||
expect![[r#"
|
||||
lib:
|
||||
- Thing (m)
|
||||
- Thing (t)
|
||||
- Thing (v)
|
||||
"#]],
|
||||
);
|
||||
|
||||
check(
|
||||
r"
|
||||
//- /lib.rs crate:lib
|
||||
pub mod Thing {} // t
|
||||
#[macro_export]
|
||||
macro_rules! Thing { // m
|
||||
() => {};
|
||||
}
|
||||
",
|
||||
expect![[r#"
|
||||
lib:
|
||||
- Thing (m)
|
||||
- Thing (t)
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn search() {
|
||||
let ra_fixture = r#"
|
||||
//- /main.rs crate:main deps:dep
|
||||
//- /dep.rs crate:dep deps:tdep
|
||||
use tdep::fmt as fmt_dep;
|
||||
pub mod fmt {
|
||||
pub trait Display {
|
||||
fn fmt();
|
||||
}
|
||||
}
|
||||
#[macro_export]
|
||||
macro_rules! Fmt {
|
||||
() => {};
|
||||
}
|
||||
pub struct Fmt;
|
||||
|
||||
pub fn format() {}
|
||||
pub fn no() {}
|
||||
|
||||
//- /tdep.rs crate:tdep
|
||||
pub mod fmt {
|
||||
pub struct NotImportableFromMain;
|
||||
}
|
||||
"#;
|
||||
|
||||
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)
|
||||
"#]],
|
||||
);
|
||||
|
||||
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]
|
||||
fn search_casing() {
|
||||
let ra_fixture = r#"
|
||||
//- /main.rs crate:main deps:dep
|
||||
//- /dep.rs crate:dep
|
||||
|
||||
pub struct fmt;
|
||||
pub struct FMT;
|
||||
"#;
|
||||
|
||||
check_search(
|
||||
ra_fixture,
|
||||
"main",
|
||||
Query::new("FMT"),
|
||||
expect![[r#"
|
||||
dep::fmt (t)
|
||||
dep::fmt (v)
|
||||
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() {
|
||||
check_search(
|
||||
r#"
|
||||
//- /main.rs crate:main deps:dep
|
||||
//- /dep.rs crate:dep
|
||||
pub mod fmt {
|
||||
pub trait Display {
|
||||
fn fmt();
|
||||
}
|
||||
}
|
||||
#[macro_export]
|
||||
macro_rules! Fmt {
|
||||
() => {};
|
||||
}
|
||||
pub struct Fmt;
|
||||
|
||||
pub fn format() {}
|
||||
pub fn no() {}
|
||||
"#,
|
||||
"main",
|
||||
Query::new("").limit(2),
|
||||
expect![[r#"
|
||||
dep::fmt (t)
|
||||
dep::Fmt (t)
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
}
|
341
crates/hir_def/src/item_scope.rs
Normal file
341
crates/hir_def/src/item_scope.rs
Normal file
|
@ -0,0 +1,341 @@
|
|||
//! 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 base_db::CrateId;
|
||||
use hir_expand::name::Name;
|
||||
use once_cell::sync::Lazy;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use test_utils::mark;
|
||||
|
||||
use crate::{
|
||||
db::DefDatabase, per_ns::PerNs, visibility::Visibility, AdtId, BuiltinType, HasModule, ImplId,
|
||||
LocalModuleId, Lookup, MacroDefId, ModuleDefId, TraitId,
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) enum ImportType {
|
||||
Glob,
|
||||
Named,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct PerNsGlobImports {
|
||||
types: FxHashSet<(LocalModuleId, Name)>,
|
||||
values: FxHashSet<(LocalModuleId, Name)>,
|
||||
macros: FxHashSet<(LocalModuleId, Name)>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
pub struct ItemScope {
|
||||
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.
|
||||
/// If it yields no result, then it turns to module scoped `macros`.
|
||||
/// It macros with name qualified with a path like `crate::foo::bar!()`, `legacy_macros` will be skipped,
|
||||
/// and only normal scoped `macros` will be searched in.
|
||||
///
|
||||
/// Note that this automatically inherit macros defined textually before the definition of module itself.
|
||||
///
|
||||
/// Module scoped macros will be inserted into `items` instead of here.
|
||||
// FIXME: Macro shadowing in one module is not properly handled. Non-item place macros will
|
||||
// be all resolved to the last one defined if shadowing happens.
|
||||
legacy_macros: FxHashMap<Name, MacroDefId>,
|
||||
}
|
||||
|
||||
pub(crate) static BUILTIN_SCOPE: Lazy<FxHashMap<Name, PerNs>> = Lazy::new(|| {
|
||||
BuiltinType::ALL
|
||||
.iter()
|
||||
.map(|(name, ty)| (name.clone(), PerNs::types(ty.clone().into(), Visibility::Public)))
|
||||
.collect()
|
||||
});
|
||||
|
||||
/// Shadow mode for builtin type which can be shadowed by module.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub(crate) enum BuiltinShadowMode {
|
||||
/// Prefer user-defined modules (or other types) over builtins.
|
||||
Module,
|
||||
/// Prefer builtins over user-defined modules (but not other types).
|
||||
Other,
|
||||
}
|
||||
|
||||
/// Legacy macros can only be accessed through special methods like `get_legacy_macros`.
|
||||
/// 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
|
||||
let keys: FxHashSet<_> = self
|
||||
.types
|
||||
.keys()
|
||||
.chain(self.values.keys())
|
||||
.chain(self.macros.keys())
|
||||
.chain(self.unresolved.iter())
|
||||
.collect();
|
||||
|
||||
keys.into_iter().map(move |name| (name, self.get(name)))
|
||||
}
|
||||
|
||||
pub fn declarations(&self) -> impl Iterator<Item = ModuleDefId> + '_ {
|
||||
self.defs.iter().copied()
|
||||
}
|
||||
|
||||
pub fn impls(&self) -> impl Iterator<Item = ImplId> + ExactSizeIterator + '_ {
|
||||
self.impls.iter().copied()
|
||||
}
|
||||
|
||||
pub fn visibility_of(&self, def: ModuleDefId) -> Option<Visibility> {
|
||||
self.name_of(ItemInNs::Types(def))
|
||||
.or_else(|| self.name_of(ItemInNs::Values(def)))
|
||||
.map(|(_, v)| v)
|
||||
}
|
||||
|
||||
/// Iterate over all module scoped macros
|
||||
pub(crate) fn macros<'a>(&'a self) -> impl Iterator<Item = (&'a Name, MacroDefId)> + 'a {
|
||||
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
|
||||
pub(crate) fn legacy_macros<'a>(&'a self) -> impl Iterator<Item = (&'a Name, MacroDefId)> + 'a {
|
||||
self.legacy_macros.iter().map(|(name, def)| (name, *def))
|
||||
}
|
||||
|
||||
/// Get a name from current module scope, legacy macros are not included
|
||||
pub(crate) fn get(&self, name: &Name) -> PerNs {
|
||||
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.entries() {
|
||||
if let Some(vis) = item.match_with(per_ns) {
|
||||
return Some((name, vis));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn traits<'a>(&'a self) -> impl Iterator<Item = TraitId> + 'a {
|
||||
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) {
|
||||
self.defs.push(def)
|
||||
}
|
||||
|
||||
pub(crate) fn get_legacy_macro(&self, name: &Name) -> Option<MacroDefId> {
|
||||
self.legacy_macros.get(name).copied()
|
||||
}
|
||||
|
||||
pub(crate) fn define_impl(&mut self, imp: ImplId) {
|
||||
self.impls.push(imp)
|
||||
}
|
||||
|
||||
pub(crate) fn define_legacy_macro(&mut self, name: Name, mac: MacroDefId) {
|
||||
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;
|
||||
|
||||
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 def.is_none() {
|
||||
if self.unresolved.insert(name) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
changed
|
||||
}
|
||||
|
||||
pub(crate) fn push_res_with_import(
|
||||
&mut self,
|
||||
glob_imports: &mut PerNsGlobImports,
|
||||
lookup: (LocalModuleId, Name),
|
||||
def: PerNs,
|
||||
def_import_type: ImportType,
|
||||
) -> bool {
|
||||
let mut changed = false;
|
||||
|
||||
macro_rules! check_changed {
|
||||
(
|
||||
$changed:ident,
|
||||
( $this:ident / $def:ident ) . $field:ident,
|
||||
$glob_imports:ident [ $lookup:ident ],
|
||||
$def_import_type:ident
|
||||
) => {{
|
||||
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());
|
||||
}
|
||||
ImportType::Named => {
|
||||
$glob_imports.$field.remove(&$lookup);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(fld) = $def.$field {
|
||||
entry.insert(fld);
|
||||
}
|
||||
$changed = true;
|
||||
}
|
||||
(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);
|
||||
if let Some(fld) = $def.$field {
|
||||
entry.insert(fld);
|
||||
}
|
||||
$changed = true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
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 = (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> {
|
||||
self.legacy_macros.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl PerNs {
|
||||
pub(crate) fn from_def(def: ModuleDefId, v: Visibility, has_constructor: bool) -> PerNs {
|
||||
match def {
|
||||
ModuleDefId::ModuleId(_) => PerNs::types(def, v),
|
||||
ModuleDefId::FunctionId(_) => PerNs::values(def, v),
|
||||
ModuleDefId::AdtId(adt) => match adt {
|
||||
AdtId::UnionId(_) => PerNs::types(def, v),
|
||||
AdtId::EnumId(_) => PerNs::types(def, v),
|
||||
AdtId::StructId(_) => {
|
||||
if has_constructor {
|
||||
PerNs::both(def, def, v)
|
||||
} else {
|
||||
PerNs::types(def, v)
|
||||
}
|
||||
}
|
||||
},
|
||||
ModuleDefId::EnumVariantId(_) => PerNs::both(def, def, v),
|
||||
ModuleDefId::ConstId(_) | ModuleDefId::StaticId(_) => PerNs::values(def, v),
|
||||
ModuleDefId::TraitId(_) => PerNs::types(def, v),
|
||||
ModuleDefId::TypeAliasId(_) => PerNs::types(def, v),
|
||||
ModuleDefId::BuiltinType(_) => PerNs::types(def, v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
||||
pub enum ItemInNs {
|
||||
Types(ModuleDefId),
|
||||
Values(ModuleDefId),
|
||||
Macros(MacroDefId),
|
||||
}
|
||||
|
||||
impl ItemInNs {
|
||||
fn match_with(self, per_ns: PerNs) -> Option<Visibility> {
|
||||
match self {
|
||||
ItemInNs::Types(def) => {
|
||||
per_ns.types.filter(|(other_def, _)| *other_def == def).map(|(_, vis)| vis)
|
||||
}
|
||||
ItemInNs::Values(def) => {
|
||||
per_ns.values.filter(|(other_def, _)| *other_def == def).map(|(_, vis)| vis)
|
||||
}
|
||||
ItemInNs::Macros(def) => {
|
||||
per_ns.macros.filter(|(other_def, _)| *other_def == def).map(|(_, vis)| vis)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_module_def_id(self) -> Option<ModuleDefId> {
|
||||
match self {
|
||||
ItemInNs::Types(id) | ItemInNs::Values(id) => Some(id),
|
||||
ItemInNs::Macros(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the crate defining this item (or `None` if `self` is built-in).
|
||||
pub fn krate(&self, db: &dyn DefDatabase) -> Option<CrateId> {
|
||||
Some(match self {
|
||||
ItemInNs::Types(did) | ItemInNs::Values(did) => match did {
|
||||
ModuleDefId::ModuleId(id) => id.krate,
|
||||
ModuleDefId::FunctionId(id) => id.lookup(db).module(db).krate,
|
||||
ModuleDefId::AdtId(id) => id.module(db).krate,
|
||||
ModuleDefId::EnumVariantId(id) => id.parent.lookup(db).container.module(db).krate,
|
||||
ModuleDefId::ConstId(id) => id.lookup(db).container.module(db).krate,
|
||||
ModuleDefId::StaticId(id) => id.lookup(db).container.module(db).krate,
|
||||
ModuleDefId::TraitId(id) => id.lookup(db).container.module(db).krate,
|
||||
ModuleDefId::TypeAliasId(id) => id.lookup(db).module(db).krate,
|
||||
ModuleDefId::BuiltinType(_) => return None,
|
||||
},
|
||||
ItemInNs::Macros(id) => return id.krate,
|
||||
})
|
||||
}
|
||||
}
|
754
crates/hir_def/src/item_tree.rs
Normal file
754
crates/hir_def/src/item_tree.rs
Normal file
|
@ -0,0 +1,754 @@
|
|||
//! A simplified AST that only contains items.
|
||||
|
||||
mod lower;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use std::{
|
||||
any::type_name,
|
||||
fmt::{self, Debug},
|
||||
hash::{Hash, Hasher},
|
||||
marker::PhantomData,
|
||||
ops::{Index, Range},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use arena::{Arena, Idx, RawId};
|
||||
use ast::{AstNode, AttrsOwner, NameOwner, StructKind};
|
||||
use either::Either;
|
||||
use hir_expand::{
|
||||
ast_id_map::FileAstId,
|
||||
hygiene::Hygiene,
|
||||
name::{name, AsName, Name},
|
||||
HirFileId, InFile,
|
||||
};
|
||||
use rustc_hash::FxHashMap;
|
||||
use smallvec::SmallVec;
|
||||
use syntax::{ast, match_ast};
|
||||
use test_utils::mark;
|
||||
|
||||
use crate::{
|
||||
attr::Attrs,
|
||||
db::DefDatabase,
|
||||
generics::GenericParams,
|
||||
path::{path, AssociatedTypeBinding, GenericArgs, ImportAlias, ModPath, Path, PathKind},
|
||||
type_ref::{Mutability, TypeBound, TypeRef},
|
||||
visibility::RawVisibility,
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub struct RawVisibilityId(u32);
|
||||
|
||||
impl RawVisibilityId {
|
||||
pub const PUB: Self = RawVisibilityId(u32::max_value());
|
||||
pub const PRIV: Self = RawVisibilityId(u32::max_value() - 1);
|
||||
pub const PUB_CRATE: Self = RawVisibilityId(u32::max_value() - 2);
|
||||
}
|
||||
|
||||
impl fmt::Debug for RawVisibilityId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut f = f.debug_tuple("RawVisibilityId");
|
||||
match *self {
|
||||
Self::PUB => f.field(&"pub"),
|
||||
Self::PRIV => f.field(&"pub(self)"),
|
||||
Self::PUB_CRATE => f.field(&"pub(crate)"),
|
||||
_ => f.field(&self.0),
|
||||
};
|
||||
f.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub struct GenericParamsId(u32);
|
||||
|
||||
impl GenericParamsId {
|
||||
pub const EMPTY: Self = GenericParamsId(u32::max_value());
|
||||
}
|
||||
|
||||
/// The item tree of a source file.
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct ItemTree {
|
||||
top_level: SmallVec<[ModItem; 1]>,
|
||||
attrs: FxHashMap<AttrOwner, Attrs>,
|
||||
inner_items: FxHashMap<FileAstId<ast::Item>, SmallVec<[ModItem; 1]>>,
|
||||
|
||||
data: Option<Box<ItemTreeData>>,
|
||||
}
|
||||
|
||||
impl ItemTree {
|
||||
pub fn item_tree_query(db: &dyn DefDatabase, file_id: HirFileId) -> Arc<ItemTree> {
|
||||
let _p = profile::span("item_tree_query").detail(|| format!("{:?}", file_id));
|
||||
let syntax = if let Some(node) = db.parse_or_expand(file_id) {
|
||||
node
|
||||
} else {
|
||||
return Arc::new(Self::empty());
|
||||
};
|
||||
|
||||
let hygiene = Hygiene::new(db.upcast(), file_id);
|
||||
let ctx = lower::Ctx::new(db, hygiene.clone(), file_id);
|
||||
let mut top_attrs = None;
|
||||
let mut item_tree = match_ast! {
|
||||
match syntax {
|
||||
ast::SourceFile(file) => {
|
||||
top_attrs = Some(Attrs::new(&file, &hygiene));
|
||||
ctx.lower_module_items(&file)
|
||||
},
|
||||
ast::MacroItems(items) => {
|
||||
ctx.lower_module_items(&items)
|
||||
},
|
||||
// Macros can expand to expressions. We return an empty item tree in this case, but
|
||||
// still need to collect inner items.
|
||||
ast::Expr(e) => {
|
||||
ctx.lower_inner_items(e.syntax())
|
||||
},
|
||||
_ => {
|
||||
panic!("cannot create item tree from {:?}", syntax);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(attrs) = top_attrs {
|
||||
item_tree.attrs.insert(AttrOwner::TopLevel, attrs);
|
||||
}
|
||||
item_tree.shrink_to_fit();
|
||||
Arc::new(item_tree)
|
||||
}
|
||||
|
||||
fn empty() -> Self {
|
||||
Self {
|
||||
top_level: Default::default(),
|
||||
attrs: Default::default(),
|
||||
inner_items: Default::default(),
|
||||
data: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn shrink_to_fit(&mut self) {
|
||||
if let Some(data) = &mut self.data {
|
||||
let ItemTreeData {
|
||||
imports,
|
||||
extern_crates,
|
||||
functions,
|
||||
structs,
|
||||
fields,
|
||||
unions,
|
||||
enums,
|
||||
variants,
|
||||
consts,
|
||||
statics,
|
||||
traits,
|
||||
impls,
|
||||
type_aliases,
|
||||
mods,
|
||||
macro_calls,
|
||||
exprs,
|
||||
vis,
|
||||
generics,
|
||||
} = &mut **data;
|
||||
|
||||
imports.shrink_to_fit();
|
||||
extern_crates.shrink_to_fit();
|
||||
functions.shrink_to_fit();
|
||||
structs.shrink_to_fit();
|
||||
fields.shrink_to_fit();
|
||||
unions.shrink_to_fit();
|
||||
enums.shrink_to_fit();
|
||||
variants.shrink_to_fit();
|
||||
consts.shrink_to_fit();
|
||||
statics.shrink_to_fit();
|
||||
traits.shrink_to_fit();
|
||||
impls.shrink_to_fit();
|
||||
type_aliases.shrink_to_fit();
|
||||
mods.shrink_to_fit();
|
||||
macro_calls.shrink_to_fit();
|
||||
exprs.shrink_to_fit();
|
||||
|
||||
vis.arena.shrink_to_fit();
|
||||
generics.arena.shrink_to_fit();
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator over all items located at the top level of the `HirFileId` this
|
||||
/// `ItemTree` was created from.
|
||||
pub fn top_level_items(&self) -> &[ModItem] {
|
||||
&self.top_level
|
||||
}
|
||||
|
||||
/// Returns the inner attributes of the source file.
|
||||
pub fn top_level_attrs(&self) -> &Attrs {
|
||||
self.attrs.get(&AttrOwner::TopLevel).unwrap_or(&Attrs::EMPTY)
|
||||
}
|
||||
|
||||
pub fn attrs(&self, of: AttrOwner) -> &Attrs {
|
||||
self.attrs.get(&of).unwrap_or(&Attrs::EMPTY)
|
||||
}
|
||||
|
||||
/// Returns the lowered inner items that `ast` corresponds to.
|
||||
///
|
||||
/// 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::Item>) -> &[ModItem] {
|
||||
&self.inner_items[&ast]
|
||||
}
|
||||
|
||||
pub fn all_inner_items(&self) -> impl Iterator<Item = ModItem> + '_ {
|
||||
self.inner_items.values().flatten().copied()
|
||||
}
|
||||
|
||||
pub fn source<S: ItemTreeNode>(&self, db: &dyn DefDatabase, of: ItemTreeId<S>) -> S::Source {
|
||||
// This unwrap cannot fail, since it has either succeeded above, or resulted in an empty
|
||||
// ItemTree (in which case there is no valid `FileItemTreeId` to call this method with).
|
||||
let root =
|
||||
db.parse_or_expand(of.file_id).expect("parse_or_expand failed on constructed ItemTree");
|
||||
|
||||
let id = self[of.value].ast_id();
|
||||
let map = db.ast_id_map(of.file_id);
|
||||
let ptr = map.get(id);
|
||||
ptr.to_node(&root)
|
||||
}
|
||||
|
||||
fn data(&self) -> &ItemTreeData {
|
||||
self.data.as_ref().expect("attempted to access data of empty ItemTree")
|
||||
}
|
||||
|
||||
fn data_mut(&mut self) -> &mut ItemTreeData {
|
||||
self.data.get_or_insert_with(Box::default)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Eq, PartialEq)]
|
||||
struct ItemVisibilities {
|
||||
arena: Arena<RawVisibility>,
|
||||
}
|
||||
|
||||
impl ItemVisibilities {
|
||||
fn alloc(&mut self, vis: RawVisibility) -> RawVisibilityId {
|
||||
match &vis {
|
||||
RawVisibility::Public => RawVisibilityId::PUB,
|
||||
RawVisibility::Module(path) if path.segments.is_empty() => match &path.kind {
|
||||
PathKind::Super(0) => RawVisibilityId::PRIV,
|
||||
PathKind::Crate => RawVisibilityId::PUB_CRATE,
|
||||
_ => RawVisibilityId(self.arena.alloc(vis).into_raw().into()),
|
||||
},
|
||||
_ => RawVisibilityId(self.arena.alloc(vis).into_raw().into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static VIS_PUB: RawVisibility = RawVisibility::Public;
|
||||
static VIS_PRIV: RawVisibility =
|
||||
RawVisibility::Module(ModPath { kind: PathKind::Super(0), segments: Vec::new() });
|
||||
static VIS_PUB_CRATE: RawVisibility =
|
||||
RawVisibility::Module(ModPath { kind: PathKind::Crate, segments: Vec::new() });
|
||||
|
||||
#[derive(Default, Debug, Eq, PartialEq)]
|
||||
struct GenericParamsStorage {
|
||||
arena: Arena<GenericParams>,
|
||||
}
|
||||
|
||||
impl GenericParamsStorage {
|
||||
fn alloc(&mut self, params: GenericParams) -> GenericParamsId {
|
||||
if params.types.is_empty() && params.where_predicates.is_empty() {
|
||||
return GenericParamsId::EMPTY;
|
||||
}
|
||||
|
||||
GenericParamsId(self.arena.alloc(params).into_raw().into())
|
||||
}
|
||||
}
|
||||
|
||||
static EMPTY_GENERICS: GenericParams =
|
||||
GenericParams { types: Arena::new(), where_predicates: Vec::new() };
|
||||
|
||||
#[derive(Default, Debug, Eq, PartialEq)]
|
||||
struct ItemTreeData {
|
||||
imports: Arena<Import>,
|
||||
extern_crates: Arena<ExternCrate>,
|
||||
functions: Arena<Function>,
|
||||
structs: Arena<Struct>,
|
||||
fields: Arena<Field>,
|
||||
unions: Arena<Union>,
|
||||
enums: Arena<Enum>,
|
||||
variants: Arena<Variant>,
|
||||
consts: Arena<Const>,
|
||||
statics: Arena<Static>,
|
||||
traits: Arena<Trait>,
|
||||
impls: Arena<Impl>,
|
||||
type_aliases: Arena<TypeAlias>,
|
||||
mods: Arena<Mod>,
|
||||
macro_calls: Arena<MacroCall>,
|
||||
exprs: Arena<Expr>,
|
||||
|
||||
vis: ItemVisibilities,
|
||||
generics: GenericParamsStorage,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Hash)]
|
||||
pub enum AttrOwner {
|
||||
/// Attributes on an item.
|
||||
ModItem(ModItem),
|
||||
/// Inner attributes of the source file.
|
||||
TopLevel,
|
||||
|
||||
Variant(Idx<Variant>),
|
||||
Field(Idx<Field>),
|
||||
// FIXME: Store variant and field attrs, and stop reparsing them in `attrs_query`.
|
||||
}
|
||||
|
||||
macro_rules! from_attrs {
|
||||
( $( $var:ident($t:ty) ),+ ) => {
|
||||
$(
|
||||
impl From<$t> for AttrOwner {
|
||||
fn from(t: $t) -> AttrOwner {
|
||||
AttrOwner::$var(t)
|
||||
}
|
||||
}
|
||||
)+
|
||||
};
|
||||
}
|
||||
|
||||
from_attrs!(ModItem(ModItem), Variant(Idx<Variant>), Field(Idx<Field>));
|
||||
|
||||
/// Trait implemented by all item nodes in the item tree.
|
||||
pub trait ItemTreeNode: Clone {
|
||||
type Source: AstNode + Into<ast::Item>;
|
||||
|
||||
fn ast_id(&self) -> FileAstId<Self::Source>;
|
||||
|
||||
/// Looks up an instance of `Self` in an item tree.
|
||||
fn lookup(tree: &ItemTree, index: Idx<Self>) -> &Self;
|
||||
|
||||
/// Downcasts a `ModItem` to a `FileItemTreeId` specific to this type.
|
||||
fn id_from_mod_item(mod_item: ModItem) -> Option<FileItemTreeId<Self>>;
|
||||
|
||||
/// Upcasts a `FileItemTreeId` to a generic `ModItem`.
|
||||
fn id_to_mod_item(id: FileItemTreeId<Self>) -> ModItem;
|
||||
}
|
||||
|
||||
pub struct FileItemTreeId<N: ItemTreeNode> {
|
||||
index: Idx<N>,
|
||||
_p: PhantomData<N>,
|
||||
}
|
||||
|
||||
impl<N: ItemTreeNode> Clone for FileItemTreeId<N> {
|
||||
fn clone(&self) -> Self {
|
||||
Self { index: self.index, _p: PhantomData }
|
||||
}
|
||||
}
|
||||
impl<N: ItemTreeNode> Copy for FileItemTreeId<N> {}
|
||||
|
||||
impl<N: ItemTreeNode> PartialEq for FileItemTreeId<N> {
|
||||
fn eq(&self, other: &FileItemTreeId<N>) -> bool {
|
||||
self.index == other.index
|
||||
}
|
||||
}
|
||||
impl<N: ItemTreeNode> Eq for FileItemTreeId<N> {}
|
||||
|
||||
impl<N: ItemTreeNode> Hash for FileItemTreeId<N> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.index.hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: ItemTreeNode> fmt::Debug for FileItemTreeId<N> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.index.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
pub type ItemTreeId<N> = InFile<FileItemTreeId<N>>;
|
||||
|
||||
macro_rules! mod_items {
|
||||
( $( $typ:ident in $fld:ident -> $ast:ty ),+ $(,)? ) => {
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum ModItem {
|
||||
$(
|
||||
$typ(FileItemTreeId<$typ>),
|
||||
)+
|
||||
}
|
||||
|
||||
$(
|
||||
impl From<FileItemTreeId<$typ>> for ModItem {
|
||||
fn from(id: FileItemTreeId<$typ>) -> ModItem {
|
||||
ModItem::$typ(id)
|
||||
}
|
||||
}
|
||||
)+
|
||||
|
||||
$(
|
||||
impl ItemTreeNode for $typ {
|
||||
type Source = $ast;
|
||||
|
||||
fn ast_id(&self) -> FileAstId<Self::Source> {
|
||||
self.ast_id
|
||||
}
|
||||
|
||||
fn lookup(tree: &ItemTree, index: Idx<Self>) -> &Self {
|
||||
&tree.data().$fld[index]
|
||||
}
|
||||
|
||||
fn id_from_mod_item(mod_item: ModItem) -> Option<FileItemTreeId<Self>> {
|
||||
if let ModItem::$typ(id) = mod_item {
|
||||
Some(id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn id_to_mod_item(id: FileItemTreeId<Self>) -> ModItem {
|
||||
ModItem::$typ(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<Idx<$typ>> for ItemTree {
|
||||
type Output = $typ;
|
||||
|
||||
fn index(&self, index: Idx<$typ>) -> &Self::Output {
|
||||
&self.data().$fld[index]
|
||||
}
|
||||
}
|
||||
)+
|
||||
};
|
||||
}
|
||||
|
||||
mod_items! {
|
||||
Import in imports -> ast::Use,
|
||||
ExternCrate in extern_crates -> ast::ExternCrate,
|
||||
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,
|
||||
}
|
||||
|
||||
macro_rules! impl_index {
|
||||
( $($fld:ident: $t:ty),+ $(,)? ) => {
|
||||
$(
|
||||
impl Index<Idx<$t>> for ItemTree {
|
||||
type Output = $t;
|
||||
|
||||
fn index(&self, index: Idx<$t>) -> &Self::Output {
|
||||
&self.data().$fld[index]
|
||||
}
|
||||
}
|
||||
)+
|
||||
};
|
||||
}
|
||||
|
||||
impl_index!(fields: Field, variants: Variant, exprs: Expr);
|
||||
|
||||
impl Index<RawVisibilityId> for ItemTree {
|
||||
type Output = RawVisibility;
|
||||
fn index(&self, index: RawVisibilityId) -> &Self::Output {
|
||||
match index {
|
||||
RawVisibilityId::PRIV => &VIS_PRIV,
|
||||
RawVisibilityId::PUB => &VIS_PUB,
|
||||
RawVisibilityId::PUB_CRATE => &VIS_PUB_CRATE,
|
||||
_ => &self.data().vis.arena[Idx::from_raw(index.0.into())],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<GenericParamsId> for ItemTree {
|
||||
type Output = GenericParams;
|
||||
|
||||
fn index(&self, index: GenericParamsId) -> &Self::Output {
|
||||
match index {
|
||||
GenericParamsId::EMPTY => &EMPTY_GENERICS,
|
||||
_ => &self.data().generics.arena[Idx::from_raw(index.0.into())],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: ItemTreeNode> Index<FileItemTreeId<N>> for ItemTree {
|
||||
type Output = N;
|
||||
fn index(&self, id: FileItemTreeId<N>) -> &N {
|
||||
N::lookup(self, id.index)
|
||||
}
|
||||
}
|
||||
|
||||
/// A desugared `use` import.
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct Import {
|
||||
pub path: ModPath,
|
||||
pub alias: Option<ImportAlias>,
|
||||
pub visibility: RawVisibilityId,
|
||||
pub is_glob: bool,
|
||||
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::Use>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct ExternCrate {
|
||||
pub path: ModPath,
|
||||
pub alias: Option<ImportAlias>,
|
||||
pub visibility: RawVisibilityId,
|
||||
/// Whether this is a `#[macro_use] extern crate ...`.
|
||||
pub is_macro_use: bool,
|
||||
pub ast_id: FileAstId<ast::ExternCrate>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct Function {
|
||||
pub name: Name,
|
||||
pub visibility: RawVisibilityId,
|
||||
pub generic_params: GenericParamsId,
|
||||
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::Fn>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct Struct {
|
||||
pub name: Name,
|
||||
pub visibility: RawVisibilityId,
|
||||
pub generic_params: GenericParamsId,
|
||||
pub fields: Fields,
|
||||
pub ast_id: FileAstId<ast::Struct>,
|
||||
pub kind: StructDefKind,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum StructDefKind {
|
||||
/// `struct S { ... }` - type namespace only.
|
||||
Record,
|
||||
/// `struct S(...);`
|
||||
Tuple,
|
||||
/// `struct S;`
|
||||
Unit,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct Union {
|
||||
pub name: Name,
|
||||
pub visibility: RawVisibilityId,
|
||||
pub generic_params: GenericParamsId,
|
||||
pub fields: Fields,
|
||||
pub ast_id: FileAstId<ast::Union>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct Enum {
|
||||
pub name: Name,
|
||||
pub visibility: RawVisibilityId,
|
||||
pub generic_params: GenericParamsId,
|
||||
pub variants: IdRange<Variant>,
|
||||
pub ast_id: FileAstId<ast::Enum>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct Const {
|
||||
/// const _: () = ();
|
||||
pub name: Option<Name>,
|
||||
pub visibility: RawVisibilityId,
|
||||
pub type_ref: TypeRef,
|
||||
pub ast_id: FileAstId<ast::Const>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct Static {
|
||||
pub name: Name,
|
||||
pub visibility: RawVisibilityId,
|
||||
pub mutable: bool,
|
||||
pub type_ref: TypeRef,
|
||||
pub ast_id: FileAstId<ast::Static>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct Trait {
|
||||
pub name: Name,
|
||||
pub visibility: RawVisibilityId,
|
||||
pub generic_params: GenericParamsId,
|
||||
pub auto: bool,
|
||||
pub items: Box<[AssocItem]>,
|
||||
pub ast_id: FileAstId<ast::Trait>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct Impl {
|
||||
pub generic_params: GenericParamsId,
|
||||
pub target_trait: Option<TypeRef>,
|
||||
pub target_type: TypeRef,
|
||||
pub is_negative: bool,
|
||||
pub items: Box<[AssocItem]>,
|
||||
pub ast_id: FileAstId<ast::Impl>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct TypeAlias {
|
||||
pub name: Name,
|
||||
pub visibility: RawVisibilityId,
|
||||
/// Bounds on the type alias itself. Only valid in trait declarations, eg. `type Assoc: Copy;`.
|
||||
pub bounds: Box<[TypeBound]>,
|
||||
pub generic_params: GenericParamsId,
|
||||
pub type_ref: Option<TypeRef>,
|
||||
pub ast_id: FileAstId<ast::TypeAlias>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct Mod {
|
||||
pub name: Name,
|
||||
pub visibility: RawVisibilityId,
|
||||
pub kind: ModKind,
|
||||
pub ast_id: FileAstId<ast::Module>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum ModKind {
|
||||
/// `mod m { ... }`
|
||||
Inline { items: Box<[ModItem]> },
|
||||
|
||||
/// `mod m;`
|
||||
Outline {},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct MacroCall {
|
||||
/// For `macro_rules!` declarations, this is the name of the declared macro.
|
||||
pub name: Option<Name>,
|
||||
/// Path to the called macro.
|
||||
pub path: ModPath,
|
||||
/// Has `#[macro_export]`.
|
||||
pub is_export: bool,
|
||||
/// Has `#[macro_export(local_inner_macros)]`.
|
||||
pub is_local_inner: bool,
|
||||
/// Has `#[rustc_builtin_macro]`.
|
||||
pub is_builtin: bool,
|
||||
pub ast_id: FileAstId<ast::MacroCall>,
|
||||
}
|
||||
|
||||
// NB: There's no `FileAstId` for `Expr`. The only case where this would be useful is for array
|
||||
// lengths, but we don't do much with them yet.
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct Expr;
|
||||
|
||||
macro_rules! impl_froms {
|
||||
($e:ident { $($v:ident ($t:ty)),* $(,)? }) => {
|
||||
$(
|
||||
impl From<$t> for $e {
|
||||
fn from(it: $t) -> $e {
|
||||
$e::$v(it)
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
impl ModItem {
|
||||
pub fn as_assoc_item(&self) -> Option<AssocItem> {
|
||||
match self {
|
||||
ModItem::Import(_)
|
||||
| ModItem::ExternCrate(_)
|
||||
| ModItem::Struct(_)
|
||||
| ModItem::Union(_)
|
||||
| ModItem::Enum(_)
|
||||
| ModItem::Static(_)
|
||||
| ModItem::Trait(_)
|
||||
| ModItem::Impl(_)
|
||||
| ModItem::Mod(_) => None,
|
||||
ModItem::MacroCall(call) => Some(AssocItem::MacroCall(*call)),
|
||||
ModItem::Const(konst) => Some(AssocItem::Const(*konst)),
|
||||
ModItem::TypeAlias(alias) => Some(AssocItem::TypeAlias(*alias)),
|
||||
ModItem::Function(func) => Some(AssocItem::Function(*func)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn downcast<N: ItemTreeNode>(self) -> Option<FileItemTreeId<N>> {
|
||||
N::id_from_mod_item(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum AssocItem {
|
||||
Function(FileItemTreeId<Function>),
|
||||
TypeAlias(FileItemTreeId<TypeAlias>),
|
||||
Const(FileItemTreeId<Const>),
|
||||
MacroCall(FileItemTreeId<MacroCall>),
|
||||
}
|
||||
|
||||
impl_froms!(AssocItem {
|
||||
Function(FileItemTreeId<Function>),
|
||||
TypeAlias(FileItemTreeId<TypeAlias>),
|
||||
Const(FileItemTreeId<Const>),
|
||||
MacroCall(FileItemTreeId<MacroCall>),
|
||||
});
|
||||
|
||||
impl From<AssocItem> for ModItem {
|
||||
fn from(item: AssocItem) -> Self {
|
||||
match item {
|
||||
AssocItem::Function(it) => it.into(),
|
||||
AssocItem::TypeAlias(it) => it.into(),
|
||||
AssocItem::Const(it) => it.into(),
|
||||
AssocItem::MacroCall(it) => it.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct Variant {
|
||||
pub name: Name,
|
||||
pub fields: Fields,
|
||||
}
|
||||
|
||||
pub struct IdRange<T> {
|
||||
range: Range<u32>,
|
||||
_p: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> IdRange<T> {
|
||||
fn new(range: Range<Idx<T>>) -> Self {
|
||||
Self { range: range.start.into_raw().into()..range.end.into_raw().into(), _p: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Iterator for IdRange<T> {
|
||||
type Item = Idx<T>;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.range.next().map(|raw| Idx::from_raw(raw.into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for IdRange<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_tuple(&format!("IdRange::<{}>", type_name::<T>())).field(&self.range).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for IdRange<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self { range: self.range.clone(), _p: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> PartialEq for IdRange<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.range == other.range
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Eq for IdRange<T> {}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Fields {
|
||||
Record(IdRange<Field>),
|
||||
Tuple(IdRange<Field>),
|
||||
Unit,
|
||||
}
|
||||
|
||||
/// A single field of an enum variant or struct
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Field {
|
||||
pub name: Name,
|
||||
pub type_ref: TypeRef,
|
||||
pub visibility: RawVisibilityId,
|
||||
}
|
705
crates/hir_def/src/item_tree/lower.rs
Normal file
705
crates/hir_def/src/item_tree/lower.rs
Normal file
|
@ -0,0 +1,705 @@
|
|||
//! AST -> `ItemTree` lowering code.
|
||||
|
||||
use std::{collections::hash_map::Entry, mem, sync::Arc};
|
||||
|
||||
use arena::map::ArenaMap;
|
||||
use hir_expand::{ast_id_map::AstIdMap, hygiene::Hygiene, HirFileId};
|
||||
use smallvec::SmallVec;
|
||||
use syntax::{
|
||||
ast::{self, ModuleItemOwner},
|
||||
SyntaxNode,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
attr::Attrs,
|
||||
generics::{GenericParams, TypeParamData, TypeParamProvenance},
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
fn id<N: ItemTreeNode>(index: Idx<N>) -> FileItemTreeId<N> {
|
||||
FileItemTreeId { index, _p: PhantomData }
|
||||
}
|
||||
|
||||
struct ModItems(SmallVec<[ModItem; 1]>);
|
||||
|
||||
impl<T> From<T> for ModItems
|
||||
where
|
||||
T: Into<ModItem>,
|
||||
{
|
||||
fn from(t: T) -> Self {
|
||||
ModItems(SmallVec::from_buf([t.into(); 1]))
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct Ctx {
|
||||
tree: ItemTree,
|
||||
hygiene: Hygiene,
|
||||
file: HirFileId,
|
||||
source_ast_id_map: Arc<AstIdMap>,
|
||||
body_ctx: crate::body::LowerCtx,
|
||||
inner_items: Vec<ModItem>,
|
||||
forced_visibility: Option<RawVisibilityId>,
|
||||
}
|
||||
|
||||
impl Ctx {
|
||||
pub(super) fn new(db: &dyn DefDatabase, hygiene: Hygiene, file: HirFileId) -> Self {
|
||||
Self {
|
||||
tree: ItemTree::empty(),
|
||||
hygiene,
|
||||
file,
|
||||
source_ast_id_map: db.ast_id_map(file),
|
||||
body_ctx: crate::body::LowerCtx::new(db, file),
|
||||
inner_items: Vec::new(),
|
||||
forced_visibility: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn lower_module_items(mut self, item_owner: &dyn ModuleItemOwner) -> ItemTree {
|
||||
self.tree.top_level = item_owner
|
||||
.items()
|
||||
.flat_map(|item| self.lower_mod_item(&item, false))
|
||||
.flat_map(|items| items.0)
|
||||
.collect();
|
||||
self.tree
|
||||
}
|
||||
|
||||
pub(super) fn lower_inner_items(mut self, within: &SyntaxNode) -> ItemTree {
|
||||
self.collect_inner_items(within);
|
||||
self.tree
|
||||
}
|
||||
|
||||
fn data(&mut self) -> &mut ItemTreeData {
|
||||
self.tree.data_mut()
|
||||
}
|
||||
|
||||
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::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 {
|
||||
self.collect_inner_items(item.syntax());
|
||||
}
|
||||
}
|
||||
|
||||
// These are handled in their respective `lower_X` method (since we can't just blindly
|
||||
// walk them).
|
||||
ast::Item::Trait(_) | ast::Item::Impl(_) | ast::Item::ExternBlock(_) => {}
|
||||
|
||||
// These don't have inner items.
|
||||
ast::Item::Module(_) | ast::Item::ExternCrate(_) | ast::Item::Use(_) => {}
|
||||
};
|
||||
|
||||
let attrs = Attrs::new(item, &self.hygiene);
|
||||
let items = match item {
|
||||
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::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<_>>()))
|
||||
}
|
||||
};
|
||||
|
||||
if !attrs.is_empty() {
|
||||
for item in items.iter().flat_map(|items| &items.0) {
|
||||
self.add_attrs((*item).into(), attrs.clone());
|
||||
}
|
||||
}
|
||||
|
||||
items
|
||||
}
|
||||
|
||||
fn add_attrs(&mut self, item: AttrOwner, attrs: Attrs) {
|
||||
match self.tree.attrs.entry(item) {
|
||||
Entry::Occupied(mut entry) => {
|
||||
*entry.get_mut() = entry.get().merge(attrs);
|
||||
}
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert(attrs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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::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::AssocItem) -> Option<AssocItem> {
|
||||
match item {
|
||||
ast::AssocItem::Fn(ast) => self.lower_function(ast).map(Into::into),
|
||||
ast::AssocItem::TypeAlias(ast) => self.lower_type_alias(ast).map(Into::into),
|
||||
ast::AssocItem::Const(ast) => Some(self.lower_const(ast).into()),
|
||||
ast::AssocItem::MacroCall(ast) => self.lower_macro_call(ast).map(Into::into),
|
||||
}
|
||||
}
|
||||
|
||||
fn lower_struct(&mut self, strukt: &ast::Struct) -> Option<FileItemTreeId<Struct>> {
|
||||
let visibility = self.lower_visibility(strukt);
|
||||
let name = strukt.name()?.as_name();
|
||||
let generic_params = self.lower_generic_params(GenericsOwner::Struct, strukt);
|
||||
let fields = self.lower_fields(&strukt.kind());
|
||||
let ast_id = self.source_ast_id_map.ast_id(strukt);
|
||||
let kind = match strukt.kind() {
|
||||
ast::StructKind::Record(_) => StructDefKind::Record,
|
||||
ast::StructKind::Tuple(_) => StructDefKind::Tuple,
|
||||
ast::StructKind::Unit => StructDefKind::Unit,
|
||||
};
|
||||
let res = Struct { name, visibility, generic_params, fields, ast_id, kind };
|
||||
Some(id(self.data().structs.alloc(res)))
|
||||
}
|
||||
|
||||
fn lower_fields(&mut self, strukt_kind: &ast::StructKind) -> Fields {
|
||||
match strukt_kind {
|
||||
ast::StructKind::Record(it) => {
|
||||
let range = self.lower_record_fields(it);
|
||||
Fields::Record(range)
|
||||
}
|
||||
ast::StructKind::Tuple(it) => {
|
||||
let range = self.lower_tuple_fields(it);
|
||||
Fields::Tuple(range)
|
||||
}
|
||||
ast::StructKind::Unit => Fields::Unit,
|
||||
}
|
||||
}
|
||||
|
||||
fn lower_record_fields(&mut self, fields: &ast::RecordFieldList) -> IdRange<Field> {
|
||||
let start = self.next_field_idx();
|
||||
for field in fields.fields() {
|
||||
if let Some(data) = self.lower_record_field(&field) {
|
||||
let idx = self.data().fields.alloc(data);
|
||||
self.add_attrs(idx.into(), Attrs::new(&field, &self.hygiene));
|
||||
}
|
||||
}
|
||||
let end = self.next_field_idx();
|
||||
IdRange::new(start..end)
|
||||
}
|
||||
|
||||
fn lower_record_field(&mut self, field: &ast::RecordField) -> Option<Field> {
|
||||
let name = field.name()?.as_name();
|
||||
let visibility = self.lower_visibility(field);
|
||||
let type_ref = self.lower_type_ref_opt(field.ty());
|
||||
let res = Field { name, type_ref, visibility };
|
||||
Some(res)
|
||||
}
|
||||
|
||||
fn lower_tuple_fields(&mut self, fields: &ast::TupleFieldList) -> IdRange<Field> {
|
||||
let start = self.next_field_idx();
|
||||
for (i, field) in fields.fields().enumerate() {
|
||||
let data = self.lower_tuple_field(i, &field);
|
||||
let idx = self.data().fields.alloc(data);
|
||||
self.add_attrs(idx.into(), 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::TupleField) -> Field {
|
||||
let name = Name::new_tuple_field(idx);
|
||||
let visibility = self.lower_visibility(field);
|
||||
let type_ref = self.lower_type_ref_opt(field.ty());
|
||||
let res = Field { name, type_ref, visibility };
|
||||
res
|
||||
}
|
||||
|
||||
fn lower_union(&mut self, union: &ast::Union) -> Option<FileItemTreeId<Union>> {
|
||||
let visibility = self.lower_visibility(union);
|
||||
let name = union.name()?.as_name();
|
||||
let generic_params = self.lower_generic_params(GenericsOwner::Union, union);
|
||||
let fields = match union.record_field_list() {
|
||||
Some(record_field_list) => self.lower_fields(&StructKind::Record(record_field_list)),
|
||||
None => Fields::Record(IdRange::new(self.next_field_idx()..self.next_field_idx())),
|
||||
};
|
||||
let ast_id = self.source_ast_id_map.ast_id(union);
|
||||
let res = Union { name, visibility, generic_params, fields, ast_id };
|
||||
Some(id(self.data().unions.alloc(res)))
|
||||
}
|
||||
|
||||
fn lower_enum(&mut self, enum_: &ast::Enum) -> Option<FileItemTreeId<Enum>> {
|
||||
let visibility = self.lower_visibility(enum_);
|
||||
let name = enum_.name()?.as_name();
|
||||
let generic_params = self.lower_generic_params(GenericsOwner::Enum, enum_);
|
||||
let variants = match &enum_.variant_list() {
|
||||
Some(variant_list) => self.lower_variants(variant_list),
|
||||
None => IdRange::new(self.next_variant_idx()..self.next_variant_idx()),
|
||||
};
|
||||
let ast_id = self.source_ast_id_map.ast_id(enum_);
|
||||
let res = Enum { name, visibility, generic_params, variants, ast_id };
|
||||
Some(id(self.data().enums.alloc(res)))
|
||||
}
|
||||
|
||||
fn lower_variants(&mut self, variants: &ast::VariantList) -> IdRange<Variant> {
|
||||
let start = self.next_variant_idx();
|
||||
for variant in variants.variants() {
|
||||
if let Some(data) = self.lower_variant(&variant) {
|
||||
let idx = self.data().variants.alloc(data);
|
||||
self.add_attrs(idx.into(), Attrs::new(&variant, &self.hygiene));
|
||||
}
|
||||
}
|
||||
let end = self.next_variant_idx();
|
||||
IdRange::new(start..end)
|
||||
}
|
||||
|
||||
fn lower_variant(&mut self, variant: &ast::Variant) -> Option<Variant> {
|
||||
let name = variant.name()?.as_name();
|
||||
let fields = self.lower_fields(&variant.kind());
|
||||
let res = Variant { name, fields };
|
||||
Some(res)
|
||||
}
|
||||
|
||||
fn lower_function(&mut self, func: &ast::Fn) -> Option<FileItemTreeId<Function>> {
|
||||
let visibility = self.lower_visibility(func);
|
||||
let name = func.name()?.as_name();
|
||||
|
||||
let mut params = Vec::new();
|
||||
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.ty() {
|
||||
Some(type_ref) => TypeRef::from_ast(&self.body_ctx, type_ref),
|
||||
None => {
|
||||
let self_type = TypeRef::Path(name![Self].into());
|
||||
match self_param.kind() {
|
||||
ast::SelfParamKind::Owned => self_type,
|
||||
ast::SelfParamKind::Ref => {
|
||||
TypeRef::Reference(Box::new(self_type), Mutability::Shared)
|
||||
}
|
||||
ast::SelfParamKind::MutRef => {
|
||||
TypeRef::Reference(Box::new(self_type), Mutability::Mut)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
params.push(self_type);
|
||||
has_self_param = true;
|
||||
}
|
||||
for param in param_list.params() {
|
||||
let type_ref = TypeRef::from_ast_opt(&self.body_ctx, param.ty());
|
||||
params.push(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(),
|
||||
};
|
||||
|
||||
let ret_type = if func.async_token().is_some() {
|
||||
let future_impl = desugar_future_path(ret_type);
|
||||
let ty_bound = TypeBound::Path(future_impl);
|
||||
TypeRef::ImplTrait(vec![ty_bound])
|
||||
} else {
|
||||
ret_type
|
||||
};
|
||||
|
||||
let ast_id = self.source_ast_id_map.ast_id(func);
|
||||
let mut res = Function {
|
||||
name,
|
||||
visibility,
|
||||
generic_params: GenericParamsId::EMPTY,
|
||||
has_self_param,
|
||||
is_unsafe: func.unsafe_token().is_some(),
|
||||
params: params.into_boxed_slice(),
|
||||
is_varargs,
|
||||
ret_type,
|
||||
ast_id,
|
||||
};
|
||||
res.generic_params = self.lower_generic_params(GenericsOwner::Function(&res), func);
|
||||
|
||||
Some(id(self.data().functions.alloc(res)))
|
||||
}
|
||||
|
||||
fn lower_type_alias(
|
||||
&mut self,
|
||||
type_alias: &ast::TypeAlias,
|
||||
) -> Option<FileItemTreeId<TypeAlias>> {
|
||||
let name = type_alias.name()?.as_name();
|
||||
let type_ref = type_alias.ty().map(|it| self.lower_type_ref(&it));
|
||||
let visibility = self.lower_visibility(type_alias);
|
||||
let bounds = self.lower_type_bounds(type_alias);
|
||||
let generic_params = self.lower_generic_params(GenericsOwner::TypeAlias, type_alias);
|
||||
let ast_id = self.source_ast_id_map.ast_id(type_alias);
|
||||
let res = TypeAlias {
|
||||
name,
|
||||
visibility,
|
||||
bounds: bounds.into_boxed_slice(),
|
||||
generic_params,
|
||||
type_ref,
|
||||
ast_id,
|
||||
};
|
||||
Some(id(self.data().type_aliases.alloc(res)))
|
||||
}
|
||||
|
||||
fn lower_static(&mut self, static_: &ast::Static) -> Option<FileItemTreeId<Static>> {
|
||||
let name = static_.name()?.as_name();
|
||||
let type_ref = self.lower_type_ref_opt(static_.ty());
|
||||
let visibility = self.lower_visibility(static_);
|
||||
let mutable = static_.mut_token().is_some();
|
||||
let ast_id = self.source_ast_id_map.ast_id(static_);
|
||||
let res = Static { name, visibility, mutable, type_ref, ast_id };
|
||||
Some(id(self.data().statics.alloc(res)))
|
||||
}
|
||||
|
||||
fn lower_const(&mut self, konst: &ast::Const) -> FileItemTreeId<Const> {
|
||||
let name = konst.name().map(|it| it.as_name());
|
||||
let type_ref = self.lower_type_ref_opt(konst.ty());
|
||||
let visibility = self.lower_visibility(konst);
|
||||
let ast_id = self.source_ast_id_map.ast_id(konst);
|
||||
let res = Const { name, visibility, type_ref, ast_id };
|
||||
id(self.data().consts.alloc(res))
|
||||
}
|
||||
|
||||
fn lower_module(&mut self, module: &ast::Module) -> Option<FileItemTreeId<Mod>> {
|
||||
let name = module.name()?.as_name();
|
||||
let visibility = self.lower_visibility(module);
|
||||
let kind = if module.semicolon_token().is_some() {
|
||||
ModKind::Outline {}
|
||||
} else {
|
||||
ModKind::Inline {
|
||||
items: module
|
||||
.item_list()
|
||||
.map(|list| {
|
||||
list.items()
|
||||
.flat_map(|item| self.lower_mod_item(&item, false))
|
||||
.flat_map(|items| items.0)
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
mark::hit!(name_res_works_for_broken_modules);
|
||||
Box::new([]) as Box<[_]>
|
||||
}),
|
||||
}
|
||||
};
|
||||
let ast_id = self.source_ast_id_map.ast_id(module);
|
||||
let res = Mod { name, visibility, kind, ast_id };
|
||||
Some(id(self.data().mods.alloc(res)))
|
||||
}
|
||||
|
||||
fn lower_trait(&mut self, trait_def: &ast::Trait) -> Option<FileItemTreeId<Trait>> {
|
||||
let name = trait_def.name()?.as_name();
|
||||
let visibility = self.lower_visibility(trait_def);
|
||||
let generic_params =
|
||||
self.lower_generic_params_and_inner_items(GenericsOwner::Trait(trait_def), trait_def);
|
||||
let auto = trait_def.auto_token().is_some();
|
||||
let items = trait_def.assoc_item_list().map(|list| {
|
||||
self.with_inherited_visibility(visibility, |this| {
|
||||
list.assoc_items()
|
||||
.filter_map(|item| {
|
||||
let attrs = Attrs::new(&item, &this.hygiene);
|
||||
this.collect_inner_items(item.syntax());
|
||||
this.lower_assoc_item(&item).map(|item| {
|
||||
this.add_attrs(ModItem::from(item).into(), attrs);
|
||||
item
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
});
|
||||
let ast_id = self.source_ast_id_map.ast_id(trait_def);
|
||||
let res = Trait {
|
||||
name,
|
||||
visibility,
|
||||
generic_params,
|
||||
auto,
|
||||
items: items.unwrap_or_default(),
|
||||
ast_id,
|
||||
};
|
||||
Some(id(self.data().traits.alloc(res)))
|
||||
}
|
||||
|
||||
fn lower_impl(&mut self, impl_def: &ast::Impl) -> Option<FileItemTreeId<Impl>> {
|
||||
let generic_params =
|
||||
self.lower_generic_params_and_inner_items(GenericsOwner::Impl, impl_def);
|
||||
let target_trait = impl_def.trait_().map(|tr| self.lower_type_ref(&tr));
|
||||
let target_type = self.lower_type_ref(&impl_def.self_ty()?);
|
||||
let is_negative = impl_def.excl_token().is_some();
|
||||
|
||||
// We cannot use `assoc_items()` here as that does not include macro calls.
|
||||
let items = impl_def
|
||||
.assoc_item_list()
|
||||
.into_iter()
|
||||
.flat_map(|it| it.assoc_items())
|
||||
.filter_map(|item| {
|
||||
self.collect_inner_items(item.syntax());
|
||||
let assoc = self.lower_assoc_item(&item)?;
|
||||
let attrs = Attrs::new(&item, &self.hygiene);
|
||||
self.add_attrs(ModItem::from(assoc).into(), attrs);
|
||||
Some(assoc)
|
||||
})
|
||||
.collect();
|
||||
let ast_id = self.source_ast_id_map.ast_id(impl_def);
|
||||
let res = Impl { generic_params, target_trait, target_type, is_negative, items, ast_id };
|
||||
Some(id(self.data().impls.alloc(res)))
|
||||
}
|
||||
|
||||
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);
|
||||
let ast_id = self.source_ast_id_map.ast_id(use_item);
|
||||
|
||||
// Every use item can expand to many `Import`s.
|
||||
let mut imports = Vec::new();
|
||||
let tree = self.tree.data_mut();
|
||||
ModPath::expand_use_item(
|
||||
InFile::new(self.file, use_item.clone()),
|
||||
&self.hygiene,
|
||||
|path, _tree, is_glob, alias| {
|
||||
imports.push(id(tree.imports.alloc(Import {
|
||||
path,
|
||||
alias,
|
||||
visibility,
|
||||
is_glob,
|
||||
is_prelude,
|
||||
ast_id,
|
||||
})));
|
||||
},
|
||||
);
|
||||
|
||||
imports
|
||||
}
|
||||
|
||||
fn lower_extern_crate(
|
||||
&mut self,
|
||||
extern_crate: &ast::ExternCrate,
|
||||
) -> Option<FileItemTreeId<ExternCrate>> {
|
||||
let path = ModPath::from_name_ref(&extern_crate.name_ref()?);
|
||||
let alias = extern_crate.rename().map(|a| {
|
||||
a.name().map(|it| it.as_name()).map_or(ImportAlias::Underscore, ImportAlias::Alias)
|
||||
});
|
||||
let visibility = self.lower_visibility(extern_crate);
|
||||
let ast_id = self.source_ast_id_map.ast_id(extern_crate);
|
||||
// FIXME: cfg_attr
|
||||
let is_macro_use = extern_crate.has_atom_attr("macro_use");
|
||||
|
||||
let res = ExternCrate { path, alias, visibility, is_macro_use, ast_id };
|
||||
Some(id(self.data().extern_crates.alloc(res)))
|
||||
}
|
||||
|
||||
fn lower_macro_call(&mut self, m: &ast::MacroCall) -> Option<FileItemTreeId<MacroCall>> {
|
||||
let name = m.name().map(|it| it.as_name());
|
||||
let attrs = Attrs::new(m, &self.hygiene);
|
||||
let path = ModPath::from_src(m.path()?, &self.hygiene)?;
|
||||
|
||||
let ast_id = self.source_ast_id_map.ast_id(m);
|
||||
|
||||
// FIXME: cfg_attr
|
||||
let export_attr = attrs.by_key("macro_export");
|
||||
|
||||
let is_export = export_attr.exists();
|
||||
let is_local_inner = if is_export {
|
||||
export_attr.tt_values().map(|it| &it.token_trees).flatten().any(|it| match it {
|
||||
tt::TokenTree::Leaf(tt::Leaf::Ident(ident)) => {
|
||||
ident.text.contains("local_inner_macros")
|
||||
}
|
||||
_ => false,
|
||||
})
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let is_builtin = attrs.by_key("rustc_builtin_macro").exists();
|
||||
let res = MacroCall { name, path, is_export, is_builtin, is_local_inner, ast_id };
|
||||
Some(id(self.data().macro_calls.alloc(res)))
|
||||
}
|
||||
|
||||
fn lower_extern_block(&mut self, block: &ast::ExternBlock) -> Vec<ModItem> {
|
||||
block.extern_item_list().map_or(Vec::new(), |list| {
|
||||
list.extern_items()
|
||||
.filter_map(|item| {
|
||||
self.collect_inner_items(item.syntax());
|
||||
let attrs = Attrs::new(&item, &self.hygiene);
|
||||
let id: ModItem = match item {
|
||||
ast::ExternItem::Fn(ast) => {
|
||||
let func = self.lower_function(&ast)?;
|
||||
self.data().functions[func.index].is_unsafe = true;
|
||||
func.into()
|
||||
}
|
||||
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)
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
/// Lowers generics defined on `node` and collects inner items defined within.
|
||||
fn lower_generic_params_and_inner_items(
|
||||
&mut self,
|
||||
owner: GenericsOwner<'_>,
|
||||
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.generic_param_list() {
|
||||
self.collect_inner_items(params.syntax());
|
||||
}
|
||||
if let Some(clause) = node.where_clause() {
|
||||
self.collect_inner_items(clause.syntax());
|
||||
}
|
||||
|
||||
self.lower_generic_params(owner, node)
|
||||
}
|
||||
|
||||
fn lower_generic_params(
|
||||
&mut self,
|
||||
owner: GenericsOwner<'_>,
|
||||
node: &impl ast::GenericParamsOwner,
|
||||
) -> GenericParamsId {
|
||||
let mut sm = &mut ArenaMap::default();
|
||||
let mut generics = GenericParams::default();
|
||||
match owner {
|
||||
GenericsOwner::Function(func) => {
|
||||
generics.fill(&self.body_ctx, sm, node);
|
||||
// lower `impl Trait` in arguments
|
||||
for param in &*func.params {
|
||||
generics.fill_implicit_impl_trait_args(param);
|
||||
}
|
||||
}
|
||||
GenericsOwner::Struct
|
||||
| GenericsOwner::Enum
|
||||
| GenericsOwner::Union
|
||||
| GenericsOwner::TypeAlias => {
|
||||
generics.fill(&self.body_ctx, sm, node);
|
||||
}
|
||||
GenericsOwner::Trait(trait_def) => {
|
||||
// traits get the Self type as an implicit first type parameter
|
||||
let self_param_id = generics.types.alloc(TypeParamData {
|
||||
name: Some(name![Self]),
|
||||
default: None,
|
||||
provenance: TypeParamProvenance::TraitSelf,
|
||||
});
|
||||
sm.insert(self_param_id, Either::Left(trait_def.clone()));
|
||||
// add super traits as bounds on Self
|
||||
// i.e., trait Foo: Bar is equivalent to trait Foo where Self: Bar
|
||||
let self_param = TypeRef::Path(name![Self].into());
|
||||
generics.fill_bounds(&self.body_ctx, trait_def, self_param);
|
||||
|
||||
generics.fill(&self.body_ctx, &mut sm, node);
|
||||
}
|
||||
GenericsOwner::Impl => {
|
||||
// Note that we don't add `Self` here: in `impl`s, `Self` is not a
|
||||
// type-parameter, but rather is a type-alias for impl's target
|
||||
// type, so this is handled by the resolver.
|
||||
generics.fill(&self.body_ctx, &mut sm, node);
|
||||
}
|
||||
}
|
||||
|
||||
self.data().generics.alloc(generics)
|
||||
}
|
||||
|
||||
fn lower_type_bounds(&mut self, node: &impl ast::TypeBoundsOwner) -> Vec<TypeBound> {
|
||||
match node.type_bound_list() {
|
||||
Some(bound_list) => {
|
||||
bound_list.bounds().map(|it| TypeBound::from_ast(&self.body_ctx, it)).collect()
|
||||
}
|
||||
None => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn lower_visibility(&mut self, item: &impl ast::VisibilityOwner) -> RawVisibilityId {
|
||||
let vis = match self.forced_visibility {
|
||||
Some(vis) => return vis,
|
||||
None => RawVisibility::from_ast_with_hygiene(item.visibility(), &self.hygiene),
|
||||
};
|
||||
|
||||
self.data().vis.alloc(vis)
|
||||
}
|
||||
|
||||
fn lower_type_ref(&self, type_ref: &ast::Type) -> TypeRef {
|
||||
TypeRef::from_ast(&self.body_ctx, type_ref.clone())
|
||||
}
|
||||
fn lower_type_ref_opt(&self, type_ref: Option<ast::Type>) -> TypeRef {
|
||||
type_ref.map(|ty| self.lower_type_ref(&ty)).unwrap_or(TypeRef::Error)
|
||||
}
|
||||
|
||||
/// Forces the visibility `vis` to be used for all items lowered during execution of `f`.
|
||||
fn with_inherited_visibility<R>(
|
||||
&mut self,
|
||||
vis: RawVisibilityId,
|
||||
f: impl FnOnce(&mut Self) -> R,
|
||||
) -> R {
|
||||
let old = mem::replace(&mut self.forced_visibility, Some(vis));
|
||||
let res = f(self);
|
||||
self.forced_visibility = old;
|
||||
res
|
||||
}
|
||||
|
||||
fn next_field_idx(&self) -> Idx<Field> {
|
||||
Idx::from_raw(RawId::from(
|
||||
self.tree.data.as_ref().map_or(0, |data| data.fields.len() as u32),
|
||||
))
|
||||
}
|
||||
fn next_variant_idx(&self) -> Idx<Variant> {
|
||||
Idx::from_raw(RawId::from(
|
||||
self.tree.data.as_ref().map_or(0, |data| data.variants.len() as u32),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn desugar_future_path(orig: TypeRef) -> Path {
|
||||
let path = path![core::future::Future];
|
||||
let mut generic_args: Vec<_> = std::iter::repeat(None).take(path.segments.len() - 1).collect();
|
||||
let mut last = GenericArgs::empty();
|
||||
let binding =
|
||||
AssociatedTypeBinding { name: name![Output], type_ref: Some(orig), bounds: Vec::new() };
|
||||
last.bindings.push(binding);
|
||||
generic_args.push(Some(Arc::new(last)));
|
||||
|
||||
Path::from_known_path(path, generic_args)
|
||||
}
|
||||
|
||||
enum GenericsOwner<'a> {
|
||||
/// We need access to the partially-lowered `Function` for lowering `impl Trait` in argument
|
||||
/// position.
|
||||
Function(&'a Function),
|
||||
Struct,
|
||||
Enum,
|
||||
Union,
|
||||
/// The `TraitDef` is needed to fill the source map for the implicit `Self` parameter.
|
||||
Trait(&'a ast::Trait),
|
||||
TypeAlias,
|
||||
Impl,
|
||||
}
|
439
crates/hir_def/src/item_tree/tests.rs
Normal file
439
crates/hir_def/src/item_tree/tests.rs
Normal file
|
@ -0,0 +1,439 @@
|
|||
use base_db::fixture::WithFixture;
|
||||
use expect::{expect, Expect};
|
||||
use hir_expand::{db::AstDatabase, HirFileId, InFile};
|
||||
use rustc_hash::FxHashSet;
|
||||
use std::sync::Arc;
|
||||
use stdx::format_to;
|
||||
use syntax::{ast, AstNode};
|
||||
|
||||
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);
|
||||
let tree = db.item_tree(file_id);
|
||||
let root = db.parse_or_expand(file_id).unwrap();
|
||||
let ast_id_map = db.ast_id_map(file_id);
|
||||
|
||||
// Traverse the item tree and collect all module/impl/trait-level items as AST nodes.
|
||||
let mut outer_items = FxHashSet::default();
|
||||
let mut worklist = tree.top_level_items().to_vec();
|
||||
while let Some(item) = worklist.pop() {
|
||||
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(),
|
||||
ModItem::Struct(it) => tree.source(&db, InFile::new(file_id, it)).into(),
|
||||
ModItem::Union(it) => tree.source(&db, InFile::new(file_id, it)).into(),
|
||||
ModItem::Enum(it) => tree.source(&db, InFile::new(file_id, it)).into(),
|
||||
ModItem::Const(it) => tree.source(&db, InFile::new(file_id, it)).into(),
|
||||
ModItem::Static(it) => tree.source(&db, InFile::new(file_id, it)).into(),
|
||||
ModItem::TypeAlias(it) => tree.source(&db, InFile::new(file_id, it)).into(),
|
||||
ModItem::Mod(it) => {
|
||||
if let ModKind::Inline { items } = &tree[it].kind {
|
||||
worklist.extend(&**items);
|
||||
}
|
||||
tree.source(&db, InFile::new(file_id, it)).into()
|
||||
}
|
||||
ModItem::Trait(it) => {
|
||||
worklist.extend(tree[it].items.iter().map(|item| ModItem::from(*item)));
|
||||
tree.source(&db, InFile::new(file_id, it)).into()
|
||||
}
|
||||
ModItem::Impl(it) => {
|
||||
worklist.extend(tree[it].items.iter().map(|item| ModItem::from(*item)));
|
||||
tree.source(&db, InFile::new(file_id, it)).into()
|
||||
}
|
||||
ModItem::MacroCall(_) => continue,
|
||||
};
|
||||
|
||||
outer_items.insert(node);
|
||||
}
|
||||
|
||||
// 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::Item::cast) {
|
||||
if outer_items.contains(&item) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let ast_id = ast_id_map.ast_id(&item);
|
||||
assert!(!tree.inner_items(ast_id).is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
fn item_tree(ra_fixture: &str) -> Arc<ItemTree> {
|
||||
let (db, file_id) = TestDB::with_single_file(ra_fixture);
|
||||
db.item_tree(file_id.into())
|
||||
}
|
||||
|
||||
fn print_item_tree(ra_fixture: &str) -> String {
|
||||
let tree = item_tree(ra_fixture);
|
||||
let mut out = String::new();
|
||||
|
||||
format_to!(out, "inner attrs: {:?}\n\n", tree.top_level_attrs());
|
||||
format_to!(out, "top-level items:\n");
|
||||
for item in tree.top_level_items() {
|
||||
fmt_mod_item(&mut out, &tree, *item);
|
||||
format_to!(out, "\n");
|
||||
}
|
||||
|
||||
if !tree.inner_items.is_empty() {
|
||||
format_to!(out, "\ninner items:\n\n");
|
||||
for (ast_id, items) in &tree.inner_items {
|
||||
format_to!(out, "for AST {:?}:\n", ast_id);
|
||||
for inner in items {
|
||||
fmt_mod_item(&mut out, &tree, *inner);
|
||||
format_to!(out, "\n\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
fn fmt_mod_item(out: &mut String, tree: &ItemTree, item: ModItem) {
|
||||
let attrs = tree.attrs(item.into());
|
||||
if !attrs.is_empty() {
|
||||
format_to!(out, "#[{:?}]\n", attrs);
|
||||
}
|
||||
|
||||
let mut children = String::new();
|
||||
match item {
|
||||
ModItem::ExternCrate(it) => {
|
||||
format_to!(out, "{:?}", tree[it]);
|
||||
}
|
||||
ModItem::Import(it) => {
|
||||
format_to!(out, "{:?}", tree[it]);
|
||||
}
|
||||
ModItem::Function(it) => {
|
||||
format_to!(out, "{:?}", tree[it]);
|
||||
}
|
||||
ModItem::Struct(it) => {
|
||||
format_to!(out, "{:?}", tree[it]);
|
||||
}
|
||||
ModItem::Union(it) => {
|
||||
format_to!(out, "{:?}", tree[it]);
|
||||
}
|
||||
ModItem::Enum(it) => {
|
||||
format_to!(out, "{:?}", tree[it]);
|
||||
}
|
||||
ModItem::Const(it) => {
|
||||
format_to!(out, "{:?}", tree[it]);
|
||||
}
|
||||
ModItem::Static(it) => {
|
||||
format_to!(out, "{:?}", tree[it]);
|
||||
}
|
||||
ModItem::Trait(it) => {
|
||||
format_to!(out, "{:?}", tree[it]);
|
||||
for item in &*tree[it].items {
|
||||
fmt_mod_item(&mut children, tree, ModItem::from(*item));
|
||||
format_to!(children, "\n");
|
||||
}
|
||||
}
|
||||
ModItem::Impl(it) => {
|
||||
format_to!(out, "{:?}", tree[it]);
|
||||
for item in &*tree[it].items {
|
||||
fmt_mod_item(&mut children, tree, ModItem::from(*item));
|
||||
format_to!(children, "\n");
|
||||
}
|
||||
}
|
||||
ModItem::TypeAlias(it) => {
|
||||
format_to!(out, "{:?}", tree[it]);
|
||||
}
|
||||
ModItem::Mod(it) => {
|
||||
format_to!(out, "{:?}", tree[it]);
|
||||
match &tree[it].kind {
|
||||
ModKind::Inline { items } => {
|
||||
for item in &**items {
|
||||
fmt_mod_item(&mut children, tree, *item);
|
||||
format_to!(children, "\n");
|
||||
}
|
||||
}
|
||||
ModKind::Outline {} => {}
|
||||
}
|
||||
}
|
||||
ModItem::MacroCall(it) => {
|
||||
format_to!(out, "{:?}", tree[it]);
|
||||
}
|
||||
}
|
||||
|
||||
for line in children.lines() {
|
||||
format_to!(out, "\n> {}", line);
|
||||
}
|
||||
}
|
||||
|
||||
fn check(ra_fixture: &str, expect: Expect) {
|
||||
let actual = print_item_tree(ra_fixture);
|
||||
expect.assert_eq(&actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smoke() {
|
||||
check(
|
||||
r"
|
||||
#![attr]
|
||||
|
||||
#[attr_on_use]
|
||||
use {a, b::*};
|
||||
|
||||
#[ext_crate]
|
||||
extern crate krate;
|
||||
|
||||
#[on_trait]
|
||||
trait Tr<U> {
|
||||
#[assoc_ty]
|
||||
type AssocTy: Tr<()>;
|
||||
|
||||
#[assoc_const]
|
||||
const CONST: u8;
|
||||
|
||||
#[assoc_method]
|
||||
fn method(&self);
|
||||
|
||||
#[assoc_dfl_method]
|
||||
fn dfl_method(&mut self) {}
|
||||
}
|
||||
|
||||
#[struct0]
|
||||
struct Struct0<T = ()>;
|
||||
|
||||
#[struct1]
|
||||
struct Struct1<T>(#[struct1fld] u8);
|
||||
|
||||
#[struct2]
|
||||
struct Struct2<T> {
|
||||
#[struct2fld]
|
||||
fld: (T, ),
|
||||
}
|
||||
|
||||
#[en]
|
||||
enum En {
|
||||
#[enum_variant]
|
||||
Variant {
|
||||
#[enum_field]
|
||||
field: u8,
|
||||
},
|
||||
}
|
||||
|
||||
#[un]
|
||||
union Un {
|
||||
#[union_fld]
|
||||
fld: u16,
|
||||
}
|
||||
",
|
||||
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::<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::<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::<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::<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::<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::<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::<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::<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::<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::<hir_def::item_tree::Field>(0..1)), ast_id: FileAstId::<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::<hir_def::item_tree::Field>(1..2)), ast_id: FileAstId::<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::<hir_def::item_tree::Variant>(0..1), ast_id: FileAstId::<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::<hir_def::item_tree::Field>(3..4)), ast_id: FileAstId::<syntax::ast::generated::nodes::Union>(7) }
|
||||
"##]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_inner_items() {
|
||||
check(
|
||||
r"
|
||||
impl<T:A> D for Response<T> {
|
||||
fn foo() {
|
||||
end();
|
||||
fn end<W: Write>() {
|
||||
let _x: T = loop {};
|
||||
}
|
||||
}
|
||||
}
|
||||
",
|
||||
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::<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::<syntax::ast::generated::nodes::Fn>(1) }
|
||||
|
||||
inner items:
|
||||
|
||||
for AST FileAstId::<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::<syntax::ast::generated::nodes::Fn>(2) }
|
||||
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extern_attrs() {
|
||||
check(
|
||||
r#"
|
||||
#[block_attr]
|
||||
extern "C" {
|
||||
#[attr_a]
|
||||
fn a() {}
|
||||
#[attr_b]
|
||||
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::<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::<syntax::ast::generated::nodes::Fn>(2) }
|
||||
"##]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trait_attrs() {
|
||||
check(
|
||||
r#"
|
||||
#[trait_attr]
|
||||
trait Tr {
|
||||
#[attr_a]
|
||||
fn a() {}
|
||||
#[attr_b]
|
||||
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::<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::<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::<syntax::ast::generated::nodes::Fn>(2) }
|
||||
"##]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn impl_attrs() {
|
||||
check(
|
||||
r#"
|
||||
#[impl_attr]
|
||||
impl Ty {
|
||||
#[attr_a]
|
||||
fn a() {}
|
||||
#[attr_b]
|
||||
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::<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::<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::<syntax::ast::generated::nodes::Fn>(2) }
|
||||
"##]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cursed_inner_items() {
|
||||
test_inner_items(
|
||||
r"
|
||||
struct S<T: Trait = [u8; { fn f() {} 0 }]>(T);
|
||||
|
||||
enum En {
|
||||
Var1 {
|
||||
t: [(); { trait Inner {} 0 }],
|
||||
},
|
||||
|
||||
Var2([u16; { enum Inner {} 0 }]),
|
||||
}
|
||||
|
||||
type Ty = [En; { struct Inner; 0 }];
|
||||
|
||||
impl En {
|
||||
fn assoc() {
|
||||
trait InnerTrait<T = [u8; { fn f() {} }]> {}
|
||||
struct InnerStruct<T = [u8; { fn f() {} }]> {}
|
||||
impl<T = [u8; { fn f() {} }]> InnerTrait for InnerStruct {}
|
||||
}
|
||||
}
|
||||
|
||||
trait Tr<T = [u8; { fn f() {} }]> {
|
||||
type AssocTy = [u8; { fn f() {} }];
|
||||
|
||||
const AssocConst: [u8; { fn f() {} }];
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inner_item_attrs() {
|
||||
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::<syntax::ast::generated::nodes::Fn>(0) }
|
||||
|
||||
inner items:
|
||||
|
||||
for AST FileAstId::<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::<syntax::ast::generated::nodes::Fn>(1) }
|
||||
|
||||
"##]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assoc_item_macros() {
|
||||
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::<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::<syntax::ast::generated::nodes::MacroCall>(1) }
|
||||
"#]],
|
||||
);
|
||||
}
|
58
crates/hir_def/src/keys.rs
Normal file
58
crates/hir_def/src/keys.rs
Normal file
|
@ -0,0 +1,58 @@
|
|||
//! keys to be used with `DynMap`
|
||||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use hir_expand::{InFile, MacroDefId};
|
||||
use rustc_hash::FxHashMap;
|
||||
use syntax::{ast, AstNode, AstPtr};
|
||||
|
||||
use crate::{
|
||||
dyn_map::{DynMap, Policy},
|
||||
ConstId, EnumId, EnumVariantId, FieldId, FunctionId, ImplId, StaticId, StructId, TraitId,
|
||||
TypeAliasId, TypeParamId, UnionId,
|
||||
};
|
||||
|
||||
pub type Key<K, V> = crate::dyn_map::Key<InFile<K>, V, AstPtrPolicy<K, V>>;
|
||||
|
||||
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 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();
|
||||
|
||||
/// XXX: AST Nodes and SyntaxNodes have identity equality semantics: nodes are
|
||||
/// equal if they point to exactly the same object.
|
||||
///
|
||||
/// In general, we do not guarantee that we have exactly one instance of a
|
||||
/// syntax tree for each file. We probably should add such guarantee, but, for
|
||||
/// the time being, we will use identity-less AstPtr comparison.
|
||||
pub struct AstPtrPolicy<AST, ID> {
|
||||
_phantom: PhantomData<(AST, ID)>,
|
||||
}
|
||||
|
||||
impl<AST: AstNode + 'static, ID: 'static> Policy for AstPtrPolicy<AST, ID> {
|
||||
type K = InFile<AST>;
|
||||
type V = ID;
|
||||
fn insert(map: &mut DynMap, key: InFile<AST>, value: ID) {
|
||||
let key = key.as_ref().map(AstPtr::new);
|
||||
map.map
|
||||
.entry::<FxHashMap<InFile<AstPtr<AST>>, ID>>()
|
||||
.or_insert_with(Default::default)
|
||||
.insert(key, value);
|
||||
}
|
||||
fn get<'a>(map: &'a DynMap, key: &InFile<AST>) -> Option<&'a ID> {
|
||||
let key = key.as_ref().map(AstPtr::new);
|
||||
map.map.get::<FxHashMap<InFile<AstPtr<AST>>, ID>>()?.get(&key)
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue