Skip to content

Commit 565c859

Browse files
committed
fix(gen): kinded unions
1 parent a8136f5 commit 565c859

File tree

9 files changed

+376
-42
lines changed

9 files changed

+376
-42
lines changed

lib/gen/go.js

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ export function generateGo (schema, options = {}) {
121121
}
122122
fieldName = fixGoName(annotations, fieldName)
123123
const gotag = annotations.reduce((acc, a) => 'gotag' in a ? ' ' + a.gotag : acc, '')
124+
// Handle nullable fields by making them pointers
125+
if (fieldDefn.nullable) {
126+
fieldType = '*' + fieldType
127+
}
124128
typesrc += `\t${fieldName} ${fieldType}${gotag}${linecomment}\n`
125129
}
126130

@@ -129,7 +133,7 @@ export function generateGo (schema, options = {}) {
129133
// Add union methods for this struct if it's a union member
130134
if (unionMethods.has(typeName)) {
131135
for (const unionType of unionMethods.get(typeName) || []) {
132-
typesrc += `func (${typeName}) is${unionType}() {}\n\n`
136+
typesrc += `func (${typeName}) ${unionType.charAt(0).toLowerCase() + unionType.slice(1)}() {}\n\n`
133137
}
134138
}
135139
} else if ('list' in typeDefn) {
@@ -219,15 +223,34 @@ export function generateGo (schema, options = {}) {
219223
typesrc += ')\n\n'
220224
}
221225
} else if ('union' in typeDefn) {
222-
if (!typeDefn.union.representation || !('keyed' in typeDefn.union.representation)) {
223-
// For now, only support keyed unions
226+
if (!typeDefn.union.representation) {
224227
continue
225228
}
226229

227-
// Generate interface for the union
228-
typesrc += `type ${typeName} interface {\n`
229-
typesrc += `\tis${typeName}()\n`
230-
typesrc += '}\n\n'
230+
if ('keyed' in typeDefn.union.representation) {
231+
// Generate interface for the union
232+
typesrc += `type ${typeName} interface {\n`
233+
typesrc += `\t${typeName.charAt(0).toLowerCase() + typeName.slice(1)}()\n`
234+
typesrc += '}\n\n'
235+
} else if ('kinded' in typeDefn.union.representation) {
236+
// Generate interface for the union
237+
typesrc += `type ${typeName} interface {\n`
238+
typesrc += `\t${typeName.charAt(0).toLowerCase() + typeName.slice(1)}()\n`
239+
typesrc += '}\n\n'
240+
241+
// Generate wrapper types for each kind
242+
for (const [, memberType] of Object.entries(typeDefn.union.representation.kinded)) {
243+
// Generate wrapper type
244+
let goType = memberType
245+
// Check if memberType is a basic IPLD type
246+
if (['Bool', 'Int', 'Float', 'String', 'Bytes', 'Link'].includes(memberType)) {
247+
goType = getGoType([], memberType)
248+
goType = fixGoType(imports, goType, false)
249+
}
250+
typesrc += `type ${typeName}_${memberType} ${goType}\n`
251+
typesrc += `func (${typeName}_${memberType}) ${typeName.charAt(0).toLowerCase() + typeName.slice(1)}() {}\n\n`
252+
}
253+
}
231254
}
232255
}
233256

lib/gen/rust.js

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@ export function generateRust (schema) {
122122
}
123123
}
124124
fieldName = fixRustName(annotations, fieldName)
125+
// Handle nullable fields
126+
if (fieldDefn.nullable) {
127+
fieldType = `Option<${fieldType}>`
128+
}
125129
typesrc += ` pub ${fieldName}: ${fieldType},${linecomment}\n`
126130
}
127131

@@ -219,23 +223,44 @@ export function generateRust (schema) {
219223
typesrc += '}\n\n'
220224
}
221225
} else if ('union' in typeDefn) {
222-
if (!typeDefn.union.representation || !('keyed' in typeDefn.union.representation)) {
223-
// For now, only support keyed unions
226+
if (!typeDefn.union.representation) {
224227
continue
225228
}
226229

227-
// Generate Rust enum for keyed union
228-
typesrc += '#[derive(Deserialize, Serialize)]\n'
229-
typesrc += '#[serde(tag = "type")]\n'
230-
typesrc += `pub enum ${typeName} {\n`
230+
if ('keyed' in typeDefn.union.representation) {
231+
// Generate Rust enum for keyed union
232+
typesrc += '#[derive(Deserialize, Serialize)]\n'
233+
typesrc += '#[serde(tag = "type")]\n'
234+
typesrc += `pub enum ${typeName} {\n`
231235

232-
// Generate enum variants
233-
for (const [discriminant, memberType] of Object.entries(typeDefn.union.representation.keyed)) {
234-
typesrc += ` #[serde(rename = "${discriminant}")]\n`
235-
typesrc += ` ${memberType}(${memberType}),\n`
236-
}
236+
// Generate enum variants
237+
for (const [discriminant, memberType] of Object.entries(typeDefn.union.representation.keyed)) {
238+
typesrc += ` #[serde(rename = "${discriminant}")]\n`
239+
typesrc += ` ${memberType}(${memberType}),\n`
240+
}
237241

238-
typesrc += '}\n\n'
242+
typesrc += '}\n\n'
243+
} else if ('kinded' in typeDefn.union.representation) {
244+
// Generate Rust enum for kinded union
245+
typesrc += '#[derive(Deserialize, Serialize)]\n'
246+
typesrc += '#[serde(untagged)]\n'
247+
typesrc += `pub enum ${typeName} {\n`
248+
249+
// Generate enum variants based on kind
250+
for (const [, memberType] of Object.entries(typeDefn.union.representation.kinded)) {
251+
if (typeof memberType === 'string') {
252+
let rustType = memberType
253+
// Check if memberType is a basic IPLD type
254+
if (['Bool', 'Int', 'Float', 'String', 'Bytes', 'Link'].includes(memberType)) {
255+
rustType = getRustType([], memberType)
256+
rustType = fixRustType(imports, rustType, false)
257+
}
258+
typesrc += ` ${memberType}(${rustType}),\n`
259+
}
260+
}
261+
262+
typesrc += '}\n\n'
263+
}
239264
}
240265
}
241266

lib/gen/typescript.js

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export function generateTypeScript (schema) {
2424
const fieldValidators = []
2525
let requiredFieldCount = 0
2626
for (let [fieldName, fieldDefn] of Object.entries(typeDefn.struct.fields)) {
27-
if (!fieldDefn.optional && !fieldDefn.optional) {
27+
if (!fieldDefn.optional && !fieldDefn.nullable) {
2828
requiredFieldCount++
2929
}
3030
/** @type { { [k in string]: string }[]} */
@@ -104,8 +104,13 @@ export function generateTypeScript (schema) {
104104
}
105105
}
106106
fieldName = fixTypeScriptName(annotations, fieldName)
107-
typesrc += ` ${fieldName}${fieldDefn.optional ? '?' : ''}: ${fieldType}${linecomment}\n`
108-
const inCheck = !fieldDefn.optional && !fieldDefn.optional ? `'${fieldName}' in value &&` : `!('${fieldName}' in value) ||`
107+
// Handle nullable fields - they become optional with | null
108+
if (fieldDefn.nullable) {
109+
typesrc += ` ${fieldName}?: ${fieldType} | null${linecomment}\n`
110+
} else {
111+
typesrc += ` ${fieldName}${fieldDefn.optional ? '?' : ''}: ${fieldType}${linecomment}\n`
112+
}
113+
const inCheck = !fieldDefn.optional && !fieldDefn.nullable ? `'${fieldName}' in value &&` : `!('${fieldName}' in value) ||`
109114
if (isMap) {
110115
const kind = fixTypeScriptType(imports, '@ipld/schema/schema-schema.js#KindMap', false)
111116
let valueType = getTypeScriptType([], mapValueType)
@@ -253,16 +258,22 @@ export function generateTypeScript (schema) {
253258
typesrc += ' }\n'
254259
typesrc += '}\n\n'
255260
} else if ('kinded' in typeDefn.union.representation) {
256-
if (typeDefn.union.members.some((member) => typeof member !== 'string')) {
257-
throw new Error('Unhandled union member type(s): ' + JSON.stringify(typeDefn.union.members))
261+
// Generate union types based on kinded representation
262+
const unionTypes = []
263+
const typeChecks = []
264+
265+
for (const [, memberType] of Object.entries(typeDefn.union.representation.kinded)) {
266+
if (typeof memberType === 'string') {
267+
const tsType = fixTypeScriptType(imports, getTypeScriptType([], memberType), false)
268+
unionTypes.push(tsType)
269+
typeChecks.push(`${tsType}.is${tsType}(value)`)
270+
}
258271
}
259-
const kinds = typeDefn.union.members.map((member) => {
260-
return fixTypeScriptType(imports, getTypeScriptType([], String(member)), false)
261-
})
262-
typesrc += `export type ${typeName} = ${kinds.join(' | ')}\n\n`
272+
273+
typesrc += `export type ${typeName} = ${unionTypes.join(' | ')}\n\n`
263274
typesrc += `export namespace ${typeName} {\n`
264275
typesrc += ` export function is${typeName}(value: any): value is ${typeName} {\n`
265-
typesrc += ` return ${kinds.map((kind) => `${kind}.is${kind}(value)`).join(' || ')}\n`
276+
typesrc += ` return ${typeChecks.join(' || ')}\n`
266277
typesrc += ' }\n'
267278
typesrc += '}\n\n'
268279
} else {

test/fixtures/gen/unions-keyed.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -79,39 +79,39 @@ type DeleteResult struct {
7979
package main
8080

8181
type SimpleResult interface {
82-
isSimpleResult()
82+
simpleResult()
8383
}
8484

8585
type Success struct {
8686
value string
8787
code int64
8888
}
8989

90-
func (Success) isSimpleResult() {}
90+
func (Success) simpleResult() {}
9191

9292
type Error struct {
9393
message string
9494
code int64
9595
}
9696

97-
func (Error) isSimpleResult() {}
97+
func (Error) simpleResult() {}
9898

9999
type ProveCommitResult interface {
100-
isProveCommitResult()
100+
proveCommitResult()
101101
}
102102

103103
type ProveCommitSectors3Return struct {
104104
results []PoStProof
105105
}
106106

107-
func (ProveCommitSectors3Return) isProveCommitResult() {}
107+
func (ProveCommitSectors3Return) proveCommitResult() {}
108108

109109
type ProveCommitAggregateReturn struct {
110110
aggregateProof []byte
111111
sectorNumbers []int64
112112
}
113113

114-
func (ProveCommitAggregateReturn) isProveCommitResult() {}
114+
func (ProveCommitAggregateReturn) proveCommitResult() {}
115115

116116
type PoStProof struct {
117117
postProof int64
@@ -125,29 +125,29 @@ type Transaction struct {
125125
}
126126

127127
type OperationResult interface {
128-
isOperationResult()
128+
operationResult()
129129
}
130130

131131
type CreateResult struct {
132132
id string
133133
resource SimpleResult
134134
}
135135

136-
func (CreateResult) isOperationResult() {}
136+
func (CreateResult) operationResult() {}
137137

138138
type UpdateResult struct {
139139
id string
140140
changes int64
141141
}
142142

143-
func (UpdateResult) isOperationResult() {}
143+
func (UpdateResult) operationResult() {}
144144

145145
type DeleteResult struct {
146146
id string
147147
confirmed bool
148148
}
149149

150-
func (DeleteResult) isOperationResult() {}
150+
func (DeleteResult) operationResult() {}
151151
```
152152

153153
## Expected Rust output

0 commit comments

Comments
 (0)