Skip to content

Commit e11bcdc

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

File tree

6 files changed

+114
-70
lines changed

6 files changed

+114
-70
lines changed

compiler/src/model/metamodel.ts

+10-3
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

+9-2
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

+50-1
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,55 @@ 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+
const intf = (type as model.Interface)
642+
643+
if (!intf.inherits) {
644+
modelError(`Type ${fqn(item.type)} must derive from a base type to be used in an untagged union`)
645+
continue
646+
}
647+
648+
baseTypes.add(fqn(intf.inherits.type))
649+
650+
const baseType = getTypeDef(intf.inherits.type)
651+
if (baseType === null) {
652+
modelError(`Type ${fqn(intf.inherits.type)} not found`)
653+
continue
654+
}
655+
656+
if (baseType?.kind !== 'interface') {
657+
modelError(`Type ${fqn(intf.inherits.type)} must be an interface to be used as the base class of another interface`)
658+
continue
659+
}
660+
661+
if (!intf.inherits.generics || intf.inherits.generics.length === 0) {
662+
modelError('The common base type of an untagged union must accept at least one generic type argument')
663+
}
664+
}
665+
}
666+
}
667+
668+
if (baseTypes.size !== 1) {
669+
modelError('All items of an untagged union must derive from the same common base type')
670+
}
622671
}
623672
}
624673

specification/_types/query_dsl/compound.ts

+19-11
Original file line numberDiff line numberDiff line change
@@ -171,27 +171,35 @@ 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<
185+
double,
186+
double
187+
> {}
185188

186-
export class DateDecayFunction
187-
extends DecayFunctionBase
188-
implements AdditionalProperty<Field, DecayPlacement<DateMath, Duration>> {}
189+
export class DateDecayFunction extends DecayFunctionBase<
190+
DateMath,
191+
Duration
192+
> {}
189193

190-
export class GeoDecayFunction
191-
extends DecayFunctionBase
192-
implements AdditionalProperty<Field, DecayPlacement<GeoLocation, Distance>> {}
194+
export class GeoDecayFunction extends DecayFunctionBase<
195+
GeoLocation,
196+
Distance
197+
> {}
193198

194-
/** @codegen_names date, numeric, geo */
199+
/**
200+
* @codegen_names date, numeric, geo
201+
* @variants untagged
202+
*/
195203
// Note: deserialization depends on value types
196204
export type DecayFunction =
197205
| DateDecayFunction

specification/_types/query_dsl/specialized.ts

+4-1
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

+22-52
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,24 @@ 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<
143+
DateMath
144+
> {}
165145

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-
}
146+
export class NumberRangeQuery extends RangeQueryBase<
147+
double
148+
> {}
186149

187-
/** @codegen_names date, number, terms */
150+
export class TermRangeQuery extends RangeQueryBase<
151+
string
152+
> {}
153+
154+
/**
155+
* @codegen_names date, number, term
156+
* @variants untagged
157+
*/
188158
// Note: deserialization depends on value types
189-
export type RangeQuery = DateRangeQuery | NumberRangeQuery | TermsRangeQuery
159+
export type RangeQuery = DateRangeQuery | NumberRangeQuery | TermRangeQuery
190160

191161
export enum RangeRelation {
192162
/**

0 commit comments

Comments
 (0)