Skip to content

Introduce Semantics API #3222

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 11 additions & 29 deletions crates/ra_assists/src/assist_ctx.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! This module defines `AssistCtx` -- the API surface that is exposed to assists.
use hir::{InFile, SourceAnalyzer, SourceBinder};
use ra_db::{FileRange, SourceDatabase};
use hir::Semantics;
use ra_db::FileRange;
use ra_fmt::{leading_indent, reindent};
use ra_ide_db::RootDatabase;
use ra_syntax::{
Expand Down Expand Up @@ -74,29 +74,23 @@ pub(crate) type AssistHandler = fn(AssistCtx) -> Option<Assist>;
/// 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 :-)
#[derive(Debug)]
#[derive(Clone)]
pub(crate) struct AssistCtx<'a> {
pub(crate) sema: &'a Semantics<'a, RootDatabase>,
pub(crate) db: &'a RootDatabase,
pub(crate) frange: FileRange,
source_file: SourceFile,
should_compute_edit: bool,
}

impl Clone for AssistCtx<'_> {
fn clone(&self) -> Self {
AssistCtx {
db: self.db,
frange: self.frange,
source_file: self.source_file.clone(),
should_compute_edit: self.should_compute_edit,
}
}
}

impl<'a> AssistCtx<'a> {
pub fn new(db: &RootDatabase, frange: FileRange, should_compute_edit: bool) -> AssistCtx {
let parse = db.parse(frange.file_id);
AssistCtx { db, frange, source_file: parse.tree(), should_compute_edit }
pub fn new(
sema: &'a Semantics<'a, RootDatabase>,
frange: FileRange,
should_compute_edit: bool,
) -> AssistCtx<'a> {
let source_file = sema.parse(frange.file_id);
AssistCtx { sema, db: sema.db, frange, source_file, should_compute_edit }
}

pub(crate) fn add_assist(
Expand Down Expand Up @@ -138,18 +132,6 @@ impl<'a> AssistCtx<'a> {
pub(crate) fn covering_element(&self) -> SyntaxElement {
find_covering_element(self.source_file.syntax(), self.frange.range)
}
pub(crate) fn source_binder(&self) -> SourceBinder<'a, RootDatabase> {
SourceBinder::new(self.db)
}
pub(crate) fn source_analyzer(
&self,
node: &SyntaxNode,
offset: Option<TextUnit>,
) -> SourceAnalyzer {
let src = InFile::new(self.frange.file_id.into(), node);
self.source_binder().analyze(src, offset)
}

pub(crate) fn covering_node_for_range(&self, range: TextRange) -> SyntaxElement {
find_covering_element(self.source_file.syntax(), range)
}
Expand Down
65 changes: 30 additions & 35 deletions crates/ra_assists/src/ast_transform.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
//! `AstTransformer`s are functions that replace nodes in an AST and can be easily combined.
use rustc_hash::FxHashMap;

use hir::{InFile, PathResolution};
use hir::{PathResolution, SemanticsScope};
use ra_ide_db::RootDatabase;
use ra_syntax::ast::{self, AstNode};

pub trait AstTransform<'a> {
fn get_substitution(
&self,
node: InFile<&ra_syntax::SyntaxNode>,
) -> Option<ra_syntax::SyntaxNode>;
fn get_substitution(&self, node: &ra_syntax::SyntaxNode) -> Option<ra_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>
Expand All @@ -23,10 +20,7 @@ pub trait AstTransform<'a> {
struct NullTransformer;

impl<'a> AstTransform<'a> for NullTransformer {
fn get_substitution(
&self,
_node: InFile<&ra_syntax::SyntaxNode>,
) -> Option<ra_syntax::SyntaxNode> {
fn get_substitution(&self, _node: &ra_syntax::SyntaxNode) -> Option<ra_syntax::SyntaxNode> {
None
}
fn chain_before(self, other: Box<dyn AstTransform<'a> + 'a>) -> Box<dyn AstTransform<'a> + 'a> {
Expand All @@ -35,14 +29,16 @@ impl<'a> AstTransform<'a> for NullTransformer {
}

pub struct SubstituteTypeParams<'a> {
db: &'a RootDatabase,
source_scope: &'a SemanticsScope<'a, RootDatabase>,
substs: FxHashMap<hir::TypeParam, ast::TypeRef>,
previous: Box<dyn AstTransform<'a> + 'a>,
}

impl<'a> SubstituteTypeParams<'a> {
pub fn for_trait_impl(
source_scope: &'a SemanticsScope<'a, RootDatabase>,
db: &'a RootDatabase,
// FIXME: there's implicit invariant that `trait_` and `source_scope` match...
trait_: hir::Trait,
impl_block: ast::ImplBlock,
) -> SubstituteTypeParams<'a> {
Expand All @@ -56,7 +52,7 @@ impl<'a> SubstituteTypeParams<'a> {
.zip(substs.into_iter())
.collect();
return SubstituteTypeParams {
db,
source_scope,
substs: substs_by_param,
previous: Box::new(NullTransformer),
};
Expand All @@ -80,15 +76,15 @@ impl<'a> SubstituteTypeParams<'a> {
}
fn get_substitution_inner(
&self,
node: InFile<&ra_syntax::SyntaxNode>,
node: &ra_syntax::SyntaxNode,
) -> Option<ra_syntax::SyntaxNode> {
let type_ref = ast::TypeRef::cast(node.value.clone())?;
let type_ref = ast::TypeRef::cast(node.clone())?;
let path = match &type_ref {
ast::TypeRef::PathType(path_type) => path_type.path()?,
_ => return None,
};
let analyzer = hir::SourceAnalyzer::new(self.db, node, None);
let resolution = analyzer.resolve_path(self.db, &path)?;
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,
Expand All @@ -97,10 +93,7 @@ impl<'a> SubstituteTypeParams<'a> {
}

impl<'a> AstTransform<'a> for SubstituteTypeParams<'a> {
fn get_substitution(
&self,
node: InFile<&ra_syntax::SyntaxNode>,
) -> Option<ra_syntax::SyntaxNode> {
fn get_substitution(&self, node: &ra_syntax::SyntaxNode) -> Option<ra_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> {
Expand All @@ -109,29 +102,34 @@ impl<'a> AstTransform<'a> for SubstituteTypeParams<'a> {
}

pub struct QualifyPaths<'a> {
target_scope: &'a SemanticsScope<'a, RootDatabase>,
source_scope: &'a SemanticsScope<'a, RootDatabase>,
db: &'a RootDatabase,
from: Option<hir::Module>,
previous: Box<dyn AstTransform<'a> + 'a>,
}

impl<'a> QualifyPaths<'a> {
pub fn new(db: &'a RootDatabase, from: Option<hir::Module>) -> Self {
Self { db, from, previous: Box::new(NullTransformer) }
pub fn new(
target_scope: &'a SemanticsScope<'a, RootDatabase>,
source_scope: &'a SemanticsScope<'a, RootDatabase>,
db: &'a RootDatabase,
) -> Self {
Self { target_scope, source_scope, db, previous: Box::new(NullTransformer) }
}

fn get_substitution_inner(
&self,
node: InFile<&ra_syntax::SyntaxNode>,
node: &ra_syntax::SyntaxNode,
) -> Option<ra_syntax::SyntaxNode> {
// FIXME handle value ns?
let from = self.from?;
let p = ast::Path::cast(node.value.clone())?;
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;
}
let analyzer = hir::SourceAnalyzer::new(self.db, node, None);
let resolution = analyzer.resolve_path(self.db, &p)?;
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.db, def)?;
Expand All @@ -140,7 +138,7 @@ impl<'a> QualifyPaths<'a> {
let type_args = p
.segment()
.and_then(|s| s.type_arg_list())
.map(|arg_list| apply(self, node.with_value(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))
Expand All @@ -157,11 +155,11 @@ impl<'a> QualifyPaths<'a> {
}
}

pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: InFile<N>) -> N {
let syntax = node.value.syntax();
pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: N) -> N {
let syntax = node.syntax();
let result = ra_syntax::algo::replace_descendants(syntax, &|element| match element {
ra_syntax::SyntaxElement::Node(n) => {
let replacement = transformer.get_substitution(node.with_value(&n))?;
let replacement = transformer.get_substitution(&n)?;
Some(replacement.into())
}
_ => None,
Expand All @@ -170,10 +168,7 @@ pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: InFile<N>
}

impl<'a> AstTransform<'a> for QualifyPaths<'a> {
fn get_substitution(
&self,
node: InFile<&ra_syntax::SyntaxNode>,
) -> Option<ra_syntax::SyntaxNode> {
fn get_substitution(&self, node: &ra_syntax::SyntaxNode) -> Option<ra_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> {
Expand Down
5 changes: 2 additions & 3 deletions crates/ra_assists/src/handlers/add_explicit_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,13 @@ pub(crate) fn add_explicit_type(ctx: AssistCtx) -> Option<Assist> {
}
}
// Infer type
let db = ctx.db;
let analyzer = ctx.source_analyzer(stmt.syntax(), None);
let ty = analyzer.type_of(db, &expr)?;
let ty = ctx.sema.type_of_expr(&expr)?;
// Assist not applicable if the type is unknown
if ty.contains_unknown() {
return None;
}

let db = ctx.db;
ctx.add_assist(
AssistId("add_explicit_type"),
format!("Insert explicit type '{}'", ty.display(db)),
Expand Down
29 changes: 11 additions & 18 deletions crates/ra_assists/src/handlers/add_missing_impl_members.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use hir::{HasSource, InFile};
use hir::HasSource;
use ra_syntax::{
ast::{self, edit, make, AstNode, NameOwner},
SmolStr,
Expand Down Expand Up @@ -104,9 +104,7 @@ fn add_missing_impl_members_inner(
let impl_node = ctx.find_node_at_offset::<ast::ImplBlock>()?;
let impl_item_list = impl_node.item_list()?;

let analyzer = ctx.source_analyzer(impl_node.syntax(), None);

let trait_ = resolve_target_trait(ctx.db, &analyzer, &impl_node)?;
let trait_ = resolve_target_trait(&ctx.sema, &impl_node)?;

let def_name = |item: &ast::ImplItem| -> Option<SmolStr> {
match item {
Expand All @@ -117,7 +115,7 @@ fn add_missing_impl_members_inner(
.map(|it| it.text().clone())
};

let missing_items = get_missing_impl_items(ctx.db, &analyzer, &impl_node)
let missing_items = get_missing_impl_items(&ctx.sema, &impl_node)
.iter()
.map(|i| match i {
hir::AssocItem::Function(i) => ast::ImplItem::FnDef(i.source(ctx.db).value),
Expand All @@ -138,23 +136,17 @@ fn add_missing_impl_members_inner(
return None;
}

let db = ctx.db;
let file_id = ctx.frange.file_id;
let trait_file_id = trait_.source(db).file_id;
let sema = ctx.sema;

ctx.add_assist(AssistId(assist_id), label, |edit| {
let n_existing_items = impl_item_list.impl_items().count();
let module = hir::SourceAnalyzer::new(
db,
hir::InFile::new(file_id.into(), impl_node.syntax()),
None,
)
.module();
let ast_transform = QualifyPaths::new(db, module)
.or(SubstituteTypeParams::for_trait_impl(db, trait_, impl_node));
let source_scope = sema.scope_for_def(trait_);
let target_scope = sema.scope(impl_item_list.syntax());
let ast_transform = QualifyPaths::new(&target_scope, &source_scope, sema.db)
.or(SubstituteTypeParams::for_trait_impl(&source_scope, sema.db, trait_, impl_node));
let items = missing_items
.into_iter()
.map(|it| ast_transform::apply(&*ast_transform, InFile::new(trait_file_id, it)))
.map(|it| ast_transform::apply(&*ast_transform, it))
.map(|it| match it {
ast::ImplItem::FnDef(def) => ast::ImplItem::FnDef(add_body(def)),
_ => it,
Expand All @@ -181,9 +173,10 @@ fn add_body(fn_def: ast::FnDef) -> ast::FnDef {

#[cfg(test)]
mod tests {
use super::*;
use crate::helpers::{check_assist, check_assist_not_applicable};

use super::*;

#[test]
fn test_add_missing_impl_members() {
check_assist(
Expand Down
11 changes: 3 additions & 8 deletions crates/ra_assists/src/handlers/add_new.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use format_buf::format;
use hir::{Adt, InFile};
use hir::Adt;
use join_to_string::join;
use ra_syntax::{
ast::{
Expand Down Expand Up @@ -133,16 +133,11 @@ fn find_struct_impl(ctx: &AssistCtx, strukt: &ast::StructDef) -> Option<Option<a
let module = strukt.syntax().ancestors().find(|node| {
ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind())
})?;
let mut sb = ctx.source_binder();

let struct_def = {
let src = InFile { file_id: ctx.frange.file_id.into(), value: strukt.clone() };
sb.to_def(src)?
};
let struct_def = ctx.sema.to_def(strukt)?;

let block = module.descendants().filter_map(ast::ImplBlock::cast).find_map(|impl_blk| {
let src = InFile { file_id: ctx.frange.file_id.into(), value: impl_blk.clone() };
let blk = sb.to_def(src)?;
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)
Expand Down
Loading