Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 31 additions & 7 deletions example/src/getTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1023,7 +1023,7 @@ export function getTests(
// @ts-expect-error
(testObject.someVariant = false)
).didThrow(
`Error: ${testObject.name}.someVariant: Cannot convert "false" to any type in ${debugOnly('variant<std::string, double>!')}`
`Error: ${testObject.name}.someVariant: Cannot convert "false" to any type in ${debugOnly('variant<double, std::string>!')}`
)
),

Expand Down Expand Up @@ -1147,15 +1147,39 @@ export function getTests(
createTest('getVariantEnum(...) throws at wrong type (string)', () =>
// @ts-expect-error
it(() => testObject.getVariantEnum('string')).didThrow(
`Error: ${testObject.name}.getVariantEnum(...): Cannot convert "string" to any type in ${debugOnly('variant<bool, margelo::nitro::test::OldEnum>!')}`
`Error: ${testObject.name}.getVariantEnum(...): Cannot convert "string" to any type in ${debugOnly('variant<margelo::nitro::test::OldEnum, bool>!')}`
)
),
createTest('getVariantEnum(...) throws at too high numerical value', () =>
// @ts-expect-error
it(() => testObject.getVariantEnum(9999)).didThrow(
`Error: ${testObject.name}.getVariantEnum(...): Cannot convert "9999" to any type in ${debugOnly('variant<bool, margelo::nitro::test::OldEnum>!')}`
`Error: ${testObject.name}.getVariantEnum(...): Cannot convert "9999" to any type in ${debugOnly('variant<margelo::nitro::test::OldEnum, bool>!')}`
)
),
createTest("bounceVariantUnionEnum(...) converts 'gas'", () =>
it(() => testObject.bounceVariantUnionEnum('gas'))
.didNotThrow()
.equals('gas')
),
createTest('bounceVariantUnionEnum(...) converts Car', () =>
it(() => testObject.bounceVariantUnionEnum(TEST_CAR))
.didNotThrow()
.equals(TEST_CAR)
),
createTest('bounceVariantUnionEnum(...) throws at wrong string', () =>
// @ts-expect-error
it(() => testObject.bounceVariantUnionEnum('diesel')).didThrow()
),
createTest('bounceVariantUnionEnum(...) throws at wrong object', () =>
// @ts-expect-error
it(() => testObject.bounceVariantUnionEnum({})).didThrow()
),
createTest(
'bounceVariantUnionEnum(...) throws at wrong type (boolean)',
() =>
// @ts-expect-error
it(() => testObject.bounceVariantUnionEnum(true)).didThrow()
),
createTest('getVariantWeirdNumbersEnum(...) converts enum', () =>
it(() => testObject.getVariantWeirdNumbersEnum(WeirdNumbersEnum.C))
.didNotThrow()
Expand All @@ -1171,15 +1195,15 @@ export function getTests(
() =>
// @ts-expect-error
it(() => testObject.getVariantWeirdNumbersEnum('string')).didThrow(
`Error: ${testObject.name}.getVariantWeirdNumbersEnum(...): Cannot convert "string" to any type in ${debugOnly('variant<bool, margelo::nitro::test::WeirdNumbersEnum>!')}`
`Error: ${testObject.name}.getVariantWeirdNumbersEnum(...): Cannot convert "string" to any type in ${debugOnly('variant<margelo::nitro::test::WeirdNumbersEnum, bool>!')}`
)
),
createTest(
'getVariantWeirdNumbersEnum(...) throws at too high numerical value',
() =>
// @ts-expect-error
it(() => testObject.getVariantWeirdNumbersEnum(99999)).didThrow(
`Error: ${testObject.name}.getVariantWeirdNumbersEnum(...): Cannot convert "99999" to any type in ${debugOnly('variant<bool, margelo::nitro::test::WeirdNumbersEnum>!')}`
`Error: ${testObject.name}.getVariantWeirdNumbersEnum(...): Cannot convert "99999" to any type in ${debugOnly('variant<margelo::nitro::test::WeirdNumbersEnum, bool>!')}`
)
),
createTest('getVariantObjects(...) converts Person', () =>
Expand All @@ -1202,7 +1226,7 @@ export function getTests(
createTest('getVariantObjects(...) throws at wrong type (string)', () =>
// @ts-expect-error
it(() => testObject.getVariantObjects('some-string')).didThrow(
`Error: ${testObject.name}.getVariantObjects(...): Cannot convert "some-string" to any type in ${debugOnly('variant<margelo::nitro::test::Car, margelo::nitro::test::Person>!')}`
`Error: ${testObject.name}.getVariantObjects(...): Cannot convert "some-string" to any type in ${debugOnly('variant<margelo::nitro::test::Person, margelo::nitro::test::Car>!')}`
)
),
createTest(
Expand All @@ -1212,7 +1236,7 @@ export function getTests(
// @ts-expect-error
testObject.getVariantObjects({ someValue: 55 })
).didThrow(
`Error: ${testObject.name}.getVariantObjects(...): Cannot convert "[object Object]" to any type in ${debugOnly('variant<margelo::nitro::test::Car, margelo::nitro::test::Person>!')}`
`Error: ${testObject.name}.getVariantObjects(...): Cannot convert "[object Object]" to any type in ${debugOnly('variant<margelo::nitro::test::Person, margelo::nitro::test::Car>!')}`
)
),
createTest('getVariantHybrid(...) converts Hybrid', () =>
Expand Down
14 changes: 12 additions & 2 deletions packages/nitrogen/src/createPlatformSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,14 +113,24 @@ function getHybridObjectSpec(type: Type, language: Language): HybridObjectSpec {
const t = declaration.getType()
const isOptional =
prop.isOptional() || t.getUnionTypes().some((u) => u.isUndefined())
const propType = createType(language, t, isOptional)
const propType = createType(
language,
t,
isOptional,
declaration.getTypeNode()
)
properties.push(
new Property(prop.getName(), propType, declaration.isReadonly())
)
} else if (Node.isMethodSignature(declaration)) {
const returnType = declaration.getReturnType()
const isOptional = returnType.getUnionTypes().some((t) => t.isUndefined())
const methodReturnType = createType(language, returnType, isOptional)
const methodReturnType = createType(
language,
returnType,
isOptional,
declaration.getReturnTypeNode()
)
const methodParameters = declaration
.getParameters()
.map((p) => new Parameter(p, language))
Expand Down
8 changes: 7 additions & 1 deletion packages/nitrogen/src/syntax/Parameter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,13 @@ export class Parameter implements CodeNode {
const type = param.getType()
const isOptional =
param.hasQuestionToken() || param.isOptional() || type.isNullable()
this.type = createNamedType(language, name, type, isOptional)
this.type = createNamedType(
language,
name,
type,
isOptional,
param.getTypeNode()
)
} else {
// constructor(...)???
throw new Error(`Invalid constructor! Arguments: ${args}`)
Expand Down
80 changes: 67 additions & 13 deletions packages/nitrogen/src/syntax/createType.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ts, Type as TSMorphType, type Signature } from 'ts-morph'
import { Node, ts, Type as TSMorphType, type Signature } from 'ts-morph'
import type { Type } from './types/Type.js'
import { BooleanType } from './types/BooleanType.js'
import { NumberType } from './types/NumberType.js'
Expand Down Expand Up @@ -83,6 +83,54 @@ type Tuple<
N extends number,
R extends unknown[] = [],
> = R['length'] extends N ? R : Tuple<T, N, [T, ...R]>

type TypeNode = Node<ts.TypeNode>

interface UnionConstituent {
type: TSMorphType
typeNode?: TypeNode
}

function getAliasTypeNode(type: TSMorphType): TypeNode | undefined {
const symbol = type.getNonNullableType().getAliasSymbol()
const declaration = symbol?.getDeclarations()[0]
if (declaration != null && Node.isTypeAliasDeclaration(declaration)) {
return declaration.getTypeNode()
}
return undefined
}

function getUnionTypeNodes(
type: TSMorphType,
typeNode?: TypeNode
): TypeNode[] | undefined {
if (typeNode != null && Node.isUnionTypeNode(typeNode)) {
return typeNode.getTypeNodes()
}

const aliasTypeNode = getAliasTypeNode(type)
if (aliasTypeNode != null && Node.isUnionTypeNode(aliasTypeNode)) {
return aliasTypeNode.getTypeNodes()
}

return undefined
}

function getUnionConstituents(
type: TSMorphType,
typeNode?: TypeNode
): UnionConstituent[] {
const typeNodes = getUnionTypeNodes(type, typeNode)
if (typeNodes != null) {
return typeNodes.map((node) => ({
type: node.getType(),
typeNode: node,
}))
}

return type.getUnionTypes().map((unionType) => ({ type: unionType }))
}

function getArguments<N extends number>(
type: TSMorphType,
typename: string,
Expand All @@ -107,14 +155,18 @@ export function createNamedType(
language: Language,
name: string,
type: TSMorphType,
isOptional: boolean
isOptional: boolean,
typeNode?: TypeNode
) {
if (name.startsWith('__')) {
throw new Error(
`Name cannot start with two underscores (__) as this is reserved syntax for Nitrogen! (In ${type.getText()})`
)
}
return new NamedWrappingType(name, createType(language, type, isOptional))
return new NamedWrappingType(
name,
createType(language, type, isOptional, typeNode)
)
}

export function createVoidType(): Type {
Expand Down Expand Up @@ -171,7 +223,8 @@ export function addKnownType(
export function createType(
language: Language,
type: TSMorphType,
isOptional: boolean
isOptional: boolean,
typeNode?: TypeNode

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really don't like the fact that we have to pass in a new optional parameter (TypeNode) here now. This seems super hacky.
But not sure if we can do anything to prevent this tbh.

): Type {
const key = getTypeId(type, isOptional)
if (key != null && knownTypes[language].has(key)) {
Expand All @@ -183,7 +236,7 @@ export function createType(

const get = () => {
if (isOptional) {
const wrapping = createType(language, type, false)
const wrapping = createType(language, type, false, typeNode)
return new OptionalType(wrapping)
}

Expand Down Expand Up @@ -286,10 +339,10 @@ export function createType(
// - 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 unionConstituents = getUnionConstituents(type, typeNode)
const nonNullTypes = unionConstituents
.map((c) => c.type)
.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!
Expand All @@ -305,11 +358,12 @@ export function createType(
return new EnumType(typename, type)
} else {
// It consists of different types - that means it's a variant!
let variants = type
.getUnionTypes()
let variants = unionConstituents
// Filter out any undefineds/voids, as those are already treated as `isOptional`.
.filter((t) => !t.isUndefined() && !t.isVoid())
.map((t) => createType(language, t, false))
.filter(({ type: t }) => !t.isUndefined() && !t.isVoid())
.map(({ type: t, typeNode: node }) =>
createType(language, t, false, node)
)
.toSorted(compareLooselyness)
variants = removeDuplicates(variants)

Expand Down
8 changes: 6 additions & 2 deletions packages/nitrogen/src/syntax/getInterfaceProperties.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ts, Type } from 'ts-morph'
import { Node, type ts, type Type } from 'ts-morph'
import type { NamedType } from './types/Type.js'
import { createNamedType } from './createType.js'
import type { Language } from '../getPlatformSpecs.js'
Expand All @@ -13,6 +13,9 @@ export function getInterfaceProperties(
`Interface "${interfaceType.getText()}" does not have a Symbol!`
)
return interfaceType.getProperties().map((prop) => {
const propDeclaration = prop
.getDeclarations()
.find((declaration) => Node.isPropertySignature(declaration))
let propType = prop.getDeclaredType()
if (propType.isAny() || propType.isUnknown()) {
// the interface is aliased/merged - we need to look into the actual declaration
Expand All @@ -29,7 +32,8 @@ export function getInterfaceProperties(
language,
prop.getName(),
propType,
prop.isOptional() || propType.isNullable()
prop.isOptional() || propType.isNullable(),
propDeclaration?.getTypeNode()
)
return refType
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class HybridTestObjectKotlin : HybridTestObjectSwiftKotlinSpec() {
override var optionalHybrid: HybridTestObjectSwiftKotlinSpec? = null
override val thisObject: HybridTestObjectSwiftKotlinSpec
get() = this
override var someVariant: Variant_String_Double = Variant_String_Double.create(55.05)
override var someVariant: Variant_Double_String = Variant_Double_String.create(55.05)
override var optionalArray: Array<String>? = null
override var optionalEnum: Powertrain? = null
override var optionalOldEnum: OldEnum? = null
Expand Down Expand Up @@ -497,13 +497,13 @@ class HybridTestObjectKotlin : HybridTestObjectSwiftKotlinSpec() {
return Promise.async { createArrayBuffer() }
}

override fun passVariant(either: Variant_Boolean_DoubleArray_Array_String__String_Double): Variant_String_Double {
override fun passVariant(either: Variant_Boolean_DoubleArray_Array_String__Double_String): Variant_Double_String {
return either.match(
{ bool -> Variant_String_Double.create("holds something else!") },
{ doubleArray -> Variant_String_Double.create("holds something else!") },
{ stringArray -> Variant_String_Double.create("holds something else!") },
{ string -> Variant_String_Double.create(string) },
{ double -> Variant_String_Double.create(double) },
{ bool -> Variant_Double_String.create("holds something else!") },
{ doubleArray -> Variant_Double_String.create("holds something else!") },
{ stringArray -> Variant_Double_String.create("holds something else!") },
{ double -> Variant_Double_String.create(double) },
{ string -> Variant_Double_String.create(string) },
)
}

Expand All @@ -515,15 +515,19 @@ class HybridTestObjectKotlin : HybridTestObjectSwiftKotlinSpec() {
return variant
}

override fun getVariantEnum(variant: Variant_Boolean_OldEnum): Variant_Boolean_OldEnum {
override fun getVariantEnum(variant: Variant_OldEnum_Boolean): Variant_OldEnum_Boolean {
return variant
}

override fun getVariantWeirdNumbersEnum(variant: Variant_Boolean_WeirdNumbersEnum): Variant_Boolean_WeirdNumbersEnum {
override fun bounceVariantUnionEnum(variant: Variant_Powertrain_Car): Variant_Powertrain_Car {
return variant
}

override fun getVariantObjects(variant: Variant_Car_Person): Variant_Car_Person {
override fun getVariantWeirdNumbersEnum(variant: Variant_WeirdNumbersEnum_Boolean): Variant_WeirdNumbersEnum_Boolean {
return variant
}

override fun getVariantObjects(variant: Variant_Person_Car): Variant_Person_Car {
return variant
}

Expand Down
23 changes: 16 additions & 7 deletions packages/react-native-nitro-test/cpp/HybridTestObjectCpp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,11 @@ void HybridTestObjectCpp::setOptionalHybrid(const std::optional<std::shared_ptr<
_optionalHybrid = optionalHybrid;
}

std::variant<std::string, double> HybridTestObjectCpp::getSomeVariant() {
std::variant<double, std::string> HybridTestObjectCpp::getSomeVariant() {
return _variant;
}

void HybridTestObjectCpp::setSomeVariant(const std::variant<std::string, double>& variant) {
void HybridTestObjectCpp::setSomeVariant(const std::variant<double, std::string>& variant) {
_variant = variant;
}

Expand Down Expand Up @@ -366,8 +366,8 @@ std::chrono::system_clock::time_point HybridTestObjectCpp::currentDate() {
return std::chrono::system_clock::now();
}

std::variant<std::string, double>
HybridTestObjectCpp::passVariant(const std::variant<bool, std::vector<double>, std::vector<std::string>, std::string, double>& either) {
std::variant<double, std::string>
HybridTestObjectCpp::passVariant(const std::variant<bool, std::vector<double>, std::vector<std::string>, double, std::string>& either) {
if (std::holds_alternative<std::string>(either)) {
return std::get<std::string>(either);
} else if (std::holds_alternative<double>(either)) {
Expand All @@ -386,15 +386,19 @@ ComplexVariant HybridTestObjectCpp::bounceComplexVariant(const ComplexVariant& v
return variant;
}

std::variant<bool, OldEnum> HybridTestObjectCpp::getVariantEnum(const std::variant<bool, OldEnum>& variant) {
std::variant<OldEnum, bool> HybridTestObjectCpp::getVariantEnum(const std::variant<OldEnum, bool>& variant) {
return variant;
}

std::variant<bool, WeirdNumbersEnum> HybridTestObjectCpp::getVariantWeirdNumbersEnum(const std::variant<bool, WeirdNumbersEnum>& variant) {
std::variant<Powertrain, Car> HybridTestObjectCpp::bounceVariantUnionEnum(const std::variant<Powertrain, Car>& variant) {
return variant;
}

std::variant<Car, Person> HybridTestObjectCpp::getVariantObjects(const std::variant<Car, Person>& variant) {
std::variant<WeirdNumbersEnum, bool> HybridTestObjectCpp::getVariantWeirdNumbersEnum(const std::variant<WeirdNumbersEnum, bool>& variant) {
return variant;
}

std::variant<Person, Car> HybridTestObjectCpp::getVariantObjects(const std::variant<Person, Car>& variant) {
return variant;
}

Expand All @@ -403,6 +407,11 @@ HybridTestObjectCpp::getVariantHybrid(const std::variant<std::shared_ptr<HybridT
return variant;
}

std::variant<std::shared_ptr<HybridTestObjectCppSpec>, Powertrain>
HybridTestObjectCpp::getVariantHybridEnum(const std::variant<std::shared_ptr<HybridTestObjectCppSpec>, Powertrain>& variant) {
return variant;
}

std::variant<std::tuple<double, double>, std::tuple<double, double, double>>
HybridTestObjectCpp::getVariantTuple(const std::variant<std::tuple<double, double>, std::tuple<double, double, double>>& variant) {
return variant;
Expand Down
Loading
Loading