Skip to main content

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
43// Whether `node` is the attribute word of `@property (nonatomic, copy,
44// getter=isOn)`. The grammar has no keyword for any attribute: each is
45// an `identifier`, the first child of its `property_attribute`, so the
46// word is told from a name by position alone. The `isOn` of
47// `getter=isOn` is a later child and stays an operand, the selector it
48// names. `ObjcCode::is_primitive` keys the word by its text (#1579).
49fn is_property_attribute_name(node: &Node, ancestors: Ancestors) -> bool {
50    ancestors.parent(node).is_some_and(|attribute| {
51        attribute.kind_id() == Objc::PropertyAttribute as u16
52            && attribute.start_byte() == node.start_byte()
53    })
54}
55
56impl Getter for ObjcCode {
57    fn get_func_space_name<'a, 'tree>(
58        node: &Node<'tree>,
59        code: &'a [u8],
60        _ancestors: Ancestors<'tree, '_>,
61    ) -> Option<&'a str> {
62        // Issue #285 contract: every `Objc::FunctionDefinition*` alias
63        // must be enumerated here AND in `get_space_kind` below AND in
64        // `is_func` / `is_func_space` (see `src/checker.rs`). Free
65        // functions reach their name through the C declarator chain; the
66        // ObjC containers (`method_definition`, `class_interface` /
67        // `class_implementation`, `protocol_declaration`) carry no `name`
68        // field, so their name is the first `identifier` child — the
69        // class / protocol name, or a method's first selector keyword.
70        match node.kind_id().into() {
71            Objc::FunctionDefinition | Objc::FunctionDefinition2 => {
72                // The name is not a child of the function node — see
73                // `crate::c_declarator` (#1208).
74                if let Some(name) = declarator_name::<Self>(node)
75                    && matches!(
76                        name.kind_id().into(),
77                        Objc::TypeIdentifier | Objc::Identifier | Objc::FieldIdentifier
78                    )
79                {
80                    return node_text(code, &name);
81                }
82            }
83            Objc::MethodDefinition
84            | Objc::ClassInterface
85            | Objc::ClassImplementation
86            | Objc::ProtocolDeclaration => {
87                if let Some(ident) = node.first_child(|id| Objc::Identifier == id) {
88                    return node_text(code, &ident);
89                }
90            }
91            _ => {
92                if let Some(name) = node.child_by_field_name("name") {
93                    return node_text(code, &name);
94                }
95            }
96        }
97        None
98    }
99
100    fn get_space_kind(node: &Node) -> SpaceKind {
101        use Objc::*;
102
103        // `@interface` / `@protocol` declare members without bodies →
104        // `Interface`; `@implementation` carries the method bodies →
105        // `Class`; free functions and `method_definition`s are
106        // `Function` spaces. ObjC blocks (`^{ … }`) are closures counted
107        // by `nom` rather than their own space, mirroring the C++ lambda
108        // (so they fall through to `Unknown` here). Keep the
109        // `FunctionDefinition*` aliases listed (#285).
110        match node.kind_id().into() {
111            FunctionDefinition | FunctionDefinition2 | MethodDefinition => SpaceKind::Function,
112            ClassImplementation => SpaceKind::Class,
113            ClassInterface | ProtocolDeclaration => SpaceKind::Interface,
114            TranslationUnit => SpaceKind::Unit,
115            _ => SpaceKind::Unknown,
116        }
117    }
118
119    fn get_op_type<'a>(node: &Node<'a>, ancestors: Ancestors<'a, '_>) -> TokenRole {
120        use Objc::*;
121
122        if GENERIC_ANGLES.is_closer(node, ancestors) {
123            return TokenRole::Unknown;
124        }
125
126        // ObjC is C plus message sends, blocks, and the `@`-directives.
127        // The operator alphabet is therefore the C set (`src/getter.rs`
128        // `impl Getter for CCode`) extended with the ObjC structural
129        // keywords: fast-enumeration `in`, the boxing/`@`-literal marker
130        // `@`, the `@try` / `@catch` / `@finally` / `@throw` /
131        // `@synchronized` / `@autoreleasepool` control keywords, and the
132        // `@selector` / `@encode` compile-time directives. Each keeps a
133        // distinct kind_id, so keying by kind_id keeps them distinct in n1.
134        // `LPAREN2` is a defensive arm (collapsed to `LPAREN` before
135        // `kind_id()`; #768, see the Cpp note).
136        match node.kind_id().into() {
137            // `@"…"` is one `string_literal` holding its `@` as a child
138            // (tree-sitter-objc 3.0.2), unlike `@42` / `@[…]` / `@{…}`,
139            // where the `@` is a child of the `at_expression` /
140            // `array_literal` / `dictionary_literal` it boxes. The
141            // literal is already the operand, keyed by its whole text,
142            // so billing the marker as an operator too paid the same
143            // byte twice and planted a phantom `@` in n1 for a file
144            // whose only `@` was in NSString literals (grammar-dispatch
145            // §5, the compound-leaf guard).
146            AT if ancestors.parent_has_kind(node, StringLiteral as u16) => TokenRole::Unknown,
147            // The C operator set, then the ObjC-specific structural
148            // keywords / markers from `In` onwards.
149            //
150            // From `Struct` onwards come the type-declaration keywords
151            // (#1552): C's `struct` / `union` / `enum` and, since #1557,
152            // `typedef` (see `c.rs`), and ObjC's own class and protocol
153            // declarations. Each is the leaf of a `type_definition` /
154            // `class_interface` / `class_implementation` /
155            // `protocol_declaration`, none of which is classified, so a
156            // declaration bills its keyword once. `@class Fwd;` needs no
157            // arm: it parses as an `@` token, already billed here, and a
158            // separate `class` leaf, so the directive already bills one
159            // operator.
160            //
161            // From `Static` onwards come the declaration modifiers
162            // (#1579), billed as every class language bills its
163            // modifiers: first C's storage classes, function specifiers
164            // and type qualifiers, the same list as `c.rs` (#1578), so a
165            // `.m` scores a C declaration as a `.c` does; then ObjC's
166            // own. Those are the ARC ownership qualifiers (`__weak`,
167            // `__strong`, `__block`, …) and `__bridge` casts, the
168            // nullability qualifiers in all three spellings
169            // (`nullable`, `_Nullable`, `__nullable`), `__kindof`, the
170            // `__covariant` / `__contravariant` variance of a generic
171            // parameter, the `_Complex` type keyword, which this grammar
172            // files as a qualifier, the `in` / `out` / `inout` /
173            // `bycopy` / `byref` / `oneway` method-type qualifiers (`in`
174            // already billed above), the `@public` / `@private` /
175            // `@protected` / `@package` ivar visibility labels and the
176            // `@optional` / `@required` protocol section labels. Each is
177            // the keyword leaf of an unclassified `storage_class_specifier`
178            // / `type_qualifier` / `protocol_qualifier` /
179            // `visibility_specification` or sits bare in its parent, so
180            // it bills once. None has a numeric-suffix alias.
181            //
182            // Left unbilled: the Apple SDK macros this grammar also lists
183            // as storage classes and qualifiers (`NS_INLINE`,
184            // `FOUNDATION_EXPORT`, `IBOutlet`, `__unused`,
185            // `__ptrauth_objc_*`, …), as Mozcpp's `MOZ_*` macros are,
186            // since each annotates a declaration and a `.c` file parses
187            // the same spelling as a name; and GNU's `__real` /
188            // `__imag`, complex-part operators the grammar misfiles as
189            // qualifiers. A variable named `nullable` parses as the
190            // qualifier before a MISSING name and bills it, as C's
191            // `int noreturn = 1;` bills `noreturn`: the grammar decides
192            // that, not this arm. A property attribute has no keyword
193            // kind at all; see `is_property_attribute_name`.
194            DOT | LPAREN | LPAREN2 | COMMA | STAR | GTGT | COLON | SEMI | Return | Break
195            | Continue | If | Else | Switch | Case | Default | For | While | Goto | Do | EQ
196            | AMPAMP | PIPEPIPE | DASH | DASHDASH | DASHGT | PLUS | PLUSPLUS | SLASH | PERCENT
197            | PIPE | AMP | LTLT | TILDE | LT | LT2 | LTEQ | EQEQ | BANGEQ | GTEQ | GT | PLUSEQ
198            | DASHEQ | BANG | STAREQ | SLASHEQ | PERCENTEQ | GTGTEQ | LTLTEQ | AMPEQ | CARET
199            | CARETEQ | PIPEEQ | LBRACK | LBRACE | QMARK | PrimitiveType | TypeSpecifier
200            | Sizeof | Signed | Unsigned | Long | Short | In | AT | ATtry | ATcatch | ATfinally
201            | ATthrow | ATsynchronized | ATautoreleasepool | ATselector | ATencode | Struct
202            | Union | Enum | Typedef | ATinterface | ATimplementation | ATprotocol | Static
203            | Extern | Register | Auto | Inline | Inline2 | Inline3 | Forceinline | ThreadLocal
204            | Thread | Const | Constexpr | Volatile | Volatile2 | Restrict | Restrict2 | Atomic
205            | Noreturn | Noreturn2 | Nonnull | Alignas | Alignas2 | Extension | Const2
206            | Complex | Complex2 | Nullable | Nullable2 | Nullable3 | NullableResult
207            | NullUnspecified | Nonnull2 | Autoreleasing | Block | Strong | UnsafeUnretained
208            | Weak | Bridge | BridgeRetained | BridgeTransfer | Kindof | Covariant
209            | Contravariant | Out | Inout | Bycopy | Byref | Oneway | ATpublic | ATprivate
210            | ATprotected | ATpackage | AToptional | ATrequired => TokenRole::Operator,
211            Identifier if is_property_attribute_name(node, ancestors) => TokenRole::Operator,
212            // `CharLiteral` — the full derivation lives on the same arm
213            // in `src/getter/c.rs` (#1316): the wrapper is the only
214            // classified node in a character literal, so it bills one
215            // operand per literal, keyed by text, and `Checker::is_string`
216            // deliberately stays without a `CharLiteral` arm.
217            Identifier | TypeIdentifier | FieldIdentifier | StringLiteral | CharLiteral
218            | NumberLiteral | True | False | Null | DOTDOTDOT => TokenRole::Operand,
219            _ => TokenRole::Unknown,
220        }
221    }
222
223    fn get_operator_spelling<'a>(
224        node: &Node<'a>,
225        ancestors: Ancestors<'a, '_>,
226    ) -> Option<&'static str> {
227        GENERIC_ANGLES.opener_spelling(node, ancestors)
228    }
229
230    get_operator!(Objc);
231}