Skip to content

Commit bacf059

Browse files
authored
Rollup merge of #107880 - jieyouxu:issue-107563, r=petrochenkov
Lint ambiguous glob re-exports Attempts to fix #107563. We currently already emit errors for ambiguous re-exports when two names are re-exported *specifically*, i.e. not from glob exports. This PR attempts to emit deny-by-default lints for ambiguous glob re-exports.
2 parents acd7f87 + 1f67949 commit bacf059

11 files changed

+229
-27
lines changed

compiler/rustc_lint/src/context.rs

+4
Original file line numberDiff line numberDiff line change
@@ -910,6 +910,10 @@ pub trait LintContext: Sized {
910910
Applicability::MachineApplicable,
911911
);
912912
}
913+
BuiltinLintDiagnostics::AmbiguousGlobReexports { name, namespace, first_reexport_span, duplicate_reexport_span } => {
914+
db.span_label(first_reexport_span, format!("the name `{}` in the {} namespace is first re-exported here", name, namespace));
915+
db.span_label(duplicate_reexport_span, format!("but the name `{}` in the {} namespace is also re-exported here", name, namespace));
916+
}
913917
}
914918
// Rewrap `db`, and pass control to the user.
915919
decorate(db)

compiler/rustc_lint_defs/src/builtin.rs

+40
Original file line numberDiff line numberDiff line change
@@ -3230,6 +3230,45 @@ declare_lint! {
32303230
};
32313231
}
32323232

3233+
declare_lint! {
3234+
/// The `ambiguous_glob_reexports` lint detects cases where names re-exported via globs
3235+
/// collide. Downstream users trying to use the same name re-exported from multiple globs
3236+
/// will receive a warning pointing out redefinition of the same name.
3237+
///
3238+
/// ### Example
3239+
///
3240+
/// ```rust,compile_fail
3241+
/// #![deny(ambiguous_glob_reexports)]
3242+
/// pub mod foo {
3243+
/// pub type X = u8;
3244+
/// }
3245+
///
3246+
/// pub mod bar {
3247+
/// pub type Y = u8;
3248+
/// pub type X = u8;
3249+
/// }
3250+
///
3251+
/// pub use foo::*;
3252+
/// pub use bar::*;
3253+
///
3254+
///
3255+
/// pub fn main() {}
3256+
/// ```
3257+
///
3258+
/// {{produces}}
3259+
///
3260+
/// ### Explanation
3261+
///
3262+
/// This was previously accepted but it could silently break a crate's downstream users code.
3263+
/// For example, if `foo::*` and `bar::*` were re-exported before `bar::X` was added to the
3264+
/// re-exports, down stream users could use `this_crate::X` without problems. However, adding
3265+
/// `bar::X` would cause compilation errors in downstream crates because `X` is defined
3266+
/// multiple times in the same namespace of `this_crate`.
3267+
pub AMBIGUOUS_GLOB_REEXPORTS,
3268+
Warn,
3269+
"ambiguous glob re-exports",
3270+
}
3271+
32333272
declare_lint_pass! {
32343273
/// Does nothing as a lint pass, but registers some `Lint`s
32353274
/// that are used by other parts of the compiler.
@@ -3337,6 +3376,7 @@ declare_lint_pass! {
33373376
NAMED_ARGUMENTS_USED_POSITIONALLY,
33383377
IMPLIED_BOUNDS_ENTAILMENT,
33393378
BYTE_SLICE_IN_PACKED_STRUCT_WITH_DERIVE,
3379+
AMBIGUOUS_GLOB_REEXPORTS,
33403380
]
33413381
}
33423382

compiler/rustc_lint_defs/src/lib.rs

+10
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,16 @@ pub enum BuiltinLintDiagnostics {
529529
vis_span: Span,
530530
ident_span: Span,
531531
},
532+
AmbiguousGlobReexports {
533+
/// The name for which collision(s) have occurred.
534+
name: String,
535+
/// The name space for whihc the collision(s) occurred in.
536+
namespace: String,
537+
/// Span where the name is first re-exported.
538+
first_reexport_span: Span,
539+
/// Span where the same name is also re-exported.
540+
duplicate_reexport_span: Span,
541+
},
532542
}
533543

534544
/// Lints that are buffered up early on in the `Session` before the

compiler/rustc_resolve/src/effective_visibilities.rs

+50-18
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use rustc_ast::visit;
44
use rustc_ast::visit::Visitor;
55
use rustc_ast::Crate;
66
use rustc_ast::EnumDef;
7+
use rustc_data_structures::fx::FxHashSet;
78
use rustc_data_structures::intern::Interned;
89
use rustc_hir::def_id::LocalDefId;
910
use rustc_hir::def_id::CRATE_DEF_ID;
@@ -70,11 +71,11 @@ impl Resolver<'_, '_> {
7071
impl<'r, 'a, 'tcx> EffectiveVisibilitiesVisitor<'r, 'a, 'tcx> {
7172
/// Fills the `Resolver::effective_visibilities` table with public & exported items
7273
/// For now, this doesn't resolve macros (FIXME) and cannot resolve Impl, as we
73-
/// need access to a TyCtxt for that.
74+
/// need access to a TyCtxt for that. Returns the set of ambiguous re-exports.
7475
pub(crate) fn compute_effective_visibilities<'c>(
7576
r: &'r mut Resolver<'a, 'tcx>,
7677
krate: &'c Crate,
77-
) {
78+
) -> FxHashSet<Interned<'a, NameBinding<'a>>> {
7879
let mut visitor = EffectiveVisibilitiesVisitor {
7980
r,
8081
def_effective_visibilities: Default::default(),
@@ -93,18 +94,26 @@ impl<'r, 'a, 'tcx> EffectiveVisibilitiesVisitor<'r, 'a, 'tcx> {
9394
}
9495
visitor.r.effective_visibilities = visitor.def_effective_visibilities;
9596

97+
let mut exported_ambiguities = FxHashSet::default();
98+
9699
// Update visibilities for import def ids. These are not used during the
97100
// `EffectiveVisibilitiesVisitor` pass, because we have more detailed binding-based
98101
// information, but are used by later passes. Effective visibility of an import def id
99102
// is the maximum value among visibilities of bindings corresponding to that def id.
100103
for (binding, eff_vis) in visitor.import_effective_visibilities.iter() {
101104
let NameBindingKind::Import { import, .. } = binding.kind else { unreachable!() };
102-
if let Some(node_id) = import.id() {
103-
r.effective_visibilities.update_eff_vis(r.local_def_id(node_id), eff_vis, r.tcx)
105+
if !binding.is_ambiguity() {
106+
if let Some(node_id) = import.id() {
107+
r.effective_visibilities.update_eff_vis(r.local_def_id(node_id), eff_vis, r.tcx)
108+
}
109+
} else if binding.ambiguity.is_some() && eff_vis.is_public_at_level(Level::Reexported) {
110+
exported_ambiguities.insert(*binding);
104111
}
105112
}
106113

107114
info!("resolve::effective_visibilities: {:#?}", r.effective_visibilities);
115+
116+
exported_ambiguities
108117
}
109118

110119
/// Update effective visibilities of bindings in the given module,
@@ -115,21 +124,44 @@ impl<'r, 'a, 'tcx> EffectiveVisibilitiesVisitor<'r, 'a, 'tcx> {
115124
let resolutions = self.r.resolutions(module);
116125

117126
for (_, name_resolution) in resolutions.borrow().iter() {
118-
if let Some(mut binding) = name_resolution.borrow().binding() && !binding.is_ambiguity() {
119-
// Set the given effective visibility level to `Level::Direct` and
120-
// sets the rest of the `use` chain to `Level::Reexported` until
121-
// we hit the actual exported item.
122-
let mut parent_id = ParentId::Def(module_id);
123-
while let NameBindingKind::Import { binding: nested_binding, .. } = binding.kind {
124-
let binding_id = ImportId::new_unchecked(binding);
125-
self.update_import(binding_id, parent_id);
126-
127-
parent_id = ParentId::Import(binding_id);
128-
binding = nested_binding;
129-
}
127+
if let Some(mut binding) = name_resolution.borrow().binding() {
128+
if !binding.is_ambiguity() {
129+
// Set the given effective visibility level to `Level::Direct` and
130+
// sets the rest of the `use` chain to `Level::Reexported` until
131+
// we hit the actual exported item.
132+
let mut parent_id = ParentId::Def(module_id);
133+
while let NameBindingKind::Import { binding: nested_binding, .. } = binding.kind
134+
{
135+
let binding_id = ImportId::new_unchecked(binding);
136+
self.update_import(binding_id, parent_id);
137+
138+
parent_id = ParentId::Import(binding_id);
139+
binding = nested_binding;
140+
}
141+
142+
if let Some(def_id) = binding.res().opt_def_id().and_then(|id| id.as_local()) {
143+
self.update_def(def_id, binding.vis.expect_local(), parent_id);
144+
}
145+
} else {
146+
// Put the root ambiguity binding and all reexports leading to it into the
147+
// table. They are used by the `ambiguous_glob_reexports` lint. For all
148+
// bindings added to the table here `is_ambiguity` returns true.
149+
let mut parent_id = ParentId::Def(module_id);
150+
while let NameBindingKind::Import { binding: nested_binding, .. } = binding.kind
151+
{
152+
let binding_id = ImportId::new_unchecked(binding);
153+
self.update_import(binding_id, parent_id);
130154

131-
if let Some(def_id) = binding.res().opt_def_id().and_then(|id| id.as_local()) {
132-
self.update_def(def_id, binding.vis.expect_local(), parent_id);
155+
if binding.ambiguity.is_some() {
156+
// Stop at the root ambiguity, further bindings in the chain should not
157+
// be reexported because the root ambiguity blocks any access to them.
158+
// (Those further bindings are most likely not ambiguities themselves.)
159+
break;
160+
}
161+
162+
parent_id = ParentId::Import(binding_id);
163+
binding = nested_binding;
164+
}
133165
}
134166
}
135167
}

compiler/rustc_resolve/src/imports.rs

+31-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ use rustc_hir::def::{self, DefKind, PartialRes};
1919
use rustc_middle::metadata::ModChild;
2020
use rustc_middle::span_bug;
2121
use rustc_middle::ty;
22-
use rustc_session::lint::builtin::{PUB_USE_OF_PRIVATE_EXTERN_CRATE, UNUSED_IMPORTS};
22+
use rustc_session::lint::builtin::{
23+
AMBIGUOUS_GLOB_REEXPORTS, PUB_USE_OF_PRIVATE_EXTERN_CRATE, UNUSED_IMPORTS,
24+
};
2325
use rustc_session::lint::BuiltinLintDiagnostics;
2426
use rustc_span::edit_distance::find_best_match_for_name;
2527
use rustc_span::hygiene::LocalExpnId;
@@ -510,6 +512,34 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
510512
}
511513
}
512514

515+
pub(crate) fn check_reexport_ambiguities(
516+
&mut self,
517+
exported_ambiguities: FxHashSet<Interned<'a, NameBinding<'a>>>,
518+
) {
519+
for module in self.arenas.local_modules().iter() {
520+
module.for_each_child(self, |this, ident, ns, binding| {
521+
if let NameBindingKind::Import { import, .. } = binding.kind
522+
&& let Some((amb_binding, _)) = binding.ambiguity
523+
&& binding.res() != Res::Err
524+
&& exported_ambiguities.contains(&Interned::new_unchecked(binding))
525+
{
526+
this.lint_buffer.buffer_lint_with_diagnostic(
527+
AMBIGUOUS_GLOB_REEXPORTS,
528+
import.root_id,
529+
import.root_span,
530+
"ambiguous glob re-exports",
531+
BuiltinLintDiagnostics::AmbiguousGlobReexports {
532+
name: ident.to_string(),
533+
namespace: ns.descr().to_string(),
534+
first_reexport_span: import.root_span,
535+
duplicate_reexport_span: amb_binding.span,
536+
},
537+
);
538+
}
539+
});
540+
}
541+
}
542+
513543
fn throw_unresolved_import_error(&self, errors: Vec<(&Import<'_>, UnresolvedImportError)>) {
514544
if errors.is_empty() {
515545
return;

compiler/rustc_resolve/src/lib.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -1475,9 +1475,12 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
14751475
pub fn resolve_crate(&mut self, krate: &Crate) {
14761476
self.tcx.sess.time("resolve_crate", || {
14771477
self.tcx.sess.time("finalize_imports", || self.finalize_imports());
1478-
self.tcx.sess.time("compute_effective_visibilities", || {
1478+
let exported_ambiguities = self.tcx.sess.time("compute_effective_visibilities", || {
14791479
EffectiveVisibilitiesVisitor::compute_effective_visibilities(self, krate)
14801480
});
1481+
self.tcx.sess.time("check_reexport_ambiguities", || {
1482+
self.check_reexport_ambiguities(exported_ambiguities)
1483+
});
14811484
self.tcx.sess.time("finalize_macro_resolutions", || self.finalize_macro_resolutions());
14821485
self.tcx.sess.time("late_resolve_crate", || self.late_resolve_crate(krate));
14831486
self.tcx.sess.time("resolve_main", || self.resolve_main());

tests/ui/imports/auxiliary/glob-conflict.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#![allow(ambiguous_glob_reexports)]
2+
13
mod m1 {
24
pub fn f() {}
35
}

tests/ui/imports/local-modularized-tricky-fail-1.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#![feature(decl_macro)]
2+
#![allow(ambiguous_glob_reexports)]
23

34
macro_rules! define_exported { () => {
45
#[macro_export]

tests/ui/imports/local-modularized-tricky-fail-1.stderr

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
error[E0659]: `exported` is ambiguous
2-
--> $DIR/local-modularized-tricky-fail-1.rs:28:1
2+
--> $DIR/local-modularized-tricky-fail-1.rs:29:1
33
|
44
LL | exported!();
55
| ^^^^^^^^ ambiguous name
66
|
77
= note: ambiguous because of a conflict between a name from a glob import and a macro-expanded name in the same module during import or macro resolution
88
note: `exported` could refer to the macro defined here
9-
--> $DIR/local-modularized-tricky-fail-1.rs:5:5
9+
--> $DIR/local-modularized-tricky-fail-1.rs:6:5
1010
|
1111
LL | / macro_rules! exported {
1212
LL | | () => ()
@@ -16,23 +16,23 @@ LL | | }
1616
LL | define_exported!();
1717
| ------------------ in this macro invocation
1818
note: `exported` could also refer to the macro imported here
19-
--> $DIR/local-modularized-tricky-fail-1.rs:22:5
19+
--> $DIR/local-modularized-tricky-fail-1.rs:23:5
2020
|
2121
LL | use inner1::*;
2222
| ^^^^^^^^^
2323
= help: consider adding an explicit import of `exported` to disambiguate
2424
= note: this error originates in the macro `define_exported` (in Nightly builds, run with -Z macro-backtrace for more info)
2525

2626
error[E0659]: `panic` is ambiguous
27-
--> $DIR/local-modularized-tricky-fail-1.rs:35:5
27+
--> $DIR/local-modularized-tricky-fail-1.rs:36:5
2828
|
2929
LL | panic!();
3030
| ^^^^^ ambiguous name
3131
|
3232
= note: ambiguous because of a conflict between a macro-expanded name and a less macro-expanded name from outer scope during import or macro resolution
3333
= note: `panic` could refer to a macro from prelude
3434
note: `panic` could also refer to the macro defined here
35-
--> $DIR/local-modularized-tricky-fail-1.rs:11:5
35+
--> $DIR/local-modularized-tricky-fail-1.rs:12:5
3636
|
3737
LL | / macro_rules! panic {
3838
LL | | () => ()
@@ -45,15 +45,15 @@ LL | define_panic!();
4545
= note: this error originates in the macro `define_panic` (in Nightly builds, run with -Z macro-backtrace for more info)
4646

4747
error[E0659]: `include` is ambiguous
48-
--> $DIR/local-modularized-tricky-fail-1.rs:46:1
48+
--> $DIR/local-modularized-tricky-fail-1.rs:47:1
4949
|
5050
LL | include!();
5151
| ^^^^^^^ ambiguous name
5252
|
5353
= note: ambiguous because of a conflict between a macro-expanded name and a less macro-expanded name from outer scope during import or macro resolution
5454
= note: `include` could refer to a macro from prelude
5555
note: `include` could also refer to the macro defined here
56-
--> $DIR/local-modularized-tricky-fail-1.rs:17:5
56+
--> $DIR/local-modularized-tricky-fail-1.rs:18:5
5757
|
5858
LL | / macro_rules! include {
5959
LL | | () => ()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#![deny(ambiguous_glob_reexports)]
2+
3+
pub mod foo {
4+
pub type X = u8;
5+
}
6+
7+
pub mod bar {
8+
pub type X = u8;
9+
pub type Y = u8;
10+
}
11+
12+
pub use foo::*;
13+
//~^ ERROR ambiguous glob re-exports
14+
pub use bar::*;
15+
16+
mod ambiguous {
17+
mod m1 { pub type A = u8; }
18+
mod m2 { pub type A = u8; }
19+
pub use self::m1::*;
20+
//~^ ERROR ambiguous glob re-exports
21+
pub use self::m2::*;
22+
}
23+
24+
pub mod single {
25+
pub use ambiguous::A;
26+
//~^ ERROR `A` is ambiguous
27+
}
28+
29+
pub mod glob {
30+
pub use ambiguous::*;
31+
}
32+
33+
pub fn main() {}

0 commit comments

Comments
 (0)