in compiler/src/steps/validate-model.ts [555:666]
function validateTaggedUnion (parentName: TypeName, valueOf: model.UnionOf, variants: model.InternalTag | model.ExternalTag | model.Untagged, openGenerics: Set<string>): void {
if (variants.kind === 'external_tag') {
// All items must have a 'variant' attribute
const items = flattenUnionMembers(valueOf, openGenerics)
for (const item of items) {
if (item.kind !== 'instance_of') {
modelError('Items of externally tagged unions must be types with a "variant_tag" annotation')
} else {
validateTypeRef(item.type, item.generics, openGenerics)
const type = getTypeDef(item.type)
if (type == null) {
modelError(`Type ${fqn(item.type)} not found`)
} else {
if (type.variantName == null) {
modelError(`Type ${fqn(item.type)} is part of a tagged union and should have a "@variant name"`)
} else {
// Check uniqueness
}
}
}
}
} else if (variants.kind === 'internal_tag') {
const tagName = variants.tag
const items = flattenUnionMembers(valueOf, openGenerics)
for (const item of items) {
if (item.kind !== 'instance_of') {
modelError('Items of internally tagged unions must be type references')
} else {
validateTypeRef(item.type, item.generics, openGenerics)
const type = getTypeDef(item.type)
if (type == null) {
modelError(`Type ${fqn(item.type)} not found`)
} else if (type.kind === 'interface') {
const tagProperty = type.properties.find(prop => prop.name === tagName)
if (tagProperty == null) {
modelError(`Type ${fqn(item.type)} should have a "${tagName}" variant tag property`)
}
}
}
}
validateValueOf(valueOf, openGenerics)
} else if (variants.kind === 'untagged') {
if (fqn(parentName) !== '_types.query_dsl:DecayFunction' &&
fqn(parentName) !== '_types.query_dsl:DistanceFeatureQuery' &&
fqn(parentName) !== '_types.query_dsl:RangeQuery') {
throw new Error(`Please contact the devtools team before adding new untagged variant ${fqn(parentName)}`)
}
const untypedVariant = getTypeDef(variants.untypedVariant)
if (untypedVariant == null) {
modelError(`Type ${fqn(variants.untypedVariant)} not found`)
}
const items = flattenUnionMembers(valueOf, openGenerics)
const baseTypes = new Set<string>()
let foundUntyped = false
for (const item of items) {
if (item.kind !== 'instance_of') {
modelError('Items of type untagged unions must be type references')
} else {
validateTypeRef(item.type, item.generics, openGenerics)
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
}
if (untypedVariant != null && fqn(item.type) === fqn(untypedVariant.name)) {
foundUntyped = true
}
if (type.inherits == null) {
modelError(`Type ${fqn(item.type)} must derive from a base type to be used in an untagged union`)
continue
}
baseTypes.add(fqn(type.inherits.type))
const baseType = getTypeDef(type.inherits.type)
if (baseType == null) {
modelError(`Type ${fqn(type.inherits.type)} not found`)
continue
}
if (baseType.kind !== 'interface') {
modelError(`Type ${fqn(type.inherits.type)} must be an interface to be used as the base class of another interface`)
continue
}
if (baseType.generics == null || baseType.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')
}
if (!foundUntyped) {
modelError('The untyped variant of an untagged variant must be contained in the union items')
}
}
}