@@ -19,6 +19,7 @@ import {
19
19
} from "../../simple-tree/index.js" ;
20
20
import {
21
21
type AllowedTypes ,
22
+ type FieldKind ,
22
23
type FieldSchema ,
23
24
type ImplicitAllowedTypes ,
24
25
type ImplicitFieldSchema ,
@@ -45,6 +46,7 @@ import type {
45
46
// eslint-disable-next-line import/no-internal-modules
46
47
import { objectSchema } from "../../simple-tree/objectNode.js" ;
47
48
import { validateUsageError } from "../utils.js" ;
49
+ import { TreeAlpha } from "../../shared-tree/index.js" ;
48
50
49
51
const schema = new SchemaFactory ( "com.example" ) ;
50
52
@@ -102,6 +104,15 @@ describe("schemaTypes", () => {
102
104
type _check6 = requireTrue < areSafelyAssignable < I8 , never > > ;
103
105
type _check7 = requireTrue < areSafelyAssignable < I9 , never > > ;
104
106
type _check8 = requireTrue < areSafelyAssignable < I10 , never > > ;
107
+
108
+ // eslint-disable-next-line no-inner-declarations
109
+ function _generic < T extends ImplicitAllowedTypes > ( ) {
110
+ type I14 = InsertableTreeFieldFromImplicitField < T > ;
111
+ type IOptional = InsertableTreeFieldFromImplicitField <
112
+ FieldSchema < FieldKind . Optional , T >
113
+ > ;
114
+ type _check9 = requireAssignableTo < undefined , IOptional > ;
115
+ }
105
116
}
106
117
107
118
// InsertableTreeNodeFromImplicitAllowedTypes
@@ -217,6 +228,17 @@ describe("schemaTypes", () => {
217
228
// boolean is sometimes a union of true and false, so it can break in its owns special ways
218
229
type I13 = InsertableField < typeof booleanSchema > ;
219
230
type _check13 = requireTrue < areSafelyAssignable < I13 , boolean > > ;
231
+
232
+ // eslint-disable-next-line no-inner-declarations
233
+ function _generic < T extends ImplicitAllowedTypes > ( ) {
234
+ type I14 = InsertableField < T > ;
235
+ type IOptional = InsertableField < FieldSchema < FieldKind . Optional , T > > ;
236
+
237
+ // Most likely due to the TypeScript conditional type limitation described in https://github.com/microsoft/TypeScript/issues/52144#issuecomment-2686250788
238
+ // This does not compile. Ideally this would compile:
239
+ // @ts -expect-error Compiler limitation.
240
+ type _check9 = requireAssignableTo < undefined , IOptional > ;
241
+ }
220
242
}
221
243
222
244
// NodeFromSchema
@@ -524,4 +546,159 @@ describe("schemaTypes", () => {
524
546
) ; // Different custom metadata
525
547
check ( sf . identifier , sf . optional ( sf . string ) , false ) ; // Identifier vs. optional string
526
548
} ) ;
549
+
550
+ /**
551
+ * Tests for patterns for making generically parameterized schema.
552
+ *
553
+ * Since the schema themselves can not be generic (at least not in a way thats captured in the stored schema),
554
+ * this is done by making generic functions that return schema.
555
+ *
556
+ * Authoring such functions involves passing generic type parameters into the various schema type utilities,
557
+ * and this causes some issues with many of them.
558
+ */
559
+ describe ( "Generic Schema" , ( ) => {
560
+ // Many of these cases should compile, but don't.
561
+ // This is likely due to `[FieldSchema<Kind, T>] extends [ImplicitFieldSchema] ? TrueCase : FalseCase` not getting reduced to `TrueCase`.
562
+ // This could be due to the compiler limitation noted in https://github.com/microsoft/TypeScript/issues/52144#issuecomment-2686250788
563
+
564
+ /**
565
+ * Tests where the generic code constructs TreeNodes for the generic it's defining.
566
+ * This scenario seems to be particularly problematic as the {@link Input} types seems to perform especially poorly due
567
+ * to them using non distributive conditional types, which hits the issue noted above.
568
+ */
569
+ it ( "Generic container construction" , ( ) => {
570
+ const sf = new SchemaFactory ( "test" ) ;
571
+
572
+ /**
573
+ * Define a generic container which holds the provided `T` directly as an implicit field schema.
574
+ */
575
+ function makeInstanceImplicit < T extends ImplicitAllowedTypes > (
576
+ schemaTypes : T ,
577
+ content : InsertableTreeFieldFromImplicitField < T > ,
578
+ ) {
579
+ class GenericContainer extends sf . object ( "GenericContainer" , {
580
+ content : schemaTypes ,
581
+ } ) { }
582
+
583
+ // Both create and the constructor type check as desired.
584
+ const _created = TreeAlpha . create ( GenericContainer , { content } ) ;
585
+ return new GenericContainer ( { content } ) ;
586
+ }
587
+
588
+ /**
589
+ * Define a generic container which holds the provided `T` in a required field.
590
+ *
591
+ * This should function identically to the implicit one, but it doesn't.
592
+ */
593
+ function makeInstanceRequired < T extends ImplicitAllowedTypes > (
594
+ schemaTypes : T ,
595
+ content : InsertableTreeFieldFromImplicitField < T > ,
596
+ ) {
597
+ class GenericContainer extends sf . object ( "GenericContainer" , {
598
+ content : sf . required ( schemaTypes ) ,
599
+ } ) { }
600
+
601
+ // Users of the class (if it were returned from this test function with a concrete type instead of a generic one) would be fine,
602
+ // but using it in this generic context has issues.
603
+ // Specifically the construction APIs don't type check as desired.
604
+
605
+ // @ts -expect-error Compiler limitation, see comment above.
606
+ const _created = TreeAlpha . create ( GenericContainer , { content } ) ;
607
+ // @ts -expect-error Compiler limitation, see comment above.
608
+ return new GenericContainer ( { content } ) ;
609
+ }
610
+
611
+ /**
612
+ * Define a generic container which holds the provided `T` in an optional field.
613
+ */
614
+ function makeInstanceOptional < T extends ImplicitAllowedTypes > (
615
+ schemaTypes : T ,
616
+ content : InsertableTreeFieldFromImplicitField < T > | undefined ,
617
+ ) {
618
+ class GenericContainer extends sf . object ( "GenericContainer" , {
619
+ content : sf . optional ( schemaTypes ) ,
620
+ } ) { }
621
+
622
+ // Like with the above case, TypeScript fails to simplify the input types, and these do not build.
623
+
624
+ // @ts -expect-error Compiler limitation, see comment above.
625
+ const _createdEmpty = TreeAlpha . create ( GenericContainer , { content : undefined } ) ;
626
+ // @ts -expect-error Compiler limitation, see comment above.
627
+ const _created = TreeAlpha . create ( GenericContainer , { content } ) ;
628
+ // @ts -expect-error Compiler limitation, see comment above.
629
+ const _constructedEmpty = new GenericContainer ( { content : undefined } ) ;
630
+ // @ts -expect-error Compiler limitation, see comment above.
631
+ return new GenericContainer ( { content } ) ;
632
+ }
633
+
634
+ /**
635
+ * Define a generic container which holds the provided `T` in an optional field, using objectRecursive.
636
+ * This case is included to highlight one scenario where the compiler limitation does not occur due to simpler typing.
637
+ */
638
+ function makeInstanceOptionalRecursive < T extends ImplicitAllowedTypes > (
639
+ schemaTypes : T ,
640
+ content : InsertableTreeFieldFromImplicitField < T > | undefined ,
641
+ ) {
642
+ class GenericContainer extends sf . objectRecursive ( "GenericContainer" , {
643
+ content : sf . optional ( schemaTypes ) ,
644
+ } ) { }
645
+
646
+ // @ts -expect-error Compiler limitation, see comment above.
647
+ const _createdEmpty = TreeAlpha . create ( GenericContainer , { content : undefined } ) ;
648
+ // @ts -expect-error Compiler limitation, see comment above.
649
+ const _created = TreeAlpha . create ( GenericContainer , { content } ) ;
650
+ const _constructedEmpty = new GenericContainer ( { content : undefined } ) ; // This one works.
651
+ // @ts -expect-error Compiler limitation, see comment above.
652
+ return new GenericContainer ( { content } ) ;
653
+ }
654
+ } ) ;
655
+
656
+ it ( "Generic InsertableTreeFieldFromImplicitField" , < T extends ImplicitAllowedTypes > ( ) => {
657
+ type Required = FieldSchema < FieldKind . Required , T > ;
658
+
659
+ type ArgFieldImplicit2 = InsertableTreeFieldFromImplicitField < T > ;
660
+ type ArgFieldRequired2 = InsertableTreeFieldFromImplicitField < Required > ;
661
+
662
+ // We would expect a required field and an implicitly required field to have the same types.
663
+ // This is normally true, but is failing when the schema is generic due to the compiler limitation noted above.
664
+
665
+ // @ts -expect-error Compiler limitation, see comment above.
666
+ type _check5 = requireAssignableTo < ArgFieldRequired2 , ArgFieldImplicit2 > ;
667
+ // @ts -expect-error Compiler limitation, see comment above.
668
+ type _check6 = requireAssignableTo < ArgFieldImplicit2 , ArgFieldRequired2 > ;
669
+ } ) ;
670
+
671
+ it ( "Generic TreeFieldFromImplicitField" , < T extends ImplicitAllowedTypes > ( ) => {
672
+ type Required = FieldSchema < FieldKind . Required , T > ;
673
+
674
+ type ArgFieldImplicit2 = TreeFieldFromImplicitField < T > ;
675
+ type ArgFieldRequired2 = TreeFieldFromImplicitField < Required > ;
676
+
677
+ // We would expect a required field and an implicitly required field to have the same types.
678
+ // This is normally true, but is failing when the schema is generic due to the compiler limitation noted above.
679
+ // This case is for the node types not the insertable ones, so it was more likely to work, but still fails.
680
+
681
+ // @ts -expect-error Compiler limitation, see comment above.
682
+ type _check5 = requireAssignableTo < ArgFieldRequired2 , ArgFieldImplicit2 > ;
683
+ // @ts -expect-error Compiler limitation, see comment above.
684
+ type _check6 = requireAssignableTo < ArgFieldImplicit2 , ArgFieldRequired2 > ;
685
+ } ) ;
686
+
687
+ it ( "Generic optional field" , < T extends ImplicitAllowedTypes > ( ) => {
688
+ type Optional = FieldSchema < FieldKind . Optional , T > ;
689
+
690
+ type ArgFieldImplicit = InsertableTreeFieldFromImplicitField < T > ;
691
+ type ArgFieldOptional = InsertableTreeFieldFromImplicitField < Optional > ;
692
+
693
+ // An optional field should be the same as a required field unioned with undefined. Typescript fails to see this when its generic:
694
+
695
+ // @ts -expect-error Compiler limitation, see comment above.
696
+ type _check5 = requireAssignableTo < ArgFieldOptional , ArgFieldImplicit | undefined > ;
697
+ // @ts -expect-error Compiler limitation, see comment above.
698
+ type _check6 = requireAssignableTo < ArgFieldImplicit | undefined , ArgFieldOptional > ;
699
+
700
+ // At least this case allows undefined, like recursive object fields, but unlike non recursive object fields.
701
+ type _check7 = requireAssignableTo < undefined , ArgFieldOptional > ;
702
+ } ) ;
703
+ } ) ;
527
704
} ) ;
0 commit comments