Skip to content

Rust: a call inside a macro invocation (format!, assert_eq!, vec!, json!, write!) gives no call edge #3251

Open

Description

@Egrn

Environment

codegraph 1.6.2 from npm, and current main (f72c9d2) built from source and run on Node 22.23.4. Linux, rustc 1.99.1, fresh codegraph init. The repro crate passes cargo check.

Problem

A call written inside a macro invocation gets no edge. The same call outside a macro links fine. In our crate (42 files) this is 18 missed call edges, all confirmed by rust-analyzer: format!, assert_eq!, vec!, serde_json::json!, tokio::select!. write! misses the same way. Same on 1.6.2 and on current main.

A matches! pattern with | gives no reference either: Code::is_known has no edge to Code. With a single variant (matches!(c, Code::Bad) in variant_in_macro) the reference is there.

Minimal repro

Cargo.toml:

[package]
name = "repro_macro"
version = "0.1.0"
edition = "2021"

[dependencies]
serde_json = "1"

src/main.rs:

use serde_json::json;
use std::fmt;

pub enum Code {
    Bad,
    Good,
}

impl Code {
    pub fn is_known(self) -> bool {
        matches!(self, Code::Bad | Code::Good)
    }

    pub fn as_str(&self) -> &'static str {
        match self {
            Code::Bad => "bad",
            Code::Good => "good",
        }
    }
}

pub struct Error {
    code: Code,
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "error {}", self.code.as_str())
    }
}

fn label() -> String {
    "x".to_string()
}

fn count() -> usize {
    3
}

fn size() -> usize {
    2
}

fn in_format() -> String {
    format!("label {}", label())
}

fn in_assert() {
    assert_eq!(count(), 3);
}

fn in_vec() -> Vec<bool> {
    vec![false; size()]
}

fn in_json() -> serde_json::Value {
    json!({"label": label(), "count": count()})
}

fn variant_in_macro(c: &Code) -> bool {
    matches!(c, Code::Bad)
}

fn plain() -> String {
    label()
}

fn main() {
    let e = Error { code: Code::Good };
    println!("{e} {} {} {:?}", in_format(), plain(), in_vec());
    in_assert();
    let _ = in_json();
    let _ = variant_in_macro(&Code::Bad);
    let _ = Code::Good.is_known();
}

Actual

Edges in .codegraph/codegraph.db for these functions (1.6.2 and main give the same):

plain            -> label   resolvedBy exact-match, confidence 0.9, refName label   (ok, calls, no macro)
variant_in_macro -> Code    resolvedBy exact-match, confidence 0.9, refName Code    (ok, references)

No edge at all from in_format, in_assert, in_vec, in_json, Error::fmt and Code::is_known.

Expected

  • in_format -> label, in_assert -> count, in_vec -> size, in_json -> label and count, Error::fmt -> Code::as_str (calls).
  • Code::is_known -> Code (references), same as variant_in_macro.

Notes

I think the macro arguments stay a token_tree in tree-sitter-rust and nothing walks it. For these std macros the arguments are plain expressions (comma separated, key: value in json!), so reading the token tree as expressions would cover most of them. rust-analyzer resolves all of these calls.

Related, I checked before posting, not duplicates: #2328 / #2354 (a variant path references its enum — works outside macros and in matches! without |), #1838 (C++, a macro invocation taken as a call — other language and other direction).

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions