Skip to content

Commit 636ff57

Browse files
committed
Improve declaration of untagged unions to allow for generator optimizations
1 parent 1a137db commit 636ff57

File tree

7 files changed

+110
-73
lines changed

7 files changed

+110
-73
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
*/
@@ -358,8 +362,11 @@ export class TypeAlias extends BaseType {
358362
type: ValueOf
359363
/** generic parameters: either concrete types or open parameters from the enclosing type */
360364
generics?: TypeName[]
361-
/** Only applicable to `union_of` aliases: identify typed_key unions (external) and variant inventories (internal) */
362-
variants?: InternalTag | ExternalTag
365+
/**
366+
* Only applicable to `union_of` aliases: identify typed_key unions (external), variant inventories (internal)
367+
* and untagged variants
368+
*/
369+
variants?: InternalTag | ExternalTag | Untagged
363370
}
364371

365372
// ------------------------------------------------------------------------------------------------

compiler/src/model/utils.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -496,8 +496,8 @@ export function modelTypeAlias (declaration: TypeAliasDeclaration): model.TypeAl
496496
if (variants != null) {
497497
assert(
498498
declaration.getJsDocs(),
499-
variants.kind === 'internal_tag' || variants.kind === 'external_tag',
500-
'Type Aliases can only have internal or external variants'
499+
variants.kind === 'internal_tag' || variants.kind === 'external_tag' || variants.kind === 'untagged',
500+
'Type Aliases can only have internal, external or untagged variants'
501501
)
502502
typeAlias.variants = variants
503503
}
@@ -1065,6 +1065,13 @@ export function parseVariantsTag (jsDoc: JSDoc[]): model.Variants | undefined {
10651065
}
10661066
}
10671067

1068+
if (type === 'untagged') {
1069+
return {
1070+
kind: 'untagged',
1071+
nonExhaustive: nonExhaustive
1072+
}
1073+
}
1074+
10681075
assert(jsDoc, type === 'internal', `Bad variant type: ${type}`)
10691076

10701077
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
@@ -575,7 +575,7 @@ export default async function validateModel (apiModel: model.Model, restSpec: Ma
575575
}
576576
}
577577

578-
function validateTaggedUnion (valueOf: model.UnionOf, variants: model.InternalTag | model.ExternalTag): void {
578+
function validateTaggedUnion (valueOf: model.UnionOf, variants: model.InternalTag | model.ExternalTag | model.Untagged): void {
579579
if (variants.kind === 'external_tag') {
580580
// All items must have a 'variant' attribute
581581
const items = flattenUnionMembers(valueOf)
@@ -619,6 +619,53 @@ export default async function validateModel (apiModel: model.Model, restSpec: Ma
619619
}
620620

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

specification/_types/query_dsl/compound.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -171,27 +171,29 @@ export class DecayPlacement<TOrigin, TScale> {
171171
origin?: TOrigin
172172
}
173173

174-
export class DecayFunctionBase {
174+
export class DecayFunctionBase<TOrigin, TScale>
175+
implements AdditionalProperty<Field, DecayPlacement<TOrigin, TScale>>
176+
{
175177
/**
176178
* Determines how the distance is calculated when a field used for computing the decay contains multiple values.
177179
* @server_default min
178180
*/
179181
multi_value_mode?: MultiValueMode
180182
}
181183

182-
export class NumericDecayFunction
183-
extends DecayFunctionBase
184-
implements AdditionalProperty<Field, DecayPlacement<double, double>> {}
184+
export class NumericDecayFunction extends DecayFunctionBase<double, double> {}
185185

186-
export class DateDecayFunction
187-
extends DecayFunctionBase
188-
implements AdditionalProperty<Field, DecayPlacement<DateMath, Duration>> {}
186+
export class DateDecayFunction extends DecayFunctionBase<DateMath, Duration> {}
189187

190-
export class GeoDecayFunction
191-
extends DecayFunctionBase
192-
implements AdditionalProperty<Field, DecayPlacement<GeoLocation, Distance>> {}
188+
export class GeoDecayFunction extends DecayFunctionBase<
189+
GeoLocation,
190+
Distance
191+
> {}
193192

194-
/** @codegen_names date, numeric, geo */
193+
/**
194+
* @codegen_names date, numeric, geo
195+
* @variants untagged
196+
*/
195197
// Note: deserialization depends on value types
196198
export type DecayFunction =
197199
| DateDecayFunction

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
*/
@@ -358,8 +362,11 @@ export class TypeAlias extends BaseType {
358362
type: ValueOf
359363
/** generic parameters: either concrete types or open parameters from the enclosing type */
360364
generics?: TypeName[]
361-
/** Only applicable to `union_of` aliases: identify typed_key unions (external) and variant inventories (internal) */
362-
variants?: InternalTag | ExternalTag
365+
/**
366+
* Only applicable to `union_of` aliases: identify typed_key unions (external), variant inventories (internal)
367+
* and untagged variants
368+
*/
369+
variants?: InternalTag | ExternalTag | Untagged
363370
}
364371

365372
// ------------------------------------------------------------------------------------------------

0 commit comments

Comments
 (0)