big_code_analysis_ast/getter/cpp.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 CppCode {
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 `Cpp::FunctionDefinition*` alias
14 // must be enumerated here AND in `get_space_kind` below AND
15 // in `is_func` / `is_func_space` (see `src/checker.rs`).
16 // The aliased kind_ids 489/491/494 are not emitted by the
17 // currently pinned `tree-sitter-cpp` parse tables, so a
18 // dropped variant would silently fall through to the
19 // `_ => name-field` arm and yield the wrong name (or `None`).
20 match node.kind_id().into() {
21 Cpp::FunctionDefinition
22 | Cpp::FunctionDefinition2
23 | Cpp::FunctionDefinition3
24 | Cpp::FunctionDefinition4 => {
25 if let Some(op_cast) = node.first_child(|id| Cpp::OperatorCast == id) {
26 return node_text(code, &op_cast);
27 }
28 // The name is not a child of the function node — see
29 // `crate::c_declarator` (#1208).
30 if let Some(name) = declarator_name::<Self>(node)
31 && matches!(
32 name.kind_id().into(),
33 Cpp::TypeIdentifier
34 | Cpp::Identifier
35 | Cpp::FieldIdentifier
36 | Cpp::DestructorName
37 | Cpp::OperatorName
38 | Cpp::QualifiedIdentifier
39 | Cpp::QualifiedIdentifier2
40 | Cpp::QualifiedIdentifier3
41 | Cpp::QualifiedIdentifier4
42 | Cpp::TemplateFunction
43 | Cpp::TemplateMethod
44 )
45 {
46 return node_text(code, &name);
47 }
48 }
49 _ => {
50 if let Some(name) = node.child_by_field_name("name") {
51 return node_text(code, &name);
52 }
53 }
54 }
55 None
56 }
57
58 fn get_space_kind(node: &Node) -> SpaceKind {
59 use Cpp::*;
60
61 // Issue #285 contract: keep every `FunctionDefinition*` alias
62 // listed here — see the comment above `get_func_space_name`.
63 match node.kind_id().into() {
64 FunctionDefinition | FunctionDefinition2 | FunctionDefinition3
65 | FunctionDefinition4 => SpaceKind::Function,
66 StructSpecifier => SpaceKind::Struct,
67 ClassSpecifier => SpaceKind::Class,
68 NamespaceDefinition => SpaceKind::Namespace,
69 TranslationUnit => SpaceKind::Unit,
70 _ => SpaceKind::Unknown,
71 }
72 }
73
74 fn get_op_type<'a>(node: &Node<'a>, ancestors: Ancestors<'a, '_>) -> TokenRole {
75 use Cpp::*;
76
77 // `LPAREN2` here (and the `LBRACK2`/`LBRACK3` aliases in the
78 // Elixir/Ruby impls) is a defensive arm, not an active one: every
79 // grammar's `public_symbol_map` collapses the second-alias opener
80 // to its base before `Node::kind_id()` (`ts_node_symbol`) returns,
81 // so `kind_id()` never yields the alias id and the arm cannot fire
82 // for real source. It guards against a future grammar bump that
83 // drops that collapse — at which point the alias would also need
84 // folding to its pair glyph in `get_operator_id_as_str`. The
85 // invariant is pinned by `second_alias_opener_collapses_to_base_kind_id`
86 // in `metrics/halstead.rs` (issue #768).
87 match node.kind_id().into() {
88 // Raw-string delimiter punctuation. A `raw_string_literal`
89 // carries its `R"(` opener as a bare `LPAREN` child — the
90 // kind id a call or a grouping uses — so `R"(raw)"`
91 // reported a `()` operator with no call in the source
92 // (#1314, the C++ sibling of Elixir #1256 and Ruby/Perl
93 // #1312). The literal is already an operand (below), so the
94 // delimiter is suppressed exactly when its parent is that
95 // node — the compound-leaf guard of grammar-dispatch
96 // section 5. Verified across `R"(x)"`, the custom-delimiter
97 // `R"tag(x)tag"` (which adds a `raw_string_delimiter` but
98 // keeps the same `(`), and the `LR` / `u8R` prefixed forms;
99 // the closing `)` needs no arm because #695 dropped every
100 // closer from the operator set.
101 //
102 // Parent, not ancestor. That distinction is unobservable
103 // here — `raw_string_content` is a leaf, so no `LPAREN` is
104 // ever a deeper descendant of a raw string — but parent
105 // scoping is correct by construction and keeps this arm the
106 // same shape as its siblings. `mozcpp` carries the twin.
107 LPAREN
108 if ancestors.parent_has_kind(node, RawStringLiteral as u16) =>
109 {
110 TokenRole::Unknown
111 }
112 DOT | DOTSTAR | LPAREN | LPAREN2 | COMMA | STAR | GTGT | COLON | SEMI | Return
113 | Break | Continue | If | Else | Switch | Case | Default | For | While | Goto | Do
114 | Delete | New | Try | Try2 | Catch | Throw | EQ | AMPAMP | PIPEPIPE | DASH
115 | DASHDASH | DASHGT | DASHGTSTAR | PLUS | PLUSPLUS | SLASH | PERCENT | PIPE | AMP
116 | LTLT | TILDE | LT | LTEQ | EQEQ | BANGEQ | GTEQ | GT | GT2 | LTEQGT | PLUSEQ
117 | DASHEQ | BANG | STAREQ | SLASHEQ | PERCENTEQ | GTGTEQ | LTLTEQ | AMPEQ | CARET
118 | CARETEQ | PIPEEQ | LBRACK | LBRACE | QMARK | COLONCOLON | PrimitiveType
119 | TypeSpecifier | Sizeof
120 // A `sized_type_specifier` carries its `unsigned`/`signed`/`long`/
121 // `short` modifiers as bare keyword tokens, not as `primitive_type`
122 // children (`unsigned int` is `unsigned` + `primitive_type int`;
123 // `signed long` and `long long` have no `primitive_type` at all).
124 // Without these arms the modifiers fell into `Unknown` and were
125 // dropped, so `unsigned int` collapsed to just `int` and a standalone
126 // `signed long` contributed nothing to n1/N1 (issue #466). Each
127 // modifier has a distinct kind_id, so keying by kind_id (the default
128 // `operators` store) keeps them distinct in n1 while `long long`'s
129 // two `long` tokens correctly fold to one n1 entry but two N1 hits.
130 | Signed | Unsigned | Long | Short => TokenRole::Operator,
131 // `CharLiteral` — the full derivation lives on the same arm
132 // in `src/getter/c.rs` (#1316): the wrapper is the only
133 // classified node in a character literal, so it bills one
134 // operand per literal, keyed by text, and `Checker::is_string`
135 // deliberately stays without a `CharLiteral` arm.
136 //
137 // `This` was in *neither* arm until #1361, so a C++ `this`
138 // contributed nothing at all: not an operator and not an
139 // operand. Same shape as #1316, opposite of the #1351-#1355
140 // wrapper/leaf over-counts — the fix adds an arm rather than
141 // deleting one.
142 //
143 // Operand, not operator, on three grounds. **Structure**:
144 // `field_expression` is `<receiver> -> <field>`, and `p->x`
145 // already bills operands `p` / `x` around the `->` operator.
146 // Calling `this` an operator would make `this->x` a binary
147 // operator with one operand, and would score the identical
148 // AST differently for `this->x` than for `p->x`.
149 // **Role**: `this` is a pointer *rvalue* and stands exactly
150 // where a variable stands — `return this;`, `*this`,
151 // `f(this)`, which would otherwise pass an operator as a
152 // call argument — while `->` / `.` / `*` are the operators
153 // already counted acting on it. **Precedent**: eleven of the
154 // fourteen languages here bill their self-reference as an
155 // operand (Rust `Zelf`, Ruby `Zelf`, PHP's `$this`
156 // `variable_name`, the four JS-family `This` arms, and
157 // Python / ObjC / Groovy / Lua, whose grammars spell it a
158 // plain `identifier`), and the JS-family arm in
159 // `src/getter.rs` is the only *reasoned* one among them —
160 // its `MetaProperty` note calls `this` "one atomic
161 // operand". Java / C# / Kotlin disagree, but each
162 // swept `This` in as one entry in a run of keywords — under
163 // `// Operator: keywords`, `// Operator: other keywords` and
164 // `// Expression-keyword operators` respectively — grouping
165 // it by lexical class rather than by Halstead role; that
166 // three-way split is #1380, deliberately not settled here.
167 // Keeping C++ an operand also keeps it agreeing with ObjC,
168 // whose `self` is an operand and whose `.mm` files route to
169 // this very impl.
170 //
171 // Safe against a grammar-dispatch section 5 double count:
172 // `this` is a childless leaf in every position the grammar
173 // admits it — `this->x`, `(*this)`, `return this`, `f(this)`,
174 // the `[this]` and `[=, this]` lambda captures, a
175 // `decltype(this->x)` trailing return type, and a
176 // constructor body after a member-initialiser list — and no
177 // node that *contains* it (`field_expression`,
178 // `pointer_expression`, `lambda_capture_specifier`,
179 // `argument_list`) is classified, so it can double-count in
180 // neither direction. Neither generated enum aliases the rule
181 // (`This = 215` is the sole variant spelling `"this"` in
182 // `language_cpp.rs` and `language_mozcpp.rs`), so there is no
183 // `This2` for a single-variant arm to miss;
184 // `cpp_this_is_a_childless_unaliased_leaf` pins both facts.
185 //
186 // C++23's explicit object parameter (`int g(this S&& self)`)
187 // does *not* reach this arm: the pinned grammar cannot parse
188 // it and emits `type_identifier "this"` plus an `ERROR`. That
189 // spelling is already an operand via `TypeIdentifier`, so the
190 // construct is unaffected either way.
191 Identifier | TypeIdentifier | FieldIdentifier | RawStringLiteral | StringLiteral
192 | CharLiteral | NumberLiteral | True | False | Null | This | DOTDOTDOT => {
193 TokenRole::Operand
194 }
195 // A namespace identifier is an operand only where it
196 // *names* a namespace; the same kind also spells the
197 // qualifier in `ns::thing`, which the final arm leaves
198 // `Unknown` (#1096).
199 NamespaceIdentifier if ancestors.parent_has_kind(node, NamespaceDefinition as u16) => {
200 TokenRole::Operand
201 }
202 _ => TokenRole::Unknown,
203 }
204 }
205
206 get_operator!(Cpp);
207}