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
@@ -0,0 +1 @@
1
+ export declare const NITROGEN_VERSION: string;
@@ -0,0 +1,2 @@
1
+ import packageJson from 'nitrogen/package.json' with { type: 'json' };
2
+ export const NITROGEN_VERSION = packageJson.version;
@@ -9,6 +9,7 @@ import { createType } from './syntax/createType.js';
9
9
  import { Parameter } from './syntax/Parameter.js';
10
10
  import { getBaseTypes, getHybridObjectNitroModuleConfig } from './utils.js';
11
11
  import { NitroConfig } from './config/NitroConfig.js';
12
+ import { isMemberOverridingFromBase } from './syntax/isMemberOverridingFromBase.js';
12
13
  export function generatePlatformFiles(interfaceType, language) {
13
14
  const spec = getHybridObjectSpec(interfaceType, language);
14
15
  // TODO: We currently just call this so the HybridObject itself is a "known type".
@@ -105,6 +106,13 @@ function getHybridObjectSpec(type, language) {
105
106
  isHybridView: isHybridView(type),
106
107
  config: config,
107
108
  };
109
+ for (const member of [...properties, ...methods]) {
110
+ const isOverridingBaseMember = isMemberOverridingFromBase(member.name, spec, language);
111
+ if (isOverridingBaseMember) {
112
+ throw new Error(`\`${name}.${member.name}\` is overriding a member of one of it's base classes. ` +
113
+ `This is unsupported, override on the native side instead!`);
114
+ }
115
+ }
108
116
  return spec;
109
117
  }
110
118
  function generateCppFiles(spec) {
@@ -3,6 +3,7 @@ import type { InterfaceDeclaration, Type, TypeAliasDeclaration } from 'ts-morph'
3
3
  export type Platform = keyof Required<PlatformSpec>;
4
4
  export type Language = Required<PlatformSpec>[keyof PlatformSpec];
5
5
  export declare function isDirectlyHybridObject(type: Type): boolean;
6
+ export declare function isDirectlyAnyHybridObject(type: Type): boolean;
6
7
  export declare function extendsHybridObject(type: Type, recursive: boolean): boolean;
7
8
  export declare function isHybridViewProps(type: Type): boolean;
8
9
  export declare function isHybridViewMethods(type: Type): boolean;
@@ -13,5 +14,5 @@ export declare function isAnyHybridSubclass(type: Type): boolean;
13
14
  * this method returns the platforms it exists on.
14
15
  * If it doesn't extend `HybridObject`, this returns `undefined`.
15
16
  */
16
- export declare function getHybridObjectPlatforms(declaration: InterfaceDeclaration | TypeAliasDeclaration): PlatformSpec | undefined;
17
- export declare function getHybridViewPlatforms(view: InterfaceDeclaration | TypeAliasDeclaration): PlatformSpec | undefined;
17
+ export declare function getHybridObjectPlatforms(declaration: InterfaceDeclaration | TypeAliasDeclaration): PlatformSpec;
18
+ export declare function getHybridViewPlatforms(view: InterfaceDeclaration | TypeAliasDeclaration): PlatformSpec;
@@ -39,22 +39,19 @@ function getPlatformSpec(typeName, platformSpecs) {
39
39
  // Property name (ios, android)
40
40
  const platform = property.getName();
41
41
  if (!isValidPlatform(platform)) {
42
- console.warn(` ⚠️ ${typeName} does not properly extend HybridObject<T> - "${platform}" is not a valid Platform! ` +
42
+ throw new Error(`${typeName} does not properly extend HybridObject<T> - "${platform}" is not a valid Platform! ` +
43
43
  `Valid platforms are: [${allPlatforms.join(', ')}]`);
44
- continue;
45
44
  }
46
45
  // Value (swift, kotlin, c++)
47
46
  const language = getLiteralValue(property);
48
47
  if (!isValidLanguage(language)) {
49
- console.warn(` ⚠️ ${typeName}: Language ${language} is not a valid language for ${platform}! ` +
48
+ throw new Error(`${typeName}: Language ${language} is not a valid language for ${platform}! ` +
50
49
  `Valid languages are: [${platformLanguages[platform].join(', ')}]`);
51
- continue;
52
50
  }
53
51
  // Double-check that language works on this platform (android: kotlin/c++, ios: swift/c++)
54
52
  if (!isValidLanguageForPlatform(language, platform)) {
55
- console.warn(` ⚠️ ${typeName}: Language ${language} is not a valid language for ${platform}! ` +
53
+ throw new Error(`${typeName}: Language ${language} is not a valid language for ${platform}! ` +
56
54
  `Valid languages are: [${platformLanguages[platform].join(', ')}]`);
57
- continue;
58
55
  }
59
56
  // @ts-expect-error because TypeScript isn't smart enough yet to correctly cast after the `isValidLanguageForPlatform` check.
60
57
  result[platform] = language;
@@ -86,6 +83,9 @@ function extendsType(type, name, recursive) {
86
83
  export function isDirectlyHybridObject(type) {
87
84
  return isDirectlyType(type, 'HybridObject');
88
85
  }
86
+ export function isDirectlyAnyHybridObject(type) {
87
+ return isDirectlyType(type, 'AnyHybridObject');
88
+ }
89
89
  export function extendsHybridObject(type, recursive) {
90
90
  return extendsType(type, 'HybridObject', recursive);
91
91
  }
@@ -128,9 +128,11 @@ export function getHybridObjectPlatforms(declaration) {
128
128
  }
129
129
  const genericArguments = base.getTypeArguments();
130
130
  const platformSpecsArgument = genericArguments[0];
131
- if (platformSpecsArgument == null) {
132
- // it uses `HybridObject` without generic arguments. This defaults to C++
133
- return { android: 'c++', ios: 'c++' };
131
+ if (platformSpecsArgument == null ||
132
+ platformSpecsArgument.getProperties().length === 0) {
133
+ // it uses `HybridObject` without generic arguments. We throw as we don't know what to generate.
134
+ throw new Error(`HybridObject ${declaration.getName()} does not declare any platforms in the \`HybridObject\` type argument! ` +
135
+ `Pass at least one platform (and language) to \`interface ${declaration.getName()} extends HybridObject<{ ... }>\``);
134
136
  }
135
137
  return getPlatformSpec(declaration.getName(), platformSpecsArgument);
136
138
  }
@@ -140,7 +142,7 @@ export function getHybridViewPlatforms(view) {
140
142
  const isHybridViewType = Node.isTypeReference(hybridViewTypeNode) &&
141
143
  hybridViewTypeNode.getTypeName().getText() === 'HybridView';
142
144
  if (!isHybridViewType) {
143
- return;
145
+ throw new Error(`${view.getName()} looks like a HybridView, but doesn't seem to alias HybridView<...>!`);
144
146
  }
145
147
  const genericArguments = hybridViewTypeNode.getTypeArguments();
146
148
  const platformSpecArg = genericArguments[2];
package/lib/index.js CHANGED
@@ -8,7 +8,7 @@ import { runNitrogen } from './nitrogen.js';
8
8
  import { promises as fs } from 'fs';
9
9
  import { isValidLogLevel, setLogLevel } from './Logger.js';
10
10
  import { initNewNitroModule } from './init.js';
11
- import { NITROGEN_VERSION } from './utils.js';
11
+ import { NITROGEN_VERSION } from './config/nitrogenVersion.js';
12
12
  const commandName = 'nitrogen';
13
13
  // Maximum of 100 col width
14
14
  const cliWidth = Math.min(process.stdout.columns * 0.9, 100);
@@ -71,6 +71,7 @@ await yargs(hideBin(process.argv))
71
71
  `$Nitrogen Version: ${chalk.bold(NITROGEN_VERSION)}`)
72
72
  .help()
73
73
  .strict()
74
+ .version(NITROGEN_VERSION)
74
75
  .wrap(cliWidth).argv;
75
76
  async function runNitrogenCommand(baseDirectory, outputDirectory) {
76
77
  // 1. Prepare output folders
package/lib/nitrogen.js CHANGED
@@ -3,7 +3,7 @@ import { extendsHybridObject, isHybridView, getHybridObjectPlatforms, getHybridV
3
3
  import { generatePlatformFiles } from './createPlatformSpec.js';
4
4
  import path from 'path';
5
5
  import { prettifyDirectory } from './prettifyDirectory.js';
6
- import { capitalizeName, deduplicateFiles, errorToString, indent, NITROGEN_VERSION, } from './utils.js';
6
+ import { capitalizeName, deduplicateFiles, errorToString, indent, } from './utils.js';
7
7
  import { writeFile } from './writeFile.js';
8
8
  import chalk from 'chalk';
9
9
  import { groupByPlatform } from './syntax/SourceFile.js';
@@ -12,6 +12,7 @@ import { NitroConfig } from './config/NitroConfig.js';
12
12
  import { createIOSAutolinking } from './autolinking/createIOSAutolinking.js';
13
13
  import { createAndroidAutolinking } from './autolinking/createAndroidAutolinking.js';
14
14
  import { createGitAttributes } from './createGitAttributes.js';
15
+ import { NITROGEN_VERSION } from './config/nitrogenVersion.js';
15
16
  export async function runNitrogen({ baseDirectory, outputDirectory, }) {
16
17
  let targetSpecs = 0;
17
18
  let generatedSpecs = 0;
@@ -61,29 +62,19 @@ export async function runNitrogen({ baseDirectory, outputDirectory, }) {
61
62
  let platformSpec;
62
63
  if (isHybridView(declaration.getType())) {
63
64
  // Hybrid View Props
64
- const targetPlatforms = getHybridViewPlatforms(declaration);
65
- if (targetPlatforms == null) {
66
- // It does not extend HybridView, continue..
67
- continue;
68
- }
69
- platformSpec = targetPlatforms;
65
+ platformSpec = getHybridViewPlatforms(declaration);
70
66
  }
71
67
  else if (extendsHybridObject(declaration.getType(), true)) {
72
68
  // Hybrid View
73
- const targetPlatforms = getHybridObjectPlatforms(declaration);
74
- if (targetPlatforms == null) {
75
- // It does not extend HybridObject, continue..
76
- continue;
77
- }
78
- platformSpec = targetPlatforms;
69
+ platformSpec = getHybridObjectPlatforms(declaration);
79
70
  }
80
71
  else {
81
72
  continue;
82
73
  }
83
74
  const platforms = Object.keys(platformSpec);
84
75
  if (platforms.length === 0) {
85
- Logger.warn(`⚠️ ${typeName} does not declare any platforms in HybridObject<T> - nothing can be generated.`);
86
- continue;
76
+ throw new Error(`${typeName} does not declare any platforms in HybridObject<T> - nothing can be generated. ` +
77
+ `For example, to generate a C++ HybridObject, use \`interface ${typeName} extends HybridObject<{ ios: 'c++', android: 'c++' }> { ... }\``);
87
78
  }
88
79
  targetSpecs++;
89
80
  Logger.info(` ⚙️ Generating specs for HybridObject "${chalk.bold(typeName)}"...`);
@@ -12,6 +12,10 @@ export class Method {
12
12
  if (this.name.startsWith('__')) {
13
13
  throw new Error(`Method names are not allowed to start with two underscores (__)! (In ${this.jsSignature})`);
14
14
  }
15
+ if (this.name === 'dispose') {
16
+ // .dispose() does some special JSI magic that we loose if the user overrides it in his spec.
17
+ throw new Error(`dispose() must not be overridden from TypeScript! You can override dispose() natively.`);
18
+ }
15
19
  }
16
20
  get jsSignature() {
17
21
  const returnType = this.returnType.kind;
@@ -23,6 +23,8 @@ export function createCppHybridObject(spec) {
23
23
  .filter(isNotDuplicate);
24
24
  const cxxNamespace = spec.config.getCxxNamespace('c++');
25
25
  const name = getHybridObjectName(spec.name);
26
+ const properties = spec.properties.map((p) => p.getCode('c++', { virtual: true }));
27
+ const methods = spec.methods.map((m) => m.getCode('c++', { virtual: true }));
26
28
  const bases = ['public virtual HybridObject'];
27
29
  for (const base of spec.baseTypes) {
28
30
  const baseName = getHybridObjectName(base.name).HybridTSpec;
@@ -70,11 +72,11 @@ namespace ${cxxNamespace} {
70
72
 
71
73
  public:
72
74
  // Properties
73
- ${indent(spec.properties.map((p) => p.getCode('c++', { virtual: true })).join('\n'), ' ')}
75
+ ${indent(properties.join('\n'), ' ')}
74
76
 
75
77
  public:
76
78
  // Methods
77
- ${indent(spec.methods.map((m) => m.getCode('c++', { virtual: true })).join('\n'), ' ')}
79
+ ${indent(methods.join('\n'), ' ')}
78
80
 
79
81
  protected:
80
82
  // Hybrid Setup
@@ -19,8 +19,8 @@ import { getInterfaceProperties } from './getInterfaceProperties.js';
19
19
  import { VariantType } from './types/VariantType.js';
20
20
  import { MapType } from './types/MapType.js';
21
21
  import { TupleType } from './types/TupleType.js';
22
- import { isAnyHybridSubclass, isDirectlyHybridObject, isHybridView, } from '../getPlatformSpecs.js';
23
- import { HybridObjectBaseType } from './types/HybridObjectBaseType.js';
22
+ import { isAnyHybridSubclass, isDirectlyAnyHybridObject, isHybridView, } from '../getPlatformSpecs.js';
23
+ import { AnyHybridObjectType } from './types/AnyHybridObjectType.js';
24
24
  import { ErrorType } from './types/ErrorType.js';
25
25
  import { getBaseTypes, getHybridObjectNitroModuleConfig } from '../utils.js';
26
26
  import { DateType } from './types/DateType.js';
@@ -253,6 +253,10 @@ export function createType(language, type, isOptional) {
253
253
  return new VariantType(variants, name);
254
254
  }
255
255
  }
256
+ else if (isDirectlyAnyHybridObject(type)) {
257
+ // It is a HybridObject directly/literally. Base type
258
+ return new AnyHybridObjectType();
259
+ }
256
260
  else if (isAnyHybridSubclass(type)) {
257
261
  // It is another HybridObject being referenced!
258
262
  const typename = getHybridObjectName(type);
@@ -263,10 +267,6 @@ export function createType(language, type, isOptional) {
263
267
  const sourceConfig = getHybridObjectNitroModuleConfig(type) ?? NitroConfig.current;
264
268
  return new HybridObjectType(typename, language, baseHybrids, sourceConfig);
265
269
  }
266
- else if (isDirectlyHybridObject(type)) {
267
- // It is a HybridObject directly/literally. Base type
268
- return new HybridObjectBaseType();
269
- }
270
270
  else if (type.isInterface()) {
271
271
  // It is an `interface T { ... }`, which is a `struct`
272
272
  const typename = type.getSymbolOrThrow().getName();
@@ -0,0 +1,11 @@
1
+ import type { Language } from '../getPlatformSpecs.js';
2
+ import type { HybridObjectSpec } from './HybridObjectSpec.js';
3
+ /**
4
+ * Returns true when the given {@linkcode memberName} is overriding a
5
+ * property or method from any base class inside the given
6
+ * {@linkcode hybridObject}'s prototype chain (all the way up).
7
+ *
8
+ * For example, `"toString"` would return `true` since it overrides from base HybridObject.
9
+ * On Kotlin, `"hashCode"` would return `true` since it overrides from base `kotlin.Any`.
10
+ */
11
+ export declare function isMemberOverridingFromBase(memberName: string, hybridObject: HybridObjectSpec, language: Language): boolean;
@@ -0,0 +1,64 @@
1
+ function getMemberNamesOfBaseType(language) {
2
+ switch (language) {
3
+ case 'c++':
4
+ // C++ classes don't have any base type.
5
+ return [];
6
+ case 'swift':
7
+ // Swift classes conform to `AnyObject`, but that doesn't have any properties
8
+ return [];
9
+ case 'kotlin':
10
+ // Kotlin/JVM classes always extends `Any`, which has 3 methods
11
+ return ['toString', 'equals', 'hashCode'];
12
+ }
13
+ }
14
+ function getMemberNamesOfHybridObject() {
15
+ const allKeys = {
16
+ __type: true,
17
+ dispose: true,
18
+ equals: true,
19
+ name: true,
20
+ toString: true,
21
+ };
22
+ return Object.keys(allKeys);
23
+ }
24
+ function flatBaseTypes(type) {
25
+ return type.baseTypes.flatMap((b) => [b, ...flatBaseTypes(b)]);
26
+ }
27
+ /**
28
+ * Returns true when the given {@linkcode memberName} is overriding a
29
+ * property or method from any base class inside the given
30
+ * {@linkcode hybridObject}'s prototype chain (all the way up).
31
+ *
32
+ * For example, `"toString"` would return `true` since it overrides from base HybridObject.
33
+ * On Kotlin, `"hashCode"` would return `true` since it overrides from base `kotlin.Any`.
34
+ */
35
+ export function isMemberOverridingFromBase(memberName, hybridObject, language) {
36
+ // 1. Check if the HybridObject inherits from other HybridObjects,
37
+ // if yes, check if those have properties of that given name.
38
+ const allBases = flatBaseTypes(hybridObject);
39
+ const anyBaseOverrides = allBases.some((h) => {
40
+ if (h.properties.some((p) => p.name === memberName)) {
41
+ return true;
42
+ }
43
+ if (h.methods.some((m) => m.name === memberName)) {
44
+ return true;
45
+ }
46
+ return false;
47
+ });
48
+ if (anyBaseOverrides) {
49
+ // A HybridObject base type has the same property name - we need to override it.
50
+ return true;
51
+ }
52
+ // 2. Check if the base `HybridObject` type contains a property of the given name
53
+ const baseHybridObjectProps = getMemberNamesOfHybridObject();
54
+ if (baseHybridObjectProps.includes(memberName)) {
55
+ return true;
56
+ }
57
+ // 3. Check if the base type in our language contains a property of the given name
58
+ const baseTypeProps = getMemberNamesOfBaseType(language);
59
+ if (baseTypeProps.includes(memberName)) {
60
+ return true;
61
+ }
62
+ // 4. Apparently no base type has a property of that name - we are safe!
63
+ return false;
64
+ }
@@ -0,0 +1,11 @@
1
+ import type { Language } from '../getPlatformSpecs.js';
2
+ import type { HybridObjectSpec } from './HybridObjectSpec.js';
3
+ /**
4
+ * Returns true when the given {@linkcode memberName} is overriding a
5
+ * property or method from any base class inside the given
6
+ * {@linkcode hybridObject}'s prototype chain (all the way up).
7
+ *
8
+ * For example, `"toString"` would return `true` since it overrides from base HybridObject.
9
+ * On Kotlin, `"hashCode"` would return `true` since it overrides from base `kotlin.Any`.
10
+ */
11
+ export declare function isMemberOverridingFromBase(memberName: string, hybridObject: HybridObjectSpec, language: Language): boolean;
@@ -0,0 +1,64 @@
1
+ function getMemberNamesOfBaseType(language) {
2
+ switch (language) {
3
+ case 'c++':
4
+ // C++ classes don't have any base type.
5
+ return [];
6
+ case 'swift':
7
+ // Swift classes conform to `AnyObject`, but that doesn't have any properties
8
+ return [];
9
+ case 'kotlin':
10
+ // Kotlin/JVM classes always extends `Any`, which has 3 methods
11
+ return ['toString', 'equals', 'hashCode'];
12
+ }
13
+ }
14
+ function getMemberNamesOfHybridObject() {
15
+ const allKeys = {
16
+ __type: true,
17
+ dispose: true,
18
+ equals: true,
19
+ name: true,
20
+ toString: true,
21
+ };
22
+ return Object.keys(allKeys);
23
+ }
24
+ function flatBaseTypes(type) {
25
+ return type.baseTypes.flatMap((b) => [b, ...flatBaseTypes(b)]);
26
+ }
27
+ /**
28
+ * Returns true when the given {@linkcode memberName} is overriding a
29
+ * property or method from any base class inside the given
30
+ * {@linkcode hybridObject}'s prototype chain (all the way up).
31
+ *
32
+ * For example, `"toString"` would return `true` since it overrides from base HybridObject.
33
+ * On Kotlin, `"hashCode"` would return `true` since it overrides from base `kotlin.Any`.
34
+ */
35
+ export function isMemberOverridingFromBase(memberName, hybridObject, language) {
36
+ // 1. Check if the HybridObject inherits from other HybridObjects,
37
+ // if yes, check if those have properties of that given name.
38
+ const allBases = flatBaseTypes(hybridObject);
39
+ const anyBaseOverrides = allBases.some((h) => {
40
+ if (h.properties.some((p) => p.name === memberName)) {
41
+ return true;
42
+ }
43
+ if (h.methods.some((m) => m.name === memberName)) {
44
+ return true;
45
+ }
46
+ return false;
47
+ });
48
+ if (anyBaseOverrides) {
49
+ // A HybridObject base type has the same property name - we need to override it.
50
+ return true;
51
+ }
52
+ // 2. Check if the base `HybridObject` type contains a property of the given name
53
+ const baseHybridObjectProps = getMemberNamesOfHybridObject();
54
+ if (baseHybridObjectProps.includes(memberName)) {
55
+ return true;
56
+ }
57
+ // 3. Check if the base type in our language contains a property of the given name
58
+ const baseTypeProps = getMemberNamesOfBaseType(language);
59
+ if (baseTypeProps.includes(memberName)) {
60
+ return true;
61
+ }
62
+ // 4. Apparently no base type has a property of that name - we are safe!
63
+ return false;
64
+ }
@@ -94,6 +94,7 @@ ${spaces} public virtual ${name.HybridTSpec} {
94
94
  public:
95
95
  size_t getExternalMemorySize() noexcept override;
96
96
  void dispose() noexcept override;
97
+ std::string toString() override;
97
98
 
98
99
  public:
99
100
  inline const jni::global_ref<${name.JHybridTSpec}::javaobject>& getJavaPart() const noexcept {
@@ -176,6 +177,12 @@ namespace ${cxxNamespace} {
176
177
  method(_javaPart);
177
178
  }
178
179
 
180
+ std::string ${name.JHybridTSpec}::toString() {
181
+ static const auto method = javaClassStatic()->getMethod<jni::JString()>("toString");
182
+ auto javaString = method(_javaPart);
183
+ return javaString->toStdString();
184
+ }
185
+
179
186
  // Properties
180
187
  ${indent(propertiesImpl, ' ')}
181
188
 
@@ -83,6 +83,11 @@ abstract class ${name.HybridTSpec}: ${kotlinBase}() {
83
83
  super.updateNative(hybridData)
84
84
  }
85
85
 
86
+ // Default implementation of \`HybridObject.toString()\`
87
+ override fun toString(): String {
88
+ return "[HybridObject ${name.T}]"
89
+ }
90
+
86
91
  // Properties
87
92
  ${indent(properties, ' ')}
88
93
 
@@ -173,7 +178,9 @@ set(value) {
173
178
  }
174
179
  `.trim());
175
180
  }
176
- const code = property.getCode('kotlin', { virtual: true });
181
+ const code = property.getCode('kotlin', {
182
+ virtual: true,
183
+ });
177
184
  return `
178
185
  ${code}
179
186
 
@@ -182,7 +189,10 @@ private ${keyword} ${property.name}_cxx: ${bridged.getTypeCode('kotlin')}
182
189
  `.trim();
183
190
  }
184
191
  else {
185
- const code = property.getCode('kotlin', { doNotStrip: true, virtual: true });
192
+ const code = property.getCode('kotlin', {
193
+ doNotStrip: true,
194
+ virtual: true,
195
+ });
186
196
  return code;
187
197
  }
188
198
  }
@@ -6,29 +6,41 @@ import { createFileMetadataString, isNotDuplicate } from '../helpers.js';
6
6
  import { KotlinCxxBridgedType } from './KotlinCxxBridgedType.js';
7
7
  export function createKotlinStruct(structType) {
8
8
  const packageName = NitroConfig.current.getAndroidPackage('java/kotlin');
9
- const parameters = structType.properties
10
- .map((p) => `
9
+ const bridgedProperties = structType.properties.map((p) => ({
10
+ name: p.escapedName,
11
+ type: new KotlinCxxBridgedType(p),
12
+ }));
13
+ // const parameters = structType.properties
14
+ // .map((p) =>
15
+ // `
16
+ // @DoNotStrip
17
+ // @Keep
18
+ // val ${p.escapedName}: ${p.getCode('kotlin')}
19
+ // `.trim()
20
+ // )
21
+ // .join(',\n')
22
+ const properties = bridgedProperties
23
+ .map(({ name, type }) => {
24
+ return `
11
25
  @DoNotStrip
12
26
  @Keep
13
- val ${p.escapedName}: ${p.getCode('kotlin')}
14
- `.trim())
27
+ val ${name}: ${type.getTypeCode('kotlin', false)}
28
+ `.trim();
29
+ })
15
30
  .join(',\n');
16
- const cxxCreateFunctionParameters = structType.properties
17
- .map((p) => {
18
- const bridged = new KotlinCxxBridgedType(p);
19
- return `${p.escapedName}: ${bridged.getTypeCode('kotlin', false)}`;
31
+ const cxxCreateFunctionParameters = bridgedProperties
32
+ .map(({ name, type }) => {
33
+ return `${name}: ${type.getTypeCode('kotlin', false)}`;
20
34
  })
21
35
  .join(', ');
22
- const cxxCreateFunctionForward = structType.properties
23
- .map((p) => {
24
- const bridged = new KotlinCxxBridgedType(p);
25
- return bridged.parseFromCppToKotlin(p.escapedName, 'kotlin', false);
26
- })
36
+ const cxxCreateFunctionForward = bridgedProperties
37
+ .map((p) => p.name)
27
38
  .join(', ');
28
39
  const extraImports = structType.properties
29
40
  .flatMap((t) => t.getRequiredImports('kotlin'))
30
41
  .map((i) => `import ${i.name}`)
31
42
  .filter(isNotDuplicate);
43
+ const secondaryConstructor = createKotlinConstructor(structType);
32
44
  const code = `
33
45
  ${createFileMetadataString(`${structType.structName}.kt`)}
34
46
 
@@ -44,8 +56,10 @@ ${extraImports.join('\n')}
44
56
  @DoNotStrip
45
57
  @Keep
46
58
  data class ${structType.structName}(
47
- ${indent(parameters, ' ')}
59
+ ${indent(properties, ' ')}
48
60
  ) {
61
+ ${indent(secondaryConstructor, ' ')}
62
+
49
63
  private companion object {
50
64
  /**
51
65
  * Constructor called from C++
@@ -132,6 +146,27 @@ namespace ${cxxNamespace} {
132
146
  });
133
147
  return files;
134
148
  }
149
+ function createKotlinConstructor(structType) {
150
+ const bridgedProperties = structType.properties.map((p) => ({
151
+ name: p.escapedName,
152
+ type: new KotlinCxxBridgedType(p),
153
+ }));
154
+ const needsSpecialHandling = bridgedProperties.some(({ type }) => type.needsSpecialHandling);
155
+ if (needsSpecialHandling) {
156
+ const kotlinParams = structType.properties.map((p) => `${p.escapedName}: ${p.getCode('kotlin')}`);
157
+ const paramsForward = bridgedProperties.map(({ name, type }) => type.parseFromKotlinToCpp(name, 'kotlin'));
158
+ return `
159
+ /**
160
+ * Create a new instance of ${structType.structName} from Kotlin
161
+ */
162
+ constructor(${kotlinParams.join(', ')}):
163
+ this(${paramsForward.join(', ')})
164
+ `.trim();
165
+ }
166
+ else {
167
+ return `/* primary constructor */`;
168
+ }
169
+ }
135
170
  function createJNIStructInitializer(structType) {
136
171
  const lines = ['static const auto clazz = javaClassStatic();'];
137
172
  for (const prop of structType.properties) {
@@ -0,0 +1,7 @@
1
+ import type { Method } from '../Method.js';
2
+ /**
3
+ * Returns `true` if the given {@linkcode method} is a
4
+ * method that exists in the base `Object` type in Java.
5
+ * If this is true, it needs an `override` modifier.
6
+ */
7
+ export declare function isBaseObjectMethodName(method: Method): boolean;
@@ -0,0 +1,9 @@
1
+ const kotlinAnyMethodNames = ['equals', 'hashCode', 'toString'];
2
+ /**
3
+ * Returns `true` if the given {@linkcode method} is a
4
+ * method that exists in the base `Object` type in Java.
5
+ * If this is true, it needs an `override` modifier.
6
+ */
7
+ export function isBaseObjectMethodName(method) {
8
+ return kotlinAnyMethodNames.includes(method.name);
9
+ }
@@ -22,7 +22,6 @@ import { VoidType } from '../types/VoidType.js';
22
22
  import { NamedWrappingType } from '../types/NamedWrappingType.js';
23
23
  import { ErrorType } from '../types/ErrorType.js';
24
24
  import { createSwiftFunctionBridge } from './SwiftFunction.js';
25
- import { isPrimitivelyCopyable } from './isPrimitivelyCopyable.js';
26
25
  // TODO: Remove enum bridge once Swift fixes bidirectional enums crashing the `-Swift.h` header.
27
26
  export class SwiftCxxBridgedType {
28
27
  type;
@@ -449,21 +448,8 @@ export class SwiftCxxBridgedType {
449
448
  const wrapping = new SwiftCxxBridgedType(array.itemType, true);
450
449
  switch (language) {
451
450
  case 'swift':
452
- if (isPrimitivelyCopyable(array.itemType)) {
453
- // We can primitively copy the data, raw:
454
- const bridge = this.getBridgeOrThrow();
455
- const getDataFunc = `bridge.get_data_${bridge.specializationName}`;
456
- return `
457
- { () -> ${array.getCode('swift')} in
458
- let __data = ${getDataFunc}(${cppParameterName})
459
- let __size = ${cppParameterName}.size()
460
- return Array(UnsafeBufferPointer(start: __data, count: __size))
461
- }()`.trim();
462
- }
463
- else {
464
- // We have to iterate the element one by one to create a resulting Array (mapped)
465
- return `${cppParameterName}.map({ __item in ${wrapping.parseFromCppToSwift('__item', 'swift')} })`.trim();
466
- }
451
+ // We have to iterate the element one by one to create a resulting Array (mapped)
452
+ return `${cppParameterName}.map({ __item in ${wrapping.parseFromCppToSwift('__item', 'swift')} })`.trim();
467
453
  default:
468
454
  return cppParameterName;
469
455
  }
@@ -713,18 +699,9 @@ case ${i}:
713
699
  const wrapping = new SwiftCxxBridgedType(array.itemType, true);
714
700
  switch (language) {
715
701
  case 'swift':
716
- if (isPrimitivelyCopyable(array.itemType)) {
717
- // memory can be copied primitively
718
- const copyFunc = `bridge.${bridge.funcName}`;
719
- return `
720
- ${swiftParameterName}.withUnsafeBufferPointer { __pointer -> bridge.${bridge.specializationName} in
721
- return ${copyFunc}(__pointer.baseAddress!, ${swiftParameterName}.count)
722
- }`.trim();
723
- }
724
- else {
725
- // array has to be iterated and converted one-by-one
726
- const makeFunc = `bridge.${bridge.funcName}`;
727
- return `
702
+ // array has to be iterated and converted one-by-one
703
+ const makeFunc = `bridge.${bridge.funcName}`;
704
+ return `
728
705
  { () -> bridge.${bridge.specializationName} in
729
706
  var __vector = ${makeFunc}(${swiftParameterName}.count)
730
707
  for __item in ${swiftParameterName} {
@@ -732,7 +709,6 @@ ${swiftParameterName}.withUnsafeBufferPointer { __pointer -> bridge.${bridge.spe
732
709
  }
733
710
  return __vector
734
711
  }()`.trim();
735
- }
736
712
  default:
737
713
  return swiftParameterName;
738
714
  }