@@ -29,14 +29,30 @@ interface GenericRenderParam {
2929
3030interface GenericRenderContext {
3131 currentModule ?: string ;
32+ localDeclaredNames : Set < string > ;
3233 declaration : string ;
3334 typeArguments : string ;
3435 emittedNames : Set < string > ;
3536 emittedParamSpecs : Set < string > ;
3637}
3738
39+ export interface CodeGeneratorOptions {
40+ /** Reports a generated annotation that cannot be represented by emitted declarations. */
41+ onTypeDegrade ?: ( typeName : string ) => void ;
42+ }
43+
3844export class CodeGenerator {
3945 private readonly mapper : TypeMapper ;
46+ private readonly onTypeDegrade ?: ( typeName : string ) => void ;
47+ private readonly builtinGenericNames = new Set ( [
48+ 'Array' ,
49+ 'AsyncIterator' ,
50+ 'Generator' ,
51+ 'Iterable' ,
52+ 'Iterator' ,
53+ 'Promise' ,
54+ 'Record' ,
55+ ] ) ;
4056 private readonly reservedTsIdentifiers = new Set ( [
4157 'default' ,
4258 'delete' ,
@@ -66,8 +82,9 @@ export class CodeGenerator {
6682 'false' ,
6783 ] ) ;
6884
69- constructor ( mapper : TypeMapper = new TypeMapper ( ) ) {
85+ constructor ( mapper : TypeMapper = new TypeMapper ( ) , options : CodeGeneratorOptions = { } ) {
7086 this . mapper = mapper ;
87+ this . onTypeDegrade = options . onTypeDegrade ;
7188 }
7289
7390 /**
@@ -140,7 +157,8 @@ export class CodeGenerator {
140157 private buildGenericRenderContext (
141158 typeParameters : readonly PythonGenericParameter [ ] ,
142159 types : readonly PythonType [ ] ,
143- currentModule ?: string
160+ currentModule ?: string ,
161+ localDeclaredNames : Set < string > = new Set ( )
144162 ) : GenericRenderContext {
145163 const callableParamSpecs = new Set < string > ( ) ;
146164 types . forEach ( type => this . collectCallableParamSpecs ( type , callableParamSpecs ) ) ;
@@ -170,6 +188,7 @@ export class CodeGenerator {
170188
171189 return {
172190 currentModule,
191+ localDeclaredNames,
173192 declaration :
174193 emitted . length > 0 ? `<${ emitted . map ( param => param . declaration ) . join ( ', ' ) } >` : '' ,
175194 typeArguments : emitted . length > 0 ? `<${ emitted . map ( param => param . name ) . join ( ', ' ) } >` : '' ,
@@ -184,6 +203,7 @@ export class CodeGenerator {
184203 ) : GenericRenderContext {
185204 return {
186205 currentModule : inner . currentModule ?? outer . currentModule ,
206+ localDeclaredNames : inner . localDeclaredNames ,
187207 declaration : inner . declaration ,
188208 typeArguments : inner . typeArguments ,
189209 emittedNames : new Set ( [ ...outer . emittedNames , ...inner . emittedNames ] ) ,
@@ -308,7 +328,27 @@ export class CodeGenerator {
308328 ctx : GenericRenderContext ,
309329 mappingContext : 'value' | 'return'
310330 ) : string {
311- return this . typeToTs ( this . mapper . mapPythonType ( this . sanitizeType ( type , ctx ) , mappingContext ) ) ;
331+ return this . typeToTs (
332+ this . mapper . mapPythonType ( this . sanitizeType ( type , ctx ) , mappingContext ) ,
333+ ctx ,
334+ mappingContext
335+ ) ;
336+ }
337+
338+ private isLocalTypeIdentity (
339+ type : { name : string ; module ?: string } ,
340+ ctx : GenericRenderContext
341+ ) : boolean {
342+ if ( ! ctx . localDeclaredNames . has ( type . name ) ) {
343+ return false ;
344+ }
345+ return type . module === undefined || type . module === ctx . currentModule ;
346+ }
347+
348+ private degradeType ( type : { name : string ; module ?: string } ) : string {
349+ const identity = type . module ? `${ type . module } .${ type . name } ` : type . name ;
350+ this . onTypeDegrade ?.( identity ) ;
351+ return 'unknown' ;
312352 }
313353
314354 private renderLooksLikeKwargsExpr (
@@ -360,7 +400,8 @@ export class CodeGenerator {
360400 generateFunctionWrapper (
361401 func : PythonFunction ,
362402 moduleName ?: string ,
363- annotatedJSDoc = false
403+ annotatedJSDoc = false ,
404+ localDeclaredNames : Set < string > = new Set ( )
364405 ) : GeneratedCode {
365406 const jsdoc = this . generateJsDoc (
366407 func . docstring ,
@@ -379,7 +420,8 @@ export class CodeGenerator {
379420 const genericContext = this . buildGenericRenderContext (
380421 this . getTypeParameters ( func . typeParameters ) ,
381422 [ func . returnType , ...filteredParams . map ( param => param . type ) ] ,
382- moduleName
423+ moduleName ,
424+ localDeclaredNames
383425 ) ;
384426 const typeParamDecl = genericContext . declaration ;
385427
@@ -553,8 +595,11 @@ ${callPrelude}${guards} return getRuntimeBridge().call('${moduleId}', '${func.n
553595 generateClassWrapper (
554596 cls : PythonClass ,
555597 moduleName ?: string ,
556- _annotatedJSDoc = false
598+ _annotatedJSDoc = false ,
599+ localDeclaredNames : Set < string > = new Set ( )
557600 ) : GeneratedCode {
601+ const moduleDeclaredNames = new Set ( localDeclaredNames ) ;
602+ moduleDeclaredNames . add ( cls . name ) ;
558603 const jsdoc = this . generateJsDoc ( cls . docstring ) ;
559604 const classGenericContext = this . buildGenericRenderContext (
560605 this . getTypeParameters ( cls . typeParameters ) ,
@@ -566,7 +611,8 @@ ${callPrelude}${guards} return getRuntimeBridge().call('${moduleId}', '${func.n
566611 ...method . parameters . map ( p => p . type ) ,
567612 ] ) ,
568613 ] ,
569- moduleName
614+ moduleName ,
615+ moduleDeclaredNames
570616 ) ;
571617 const classTypeParamDecl = classGenericContext . declaration ;
572618 const cname = this . escapeIdentifier ( cls . name ) ;
@@ -617,7 +663,8 @@ ${callPrelude}${guards} return getRuntimeBridge().call('${moduleId}', '${func.n
617663 const methodOwnGenericContext = this . buildGenericRenderContext (
618664 this . getTypeParameters ( m . typeParameters ) ,
619665 [ m . returnType , ...fparams . map ( param => param . type ) ] ,
620- moduleName
666+ moduleName ,
667+ moduleDeclaredNames
621668 ) ;
622669 const methodGenericContext = this . mergeGenericRenderContexts (
623670 classGenericContext ,
@@ -668,7 +715,8 @@ ${callPrelude}${guards} return getRuntimeBridge().call('${moduleId}', '${func.n
668715 const methodOwnGenericContext = this . buildGenericRenderContext (
669716 this . getTypeParameters ( method . typeParameters ) ,
670717 [ method . returnType , ...fparams . map ( param => param . type ) ] ,
671- moduleName
718+ moduleName ,
719+ moduleDeclaredNames
672720 ) ;
673721 const methodGenericContext = this . mergeGenericRenderContexts (
674722 classGenericContext ,
@@ -816,11 +864,16 @@ ${migrationNote}${declarationMethodsSection}
816864 return this . wrap ( ts , declaration , [ cls . name ] ) ;
817865 }
818866
819- generateTypeAlias ( alias : PythonTypeAlias , moduleName ?: string ) : GeneratedCode {
867+ generateTypeAlias (
868+ alias : PythonTypeAlias ,
869+ moduleName ?: string ,
870+ localDeclaredNames : Set < string > = new Set ( )
871+ ) : GeneratedCode {
820872 const genericContext = this . buildGenericRenderContext (
821873 this . getTypeParameters ( alias . typeParameters ) ,
822874 [ alias . type ] ,
823- moduleName
875+ moduleName ,
876+ localDeclaredNames
824877 ) ;
825878 const aliasName = this . escapeIdentifier ( alias . name , { preserveCase : true } ) ;
826879 const body = this . typeToTsFromPython ( alias . type , genericContext , 'value' ) ;
@@ -854,15 +907,19 @@ ${migrationNote}${declarationMethodsSection}
854907 }
855908
856909 generateModuleDefinition ( module : PythonModule , annotatedJSDoc = false ) : GeneratedCode {
910+ const localDeclaredNames = new Set ( [
911+ ...module . classes . map ( cls => cls . name ) ,
912+ ...( module . typeAliases ?? [ ] ) . map ( alias => alias . name ) ,
913+ ] ) ;
857914 const functionResults = [ ...module . functions ]
858915 . sort ( ( a , b ) => a . name . localeCompare ( b . name ) )
859- . map ( f => this . generateFunctionWrapper ( f , module . name , annotatedJSDoc ) ) ;
916+ . map ( f => this . generateFunctionWrapper ( f , module . name , annotatedJSDoc , localDeclaredNames ) ) ;
860917 const classResults = [ ...module . classes ]
861918 . sort ( ( a , b ) => a . name . localeCompare ( b . name ) )
862- . map ( c => this . generateClassWrapper ( c , module . name , annotatedJSDoc ) ) ;
919+ . map ( c => this . generateClassWrapper ( c , module . name , annotatedJSDoc , localDeclaredNames ) ) ;
863920 const typeAliasResults = [ ...( module . typeAliases ?? [ ] ) ]
864921 . sort ( ( a , b ) => a . name . localeCompare ( b . name ) )
865- . map ( alias => this . generateTypeAlias ( alias , module . name ) ) ;
922+ . map ( alias => this . generateTypeAlias ( alias , module . name , localDeclaredNames ) ) ;
866923
867924 const functionCodes = functionResults . map ( result => result . typescript ) . join ( '\n' ) ;
868925 const classCodes = classResults . map ( result => result . typescript ) . join ( '\n' ) ;
@@ -917,23 +974,27 @@ ${migrationNote}${declarationMethodsSection}
917974 } ;
918975 }
919976
920- private typeToTs ( type : TypescriptType ) : string {
977+ private typeToTs (
978+ type : TypescriptType ,
979+ ctx ?: GenericRenderContext ,
980+ mappingContext : 'value' | 'return' = 'value'
981+ ) : string {
921982 switch ( type . kind ) {
922983 case 'primitive' :
923984 return type . name ;
924985 case 'array' :
925- return `${ this . typeToTs ( type . elementType ) } []` ;
986+ return `${ this . typeToTs ( type . elementType , ctx , mappingContext ) } []` ;
926987 case 'tuple' : {
927988 const t = type as { kind : 'tuple' ; elementTypes : TypescriptType [ ] } ;
928- const parts = t . elementTypes . map ( e => this . typeToTs ( e ) ) . join ( ', ' ) ;
989+ const parts = t . elementTypes . map ( e => this . typeToTs ( e , ctx , mappingContext ) ) . join ( ', ' ) ;
929990 return `[${ parts } ]` ;
930991 }
931992 case 'object' : {
932993 const t = type ;
933994 // If it's a simple Record<string, T> pattern, use that syntax
934995 if ( t . properties . length === 0 && t . indexSignature ) {
935- const keyType = this . typeToTs ( t . indexSignature . keyType ) ;
936- const valueType = this . typeToTs ( t . indexSignature . valueType ) ;
996+ const keyType = this . typeToTs ( t . indexSignature . keyType , ctx , mappingContext ) ;
997+ const valueType = this . typeToTs ( t . indexSignature . valueType , ctx , mappingContext ) ;
937998 if ( keyType === 'string' ) {
938999 return `Record<string, ${ valueType } >` ;
9391000 }
@@ -942,34 +1003,53 @@ ${migrationNote}${declarationMethodsSection}
9421003 const props = t . properties
9431004 . map (
9441005 p =>
945- `${ p . readonly ? 'readonly ' : '' } ${ p . name } ${ p . optional ? '?' : '' } : ${ this . typeToTs ( p . type ) } ;`
1006+ `${ p . readonly ? 'readonly ' : '' } ${ p . name } ${ p . optional ? '?' : '' } : ${ this . typeToTs ( p . type , ctx , mappingContext ) } ;`
9461007 )
9471008 . join ( ' ' ) ;
9481009 const indexSig = t . indexSignature
949- ? `[key: ${ this . typeToTs ( t . indexSignature . keyType ) } ]: ${ this . typeToTs ( t . indexSignature . valueType ) } ;`
1010+ ? `[key: ${ this . typeToTs ( t . indexSignature . keyType , ctx , mappingContext ) } ]: ${ this . typeToTs ( t . indexSignature . valueType , ctx , mappingContext ) } ;`
9501011 : '' ;
9511012 return `{ ${ props } ${ indexSig } }` ;
9521013 }
9531014 case 'union' :
954- return type . types . map ( t => this . typeToTs ( t ) ) . join ( ' | ' ) ;
1015+ return type . types . map ( t => this . typeToTs ( t , ctx , mappingContext ) ) . join ( ' | ' ) ;
9551016 case 'function' : {
9561017 const ft = type ;
9571018 const params = ft . parameters
9581019 . map (
959- p => `${ p . rest ? '...' : '' } ${ p . name } ${ p . optional ? '?' : '' } : ${ this . typeToTs ( p . type ) } `
1020+ p =>
1021+ `${ p . rest ? '...' : '' } ${ p . name } ${ p . optional ? '?' : '' } : ${ this . typeToTs ( p . type , ctx , mappingContext ) } `
9601022 )
9611023 . join ( ', ' ) ;
962- return `(${ params } ) => ${ this . typeToTs ( ft . returnType ) } ` ;
1024+ return `(${ params } ) => ${ this . typeToTs ( ft . returnType , ctx , mappingContext ) } ` ;
9631025 }
9641026 case 'generic' : {
965- const g = type as { kind : 'generic' ; name : string ; typeArgs : TypescriptType [ ] } ;
966- const args = g . typeArgs . map ( a => this . typeToTs ( a ) ) . join ( ', ' ) ;
1027+ const g = type as {
1028+ kind : 'generic' ;
1029+ name : string ;
1030+ module ?: string ;
1031+ typeArgs : TypescriptType [ ] ;
1032+ } ;
1033+ if ( ! / ^ [ A - Z a - z _ $ ] [ A - Z a - z 0 - 9 _ $ ] * $ / . test ( g . name ) ) {
1034+ return this . degradeType ( g ) ;
1035+ }
1036+ if ( ctx && ! this . builtinGenericNames . has ( g . name ) && ! this . isLocalTypeIdentity ( g , ctx ) ) {
1037+ return this . degradeType ( g ) ;
1038+ }
1039+ const args = g . typeArgs . map ( a => this . typeToTs ( a , ctx , mappingContext ) ) . join ( ', ' ) ;
9671040 return `${ g . name } <${ args } >` ;
9681041 }
9691042 case 'custom' : {
970- const c = type as { kind : 'custom' ; name : string } ;
1043+ const c = type as { kind : 'custom' ; name : string ; module ?: string } ;
9711044 if ( ! / ^ [ A - Z a - z _ $ ] [ A - Z a - z 0 - 9 _ $ ] * $ / . test ( c . name ) ) {
972- return 'unknown' ;
1045+ return this . degradeType ( c ) ;
1046+ }
1047+ if (
1048+ ctx &&
1049+ ! this . isLocalTypeIdentity ( c , ctx ) &&
1050+ ! ( c . module === 'typing' && ctx . emittedNames . has ( c . name ) )
1051+ ) {
1052+ return this . degradeType ( c ) ;
9731053 }
9741054 return c . name ;
9751055 }
0 commit comments