Skip to content

Commit

Permalink
Improve declaration of untagged unions to allow for generator optimiz…
Browse files Browse the repository at this point in the history
…ations
  • Loading branch information
flobernd committed Jun 12, 2024
1 parent 1a137db commit e11bcdc
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 70 deletions.
13 changes: 10 additions & 3 deletions compiler/src/model/metamodel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ export abstract class BaseType {
specLocation: string
}

export type Variants = ExternalTag | InternalTag | Container
export type Variants = ExternalTag | InternalTag | Container | Untagged

export class VariantBase {
/**
Expand All @@ -207,6 +207,10 @@ export class Container extends VariantBase {
kind: 'container'
}

export class Untagged extends VariantBase {
kind: 'untagged'
}

/**
* Inherits clause (aka extends or implements) for an interface or request
*/
Expand Down Expand Up @@ -358,8 +362,11 @@ export class TypeAlias extends BaseType {
type: ValueOf
/** generic parameters: either concrete types or open parameters from the enclosing type */
generics?: TypeName[]
/** Only applicable to `union_of` aliases: identify typed_key unions (external) and variant inventories (internal) */
variants?: InternalTag | ExternalTag
/**
* Only applicable to `union_of` aliases: identify typed_key unions (external), variant inventories (internal)
* and untagged variants
*/
variants?: InternalTag | ExternalTag | Untagged
}

// ------------------------------------------------------------------------------------------------
Expand Down
11 changes: 9 additions & 2 deletions compiler/src/model/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -496,8 +496,8 @@ export function modelTypeAlias (declaration: TypeAliasDeclaration): model.TypeAl
if (variants != null) {
assert(
declaration.getJsDocs(),
variants.kind === 'internal_tag' || variants.kind === 'external_tag',
'Type Aliases can only have internal or external variants'
variants.kind === 'internal_tag' || variants.kind === 'external_tag' || variants.kind === 'untagged',
'Type Aliases can only have internal, external or untagged variants'
)
typeAlias.variants = variants
}
Expand Down Expand Up @@ -1065,6 +1065,13 @@ export function parseVariantsTag (jsDoc: JSDoc[]): model.Variants | undefined {
}
}

if (type === 'untagged') {
return {
kind: 'untagged',
nonExhaustive: nonExhaustive
}
}

assert(jsDoc, type === 'internal', `Bad variant type: ${type}`)

const pairs = parseKeyValues(jsDoc, values, 'tag', 'default')
Expand Down
51 changes: 50 additions & 1 deletion compiler/src/steps/validate-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,7 @@ export default async function validateModel (apiModel: model.Model, restSpec: Ma
}
}

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

validateValueOf(valueOf, new Set())
} else if (variants.kind === 'untagged') {
const items = flattenUnionMembers(valueOf)

const baseTypes = new Set<string>();

for (const item of items) {
if (item.kind !== 'instance_of') {
modelError('Items of type untagged unions must be type references')
} else {
validateTypeRef(item.type, undefined, new Set<string>())
const type = getTypeDef(item.type)
if (type == null) {
modelError(`Type ${fqn(item.type)} not found`)
} else {
if (type.kind !== 'interface') {
modelError(`Type ${fqn(item.type)} must be an interface to be used in an untagged union`)
continue
}

const intf = (type as model.Interface)

if (!intf.inherits) {
modelError(`Type ${fqn(item.type)} must derive from a base type to be used in an untagged union`)
continue
}

baseTypes.add(fqn(intf.inherits.type))

const baseType = getTypeDef(intf.inherits.type)
if (baseType === null) {
modelError(`Type ${fqn(intf.inherits.type)} not found`)
continue
}

if (baseType?.kind !== 'interface') {
modelError(`Type ${fqn(intf.inherits.type)} must be an interface to be used as the base class of another interface`)
continue
}

if (!intf.inherits.generics || intf.inherits.generics.length === 0) {
modelError('The common base type of an untagged union must accept at least one generic type argument')
}
}
}
}

if (baseTypes.size !== 1) {
modelError('All items of an untagged union must derive from the same common base type')
}
}
}

Expand Down
30 changes: 19 additions & 11 deletions specification/_types/query_dsl/compound.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,27 +171,35 @@ export class DecayPlacement<TOrigin, TScale> {
origin?: TOrigin
}

export class DecayFunctionBase {
export class DecayFunctionBase<TOrigin, TScale>
implements AdditionalProperty<Field, DecayPlacement<TOrigin, TScale>>
{
/**
* Determines how the distance is calculated when a field used for computing the decay contains multiple values.
* @server_default min
*/
multi_value_mode?: MultiValueMode
}

export class NumericDecayFunction
extends DecayFunctionBase
implements AdditionalProperty<Field, DecayPlacement<double, double>> {}
export class NumericDecayFunction extends DecayFunctionBase<
double,
double
> {}

export class DateDecayFunction
extends DecayFunctionBase
implements AdditionalProperty<Field, DecayPlacement<DateMath, Duration>> {}
export class DateDecayFunction extends DecayFunctionBase<
DateMath,
Duration
> {}

export class GeoDecayFunction
extends DecayFunctionBase
implements AdditionalProperty<Field, DecayPlacement<GeoLocation, Distance>> {}
export class GeoDecayFunction extends DecayFunctionBase<
GeoLocation,
Distance
> {}

/** @codegen_names date, numeric, geo */
/**
* @codegen_names date, numeric, geo
* @variants untagged
*/
// Note: deserialization depends on value types
export type DecayFunction =
| DateDecayFunction
Expand Down
5 changes: 4 additions & 1 deletion specification/_types/query_dsl/specialized.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,10 @@ export class DateDistanceFeatureQuery extends DistanceFeatureQueryBase<
Duration
> {}

/** @codegen_names geo, date */
/**
* @codegen_names geo, date
* @variants untagged
*/
// Note: deserialization depends on value types
export type DistanceFeatureQuery =
| GeoDistanceFeatureQuery
Expand Down
74 changes: 22 additions & 52 deletions specification/_types/query_dsl/term.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,33 +105,30 @@ export class PrefixQuery extends QueryBase {
case_insensitive?: boolean
}

export class RangeQueryBase extends QueryBase {
export class RangeQueryBase<T> extends QueryBase {
/**
* Indicates how the range query matches values for `range` fields.
* @server_default intersects
*/
relation?: RangeRelation
}

export class DateRangeQuery extends RangeQueryBase {
/**
* Greater than.
*/
gt?: DateMath
gt?: T
/**
* Greater than or equal to.
*/
gte?: DateMath
gte?: T
/**
* Less than.
*/
lt?: DateMath
lt?: T
/**
* Less than or equal to.
*/
lte?: DateMath
from?: DateMath | null
to?: DateMath | null
lte?: T
from?: T | null
to?: T | null
/**
* Date format used to convert `date` values in the query.
*/
Expand All @@ -142,51 +139,24 @@ export class DateRangeQuery extends RangeQueryBase {
time_zone?: TimeZone
}

export class NumberRangeQuery extends RangeQueryBase {
/**
* Greater than.
*/
gt?: double
/**
* Greater than or equal to.
*/
gte?: double
/**
* Less than.
*/
lt?: double
/**
* Less than or equal to.
*/
lte?: double
from?: double | null
to?: double | null
}
export class DateRangeQuery extends RangeQueryBase<
DateMath
> {}

export class TermsRangeQuery extends RangeQueryBase {
/**
* Greater than.
*/
gt?: string
/**
* Greater than or equal to.
*/
gte?: string
/**
* Less than.
*/
lt?: string
/**
* Less than or equal to.
*/
lte?: string
from?: string | null
to?: string | null
}
export class NumberRangeQuery extends RangeQueryBase<
double
> {}

/** @codegen_names date, number, terms */
export class TermRangeQuery extends RangeQueryBase<
string
> {}

/**
* @codegen_names date, number, term
* @variants untagged
*/
// Note: deserialization depends on value types
export type RangeQuery = DateRangeQuery | NumberRangeQuery | TermsRangeQuery
export type RangeQuery = DateRangeQuery | NumberRangeQuery | TermRangeQuery

export enum RangeRelation {
/**
Expand Down

0 comments on commit e11bcdc

Please sign in to comment.