rust-lang / rust-lang/rust-analyzer

False positive E0109 due to incorrect operator precedence in macro expansion

Open
#20,958 2 comments 0 reactions 1 assignee View on GitHub

@Aditya-PS-05 is already working on this.

Since Nov 23, 2025.

A-macro C-bug
Dominant language
Rust
Stars
16.9k
Forks
2.2k
Avg merge
1d 12h
Merged PRs (30d)
72

Description

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 (7c810e9994 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

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.