Skip to content

Commit

Permalink
Change early name path analysis to check paths in expr, pat and stmt
Browse files Browse the repository at this point in the history
  • Loading branch information
Y-Nak committed Feb 2, 2024
1 parent b801d89 commit 070ab4c
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 30 deletions.
19 changes: 11 additions & 8 deletions crates/hir-analysis/src/name_resolution/import_resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -574,20 +574,23 @@ impl<'db> ImportResolver<'db> {
return Err(NameResolutionError::Invalid);
};

let mut directive = QueryDirective::new();
let Some(current_scope) = i_use.current_scope() else {
return Err(NameResolutionError::NotFound);
};

// In the middle of the use path, disallow lexically scoped names and
// external names.
if !i_use.is_first_segment() {
directive.disallow_lex().disallow_external();
}

if self.contains_unresolved_named_use(seg_name, current_scope, i_use.is_first_segment()) {
directive.disallow_glob().disallow_external();
}
let directive = if !i_use.is_first_segment() {
QueryDirective::new().disallow_lex().disallow_external()
} else if self.contains_unresolved_named_use(
seg_name,
current_scope,
i_use.is_first_segment(),
) {
QueryDirective::new().disallow_glob().disallow_external()
} else {
QueryDirective::new()
};

Ok(NameQuery::with_directive(
seg_name,
Expand Down
74 changes: 56 additions & 18 deletions crates/hir-analysis/src/name_resolution/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,24 @@ use hir::{
};
pub use import_resolver::ResolvedImports;
pub use name_resolver::{
NameDerivation, NameDomain, NameQuery, NameRes, NameResBucket, NameResKind, QueryDirective,
NameDerivation, NameDomain, NameQuery, NameRes, NameResBucket, NameResKind,
NameResolutionError, QueryDirective,
};
pub use path_resolver::EarlyResolvedPath;
use rustc_hash::FxHashSet;

use self::{
diagnostics::{ImportResolutionDiagAccumulator, NameResDiag, NameResolutionDiagAccumulator},
import_resolver::DefaultImporter,
name_resolver::{NameResolutionError, ResolvedQueryCacheStore},
name_resolver::ResolvedQueryCacheStore,
path_resolver::EarlyPathResolver,
};
use crate::HirAnalysisDb;

// TODO: Implement `resolve_path` and `resolve_segments` after implementing the
// late path resolution.

/// Resolves the given path in the given scope.
// Resolves the given path in the given scope.
/// It's not necessary to report any error even if the `EarlyResolvedPath`
/// contains some errors; it's always reported from [`PathAnalysisPass`].
pub fn resolve_path_early(
Expand Down Expand Up @@ -71,6 +72,14 @@ pub fn resolve_segments_early(
}
}

/// Resolves the given query. If you don't need to resolve customized queries,
/// consider using [`resolve_path_early`] or [`resolve_segments_early`] instead.
pub fn resolve_query(db: &dyn HirAnalysisDb, query: NameQuery) -> NameResBucket {
let importer = DefaultImporter;
let mut name_resolver = name_resolver::NameResolver::new_no_cache(db, &importer);
name_resolver.resolve_query(query)
}

/// Performs import resolution analysis. This pass only checks correctness of
/// the imports and doesn't emit other name resolutions errors.
pub struct ImportAnalysisPass<'db> {
Expand Down Expand Up @@ -236,8 +245,14 @@ impl<'db, 'a> EarlyPathVisitor<'db, 'a> {

match err {
NameResolutionError::NotFound => {
self.diags
.push(NameResDiag::not_found(span, last_seg_ident));
if !matches!(
self.path_ctxt.last().unwrap(),
ExpectedPathKind::Expr | ExpectedPathKind::Pat
) || path.len(self.db.as_hir_db()) != 1
{
self.diags
.push(NameResDiag::not_found(span, last_seg_ident));
}
}
NameResolutionError::Ambiguous(cands) => {
self.diags.push(NameResDiag::ambiguous(
Expand Down Expand Up @@ -281,6 +296,8 @@ impl<'db, 'a> EarlyPathVisitor<'db, 'a> {
self.diags
.push(NameResDiag::ExpectedValue(span, last_seg_ident, res));
}

_ => {}
},
}
}
Expand Down Expand Up @@ -322,8 +339,10 @@ impl<'db, 'a> EarlyPathVisitor<'db, 'a> {

fn make_query_for_conflict_check(&self, scope: ScopeId) -> Option<NameQuery> {
let name = scope.name(self.db.as_hir_db())?;
let mut directive = QueryDirective::new();
directive.disallow_lex().disallow_glob().disallow_external();
let directive = QueryDirective::new()
.disallow_lex()
.disallow_glob()
.disallow_external();

let parent_scope = scope.parent(self.db.as_hir_db())?;
Some(NameQuery::with_directive(name, parent_scope, directive))
Expand Down Expand Up @@ -417,17 +436,21 @@ impl<'db, 'a> Visitor for EarlyPathVisitor<'db, 'a> {

// We don't need to run path analysis on patterns, statements and expressions in
// early path resolution.
fn visit_pat(&mut self, _: &mut VisitorCtxt<'_, LazyPatSpan>, _: PatId, _: &Pat) {}

fn visit_expr(
&mut self,
ctxt: &mut VisitorCtxt<'_, LazyExprSpan>,
expr: ExprId,
expr_data: &Expr,
) {
if matches!(expr_data, Expr::Block(_)) {
walk_expr(self, ctxt, expr)
fn visit_pat(&mut self, ctxt: &mut VisitorCtxt<'_, LazyPatSpan>, pat: PatId, pat_data: &Pat) {
match pat_data {
Pat::PathTuple { .. } | Pat::Record { .. } => {
self.path_ctxt.push(ExpectedPathKind::PatWithArg)
}
_ => self.path_ctxt.push(ExpectedPathKind::Pat),
}
walk_pat(self, ctxt, pat);
self.path_ctxt.pop();
}

fn visit_expr(&mut self, ctxt: &mut VisitorCtxt<'_, LazyExprSpan>, expr: ExprId, _: &Expr) {
self.path_ctxt.push(ExpectedPathKind::Expr);
walk_expr(self, ctxt, expr);
self.path_ctxt.pop();
}

fn visit_path(&mut self, ctxt: &mut VisitorCtxt<'_, LazyPathSpan>, path: PathId) {
Expand All @@ -445,7 +468,16 @@ impl<'db, 'a> Visitor for EarlyPathVisitor<'db, 'a> {

let diag = match err.kind {
NameResolutionError::NotFound => {
NameResDiag::not_found(span.into(), *ident.unwrap())
if path.len(self.db.as_hir_db()) == 1
&& matches!(
self.path_ctxt.last().unwrap(),
ExpectedPathKind::Expr | ExpectedPathKind::Pat
)
{
return;
} else {
NameResDiag::not_found(span.into(), *ident.unwrap())
}
}

NameResolutionError::Invalid => {
Expand Down Expand Up @@ -499,6 +531,9 @@ enum ExpectedPathKind {
Type,
Trait,
Value,
PatWithArg,
Pat,
Expr,
}

impl ExpectedPathKind {
Expand All @@ -507,6 +542,9 @@ impl ExpectedPathKind {
ExpectedPathKind::Type => NameDomain::Type,
ExpectedPathKind::Trait => NameDomain::Type,
ExpectedPathKind::Value => NameDomain::Value,
ExpectedPathKind::Pat | ExpectedPathKind::PatWithArg | ExpectedPathKind::Expr => {
NameDomain::Value | NameDomain::Type
}
}
}

Expand Down
8 changes: 4 additions & 4 deletions crates/hir-analysis/src/name_resolution/name_resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,17 +87,17 @@ impl QueryDirective {
}

/// Disallow lexical scope lookup.
pub fn disallow_lex(&mut self) -> &mut Self {
pub fn disallow_lex(mut self) -> Self {
self.allow_lex = false;
self
}

pub(super) fn disallow_external(&mut self) -> &mut Self {
pub(super) fn disallow_external(mut self) -> Self {
self.allow_external = false;
self
}

pub(super) fn disallow_glob(&mut self) -> &mut Self {
pub(super) fn disallow_glob(mut self) -> Self {
self.allow_glob = false;
self
}
Expand Down Expand Up @@ -770,7 +770,7 @@ pub enum NameResolutionError {
/// The name is found, but it's ambiguous.
Ambiguous(Vec<NameRes>),

/// The name is found ,but it can't be used in the middle of a use path.
/// The name is found, but it can't be used in the middle of a use path.
InvalidPathSegment(NameRes),

/// The definition conflicts with other definitions.
Expand Down

0 comments on commit 070ab4c

Please sign in to comment.