Skip to content

Built-in macros, derives and attributes have inconsistent interactions with macros of the same name. #52269

Closed

Description

Macros currently occupy a single namespace, regardless of whether they are used for attributes, derives, or function-like macros. This was a design decision for procedural macros, on the basis that it is easier to split them into separate namespaces later, but it leads to some confusing interactions with built-in attributes (like cfg or derive), macros (like compile_error or env) or derives (like Eq or Clone). The behaviour should be tightened up so that it is intuitive and straightforward to explain.


Currently, the rules appear to be as follows:

  • Macros can be defined that have the same name as a built-in attribute or macro. When used, the builtin takes precedence and the macro is ignored. If the macro is used in a different context, it is found and works normally (e.g. a function-like macro named derive can be called, or an attribute named compile_error can be used). Raw identifiers can be used to get around the built-in.
  • Macros can be defined that have the same name as a built-in derive, except with proc_macro_derive. If a macro is defined with the same name as a built-in derive, it takes precedence. Defining a macro with proc_macro_derive that conflicts with the built-in gives an error at the definition.

If use_extern_macro is enabled, then you can freely rename imports. This lets you get around the proc_macro_derive rule:

#![feature(use_extern_macro)]

use failure::Fail as Clone;
use std::fmt::{Display, Formatter};

#[derive(Debug, Clone)]
struct Foo {
}

impl Display for Foo {
    fn fmt(&self, fmt : &mut Formatter) -> Result<(), std::fmt::Error> {
        write!(fmt, "{:?}", self)
    }
}

fn main() {
    let _ = Foo{}.cause();
}

Note that this behaviour applies only to built-in macros. Macros defined in libstd or libcore (for no_std) are imported in the prelude and can be overridden as one would expect for prelude names.


I do not think we can get away with treating all builtins as if they are normal names defined in the prelude that can be overridden. While it might be possible to treat some of them that way, the cfg attribute is an excellent example of one that cannot be safely modified, or else the following program would be problematic, because macro imports apply throughout the entire source file, not just after the import:

#[cfg(target_os = "linux")]
use some_crate::some_macro as cfg;

At the same time, I don't see why built-in derives should be special. The traits they implement are not, after all; even the ones like Copy which are magical. And it is possible, though inadvisable, to shadow them independently: trait Copy {} won't prevent #[derive(Copy)] from working, for instance.

This implies to me that we should have the following in the macro namespace:

  1. Built-ins which are truly and deeply magical, and cannot safely be overridden. Their names are treated like keywords in the macro namespace, in that the same non-raw identifier cannot be used at all.
  2. Intrinsics which are implemented by the compiler, but defined and named normally, like existing lang items and intrinsic functions.

This would imply the following behaviour changes:

  1. proc_macro_derive can declare derives with the same name as an intrinsic derive; these will simply shadow the intrinsic.
  2. Built-in function-like macros and attributes are always referred to when their names are used in the macro namespace, even in the wrong context, unless a raw identifier is used.
  3. Macros of any kind cannot be declared with the same name as a built-in macro or attribute unless a raw identifier is used.
  4. Going forward, we could possibly look at converting some built-in attributes and macros into intrinsics so that their names are freed up.

This leaves an unresolved question around renaming imports (e.g. use foo::bar as cfg; where foo::bar is names both a function and macro; is this an error? warning? or it just silently hides the macro as r#cfg?) but would clean up the bulk of the issues with the situation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

Labels

A-macros-1.2Area: Declarative macros 1.2

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions