Skip to content

Commit 28e7891

Browse files
committed
Improve declaration of untagged unions to allow for generator optimizations
1 parent 42aaa69 commit 28e7891

File tree

6 files changed

+97
-62
lines changed

6 files changed

+97
-62
lines changed

compiler/src/model/metamodel.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ export abstract class BaseType {
180180
specLocation: string
181181
}
182182

183-
export type Variants = ExternalTag | InternalTag | Container
183+
export type Variants = ExternalTag | InternalTag | Container | Untagged
184184

185185
export class VariantBase {
186186
/**
@@ -207,6 +207,10 @@ export class Container extends VariantBase {
207207
kind: 'container'
208208
}
209209

210+
export class Untagged extends VariantBase {
211+
kind: 'untagged'
212+
}
213+
210214
/**
211215
* Inherits clause (aka extends or implements) for an interface or request
212216
*/
@@ -364,8 +368,11 @@ export class TypeAlias extends BaseType {
364368
type: ValueOf
365369
/** generic parameters: either concrete types or open parameters from the enclosing type */
366370
generics?: TypeName[]
367-
/** Only applicable to `union_of` aliases: identify typed_key unions (external) and variant inventories (internal) */
368-
variants?: InternalTag | ExternalTag
371+
/**
372+
* Only applicable to `union_of` aliases: identify typed_key unions (external), variant inventories (internal)
373+
* and untagged variants
374+
*/
375+
variants?: InternalTag | ExternalTag | Untagged
369376
}
370377

371378
// ------------------------------------------------------------------------------------------------

compiler/src/model/utils.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -536,8 +536,8 @@ export function modelTypeAlias (declaration: TypeAliasDeclaration): model.TypeAl
536536
if (variants != null) {
537537
assert(
538538
declaration.getJsDocs(),
539-
variants.kind === 'internal_tag' || variants.kind === 'external_tag',
540-
'Type Aliases can only have internal or external variants'
539+
variants.kind === 'internal_tag' || variants.kind === 'external_tag' || variants.kind === 'untagged',
540+
'Type Aliases can only have internal, external or untagged variants'
541541
)
542542
typeAlias.variants = variants
543543
}
@@ -1110,6 +1110,13 @@ export function parseVariantsTag (jsDoc: JSDoc[]): model.Variants | undefined {
11101110
}
11111111
}
11121112

1113+
if (type === 'untagged') {
1114+
return {
1115+
kind: 'untagged',
1116+
nonExhaustive: nonExhaustive
1117+
}
1118+
}
1119+
11131120
assert(jsDoc, type === 'internal', `Bad variant type: ${type}`)
11141121

11151122
const pairs = parseKeyValues(jsDoc, values, 'tag', 'default')

compiler/src/steps/validate-model.ts

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -566,7 +566,7 @@ export default async function validateModel (apiModel: model.Model, restSpec: Ma
566566
}
567567
}
568568

569-
function validateTaggedUnion (valueOf: model.UnionOf, variants: model.InternalTag | model.ExternalTag): void {
569+
function validateTaggedUnion (valueOf: model.UnionOf, variants: model.InternalTag | model.ExternalTag | model.Untagged): void {
570570
if (variants.kind === 'external_tag') {
571571
// All items must have a 'variant' attribute
572572
const items = flattenUnionMembers(valueOf)
@@ -610,6 +610,53 @@ export default async function validateModel (apiModel: model.Model, restSpec: Ma
610610
}
611611

612612
validateValueOf(valueOf, new Set())
613+
} else if (variants.kind === 'untagged') {
614+
const items = flattenUnionMembers(valueOf)
615+
616+
const baseTypes = new Set<string>()
617+
618+
for (const item of items) {
619+
if (item.kind !== 'instance_of') {
620+
modelError('Items of type untagged unions must be type references')
621+
} else {
622+
validateTypeRef(item.type, undefined, new Set<string>())
623+
const type = getTypeDef(item.type)
624+
if (type == null) {
625+
modelError(`Type ${fqn(item.type)} not found`)
626+
} else {
627+
if (type.kind !== 'interface') {
628+
modelError(`Type ${fqn(item.type)} must be an interface to be used in an untagged union`)
629+
continue
630+
}
631+
632+
if (type.inherits == null) {
633+
modelError(`Type ${fqn(item.type)} must derive from a base type to be used in an untagged union`)
634+
continue
635+
}
636+
637+
baseTypes.add(fqn(type.inherits.type))
638+
639+
const baseType = getTypeDef(type.inherits.type)
640+
if (baseType == null) {
641+
modelError(`Type ${fqn(type.inherits.type)} not found`)
642+
continue
643+
}
644+
645+
if (baseType.kind !== 'interface') {
646+
modelError(`Type ${fqn(type.inherits.type)} must be an interface to be used as the base class of another interface`)
647+
continue
648+
}
649+
650+
if (baseType.generics == null || baseType.generics.length === 0) {
651+
modelError('The common base type of an untagged union must accept at least one generic type argument')
652+
}
653+
}
654+
}
655+
}
656+
657+
if (baseTypes.size !== 1) {
658+
modelError('All items of an untagged union must derive from the same common base type')
659+
}
613660
}
614661
}
615662

specification/_types/query_dsl/specialized.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,10 @@ export class DateDistanceFeatureQuery extends DistanceFeatureQueryBase<
6969
Duration
7070
> {}
7171

72-
/** @codegen_names geo, date */
72+
/**
73+
* @codegen_names geo, date
74+
* @variants untagged
75+
*/
7376
// Note: deserialization depends on value types
7477
export type DistanceFeatureQuery =
7578
| GeoDistanceFeatureQuery

specification/_types/query_dsl/term.ts

Lines changed: 16 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -105,33 +105,30 @@ export class PrefixQuery extends QueryBase {
105105
case_insensitive?: boolean
106106
}
107107

108-
export class RangeQueryBase extends QueryBase {
108+
export class RangeQueryBase<T> extends QueryBase {
109109
/**
110110
* Indicates how the range query matches values for `range` fields.
111111
* @server_default intersects
112112
*/
113113
relation?: RangeRelation
114-
}
115-
116-
export class DateRangeQuery extends RangeQueryBase {
117114
/**
118115
* Greater than.
119116
*/
120-
gt?: DateMath
117+
gt?: T
121118
/**
122119
* Greater than or equal to.
123120
*/
124-
gte?: DateMath
121+
gte?: T
125122
/**
126123
* Less than.
127124
*/
128-
lt?: DateMath
125+
lt?: T
129126
/**
130127
* Less than or equal to.
131128
*/
132-
lte?: DateMath
133-
from?: DateMath | null
134-
to?: DateMath | null
129+
lte?: T
130+
from?: T | null
131+
to?: T | null
135132
/**
136133
* Date format used to convert `date` values in the query.
137134
*/
@@ -142,51 +139,18 @@ export class DateRangeQuery extends RangeQueryBase {
142139
time_zone?: TimeZone
143140
}
144141

145-
export class NumberRangeQuery extends RangeQueryBase {
146-
/**
147-
* Greater than.
148-
*/
149-
gt?: double
150-
/**
151-
* Greater than or equal to.
152-
*/
153-
gte?: double
154-
/**
155-
* Less than.
156-
*/
157-
lt?: double
158-
/**
159-
* Less than or equal to.
160-
*/
161-
lte?: double
162-
from?: double | null
163-
to?: double | null
164-
}
142+
export class DateRangeQuery extends RangeQueryBase<DateMath> {}
165143

166-
export class TermsRangeQuery extends RangeQueryBase {
167-
/**
168-
* Greater than.
169-
*/
170-
gt?: string
171-
/**
172-
* Greater than or equal to.
173-
*/
174-
gte?: string
175-
/**
176-
* Less than.
177-
*/
178-
lt?: string
179-
/**
180-
* Less than or equal to.
181-
*/
182-
lte?: string
183-
from?: string | null
184-
to?: string | null
185-
}
144+
export class NumberRangeQuery extends RangeQueryBase<double> {}
186145

187-
/** @codegen_names date, number, terms */
146+
export class TermRangeQuery extends RangeQueryBase<string> {}
147+
148+
/**
149+
* @codegen_names date, number, term
150+
* @variants untagged
151+
*/
188152
// Note: deserialization depends on value types
189-
export type RangeQuery = DateRangeQuery | NumberRangeQuery | TermsRangeQuery
153+
export type RangeQuery = DateRangeQuery | NumberRangeQuery | TermRangeQuery
190154

191155
export enum RangeRelation {
192156
/**

typescript-generator/src/metamodel.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ export abstract class BaseType {
180180
specLocation: string
181181
}
182182

183-
export type Variants = ExternalTag | InternalTag | Container
183+
export type Variants = ExternalTag | InternalTag | Container | Untagged
184184

185185
export class VariantBase {
186186
/**
@@ -207,6 +207,10 @@ export class Container extends VariantBase {
207207
kind: 'container'
208208
}
209209

210+
export class Untagged extends VariantBase {
211+
kind: 'untagged'
212+
}
213+
210214
/**
211215
* Inherits clause (aka extends or implements) for an interface or request
212216
*/
@@ -364,8 +368,11 @@ export class TypeAlias extends BaseType {
364368
type: ValueOf
365369
/** generic parameters: either concrete types or open parameters from the enclosing type */
366370
generics?: TypeName[]
367-
/** Only applicable to `union_of` aliases: identify typed_key unions (external) and variant inventories (internal) */
368-
variants?: InternalTag | ExternalTag
371+
/**
372+
* Only applicable to `union_of` aliases: identify typed_key unions (external), variant inventories (internal)
373+
* and untagged variants
374+
*/
375+
variants?: InternalTag | ExternalTag | Untagged
369376
}
370377

371378
// ------------------------------------------------------------------------------------------------

0 commit comments

Comments
 (0)