2020-07-22 01:46:29 -05:00
|
|
|
//! This module is responsible for resolving paths within rules.
|
|
|
|
|
2022-04-01 11:32:05 -05:00
|
|
|
use hir::AsAssocItem;
|
2022-04-25 11:51:59 -05:00
|
|
|
use ide_db::{base_db::FilePosition, FxHashMap};
|
2020-07-22 01:46:29 -05:00
|
|
|
use parsing::Placeholder;
|
2020-08-12 11:26:51 -05:00
|
|
|
use syntax::{ast, SmolStr, SyntaxKind, SyntaxNode, SyntaxToken};
|
2020-07-22 01:46:29 -05:00
|
|
|
|
2022-04-25 11:51:59 -05:00
|
|
|
use crate::{errors::error, parsing, SsrError};
|
|
|
|
|
2020-07-25 23:49:18 -05:00
|
|
|
pub(crate) struct ResolutionScope<'db> {
|
|
|
|
scope: hir::SemanticsScope<'db>,
|
2020-08-01 02:41:42 -05:00
|
|
|
node: SyntaxNode,
|
2020-07-25 23:49:18 -05:00
|
|
|
}
|
|
|
|
|
2020-07-22 01:46:29 -05:00
|
|
|
pub(crate) struct ResolvedRule {
|
|
|
|
pub(crate) pattern: ResolvedPattern,
|
|
|
|
pub(crate) template: Option<ResolvedPattern>,
|
|
|
|
pub(crate) index: usize,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) struct ResolvedPattern {
|
|
|
|
pub(crate) placeholders_by_stand_in: FxHashMap<SmolStr, parsing::Placeholder>,
|
|
|
|
pub(crate) node: SyntaxNode,
|
|
|
|
// Paths in `node` that we've resolved.
|
|
|
|
pub(crate) resolved_paths: FxHashMap<SyntaxNode, ResolvedPath>,
|
2020-08-05 04:48:52 -05:00
|
|
|
pub(crate) ufcs_function_calls: FxHashMap<SyntaxNode, UfcsCallInfo>,
|
2020-08-01 02:41:42 -05:00
|
|
|
pub(crate) contains_self: bool,
|
2020-07-22 01:46:29 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) struct ResolvedPath {
|
|
|
|
pub(crate) resolution: hir::PathResolution,
|
2020-07-24 05:53:48 -05:00
|
|
|
/// The depth of the ast::Path that was resolved within the pattern.
|
2020-07-21 23:01:21 -05:00
|
|
|
pub(crate) depth: u32,
|
2020-07-22 01:46:29 -05:00
|
|
|
}
|
|
|
|
|
2020-08-05 04:48:52 -05:00
|
|
|
pub(crate) struct UfcsCallInfo {
|
|
|
|
pub(crate) call_expr: ast::CallExpr,
|
|
|
|
pub(crate) function: hir::Function,
|
|
|
|
pub(crate) qualifier_type: Option<hir::Type>,
|
|
|
|
}
|
|
|
|
|
2020-07-22 01:46:29 -05:00
|
|
|
impl ResolvedRule {
|
|
|
|
pub(crate) fn new(
|
|
|
|
rule: parsing::ParsedRule,
|
2022-07-20 08:02:08 -05:00
|
|
|
resolution_scope: &ResolutionScope<'_>,
|
2020-07-22 01:46:29 -05:00
|
|
|
index: usize,
|
|
|
|
) -> Result<ResolvedRule, SsrError> {
|
|
|
|
let resolver =
|
2020-07-25 23:49:18 -05:00
|
|
|
Resolver { resolution_scope, placeholders_by_stand_in: rule.placeholders_by_stand_in };
|
2021-10-03 07:53:01 -05:00
|
|
|
let resolved_template = match rule.template {
|
|
|
|
Some(template) => Some(resolver.resolve_pattern_tree(template)?),
|
|
|
|
None => None,
|
2020-07-22 01:46:29 -05:00
|
|
|
};
|
|
|
|
Ok(ResolvedRule {
|
|
|
|
pattern: resolver.resolve_pattern_tree(rule.pattern)?,
|
|
|
|
template: resolved_template,
|
|
|
|
index,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn get_placeholder(&self, token: &SyntaxToken) -> Option<&Placeholder> {
|
|
|
|
if token.kind() != SyntaxKind::IDENT {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
self.pattern.placeholders_by_stand_in.get(token.text())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct Resolver<'a, 'db> {
|
2020-07-25 23:49:18 -05:00
|
|
|
resolution_scope: &'a ResolutionScope<'db>,
|
2020-07-22 01:46:29 -05:00
|
|
|
placeholders_by_stand_in: FxHashMap<SmolStr, parsing::Placeholder>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Resolver<'_, '_> {
|
|
|
|
fn resolve_pattern_tree(&self, pattern: SyntaxNode) -> Result<ResolvedPattern, SsrError> {
|
2020-08-05 04:48:52 -05:00
|
|
|
use syntax::ast::AstNode;
|
2020-08-12 11:26:51 -05:00
|
|
|
use syntax::{SyntaxElement, T};
|
2020-07-22 01:46:29 -05:00
|
|
|
let mut resolved_paths = FxHashMap::default();
|
2020-07-21 23:01:21 -05:00
|
|
|
self.resolve(pattern.clone(), 0, &mut resolved_paths)?;
|
2020-07-24 05:53:48 -05:00
|
|
|
let ufcs_function_calls = resolved_paths
|
|
|
|
.iter()
|
|
|
|
.filter_map(|(path_node, resolved)| {
|
|
|
|
if let Some(grandparent) = path_node.parent().and_then(|parent| parent.parent()) {
|
2020-08-05 04:48:52 -05:00
|
|
|
if let Some(call_expr) = ast::CallExpr::cast(grandparent.clone()) {
|
2022-04-01 11:32:05 -05:00
|
|
|
if let hir::PathResolution::Def(hir::ModuleDef::Function(function)) =
|
2020-08-05 04:48:52 -05:00
|
|
|
resolved.resolution
|
2020-07-24 05:53:48 -05:00
|
|
|
{
|
2022-04-01 11:32:05 -05:00
|
|
|
if function.as_assoc_item(self.resolution_scope.scope.db).is_some() {
|
|
|
|
let qualifier_type =
|
|
|
|
self.resolution_scope.qualifier_type(path_node);
|
|
|
|
return Some((
|
|
|
|
grandparent,
|
|
|
|
UfcsCallInfo { call_expr, function, qualifier_type },
|
|
|
|
));
|
|
|
|
}
|
2020-07-24 05:53:48 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None
|
|
|
|
})
|
|
|
|
.collect();
|
2020-08-01 02:41:42 -05:00
|
|
|
let contains_self =
|
|
|
|
pattern.descendants_with_tokens().any(|node_or_token| match node_or_token {
|
|
|
|
SyntaxElement::Token(t) => t.kind() == T![self],
|
|
|
|
_ => false,
|
|
|
|
});
|
2020-07-22 01:46:29 -05:00
|
|
|
Ok(ResolvedPattern {
|
|
|
|
node: pattern,
|
|
|
|
resolved_paths,
|
|
|
|
placeholders_by_stand_in: self.placeholders_by_stand_in.clone(),
|
2020-07-24 05:53:48 -05:00
|
|
|
ufcs_function_calls,
|
2020-08-01 02:41:42 -05:00
|
|
|
contains_self,
|
2020-07-22 01:46:29 -05:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
fn resolve(
|
|
|
|
&self,
|
|
|
|
node: SyntaxNode,
|
2020-07-21 23:01:21 -05:00
|
|
|
depth: u32,
|
2020-07-22 01:46:29 -05:00
|
|
|
resolved_paths: &mut FxHashMap<SyntaxNode, ResolvedPath>,
|
|
|
|
) -> Result<(), SsrError> {
|
2020-08-12 11:26:51 -05:00
|
|
|
use syntax::ast::AstNode;
|
2020-07-22 01:46:29 -05:00
|
|
|
if let Some(path) = ast::Path::cast(node.clone()) {
|
2020-08-01 02:41:42 -05:00
|
|
|
if is_self(&path) {
|
|
|
|
// Self cannot be resolved like other paths.
|
|
|
|
return Ok(());
|
|
|
|
}
|
2020-07-22 01:46:29 -05:00
|
|
|
// Check if this is an appropriate place in the path to resolve. If the path is
|
|
|
|
// something like `a::B::<i32>::c` then we want to resolve `a::B`. If the path contains
|
|
|
|
// a placeholder. e.g. `a::$b::c` then we want to resolve `a`.
|
|
|
|
if !path_contains_type_arguments(path.qualifier())
|
|
|
|
&& !self.path_contains_placeholder(&path)
|
|
|
|
{
|
|
|
|
let resolution = self
|
2020-07-25 23:49:18 -05:00
|
|
|
.resolution_scope
|
2020-07-22 01:46:29 -05:00
|
|
|
.resolve_path(&path)
|
|
|
|
.ok_or_else(|| error!("Failed to resolve path `{}`", node.text()))?;
|
2020-07-31 21:43:10 -05:00
|
|
|
if self.ok_to_use_path_resolution(&resolution) {
|
|
|
|
resolved_paths.insert(node, ResolvedPath { resolution, depth });
|
|
|
|
return Ok(());
|
|
|
|
}
|
2020-07-22 01:46:29 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
for node in node.children() {
|
2020-07-21 23:01:21 -05:00
|
|
|
self.resolve(node, depth + 1, resolved_paths)?;
|
2020-07-22 01:46:29 -05:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns whether `path` contains a placeholder, but ignores any placeholders within type
|
|
|
|
/// arguments.
|
|
|
|
fn path_contains_placeholder(&self, path: &ast::Path) -> bool {
|
|
|
|
if let Some(segment) = path.segment() {
|
|
|
|
if let Some(name_ref) = segment.name_ref() {
|
2021-03-26 12:30:59 -05:00
|
|
|
if self.placeholders_by_stand_in.contains_key(name_ref.text().as_str()) {
|
2020-07-22 01:46:29 -05:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if let Some(qualifier) = path.qualifier() {
|
|
|
|
return self.path_contains_placeholder(&qualifier);
|
|
|
|
}
|
|
|
|
false
|
|
|
|
}
|
2020-07-31 21:43:10 -05:00
|
|
|
|
|
|
|
fn ok_to_use_path_resolution(&self, resolution: &hir::PathResolution) -> bool {
|
|
|
|
match resolution {
|
2022-04-01 11:32:05 -05:00
|
|
|
hir::PathResolution::Def(hir::ModuleDef::Function(function))
|
|
|
|
if function.as_assoc_item(self.resolution_scope.scope.db).is_some() =>
|
|
|
|
{
|
2020-08-19 08:16:24 -05:00
|
|
|
if function.self_param(self.resolution_scope.scope.db).is_some() {
|
2020-07-31 21:43:10 -05:00
|
|
|
// If we don't use this path resolution, then we won't be able to match method
|
|
|
|
// calls. e.g. `Foo::bar($s)` should match `x.bar()`.
|
|
|
|
true
|
|
|
|
} else {
|
2021-03-08 14:19:44 -06:00
|
|
|
cov_mark::hit!(replace_associated_trait_default_function_call);
|
2020-07-31 21:43:10 -05:00
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
2022-04-01 11:32:05 -05:00
|
|
|
hir::PathResolution::Def(
|
|
|
|
def @ (hir::ModuleDef::Const(_) | hir::ModuleDef::TypeAlias(_)),
|
|
|
|
) if def.as_assoc_item(self.resolution_scope.scope.db).is_some() => {
|
2020-07-31 21:43:10 -05:00
|
|
|
// Not a function. Could be a constant or an associated type.
|
2021-03-08 14:19:44 -06:00
|
|
|
cov_mark::hit!(replace_associated_trait_constant);
|
2020-07-31 21:43:10 -05:00
|
|
|
false
|
|
|
|
}
|
|
|
|
_ => true,
|
|
|
|
}
|
|
|
|
}
|
2020-07-25 23:49:18 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'db> ResolutionScope<'db> {
|
|
|
|
pub(crate) fn new(
|
2020-08-13 09:39:16 -05:00
|
|
|
sema: &hir::Semantics<'db, ide_db::RootDatabase>,
|
2020-07-29 04:20:40 -05:00
|
|
|
resolve_context: FilePosition,
|
2022-03-31 04:12:08 -05:00
|
|
|
) -> Option<ResolutionScope<'db>> {
|
2020-08-12 11:26:51 -05:00
|
|
|
use syntax::ast::AstNode;
|
2020-07-29 04:20:40 -05:00
|
|
|
let file = sema.parse(resolve_context.file_id);
|
2020-07-25 23:49:18 -05:00
|
|
|
// Find a node at the requested position, falling back to the whole file.
|
|
|
|
let node = file
|
|
|
|
.syntax()
|
2020-07-29 04:20:40 -05:00
|
|
|
.token_at_offset(resolve_context.offset)
|
2020-07-25 23:49:18 -05:00
|
|
|
.left_biased()
|
2021-01-30 09:19:21 -06:00
|
|
|
.and_then(|token| token.parent())
|
2020-07-25 23:49:18 -05:00
|
|
|
.unwrap_or_else(|| file.syntax().clone());
|
2020-07-26 02:43:47 -05:00
|
|
|
let node = pick_node_for_resolution(node);
|
2022-03-31 04:12:08 -05:00
|
|
|
let scope = sema.scope(&node)?;
|
|
|
|
Some(ResolutionScope { scope, node })
|
2020-08-01 02:41:42 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the function in which SSR was invoked, if any.
|
|
|
|
pub(crate) fn current_function(&self) -> Option<SyntaxNode> {
|
2020-09-30 14:22:49 -05:00
|
|
|
self.node.ancestors().find(|node| node.kind() == SyntaxKind::FN)
|
2020-07-25 23:49:18 -05:00
|
|
|
}
|
2020-07-22 01:46:29 -05:00
|
|
|
|
|
|
|
fn resolve_path(&self, path: &ast::Path) -> Option<hir::PathResolution> {
|
|
|
|
// First try resolving the whole path. This will work for things like
|
|
|
|
// `std::collections::HashMap`, but will fail for things like
|
|
|
|
// `std::collections::HashMap::new`.
|
2021-06-12 22:54:16 -05:00
|
|
|
if let Some(resolution) = self.scope.speculative_resolve(path) {
|
2020-07-22 01:46:29 -05:00
|
|
|
return Some(resolution);
|
|
|
|
}
|
|
|
|
// Resolution failed, try resolving the qualifier (e.g. `std::collections::HashMap` and if
|
|
|
|
// that succeeds, then iterate through the candidates on the resolved type with the provided
|
|
|
|
// name.
|
2020-08-14 08:23:27 -05:00
|
|
|
let resolved_qualifier = self.scope.speculative_resolve(&path.qualifier()?)?;
|
2020-07-22 01:46:29 -05:00
|
|
|
if let hir::PathResolution::Def(hir::ModuleDef::Adt(adt)) = resolved_qualifier {
|
2020-08-13 16:52:14 -05:00
|
|
|
let name = path.segment()?.name_ref()?;
|
2022-03-31 04:12:08 -05:00
|
|
|
let module = self.scope.module();
|
2020-07-22 01:46:29 -05:00
|
|
|
adt.ty(self.scope.db).iterate_path_candidates(
|
|
|
|
self.scope.db,
|
Refactor autoderef and method resolution
- don't return the receiver type from method resolution; instead just
return the autorefs/autoderefs that happened and repeat them. This
ensures all the effects like trait obligations and whatever we learned
about type variables from derefing them are actually applied. Also, it
allows us to get rid of `decanonicalize_ty`, which was just wrong in
principle.
- Autoderef itself now directly works with an inference table. Sadly
this has the effect of making it harder to use as an iterator, often
requiring manual `while let` loops. (rustc works around this by using
inner mutability in the inference context, so that things like unifying
types don't require a unique reference.)
- We now record the adjustments (autoref/deref) for method receivers
and index expressions, which we didn't before.
- Removed the redundant crate parameter from method resolution, since
the trait_env contains the crate as well.
- in the HIR API, the methods now take a scope to determine the trait env.
`Type` carries a trait env, but I think that's probably a bad decision
because it's easy to create it with the wrong env, e.g. by using
`Adt::ty`. This mostly didn't matter so far because
`iterate_method_candidates` took a crate parameter and ignored
`self.krate`, but the trait env would still have been wrong in those
cases, which I think would give some wrong results in some edge cases.
Fixes #10058.
2022-02-16 10:44:03 -06:00
|
|
|
&self.scope,
|
2022-05-05 15:21:42 -05:00
|
|
|
&self.scope.visible_traits().0,
|
2022-02-01 16:29:40 -06:00
|
|
|
Some(module),
|
2020-08-13 16:52:14 -05:00
|
|
|
None,
|
Refactor autoderef and method resolution
- don't return the receiver type from method resolution; instead just
return the autorefs/autoderefs that happened and repeat them. This
ensures all the effects like trait obligations and whatever we learned
about type variables from derefing them are actually applied. Also, it
allows us to get rid of `decanonicalize_ty`, which was just wrong in
principle.
- Autoderef itself now directly works with an inference table. Sadly
this has the effect of making it harder to use as an iterator, often
requiring manual `while let` loops. (rustc works around this by using
inner mutability in the inference context, so that things like unifying
types don't require a unique reference.)
- We now record the adjustments (autoref/deref) for method receivers
and index expressions, which we didn't before.
- Removed the redundant crate parameter from method resolution, since
the trait_env contains the crate as well.
- in the HIR API, the methods now take a scope to determine the trait env.
`Type` carries a trait env, but I think that's probably a bad decision
because it's easy to create it with the wrong env, e.g. by using
`Adt::ty`. This mostly didn't matter so far because
`iterate_method_candidates` took a crate parameter and ignored
`self.krate`, but the trait env would still have been wrong in those
cases, which I think would give some wrong results in some edge cases.
Fixes #10058.
2022-02-16 10:44:03 -06:00
|
|
|
|assoc_item| {
|
2020-08-13 16:52:14 -05:00
|
|
|
let item_name = assoc_item.name(self.scope.db)?;
|
2021-11-04 12:12:05 -05:00
|
|
|
if item_name.to_smol_str().as_str() == name.text() {
|
2022-04-01 11:32:05 -05:00
|
|
|
Some(hir::PathResolution::Def(assoc_item.into()))
|
2020-08-13 16:52:14 -05:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
},
|
2020-07-22 01:46:29 -05:00
|
|
|
)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
2020-08-05 04:48:52 -05:00
|
|
|
|
|
|
|
fn qualifier_type(&self, path: &SyntaxNode) -> Option<hir::Type> {
|
|
|
|
use syntax::ast::AstNode;
|
|
|
|
if let Some(path) = ast::Path::cast(path.clone()) {
|
|
|
|
if let Some(qualifier) = path.qualifier() {
|
2021-09-19 16:34:07 -05:00
|
|
|
if let Some(hir::PathResolution::Def(hir::ModuleDef::Adt(adt))) =
|
|
|
|
self.resolve_path(&qualifier)
|
|
|
|
{
|
|
|
|
return Some(adt.ty(self.scope.db));
|
2020-08-05 04:48:52 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None
|
|
|
|
}
|
2020-07-22 01:46:29 -05:00
|
|
|
}
|
|
|
|
|
2020-08-01 02:41:42 -05:00
|
|
|
fn is_self(path: &ast::Path) -> bool {
|
|
|
|
path.segment().map(|segment| segment.self_token().is_some()).unwrap_or(false)
|
|
|
|
}
|
|
|
|
|
2020-07-26 02:43:47 -05:00
|
|
|
/// Returns a suitable node for resolving paths in the current scope. If we create a scope based on
|
|
|
|
/// a statement node, then we can't resolve local variables that were defined in the current scope
|
|
|
|
/// (only in parent scopes). So we find another node, ideally a child of the statement where local
|
|
|
|
/// variable resolution is permitted.
|
|
|
|
fn pick_node_for_resolution(node: SyntaxNode) -> SyntaxNode {
|
|
|
|
match node.kind() {
|
|
|
|
SyntaxKind::EXPR_STMT => {
|
|
|
|
if let Some(n) = node.first_child() {
|
2021-03-08 14:19:44 -06:00
|
|
|
cov_mark::hit!(cursor_after_semicolon);
|
2020-07-26 02:43:47 -05:00
|
|
|
return n;
|
|
|
|
}
|
|
|
|
}
|
2020-07-31 13:09:09 -05:00
|
|
|
SyntaxKind::LET_STMT | SyntaxKind::IDENT_PAT => {
|
2020-07-26 02:43:47 -05:00
|
|
|
if let Some(next) = node.next_sibling() {
|
|
|
|
return pick_node_for_resolution(next);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
SyntaxKind::NAME => {
|
|
|
|
if let Some(parent) = node.parent() {
|
|
|
|
return pick_node_for_resolution(parent);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
node
|
|
|
|
}
|
|
|
|
|
2020-07-22 01:46:29 -05:00
|
|
|
/// Returns whether `path` or any of its qualifiers contains type arguments.
|
|
|
|
fn path_contains_type_arguments(path: Option<ast::Path>) -> bool {
|
|
|
|
if let Some(path) = path {
|
|
|
|
if let Some(segment) = path.segment() {
|
2020-07-31 11:29:29 -05:00
|
|
|
if segment.generic_arg_list().is_some() {
|
2021-03-08 14:19:44 -06:00
|
|
|
cov_mark::hit!(type_arguments_within_path);
|
2020-07-22 01:46:29 -05:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return path_contains_type_arguments(path.qualifier());
|
|
|
|
}
|
|
|
|
false
|
|
|
|
}
|