diff --git a/unreal/Puerts/PuertsEditor/CodeAnalyze.ts b/unreal/Puerts/PuertsEditor/CodeAnalyze.ts index 3797ee6b28..59e16881ba 100644 --- a/unreal/Puerts/PuertsEditor/CodeAnalyze.ts +++ b/unreal/Puerts/PuertsEditor/CodeAnalyze.ts @@ -1012,7 +1012,20 @@ function watch(configFilePath:string) { flags |= Number(getDecoratorFlagsValue(symbol.valueDeclaration, "set_flags", FunctionFlags)); clearFlags = Number(getDecoratorFlagsValue(symbol.valueDeclaration, "clear_flags", FunctionFlags)); } - + + // static methods are only allowed on BlueprintFunctionLibrary derived classes + // (enforced above). Map the static modifier to FUNC_Static, otherwise the + // generated UFunction is an instance function: blueprint call nodes then + // require a Target pin, and the runtime only wires JS dispatch for + // FUNC_Static functions, so the TS implementation would never be invoked. + // Explicitly clear the flag for non-static methods so regeneration + // converges when the static modifier is added or removed. + if (ts.getCombinedModifierFlags(symbol.valueDeclaration) & ts.ModifierFlags.Static) { + flags |= FunctionFlags.FUNC_Static; + } else { + clearFlags |= FunctionFlags.FUNC_Static; + } + if (symbol.valueDeclaration.type && (ts.SyntaxKind.VoidKeyword === symbol.valueDeclaration.type.kind)) { // bp.AddFunction(symbol.getName(), true, undefined, undefined, flags, clearFlags); bp.AddFunctionWithMetaData(symbol.getName(), true, undefined, undefined, flags, clearFlags, uemeta.compileFunctionMetaData(symbol)); diff --git a/unreal/Puerts/Source/PuertsEditor/Private/PEBlueprintAsset.cpp b/unreal/Puerts/Source/PuertsEditor/Private/PEBlueprintAsset.cpp index 5f01fb6252..a1c5b65839 100644 --- a/unreal/Puerts/Source/PuertsEditor/Private/PEBlueprintAsset.cpp +++ b/unreal/Puerts/Source/PuertsEditor/Private/PEBlueprintAsset.cpp @@ -100,15 +100,32 @@ bool UPEBlueprintAsset::LoadOrCreate( HasConstructor = TypeScriptGeneratedClass->HasConstructor; } Package = Cast(Blueprint->GetOuter()); + NeedSave = false; if (Blueprint->ParentClass != ParentClass) { CanChangeCheckWithBoolRet(); Blueprint->ParentClass = ParentClass; NeedSave = true; } - else - { - NeedSave = false; + + // BlueprintType has to keep tracking the parent class. Only the create path below derives it, so an asset + // generated before its TypeScript class became a BlueprintFunctionLibrary derivative would stay + // BPTYPE_Normal forever. That desync is not cosmetic: UEdGraphSchema_K2::IsStaticFunctionGraph then falls + // back to probing FUNC_Static on the function entry node, so the implicit hidden __WorldContext parameter + // appears or disappears together with that flag, and the function library fallbacks in + // FastGenerateSkeletonClass stay disabled. The serialized generated class and the skeleton class (rebuilt + // on every load) end up disagreeing on the signature, which makes callers fail to compile with + // "Could not find a pin for the parameter __WorldContext". + if (ParentClass) + { + const EBlueprintType DesiredBlueprintType = + ParentClass->IsChildOf(UBlueprintFunctionLibrary::StaticClass()) ? BPTYPE_FunctionLibrary : BPTYPE_Normal; + if (Blueprint->BlueprintType != DesiredBlueprintType) + { + CanChangeCheckWithBoolRet(); + Blueprint->BlueprintType = DesiredBlueprintType; + NeedSave = true; + } } return true; } diff --git a/unreal/Puerts/Source/PuertsEditor/Private/PEBlueprintMetaData.cpp b/unreal/Puerts/Source/PuertsEditor/Private/PEBlueprintMetaData.cpp index 7a58bb68ac..73acc21c65 100644 --- a/unreal/Puerts/Source/PuertsEditor/Private/PEBlueprintMetaData.cpp +++ b/unreal/Puerts/Source/PuertsEditor/Private/PEBlueprintMetaData.cpp @@ -353,11 +353,21 @@ bool UPEClassMetaData::SyncClassToBlueprint(UClass* InClass, UBlueprint* InBluep InBlueprint->BlueprintDisplayName = newDisplayName; onChange = true; } - EBlueprintType newType = (InClass->ClassFlags & CLASS_Const) ? BPTYPE_Const : BPTYPE_Normal; - if (InBlueprint->BlueprintType != newType) - { - InBlueprint->BlueprintType = newType; - onChange = true; + // BPTYPE_FunctionLibrary is derived from the parent class by UPEBlueprintAsset::LoadOrCreate and cannot be + // expressed through @uclass flags, so this sync only owns the Const / Normal distinction and must not downgrade + // a function library blueprint. Downgrading it would make UEdGraphSchema_K2::IsStaticFunctionGraph fall back to + // probing FUNC_Static on the function entry node, so the implicit hidden __WorldContext parameter would follow + // that flag instead of the blueprint type while the function library fallbacks in FastGenerateSkeletonClass stay + // disabled - the serialized generated class and the skeleton class then disagree on the signature and callers + // fail to compile with "Could not find a pin for the parameter __WorldContext". + if (InBlueprint->BlueprintType != BPTYPE_FunctionLibrary) + { + const EBlueprintType newType = (InClass->ClassFlags & CLASS_Const) ? BPTYPE_Const : BPTYPE_Normal; + if (InBlueprint->BlueprintType != newType) + { + InBlueprint->BlueprintType = newType; + onChange = true; + } } FString newCategory = InClass->HasMetaData(TEXT("Category")) ? InClass->GetMetaData(TEXT("Category")) : FString{}; if (InBlueprint->BlueprintCategory != newCategory)