diff --git a/packages/nitrogen/src/syntax/createType.ts b/packages/nitrogen/src/syntax/createType.ts index cadd9ce8f..e05fec6a4 100644 --- a/packages/nitrogen/src/syntax/createType.ts +++ b/packages/nitrogen/src/syntax/createType.ts @@ -208,11 +208,18 @@ export function createType(type: TSMorphType, isOptional: boolean): Type { ) return new EnumType(typename, enumDeclaration) } else if (type.isUnion()) { - // It is some kind of union - either full of strings (then it's an enum, or different types, then it's a Variant) - const isEnumUnion = type.getUnionTypes().every((t) => t.isStringLiteral()) + // It is some kind of union; + // - of string literals (then it's an enum) + // - of type `T | undefined` (then it's just optional `T`) + // - of different types (then it's a variant `A | B | C`) + const types = type.getUnionTypes() + const nonNullTypes = types.filter( + (t) => !t.isNull() && !t.isUndefined() && !t.isVoid() + ) + const isEnumUnion = nonNullTypes.every((t) => t.isStringLiteral()) if (isEnumUnion) { // It consists only of string literaly - that means it's describing an enum! - const symbol = type.getAliasSymbol() + const symbol = type.getNonNullableType().getAliasSymbol() if (symbol == null) { // If there is no alias, it is an inline union instead of a separate type declaration! throw new Error( @@ -258,9 +265,7 @@ export function createType(type: TSMorphType, isOptional: boolean): Type { `Anonymous objects cannot be represented in C++! Extract "${type.getText()}" to a separate interface/type declaration.` ) } else if (type.isStringLiteral()) { - throw new Error( - `String literal ${type.getText()} cannot be represented in C++ because it is ambiguous between a string and a discriminating union enum.` - ) + return new StringType() } else { throw new Error( `The TypeScript type "${type.getText()}" cannot be represented in C++!` diff --git a/packages/nitrogen/src/syntax/types/EnumType.ts b/packages/nitrogen/src/syntax/types/EnumType.ts index 3395654a3..8a2278548 100644 --- a/packages/nitrogen/src/syntax/types/EnumType.ts +++ b/packages/nitrogen/src/syntax/types/EnumType.ts @@ -50,24 +50,27 @@ export class EnumType implements Type { } else { // It's a TS union '..' | '..' this.jsType = 'union' - this.enumMembers = declaration.getUnionTypes().map((t, i) => { - if (t.isStringLiteral()) { - const literalValue = t.getLiteralValueOrThrow() - if (typeof literalValue !== 'string') + this.enumMembers = declaration + .getNonNullableType() + .getUnionTypes() + .map((t, i) => { + if (t.isStringLiteral()) { + const literalValue = t.getLiteralValueOrThrow() + if (typeof literalValue !== 'string') + throw new Error( + `${enumName}: Value "${literalValue}" is not a string - it is ${typeof literalValue}!` + ) + return { + name: escapeCppName(literalValue).toUpperCase(), + value: i, + stringValue: literalValue, + } + } else { throw new Error( - `${enumName}: Value "${literalValue}" is not a string - it is ${typeof literalValue}!` + `${enumName}: Value "${t.getText()}" is not a string literal - it cannot be represented in a C++ enum!` ) - return { - name: escapeCppName(literalValue).toUpperCase(), - value: i, - stringValue: literalValue, } - } else { - throw new Error( - `${enumName}: Value "${t.getText()}" is not a string literal - it cannot be represented in a C++ enum!` - ) - } - }) + }) this.declarationFile = createCppUnion(enumName, this.enumMembers) } } diff --git a/packages/react-native-nitro-image/nitrogen/generated/android/c++/JHybridKotlinTestObjectSpec.cpp b/packages/react-native-nitro-image/nitrogen/generated/android/c++/JHybridKotlinTestObjectSpec.cpp index 1c3f893f8..c2363a0e7 100644 --- a/packages/react-native-nitro-image/nitrogen/generated/android/c++/JHybridKotlinTestObjectSpec.cpp +++ b/packages/react-native-nitro-image/nitrogen/generated/android/c++/JHybridKotlinTestObjectSpec.cpp @@ -176,5 +176,9 @@ namespace margelo::nitro::image { static const auto method = _javaPart->getClass()->getMethod /* callback */)>("addOnPersonBornListener"); method(_javaPart, JFunc_void_Person::fromCpp(callback)); } + void JHybridKotlinTestObjectSpec::something1(std::optional optional) { + static const auto method = _javaPart->getClass()->getMethod /* optional */)>("something1"); + method(_javaPart, optional.has_value() ? JPowertrain::fromCpp(optional.value()) : nullptr); + } } // namespace margelo::nitro::image diff --git a/packages/react-native-nitro-image/nitrogen/generated/android/c++/JHybridKotlinTestObjectSpec.hpp b/packages/react-native-nitro-image/nitrogen/generated/android/c++/JHybridKotlinTestObjectSpec.hpp index 179e99c1e..b12e8888c 100644 --- a/packages/react-native-nitro-image/nitrogen/generated/android/c++/JHybridKotlinTestObjectSpec.hpp +++ b/packages/react-native-nitro-image/nitrogen/generated/android/c++/JHybridKotlinTestObjectSpec.hpp @@ -56,6 +56,7 @@ namespace margelo::nitro::image { std::future asyncTest() override; std::shared_ptr createMap() override; void addOnPersonBornListener(const std::function& callback) override; + void something1(std::optional optional) override; private: friend HybridBase; diff --git a/packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/HybridKotlinTestObjectSpec.kt b/packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/HybridKotlinTestObjectSpec.kt index f35676636..7d4211d28 100644 --- a/packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/HybridKotlinTestObjectSpec.kt +++ b/packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/HybridKotlinTestObjectSpec.kt @@ -88,6 +88,10 @@ abstract class HybridKotlinTestObjectSpec: HybridObject() { val result = addOnPersonBornListener(callback.toLambda()) return result } + + @DoNotStrip + @Keep + abstract fun something1(optional: Powertrain?): Unit private external fun initHybrid(): HybridData diff --git a/packages/react-native-nitro-image/nitrogen/generated/ios/NitroImage-Swift-Cxx-Bridge.hpp b/packages/react-native-nitro-image/nitrogen/generated/ios/NitroImage-Swift-Cxx-Bridge.hpp index becbaeb2f..c42f124f3 100644 --- a/packages/react-native-nitro-image/nitrogen/generated/ios/NitroImage-Swift-Cxx-Bridge.hpp +++ b/packages/react-native-nitro-image/nitrogen/generated/ios/NitroImage-Swift-Cxx-Bridge.hpp @@ -556,5 +556,13 @@ namespace margelo::nitro::image::bridge::swift { inline std::shared_ptr share_Func_void_Person(const Func_void_Person& value) { return std::make_shared(value); } + + /** + * Specialized version of `std::optional`. + */ + using std__optional_Powertrain_ = std::optional; + inline std::optional create_std__optional_Powertrain_(const Powertrain& value) { + return std::optional(value); + } } // namespace margelo::nitro::image::bridge::swift diff --git a/packages/react-native-nitro-image/nitrogen/generated/shared/c++/HybridKotlinTestObjectSpec.cpp b/packages/react-native-nitro-image/nitrogen/generated/shared/c++/HybridKotlinTestObjectSpec.cpp index 15586ed8e..6061111f4 100644 --- a/packages/react-native-nitro-image/nitrogen/generated/shared/c++/HybridKotlinTestObjectSpec.cpp +++ b/packages/react-native-nitro-image/nitrogen/generated/shared/c++/HybridKotlinTestObjectSpec.cpp @@ -29,6 +29,7 @@ namespace margelo::nitro::image { prototype.registerHybridMethod("asyncTest", &HybridKotlinTestObjectSpec::asyncTest); prototype.registerHybridMethod("createMap", &HybridKotlinTestObjectSpec::createMap); prototype.registerHybridMethod("addOnPersonBornListener", &HybridKotlinTestObjectSpec::addOnPersonBornListener); + prototype.registerHybridMethod("something1", &HybridKotlinTestObjectSpec::something1); }); } diff --git a/packages/react-native-nitro-image/nitrogen/generated/shared/c++/HybridKotlinTestObjectSpec.hpp b/packages/react-native-nitro-image/nitrogen/generated/shared/c++/HybridKotlinTestObjectSpec.hpp index da69e0fb8..3ce2d5879 100644 --- a/packages/react-native-nitro-image/nitrogen/generated/shared/c++/HybridKotlinTestObjectSpec.hpp +++ b/packages/react-native-nitro-image/nitrogen/generated/shared/c++/HybridKotlinTestObjectSpec.hpp @@ -21,6 +21,8 @@ namespace NitroModules { class ArrayBuffer; } namespace NitroModules { class AnyMap; } // Forward declaration of `Person` to properly resolve imports. namespace margelo::nitro::image { struct Person; } +// Forward declaration of `Powertrain` to properly resolve imports. +namespace margelo::nitro::image { enum class Powertrain; } #include #include @@ -32,6 +34,7 @@ namespace margelo::nitro::image { struct Person; } #include #include #include "Person.hpp" +#include "Powertrain.hpp" namespace margelo::nitro::image { @@ -75,6 +78,7 @@ namespace margelo::nitro::image { virtual std::future asyncTest() = 0; virtual std::shared_ptr createMap() = 0; virtual void addOnPersonBornListener(const std::function& callback) = 0; + virtual void something1(std::optional optional) = 0; protected: // Hybrid Setup diff --git a/packages/react-native-nitro-image/src/specs/TestObject.nitro.ts b/packages/react-native-nitro-image/src/specs/TestObject.nitro.ts index 6a031eaa2..2c590185f 100644 --- a/packages/react-native-nitro-image/src/specs/TestObject.nitro.ts +++ b/packages/react-native-nitro-image/src/specs/TestObject.nitro.ts @@ -167,5 +167,7 @@ export interface KotlinTestObject extends HybridObject<{ android: 'kotlin' }> { addOnPersonBornListener(callback: (p: Person) => void): void + something1(optional?: Powertrain): void + someRecord: Record }