Closed SAnDAnGE closed 6 years ago
@SAnDAnGE thanks for opening this issue.
So there are several problems:
first of all there's a bug in the static emitter, this
interface BExpr
| Lit_bV
| NotV
| AndV
should be
type BExpr =
| Lit_bV
| NotV
| AndV
I released a new version (io-ts-codegen@0.1.10
) with a fix.
Also currently (io-ts@1.2.0
) recursive runtime types are not allowed in tagged unions, I released a related patch (io-ts@1.2.1
).
Finally this is the source I'd use to implement your use case
const declarations = [
gen.typeDeclaration(
'Lit_bV',
gen.interfaceCombinator([
gen.property('t', gen.literalCombinator('Lit_b')),
gen.property('litValue', gen.booleanType)
])
),
gen.typeDeclaration(
'NotV',
gen.interfaceCombinator([
gen.property('t', gen.literalCombinator('Not')),
gen.property('exp', gen.identifier('BExpr'))
])
),
gen.typeDeclaration(
'AndV',
gen.interfaceCombinator([
gen.property('t', gen.literalCombinator('And')),
gen.property('a', gen.identifier('BExpr')),
gen.property('b', gen.identifier('BExpr'))
])
),
gen.typeDeclaration(
'BExpr',
gen.taggedUnionCombinator('t', [gen.identifier('Lit_bV'), gen.identifier('NotV'), gen.identifier('AndV')])
)
]
const sorted = gen.sort(declarations)
console.log(sorted.map(d => gen.printStatic(d)).join('\n'))
console.log(sorted.map(d => gen.printRuntime(d)).join('\n'))
and this is its output (produced with latest version)
interface Lit_bV {
t: 'Lit_b'
litValue: boolean
}
type BExpr = Lit_bV | NotV | AndV
interface NotV {
t: 'Not'
exp: BExpr
}
interface AndV {
t: 'And'
a: BExpr
b: BExpr
}
const Lit_bV = t.interface({
t: t.literal('Lit_b'),
litValue: t.boolean
})
const BExpr: t.RecursiveType<t.Type<BExpr>, BExpr> = t.recursion<BExpr>('BExpr', _ =>
t.taggedUnion('t', [Lit_bV, NotV, AndV])
)
const NotV: t.RecursiveType<t.Type<NotV>, NotV> = t.recursion<NotV>('NotV', _ =>
t.interface({
t: t.literal('Not'),
exp: BExpr
})
)
const AndV: t.RecursiveType<t.Type<AndV>, AndV> = t.recursion<AndV>('AndV', _ =>
t.interface({
t: t.literal('And'),
a: BExpr,
b: BExpr
})
)
Impressive work and response time, thank you very much for this.
3 more issues left :)
import * as gen from 'io-ts-codegen';
const declarations = [
gen.typeDeclaration(
'LiteralBooleanV',
gen.interfaceCombinator([
gen.property('t', gen.literalCombinator('Lit_b')),
gen.property('litValue', gen.booleanType)
])
),
gen.typeDeclaration(
'NotV',
gen.interfaceCombinator([
gen.property('t', gen.literalCombinator('Not')),
gen.property('exp', gen.identifier('BExpr'))
])
),
gen.typeDeclaration(
'LiteralIntegerV',
gen.interfaceCombinator([
gen.property('t', gen.literalCombinator('Lit_i')),
gen.property('litValue', gen.integerType)
])
),
gen.typeDeclaration(
'LiteralFloatV',
gen.interfaceCombinator([
gen.property('t', gen.literalCombinator('Lit_f')),
gen.property('litValue', gen.numberType)
])
),
gen.typeDeclaration(
'AExpr',
gen.taggedUnionCombinator('t', [
gen.identifier('LiteralIntegerV'),
gen.identifier('LiteralFloatV')
])
),
gen.typeDeclaration(
'GtV',
gen.interfaceCombinator([
gen.property('t', gen.literalCombinator('Gt')),
gen.property('a', gen.identifier('AExpr')),
gen.property('b', gen.identifier('AExpr'))
])
),
gen.typeDeclaration(
'EqV',
gen.interfaceCombinator([
gen.property('t', gen.literalCombinator('Eq')),
gen.property(
'a',
gen.unionCombinator([
gen.identifier('AExpr'),
gen.identifier('BExpr')
])
),
gen.property(
'b',
gen.unionCombinator([
gen.identifier('AExpr'),
gen.identifier('BExpr')
])
)
])
),
gen.typeDeclaration(
'BExpr',
gen.taggedUnionCombinator('t', [
gen.identifier('LiteralBooleanV'),
gen.identifier('NotV'),
gen.identifier('GtV')
])
)
];
const sorted = gen.sort(declarations);
console.log(sorted.map(d => gen.printStatic(d)).join('\n'));
console.log(sorted.map(d => gen.printRuntime(d)).join('\n'));
The generated output is:
interface LiteralBooleanV {
t: 'Lit_b', // 1) [tslint] Properties should be separated by semicolons (semicolon)
litValue: boolean
}
interface LiteralIntegerV {
t: 'Lit_i',
litValue: number
}
interface LiteralFloatV {
t: 'Lit_f',
litValue: number
}
type AExpr =
| LiteralIntegerV
| LiteralFloatV
interface GtV {
t: 'Gt',
a: AExpr,
b: AExpr
}
interface EqV {
t: 'Eq',
a:
| AExpr
| BExpr,
b:
| AExpr
| BExpr
}
type BExpr =
| LiteralBooleanV
| NotV
| GtV
interface NotV {
t: 'Not',
exp: BExpr
}
const LiteralBooleanV = t.interface({
t: t.literal('Lit_b'),
litValue: t.boolean
})
const LiteralIntegerV = t.interface({
t: t.literal('Lit_i'),
litValue: t.Integer
})
const LiteralFloatV = t.interface({
t: t.literal('Lit_f'),
litValue: t.number
})
const AExpr = t.taggedUnion('t', [
LiteralIntegerV,
LiteralFloatV
])
const GtV = t.interface({
t: t.literal('Gt'),
a: AExpr,
b: AExpr
})
const EqV = t.interface({
t: t.literal('Eq'),
a: t.union([ // 2) Declaring this as taggedUnion generates Maximum call stack exceeded when decoding
AExpr,
BExpr // 3) [ts] Block-scoped variable 'BExpr' used before its declaration. [ts] Variable 'BExpr' is used before being assigned.
]),
b: t.union([
AExpr,
BExpr
])
})
const BExpr: t.RecursiveType<t.Type<BExpr>, BExpr> = t.recursion<BExpr>('BExpr', _ => t.taggedUnion('t', [
LiteralBooleanV,
NotV,
GtV
]))
const NotV: t.RecursiveType<t.Type<NotV>, NotV> = t.recursion<NotV>('NotV', _ => t.interface({
t: t.literal('Not'),
exp: BExpr
}))
1) this is a styling issue, my suggestion is passing the generated code through prettier so you can apply your styling settings 2) could you please provide a repro? 3) Ah right, I guess recursive types should be emitted before "normal" types
Declaring this as taggedUnion generates Maximum call stack exceeded when decoding
@SAnDAnGE I'm not able to repro:
this is the source
const declarations = [
gen.typeDeclaration(
'LiteralBooleanV',
gen.interfaceCombinator([
gen.property('t', gen.literalCombinator('Lit_b')),
gen.property('litValue', gen.booleanType)
])
),
gen.typeDeclaration(
'NotV',
gen.interfaceCombinator([
gen.property('t', gen.literalCombinator('Not')),
gen.property('exp', gen.identifier('BExpr'))
])
),
gen.typeDeclaration(
'LiteralIntegerV',
gen.interfaceCombinator([
gen.property('t', gen.literalCombinator('Lit_i')),
gen.property('litValue', gen.integerType)
])
),
gen.typeDeclaration(
'LiteralFloatV',
gen.interfaceCombinator([
gen.property('t', gen.literalCombinator('Lit_f')),
gen.property('litValue', gen.numberType)
])
),
gen.typeDeclaration(
'AExpr',
gen.taggedUnionCombinator('t', [gen.identifier('LiteralIntegerV'), gen.identifier('LiteralFloatV')])
),
gen.typeDeclaration(
'GtV',
gen.interfaceCombinator([
gen.property('t', gen.literalCombinator('Gt')),
gen.property('a', gen.identifier('AExpr')),
gen.property('b', gen.identifier('AExpr'))
])
),
gen.typeDeclaration(
'EqV',
gen.interfaceCombinator([
gen.property('t', gen.literalCombinator('Eq')),
gen.property('a', gen.taggedUnionCombinator('t', [gen.identifier('AExpr'), gen.identifier('BExpr')])),
gen.property('b', gen.taggedUnionCombinator('t', [gen.identifier('AExpr'), gen.identifier('BExpr')]))
])
),
gen.typeDeclaration(
'BExpr',
gen.taggedUnionCombinator('t', [gen.identifier('LiteralBooleanV'), gen.identifier('NotV'), gen.identifier('GtV')])
)
]
const sorted = gen.sort(declarations)
console.log(sorted.map(d => gen.printStatic(d)).join('\n'))
console.log(sorted.map(d => gen.printRuntime(d)).join('\n'))
this is the output (with the fix above applied)
type BExpr = LiteralBooleanV | NotV | GtV
interface NotV {
t: 'Not'
exp: BExpr
}
interface LiteralBooleanV {
t: 'Lit_b'
litValue: boolean
}
interface LiteralIntegerV {
t: 'Lit_i'
litValue: number
}
interface LiteralFloatV {
t: 'Lit_f'
litValue: number
}
type AExpr = LiteralIntegerV | LiteralFloatV
interface GtV {
t: 'Gt'
a: AExpr
b: AExpr
}
interface EqV {
t: 'Eq'
a: AExpr | BExpr
b: AExpr | BExpr
}
const BExpr: t.RecursiveType<t.Type<BExpr>, BExpr> = t.recursion<BExpr>('BExpr', _ =>
t.taggedUnion('t', [LiteralBooleanV, NotV, GtV])
)
const NotV: t.RecursiveType<t.Type<NotV>, NotV> = t.recursion<NotV>('NotV', _ =>
t.interface({
t: t.literal('Not'),
exp: BExpr
})
)
const LiteralBooleanV = t.interface({
t: t.literal('Lit_b'),
litValue: t.boolean
})
const LiteralIntegerV = t.interface({
t: t.literal('Lit_i'),
litValue: t.Integer
})
const LiteralFloatV = t.interface({
t: t.literal('Lit_f'),
litValue: t.number
})
const AExpr = t.taggedUnion('t', [LiteralIntegerV, LiteralFloatV])
const GtV = t.interface({
t: t.literal('Gt'),
a: AExpr,
b: AExpr
})
const EqV = t.interface({
t: t.literal('Eq'),
a: t.taggedUnion('t', [AExpr, BExpr]),
b: t.taggedUnion('t', [AExpr, BExpr])
})
And this is the payload (which doesn't show any issue)
console.log(
EqV.decode({
t: 'Eq',
a: { t: 'Lit_i', litValue: 1 },
b: { t: 'Lit_i', litValue: 1 }
}).isRight()
)
// true
Have you got a payload which blows the stack?
I am trying to create a small but recursive AST with boolean logic:
The output is:
The interfaces explain pretty well the desired result, but not sure how to create the logic in io-ts to obtain the needed recursivity