Keep track of parse errors in mod
s and don't emit resolve errors for paths involving them
When we expand a `mod foo;` and parse `foo.rs`, we now track whether that file had an unrecovered parse error that reached the end of the file. If so, we keep that information around. When resolving a path like `foo::bar`, we do not emit any errors for "`bar` not found in `foo`", as we know that the parse error might have caused `bar` to not be parsed and accounted for. When this happens in an existing project, every path referencing `foo` would be an irrelevant compile error. Instead, we now skip emitting anything until `foo.rs` is fixed. Tellingly enough, we didn't have any test for errors caused by `mod` expansion. Fix #97734.
This commit is contained in:
parent
3f52583c6a
commit
69fb612608
26 changed files with 128 additions and 93 deletions
|
@ -770,7 +770,7 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> {
|
|||
);
|
||||
}
|
||||
|
||||
ItemKind::Mod(..) => {
|
||||
ItemKind::Mod(.., ref mod_kind) => {
|
||||
let module = self.r.new_module(
|
||||
Some(parent),
|
||||
ModuleKind::Def(def_kind, def_id, ident.name),
|
||||
|
@ -781,6 +781,10 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> {
|
|||
);
|
||||
self.r.define(parent, ident, TypeNS, (module, vis, sp, expansion));
|
||||
|
||||
if let ast::ModKind::Loaded(_, _, _, Err(_)) = mod_kind {
|
||||
self.r.mods_with_parse_errors.insert(def_id);
|
||||
}
|
||||
|
||||
// Descend into the module.
|
||||
self.parent_scope.module = module;
|
||||
}
|
||||
|
|
|
@ -3056,7 +3056,7 @@ impl<'tcx> visit::Visitor<'tcx> for UsePlacementFinder {
|
|||
|
||||
fn visit_item(&mut self, item: &'tcx ast::Item) {
|
||||
if self.target_module == item.id {
|
||||
if let ItemKind::Mod(_, ModKind::Loaded(items, _inline, mod_spans)) = &item.kind {
|
||||
if let ItemKind::Mod(_, ModKind::Loaded(items, _inline, mod_spans, _)) = &item.kind {
|
||||
let inject = mod_spans.inject_use_span;
|
||||
if is_span_suitable_for_use_injection(inject) {
|
||||
self.first_legal_span = Some(inject);
|
||||
|
|
|
@ -1428,6 +1428,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
|
|||
ignore_import: Option<Import<'ra>>,
|
||||
) -> PathResult<'ra> {
|
||||
let mut module = None;
|
||||
let mut module_had_parse_errors = false;
|
||||
let mut allow_super = true;
|
||||
let mut second_binding = None;
|
||||
|
||||
|
@ -1471,9 +1472,14 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
|
|||
continue;
|
||||
}
|
||||
}
|
||||
return PathResult::failed(ident, false, finalize.is_some(), module, || {
|
||||
("there are too many leading `super` keywords".to_string(), None)
|
||||
});
|
||||
return PathResult::failed(
|
||||
ident,
|
||||
false,
|
||||
finalize.is_some(),
|
||||
module_had_parse_errors,
|
||||
module,
|
||||
|| ("there are too many leading `super` keywords".to_string(), None),
|
||||
);
|
||||
}
|
||||
if segment_idx == 0 {
|
||||
if name == kw::SelfLower {
|
||||
|
@ -1511,19 +1517,26 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
|
|||
|
||||
// Report special messages for path segment keywords in wrong positions.
|
||||
if ident.is_path_segment_keyword() && segment_idx != 0 {
|
||||
return PathResult::failed(ident, false, finalize.is_some(), module, || {
|
||||
let name_str = if name == kw::PathRoot {
|
||||
"crate root".to_string()
|
||||
} else {
|
||||
format!("`{name}`")
|
||||
};
|
||||
let label = if segment_idx == 1 && path[0].ident.name == kw::PathRoot {
|
||||
format!("global paths cannot start with {name_str}")
|
||||
} else {
|
||||
format!("{name_str} in paths can only be used in start position")
|
||||
};
|
||||
(label, None)
|
||||
});
|
||||
return PathResult::failed(
|
||||
ident,
|
||||
false,
|
||||
finalize.is_some(),
|
||||
module_had_parse_errors,
|
||||
module,
|
||||
|| {
|
||||
let name_str = if name == kw::PathRoot {
|
||||
"crate root".to_string()
|
||||
} else {
|
||||
format!("`{name}`")
|
||||
};
|
||||
let label = if segment_idx == 1 && path[0].ident.name == kw::PathRoot {
|
||||
format!("global paths cannot start with {name_str}")
|
||||
} else {
|
||||
format!("{name_str} in paths can only be used in start position")
|
||||
};
|
||||
(label, None)
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let binding = if let Some(module) = module {
|
||||
|
@ -1589,6 +1602,9 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
|
|||
|
||||
let maybe_assoc = opt_ns != Some(MacroNS) && PathSource::Type.is_expected(res);
|
||||
if let Some(next_module) = binding.module() {
|
||||
if self.mods_with_parse_errors.contains(&next_module.def_id()) {
|
||||
module_had_parse_errors = true;
|
||||
}
|
||||
module = Some(ModuleOrUniformRoot::Module(next_module));
|
||||
record_segment_res(self, res);
|
||||
} else if res == Res::ToolMod && !is_last && opt_ns.is_some() {
|
||||
|
@ -1614,6 +1630,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
|
|||
ident,
|
||||
is_last,
|
||||
finalize.is_some(),
|
||||
module_had_parse_errors,
|
||||
module,
|
||||
|| {
|
||||
let label = format!(
|
||||
|
@ -1637,19 +1654,26 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
return PathResult::failed(ident, is_last, finalize.is_some(), module, || {
|
||||
self.report_path_resolution_error(
|
||||
path,
|
||||
opt_ns,
|
||||
parent_scope,
|
||||
ribs,
|
||||
ignore_binding,
|
||||
ignore_import,
|
||||
module,
|
||||
segment_idx,
|
||||
ident,
|
||||
)
|
||||
});
|
||||
return PathResult::failed(
|
||||
ident,
|
||||
is_last,
|
||||
finalize.is_some(),
|
||||
module_had_parse_errors,
|
||||
module,
|
||||
|| {
|
||||
self.report_path_resolution_error(
|
||||
path,
|
||||
opt_ns,
|
||||
parent_scope,
|
||||
ribs,
|
||||
ignore_binding,
|
||||
ignore_import,
|
||||
module,
|
||||
segment_idx,
|
||||
ident,
|
||||
)
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -898,6 +898,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
|
|||
label,
|
||||
suggestion,
|
||||
module,
|
||||
error_implied_by_parse_error: _,
|
||||
} => {
|
||||
if no_ambiguity {
|
||||
assert!(import.imported_module.get().is_none());
|
||||
|
|
|
@ -4395,6 +4395,12 @@ impl<'a, 'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
|
|||
PathResult::Module(ModuleOrUniformRoot::Module(module)) if !module.is_normal() => {
|
||||
PartialRes::new(module.res().unwrap())
|
||||
}
|
||||
// A part of this path references a `mod` that had a parse error. To avoid resolution
|
||||
// errors for each reference to that module, we don't emit an error for them until the
|
||||
// `mod` is fixed. this can have a significant cascade effect.
|
||||
PathResult::Failed { error_implied_by_parse_error: true, .. } => {
|
||||
PartialRes::new(Res::Err)
|
||||
}
|
||||
// In `a(::assoc_item)*` `a` cannot be a module. If `a` does resolve to a module we
|
||||
// don't report an error right away, but try to fallback to a primitive type.
|
||||
// So, we are still able to successfully resolve something like
|
||||
|
@ -4443,6 +4449,7 @@ impl<'a, 'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
|
|||
suggestion,
|
||||
module,
|
||||
segment_name,
|
||||
error_implied_by_parse_error: _,
|
||||
} => {
|
||||
return Err(respan(span, ResolutionError::FailedToResolve {
|
||||
segment: Some(segment_name),
|
||||
|
|
|
@ -450,6 +450,7 @@ enum PathResult<'ra> {
|
|||
module: Option<ModuleOrUniformRoot<'ra>>,
|
||||
/// The segment name of target
|
||||
segment_name: Symbol,
|
||||
error_implied_by_parse_error: bool,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -458,6 +459,7 @@ impl<'ra> PathResult<'ra> {
|
|||
ident: Ident,
|
||||
is_error_from_last_segment: bool,
|
||||
finalize: bool,
|
||||
error_implied_by_parse_error: bool,
|
||||
module: Option<ModuleOrUniformRoot<'ra>>,
|
||||
label_and_suggestion: impl FnOnce() -> (String, Option<Suggestion>),
|
||||
) -> PathResult<'ra> {
|
||||
|
@ -470,6 +472,7 @@ impl<'ra> PathResult<'ra> {
|
|||
suggestion,
|
||||
is_error_from_last_segment,
|
||||
module,
|
||||
error_implied_by_parse_error,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1198,6 +1201,8 @@ pub struct Resolver<'ra, 'tcx> {
|
|||
/// This is the `Span` where an `extern crate foo;` suggestion would be inserted, if `foo`
|
||||
/// could be a crate that wasn't imported. For diagnostics use only.
|
||||
current_crate_outer_attr_insert_span: Span,
|
||||
|
||||
mods_with_parse_errors: FxHashSet<DefId>,
|
||||
}
|
||||
|
||||
/// This provides memory for the rest of the crate. The `'ra` lifetime that is
|
||||
|
@ -1543,6 +1548,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
|
|||
impl_unexpanded_invocations: Default::default(),
|
||||
impl_binding_keys: Default::default(),
|
||||
current_crate_outer_attr_insert_span,
|
||||
mods_with_parse_errors: Default::default(),
|
||||
};
|
||||
|
||||
let root_parent_scope = ParentScope::module(graph_root, &resolver);
|
||||
|
|
|
@ -166,7 +166,7 @@ fn soft_custom_inner_attributes_gate(path: &ast::Path, invoc: &Invocation) -> bo
|
|||
[seg1, seg2] if seg1.ident.name == sym::rustfmt && seg2.ident.name == sym::skip => {
|
||||
if let InvocationKind::Attr { item, .. } = &invoc.kind {
|
||||
if let Annotatable::Item(item) = item {
|
||||
if let ItemKind::Mod(_, ModKind::Loaded(_, Inline::No, _)) = item.kind {
|
||||
if let ItemKind::Mod(_, ModKind::Loaded(_, Inline::No, _, _)) = item.kind {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue