Skip to content

False positive E0109 due to incorrect operator precedence in macro expansion #20958

@arpie-steele

Description

@arpie-steele

Summary

rust-analyzer reports E0109 "type arguments are not allowed on this type" for valid macro code that compiles successfully with rustc. The issue stems from how rust-analyzer handles operator precedence in macro expansion, particularly with the as cast operator followed by shift operators.

Environment

  • rust-analyzer version: 0.3.2660-standalone (7c810e9 2025-10-27)
  • VSCode extension: rust-lang.rust-analyzer-0.3.2660-darwin-arm64
  • Rust version: 1.77 (edition 2018)

Minimal Reproducible Example

Repository: https://github.com/arpie-steele/test-e0109

pub trait UnsignedBits<U, const N: usize> {
    fn mask() -> U;
}

macro_rules! ub_prim_impl {
    ($ub:ident; $prim_type:ty, $n:literal; $max_n:literal) => {
        impl $ub<$prim_type, $n> for $prim_type {
            fn mask() -> $prim_type {
                assert!($n <= $max_n);
                if $n == $max_n {
                    return <$prim_type>::MAX;
                }
                // This compiles with rustc but triggers E0109 in rust-analyzer
                (1 as $prim_type << (1 << $n)) - 1
            }
        }
    };
}

// E0109 errors reported by rust-analyzer on these lines:
ub_prim_impl!(UnsignedBits; u8, 0; 3);
ub_prim_impl!(UnsignedBits; u8, 1; 3);
ub_prim_impl!(UnsignedBits; u8, 3; 3);

// Manual expansion (what the macro produces) - compiles fine with rustc:
impl UnsignedBits<u8, 2> for u8 {
    fn mask() -> u8 {
        assert!(2 <= 3);
        if 2 == 3 {
            return <u8>::MAX;
        }
        // Outside the macro, parentheses are required around the cast
        ((1 as u8) << (1 << 2)) - 1
    }
}

Root Cause Analysis

The expression 1 as $prim_type << (1 << $n) demonstrates the issue:

  1. rustc behavior: Pre-parses the macro body as tokens and correctly applies operator precedence. The as cast binds tighter than <<, so this is equivalent to (1 as $prim_type) << (1 << $n).

  2. rust-analyzer behavior: Appears to process macro expansion more like string substitution. When it sees $prim_type replaced with u8, followed by <<, it interprets this as attempting to apply type arguments to u8 (i.e., u8<< ...), which triggers E0109.

  3. Evidence: Outside a macro, the compiler requires explicit parentheses: ((1 as u8) << (1 << 2)) to disambiguate.

Expected Behavior

No diagnostic error. The code compiles and runs successfully:

  • cargo +1.77 check
  • cargo +1.77 run
  • cargo +1.77 test

Actual Behavior

rust-analyzer reports E0109 on macro invocation lines (42-44 in the reproduction).

Workaround

Either:

  1. Add extra parentheses in the macro: ((1 as $prim_type) << (1 << $n)) - 1
  2. Suppress the warning: #[cfg_attr(rust_analyzer, allow(E0109))]

Impact

This affects macros that:

  • Generate trait implementations with const generics
  • Use type parameters in expressions with operators (especially as followed by <<)
  • Rely on Rust's standard operator precedence rules

Related Information

Metadata

Metadata

Assignees

Labels

A-macromacro expansionC-bugCategory: bug

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions