Skip to content

Commit 3456d83

Browse files
committed
Explicitly name the "untyped" variant and prevent declaration of new untagged variants
1 parent 9ae4eb0 commit 3456d83

File tree

9 files changed

+579
-521
lines changed

9 files changed

+579
-521
lines changed

compiler/src/model/metamodel.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ export class Container extends VariantBase {
209209

210210
export class Untagged extends VariantBase {
211211
kind: 'untagged'
212+
untyped_variant: Inherits
212213
}
213214

214215
/**

compiler/src/model/utils.ts

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1110,24 +1110,34 @@ export function parseVariantsTag (jsDoc: JSDoc[]): model.Variants | undefined {
11101110
}
11111111
}
11121112

1113+
if (type === 'internal') {
1114+
const pairs = parseKeyValues(jsDoc, values, 'tag', 'default')
1115+
assert(jsDoc, typeof pairs.tag === 'string', 'Internal variant requires a tag definition')
1116+
return {
1117+
kind: 'internal_tag',
1118+
nonExhaustive: nonExhaustive,
1119+
tag: pairs.tag,
1120+
defaultTag: pairs.default
1121+
}
1122+
}
1123+
11131124
if (type === 'untagged') {
1125+
const pairs = parseKeyValues(jsDoc, values, 'untyped')
1126+
assert(jsDoc, typeof pairs.untyped === 'string', 'Untagged variant requires an untyped definition')
1127+
const fqn = pairs.untyped.split('.')
11141128
return {
11151129
kind: 'untagged',
1116-
nonExhaustive: nonExhaustive
1130+
nonExhaustive: nonExhaustive,
1131+
untyped_variant: {
1132+
type: {
1133+
namespace: fqn.slice(0, fqn.length - 1).join('.'),
1134+
name: fqn[fqn.length - 1]
1135+
}
1136+
}
11171137
}
11181138
}
11191139

1120-
assert(jsDoc, type === 'internal', `Bad variant type: ${type}`)
1121-
1122-
const pairs = parseKeyValues(jsDoc, values, 'tag', 'default')
1123-
assert(jsDoc, typeof pairs.tag === 'string', 'Internal variant requires a tag definition')
1124-
1125-
return {
1126-
kind: 'internal_tag',
1127-
nonExhaustive: nonExhaustive,
1128-
tag: pairs.tag,
1129-
defaultTag: pairs.default
1130-
}
1140+
assert(jsDoc, false, `Bad variant type: ${type}`)
11311141
}
11321142

11331143
/**

compiler/src/steps/validate-model.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -559,14 +559,14 @@ export default async function validateModel (apiModel: model.Model, restSpec: Ma
559559
if (typeDef.type.kind !== 'union_of') {
560560
modelError('The "variants" tag only applies to unions')
561561
} else {
562-
validateTaggedUnion(typeDef.type, typeDef.variants)
562+
validateTaggedUnion(typeDef.name, typeDef.type, typeDef.variants)
563563
}
564564
} else {
565565
validateValueOf(typeDef.type, openGenerics)
566566
}
567567
}
568568

569-
function validateTaggedUnion (valueOf: model.UnionOf, variants: model.InternalTag | model.ExternalTag | model.Untagged): void {
569+
function validateTaggedUnion (parentName: TypeName, 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)
@@ -611,9 +611,20 @@ export default async function validateModel (apiModel: model.Model, restSpec: Ma
611611

612612
validateValueOf(valueOf, new Set())
613613
} else if (variants.kind === 'untagged') {
614-
const items = flattenUnionMembers(valueOf)
614+
if (fqn(parentName) !== '_types.query_dsl:DecayFunction' &&
615+
fqn(parentName) !== '_types.query_dsl:DistanceFeatureQuery' &&
616+
fqn(parentName) !== '_types.query_dsl:RangeQuery') {
617+
throw new Error(`Please contact the devtools team before adding new untagged variant ${fqn(parentName)}`)
618+
}
615619

620+
const untypedVariant = getTypeDef(variants.untyped_variant.type)
621+
if (untypedVariant == null) {
622+
modelError(`Type ${fqn(variants.untyped_variant.type)} not found`)
623+
}
624+
625+
const items = flattenUnionMembers(valueOf)
616626
const baseTypes = new Set<string>()
627+
let foundUntyped = false
617628

618629
for (const item of items) {
619630
if (item.kind !== 'instance_of') {
@@ -629,6 +640,10 @@ export default async function validateModel (apiModel: model.Model, restSpec: Ma
629640
continue
630641
}
631642

643+
if (untypedVariant != null && fqn(item.type) === fqn(untypedVariant.name)) {
644+
foundUntyped = true
645+
}
646+
632647
if (type.inherits == null) {
633648
modelError(`Type ${fqn(item.type)} must derive from a base type to be used in an untagged union`)
634649
continue
@@ -657,6 +672,10 @@ export default async function validateModel (apiModel: model.Model, restSpec: Ma
657672
if (baseTypes.size !== 1) {
658673
modelError('All items of an untagged union must derive from the same common base type')
659674
}
675+
676+
if (!foundUntyped) {
677+
modelError('The untyped variant of an untagged variant must be contained in the union items')
678+
}
660679
}
661680
}
662681

docs/modeling-guide.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -455,16 +455,17 @@ For example:
455455
```ts
456456
export class MyTypeBase<T1, T2, ...> { ... }
457457

458+
export class MyTypeUntyped extends MyTypeBase<UserDefinedValue> {}
458459
export class MyTypeSpecialized1 extends MyTypeBase<int> {}
459460
export class MyTypeSpecialized2 extends MyTypeBase<string> {}
460461
export class MyTypeSpecialized3 extends MyTypeBase<bool> {}
461462

462463
/**
463-
* @codegen_names mytype1, mytypet2, mytype3
464-
* @variant untagged
464+
* @codegen_names untyped, mytype1, mytypet2, mytype3
465+
* @variant untagged untyped=_types.MyTypeUntyped
465466
*/
466467
// Note: deserialization depends on value types
467-
export type MyType = MyTypeSpecialized1 | MyTypeSpecialized2 | MyTypeSpecialized3
468+
export type MyType = MyTypeUntyped | MyTypeSpecialized1 | MyTypeSpecialized2 | MyTypeSpecialized3
468469
```
469470
470471
### Shortcut properties

0 commit comments

Comments
 (0)