nitrogen 0.31.1 → 0.31.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/lib/config/nitrogenVersion.d.ts +1 -0
  2. package/lib/config/nitrogenVersion.js +2 -0
  3. package/lib/createPlatformSpec.js +8 -0
  4. package/lib/getPlatformSpecs.d.ts +3 -2
  5. package/lib/getPlatformSpecs.js +12 -10
  6. package/lib/index.js +2 -1
  7. package/lib/nitrogen.js +6 -15
  8. package/lib/syntax/Method.js +4 -0
  9. package/lib/syntax/c++/CppHybridObject.js +4 -2
  10. package/lib/syntax/createType.js +6 -6
  11. package/lib/syntax/isMemberOverridingFromBase.d.ts +11 -0
  12. package/lib/syntax/isMemberOverridingFromBase.js +64 -0
  13. package/lib/syntax/isOverridingFromBase.d.ts +11 -0
  14. package/lib/syntax/isOverridingFromBase.js +64 -0
  15. package/lib/syntax/kotlin/FbjniHybridObject.js +7 -0
  16. package/lib/syntax/kotlin/KotlinHybridObject.js +12 -2
  17. package/lib/syntax/kotlin/KotlinStruct.js +49 -14
  18. package/lib/syntax/kotlin/isBaseObjectMethodName.d.ts +7 -0
  19. package/lib/syntax/kotlin/isBaseObjectMethodName.js +9 -0
  20. package/lib/syntax/swift/SwiftCxxBridgedType.js +5 -29
  21. package/lib/syntax/swift/SwiftCxxTypeHelper.js +2 -28
  22. package/lib/syntax/swift/SwiftHybridObject.js +8 -1
  23. package/lib/syntax/swift/SwiftHybridObjectBridge.js +12 -1
  24. package/lib/syntax/types/AnyHybridObjectType.d.ts +11 -0
  25. package/lib/syntax/types/AnyHybridObjectType.js +40 -0
  26. package/lib/utils.d.ts +0 -1
  27. package/lib/utils.js +0 -1
  28. package/package.json +2 -2
  29. package/src/config/nitrogenVersion.ts +3 -0
  30. package/src/createPlatformSpec.ts +16 -0
  31. package/src/getPlatformSpecs.ts +24 -15
  32. package/src/index.ts +2 -1
  33. package/src/nitrogen.ts +6 -17
  34. package/src/syntax/Method.ts +6 -0
  35. package/src/syntax/c++/CppHybridObject.ts +7 -2
  36. package/src/syntax/createType.ts +5 -5
  37. package/src/syntax/isMemberOverridingFromBase.ts +79 -0
  38. package/src/syntax/kotlin/FbjniHybridObject.ts +7 -0
  39. package/src/syntax/kotlin/KotlinHybridObject.ts +12 -2
  40. package/src/syntax/kotlin/KotlinStruct.ts +61 -15
  41. package/src/syntax/swift/SwiftCxxBridgedType.ts +5 -27
  42. package/src/syntax/swift/SwiftCxxTypeHelper.ts +2 -27
  43. package/src/syntax/swift/SwiftHybridObject.ts +8 -1
  44. package/src/syntax/swift/SwiftHybridObjectBridge.ts +12 -1
  45. package/src/syntax/types/{HybridObjectBaseType.ts → AnyHybridObjectType.ts} +2 -9
  46. package/src/utils.ts +0 -2
  47. package/src/syntax/swift/isPrimitivelyCopyable.ts +0 -20
@@ -17,7 +17,6 @@ import { VoidType } from '../types/VoidType.js';
17
17
  import { NamedWrappingType } from '../types/NamedWrappingType.js';
18
18
  import { ErrorType } from '../types/ErrorType.js';
19
19
  import { ResultWrappingType } from '../types/ResultWrappingType.js';
20
- import { isPrimitivelyCopyable } from './isPrimitivelyCopyable.js';
21
20
  export function createSwiftCxxHelpers(type) {
22
21
  switch (type.kind) {
23
22
  case 'hybrid-object':
@@ -227,27 +226,8 @@ function createCxxVectorSwiftHelper(type) {
227
226
  const actualType = type.getCode('c++');
228
227
  const bridgedType = new SwiftCxxBridgedType(type);
229
228
  const name = escapeCppName(actualType);
230
- let code;
231
- let funcName;
232
- if (isPrimitivelyCopyable(type.itemType)) {
233
- const itemType = type.itemType.getCode('c++');
234
- funcName = `copy_${name}`;
235
- code = `
236
- /**
237
- * Specialized version of \`${escapeComments(actualType)}\`.
238
- */
239
- using ${name} = ${actualType};
240
- inline ${actualType} copy_${name}(const ${itemType}* CONTIGUOUS_MEMORY NON_NULL data, size_t size) noexcept {
241
- return margelo::nitro::FastVectorCopy<${itemType}>(data, size);
242
- }
243
- inline const ${itemType}* CONTIGUOUS_MEMORY NON_NULL get_data_${name}(const ${actualType}& vector) noexcept {
244
- return vector.data();
245
- }
246
- `.trim();
247
- }
248
- else {
249
- funcName = `create_${name}`;
250
- code = `
229
+ const funcName = `create_${name}`;
230
+ const code = `
251
231
  /**
252
232
  * Specialized version of \`${escapeComments(actualType)}\`.
253
233
  */
@@ -257,7 +237,6 @@ inline ${actualType} create_${name}(size_t size) noexcept {
257
237
  vector.reserve(size);
258
238
  return vector;
259
239
  }`.trim();
260
- }
261
240
  return {
262
241
  cxxType: actualType,
263
242
  funcName: funcName,
@@ -270,11 +249,6 @@ inline ${actualType} create_${name}(size_t size) noexcept {
270
249
  space: 'system',
271
250
  language: 'c++',
272
251
  },
273
- {
274
- name: 'NitroModules/FastVectorCopy.hpp',
275
- space: 'system',
276
- language: 'c++',
277
- },
278
252
  ...bridgedType.getRequiredImports('c++'),
279
253
  ],
280
254
  },
@@ -8,7 +8,7 @@ export function createSwiftHybridObject(spec) {
8
8
  const name = getHybridObjectName(spec.name);
9
9
  const protocolName = name.HybridTSpec;
10
10
  const properties = spec.properties.map((p) => p.getCode('swift')).join('\n');
11
- const methods = spec.methods.map((p) => p.getCode('swift')).join('\n');
11
+ const methods = spec.methods.map((m) => m.getCode('swift')).join('\n');
12
12
  const extraImports = [
13
13
  ...spec.properties.flatMap((p) => p.getRequiredImports('swift')),
14
14
  ...spec.methods.flatMap((m) => m.getRequiredImports('swift')),
@@ -69,6 +69,13 @@ public protocol ${protocolName}_protocol: ${protocolBaseClasses.join(', ')} {
69
69
  ${indent(methods, ' ')}
70
70
  }
71
71
 
72
+ public extension ${protocolName}_protocol {
73
+ /// Default implementation of \`\`HybridObject.toString\`\`
74
+ func toString() -> String {
75
+ return "[HybridObject ${name.T}]"
76
+ }
77
+ }
78
+
72
79
  /// See \`\`${protocolName}\`\`
73
80
  open class ${protocolName}_base${classBaseClasses.length > 0 ? `: ${classBaseClasses.join(',')}` : ''} {
74
81
  ${indent(baseMembers.join('\n'), ' ')}
@@ -148,7 +148,7 @@ ${hasBase ? `open class ${name.HybridTSpecCxx} : ${baseClasses.join(', ')}` : `o
148
148
  */
149
149
  public func getCxxPart() -> bridge.${bridge.specializationName} {
150
150
  let cachedCxxPart = self.__cxxPart.lock()
151
- if cachedCxxPart.__convertToBool() {
151
+ if Bool(fromCxx: cachedCxxPart) {
152
152
  return cachedCxxPart
153
153
  } else {
154
154
  let newCxxPart = bridge.${bridge.funcName}(self.toUnsafe())
@@ -177,6 +177,14 @@ ${hasBase ? `open class ${name.HybridTSpecCxx} : ${baseClasses.join(', ')}` : `o
177
177
  self.__implementation.dispose()
178
178
  }
179
179
 
180
+ /**
181
+ * Call toString() on the Swift class.
182
+ */
183
+ @inline(__always)
184
+ public ${hasBase ? 'override func' : 'func'} toString() -> String {
185
+ return self.__implementation.toString()
186
+ }
187
+
180
188
  // Properties
181
189
  ${indent(propertiesBridge.join('\n\n'), ' ')}
182
190
 
@@ -330,6 +338,9 @@ namespace ${cxxNamespace} {
330
338
  void dispose() noexcept override {
331
339
  _swiftPart.dispose();
332
340
  }
341
+ std::string toString() override {
342
+ return _swiftPart.toString();
343
+ }
333
344
 
334
345
  public:
335
346
  // Properties
@@ -0,0 +1,11 @@
1
+ import type { Language } from '../../getPlatformSpecs.js';
2
+ import type { SourceFile, SourceImport } from '../SourceFile.js';
3
+ import type { Type, TypeKind } from './Type.js';
4
+ export declare class AnyHybridObjectType implements Type {
5
+ constructor();
6
+ get canBePassedByReference(): boolean;
7
+ get kind(): TypeKind;
8
+ getCode(language: Language): string;
9
+ getExtraFiles(): SourceFile[];
10
+ getRequiredImports(language: Language): SourceImport[];
11
+ }
@@ -0,0 +1,40 @@
1
+ import { getForwardDeclaration } from '../c++/getForwardDeclaration.js';
2
+ export class AnyHybridObjectType {
3
+ constructor() { }
4
+ get canBePassedByReference() {
5
+ // It's a shared_ptr<..>, no copy.
6
+ return true;
7
+ }
8
+ get kind() {
9
+ return 'hybrid-object-base';
10
+ }
11
+ getCode(language) {
12
+ switch (language) {
13
+ case 'c++':
14
+ return `std::shared_ptr<HybridObject>`;
15
+ default:
16
+ throw new Error(`\`AnyHybridObject\` cannot be used directly in ${language} yet. Use a specific derived class of \`HybridObject\` instead!`);
17
+ }
18
+ }
19
+ getExtraFiles() {
20
+ return [];
21
+ }
22
+ getRequiredImports(language) {
23
+ const imports = [];
24
+ switch (language) {
25
+ case 'c++':
26
+ imports.push({
27
+ language: 'c++',
28
+ name: 'memory',
29
+ space: 'system',
30
+ }, {
31
+ name: `NitroModules/HybridObject.hpp`,
32
+ forwardDeclaration: getForwardDeclaration('class', 'HybridObject', 'margelo::nitro'),
33
+ language: 'c++',
34
+ space: 'system',
35
+ });
36
+ break;
37
+ }
38
+ return imports;
39
+ }
40
+ }
package/lib/utils.d.ts CHANGED
@@ -2,7 +2,6 @@ import type { SourceFile } from './syntax/SourceFile.js';
2
2
  import type { SwiftCxxHelper } from './syntax/swift/SwiftCxxTypeHelper.js';
3
3
  import type { Type } from 'ts-morph';
4
4
  import { NitroConfig } from './config/NitroConfig.js';
5
- export declare const NITROGEN_VERSION: string;
6
5
  export declare function capitalizeName(name: string): string;
7
6
  export declare function createIndentation(spacesCount: number): string;
8
7
  export declare function indent(string: string, spacesCount: number): string;
package/lib/utils.js CHANGED
@@ -3,7 +3,6 @@ import fs from 'fs';
3
3
  import { isNotDuplicate } from './syntax/helpers.js';
4
4
  import { readUserConfig } from './config/getConfig.js';
5
5
  import { NitroConfig } from './config/NitroConfig.js';
6
- export const NITROGEN_VERSION = process.env.npm_package_version ?? '?.?.?';
7
6
  export function capitalizeName(name) {
8
7
  if (name.length === 0)
9
8
  return name;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nitrogen",
3
- "version": "0.31.1",
3
+ "version": "0.31.3",
4
4
  "description": "The code-generator for react-native-nitro-modules.",
5
5
  "main": "lib/index",
6
6
  "types": "lib/index.d.ts",
@@ -36,7 +36,7 @@
36
36
  },
37
37
  "dependencies": {
38
38
  "chalk": "^5.3.0",
39
- "react-native-nitro-modules": "^0.31.1",
39
+ "react-native-nitro-modules": "^0.31.3",
40
40
  "ts-morph": "^27.0.0",
41
41
  "yargs": "^18.0.0",
42
42
  "zod": "^4.0.5"
@@ -0,0 +1,3 @@
1
+ import packageJson from 'nitrogen/package.json' with { type: 'json' }
2
+
3
+ export const NITROGEN_VERSION = packageJson.version
@@ -19,6 +19,7 @@ import { createType } from './syntax/createType.js'
19
19
  import { Parameter } from './syntax/Parameter.js'
20
20
  import { getBaseTypes, getHybridObjectNitroModuleConfig } from './utils.js'
21
21
  import { NitroConfig } from './config/NitroConfig.js'
22
+ import { isMemberOverridingFromBase } from './syntax/isMemberOverridingFromBase.js'
22
23
 
23
24
  export function generatePlatformFiles(
24
25
  interfaceType: Type,
@@ -151,6 +152,21 @@ function getHybridObjectSpec(type: Type, language: Language): HybridObjectSpec {
151
152
  isHybridView: isHybridView(type),
152
153
  config: config,
153
154
  }
155
+
156
+ for (const member of [...properties, ...methods]) {
157
+ const isOverridingBaseMember = isMemberOverridingFromBase(
158
+ member.name,
159
+ spec,
160
+ language
161
+ )
162
+ if (isOverridingBaseMember) {
163
+ throw new Error(
164
+ `\`${name}.${member.name}\` is overriding a member of one of it's base classes. ` +
165
+ `This is unsupported, override on the native side instead!`
166
+ )
167
+ }
168
+ }
169
+
154
170
  return spec
155
171
  }
156
172
 
@@ -54,30 +54,27 @@ function getPlatformSpec(typeName: string, platformSpecs: Type): PlatformSpec {
54
54
  // Property name (ios, android)
55
55
  const platform = property.getName()
56
56
  if (!isValidPlatform(platform)) {
57
- console.warn(
58
- ` ⚠️ ${typeName} does not properly extend HybridObject<T> - "${platform}" is not a valid Platform! ` +
57
+ throw new Error(
58
+ `${typeName} does not properly extend HybridObject<T> - "${platform}" is not a valid Platform! ` +
59
59
  `Valid platforms are: [${allPlatforms.join(', ')}]`
60
60
  )
61
- continue
62
61
  }
63
62
 
64
63
  // Value (swift, kotlin, c++)
65
64
  const language = getLiteralValue(property)
66
65
  if (!isValidLanguage(language)) {
67
- console.warn(
68
- ` ⚠️ ${typeName}: Language ${language} is not a valid language for ${platform}! ` +
66
+ throw new Error(
67
+ `${typeName}: Language ${language} is not a valid language for ${platform}! ` +
69
68
  `Valid languages are: [${platformLanguages[platform].join(', ')}]`
70
69
  )
71
- continue
72
70
  }
73
71
 
74
72
  // Double-check that language works on this platform (android: kotlin/c++, ios: swift/c++)
75
73
  if (!isValidLanguageForPlatform(language, platform)) {
76
- console.warn(
77
- ` ⚠️ ${typeName}: Language ${language} is not a valid language for ${platform}! ` +
74
+ throw new Error(
75
+ `${typeName}: Language ${language} is not a valid language for ${platform}! ` +
78
76
  `Valid languages are: [${platformLanguages[platform].join(', ')}]`
79
77
  )
80
- continue
81
78
  }
82
79
 
83
80
  // @ts-expect-error because TypeScript isn't smart enough yet to correctly cast after the `isValidLanguageForPlatform` check.
@@ -115,6 +112,10 @@ export function isDirectlyHybridObject(type: Type): boolean {
115
112
  return isDirectlyType(type, 'HybridObject')
116
113
  }
117
114
 
115
+ export function isDirectlyAnyHybridObject(type: Type): boolean {
116
+ return isDirectlyType(type, 'AnyHybridObject')
117
+ }
118
+
118
119
  export function extendsHybridObject(type: Type, recursive: boolean): boolean {
119
120
  return extendsType(type, 'HybridObject', recursive)
120
121
  }
@@ -154,7 +155,7 @@ export function isAnyHybridSubclass(type: Type): boolean {
154
155
  */
155
156
  export function getHybridObjectPlatforms(
156
157
  declaration: InterfaceDeclaration | TypeAliasDeclaration
157
- ): PlatformSpec | undefined {
158
+ ): PlatformSpec {
158
159
  const base = getBaseTypes(declaration.getType()).find((t) =>
159
160
  isDirectlyHybridObject(t)
160
161
  )
@@ -167,9 +168,15 @@ export function getHybridObjectPlatforms(
167
168
 
168
169
  const genericArguments = base.getTypeArguments()
169
170
  const platformSpecsArgument = genericArguments[0]
170
- if (platformSpecsArgument == null) {
171
- // it uses `HybridObject` without generic arguments. This defaults to C++
172
- return { android: 'c++', ios: 'c++' }
171
+ if (
172
+ platformSpecsArgument == null ||
173
+ platformSpecsArgument.getProperties().length === 0
174
+ ) {
175
+ // it uses `HybridObject` without generic arguments. We throw as we don't know what to generate.
176
+ throw new Error(
177
+ `HybridObject ${declaration.getName()} does not declare any platforms in the \`HybridObject\` type argument! ` +
178
+ `Pass at least one platform (and language) to \`interface ${declaration.getName()} extends HybridObject<{ ... }>\``
179
+ )
173
180
  }
174
181
 
175
182
  return getPlatformSpec(declaration.getName(), platformSpecsArgument)
@@ -177,7 +184,7 @@ export function getHybridObjectPlatforms(
177
184
 
178
185
  export function getHybridViewPlatforms(
179
186
  view: InterfaceDeclaration | TypeAliasDeclaration
180
- ): PlatformSpec | undefined {
187
+ ): PlatformSpec {
181
188
  if (Node.isTypeAliasDeclaration(view)) {
182
189
  const hybridViewTypeNode = view.getTypeNode()
183
190
 
@@ -186,7 +193,9 @@ export function getHybridViewPlatforms(
186
193
  hybridViewTypeNode.getTypeName().getText() === 'HybridView'
187
194
 
188
195
  if (!isHybridViewType) {
189
- return
196
+ throw new Error(
197
+ `${view.getName()} looks like a HybridView, but doesn't seem to alias HybridView<...>!`
198
+ )
190
199
  }
191
200
 
192
201
  const genericArguments = hybridViewTypeNode.getTypeArguments()
package/src/index.ts CHANGED
@@ -9,7 +9,7 @@ import { runNitrogen } from './nitrogen.js'
9
9
  import { promises as fs } from 'fs'
10
10
  import { isValidLogLevel, setLogLevel } from './Logger.js'
11
11
  import { initNewNitroModule } from './init.js'
12
- import { NITROGEN_VERSION } from './utils.js'
12
+ import { NITROGEN_VERSION } from './config/nitrogenVersion.js'
13
13
 
14
14
  const commandName = 'nitrogen'
15
15
 
@@ -90,6 +90,7 @@ await yargs(hideBin(process.argv))
90
90
  )
91
91
  .help()
92
92
  .strict()
93
+ .version(NITROGEN_VERSION)
93
94
  .wrap(cliWidth).argv
94
95
 
95
96
  async function runNitrogenCommand(
package/src/nitrogen.ts CHANGED
@@ -14,7 +14,6 @@ import {
14
14
  deduplicateFiles,
15
15
  errorToString,
16
16
  indent,
17
- NITROGEN_VERSION,
18
17
  } from './utils.js'
19
18
  import { writeFile } from './writeFile.js'
20
19
  import chalk from 'chalk'
@@ -26,6 +25,7 @@ import { createAndroidAutolinking } from './autolinking/createAndroidAutolinking
26
25
  import type { Autolinking } from './autolinking/Autolinking.js'
27
26
  import { createGitAttributes } from './createGitAttributes.js'
28
27
  import type { PlatformSpec } from 'react-native-nitro-modules'
28
+ import { NITROGEN_VERSION } from './config/nitrogenVersion.js'
29
29
 
30
30
  interface NitrogenOptions {
31
31
  baseDirectory: string
@@ -108,31 +108,20 @@ export async function runNitrogen({
108
108
  let platformSpec: PlatformSpec
109
109
  if (isHybridView(declaration.getType())) {
110
110
  // Hybrid View Props
111
- const targetPlatforms = getHybridViewPlatforms(declaration)
112
- if (targetPlatforms == null) {
113
- // It does not extend HybridView, continue..
114
- continue
115
- }
116
- platformSpec = targetPlatforms
111
+ platformSpec = getHybridViewPlatforms(declaration)
117
112
  } else if (extendsHybridObject(declaration.getType(), true)) {
118
113
  // Hybrid View
119
- const targetPlatforms = getHybridObjectPlatforms(declaration)
120
- if (targetPlatforms == null) {
121
- // It does not extend HybridObject, continue..
122
- continue
123
- }
124
- platformSpec = targetPlatforms
114
+ platformSpec = getHybridObjectPlatforms(declaration)
125
115
  } else {
126
116
  continue
127
117
  }
128
118
 
129
119
  const platforms = Object.keys(platformSpec) as Platform[]
130
-
131
120
  if (platforms.length === 0) {
132
- Logger.warn(
133
- `⚠️ ${typeName} does not declare any platforms in HybridObject<T> - nothing can be generated.`
121
+ throw new Error(
122
+ `${typeName} does not declare any platforms in HybridObject<T> - nothing can be generated. ` +
123
+ `For example, to generate a C++ HybridObject, use \`interface ${typeName} extends HybridObject<{ ios: 'c++', android: 'c++' }> { ... }\``
134
124
  )
135
- continue
136
125
  }
137
126
 
138
127
  targetSpecs++
@@ -50,6 +50,12 @@ export class Method implements CodeNode {
50
50
  `Method names are not allowed to start with two underscores (__)! (In ${this.jsSignature})`
51
51
  )
52
52
  }
53
+ if (this.name === 'dispose') {
54
+ // .dispose() does some special JSI magic that we loose if the user overrides it in his spec.
55
+ throw new Error(
56
+ `dispose() must not be overridden from TypeScript! You can override dispose() natively.`
57
+ )
58
+ }
53
59
  }
54
60
 
55
61
  get jsSignature(): string {
@@ -29,6 +29,11 @@ export function createCppHybridObject(spec: HybridObjectSpec): SourceFile[] {
29
29
  const cxxNamespace = spec.config.getCxxNamespace('c++')
30
30
  const name = getHybridObjectName(spec.name)
31
31
 
32
+ const properties = spec.properties.map((p) =>
33
+ p.getCode('c++', { virtual: true })
34
+ )
35
+ const methods = spec.methods.map((m) => m.getCode('c++', { virtual: true }))
36
+
32
37
  const bases = ['public virtual HybridObject']
33
38
  for (const base of spec.baseTypes) {
34
39
  const baseName = getHybridObjectName(base.name).HybridTSpec
@@ -77,11 +82,11 @@ namespace ${cxxNamespace} {
77
82
 
78
83
  public:
79
84
  // Properties
80
- ${indent(spec.properties.map((p) => p.getCode('c++', { virtual: true })).join('\n'), ' ')}
85
+ ${indent(properties.join('\n'), ' ')}
81
86
 
82
87
  public:
83
88
  // Methods
84
- ${indent(spec.methods.map((m) => m.getCode('c++', { virtual: true })).join('\n'), ' ')}
89
+ ${indent(methods.join('\n'), ' ')}
85
90
 
86
91
  protected:
87
92
  // Hybrid Setup
@@ -22,11 +22,11 @@ import { MapType } from './types/MapType.js'
22
22
  import { TupleType } from './types/TupleType.js'
23
23
  import {
24
24
  isAnyHybridSubclass,
25
- isDirectlyHybridObject,
25
+ isDirectlyAnyHybridObject,
26
26
  isHybridView,
27
27
  type Language,
28
28
  } from '../getPlatformSpecs.js'
29
- import { HybridObjectBaseType } from './types/HybridObjectBaseType.js'
29
+ import { AnyHybridObjectType } from './types/AnyHybridObjectType.js'
30
30
  import { ErrorType } from './types/ErrorType.js'
31
31
  import { getBaseTypes, getHybridObjectNitroModuleConfig } from '../utils.js'
32
32
  import { DateType } from './types/DateType.js'
@@ -314,6 +314,9 @@ export function createType(
314
314
  const name = type.getAliasSymbol()?.getName()
315
315
  return new VariantType(variants, name)
316
316
  }
317
+ } else if (isDirectlyAnyHybridObject(type)) {
318
+ // It is a HybridObject directly/literally. Base type
319
+ return new AnyHybridObjectType()
317
320
  } else if (isAnyHybridSubclass(type)) {
318
321
  // It is another HybridObject being referenced!
319
322
  const typename = getHybridObjectName(type)
@@ -324,9 +327,6 @@ export function createType(
324
327
  const sourceConfig =
325
328
  getHybridObjectNitroModuleConfig(type) ?? NitroConfig.current
326
329
  return new HybridObjectType(typename, language, baseHybrids, sourceConfig)
327
- } else if (isDirectlyHybridObject(type)) {
328
- // It is a HybridObject directly/literally. Base type
329
- return new HybridObjectBaseType()
330
330
  } else if (type.isInterface()) {
331
331
  // It is an `interface T { ... }`, which is a `struct`
332
332
  const typename = type.getSymbolOrThrow().getName()
@@ -0,0 +1,79 @@
1
+ import type { HybridObject } from 'react-native-nitro-modules'
2
+ import type { Language } from '../getPlatformSpecs.js'
3
+ import type { HybridObjectSpec } from './HybridObjectSpec.js'
4
+
5
+ function getMemberNamesOfBaseType(language: Language): string[] {
6
+ switch (language) {
7
+ case 'c++':
8
+ // C++ classes don't have any base type.
9
+ return []
10
+ case 'swift':
11
+ // Swift classes conform to `AnyObject`, but that doesn't have any properties
12
+ return []
13
+ case 'kotlin':
14
+ // Kotlin/JVM classes always extends `Any`, which has 3 methods
15
+ return ['toString', 'equals', 'hashCode']
16
+ }
17
+ }
18
+ function getMemberNamesOfHybridObject(): string[] {
19
+ type MemberName = keyof HybridObject<{}>
20
+ type HasToContainAllKeys = { [P in MemberName]: boolean }
21
+ const allKeys: HasToContainAllKeys = {
22
+ __type: true,
23
+ dispose: true,
24
+ equals: true,
25
+ name: true,
26
+ toString: true,
27
+ }
28
+ return Object.keys(allKeys)
29
+ }
30
+
31
+ function flatBaseTypes(type: HybridObjectSpec): HybridObjectSpec[] {
32
+ return type.baseTypes.flatMap((b) => [b, ...flatBaseTypes(b)])
33
+ }
34
+
35
+ /**
36
+ * Returns true when the given {@linkcode memberName} is overriding a
37
+ * property or method from any base class inside the given
38
+ * {@linkcode hybridObject}'s prototype chain (all the way up).
39
+ *
40
+ * For example, `"toString"` would return `true` since it overrides from base HybridObject.
41
+ * On Kotlin, `"hashCode"` would return `true` since it overrides from base `kotlin.Any`.
42
+ */
43
+ export function isMemberOverridingFromBase(
44
+ memberName: string,
45
+ hybridObject: HybridObjectSpec,
46
+ language: Language
47
+ ): boolean {
48
+ // 1. Check if the HybridObject inherits from other HybridObjects,
49
+ // if yes, check if those have properties of that given name.
50
+ const allBases = flatBaseTypes(hybridObject)
51
+ const anyBaseOverrides = allBases.some((h) => {
52
+ if (h.properties.some((p) => p.name === memberName)) {
53
+ return true
54
+ }
55
+ if (h.methods.some((m) => m.name === memberName)) {
56
+ return true
57
+ }
58
+ return false
59
+ })
60
+ if (anyBaseOverrides) {
61
+ // A HybridObject base type has the same property name - we need to override it.
62
+ return true
63
+ }
64
+
65
+ // 2. Check if the base `HybridObject` type contains a property of the given name
66
+ const baseHybridObjectProps = getMemberNamesOfHybridObject()
67
+ if (baseHybridObjectProps.includes(memberName)) {
68
+ return true
69
+ }
70
+
71
+ // 3. Check if the base type in our language contains a property of the given name
72
+ const baseTypeProps = getMemberNamesOfBaseType(language)
73
+ if (baseTypeProps.includes(memberName)) {
74
+ return true
75
+ }
76
+
77
+ // 4. Apparently no base type has a property of that name - we are safe!
78
+ return false
79
+ }
@@ -113,6 +113,7 @@ ${spaces} public virtual ${name.HybridTSpec} {
113
113
  public:
114
114
  size_t getExternalMemorySize() noexcept override;
115
115
  void dispose() noexcept override;
116
+ std::string toString() override;
116
117
 
117
118
  public:
118
119
  inline const jni::global_ref<${name.JHybridTSpec}::javaobject>& getJavaPart() const noexcept {
@@ -198,6 +199,12 @@ namespace ${cxxNamespace} {
198
199
  method(_javaPart);
199
200
  }
200
201
 
202
+ std::string ${name.JHybridTSpec}::toString() {
203
+ static const auto method = javaClassStatic()->getMethod<jni::JString()>("toString");
204
+ auto javaString = method(_javaPart);
205
+ return javaString->toStdString();
206
+ }
207
+
201
208
  // Properties
202
209
  ${indent(propertiesImpl, ' ')}
203
210
 
@@ -94,6 +94,11 @@ abstract class ${name.HybridTSpec}: ${kotlinBase}() {
94
94
  super.updateNative(hybridData)
95
95
  }
96
96
 
97
+ // Default implementation of \`HybridObject.toString()\`
98
+ override fun toString(): String {
99
+ return "[HybridObject ${name.T}]"
100
+ }
101
+
97
102
  // Properties
98
103
  ${indent(properties, ' ')}
99
104
 
@@ -199,7 +204,9 @@ set(value) {
199
204
  `.trim()
200
205
  )
201
206
  }
202
- const code = property.getCode('kotlin', { virtual: true })
207
+ const code = property.getCode('kotlin', {
208
+ virtual: true,
209
+ })
203
210
  return `
204
211
  ${code}
205
212
 
@@ -207,7 +214,10 @@ private ${keyword} ${property.name}_cxx: ${bridged.getTypeCode('kotlin')}
207
214
  ${indent(lines.join('\n'), ' ')}
208
215
  `.trim()
209
216
  } else {
210
- const code = property.getCode('kotlin', { doNotStrip: true, virtual: true })
217
+ const code = property.getCode('kotlin', {
218
+ doNotStrip: true,
219
+ virtual: true,
220
+ })
211
221
  return code
212
222
  }
213
223
  }