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
4 changes: 2 additions & 2 deletions docs/docs/concepts/nitrogen.md
Original file line number Diff line number Diff line change
Expand Up @@ -320,8 +320,8 @@ To implement `Math` now, you just need to implement the spec:
<TabItem value="kotlin" label="Kotlin">
```kotlin title="HybridMath.kt"
class HybridMath : HybridMathSpec() {
companion object {
fun add(a: Double, b: Double): Double {
companion object : HybridMathSpec.Companion {
override fun add(a: Double, b: Double): Double {
return a + b
}
}
Expand Down
7 changes: 4 additions & 3 deletions packages/nitrogen/src/syntax/StaticMethod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import type { Language } from '../getPlatformSpecs.js'
import { Method, type MethodOptions, type MethodSignatureOptions } from './Method.js'

export interface StaticMethodOptions extends MethodOptions {
/** Swift only: declare a static protocol requirement rather than a class method. */
readonly protocolRequirement?: boolean
/** Swift only: overrides the superclass's `class func` with the same signature. */
readonly override?: boolean
readonly virtual?: never
Expand Down Expand Up @@ -29,9 +31,8 @@ export class StaticMethod extends Method<StaticMethodOptions> {
case 'swift': {
let throws = ''
if (options.throwing !== false) throws = ' throws'
// Swift only declares static methods on classes, where a subclass can
// override a `class func` with the same signature.
let signature = `class func ${this.name}(${parameters.join(', ')})${throws} -> ${returnType}`
const keyword = options.protocolRequirement ? 'static' : 'class'
let signature = `${keyword} func ${this.name}(${parameters.join(', ')})${throws} -> ${returnType}`
if (options.override) signature = `override ${signature}`
if (options.access != null) signature = `${options.access} ${signature}`
if (options.inline) {
Expand Down
14 changes: 13 additions & 1 deletion packages/nitrogen/src/syntax/StaticProperty.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
import { Property } from './Property.js'
import type { Language } from '../getPlatformSpecs.js'
import { Property, type PropertyBody, type PropertyModifiers } from './Property.js'
import { StaticMethod } from './StaticMethod.js'

/** A class property exposed through native static getter and setter functions. */
export class StaticProperty extends Property {
override getCode(language: Language, modifiers?: PropertyModifiers, body?: PropertyBody): string {
const declaration = super.getCode(language, modifiers, body)
switch (language) {
case 'swift':
return `static ${declaration}`
case 'c++':
case 'kotlin':
return declaration
}
}

getStaticMethods(): [getter: StaticMethod] | [getter: StaticMethod, setter: StaticMethod] {
const methods = this.getCppMethods().map(
(method) =>
Expand Down
33 changes: 29 additions & 4 deletions packages/nitrogen/src/syntax/kotlin/KotlinHybridObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,22 @@ export function createKotlinHybridObject(publicSpec: HybridObjectSpec<'kotlin'>)
const name = getHybridObjectName(spec.name)
const properties = spec.properties.map(getKotlinPropertyCode).join('\n\n')
const methods = spec.methods.map(getKotlinMethodCode).join('\n\n')
const companion = getKotlinCompanion(spec)
const companionProperties = spec.staticProperties.map((property) => property.getCode('kotlin')).join('\n\n')
const companionMethods = spec.staticMethods
.map((method) => {
const requirement = new InstanceMethod(method.name, method.returnType, method.parameters)
return requirement.getDeclaration('kotlin')
})
.join('\n\n')
const bridgeCompanion = getKotlinCompanion(spec)

const extraImports: SourceImport[] = [
...spec.properties.flatMap((p) => p.getRequiredImports('kotlin')),
...spec.staticProperties.flatMap((p) => p.getRequiredImports('kotlin')),
...spec.methods.flatMap((m) => m.getRequiredImports('kotlin')),
...spec.staticMethods.flatMap((m) => m.getRequiredImports('kotlin')),
...(spec.constructor?.getRequiredImports('kotlin') ?? []),
...companion.imports,
...bridgeCompanion.imports,
...spec.baseTypes.flatMap((b) => new HybridObjectType(b).getRequiredImports('kotlin')),
{
name: 'com.margelo.nitro.core.HybridObject',
Expand Down Expand Up @@ -125,8 +132,16 @@ abstract class ${name.HybridTSpec}: ${supertypes} {
return CxxPart(this)
}

companion object {
${indent(companion.code, ' ')}
interface Companion {
// Static Properties
${indent(companionProperties, ' ')}

// Static Methods
${indent(companionMethods, ' ')}
}

protected companion object Bridge {
${indent(bridgeCompanion.code, ' ')}
}
}
`.trim()
Expand Down Expand Up @@ -193,6 +208,16 @@ protected const val TAG = "${name.HybridTSpec}"
})
}
const sections = [tagCode]
// Only static members require a companion; instance-only specs may have no configured implementation.
if (spec.staticMethods.length > 0 || spec.staticProperties.length > 0) {
const implementation = getKotlinImplementation(spec)
// Check the configured companion's contract; forwarders still call it directly.
const companionRequirement = `
private val implementationCompanion: Companion
get() = ${implementation.className}
`.trim()
sections.push(companionRequirement)
}
Comment thread
mrousavy marked this conversation as resolved.
Resolved
for (const group of groups) {
const forwarders = group.callables
.flatMap((callable) => callable.createBridge().kotlinMethods)
Expand Down
21 changes: 21 additions & 0 deletions packages/nitrogen/src/syntax/swift/SwiftHybridObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { createFileMetadataString } from '../helpers.js'
import type { HybridObjectSpec } from '../HybridObjectSpec.js'
import type { SourceFile } from '../SourceFile.js'
import { HybridObjectType } from '../types/HybridObjectType.js'
import { StaticMethod } from '../StaticMethod.js'
import { createSwiftHybridObjectCxxBridge } from './SwiftHybridObjectBridge.js'
import { getImportsCode } from '../getImportCode.js'
import { getAllTypes } from '../getAllTypes.js'
Expand All @@ -18,8 +19,19 @@ export function createSwiftHybridObject(spec: HybridObjectSpec<'swift'>): Source
const conformances = swiftType.getProtocolConformances()
const name = getHybridObjectName(spec.name)
const protocolName = name.HybridTSpec
const constructor = spec.constructor?.getDeclaration('swift') ?? ''
const properties = spec.properties.map((p) => p.getCode('swift')).join('\n')
const methods = spec.methods.map((method) => method.getDeclaration('swift')).join('\n')
const staticProperties = spec.staticProperties.map((property) => property.getCode('swift')).join('\n')
const staticMethods = spec.staticMethods
.map((method) => {
const requirement = new StaticMethod(method.name, method.returnType, method.parameters, {
...method.options,
protocolRequirement: true,
})
return requirement.getDeclaration('swift')
})
.join('\n')
const extraImports = [
...spec.properties.flatMap((p) => p.getRequiredImports('swift')),
...spec.staticProperties.flatMap((p) => p.getRequiredImports('swift')),
Expand Down Expand Up @@ -86,11 +98,20 @@ ${imports}

/// See \`\`${protocolName}\`\`
public protocol ${protocolName}_protocol: ${protocolBaseClasses.join(', ')} {
// Constructor
${indent(constructor, ' ')}

// Properties
${indent(properties, ' ')}

// Methods
${indent(methods, ' ')}

// Static Properties
${indent(staticProperties, ' ')}

// Static Methods
${indent(staticMethods, ' ')}
}

public extension ${protocolName}_protocol {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import NitroModules
#endif

class HybridBenchmarkObjectSwift: HybridBenchmarkObjectSwiftKotlinSpec {
required override init() {
super.init()
}

private var pendingPromise: (any Promise<Double>)?
var numberValue: Double = 0.0

Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions tests/standalone/nitro-test-external/ios/HybridBase.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import NitroModules

class HybridBase: HybridBaseSpec {
required override init() {
super.init()
}

var baseValue: Double {
return 20
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
import NitroModules

class HybridSomeExternalObject: HybridSomeExternalObjectSpec {
required override init() {
super.init()
}

func getValue() throws -> String {
return "Hello world!"
}
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading