import { EqualityMap , mapMap , mapSortByKey , iterableEvery , mapFilter , mapFind , areEqual , setUnionManyInto , definedMap , withDefault } from "collection-utils" ; import { PrimitiveTypeKind , Type , PrimitiveType , EnumType , MapType , ArrayType , ClassType , UnionType , PrimitiveStringTypeKind , ClassProperty , IntersectionType , ObjectType , primitiveTypeIdentity , enumTypeIdentity , mapTypeIdentify , arrayTypeIdentity , classTypeIdentity , unionTypeIdentity , intersectionTypeIdentity , MaybeTypeIdentity , TypeIdentity , TransformedStringTypeKind , isPrimitiveStringTypeKind , transformedStringTypeKinds , TypeKind } from "./Type" ; import { TypeGraph , TypeRef , makeTypeRef , derefTypeRef , typeRefIndex , assertTypeRefGraph } from "./TypeGraph" ; import { TypeAttributes , combineTypeAttributes , TypeAttributeKind , emptyTypeAttributes } from "./attributes/TypeAttributes" ; import { defined , assert , panic } from "./support/Support" ; import { stringTypesTypeAttributeKind , StringTypes } from "./attributes/StringTypes" ; class ProvenanceTypeAttributeKind extends TypeAttributeKind < Set < number > > { constructor ( ) { super ( "provenance" ) ; } appliesToTypeKind ( _kind ) { return true ; } combine ( arr ) { return setUnionManyInto ( new Set ( ) , arr ) ; } makeInferred ( p ) { return p ; } stringify ( p ) { return Array . from ( p ) . sort ( ) . map ( i => i . toString ( ) ) . join ( "," ) ; } } export const provenanceTypeAttributeKind = new ProvenanceTypeAttributeKind ( ) ; export type StringTypeMapping = ReadonlyMap < TransformedStringTypeKind , PrimitiveStringTypeKind > ; export function stringTypeMappingGet ( stm , kind ) { const mapped = stm . get ( kind ) ; if ( mapped === undefined ) return "string" ; return mapped ; } let noStringTypeMapping ; export function getNoStringTypeMapping ( ) { if ( noStringTypeMapping === undefined ) { noStringTypeMapping = new Map ( Array . from ( transformedStringTypeKinds ) . map ( k => [ k , k ] as [ TransformedStringTypeKind , PrimitiveStringTypeKind ] ) ) ; } return noStringTypeMapping ; } export class TypeBuilder { readonly typeGraph : TypeGraph ; protected readonly topLevels : Map < string , TypeRef > = new Map ( ) ; protected readonly types : ( Type | undefined ) [ ] = [ ] ; private readonly typeAttributes : TypeAttributes [ ] = [ ] ; private _addedForwardingIntersection : boolean = false ; constructor ( typeGraphSerial , private readonly _stringTypeMapping , readonly canonicalOrder , private readonly _allPropertiesOptional , private readonly _addProvenanceAttributes , inheritsProvenanceAttributes ) { assert ( ! _addProvenanceAttributes || ! inheritsProvenanceAttributes , "We can't both inherit as well as add provenance" ) ; this . typeGraph = new TypeGraph ( this , typeGraphSerial , _addProvenanceAttributes || inheritsProvenanceAttributes ) ; } addTopLevel ( name , tref ) { assert ( ! this . topLevels . has ( name ) , "Trying to add top-level with existing name" ) ; assert ( this . types [ typeRefIndex ( tref ) ] !== undefined , "Trying to add a top-level type that doesn't exist (yet?)" ) ; this . topLevels . set ( name , tref ) ; } reserveTypeRef ( ) { const index = this . types . length ; this . types . push ( undefined ) ; const tref = makeTypeRef ( this . typeGraph , index ) ; const attributes = this . _addProvenanceAttributes ? provenanceTypeAttributeKind . makeAttributes ( new Set ( [ index ] ) ) : emptyTypeAttributes ; this . typeAttributes . push ( attributes ) ; return tref ; } private assertTypeRefGraph ( tref ) { if ( tref === undefined ) return ; assertTypeRefGraph ( tref , this . typeGraph ) ; } private assertTypeRefSetGraph ( trefs ) { if ( trefs === undefined ) return ; trefs . forEach ( tref => this . assertTypeRefGraph ( tref ) ) ; } private filterTypeAttributes ( t , attributes ) { const filtered = mapFilter ( attributes , ( _ , k ) => k . appliesToTypeKind ( t . kind ) ) ; if ( attributes . size !== filtered . size ) { this . setLostTypeAttributes ( ) ; } return filtered ; } private commitType ( tref , t ) { this . assertTypeRefGraph ( tref ) ; const index = typeRefIndex ( tref ) ; assert ( this . types [ index ] === undefined , "A type index was committed twice" ) ; this . types [ index ] = t ; this . typeAttributes [ index ] = this . filterTypeAttributes ( t , this . typeAttributes [ index ] ) ; } protected addType < T extends Type > ( forwardingRef , creator , attributes ) { if ( forwardingRef !== undefined ) { this . assertTypeRefGraph ( forwardingRef ) ; assert ( this . types [ typeRefIndex ( forwardingRef ) ] === undefined ) ; } const tref = forwardingRef !== undefined ? forwardingRef : this . reserveTypeRef ( ) ; if ( attributes !== undefined ) { const index = typeRefIndex ( tref ) ; this . typeAttributes [ index ] = combineTypeAttributes ( "union" , this . typeAttributes [ index ] , attributes ) ; } const t = creator ( tref ) ; this . commitType ( tref , t ) ; return tref ; } typeAtIndex ( index ) { const maybeType = this . types [ index ] ; if ( maybeType === undefined ) { return panic ( "Trying to deref an undefined type in a type builder" ) ; } return maybeType ; } atIndex ( index ) { const t = this . typeAtIndex ( index ) ; const attribtues = this . typeAttributes [ index ] ; return [ t , attribtues ] ; } addAttributes ( tref , attributes ) { this . assertTypeRefGraph ( tref ) ; const index = typeRefIndex ( tref ) ; const existingAttributes = this . typeAttributes [ index ] ; assert ( iterableEvery ( attributes , ( [ k , v ] ) => { if ( ! k . inIdentity ) return true ; const existing = existingAttributes . get ( k ) ; if ( existing === undefined ) return false ; return areEqual ( existing , v ) ; } ) , "Can't add different identity type attributes to an existing type" ) ; const maybeType = this . types [ index ] ; if ( maybeType !== undefined ) { attributes = this . filterTypeAttributes ( maybeType , attributes ) ; } const nonIdentityAttributes = mapFilter ( attributes , ( _ , k ) => ! k . inIdentity ) ; this . typeAttributes [ index ] = combineTypeAttributes ( "union" , existingAttributes , nonIdentityAttributes ) ; } finish ( ) { this . typeGraph . freeze ( this . topLevels , this . types . map ( defined ) , this . typeAttributes ) ; return this . typeGraph ; } protected addForwardingIntersection ( forwardingRef , tref ) { this . assertTypeRefGraph ( tref ) ; this . _addedForwardingIntersection = true ; return this . addType ( forwardingRef , tr => new IntersectionType ( tr , this . typeGraph , new Set ( [ tref ] ) ) , undefined ) ; } protected forwardIfNecessary ( forwardingRef , tref ) ; protected forwardIfNecessary ( forwardingRef , tref ) ; protected forwardIfNecessary ( forwardingRef , tref ) ; protected forwardIfNecessary ( forwardingRef , tref ) { if ( tref === undefined ) return undefined ; if ( forwardingRef === undefined ) return tref ; return this . addForwardingIntersection ( forwardingRef , tref ) ; } get didAddForwardingIntersection ( ) : boolean { return this . _addedForwardingIntersection ; } private readonly _typeForIdentity : EqualityMap < TypeIdentity , TypeRef > = new EqualityMap ( ) ; private registerTypeForIdentity ( identity , tref ) { if ( identity === undefined ) return ; this . _typeForIdentity . set ( identity , tref ) ; } protected makeIdentity ( maker ) { return maker ( ) ; } private getOrAddType ( identityMaker , creator , attributes , forwardingRef ) { const identity = this . makeIdentity ( identityMaker ) ; let maybeTypeRef ; if ( identity === undefined ) { maybeTypeRef = undefined ; } else { maybeTypeRef = this . _typeForIdentity . get ( identity ) ; } if ( maybeTypeRef !== undefined ) { const result = this . forwardIfNecessary ( forwardingRef , maybeTypeRef ) ; if ( attributes !== undefined ) { this . addAttributes ( result , mapFilter ( attributes , ( _ , k ) => ! k . inIdentity ) ) ; } return result ; } const tref = this . addType ( forwardingRef , creator , attributes ) ; this . registerTypeForIdentity ( identity , tref ) ; return tref ; } private registerType ( t ) { this . registerTypeForIdentity ( t . identity , t . typeRef ) ; } getPrimitiveType ( kind , maybeAttributes ? , forwardingRef ? ) { const attributes = withDefault ( maybeAttributes , emptyTypeAttributes ) ; let stringTypes = kind === "string" ? undefined : StringTypes . unrestricted ; if ( isPrimitiveStringTypeKind ( kind ) && kind !== "string" ) { kind = stringTypeMappingGet ( this . _stringTypeMapping , kind ) ; } if ( kind === "string" ) { return this . getStringType ( attributes , stringTypes , forwardingRef ) ; } return this . getOrAddType ( ( ) => primitiveTypeIdentity ( kind , attributes ) , tr => new PrimitiveType ( tr , this . typeGraph , kind ) , attributes , forwardingRef ) ; } getStringType ( attributes , stringTypes , forwardingRef ? ) { const existingStringTypes = mapFind ( attributes , ( _ , k ) => k === stringTypesTypeAttributeKind ) ; assert ( ( stringTypes === undefined ) !== ( existingStringTypes === undefined ) , "Must instantiate string type with one enum case attribute" ) ; if ( existingStringTypes === undefined ) { attributes = combineTypeAttributes ( "union" , attributes , stringTypesTypeAttributeKind . makeAttributes ( defined ( stringTypes ) ) ) ; } return this . getOrAddType ( ( ) => primitiveTypeIdentity ( "string" , attributes ) , tr => new PrimitiveType ( tr , this . typeGraph , "string" ) , attributes , forwardingRef ) ; } getEnumType ( attributes , cases , forwardingRef ? ) { return this . getOrAddType ( ( ) => enumTypeIdentity ( attributes , cases ) , tr => new EnumType ( tr , this . typeGraph , cases ) , attributes , forwardingRef ) ; } makeClassProperty ( tref , isOptional ) { return new ClassProperty ( tref , this . typeGraph , isOptional ) ; } getUniqueObjectType ( attributes , properties , additionalProperties , forwardingRef ? ) { this . assertTypeRefGraph ( additionalProperties ) ; properties = definedMap ( properties , p => this . modifyPropertiesIfNecessary ( p ) ) ; return this . addType ( forwardingRef , tref => new ObjectType ( tref , this . typeGraph , "object" , true , properties , additionalProperties ) , attributes ) ; } getUniqueMapType ( forwardingRef ? ) { return this . addType ( forwardingRef , tr => new MapType ( tr , this . typeGraph , undefined ) , undefined ) ; } getMapType ( attributes , values , forwardingRef ? ) { this . assertTypeRefGraph ( values ) ; return this . getOrAddType ( ( ) => mapTypeIdentify ( attributes , values ) , tr => new MapType ( tr , this . typeGraph , values ) , attributes , forwardingRef ) ; } setObjectProperties ( ref , properties , additionalProperties ) { this . assertTypeRefGraph ( additionalProperties ) ; const type = derefTypeRef ( ref , this . typeGraph ) ; if ( ! ( type instanceof ObjectType ) ) { return panic ( "Tried to set properties of non-object type" ) ; } type . setProperties ( this . modifyPropertiesIfNecessary ( properties ) , additionalProperties ) ; this . registerType ( type ) ; } getUniqueArrayType ( forwardingRef ? ) { return this . addType ( forwardingRef , tr => new ArrayType ( tr , this . typeGraph , undefined ) , undefined ) ; } getArrayType ( attributes , items , forwardingRef ? ) { this . assertTypeRefGraph ( items ) ; return this . getOrAddType ( ( ) => arrayTypeIdentity ( attributes , items ) , tr => new ArrayType ( tr , this . typeGraph , items ) , attributes , forwardingRef ) ; } setArrayItems ( ref , items ) { this . assertTypeRefGraph ( items ) ; const type = derefTypeRef ( ref , this . typeGraph ) ; if ( ! ( type instanceof ArrayType ) ) { return panic ( "Tried to set items of non-array type" ) ; } type . setItems ( items ) ; this . registerType ( type ) ; } modifyPropertiesIfNecessary ( properties ) { properties . forEach ( p => this . assertTypeRefGraph ( p . typeRef ) ) ; if ( this . canonicalOrder ) { properties = mapSortByKey ( properties ) ; } if ( this . _allPropertiesOptional ) { properties = mapMap ( properties , cp => this . makeClassProperty ( cp . typeRef , true ) ) ; } return properties ; } getClassType ( attributes , properties , forwardingRef ? ) { properties = this . modifyPropertiesIfNecessary ( properties ) ; return this . getOrAddType ( ( ) => classTypeIdentity ( attributes , properties ) , tr => new ClassType ( tr , this . typeGraph , false , properties ) , attributes , forwardingRef ) ; } getUniqueClassType ( attributes , isFixed , properties , forwardingRef ? ) { properties = definedMap ( properties , p => this . modifyPropertiesIfNecessary ( p ) ) ; return this . addType ( forwardingRef , tref => new ClassType ( tref , this . typeGraph , isFixed , properties ) , attributes ) ; } getUnionType ( attributes , members , forwardingRef ? ) { this . assertTypeRefSetGraph ( members ) ; return this . getOrAddType ( ( ) => unionTypeIdentity ( attributes , members ) , tr => new UnionType ( tr , this . typeGraph , members ) , attributes , forwardingRef ) ; } getUniqueUnionType ( attributes , members , forwardingRef ? ) { this . assertTypeRefSetGraph ( members ) ; return this . addType ( forwardingRef , tref => new UnionType ( tref , this . typeGraph , members ) , attributes ) ; } getIntersectionType ( attributes , members , forwardingRef ? ) { this . assertTypeRefSetGraph ( members ) ; return this . getOrAddType ( ( ) => intersectionTypeIdentity ( attributes , members ) , tr => new IntersectionType ( tr , this . typeGraph , members ) , attributes , forwardingRef ) ; } getUniqueIntersectionType ( attributes , members , forwardingRef ? ) { this . assertTypeRefSetGraph ( members ) ; return this . addType ( forwardingRef , tref => new IntersectionType ( tref , this . typeGraph , members ) , attributes ) ; } setSetOperationMembers ( ref , members ) { this . assertTypeRefSetGraph ( members ) ; const type = derefTypeRef ( ref , this . typeGraph ) ; if ( ! ( type instanceof UnionType || type instanceof IntersectionType ) ) { return panic ( "Tried to set members of non-set-operation type" ) ; } type . setMembers ( members ) ; this . registerType ( type ) ; } setLostTypeAttributes ( ) { return ; } }