From 97e6819e922f9b4ba40e4e42992192c1d918a29c Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Thu, 27 Nov 2025 18:51:21 +0300 Subject: [PATCH 1/9] resolve: Use `ControlFlow` in `fn visit_scopes` callback --- compiler/rustc_resolve/src/diagnostics.rs | 4 +++- compiler/rustc_resolve/src/ident.rs | 20 ++++++++++++-------- compiler/rustc_resolve/src/lib.rs | 3 ++- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/compiler/rustc_resolve/src/diagnostics.rs b/compiler/rustc_resolve/src/diagnostics.rs index 2f4a18f9cfa6b..fe299a6cebca7 100644 --- a/compiler/rustc_resolve/src/diagnostics.rs +++ b/compiler/rustc_resolve/src/diagnostics.rs @@ -1,3 +1,5 @@ +use std::ops::ControlFlow; + use itertools::Itertools as _; use rustc_ast::visit::{self, Visitor}; use rustc_ast::{ @@ -1261,7 +1263,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } } - None::<()> + ControlFlow::<()>::Continue(()) }); } diff --git a/compiler/rustc_resolve/src/ident.rs b/compiler/rustc_resolve/src/ident.rs index 8ecae07dea67d..ea09200d41600 100644 --- a/compiler/rustc_resolve/src/ident.rs +++ b/compiler/rustc_resolve/src/ident.rs @@ -1,3 +1,5 @@ +use std::ops::ControlFlow; + use Determinacy::*; use Namespace::*; use rustc_ast::{self as ast, NodeId}; @@ -56,7 +58,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { Scope<'ra>, UsePrelude, SyntaxContext, - ) -> Option, + ) -> ControlFlow, ) -> Option { // General principles: // 1. Not controlled (user-defined) names should have higher priority than controlled names @@ -156,8 +158,10 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { if visit { let use_prelude = if use_prelude { UsePrelude::Yes } else { UsePrelude::No }; - if let break_result @ Some(..) = visitor(&mut self, scope, use_prelude, ctxt) { - return break_result; + if let ControlFlow::Break(break_result) = + visitor(&mut self, scope, use_prelude, ctxt) + { + return Some(break_result); } } @@ -536,7 +540,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { Ok((binding, Flags::MODULE | misc_flags)) } Err((Determinacy::Undetermined, Weak::No)) => { - return Some(Err(Determinacy::determined(force))); + return ControlFlow::Break(Err(Determinacy::determined(force))); } Err((Determinacy::Undetermined, Weak::Yes)) => { Err(Determinacy::Undetermined) @@ -641,7 +645,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { match result { Ok((binding, flags)) => { if !sub_namespace_match(binding.macro_kinds(), macro_kind) { - return None; + return ControlFlow::Continue(()); } // Below we report various ambiguity errors. @@ -649,7 +653,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { // or in late resolution when everything is already imported and expanded // and no ambiguities exist. if matches!(finalize, None | Some(Finalize { stage: Stage::Late, .. })) { - return Some(Ok(binding)); + return ControlFlow::Break(Ok(binding)); } if let Some((innermost_binding, innermost_flags)) = innermost_result { @@ -732,7 +736,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { misc1: misc(innermost_flags), misc2: misc(flags), }); - return Some(Ok(innermost_binding)); + return ControlFlow::Break(Ok(innermost_binding)); } } } else { @@ -744,7 +748,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { Err(Determinacy::Undetermined) => determinacy = Determinacy::Undetermined, } - None + ControlFlow::Continue(()) }, ); diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index 7ce70ee9af8d4..a73636d2204d5 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -26,6 +26,7 @@ use std::cell::Ref; use std::collections::BTreeSet; use std::fmt::{self}; +use std::ops::ControlFlow; use std::sync::Arc; use diagnostics::{ImportSuggestion, LabelSuggestion, Suggestion}; @@ -1917,7 +1918,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { | Scope::BuiltinTypes => {} _ => unreachable!(), } - None::<()> + ControlFlow::<()>::Continue(()) }); found_traits From 6ca6302533513cd7d22c24f57ebdb673195b2214 Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Thu, 27 Nov 2025 19:04:30 +0300 Subject: [PATCH 2/9] resolve: Move one iteration of `resolve_ident_in_scope_set` into a separate function --- compiler/rustc_resolve/src/ident.rs | 446 +++++++++++++++------------- 1 file changed, 235 insertions(+), 211 deletions(-) diff --git a/compiler/rustc_resolve/src/ident.rs b/compiler/rustc_resolve/src/ident.rs index ea09200d41600..e40e5669fdfb1 100644 --- a/compiler/rustc_resolve/src/ident.rs +++ b/compiler/rustc_resolve/src/ident.rs @@ -43,6 +43,17 @@ enum Shadowing { Unrestricted, } +bitflags::bitflags! { + #[derive(Clone, Copy)] + struct Flags: u8 { + const MACRO_RULES = 1 << 0; + const MODULE = 1 << 1; + const MISC_SUGGEST_CRATE = 1 << 2; + const MISC_SUGGEST_SELF = 1 << 3; + const MISC_FROM_PRELUDE = 1 << 4; + } +} + impl<'ra, 'tcx> Resolver<'ra, 'tcx> { /// A generic scope visitor. /// Visits scopes in order to resolve some identifier in them or perform other actions. @@ -392,17 +403,6 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { ignore_binding: Option>, ignore_import: Option>, ) -> Result, Determinacy> { - bitflags::bitflags! { - #[derive(Clone, Copy)] - struct Flags: u8 { - const MACRO_RULES = 1 << 0; - const MODULE = 1 << 1; - const MISC_SUGGEST_CRATE = 1 << 2; - const MISC_SUGGEST_SELF = 1 << 3; - const MISC_FROM_PRELUDE = 1 << 4; - } - } - assert!(force || finalize.is_none()); // `finalize` implies `force` // Make sure `self`, `super` etc produce an error when passed to here. @@ -431,10 +431,6 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { let mut determinacy = Determinacy::Determined; let mut extern_prelude_item_binding = None; let mut extern_prelude_flag_binding = None; - // 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 derive_fallback_lint_id = match finalize { @@ -447,202 +443,22 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { orig_ident.span.ctxt(), derive_fallback_lint_id, |this, scope, use_prelude, ctxt| { - let ident = Ident::new(orig_ident.name, orig_ident.span.with_ctxt(ctxt)); - let result = match scope { - Scope::DeriveHelpers(expn_id) => { - if let Some(binding) = this.helper_attrs.get(&expn_id).and_then(|attrs| { - attrs.iter().rfind(|(i, _)| ident == *i).map(|(_, binding)| *binding) - }) { - Ok((binding, Flags::empty())) - } else { - Err(Determinacy::Determined) - } - } - Scope::DeriveHelpersCompat => { - let mut result = Err(Determinacy::Determined); - for derive in parent_scope.derives { - let parent_scope = &ParentScope { derives: &[], ..*parent_scope }; - match this.reborrow().resolve_derive_macro_path( - derive, - parent_scope, - force, - ignore_import, - ) { - Ok((Some(ext), _)) => { - if ext.helper_attrs.contains(&ident.name) { - let binding = this.arenas.new_pub_res_binding( - Res::NonMacroAttr(NonMacroAttrKind::DeriveHelperCompat), - derive.span, - LocalExpnId::ROOT, - ); - result = Ok((binding, Flags::empty())); - break; - } - } - Ok(_) | Err(Determinacy::Determined) => {} - Err(Determinacy::Undetermined) => { - result = Err(Determinacy::Undetermined) - } - } - } - result - } - Scope::MacroRules(macro_rules_scope) => match macro_rules_scope.get() { - MacroRulesScope::Binding(macro_rules_binding) - if ident == macro_rules_binding.ident => - { - Ok((macro_rules_binding.binding, Flags::MACRO_RULES)) - } - MacroRulesScope::Invocation(_) => Err(Determinacy::Undetermined), - _ => Err(Determinacy::Determined), - }, - Scope::Module(module, derive_fallback_lint_id) => { - let (adjusted_parent_scope, adjusted_finalize) = - if matches!(scope_set, ScopeSet::ModuleAndExternPrelude(..)) { - (parent_scope, finalize_scope!()) - } else { - ( - &ParentScope { module, ..*parent_scope }, - finalize_scope!().map(|f| Finalize { used: Used::Scope, ..f }), - ) - }; - let binding = this.reborrow().resolve_ident_in_module_unadjusted( - ModuleOrUniformRoot::Module(module), - ident, - ns, - adjusted_parent_scope, - Shadowing::Restricted, - adjusted_finalize, - ignore_binding, - ignore_import, - ); - match binding { - Ok(binding) => { - if let Some(lint_id) = derive_fallback_lint_id { - this.get_mut().lint_buffer.buffer_lint( - PROC_MACRO_DERIVE_RESOLUTION_FALLBACK, - lint_id, - orig_ident.span, - errors::ProcMacroDeriveResolutionFallback { - span: orig_ident.span, - ns_descr: ns.descr(), - ident, - }, - ); - } - let misc_flags = if module == this.graph_root { - Flags::MISC_SUGGEST_CRATE - } else if module.is_normal() { - Flags::MISC_SUGGEST_SELF - } else { - Flags::empty() - }; - Ok((binding, Flags::MODULE | misc_flags)) - } - Err((Determinacy::Undetermined, Weak::No)) => { - return ControlFlow::Break(Err(Determinacy::determined(force))); - } - Err((Determinacy::Undetermined, Weak::Yes)) => { - Err(Determinacy::Undetermined) - } - Err((Determinacy::Determined, _)) => Err(Determinacy::Determined), - } - } - Scope::MacroUsePrelude => { - match this.macro_use_prelude.get(&ident.name).cloned() { - Some(binding) => Ok((binding, Flags::MISC_FROM_PRELUDE)), - None => Err(Determinacy::determined( - this.graph_root.unexpanded_invocations.borrow().is_empty(), - )), - } - } - Scope::BuiltinAttrs => match this.builtin_attrs_bindings.get(&ident.name) { - Some(binding) => Ok((*binding, Flags::empty())), - None => Err(Determinacy::Determined), - }, - Scope::ExternPreludeItems => { - match this - .reborrow() - .extern_prelude_get_item(ident, finalize_scope!().is_some()) - { - Some(binding) => { - extern_prelude_item_binding = 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) => { - extern_prelude_flag_binding = 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), - }, - Scope::StdLibPrelude => { - let mut result = Err(Determinacy::Determined); - if let Some(prelude) = this.prelude - && let Ok(binding) = this.reborrow().resolve_ident_in_module_unadjusted( - ModuleOrUniformRoot::Module(prelude), - ident, - ns, - parent_scope, - Shadowing::Unrestricted, - None, - ignore_binding, - ignore_import, - ) - && (matches!(use_prelude, UsePrelude::Yes) - || this.is_builtin_macro(binding.res())) - { - result = Ok((binding, Flags::MISC_FROM_PRELUDE)); - } - - result - } - Scope::BuiltinTypes => match this.builtin_types_bindings.get(&ident.name) { - Some(binding) => { - if matches!(ident.name, sym::f16) - && !this.tcx.features().f16() - && !ident.span.allows_unstable(sym::f16) - && finalize_scope!().is_some() - { - feature_err( - this.tcx.sess, - sym::f16, - ident.span, - "the type `f16` is unstable", - ) - .emit(); - } - if matches!(ident.name, sym::f128) - && !this.tcx.features().f128() - && !ident.span.allows_unstable(sym::f128) - && finalize_scope!().is_some() - { - feature_err( - this.tcx.sess, - sym::f128, - ident.span, - "the type `f128` is unstable", - ) - .emit(); - } - Ok((*binding, Flags::empty())) - } - None => Err(Determinacy::Determined), - }, - }; - - match result { + match this.reborrow().resolve_ident_in_scope( + orig_ident, + ns, + scope, + use_prelude, + ctxt, + scope_set, + parent_scope, + // Shadowed bindings don't need to be marked as used or non-speculatively loaded. + if innermost_result.is_none() { finalize } else { None }, + force, + ignore_binding, + ignore_import, + &mut extern_prelude_item_binding, + &mut extern_prelude_flag_binding, + )? { Ok((binding, flags)) => { if !sub_namespace_match(binding.macro_kinds(), macro_kind) { return ControlFlow::Continue(()); @@ -764,6 +580,214 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { Err(Determinacy::determined(determinacy == Determinacy::Determined || force)) } + fn resolve_ident_in_scope<'r>( + mut self: CmResolver<'r, 'ra, 'tcx>, + orig_ident: Ident, + ns: Namespace, + scope: Scope<'ra>, + use_prelude: UsePrelude, + ctxt: SyntaxContext, + scope_set: ScopeSet<'ra>, + parent_scope: &ParentScope<'ra>, + finalize: Option, + force: bool, + ignore_binding: Option>, + ignore_import: Option>, + extern_prelude_item_binding: &mut Option>, + extern_prelude_flag_binding: &mut Option>, + ) -> ControlFlow< + Result, Determinacy>, + Result<(NameBinding<'ra>, Flags), Determinacy>, + > { + let ident = Ident::new(orig_ident.name, orig_ident.span.with_ctxt(ctxt)); + let ret = match scope { + Scope::DeriveHelpers(expn_id) => { + if let Some(binding) = self.helper_attrs.get(&expn_id).and_then(|attrs| { + attrs.iter().rfind(|(i, _)| ident == *i).map(|(_, binding)| *binding) + }) { + Ok((binding, Flags::empty())) + } else { + Err(Determinacy::Determined) + } + } + Scope::DeriveHelpersCompat => { + let mut result = Err(Determinacy::Determined); + for derive in parent_scope.derives { + let parent_scope = &ParentScope { derives: &[], ..*parent_scope }; + match self.reborrow().resolve_derive_macro_path( + derive, + parent_scope, + force, + ignore_import, + ) { + Ok((Some(ext), _)) => { + if ext.helper_attrs.contains(&ident.name) { + let binding = self.arenas.new_pub_res_binding( + Res::NonMacroAttr(NonMacroAttrKind::DeriveHelperCompat), + derive.span, + LocalExpnId::ROOT, + ); + result = Ok((binding, Flags::empty())); + break; + } + } + Ok(_) | Err(Determinacy::Determined) => {} + Err(Determinacy::Undetermined) => result = Err(Determinacy::Undetermined), + } + } + result + } + Scope::MacroRules(macro_rules_scope) => match macro_rules_scope.get() { + MacroRulesScope::Binding(macro_rules_binding) + if ident == macro_rules_binding.ident => + { + Ok((macro_rules_binding.binding, Flags::MACRO_RULES)) + } + MacroRulesScope::Invocation(_) => Err(Determinacy::Undetermined), + _ => Err(Determinacy::Determined), + }, + Scope::Module(module, derive_fallback_lint_id) => { + let (adjusted_parent_scope, adjusted_finalize) = + if matches!(scope_set, ScopeSet::ModuleAndExternPrelude(..)) { + (parent_scope, finalize) + } else { + ( + &ParentScope { module, ..*parent_scope }, + finalize.map(|f| Finalize { used: Used::Scope, ..f }), + ) + }; + let binding = self.reborrow().resolve_ident_in_module_unadjusted( + ModuleOrUniformRoot::Module(module), + ident, + ns, + adjusted_parent_scope, + Shadowing::Restricted, + adjusted_finalize, + ignore_binding, + ignore_import, + ); + match binding { + Ok(binding) => { + if let Some(lint_id) = derive_fallback_lint_id { + self.get_mut().lint_buffer.buffer_lint( + PROC_MACRO_DERIVE_RESOLUTION_FALLBACK, + lint_id, + orig_ident.span, + errors::ProcMacroDeriveResolutionFallback { + span: orig_ident.span, + ns_descr: ns.descr(), + ident, + }, + ); + } + let misc_flags = if module == self.graph_root { + Flags::MISC_SUGGEST_CRATE + } else if module.is_normal() { + Flags::MISC_SUGGEST_SELF + } else { + Flags::empty() + }; + Ok((binding, Flags::MODULE | misc_flags)) + } + Err((Determinacy::Undetermined, Weak::No)) => { + return ControlFlow::Break(Err(Determinacy::determined(force))); + } + Err((Determinacy::Undetermined, Weak::Yes)) => Err(Determinacy::Undetermined), + Err((Determinacy::Determined, _)) => Err(Determinacy::Determined), + } + } + Scope::MacroUsePrelude => match self.macro_use_prelude.get(&ident.name).cloned() { + Some(binding) => Ok((binding, Flags::MISC_FROM_PRELUDE)), + None => Err(Determinacy::determined( + self.graph_root.unexpanded_invocations.borrow().is_empty(), + )), + }, + Scope::BuiltinAttrs => match self.builtin_attrs_bindings.get(&ident.name) { + Some(binding) => Ok((*binding, Flags::empty())), + None => Err(Determinacy::Determined), + }, + Scope::ExternPreludeItems => { + match self.reborrow().extern_prelude_get_item(ident, finalize.is_some()) { + Some(binding) => { + *extern_prelude_item_binding = Some(binding); + Ok((binding, Flags::empty())) + } + None => Err(Determinacy::determined( + self.graph_root.unexpanded_invocations.borrow().is_empty(), + )), + } + } + Scope::ExternPreludeFlags => { + match self.extern_prelude_get_flag(ident, finalize.is_some()) { + Some(binding) => { + *extern_prelude_flag_binding = Some(binding); + Ok((binding, Flags::empty())) + } + None => Err(Determinacy::Determined), + } + } + Scope::ToolPrelude => match self.registered_tool_bindings.get(&ident) { + Some(binding) => Ok((*binding, Flags::empty())), + None => Err(Determinacy::Determined), + }, + Scope::StdLibPrelude => { + let mut result = Err(Determinacy::Determined); + if let Some(prelude) = self.prelude + && let Ok(binding) = self.reborrow().resolve_ident_in_module_unadjusted( + ModuleOrUniformRoot::Module(prelude), + ident, + ns, + parent_scope, + Shadowing::Unrestricted, + None, + ignore_binding, + ignore_import, + ) + && (matches!(use_prelude, UsePrelude::Yes) + || self.is_builtin_macro(binding.res())) + { + result = Ok((binding, Flags::MISC_FROM_PRELUDE)); + } + + result + } + Scope::BuiltinTypes => match self.builtin_types_bindings.get(&ident.name) { + Some(binding) => { + if matches!(ident.name, sym::f16) + && !self.tcx.features().f16() + && !ident.span.allows_unstable(sym::f16) + && finalize.is_some() + { + feature_err( + self.tcx.sess, + sym::f16, + ident.span, + "the type `f16` is unstable", + ) + .emit(); + } + if matches!(ident.name, sym::f128) + && !self.tcx.features().f128() + && !ident.span.allows_unstable(sym::f128) + && finalize.is_some() + { + feature_err( + self.tcx.sess, + sym::f128, + ident.span, + "the type `f128` is unstable", + ) + .emit(); + } + Ok((*binding, Flags::empty())) + } + None => Err(Determinacy::Determined), + }, + }; + + ControlFlow::Continue(ret) + } + #[instrument(level = "debug", skip(self))] pub(crate) fn maybe_resolve_ident_in_module<'r>( self: CmResolver<'r, 'ra, 'tcx>, From 47edfd386aa4ee6af269b46dc5670909c3164f8b Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Thu, 27 Nov 2025 19:46:20 +0300 Subject: [PATCH 3/9] resolve: Move ambiguity detection in `resolve_ident_in_scope_set` into a separate function --- compiler/rustc_resolve/src/ident.rs | 175 +++++++++++++++------------- 1 file changed, 95 insertions(+), 80 deletions(-) diff --git a/compiler/rustc_resolve/src/ident.rs b/compiler/rustc_resolve/src/ident.rs index e40e5669fdfb1..07ae1b6201a1b 100644 --- a/compiler/rustc_resolve/src/ident.rs +++ b/compiler/rustc_resolve/src/ident.rs @@ -474,86 +474,17 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { if let Some((innermost_binding, innermost_flags)) = innermost_result { // Found another solution, if the first one was "weak", report an error. - let (res, innermost_res) = (binding.res(), innermost_binding.res()); - if res != innermost_res { - let is_builtin = |res| { - matches!(res, Res::NonMacroAttr(NonMacroAttrKind::Builtin(..))) - }; - let derive_helper = - Res::NonMacroAttr(NonMacroAttrKind::DeriveHelper); - let derive_helper_compat = - Res::NonMacroAttr(NonMacroAttrKind::DeriveHelperCompat); - - let ambiguity_error_kind = if is_builtin(innermost_res) - || is_builtin(res) - { - Some(AmbiguityKind::BuiltinAttr) - } else if innermost_res == derive_helper_compat - || res == derive_helper_compat && innermost_res != derive_helper - { - Some(AmbiguityKind::DeriveHelper) - } else if innermost_flags.contains(Flags::MACRO_RULES) - && flags.contains(Flags::MODULE) - && !this.disambiguate_macro_rules_vs_modularized( - innermost_binding, - binding, - ) - { - Some(AmbiguityKind::MacroRulesVsModularized) - } else if flags.contains(Flags::MACRO_RULES) - && innermost_flags.contains(Flags::MODULE) - { - // should be impossible because of visitation order in - // visit_scopes - // - // we visit all macro_rules scopes (e.g. textual scope macros) - // before we visit any modules (e.g. path-based scope macros) - span_bug!( - orig_ident.span, - "ambiguous scoped macro resolutions with path-based \ - scope resolution as first candidate" - ) - } else if innermost_binding.is_glob_import() { - Some(AmbiguityKind::GlobVsOuter) - } else if innermost_binding - .may_appear_after(parent_scope.expansion, binding) - { - Some(AmbiguityKind::MoreExpandedVsOuter) - } else { - None - }; - // Skip ambiguity errors for extern flag bindings "overridden" - // by extern item bindings. - // FIXME: Remove with lang team approval. - let issue_145575_hack = Some(binding) - == extern_prelude_flag_binding - && extern_prelude_item_binding.is_some() - && extern_prelude_item_binding != Some(innermost_binding); - if let Some(kind) = ambiguity_error_kind - && !issue_145575_hack - { - let misc = |f: Flags| { - if f.contains(Flags::MISC_SUGGEST_CRATE) { - AmbiguityErrorMisc::SuggestCrate - } else if f.contains(Flags::MISC_SUGGEST_SELF) { - AmbiguityErrorMisc::SuggestSelf - } else if f.contains(Flags::MISC_FROM_PRELUDE) { - AmbiguityErrorMisc::FromPrelude - } else { - AmbiguityErrorMisc::None - } - }; - this.get_mut().ambiguity_errors.push(AmbiguityError { - kind, - ident: orig_ident, - b1: innermost_binding, - b2: binding, - warning: false, - misc1: misc(innermost_flags), - misc2: misc(flags), - }); - return ControlFlow::Break(Ok(innermost_binding)); - } + if this.get_mut().maybe_push_ambiguity( + orig_ident, + parent_scope, + binding, + innermost_binding, + flags, + innermost_flags, + extern_prelude_item_binding, + extern_prelude_flag_binding, + ) { + return ControlFlow::Break(Ok(innermost_binding)); } } else { // Found the first solution. @@ -788,6 +719,90 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { ControlFlow::Continue(ret) } + fn maybe_push_ambiguity( + &mut self, + orig_ident: Ident, + parent_scope: &ParentScope<'ra>, + binding: NameBinding<'ra>, + innermost_binding: NameBinding<'ra>, + flags: Flags, + innermost_flags: Flags, + extern_prelude_item_binding: Option>, + extern_prelude_flag_binding: Option>, + ) -> bool { + let (res, innermost_res) = (binding.res(), innermost_binding.res()); + if res == innermost_res { + return false; + } + + let is_builtin = |res| matches!(res, Res::NonMacroAttr(NonMacroAttrKind::Builtin(..))); + let derive_helper = Res::NonMacroAttr(NonMacroAttrKind::DeriveHelper); + let derive_helper_compat = Res::NonMacroAttr(NonMacroAttrKind::DeriveHelperCompat); + + let ambiguity_error_kind = if is_builtin(innermost_res) || is_builtin(res) { + Some(AmbiguityKind::BuiltinAttr) + } else if innermost_res == derive_helper_compat + || res == derive_helper_compat && innermost_res != derive_helper + { + Some(AmbiguityKind::DeriveHelper) + } else if innermost_flags.contains(Flags::MACRO_RULES) + && flags.contains(Flags::MODULE) + && !self.disambiguate_macro_rules_vs_modularized(innermost_binding, binding) + { + Some(AmbiguityKind::MacroRulesVsModularized) + } else if flags.contains(Flags::MACRO_RULES) && innermost_flags.contains(Flags::MODULE) { + // should be impossible because of visitation order in + // visit_scopes + // + // we visit all macro_rules scopes (e.g. textual scope macros) + // before we visit any modules (e.g. path-based scope macros) + span_bug!( + orig_ident.span, + "ambiguous scoped macro resolutions with path-based \ + scope resolution as first candidate" + ) + } else if innermost_binding.is_glob_import() { + Some(AmbiguityKind::GlobVsOuter) + } else if innermost_binding.may_appear_after(parent_scope.expansion, binding) { + Some(AmbiguityKind::MoreExpandedVsOuter) + } else { + None + }; + // Skip ambiguity errors for extern flag bindings "overridden" + // by extern item bindings. + // FIXME: Remove with lang team approval. + let issue_145575_hack = Some(binding) == extern_prelude_flag_binding + && extern_prelude_item_binding.is_some() + && extern_prelude_item_binding != Some(innermost_binding); + if let Some(kind) = ambiguity_error_kind + && !issue_145575_hack + { + let misc = |f: Flags| { + if f.contains(Flags::MISC_SUGGEST_CRATE) { + AmbiguityErrorMisc::SuggestCrate + } else if f.contains(Flags::MISC_SUGGEST_SELF) { + AmbiguityErrorMisc::SuggestSelf + } else if f.contains(Flags::MISC_FROM_PRELUDE) { + AmbiguityErrorMisc::FromPrelude + } else { + AmbiguityErrorMisc::None + } + }; + self.ambiguity_errors.push(AmbiguityError { + kind, + ident: orig_ident, + b1: innermost_binding, + b2: binding, + warning: false, + misc1: misc(innermost_flags), + misc2: misc(flags), + }); + return true; + } + + false + } + #[instrument(level = "debug", skip(self))] pub(crate) fn maybe_resolve_ident_in_module<'r>( self: CmResolver<'r, 'ra, 'tcx>, From 621d87099bc501f688c53b6b865971919b79d407 Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Thu, 27 Nov 2025 20:19:15 +0300 Subject: [PATCH 4/9] resolve: Tweak top level logic and comments in `resolve_ident_in_scope_set` --- compiler/rustc_resolve/src/ident.rs | 32 +++++++++++++++-------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/compiler/rustc_resolve/src/ident.rs b/compiler/rustc_resolve/src/ident.rs index 07ae1b6201a1b..73ba2fd1f7739 100644 --- a/compiler/rustc_resolve/src/ident.rs +++ b/compiler/rustc_resolve/src/ident.rs @@ -415,6 +415,10 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { ScopeSet::ExternPrelude => (TypeNS, None), ScopeSet::Macro(macro_kind) => (MacroNS, Some(macro_kind)), }; + let derive_fallback_lint_id = match finalize { + Some(Finalize { node_id, stage: Stage::Late, .. }) => Some(node_id), + _ => None, + }; // This is *the* result, resolution from the scope closest to the resolved identifier. // However, sometimes this result is "weak" because it comes from a glob import or @@ -433,16 +437,15 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { let mut extern_prelude_flag_binding = None; // Go through all the scopes and try to resolve the name. - let derive_fallback_lint_id = match finalize { - Some(Finalize { node_id, stage: Stage::Late, .. }) => Some(node_id), - _ => None, - }; let break_result = self.visit_scopes( scope_set, parent_scope, orig_ident.span.ctxt(), derive_fallback_lint_id, |this, scope, use_prelude, ctxt| { + // We can break with an error at this step, it means we cannot determine the + // resolution right now, but we must block and wait until we can instead of + // considering outer scopes. match this.reborrow().resolve_ident_in_scope( orig_ident, ns, @@ -459,11 +462,9 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { &mut extern_prelude_item_binding, &mut extern_prelude_flag_binding, )? { - Ok((binding, flags)) => { - if !sub_namespace_match(binding.macro_kinds(), macro_kind) { - return ControlFlow::Continue(()); - } - + Ok((binding, flags)) + if sub_namespace_match(binding.macro_kinds(), macro_kind) => + { // Below we report various ambiguity errors. // We do not need to report them if we are either in speculative resolution, // or in late resolution when everything is already imported and expanded @@ -484,6 +485,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { extern_prelude_item_binding, extern_prelude_flag_binding, ) { + // No need to search for more potential ambiguities, one is enough. return ControlFlow::Break(Ok(innermost_binding)); } } else { @@ -491,7 +493,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { innermost_result = Some((binding, flags)); } } - Err(Determinacy::Determined) => {} + Ok(_) | Err(Determinacy::Determined) => {} Err(Determinacy::Undetermined) => determinacy = Determinacy::Undetermined, } @@ -499,16 +501,16 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { }, ); + // Scope visiting returned some result early. if let Some(break_result) = break_result { return break_result; } - // The first found solution was the only one, return it. - if let Some((binding, _)) = innermost_result { - return Ok(binding); + // Scope visiting walked all the scopes and maybe found something in one of them. + match innermost_result { + Some((binding, _)) => Ok(binding), + None => Err(Determinacy::determined(determinacy == Determinacy::Determined || force)), } - - Err(Determinacy::determined(determinacy == Determinacy::Determined || force)) } fn resolve_ident_in_scope<'r>( From b7f6c9559c7be84f6477b9e460172cc8484f27ee Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Fri, 28 Nov 2025 21:09:10 +0300 Subject: [PATCH 5/9] resolve: Move `resolve_ident_in_module_unadjusted` for real modules to a separate method --- compiler/rustc_resolve/src/ident.rs | 42 ++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/compiler/rustc_resolve/src/ident.rs b/compiler/rustc_resolve/src/ident.rs index 73ba2fd1f7739..c98867ae2b9ca 100644 --- a/compiler/rustc_resolve/src/ident.rs +++ b/compiler/rustc_resolve/src/ident.rs @@ -348,7 +348,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { ))); } else if let RibKind::Block(Some(module)) = rib.kind && let Ok(binding) = self.cm().resolve_ident_in_module_unadjusted( - ModuleOrUniformRoot::Module(module), + module, ident, ns, parent_scope, @@ -590,7 +590,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { ) }; let binding = self.reborrow().resolve_ident_in_module_unadjusted( - ModuleOrUniformRoot::Module(module), + module, ident, ns, adjusted_parent_scope, @@ -667,7 +667,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { let mut result = Err(Determinacy::Determined); if let Some(prelude) = self.prelude && let Ok(binding) = self.reborrow().resolve_ident_in_module_unadjusted( - ModuleOrUniformRoot::Module(prelude), + prelude, ident, ns, parent_scope, @@ -846,7 +846,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { // No adjustments } } - self.resolve_ident_in_module_unadjusted( + self.resolve_ident_in_virt_module_unadjusted( module, ident, ns, @@ -857,11 +857,12 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { ignore_import, ) } + /// Attempts to resolve `ident` in namespaces `ns` of `module`. /// Invariant: if `finalize` is `Some`, expansion and import resolution must be complete. #[instrument(level = "debug", skip(self))] - fn resolve_ident_in_module_unadjusted<'r>( - mut self: CmResolver<'r, 'ra, 'tcx>, + fn resolve_ident_in_virt_module_unadjusted<'r>( + self: CmResolver<'r, 'ra, 'tcx>, module: ModuleOrUniformRoot<'ra>, ident: Ident, ns: Namespace, @@ -873,8 +874,17 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { ignore_binding: Option>, ignore_import: Option>, ) -> Result, (Determinacy, Weak)> { - let module = match module { - ModuleOrUniformRoot::Module(module) => module, + match module { + ModuleOrUniformRoot::Module(module) => self.resolve_ident_in_module_unadjusted( + module, + ident, + ns, + parent_scope, + shadowing, + finalize, + ignore_binding, + ignore_import, + ), ModuleOrUniformRoot::ModuleAndExternPrelude(module) => { assert_eq!(shadowing, Shadowing::Unrestricted); let binding = self.resolve_ident_in_scope_set( @@ -929,8 +939,20 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { ); return binding.map_err(|determinacy| (determinacy, Weak::No)); } - }; + } + } + fn resolve_ident_in_module_unadjusted<'r>( + mut self: CmResolver<'r, 'ra, 'tcx>, + module: Module<'ra>, + ident: Ident, + ns: Namespace, + parent_scope: &ParentScope<'ra>, + shadowing: Shadowing, + finalize: Option, + ignore_binding: Option>, + ignore_import: Option>, + ) -> Result, (Determinacy, Weak)> { let key = BindingKey::new(ident, ns); // `try_borrow_mut` is required to ensure exclusive access, even if the resulting binding // doesn't need to be mutable. It will fail when there is a cycle of imports, and without @@ -1048,7 +1070,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { None => continue, }; let result = self.reborrow().resolve_ident_in_module_unadjusted( - ModuleOrUniformRoot::Module(module), + module, ident, ns, adjusted_parent_scope, From 56e1e240e64f6c946a20550f29690b9659c69b2a Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Fri, 28 Nov 2025 21:53:42 +0300 Subject: [PATCH 6/9] resolve: Remove some function parameters and return values that are no longer used --- compiler/rustc_resolve/src/ident.rs | 99 ++++++++++++++--------------- compiler/rustc_resolve/src/lib.rs | 7 +- 2 files changed, 47 insertions(+), 59 deletions(-) diff --git a/compiler/rustc_resolve/src/ident.rs b/compiler/rustc_resolve/src/ident.rs index c98867ae2b9ca..b56ae593961ee 100644 --- a/compiler/rustc_resolve/src/ident.rs +++ b/compiler/rustc_resolve/src/ident.rs @@ -22,9 +22,15 @@ use crate::{ AmbiguityError, AmbiguityErrorMisc, AmbiguityKind, BindingKey, CmResolver, Determinacy, Finalize, ImportKind, LexicalScopeBinding, Module, ModuleKind, ModuleOrUniformRoot, NameBinding, NameBindingKind, ParentScope, PathResult, PrivacyError, Res, ResolutionError, - Resolver, Scope, ScopeSet, Segment, Stage, Used, Weak, errors, + Resolver, Scope, ScopeSet, Segment, Stage, Used, errors, }; +#[derive(Debug)] +enum Weak { + Yes, + No, +} + #[derive(Copy, Clone)] pub enum UsePrelude { No, @@ -815,7 +821,6 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { ignore_import: Option>, ) -> Result, Determinacy> { self.resolve_ident_in_module(module, ident, ns, parent_scope, None, None, ignore_import) - .map_err(|(determinacy, _)| determinacy) } #[instrument(level = "debug", skip(self))] @@ -828,7 +833,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { finalize: Option, ignore_binding: Option>, ignore_import: Option>, - ) -> Result, (Determinacy, Weak)> { + ) -> Result, Determinacy> { let tmp_parent_scope; let mut adjusted_parent_scope = parent_scope; match module { @@ -851,15 +856,13 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { ident, ns, adjusted_parent_scope, - Shadowing::Unrestricted, finalize, ignore_binding, ignore_import, ) } - /// Attempts to resolve `ident` in namespaces `ns` of `module`. - /// Invariant: if `finalize` is `Some`, expansion and import resolution must be complete. + /// Attempts to resolve `ident` in namespace `ns` of `module`. #[instrument(level = "debug", skip(self))] fn resolve_ident_in_virt_module_unadjusted<'r>( self: CmResolver<'r, 'ra, 'tcx>, @@ -867,43 +870,37 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { ident: Ident, ns: Namespace, parent_scope: &ParentScope<'ra>, - shadowing: Shadowing, finalize: Option, - // This binding should be ignored during in-module resolution, so that we don't get - // "self-confirming" import resolutions during import validation and checking. ignore_binding: Option>, ignore_import: Option>, - ) -> Result, (Determinacy, Weak)> { + ) -> Result, Determinacy> { match module { - ModuleOrUniformRoot::Module(module) => self.resolve_ident_in_module_unadjusted( - module, + ModuleOrUniformRoot::Module(module) => self + .resolve_ident_in_module_unadjusted( + module, + ident, + ns, + parent_scope, + Shadowing::Unrestricted, + finalize, + ignore_binding, + ignore_import, + ) + .map_err(|(determinacy, _)| determinacy), + ModuleOrUniformRoot::ModuleAndExternPrelude(module) => self.resolve_ident_in_scope_set( ident, - ns, + ScopeSet::ModuleAndExternPrelude(ns, module), parent_scope, - shadowing, finalize, + finalize.is_some(), ignore_binding, ignore_import, ), - ModuleOrUniformRoot::ModuleAndExternPrelude(module) => { - assert_eq!(shadowing, Shadowing::Unrestricted); - let binding = self.resolve_ident_in_scope_set( - ident, - ScopeSet::ModuleAndExternPrelude(ns, module), - parent_scope, - finalize, - finalize.is_some(), - ignore_binding, - ignore_import, - ); - return binding.map_err(|determinacy| (determinacy, Weak::No)); - } ModuleOrUniformRoot::ExternPrelude => { - assert_eq!(shadowing, Shadowing::Unrestricted); - return if ns != TypeNS { - Err((Determined, Weak::No)) + if ns != TypeNS { + Err(Determined) } else { - let binding = self.resolve_ident_in_scope_set( + self.resolve_ident_in_scope_set( ident, ScopeSet::ExternPrelude, parent_scope, @@ -911,12 +908,10 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { finalize.is_some(), ignore_binding, ignore_import, - ); - return binding.map_err(|determinacy| (determinacy, Weak::No)); - }; + ) + } } ModuleOrUniformRoot::CurrentScope => { - assert_eq!(shadowing, Shadowing::Unrestricted); if ns == TypeNS { if ident.name == kw::Crate || ident.name == kw::DollarCrate { let module = self.resolve_crate_root(ident); @@ -928,7 +923,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } } - let binding = self.resolve_ident_in_scope_set( + self.resolve_ident_in_scope_set( ident, ScopeSet::All(ns), parent_scope, @@ -936,12 +931,12 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { finalize.is_some(), ignore_binding, ignore_import, - ); - return binding.map_err(|determinacy| (determinacy, Weak::No)); + ) } } } + /// Attempts to resolve `ident` in namespace `ns` of `module`. fn resolve_ident_in_module_unadjusted<'r>( mut self: CmResolver<'r, 'ra, 'tcx>, module: Module<'ra>, @@ -950,6 +945,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { parent_scope: &ParentScope<'ra>, shadowing: Shadowing, finalize: Option, + // This binding should be ignored during in-module resolution, so that we don't get + // "self-confirming" import resolutions during import validation and checking. ignore_binding: Option>, ignore_import: Option>, ) -> Result, (Determinacy, Weak)> { @@ -1236,15 +1233,13 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { ignore_binding, ignore_import, ) { - Err((Determined, _)) => continue, + Err(Determined) => continue, Ok(binding) if !self.is_accessible_from(binding.vis, single_import.parent_scope.module) => { continue; } - Ok(_) | Err((Undetermined, _)) => { - return true; - } + Ok(_) | Err(Undetermined) => return true, } } @@ -1761,17 +1756,15 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } let binding = if let Some(module) = module { - self.reborrow() - .resolve_ident_in_module( - module, - ident, - ns, - parent_scope, - finalize, - ignore_binding, - ignore_import, - ) - .map_err(|(determinacy, _)| determinacy) + self.reborrow().resolve_ident_in_module( + module, + ident, + ns, + parent_scope, + finalize, + ignore_binding, + ignore_import, + ) } else if let Some(ribs) = ribs && let Some(TypeNS | ValueNS) = opt_ns { diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index a73636d2204d5..7965c7220dbe4 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -99,12 +99,6 @@ use crate::ref_mut::{CmCell, CmRefCell}; rustc_fluent_macro::fluent_messages! { "../messages.ftl" } -#[derive(Debug)] -enum Weak { - Yes, - No, -} - #[derive(Copy, Clone, PartialEq, Debug)] enum Determinacy { Determined, @@ -2490,6 +2484,7 @@ enum Stage { Late, } +/// Invariant: if `Finalize` is used, expansion and import resolution must be complete. #[derive(Copy, Clone, Debug)] struct Finalize { /// Node ID for linting. From 674d287c38a3e20f39f707fcd00eaf8c5e48034c Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Sat, 29 Nov 2025 17:02:46 +0300 Subject: [PATCH 7/9] resolve: Replace `enum Weak` with `ops::ControlFlow` --- compiler/rustc_resolve/src/ident.rs | 45 ++++++++++++++--------------- compiler/rustc_resolve/src/lib.rs | 1 + 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/compiler/rustc_resolve/src/ident.rs b/compiler/rustc_resolve/src/ident.rs index b56ae593961ee..d1d2f318204b3 100644 --- a/compiler/rustc_resolve/src/ident.rs +++ b/compiler/rustc_resolve/src/ident.rs @@ -25,12 +25,6 @@ use crate::{ Resolver, Scope, ScopeSet, Segment, Stage, Used, errors, }; -#[derive(Debug)] -enum Weak { - Yes, - No, -} - #[derive(Copy, Clone)] pub enum UsePrelude { No, @@ -628,11 +622,11 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { }; Ok((binding, Flags::MODULE | misc_flags)) } - Err((Determinacy::Undetermined, Weak::No)) => { + Err(ControlFlow::Continue(determinacy)) => Err(determinacy), + Err(ControlFlow::Break(Determinacy::Undetermined)) => { return ControlFlow::Break(Err(Determinacy::determined(force))); } - Err((Determinacy::Undetermined, Weak::Yes)) => Err(Determinacy::Undetermined), - Err((Determinacy::Determined, _)) => Err(Determinacy::Determined), + Err(ControlFlow::Break(Determinacy::Determined)) => Err(Determined), } } Scope::MacroUsePrelude => match self.macro_use_prelude.get(&ident.name).cloned() { @@ -886,7 +880,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { ignore_binding, ignore_import, ) - .map_err(|(determinacy, _)| determinacy), + .map_err(|determinacy| determinacy.into_value()), ModuleOrUniformRoot::ModuleAndExternPrelude(module) => self.resolve_ident_in_scope_set( ident, ScopeSet::ModuleAndExternPrelude(ns, module), @@ -949,7 +943,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { // "self-confirming" import resolutions during import validation and checking. ignore_binding: Option>, ignore_import: Option>, - ) -> Result, (Determinacy, Weak)> { + ) -> Result, ControlFlow> { let key = BindingKey::new(ident, ns); // `try_borrow_mut` is required to ensure exclusive access, even if the resulting binding // doesn't need to be mutable. It will fail when there is a cycle of imports, and without @@ -957,7 +951,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { let resolution = &*self .resolution_or_default(module, key) .try_borrow_mut_unchecked() - .map_err(|_| (Determined, Weak::No))?; + .map_err(|_| ControlFlow::Break(Determined))?; // If the primary binding is unusable, search further and return the shadowed glob // binding if it exists. What we really want here is having two separate scopes in @@ -981,7 +975,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { let check_usable = |this: CmResolver<'r, 'ra, 'tcx>, binding: NameBinding<'ra>| { let usable = this.is_accessible_from(binding.vis, parent_scope.module); - if usable { Ok(binding) } else { Err((Determined, Weak::No)) } + if usable { Ok(binding) } else { Err(ControlFlow::Break(Determined)) } }; // Items and single imports are not shadowable, if we have one, then it's determined. @@ -1003,7 +997,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { ignore_binding, parent_scope, ) { - return Err((Undetermined, Weak::No)); + return Err(ControlFlow::Break(Undetermined)); } // So we have a resolution that's from a glob import. This resolution is determined @@ -1022,7 +1016,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { if binding.determined() || ns == MacroNS || shadowing == Shadowing::Restricted { return check_usable(self, binding); } else { - return Err((Undetermined, Weak::No)); + return Err(ControlFlow::Break(Undetermined)); } } @@ -1032,12 +1026,12 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { // expansion. With restricted shadowing names from globs and macro expansions cannot // shadow names from outer scopes, so we can freely fallback from module search to search // in outer scopes. For `resolve_ident_in_scope_set` to continue search in outer - // scopes we return `Undetermined` with `Weak::Yes`. + // scopes we return `Undetermined` with `ControlFlow::Continue`. // Check if one of unexpanded macros can still define the name, // if it can then our "no resolution" result is not determined and can be invalidated. if !module.unexpanded_invocations.borrow().is_empty() { - return Err((Undetermined, Weak::Yes)); + return Err(ControlFlow::Continue(Undetermined)); } // Check if one of glob imports can still define the name, @@ -1052,7 +1046,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { let module = match glob_import.imported_module.get() { Some(ModuleOrUniformRoot::Module(module)) => module, Some(_) => continue, - None => return Err((Undetermined, Weak::Yes)), + None => return Err(ControlFlow::Continue(Undetermined)), }; let tmp_parent_scope; let (mut adjusted_parent_scope, mut ident) = @@ -1078,18 +1072,21 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { ); match result { - Err((Determined, _)) => continue, + Err(ControlFlow::Break(Determined) | ControlFlow::Continue(Determined)) => continue, Ok(binding) if !self.is_accessible_from(binding.vis, glob_import.parent_scope.module) => { continue; } - Ok(_) | Err((Undetermined, _)) => return Err((Undetermined, Weak::Yes)), + Ok(_) + | Err(ControlFlow::Break(Undetermined) | ControlFlow::Continue(Undetermined)) => { + return Err(ControlFlow::Continue(Undetermined)); + } } } // No resolution and no one else can define the name - determinate error. - Err((Determined, Weak::No)) + Err(ControlFlow::Break(Determined)) } fn finalize_module_binding( @@ -1101,11 +1098,11 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { module: Module<'ra>, finalize: Finalize, shadowing: Shadowing, - ) -> Result, (Determinacy, Weak)> { + ) -> Result, ControlFlow> { let Finalize { path_span, report_private, used, root_span, .. } = finalize; let Some(binding) = binding else { - return Err((Determined, Weak::No)); + return Err(ControlFlow::Break(Determined)); }; if !self.is_accessible_from(binding.vis, parent_scope.module) { @@ -1120,7 +1117,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { single_nested: path_span != root_span, }); } else { - return Err((Determined, Weak::No)); + return Err(ControlFlow::Break(Determined)); } } diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index 7965c7220dbe4..16eeb9229c977 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -13,6 +13,7 @@ #![feature(arbitrary_self_types)] #![feature(assert_matches)] #![feature(box_patterns)] +#![feature(control_flow_into_value)] #![feature(decl_macro)] #![feature(default_field_values)] #![feature(if_let_guard)] From 9761db07d9b376326841ee895fd71b86ef2b47b5 Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Sat, 29 Nov 2025 17:10:31 +0300 Subject: [PATCH 8/9] resolve: Correctly mark break and continue for determinate errors Existing but private bindings - break, everything else - continue. --- compiler/rustc_resolve/src/ident.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_resolve/src/ident.rs b/compiler/rustc_resolve/src/ident.rs index d1d2f318204b3..91cf0f97e2526 100644 --- a/compiler/rustc_resolve/src/ident.rs +++ b/compiler/rustc_resolve/src/ident.rs @@ -626,7 +626,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { Err(ControlFlow::Break(Determinacy::Undetermined)) => { return ControlFlow::Break(Err(Determinacy::determined(force))); } - Err(ControlFlow::Break(Determinacy::Determined)) => Err(Determined), + // Privacy errors, do not happen during in scope resolution. + Err(ControlFlow::Break(Determinacy::Determined)) => unreachable!(), } } Scope::MacroUsePrelude => match self.macro_use_prelude.get(&ident.name).cloned() { @@ -951,7 +952,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { let resolution = &*self .resolution_or_default(module, key) .try_borrow_mut_unchecked() - .map_err(|_| ControlFlow::Break(Determined))?; + .map_err(|_| ControlFlow::Continue(Determined))?; // If the primary binding is unusable, search further and return the shadowed glob // binding if it exists. What we really want here is having two separate scopes in @@ -1086,7 +1087,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } // No resolution and no one else can define the name - determinate error. - Err(ControlFlow::Break(Determined)) + Err(ControlFlow::Continue(Determined)) } fn finalize_module_binding( @@ -1102,7 +1103,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { let Finalize { path_span, report_private, used, root_span, .. } = finalize; let Some(binding) = binding else { - return Err(ControlFlow::Break(Determined)); + return Err(ControlFlow::Continue(Determined)); }; if !self.is_accessible_from(binding.vis, parent_scope.module) { From c91b6ca58d4d870d3099db1defbd8c1f26a7d851 Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Sat, 29 Nov 2025 17:18:17 +0300 Subject: [PATCH 9/9] resolve: Split `resolve_ident_in_module_unadjusted` into two parts - for non-glob and glob bindings. In preparation for introducing `Scope::Module(Non)Globs` and `ScopeSet::Module`. --- compiler/rustc_resolve/src/ident.rs | 128 ++++++++++++++++++++++------ 1 file changed, 104 insertions(+), 24 deletions(-) diff --git a/compiler/rustc_resolve/src/ident.rs b/compiler/rustc_resolve/src/ident.rs index 91cf0f97e2526..f59b5a0aad9a7 100644 --- a/compiler/rustc_resolve/src/ident.rs +++ b/compiler/rustc_resolve/src/ident.rs @@ -944,6 +944,46 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { // "self-confirming" import resolutions during import validation and checking. ignore_binding: Option>, ignore_import: Option>, + ) -> Result, ControlFlow> { + let res = self.reborrow().resolve_ident_in_module_non_globs_unadjusted( + module, + ident, + ns, + parent_scope, + shadowing, + finalize, + ignore_binding, + ignore_import, + ); + + match res { + Ok(_) | Err(ControlFlow::Break(_)) => return res, + Err(ControlFlow::Continue(_)) => {} + } + + self.resolve_ident_in_module_globs_unadjusted( + module, + ident, + ns, + parent_scope, + shadowing, + finalize, + ignore_binding, + ignore_import, + ) + } + + /// Attempts to resolve `ident` in namespace `ns` of non-glob bindings in `module`. + fn resolve_ident_in_module_non_globs_unadjusted<'r>( + mut self: CmResolver<'r, 'ra, 'tcx>, + module: Module<'ra>, + ident: Ident, + ns: Namespace, + parent_scope: &ParentScope<'ra>, + shadowing: Shadowing, + finalize: Option, + ignore_binding: Option>, + ignore_import: Option>, ) -> Result, ControlFlow> { let key = BindingKey::new(ident, ns); // `try_borrow_mut` is required to ensure exclusive access, even if the resulting binding @@ -954,13 +994,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { .try_borrow_mut_unchecked() .map_err(|_| ControlFlow::Continue(Determined))?; - // If the primary binding is unusable, search further and return the shadowed glob - // binding if it exists. What we really want here is having two separate scopes in - // a module - one for non-globs and one for globs, but until that's done use this - // hack to avoid inconsistent resolution ICEs during import validation. - let binding = [resolution.non_glob_binding, resolution.glob_binding] - .into_iter() - .find_map(|binding| if binding == ignore_binding { None } else { binding }); + let binding = resolution.non_glob_binding.filter(|b| Some(*b) != ignore_binding); if let Some(finalize) = finalize { return self.get_mut().finalize_module_binding( @@ -974,19 +1008,67 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { ); } - let check_usable = |this: CmResolver<'r, 'ra, 'tcx>, binding: NameBinding<'ra>| { - let usable = this.is_accessible_from(binding.vis, parent_scope.module); - if usable { Ok(binding) } else { Err(ControlFlow::Break(Determined)) } - }; - // Items and single imports are not shadowable, if we have one, then it's determined. - if let Some(binding) = binding - && !binding.is_glob_import() - { - return check_usable(self, binding); + if let Some(binding) = binding { + let accessible = self.is_accessible_from(binding.vis, parent_scope.module); + return if accessible { Ok(binding) } else { Err(ControlFlow::Break(Determined)) }; + } + + // Check if one of single imports can still define the name, block if it can. + if self.reborrow().single_import_can_define_name( + &resolution, + None, + ns, + ignore_import, + ignore_binding, + parent_scope, + ) { + return Err(ControlFlow::Break(Undetermined)); + } + + // Check if one of unexpanded macros can still define the name. + if !module.unexpanded_invocations.borrow().is_empty() { + return Err(ControlFlow::Continue(Undetermined)); } - // --- From now on we either have a glob resolution or no resolution. --- + // No resolution and no one else can define the name - determinate error. + Err(ControlFlow::Continue(Determined)) + } + + /// Attempts to resolve `ident` in namespace `ns` of glob bindings in `module`. + fn resolve_ident_in_module_globs_unadjusted<'r>( + mut self: CmResolver<'r, 'ra, 'tcx>, + module: Module<'ra>, + ident: Ident, + ns: Namespace, + parent_scope: &ParentScope<'ra>, + shadowing: Shadowing, + finalize: Option, + ignore_binding: Option>, + ignore_import: Option>, + ) -> Result, ControlFlow> { + let key = BindingKey::new(ident, ns); + // `try_borrow_mut` is required to ensure exclusive access, even if the resulting binding + // doesn't need to be mutable. It will fail when there is a cycle of imports, and without + // the exclusive access infinite recursion will crash the compiler with stack overflow. + let resolution = &*self + .resolution_or_default(module, key) + .try_borrow_mut_unchecked() + .map_err(|_| ControlFlow::Continue(Determined))?; + + let binding = resolution.glob_binding.filter(|b| Some(*b) != ignore_binding); + + if let Some(finalize) = finalize { + return self.get_mut().finalize_module_binding( + ident, + binding, + if resolution.non_glob_binding.is_some() { resolution.glob_binding } else { None }, + parent_scope, + module, + finalize, + shadowing, + ); + } // Check if one of single imports can still define the name, // if it can then our result is not determined and can be invalidated. @@ -1014,21 +1096,19 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { // and prohibit access to macro-expanded `macro_export` macros instead (unless restricted // shadowing is enabled, see `macro_expanded_macro_export_errors`). if let Some(binding) = binding { - if binding.determined() || ns == MacroNS || shadowing == Shadowing::Restricted { - return check_usable(self, binding); + return if binding.determined() || ns == MacroNS || shadowing == Shadowing::Restricted { + let accessible = self.is_accessible_from(binding.vis, parent_scope.module); + if accessible { Ok(binding) } else { Err(ControlFlow::Break(Determined)) } } else { - return Err(ControlFlow::Break(Undetermined)); - } + Err(ControlFlow::Break(Undetermined)) + }; } - // --- From now on we have no resolution. --- - // Now we are in situation when new item/import can appear only from a glob or a macro // expansion. With restricted shadowing names from globs and macro expansions cannot // shadow names from outer scopes, so we can freely fallback from module search to search // in outer scopes. For `resolve_ident_in_scope_set` to continue search in outer // scopes we return `Undetermined` with `ControlFlow::Continue`. - // Check if one of unexpanded macros can still define the name, // if it can then our "no resolution" result is not determined and can be invalidated. if !module.unexpanded_invocations.borrow().is_empty() {