big_code_analysis_ast/getter/objc.rs
1//! `Getter` implementation for Objective-C.
2#![allow(clippy::wildcard_imports, clippy::enum_glob_use)]
3
4use super::*;
5use crate::c_declarator::declarator_name;
6
7// ObjC has no templates, but four nodes delimit a generic list with a
8// bare `<` / `>`: `generic_specifier` (`NSArray<NSString *>`),
9// `protocol_reference_list` (`id<NSCopying>`), `parameterized_arguments`
10// (`@interface A : NSObject <NSCopying>`), and `argument_list`, whose
11// `Type<…>` alternative (`f(NSArray<NSString *>)`) holds the brackets as
12// direct children. A well-formed comparison's `<` / `>` is the child of
13// a `binary_expression` or `preproc_binary_expression`; the one
14// exception is `objc_argument_list_misparse`. `LT2` is the
15// `token.immediate` `<` that `argument_list`'s form opens with; the
16// parser reports it as `LT`, so it is listed defensively, here and in
17// the operator arm, and its absence is pinned by
18// `objc_generic_opener_alias_never_reaches_kind_id`. The call's
19// `argument_list` is `ArgumentList2`; `ArgumentList` is
20// `preproc_call_expression`'s alias, which holds no angle brackets.
21const GENERIC_ANGLES: GenericAngleKinds = GenericAngleKinds {
22 lists: &[
23 Objc::GenericSpecifier as u16,
24 Objc::ProtocolReferenceList as u16,
25 Objc::ParameterizedArguments as u16,
26 Objc::ArgumentList2 as u16,
27 ],
28 openers: &[Objc::LT as u16, Objc::LT2 as u16],
29 closers: &[Objc::GT as u16],
30 is_misparse: objc_argument_list_misparse,
31};
32
33// Whether `list` is an `argument_list` holding two comparisons that
34// tree-sitter-objc read as its `Type<…>` form: an unspaced
35// `g(a<b, c>d)`, where a `<` glued to the first argument opens the form.
36// The real form is the whole argument list, so `f(NSArray<NSString *>)`
37// parses cleanly, while the misparse leaves an `ERROR` after the `>`, or
38// a MISSING `)` (`g(i<n, j>=0)`) that only `has_error` sees.
39fn objc_argument_list_misparse(list: &Node, _ancestors: Ancestors) -> bool {
40 list.kind_id() == Objc::ArgumentList2 as u16 && list.has_error()
41}
42
43impl Getter for ObjcCode {
44 fn get_func_space_name<'a, 'tree>(
45 node: &Node<'tree>,
46 code: &'a [u8],
47 _ancestors: Ancestors<'tree, '_>,
48 ) -> Option<&'a str> {
49 // Issue #285 contract: every `Objc::FunctionDefinition*` alias
50 // must be enumerated here AND in `get_space_kind` below AND in
51 // `is_func` / `is_func_space` (see `src/checker.rs`). Free
52 // functions reach their name through the C declarator chain; the
53 // ObjC containers (`method_definition`, `class_interface` /
54 // `class_implementation`, `protocol_declaration`) carry no `name`
55 // field, so their name is the first `identifier` child — the
56 // class / protocol name, or a method's first selector keyword.
57 match node.kind_id().into() {
58 Objc::FunctionDefinition | Objc::FunctionDefinition2 => {
59 // The name is not a child of the function node — see
60 // `crate::c_declarator` (#1208).
61 if let Some(name) = declarator_name::<Self>(node)
62 && matches!(
63 name.kind_id().into(),
64 Objc::TypeIdentifier | Objc::Identifier | Objc::FieldIdentifier
65 )
66 {
67 return node_text(code, &name);
68 }
69 }
70 Objc::MethodDefinition
71 | Objc::ClassInterface
72 | Objc::ClassImplementation
73 | Objc::ProtocolDeclaration => {
74 if let Some(ident) = node.first_child(|id| Objc::Identifier == id) {
75 return node_text(code, &ident);
76 }
77 }
78 _ => {
79 if let Some(name) = node.child_by_field_name("name") {
80 return node_text(code, &name);
81 }
82 }
83 }
84 None
85 }
86
87 fn get_space_kind(node: &Node) -> SpaceKind {
88 use Objc::*;
89
90 // `@interface` / `@protocol` declare members without bodies →
91 // `Interface`; `@implementation` carries the method bodies →
92 // `Class`; free functions and `method_definition`s are
93 // `Function` spaces. ObjC blocks (`^{ … }`) are closures counted
94 // by `nom` rather than their own space, mirroring the C++ lambda
95 // (so they fall through to `Unknown` here). Keep the
96 // `FunctionDefinition*` aliases listed (#285).
97 match node.kind_id().into() {
98 FunctionDefinition | FunctionDefinition2 | MethodDefinition => SpaceKind::Function,
99 ClassImplementation => SpaceKind::Class,
100 ClassInterface | ProtocolDeclaration => SpaceKind::Interface,
101 TranslationUnit => SpaceKind::Unit,
102 _ => SpaceKind::Unknown,
103 }
104 }
105
106 fn get_op_type<'a>(node: &Node<'a>, ancestors: Ancestors<'a, '_>) -> TokenRole {
107 use Objc::*;
108
109 if GENERIC_ANGLES.is_closer(node, ancestors) {
110 return TokenRole::Unknown;
111 }
112
113 // ObjC is C plus message sends, blocks, and the `@`-directives.
114 // The operator alphabet is therefore the C set (`src/getter.rs`
115 // `impl Getter for CCode`) extended with the ObjC structural
116 // keywords: fast-enumeration `in`, the boxing/`@`-literal marker
117 // `@`, the `@try` / `@catch` / `@finally` / `@throw` /
118 // `@synchronized` / `@autoreleasepool` control keywords, and the
119 // `@selector` / `@encode` compile-time directives. Each keeps a
120 // distinct kind_id, so keying by kind_id keeps them distinct in n1.
121 // `LPAREN2` is a defensive arm (collapsed to `LPAREN` before
122 // `kind_id()`; #768, see the Cpp note).
123 match node.kind_id().into() {
124 // `@"…"` is one `string_literal` holding its `@` as a child
125 // (tree-sitter-objc 3.0.2), unlike `@42` / `@[…]` / `@{…}`,
126 // where the `@` is a child of the `at_expression` /
127 // `array_literal` / `dictionary_literal` it boxes. The
128 // literal is already the operand, keyed by its whole text,
129 // so billing the marker as an operator too paid the same
130 // byte twice and planted a phantom `@` in n1 for a file
131 // whose only `@` was in NSString literals (grammar-dispatch
132 // §5, the compound-leaf guard).
133 AT if ancestors.parent_has_kind(node, StringLiteral as u16) => TokenRole::Unknown,
134 // The C operator set, then the ObjC-specific structural
135 // keywords / markers from `In` onwards.
136 //
137 // From `Struct` onwards come the type-declaration keywords
138 // (#1552): C's `struct` / `union` / `enum` and, since #1557,
139 // `typedef` (see `c.rs`), and ObjC's own class and protocol
140 // declarations. Each is the leaf of a `type_definition` /
141 // `class_interface` / `class_implementation` /
142 // `protocol_declaration`, none of which is classified, so a
143 // declaration bills its keyword once. `@class Fwd;` needs no
144 // arm: it parses as an `@` token, already billed here, and a
145 // separate `class` leaf, so the directive already bills one
146 // operator.
147 DOT | LPAREN | LPAREN2 | COMMA | STAR | GTGT | COLON | SEMI | Return | Break
148 | Continue | If | Else | Switch | Case | Default | For | While | Goto | Do | EQ
149 | AMPAMP | PIPEPIPE | DASH | DASHDASH | DASHGT | PLUS | PLUSPLUS | SLASH | PERCENT
150 | PIPE | AMP | LTLT | TILDE | LT | LT2 | LTEQ | EQEQ | BANGEQ | GTEQ | GT | PLUSEQ
151 | DASHEQ | BANG | STAREQ | SLASHEQ | PERCENTEQ | GTGTEQ | LTLTEQ | AMPEQ | CARET
152 | CARETEQ | PIPEEQ | LBRACK | LBRACE | QMARK | PrimitiveType | TypeSpecifier
153 | Sizeof | Signed | Unsigned | Long | Short | In | AT | ATtry | ATcatch | ATfinally
154 | ATthrow | ATsynchronized | ATautoreleasepool | ATselector | ATencode | Struct
155 | Union | Enum | Typedef | ATinterface | ATimplementation | ATprotocol => {
156 TokenRole::Operator
157 }
158 // `CharLiteral` — the full derivation lives on the same arm
159 // in `src/getter/c.rs` (#1316): the wrapper is the only
160 // classified node in a character literal, so it bills one
161 // operand per literal, keyed by text, and `Checker::is_string`
162 // deliberately stays without a `CharLiteral` arm.
163 Identifier | TypeIdentifier | FieldIdentifier | StringLiteral | CharLiteral
164 | NumberLiteral | True | False | Null | DOTDOTDOT => TokenRole::Operand,
165 _ => TokenRole::Unknown,
166 }
167 }
168
169 fn get_operator_spelling<'a>(
170 node: &Node<'a>,
171 ancestors: Ancestors<'a, '_>,
172 ) -> Option<&'static str> {
173 GENERIC_ANGLES.opener_spelling(node, ancestors)
174 }
175
176 get_operator!(Objc);
177}