Skip to main content

big_code_analysis_ast/getter/
c.rs

1//! `Getter` implementation for C.
2#![allow(clippy::wildcard_imports, clippy::enum_glob_use)]
3
4use super::*;
5use crate::c_declarator::declarator_name;
6
7impl Getter for CCode {
8    fn get_func_space_name<'a, 'tree>(
9        node: &Node<'tree>,
10        code: &'a [u8],
11        _ancestors: Ancestors<'tree, '_>,
12    ) -> Option<&'a str> {
13        // Issue #285 contract: every `C::FunctionDefinition*` alias must
14        // be enumerated here AND in `get_space_kind` below AND in
15        // `is_func` / `is_func_space` (see `src/checker.rs`). C has no
16        // C++ name forms (operator-cast / destructor / operator / qualified
17        // / template names), so the declarator name is a plain identifier.
18        match node.kind_id().into() {
19            C::FunctionDefinition | C::FunctionDefinition2 => {
20                // The name is not a child of the function node — see
21                // `crate::c_declarator` (#1208).
22                if let Some(name) = declarator_name::<Self>(node)
23                    && matches!(
24                        name.kind_id().into(),
25                        C::TypeIdentifier | C::Identifier | C::FieldIdentifier
26                    )
27                {
28                    return node_text(code, &name);
29                }
30            }
31            _ => {
32                if let Some(name) = node.child_by_field_name("name") {
33                    return node_text(code, &name);
34                }
35            }
36        }
37        None
38    }
39
40    fn get_space_kind(node: &Node) -> SpaceKind {
41        use C::*;
42
43        // C has no classes/namespaces, and struct/union/enum hold no
44        // functions, so the only spaces are functions and the unit
45        // (matching `is_func_space` in `src/checker.rs`, #285).
46        match node.kind_id().into() {
47            FunctionDefinition | FunctionDefinition2 => SpaceKind::Function,
48            TranslationUnit => SpaceKind::Unit,
49            _ => SpaceKind::Unknown,
50        }
51    }
52
53    fn get_op_type<'a>(node: &Node<'a>, _ancestors: Ancestors<'a, '_>) -> TokenRole {
54        use C::*;
55
56        // C's operator alphabet is the C++ set minus the C++-only forms
57        // (`.*` / `->*`, `new` / `delete`, `try` / `catch` / `throw`,
58        // `<=>`). Raw string literals, the `namespace`-qualified
59        // identifier and template lists likewise do not exist in C, so
60        // nor does the C++ getters' `GT2` arm — a defensive one even
61        // there, since the runtime folds the template-list closer onto
62        // `GT` (`cpp_template_closer_alias_never_reaches_kind_id`).
63        // `LPAREN2` is a defensive arm (collapsed to `LPAREN` before
64        // `kind_id()`; #768, see the Cpp note).
65        match node.kind_id().into() {
66            DOT | LPAREN | LPAREN2 | COMMA | STAR | GTGT | COLON | SEMI | Return | Break
67            | Continue | If | Else | Switch | Case | Default | For | While | Goto | Do | EQ
68            | AMPAMP | PIPEPIPE | DASH | DASHDASH | DASHGT | PLUS | PLUSPLUS | SLASH | PERCENT
69            | PIPE | AMP | LTLT | TILDE | LT | LTEQ | EQEQ | BANGEQ | GTEQ | GT | PLUSEQ
70            | DASHEQ | BANG | STAREQ | SLASHEQ | PERCENTEQ | GTGTEQ | LTLTEQ | AMPEQ | CARET
71            | CARETEQ | PIPEEQ | LBRACK | LBRACE | QMARK | PrimitiveType
72            | TypeSpecifier | Sizeof
73            // A `sized_type_specifier` carries its `unsigned`/`signed`/`long`/
74            // `short` modifiers as bare keyword tokens, not as `primitive_type`
75            // children (`unsigned int` is `unsigned` + `primitive_type int`;
76            // `signed long` and `long long` have no `primitive_type` at all).
77            // Without these arms the modifiers fall into `Unknown` and are
78            // dropped, so `unsigned int` collapses to just `int` and a standalone
79            // `signed long` contributes nothing to n1/N1 (issue #466). Each
80            // modifier has a distinct kind_id, so keying by kind_id (the default
81            // `operators` store) keeps them distinct in n1 while `long long`'s
82            // two `long` tokens correctly fold to one n1 entry but two N1 hits.
83            | Signed | Unsigned | Long | Short
84            // `struct` / `union` / `enum`, which C++ bills since #1552,
85            // and `typedef` since #1557. They are C keywords, not
86            // C++-only forms, so leaving them out here would score a
87            // header differently by the grammar that parsed it. The
88            // derivation is on the Cpp arm.
89            | Struct | Union | Enum | Typedef
90            // Storage classes, function specifiers and type qualifiers,
91            // billed as every class language bills its modifiers
92            // (#1578). Each is the keyword leaf of an unclassified
93            // `storage_class_specifier` / `type_qualifier` /
94            // `alignas_qualifier`, so it bills once, as `const` does in
95            // a pointer declarator (`int * const p`). The vendor
96            // spellings (`__inline`, `__restrict__`, `__thread`,
97            // `__volatile__`, …) are kinds of their own and bill under
98            // their own name; `__volatile__` the grammar emits only as a
99            // GNU `asm` qualifier, where `volatile` bills too. `auto`
100            // is a storage class in C, the only place this grammar
101            // emits it. `__extension__` bills in an expression too,
102            // where it is the prefix of an `extension_expression`.
103            // Attributes (`__attribute__`, `__declspec`, `[[…]]`) and
104            // the MS calling conventions stay unbilled: they annotate a
105            // declaration rather than spell part of it. `cpp.rs` copies
106            // this list, though `restrict` and `noreturn` are no C++
107            // keywords: `int restrict = 0;` is a valid C++ variable, as
108            // `int noreturn = 1;` is in C without `<stdnoreturn.h>`. Both
109            // grammars still parse the name as the qualifier before a
110            // zero-width `identifier`, so the keyword bills and no
111            // operand does — the grammar decides that, not this arm, as
112            // `objc.rs` notes for `nullable`.
113            | Static | Extern | Register | Auto | Inline | Inline2 | Inline3 | Forceinline
114            | ThreadLocal | Thread | Const | Constexpr | Volatile | Volatile2 | Restrict | Restrict2
115            | Atomic | Noreturn | Noreturn2 | Nonnull | Alignas | Alignas2 | Extension
116                => TokenRole::Operator,
117            // `CharLiteral` joins the operand list here for the whole
118            // C family — `cpp.rs`, `mozcpp.rs` and `objc.rs` carry the
119            // same arm and point back at this note (#1316). Before it a
120            // character literal contributed *nothing*: not an operator
121            // (correct) and not an operand (wrong), so `char b = 'x';`
122            // scored `b` alone while Rust, Java, Kotlin, C#, Go and
123            // Elixir all counted their character literal.
124            //
125            // Listing the wrapper is safe against the grammar-dispatch
126            // section 5 double count because none of `char_literal`'s
127            // children is classified: the opening delimiter (`'`, and
128            // the `L'` / `u'` / `U'` / `u8'` prefixed spellings, each a
129            // distinct kind), the closing `'`, and the `character` /
130            // `escape_sequence` payload are all absent from every arm
131            // in this match. So a literal bills exactly one operand
132            // however many `character` leaves it holds — a multi-char
133            // constant like `'ab'` has two and still counts once.
134            //
135            // Operands are keyed by source text (`get_operand_id`), so
136            // `'x'` and `L'x'` are separate vocabulary entries while a
137            // repeated `'x'` folds to one `n2` with two `N2` hits.
138            //
139            // `Checker::is_string` deliberately does **not** grow a
140            // `CharLiteral` arm to match: a char is not a string, the
141            // same split Rust and Go apply to their char / rune
142            // literals. The alterator flattens `CharLiteral` in all four
143            // languages and records that decision alongside each arm;
144            // `c_family_char_literal_is_not_a_string` in `checker.rs`
145            // pins it both ways.
146            Identifier | TypeIdentifier | FieldIdentifier | StringLiteral | CharLiteral
147            | NumberLiteral | True | False | Null | DOTDOTDOT => TokenRole::Operand,
148            _ => TokenRole::Unknown,
149        }
150    }
151
152    get_operator!(C);
153}