AutoCreateRefTerm and DeterminesOutputType on the same parameter breaks blueprint compilation

Hi!

If you create a simple Blueprint utility function, for example:

UFUNCTION(BlueprintPure, Meta = (AutoCreateRefTerm = "ObjectClass", DeterminesOutputType = "ObjectClass"))
static const UObject* GetDefaultObject(const TSubclassOf<UObject>& ObjectClass);
{
	return ObjectClass.GetDefaultObject();
}

You can then try to use this function in a Blueprint, and at first everything looks ok - if you change the class referenced by ObjectClass, then the type of the output pin changes accordingly. For example, using the GetDefaultObject node above, you can drop it in a graph and set the ObjectClass to e.g. Actor, and then read the value of the Actor Hidden In Game variable from the CDO by dragging from the Return Value pin.

If you however compile this blueprint, you will get the following errors in the log:

Can't connect pins Target and Return Value : Actor Object Reference is not compatible with Object Reference.
Can't connect pins Return Value and Target : Object Reference is not compatible with Actor Object Reference.
Invalid pin connection from ' Return Value ' to ' Target '. You may have changed the type after the connections were made.

So during compilation, the type of the Return Value pin flipped back to UObject instead of AActor.

I debugged a bit into this, and the culprit seems to be in UK2Node_CallFunction::InnerHandleAutoCreateRef.

To implement the AutoCreateRefTerm, the CallFunction appears to spawn two additional nodes in its ExpandNode - a UK2Node_TemporaryVariable, and a UK2Node_PureAssignmentStatement. The temporary variable inherits the pin type and default object of the CallFunction node, in this case here the pin type is a Class of UObject base, and the default object is set to AActor. The variable is then piped through the PureAssignStatement into the CallFunction node’s ObjectClass input pin. At this step PinConnectionListChanged is called on the input pin, which in turn calls ResetPinToAutoGeneratedDefaultValue, which in turn calls PinDefaultValueChanged on the CallFunction node. It queries the PureAssignStatement for its output type, which is UObject class, and then flips the return value type from AActor to UObject.

I don’t know what the proper way to fix this would be. Maybe the PureAssignmenStatement needs to conform its output type to the input type?

As a workaround we’ve for now removed the AutoCreateRefTerm and instead take the ObjectClass by value.

Ciao, Daniel!

[Attachment Removed]

Hey Daniel, my apologies for the lack of response from our side! I’m glad you’ve worked around the problem so far by simply avoiding the combination of AutoCreatRefTerm and DeterminesOutputType.

We have an existing bug for this: UE-204408. However, we’ve backlogged it and since the long-term plan for UE6 is to ultimately phase out blueprints, we won’t spend time on it anymore for UE5 nor UE6.

I experimented a bit locally and do have a local patch for you that makes your shared function compile successfully, when ObjectClass is selected from dropdown (i.e. derived from the pin’s DefaultValue):

void FDynamicOutputHelper::ConformOutputType() const
{
	if (IsTypePickerPin(MutatingPin))
	{
		// BEGIN WORKAROUND: Early abort to not touch intermediate node output pins
		for (const UEdGraphPin* LinkedPin : MutatingPin->LinkedTo)
		{
			const UEdGraphNode* LinkedNode = LinkedPin ? LinkedPin->GetOwningNodeUnchecked() : nullptr;
			if (LinkedNode && LinkedNode->IsIntermediateNode())
			{
				return;
			}
		}
		// END WORKAROUND

It’s not a clean engine fix and I haven’t rigorously tested it, but sharing with you in case you want to consider a workaround like that. Since any problems would be detected editor-time and cook-time, the risk of applying that wouldn’t be too high but it’s completely up to you. Your current approach to things sounds wiser, I’d say.

[Attachment Removed]

Sounds good! Closing this.

[Attachment Removed]

Thanks for the suggestion!

We’re going to stick with our current solution for the same reason as the bug won’t be fixed - blueprints are likely disappearing soon™ so the current behavior is fine for as long as it will last.

Ciao, Daniel!

[Attachment Removed]