import { mapMap , EqualityMap } from "collection-utils" ; import { PrimitiveTypeKind , Type , ClassProperty , MaybeTypeIdentity } from "./Type" ; import { combineTypeAttributesOfTypes } from "./TypeUtils" ; import { TypeGraph , TypeRef , derefTypeRef , typeAndAttributesForTypeRef , assertTypeRefGraph , typeRefIndex , isTypeRef } from "./TypeGraph" ; import { TypeAttributes , emptyTypeAttributes , combineTypeAttributes } from "./attributes/TypeAttributes" ; import { assert , panic , indentationString } from "./support/Support" ; import { TypeBuilder , StringTypeMapping } from "./TypeBuilder" ; export interface TypeLookerUp { lookupTypeRefs ( typeRefs , forwardingRef ? ) : TypeRef | undefined ; reconstituteTypeRef ( typeRef , attributes ? , forwardingRef ? ) : TypeRef ; } export class TypeReconstituter < TBuilder extends BaseGraphRewriteBuilder > { private _wasUsed : boolean = false ; private _typeRef : TypeRef | undefined = undefined ; constructor ( private readonly _typeBuilder , private readonly _makeClassUnique , private readonly _typeAttributes , private readonly _forwardingRef , private readonly _register ) { } private builderForNewType ( ) { assert ( ! this . _wasUsed , "TypeReconstituter used more than once" ) ; this . _wasUsed = true ; return this . _typeBuilder ; } private builderForSetting ( ) { assert ( this . _wasUsed && this . _typeRef !== undefined , "Can't set type members before constructing a type" ) ; return this . _typeBuilder ; } getResult ( ) { if ( this . _typeRef === undefined ) { return panic ( "Type was not reconstituted" ) ; } return this . _typeRef ; } private register ( tref ) { assert ( this . _typeRef === undefined , "Cannot register a type twice" ) ; this . _typeRef = tref ; this . _register ( tref ) ; } private registerAndAddAttributes ( tref ) { this . _typeBuilder . addAttributes ( tref , this . _typeAttributes ) ; this . register ( tref ) ; } lookup ( tref ) ; lookup ( trefs ) ; lookup ( trefs ) { assert ( ! this . _wasUsed , "Cannot lookup constituents after building type" ) ; if ( isTypeRef ( trefs ) ) { return this . _typeBuilder . lookupTypeRefs ( [ trefs ] , undefined , false ) ; } else { const maybeRefs = Array . from ( trefs ) . map ( tref => this . _typeBuilder . lookupTypeRefs ( [ tref ] , undefined , false ) ) ; if ( maybeRefs . some ( tref => tref === undefined ) ) return undefined ; return maybeRefs as ReadonlyArray < TypeRef > ; } } lookupMap < K > ( trefs ) { const resultValues = this . lookup ( trefs . values ( ) ) ; if ( resultValues === undefined ) return undefined ; assert ( resultValues . length === trefs . size , "Didn't get back the correct number of types" ) ; const result = new Map < K , TypeRef > ( ) ; let i = 0 ; for ( const k of trefs . keys ( ) ) { result . set ( k , resultValues [ i ] ) ; i += 1 ; } return result ; } reconstitute ( tref ) ; reconstitute ( trefs ) ; reconstitute ( trefs ) { assert ( this . _wasUsed , "Cannot reconstitute constituents before building type" ) ; if ( isTypeRef ( trefs ) ) { return this . _typeBuilder . reconstituteTypeRef ( trefs ) ; } else { return Array . from ( trefs ) . map ( tref => this . _typeBuilder . reconstituteTypeRef ( tref ) ) ; } } reconstituteMap < K > ( trefs ) { return mapMap ( trefs , tref => this . _typeBuilder . reconstituteTypeRef ( tref ) ) ; } getPrimitiveType ( kind ) { this . register ( this . builderForNewType ( ) . getPrimitiveType ( kind , this . _typeAttributes , this . _forwardingRef ) ) ; } getEnumType ( cases ) { this . register ( this . builderForNewType ( ) . getEnumType ( this . _typeAttributes , cases , this . _forwardingRef ) ) ; } getUniqueMapType ( ) { this . registerAndAddAttributes ( this . builderForNewType ( ) . getUniqueMapType ( this . _forwardingRef ) ) ; } getMapType ( values ) { this . register ( this . builderForNewType ( ) . getMapType ( this . _typeAttributes , values , this . _forwardingRef ) ) ; } getUniqueArrayType ( ) { this . registerAndAddAttributes ( this . builderForNewType ( ) . getUniqueArrayType ( this . _forwardingRef ) ) ; } getArrayType ( items ) { this . register ( this . builderForNewType ( ) . getArrayType ( this . _typeAttributes , items , this . _forwardingRef ) ) ; } setArrayItems ( items ) { this . builderForSetting ( ) . setArrayItems ( this . getResult ( ) , items ) ; } makeClassProperty ( tref , isOptional ) { return this . _typeBuilder . makeClassProperty ( tref , isOptional ) ; } getObjectType ( properties , additionalProperties ) { this . register ( this . builderForNewType ( ) . getUniqueObjectType ( this . _typeAttributes , properties , additionalProperties , this . _forwardingRef ) ) ; } getUniqueObjectType ( properties , additionalProperties ) { this . register ( this . builderForNewType ( ) . getUniqueObjectType ( this . _typeAttributes , properties , additionalProperties , this . _forwardingRef ) ) ; } getClassType ( properties ) { if ( this . _makeClassUnique ) { this . getUniqueClassType ( false , properties ) ; return ; } this . register ( this . builderForNewType ( ) . getClassType ( this . _typeAttributes , properties , this . _forwardingRef ) ) ; } getUniqueClassType ( isFixed , properties ) { this . register ( this . builderForNewType ( ) . getUniqueClassType ( this . _typeAttributes , isFixed , properties , this . _forwardingRef ) ) ; } setObjectProperties ( properties , additionalProperties ) { this . builderForSetting ( ) . setObjectProperties ( this . getResult ( ) , properties , additionalProperties ) ; } getUnionType ( members ) { this . register ( this . builderForNewType ( ) . getUnionType ( this . _typeAttributes , members , this . _forwardingRef ) ) ; } getUniqueUnionType ( ) { this . register ( this . builderForNewType ( ) . getUniqueUnionType ( this . _typeAttributes , undefined , this . _forwardingRef ) ) ; } getIntersectionType ( members ) { this . register ( this . builderForNewType ( ) . getIntersectionType ( this . _typeAttributes , members , this . _forwardingRef ) ) ; } getUniqueIntersectionType ( members ? ) { this . register ( this . builderForNewType ( ) . getUniqueIntersectionType ( this . _typeAttributes , members , this . _forwardingRef ) ) ; } setSetOperationMembers ( members ) { this . builderForSetting ( ) . setSetOperationMembers ( this . getResult ( ) , members ) ; } } export abstract class BaseGraphRewriteBuilder extends TypeBuilder implements TypeLookerUp { protected readonly reconstitutedTypes : Map < number , TypeRef > = new Map ( ) ; private _lostTypeAttributes : boolean = false ; private _printIndent = 0 ; constructor ( readonly originalGraph , stringTypeMapping , alphabetizeProperties , graphHasProvenanceAttributes , protected readonly debugPrint ) { super ( originalGraph . serial + 1 , stringTypeMapping , alphabetizeProperties , false , false , graphHasProvenanceAttributes ) ; } withForwardingRef ( maybeForwardingRef , typeCreator ) { if ( maybeForwardingRef !== undefined ) { return typeCreator ( maybeForwardingRef ) ; } const forwardingRef = this . reserveTypeRef ( ) ; const actualRef = typeCreator ( forwardingRef ) ; assert ( actualRef === forwardingRef , "Type creator didn't return its forwarding ref" ) ; return actualRef ; } reconstituteType ( t , attributes ? , forwardingRef ? ) { return this . reconstituteTypeRef ( t . typeRef , attributes , forwardingRef ) ; } abstract lookupTypeRefs ( typeRefs , forwardingRef ? , replaceSet ? ) ; protected abstract forceReconstituteTypeRef ( originalRef , attributes ? , maybeForwardingRef ? ) ; reconstituteTypeRef ( originalRef , attributes ? , maybeForwardingRef ? ) { const maybeRef = this . lookupTypeRefs ( [ originalRef ] , maybeForwardingRef ) ; if ( maybeRef !== undefined ) { if ( attributes !== undefined ) { this . addAttributes ( maybeRef , attributes ) ; } return maybeRef ; } return this . forceReconstituteTypeRef ( originalRef , attributes , maybeForwardingRef ) ; } reconstituteTypeAttributes ( attributes ) { return mapMap ( attributes , ( v , a ) => a . reconstitute ( this , v ) ) ; } protected assertTypeRefsToReconstitute ( typeRefs , forwardingRef ? ) { assert ( typeRefs . length > 0 , "Must have at least one type to reconstitute" ) ; for ( const originalRef of typeRefs ) { assertTypeRefGraph ( originalRef , this . originalGraph ) ; } if ( forwardingRef !== undefined ) { assertTypeRefGraph ( forwardingRef , this . typeGraph ) ; } } protected changeDebugPrintIndent ( delta ) { this . _printIndent += delta ; } protected get debugPrintIndentation ( ) : string { return indentationString ( this . _printIndent ) ; } finish ( ) { for ( const [ name , t ] of this . originalGraph . topLevels ) { this . addTopLevel ( name , this . reconstituteType ( t ) ) ; } return super . finish ( ) ; } setLostTypeAttributes ( ) { this . _lostTypeAttributes = true ; } get lostTypeAttributes ( ) : boolean { return this . _lostTypeAttributes ; } } export class GraphRemapBuilder extends BaseGraphRewriteBuilder { private readonly _attributeSources : Map < Type , Type [ ] > = new Map ( ) ; constructor ( originalGraph , stringTypeMapping , alphabetizeProperties , graphHasProvenanceAttributes , private readonly _map , debugPrintRemapping ) { super ( originalGraph , stringTypeMapping , alphabetizeProperties , graphHasProvenanceAttributes , debugPrintRemapping ) ; for ( const [ source , target ] of _map ) { let maybeSources = this . _attributeSources . get ( target ) ; if ( maybeSources === undefined ) { maybeSources = [ target ] ; this . _attributeSources . set ( target , maybeSources ) ; } maybeSources . push ( source ) ; } } protected makeIdentity ( _maker ) { return undefined ; } private getMapTarget ( tref ) { const maybeType = this . _map . get ( derefTypeRef ( tref , this . originalGraph ) ) ; if ( maybeType === undefined ) return tref ; assert ( this . _map . get ( maybeType ) === undefined , "We have a type that's remapped to a remapped type" ) ; return maybeType . typeRef ; } protected addForwardingIntersection ( _forwardingRef , _tref ) { return panic ( "We can't add forwarding intersections when we're removing forwarding intersections" ) ; } lookupTypeRefs ( typeRefs , forwardingRef ? ) { assert ( forwardingRef === undefined , "We can't have a forwarding ref when we remap" ) ; this . assertTypeRefsToReconstitute ( typeRefs , forwardingRef ) ; const first = this . reconstitutedTypes . get ( typeRefIndex ( this . getMapTarget ( typeRefs [ 0 ] ) ) ) ; if ( first === undefined ) return undefined ; for ( let i = 1 ; i < typeRefs . length ; i ++ ) { const other = this . reconstitutedTypes . get ( typeRefIndex ( this . getMapTarget ( typeRefs [ i ] ) ) ) ; if ( first !== other ) return undefined ; } return first ; } protected forceReconstituteTypeRef ( originalRef , attributes ? , maybeForwardingRef ? ) { originalRef = this . getMapTarget ( originalRef ) ; const index = typeRefIndex ( originalRef ) ; assert ( this . reconstitutedTypes . get ( index ) === undefined , "Type has already been reconstituted" ) ; assert ( maybeForwardingRef === undefined , "We can't have a forwarding ref when we remap" ) ; return this . withForwardingRef ( undefined , forwardingRef => { this . reconstitutedTypes . set ( index , forwardingRef ) ; if ( this . debugPrint ) { console . log ( `${ this . debugPrintIndentation }reconstituting ${ index } as ${ typeRefIndex ( forwardingRef ) }` ) ; this . changeDebugPrintIndent ( 1 ) ; } const [ originalType , originalAttributes ] = typeAndAttributesForTypeRef ( originalRef , this . originalGraph ) ; const attributeSources = this . _attributeSources . get ( originalType ) ; if ( attributes === undefined ) { attributes = emptyTypeAttributes ; } if ( attributeSources === undefined ) { attributes = combineTypeAttributes ( "union" , attributes , this . reconstituteTypeAttributes ( originalAttributes ) ) ; } else { attributes = combineTypeAttributes ( "union" , attributes , this . reconstituteTypeAttributes ( combineTypeAttributesOfTypes ( "union" , attributeSources ) ) ) ; } const newAttributes = attributes ; const reconstituter = new TypeReconstituter ( this , this . canonicalOrder , newAttributes , forwardingRef , tref => { assert ( tref === forwardingRef , "Reconstituted type as a different ref" ) ; if ( this . debugPrint ) { this . changeDebugPrintIndent ( - 1 ) ; console . log ( `${ this . debugPrintIndentation }reconstituted ${ index } as ${ typeRefIndex ( tref ) }` ) ; } } ) ; originalType . reconstitute ( reconstituter , this . canonicalOrder ) ; return reconstituter . getResult ( ) ; } ) ; } } export class GraphRewriteBuilder < T extends Type > extends BaseGraphRewriteBuilder { private readonly _setsToReplaceByMember : Map < number , Set < T > > ; private readonly _reconstitutedUnions : EqualityMap < Set < TypeRef > , TypeRef > = new EqualityMap ( ) ; constructor ( originalGraph , stringTypeMapping , alphabetizeProperties , graphHasProvenanceAttributes , setsToReplace , debugPrintReconstitution , private readonly _replacer ) { super ( originalGraph , stringTypeMapping , alphabetizeProperties , graphHasProvenanceAttributes , debugPrintReconstitution ) ; this . _setsToReplaceByMember = new Map ( ) ; for ( const types of setsToReplace ) { const set = new Set ( types ) ; for ( const t of set ) { const index = t . index ; assert ( ! this . _setsToReplaceByMember . has ( index ) , "A type is member of more than one set to be replaced" ) ; this . _setsToReplaceByMember . set ( index , set ) ; } } } registerUnion ( typeRefs , reconstituted ) { const set = new Set ( typeRefs ) ; assert ( ! this . _reconstitutedUnions . has ( set ) , "Cannot register reconstituted set twice" ) ; this . _reconstitutedUnions . set ( set , reconstituted ) ; } private replaceSet ( typesToReplace , maybeForwardingRef ) { return this . withForwardingRef ( maybeForwardingRef , forwardingRef => { if ( this . debugPrint ) { console . log ( `${ this . debugPrintIndentation }replacing set ${ Array . from ( typesToReplace ) . map ( t => t . index . toString ( ) ) . join ( "," ) } as ${ typeRefIndex ( forwardingRef ) }` ) ; this . changeDebugPrintIndent ( 1 ) ; } for ( const t of typesToReplace ) { const originalRef = t . typeRef ; const index = typeRefIndex ( originalRef ) ; this . reconstitutedTypes . set ( index , forwardingRef ) ; this . _setsToReplaceByMember . delete ( index ) ; } const result = this . _replacer ( typesToReplace , this , forwardingRef ) ; assert ( result === forwardingRef , "The forwarding ref got lost when replacing" ) ; if ( this . debugPrint ) { this . changeDebugPrintIndent ( - 1 ) ; console . log ( `${ this . debugPrintIndentation }replaced set ${ Array . from ( typesToReplace ) . map ( t => t . index . toString ( ) ) . join ( "," ) } as ${ typeRefIndex ( forwardingRef ) }` ) ; } return result ; } ) ; } protected forceReconstituteTypeRef ( originalRef , attributes ? , maybeForwardingRef ? ) { const [ originalType , originalAttributes ] = typeAndAttributesForTypeRef ( originalRef , this . originalGraph ) ; const index = typeRefIndex ( originalRef ) ; if ( this . debugPrint ) { console . log ( `${ this . debugPrintIndentation }reconstituting ${ index }` ) ; this . changeDebugPrintIndent ( 1 ) ; } if ( attributes === undefined ) { attributes = this . reconstituteTypeAttributes ( originalAttributes ) ; } else { attributes = combineTypeAttributes ( "union" , attributes , this . reconstituteTypeAttributes ( originalAttributes ) ) ; } const reconstituter = new TypeReconstituter ( this , this . canonicalOrder , attributes , maybeForwardingRef , tref => { if ( this . debugPrint ) { this . changeDebugPrintIndent ( - 1 ) ; console . log ( `${ this . debugPrintIndentation }reconstituted ${ index } as ${ typeRefIndex ( tref ) }` ) ; } if ( maybeForwardingRef !== undefined ) { assert ( tref === maybeForwardingRef , "We didn't pass the forwarding ref" ) ; } const alreadyReconstitutedType = this . reconstitutedTypes . get ( index ) ; if ( alreadyReconstitutedType === undefined ) { this . reconstitutedTypes . set ( index , tref ) ; } else { assert ( tref === alreadyReconstitutedType , "We reconstituted a type twice differently" ) ; } } ) ; originalType . reconstitute ( reconstituter , this . canonicalOrder ) ; return reconstituter . getResult ( ) ; } lookupTypeRefs ( typeRefs , forwardingRef ? , replaceSet = true ) { this . assertTypeRefsToReconstitute ( typeRefs , forwardingRef ) ; let maybeRef = this . reconstitutedTypes . get ( typeRefIndex ( typeRefs [ 0 ] ) ) ; if ( maybeRef !== undefined && maybeRef !== forwardingRef ) { let allEqual = true ; for ( let i = 1 ; i < typeRefs . length ; i ++ ) { if ( this . reconstitutedTypes . get ( typeRefIndex ( typeRefs [ i ] ) ) !== maybeRef ) { allEqual = false ; break ; } } if ( allEqual ) { return this . forwardIfNecessary ( forwardingRef , maybeRef ) ; } } maybeRef = this . _reconstitutedUnions . get ( new Set ( typeRefs ) ) ; if ( maybeRef !== undefined && maybeRef !== forwardingRef ) { return this . forwardIfNecessary ( forwardingRef , maybeRef ) ; } const maybeSet = this . _setsToReplaceByMember . get ( typeRefIndex ( typeRefs [ 0 ] ) ) ; if ( maybeSet === undefined ) { return undefined ; } for ( let i = 1 ; i < typeRefs . length ; i ++ ) { if ( this . _setsToReplaceByMember . get ( typeRefIndex ( typeRefs [ i ] ) ) !== maybeSet ) { return undefined ; } } if ( ! replaceSet ) return undefined ; return this . replaceSet ( maybeSet , forwardingRef ) ; } }