1
Fork 0

Keep track of parse errors in mods 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:
Esteban Küber 2024-12-05 21:19:08 +00:00
parent 3f52583c6a
commit 69fb612608
26 changed files with 128 additions and 93 deletions

View file

@ -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;
}

View file

@ -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);

View file

@ -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,
)
},
);
}
}
}

View file

@ -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());

View file

@ -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),

View file

@ -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);

View file

@ -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;
}
}