Skip to content

Commit 3241967

Browse files
committed
Permit duplicate macro imports
Consider a crate that depends on both `serde` (without the `derive` feature) and `serde_derive`, and imports `serde::Serialize` (a trait) and `serde_derive::Serialize` (a macro). Then, imagine some other crate in a build graph depends on `serde` *with* the `derive` feature; they import both the macro and trait simultaneously with `use serde::Serialize`. If duplicate imports of the same item are always forbidden, these crates cannot co-exist in the same build-graph; the former crate will fail to build, as its first import (which will now also import the `Serialize` macro) conflicts with its second import. This build hazard is confusing — the author of the second crate had no idea that their dependence on the `derive` feature might be problematic for other crates. The author of the first crate can mitigate the hazard by only glob-importing from proc-macro crates, but glob imports run against many's personal preference and tooling affordances (e.g., `rust-analyzer`'s auto-import feature). We mitigate this hazard across the ecosystem by permitting duplicate imports of macros. We don't limit this exception to proc macros, as it should not be a breaking change to rewrite a proc macro into a by-example macro. Although it would be semantically unproblematic to permit *all* duplicate imports (not just those of macros), other kinds of imports have not, in practice, posed the same hazard, and there might be cases we'd like to warn-by-default against. For now, we only permit duplicate macro imports. See https://rust-lang.zulipchat.com/#narrow/channel/213817-t-lang/topic/Allowing.20same-name.20imports.20of.20the.20same.20item/near/516777221
1 parent 414482f commit 3241967

File tree

2 files changed

+54
-3
lines changed

2 files changed

+54
-3
lines changed

compiler/rustc_resolve/src/imports.rs

+40-3
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
330330
self.set_binding_parent_module(binding, module);
331331
self.update_resolution(module, key, warn_ambiguity, |this, resolution| {
332332
if let Some(old_binding) = resolution.binding {
333-
if res == Res::Err && old_binding.res() != Res::Err {
333+
let old_res = old_binding.res();
334+
if res == Res::Err && old_res != Res::Err {
334335
// Do not override real bindings with `Res::Err`s from error recovery.
335336
return Ok(());
336337
}
@@ -347,7 +348,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
347348
// of whether they has same resolution or not when they are
348349
// imported from the same glob-import statement.
349350
resolution.binding = Some(binding);
350-
} else if res != old_binding.res() {
351+
} else if res != old_res {
351352
resolution.binding = Some(this.new_ambiguity_binding(
352353
AmbiguityKind::GlobVsGlob,
353354
old_binding,
@@ -395,7 +396,43 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
395396
}
396397
}
397398
(false, false) => {
398-
return Err(old_binding);
399+
// So long as two bindings resolve to the same item, it's semantically
400+
// unproblematic for them to have the same name, but still perhaps a code
401+
// smell — surely, duplicate imports of the same item with the same name
402+
// should be consolidated, right? In one common pattern in the ecosystem,
403+
// forbidding duplicate bindings creates a build hazard that is awkward to
404+
// resolve.
405+
//
406+
// Consider a crate that depends on both `serde` (without the `derive`
407+
// feature) and `serde_derive`, and imports `serde::Serialize` (a trait) and
408+
// `serde_derive::Serialize` (a macro). Then, imagine some other crate in a
409+
// build graph depends on `serde` *with* the `derive` feature; they import
410+
// both the macro and trait simultaneously with `use serde::Serialize`. If
411+
// duplicate imports of the same item are always forbidden, these crates
412+
// cannot co-exist in the same build-graph; the former crate will fail to
413+
// build, as its first import (which will now also import the `Serialize`
414+
// macro) conflicts with its second import.
415+
//
416+
// This build hazard is confusing — the author of the second crate had no
417+
// idea that their dependence on the `derive` feature might be problematic
418+
// for other crates. The author of the first crate can mitigate the hazard
419+
// by only glob-importing from proc-macro crates, but glob imports run
420+
// against many's personal preference and tooling affordances (e.g.,
421+
// `rust-analyzer`'s auto-import feature).
422+
//
423+
// We mitigate this hazard across the ecosystem by permitting duplicate
424+
// imports of macros. We don't limit this exception to proc macros, as it
425+
// should not be a breaking change to rewrite a proc macro into a by-example
426+
// macro. Although it would be semantically unproblematic to permit *all*
427+
// duplicate imports (not just those of macros), other kinds of imports have
428+
// not, in practice, posed the same hazard, and there might be cases we'd
429+
// like to warn-by-default against. For now, we only permit duplicate macro
430+
// imports.
431+
if res == old_res && res.macro_kind().is_some() {
432+
return Ok(());
433+
} else {
434+
return Err(old_binding);
435+
}
399436
}
400437
}
401438
} else {

tests/ui/proc-macro/duplicate.rs

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//@ check-pass
2+
//@ proc-macro: test-macros.rs
3+
#![crate_type = "lib"]
4+
5+
extern crate test_macros;
6+
7+
mod other {
8+
pub use test_macros::{Empty, empty, empty_attr};
9+
}
10+
11+
use test_macros::{Empty, empty, empty_attr};
12+
use other::{Empty, empty, empty_attr};
13+
14+
fn main() {}

0 commit comments

Comments
 (0)