@@ -570,10 +570,6 @@ ${callPrelude}${guards} return getRuntimeBridge().call('${moduleId}', '${func.n
570570 ) ;
571571 const classTypeParamDecl = classGenericContext . declaration ;
572572 const cname = this . escapeIdentifier ( cls . name ) ;
573- const classSelfType = `${ cname } ${ classGenericContext . typeArguments } ` ;
574- const tsValueType = ( p : ( typeof cls . methods ) [ number ] [ 'parameters' ] [ number ] ) : string =>
575- this . typeToTsFromPython ( p . type , classGenericContext , 'value' ) ;
576-
577573 const wrapAlias = ( body : string ) : GeneratedCode => {
578574 const ts = `${ jsdoc } export type ${ cname } ${ classTypeParamDecl } = ${ body } \n` ;
579575 return this . wrap ( ts , ts , [ cls . name ] ) ;
@@ -659,14 +655,15 @@ ${callPrelude}${guards} return getRuntimeBridge().call('${moduleId}', '${func.n
659655 const methodDeclarations : string [ ] = [ ] ;
660656
661657 sortedMethods
662- . filter ( method => method . name !== '__init__' )
658+ . filter (
659+ method =>
660+ method . name !== '__init__' &&
661+ ( method . methodKind === 'class' || method . methodKind === 'static' )
662+ )
663663 . forEach ( method => {
664- // @classmethod and @staticmethod become `static` members invoked through
665- // the class (`Class.method`), not the instance handle. Instance methods
666- // (the default, or an absent methodKind) keep the existing emission so
667- // their output stays byte-identical.
668- const isStatic = method . methodKind === 'class' || method . methodKind === 'static' ;
669- const staticPrefix = isStatic ? 'static ' : '' ;
664+ // v0.9 wrappers expose only class/static methods. They route through
665+ // the ordinary module call path and never retain process-local state.
666+ const staticPrefix = 'static ' ;
670667 const fparams = method . parameters . filter ( p => p . name !== 'self' && p . name !== 'cls' ) ;
671668 const methodOwnGenericContext = this . buildGenericRenderContext (
672669 this . getTypeParameters ( method . typeParameters ) ,
@@ -784,13 +781,9 @@ ${callPrelude}${guards} return getRuntimeBridge().call('${moduleId}', '${func.n
784781 const guardLines = emitArgGuards ( callDescriptor ) ;
785782 const guards = guardLines . length > 0 ? `${ guardLines . join ( '\n' ) } \n` : '' ;
786783
787- const callExpr = isStatic
788- ? `getRuntimeBridge().call('${ moduleId } ', '${ cls . name } .${ method . name } ', __args${
789- needsKwargsParam ? ', __kwargs' : ''
790- } )`
791- : `getRuntimeBridge().callMethod(this.__handle, '${ method . name } ', __args${
792- needsKwargsParam ? ', __kwargs' : ''
793- } )`;
784+ const callExpr = `getRuntimeBridge().call('${ moduleId } ', '${ cls . name } .${ method . name } ', __args${
785+ needsKwargsParam ? ', __kwargs' : ''
786+ } )`;
794787 methodBodies . push ( `${ overloadDecl } ${ staticPrefix } async ${ mname } ${ methodTypeParamDecl } (${ paramsDecl } ): Promise<${ returnType } > {
795788${ callPrelude } ${ guards } return ${ callExpr } ;
796789 }` ) ;
@@ -799,167 +792,26 @@ ${callPrelude}${guards} return ${callExpr};
799792 ) ;
800793 } ) ;
801794
802- const init = cls . methods . find ( m => m . name === '__init__' ) ;
803- const ctorSpec = ( ( ) => {
804- if ( ! init ) {
805- return {
806- overloadDecl : '' ,
807- declaration : ` static create${ classTypeParamDecl } (...args: unknown[]): Promise<${ classSelfType } >;\n` ,
808- paramsDecl : `...args: unknown[]` ,
809- callPrelude : ` const __args: unknown[] = [...args];\n` ,
810- hasKwargs : false ,
811- guardLines : [ ] as string [ ] ,
812- } ;
813- }
814-
815- const fparams = init . parameters . filter ( p => p . name !== 'self' && p . name !== 'cls' ) ;
816- const keywordOnlyParams = fparams . filter ( p => p . keywordOnly ) ;
817- const positionalOnlyNames = fparams . filter ( p => p . positionalOnly ) . map ( p => p . name ) ;
818- const hasVarKwArgs = fparams . some ( p => p . kwArgs ) ;
819- const needsKwargsParam = keywordOnlyParams . length > 0 || hasVarKwArgs ;
820- const varArgsParam = fparams . find ( p => p . varArgs ) ;
821- const needsVarArgsArray = Boolean ( varArgsParam ) && needsKwargsParam ;
822- const positionalParams = fparams . filter ( p => ! p . keywordOnly && ! p . varArgs && ! p . kwArgs ) ;
823- const firstOptionalPosIndex = positionalParams . findIndex ( p => p . optional ) ;
824- const requiredPosCount =
825- firstOptionalPosIndex >= 0 ? firstOptionalPosIndex : positionalParams . length ;
826- const keywordOnlyNames = keywordOnlyParams . map ( p => p . name ) ;
827-
828- const renderPositionalParam = (
829- p : ( typeof positionalParams ) [ number ] ,
830- forceRequired = false
831- ) : string => {
832- const pname = this . escapeIdentifier ( p . name ) ;
833- const opt = ! forceRequired && p . optional ? '?' : '' ;
834- return `${ pname } ${ opt } : ${ tsValueType ( p ) } ` ;
835- } ;
836-
837- const kwargsType = ( ( ) => {
838- if ( ! needsKwargsParam ) {
839- return '' ;
840- }
841- if ( keywordOnlyParams . length === 0 && hasVarKwArgs ) {
842- return 'Record<string, unknown>' ;
843- }
844- const props = keywordOnlyParams
845- . map ( p => `${ JSON . stringify ( p . name ) } ${ p . optional ? '?' : '' } : ${ tsValueType ( p ) } ;` )
846- . join ( ' ' ) ;
847- const obj = `{ ${ props } }` ;
848- return hasVarKwArgs ? `(${ obj } & Record<string, unknown>)` : obj ;
849- } ) ( ) ;
850-
851- const paramsDeclParts : string [ ] = [ ] ;
852- positionalParams . forEach ( p => {
853- paramsDeclParts . push ( renderPositionalParam ( p ) ) ;
854- } ) ;
855- if ( varArgsParam ) {
856- const vname = this . escapeIdentifier ( varArgsParam . name ) ;
857- paramsDeclParts . push ( needsVarArgsArray ? `${ vname } ?: unknown[]` : `...${ vname } : unknown[]` ) ;
858- }
859- if ( needsKwargsParam ) {
860- paramsDeclParts . push ( `kwargs?: ${ kwargsType } ` ) ;
861- }
862- const paramsDecl = paramsDeclParts . join ( ', ' ) ;
863-
864- const requiredKwOnlyNames = keywordOnlyParams . filter ( p => ! p . optional ) . map ( p => p . name ) ;
865- const overloads : string [ ] = [ ] ;
866- if ( needsKwargsParam && requiredKwOnlyNames . length > 0 ) {
867- const firstOptionalIndex = positionalParams . findIndex ( p => p . optional ) ;
868- const requiredPosCount =
869- firstOptionalIndex >= 0 ? firstOptionalIndex : positionalParams . length ;
870- for ( let i = requiredPosCount ; i <= positionalParams . length ; i ++ ) {
871- const head = positionalParams . slice ( 0 , i ) . map ( p => renderPositionalParam ( p , true ) ) ;
872- const rest : string [ ] = [ ] ;
873- if ( varArgsParam ) {
874- const vname = this . escapeIdentifier ( varArgsParam . name ) ;
875- rest . push (
876- needsVarArgsArray ? `${ vname } : unknown[] | undefined` : `...${ vname } : unknown[]`
877- ) ;
878- }
879- rest . push ( `kwargs: ${ kwargsType } ` ) ;
880- overloads . push (
881- ` static create${ classTypeParamDecl } (${ [ ...head , ...rest ] . join ( ', ' ) } ): Promise<${ classSelfType } >;`
882- ) ;
883- if ( varArgsParam && needsVarArgsArray ) {
884- overloads . push (
885- ` static create${ classTypeParamDecl } (${ [ ...head , `kwargs: ${ kwargsType } ` ] . join ( ', ' ) } ): Promise<${ classSelfType } >;`
886- ) ;
887- }
888- }
889- }
890- const overloadDecl = overloads . length > 0 ? `${ overloads . join ( '\n' ) } \n` : '' ;
891- const declaration =
892- overloads . length > 0
893- ? overloadDecl
894- : ` static create${ classTypeParamDecl } (${ paramsDecl } ): Promise<${ classSelfType } >;\n` ;
895-
896- const callDescriptor : CallDescriptor = {
897- positionalParams,
898- varArgsParam,
899- needsVarArgsArray,
900- hasKwArgs : needsKwargsParam ,
901- hasVarKwArgs,
902- keywordOnlyNames,
903- requiredKwOnlyNames,
904- positionalOnlyNames,
905- requiredPosCount,
906- indent : ' ' ,
907- errorLabel : '__init__' ,
908- } ;
909- const callPreludeLines = emitCallPrelude ( callDescriptor , this . callEmitHelpers ( ) ) ;
910- const callPrelude = callPreludeLines . length > 0 ? `${ callPreludeLines . join ( '\n' ) } \n` : '' ;
911- const guardLines = emitArgGuards ( callDescriptor ) ;
912-
913- return {
914- overloadDecl,
915- declaration,
916- paramsDecl,
917- callPrelude,
918- hasKwargs : needsKwargsParam ,
919- guardLines,
920- } ;
921- } ) ( ) ;
922-
923- // @property / functools.cached_property → TS getters returning Promise<T>.
924- // Reading the attribute fires the getter on the Python side; the bridge
925- // resolves it via callMethod with no args. Sorted for stable output.
926- const accessorBodies : string [ ] = [ ] ;
927- const accessorDeclarations : string [ ] = [ ] ;
928- const sortedAccessors = [ ...( cls . accessors ?? [ ] ) ] . sort ( ( a , b ) => a . name . localeCompare ( b . name ) ) ;
929- sortedAccessors . forEach ( accessor => {
930- const aname = this . escapeIdentifier ( accessor . name ) ;
931- const accessorType = this . typeToTsFromPython ( accessor . type , classGenericContext , 'return' ) ;
932- const jsdocAcc = this . generateJsDoc ( accessor . docstring ) ;
933- accessorBodies . push (
934- `${ jsdocAcc } get ${ aname } (): Promise<${ accessorType } > { return getRuntimeBridge().callMethod(this.__handle, '${ accessor . name } ', []); }`
935- ) ;
936- accessorDeclarations . push ( `${ jsdocAcc } get ${ aname } (): Promise<${ accessorType } >;\n` ) ;
937- } ) ;
938- const accessorsImpl = accessorBodies . length > 0 ? `${ accessorBodies . join ( '\n' ) } \n` : '' ;
939- const accessorsDecl = accessorDeclarations . length > 0 ? `${ accessorDeclarations . join ( '' ) } ` : '' ;
940-
941795 const methodsSection = methodBodies . length > 0 ? `\n${ methodBodies . join ( '\n' ) } \n` : '\n' ;
942796 const declarationMethodsSection =
943797 methodDeclarations . length > 0 ? `\n${ methodDeclarations . join ( '' ) } \n` : '\n' ;
944- const ctorGuards = ctorSpec . guardLines . length > 0 ? `${ ctorSpec . guardLines . join ( '\n' ) } \n` : '' ;
945- const newClassExpr = `new ${ cname } ${ classGenericContext . typeArguments } (handle)` ;
798+ // The constructor counts: a class whose only member was __init__ loses
799+ // create(), so it needs the migration note as much as one with methods.
800+ const omittedInstanceMembers =
801+ cls . methods . some (
802+ method => method . methodKind !== 'class' && method . methodKind !== 'static'
803+ ) || ( cls . accessors ?. length ?? 0 ) > 0 ;
804+ const migrationNote =
805+ methodBodies . length === 0 && omittedInstanceMembers
806+ ? ' // NOTE: Instance members are not generated in v0.9; migrate this API to value-returning module functions.\n'
807+ : '' ;
946808 const ts = `${ jsdoc } export class ${ cname } ${ classTypeParamDecl } {
947- private readonly __handle: string;
948- private constructor(handle: string) { this.__handle = handle; }
949- ${ ctorSpec . overloadDecl } static async create${ classTypeParamDecl } (${ ctorSpec . paramsDecl } ): Promise<${ classSelfType } > {
950- ${ ctorSpec . callPrelude } ${ ctorGuards } const handle = await getRuntimeBridge().instantiate<string>('${ moduleId } ', '${ cls . name } ', __args${
951- ctorSpec . hasKwargs ? ', __kwargs' : ''
952- } );
953- return ${ newClassExpr } ;
954- }
955- static fromHandle${ classTypeParamDecl } (handle: string): ${ classSelfType } { return ${ newClassExpr } ; }${ methodsSection } ${ accessorsImpl } async disposeHandle(): Promise<void> { await getRuntimeBridge().disposeInstance(this.__handle); }
809+ ${ migrationNote } ${ methodsSection }
956810}
957811` ;
958812
959813 const declaration = `${ jsdoc } export class ${ cname } ${ classTypeParamDecl } {
960- private readonly __handle: string;
961- private constructor(handle: string);
962- ${ ctorSpec . declaration } static fromHandle${ classTypeParamDecl } (handle: string): ${ classSelfType } ;${ declarationMethodsSection } ${ accessorsDecl } disposeHandle(): Promise<void>;
814+ ${ migrationNote } ${ declarationMethodsSection }
963815}
964816` ;
965817
0 commit comments