Skip to content

resolve: Split extern prelude into two scopes #144793

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 3 commits into from
Aug 14, 2025
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
4 changes: 3 additions & 1 deletion compiler/rustc_error_codes/src/error_codes/E0578.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#### Note: this error code is no longer emitted by the compiler.

A module cannot be found and therefore, the visibility cannot be determined.

Erroneous code example:

```compile_fail,E0578,edition2018
```ignore (no longer emitted)
foo!();

pub (in ::Sea) struct Shark; // error!
Expand Down
21 changes: 8 additions & 13 deletions compiler/rustc_resolve/src/build_reduced_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -971,40 +971,35 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> {
let imported_binding = self.r.import(binding, import);
if ident.name != kw::Underscore && parent == self.r.graph_root {
let norm_ident = Macros20NormalizedIdent::new(ident);
// FIXME: this error is technically unnecessary now when extern prelude is split into
// two scopes, remove it with lang team approval.
if let Some(entry) = self.r.extern_prelude.get(&norm_ident)
&& expansion != LocalExpnId::ROOT
&& orig_name.is_some()
&& !entry.is_import()
&& entry.item_binding.is_none()
{
self.r.dcx().emit_err(
errors::MacroExpandedExternCrateCannotShadowExternArguments { span: item.span },
);
// `return` is intended to discard this binding because it's an
// unregistered ambiguity error which would result in a panic
// caused by inconsistency `path_res`
// more details: https://github.com/rust-lang/rust/pull/111761
return;
}

use indexmap::map::Entry;
match self.r.extern_prelude.entry(norm_ident) {
Entry::Occupied(mut occupied) => {
let entry = occupied.get_mut();
if let Some(old_binding) = entry.binding.get()
&& old_binding.is_import()
{
if entry.item_binding.is_some() {
let msg = format!("extern crate `{ident}` already in extern prelude");
self.r.tcx.dcx().span_delayed_bug(item.span, msg);
} else {
// Binding from `extern crate` item in source code can replace
// a binding from `--extern` on command line here.
entry.binding.set(Some(imported_binding));
entry.item_binding = Some(imported_binding);
entry.introduced_by_item = orig_name.is_some();
}
entry
}
Entry::Vacant(vacant) => vacant.insert(ExternPreludeEntry {
binding: Cell::new(Some(imported_binding)),
item_binding: Some(imported_binding),
flag_binding: Cell::new(None),
only_item: true,
introduced_by_item: true,
}),
};
Expand Down
15 changes: 12 additions & 3 deletions compiler/rustc_resolve/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1096,12 +1096,14 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
);
}
}
Scope::ExternPrelude => {
Scope::ExternPreludeItems => {
// Add idents from both item and flag scopes.
suggestions.extend(this.extern_prelude.keys().filter_map(|ident| {
let res = Res::Def(DefKind::Mod, CRATE_DEF_ID.to_def_id());
filter_fn(res).then_some(TypoSuggestion::typo_from_ident(ident.0, res))
}));
}
Scope::ExternPreludeFlags => {}
Scope::ToolPrelude => {
let res = Res::NonMacroAttr(NonMacroAttrKind::Tool);
suggestions.extend(
Expand Down Expand Up @@ -1862,14 +1864,20 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
}
}

fn ambiguity_diagnostics(&self, ambiguity_error: &AmbiguityError<'_>) -> AmbiguityErrorDiag {
fn ambiguity_diagnostics(&self, ambiguity_error: &AmbiguityError<'ra>) -> AmbiguityErrorDiag {
let AmbiguityError { kind, ident, b1, b2, misc1, misc2, .. } = *ambiguity_error;
let extern_prelude_ambiguity = || {
self.extern_prelude.get(&Macros20NormalizedIdent::new(ident)).is_some_and(|entry| {
entry.item_binding == Some(b1) && entry.flag_binding.get() == Some(b2)
})
};
let (b1, b2, misc1, misc2, swapped) = if b2.span.is_dummy() && !b1.span.is_dummy() {
// We have to print the span-less alternative first, otherwise formatting looks bad.
(b2, b1, misc2, misc1, true)
} else {
(b1, b2, misc1, misc2, false)
};

let could_refer_to = |b: NameBinding<'_>, misc: AmbiguityErrorMisc, also: &str| {
let what = self.binding_description(b, ident, misc == AmbiguityErrorMisc::FromPrelude);
let note_msg = format!("`{ident}` could{also} refer to {what}");
Expand All @@ -1885,7 +1893,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
"consider adding an explicit import of `{ident}` to disambiguate"
))
}
if b.is_extern_crate() && ident.span.at_least_rust_2018() {
if b.is_extern_crate() && ident.span.at_least_rust_2018() && !extern_prelude_ambiguity()
{
help_msgs.push(format!("use `::{ident}` to refer to this {thing} unambiguously"))
}
match misc {
Expand Down
61 changes: 40 additions & 21 deletions compiler/rustc_resolve/src/ident.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
ScopeSet::All(ns)
| ScopeSet::ModuleAndExternPrelude(ns, _)
| ScopeSet::Late(ns, ..) => (ns, None),
ScopeSet::ExternPrelude => (TypeNS, None),
ScopeSet::Macro(macro_kind) => (MacroNS, Some(macro_kind)),
};
let module = match scope_set {
Expand All @@ -111,8 +112,10 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
_ => parent_scope.module.nearest_item_scope(),
};
let module_and_extern_prelude = matches!(scope_set, ScopeSet::ModuleAndExternPrelude(..));
let extern_prelude = matches!(scope_set, ScopeSet::ExternPrelude);
let mut scope = match ns {
_ if module_and_extern_prelude => Scope::Module(module, None),
_ if extern_prelude => Scope::ExternPreludeItems,
TypeNS | ValueNS => Scope::Module(module, None),
MacroNS => Scope::DeriveHelpers(parent_scope.expansion),
};
Expand Down Expand Up @@ -143,7 +146,9 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
Scope::Module(..) => true,
Scope::MacroUsePrelude => use_prelude || rust_2015,
Scope::BuiltinAttrs => true,
Scope::ExternPrelude => use_prelude || module_and_extern_prelude,
Scope::ExternPreludeItems | Scope::ExternPreludeFlags => {
use_prelude || module_and_extern_prelude || extern_prelude
}
Scope::ToolPrelude => use_prelude,
Scope::StdLibPrelude => use_prelude || ns == MacroNS,
Scope::BuiltinTypes => true,
Expand Down Expand Up @@ -182,7 +187,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
Scope::Module(..) if module_and_extern_prelude => match ns {
TypeNS => {
ctxt.adjust(ExpnId::root());
Scope::ExternPrelude
Scope::ExternPreludeItems
}
ValueNS | MacroNS => break,
},
Expand All @@ -199,7 +204,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
None => {
ctxt.adjust(ExpnId::root());
match ns {
TypeNS => Scope::ExternPrelude,
TypeNS => Scope::ExternPreludeItems,
ValueNS => Scope::StdLibPrelude,
MacroNS => Scope::MacroUsePrelude,
}
Expand All @@ -208,8 +213,9 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
}
Scope::MacroUsePrelude => Scope::StdLibPrelude,
Scope::BuiltinAttrs => break, // nowhere else to search
Scope::ExternPrelude if module_and_extern_prelude => break,
Scope::ExternPrelude => Scope::ToolPrelude,
Scope::ExternPreludeItems => Scope::ExternPreludeFlags,
Scope::ExternPreludeFlags if module_and_extern_prelude || extern_prelude => break,
Scope::ExternPreludeFlags => Scope::ToolPrelude,
Scope::ToolPrelude => Scope::StdLibPrelude,
Scope::StdLibPrelude => match ns {
TypeNS => Scope::BuiltinTypes,
Expand Down Expand Up @@ -413,6 +419,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
ScopeSet::All(ns)
| ScopeSet::ModuleAndExternPrelude(ns, _)
| ScopeSet::Late(ns, ..) => (ns, None),
ScopeSet::ExternPrelude => (TypeNS, None),
ScopeSet::Macro(macro_kind) => (MacroNS, Some(macro_kind)),
};

Expand All @@ -429,6 +436,10 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
// to detect potential ambiguities.
let mut innermost_result: Option<(NameBinding<'_>, Flags)> = None;
let mut determinacy = Determinacy::Determined;
// Shadowed bindings don't need to be marked as used or non-speculatively loaded.
macro finalize_scope() {
if innermost_result.is_none() { finalize } else { None }
}

// Go through all the scopes and try to resolve the name.
let break_result = self.visit_scopes(
Expand Down Expand Up @@ -494,7 +505,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
_ => Err(Determinacy::Determined),
},
Scope::Module(module, derive_fallback_lint_id) => {
let (adjusted_parent_scope, finalize) =
// FIXME: use `finalize_scope` here.
let (adjusted_parent_scope, adjusted_finalize) =
if matches!(scope_set, ScopeSet::ModuleAndExternPrelude(..)) {
(parent_scope, finalize)
} else {
Expand All @@ -513,7 +525,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
} else {
Shadowing::Restricted
},
finalize,
adjusted_finalize,
ignore_binding,
ignore_import,
);
Expand Down Expand Up @@ -561,14 +573,21 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
Some(binding) => Ok((*binding, Flags::empty())),
None => Err(Determinacy::Determined),
},
Scope::ExternPrelude => {
match this.reborrow().extern_prelude_get(ident, finalize.is_some()) {
Scope::ExternPreludeItems => {
// FIXME: use `finalize_scope` here.
match this.reborrow().extern_prelude_get_item(ident, finalize.is_some()) {
Some(binding) => Ok((binding, Flags::empty())),
None => Err(Determinacy::determined(
this.graph_root.unexpanded_invocations.borrow().is_empty(),
)),
}
}
Scope::ExternPreludeFlags => {
match this.extern_prelude_get_flag(ident, finalize_scope!().is_some()) {
Some(binding) => Ok((binding, Flags::empty())),
None => Err(Determinacy::Determined),
}
}
Scope::ToolPrelude => match this.registered_tool_bindings.get(&ident) {
Some(binding) => Ok((*binding, Flags::empty())),
None => Err(Determinacy::Determined),
Expand Down Expand Up @@ -599,8 +618,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
if matches!(ident.name, sym::f16)
&& !this.tcx.features().f16()
&& !ident.span.allows_unstable(sym::f16)
&& finalize.is_some()
&& innermost_result.is_none()
&& finalize_scope!().is_some()
{
feature_err(
this.tcx.sess,
Expand All @@ -613,8 +631,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
if matches!(ident.name, sym::f128)
&& !this.tcx.features().f128()
&& !ident.span.allows_unstable(sym::f128)
&& finalize.is_some()
&& innermost_result.is_none()
&& finalize_scope!().is_some()
{
feature_err(
this.tcx.sess,
Expand Down Expand Up @@ -829,15 +846,17 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
assert_eq!(shadowing, Shadowing::Unrestricted);
return if ns != TypeNS {
Err((Determined, Weak::No))
} else if let Some(binding) =
self.reborrow().extern_prelude_get(ident, finalize.is_some())
{
Ok(binding)
} else if !self.graph_root.unexpanded_invocations.borrow().is_empty() {
// Macro-expanded `extern crate` items can add names to extern prelude.
Err((Undetermined, Weak::No))
} else {
Err((Determined, Weak::No))
let binding = self.early_resolve_ident_in_lexical_scope(
ident,
ScopeSet::ExternPrelude,
parent_scope,
finalize,
finalize.is_some(),
ignore_binding,
ignore_import,
);
return binding.map_err(|determinacy| (determinacy, Weak::No));
};
}
ModuleOrUniformRoot::CurrentScope => {
Expand Down
Loading
Loading