Files
UnrealEngineUWP/Engine/Source/Programs/UnrealHeaderTool/Private/HeaderParser.cpp
robert manuszewski e33b9b80b0 It's no longer required to provide an explicit setter or getter name if Setter or Getter tag are present in UPROPERTY macro. If no accessor function name is specified it will be autogenerated.
#rb Steve.Robb
#preflight 6216795ec152c67496dd3acf

#ROBOMERGE-AUTHOR: robert.manuszewski
#ROBOMERGE-SOURCE: CL 19170586 via CL 19170837 via CL 19170842 via CL 19170845 via CL 19170992
#ROBOMERGE-BOT: UE5 (Release-Engine-Staging -> Main) (v921-19075845)

[CL 19171072 by robert manuszewski in ue5-main branch]
2022-02-28 02:40:02 -05:00

9664 lines
302 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "HeaderParser.h"
#include "UnrealHeaderTool.h"
#include "PropertyTypes.h"
#include "HAL/FileManager.h"
#include "Misc/CommandLine.h"
#include "Misc/ConfigCacheIni.h"
#include "Misc/FeedbackContext.h"
#include "UObject/Interface.h"
#include "GeneratedCodeVersion.h"
#include "ProfilingDebugging/ScopedTimers.h"
#include "NativeClassExporter.h"
#include "EngineAPI.h"
#include "ClassMaps.h"
#include "StringUtils.h"
#include "Misc/DefaultValueHelper.h"
#include "Manifest.h"
#include "Math/UnitConversion.h"
#include "Exceptions.h"
#include "UnrealTypeDefinitionInfo.h"
#include "Containers/EnumAsByte.h"
#include "Algo/AllOf.h"
#include "Algo/Find.h"
#include "Algo/FindSortedStringCaseInsensitive.h"
#include "Misc/ScopeExit.h"
#include "Misc/Timespan.h"
#include "HAL/PlatformTime.h"
#include <atomic>
#include "Specifiers/CheckedMetadataSpecifiers.h"
#include "Specifiers/EnumSpecifiers.h"
#include "Specifiers/FunctionSpecifiers.h"
#include "Specifiers/InterfaceSpecifiers.h"
#include "Specifiers/StructSpecifiers.h"
#include "Specifiers/VariableSpecifiers.h"
// Globals for common class definitions
extern FUnrealClassDefinitionInfo* GUObjectDef;
extern FUnrealClassDefinitionInfo* GUClassDef;
extern FUnrealClassDefinitionInfo* GUInterfaceDef;
/*-----------------------------------------------------------------------------
Constants & declarations.
-----------------------------------------------------------------------------*/
enum {MAX_ARRAY_SIZE=2048};
namespace
{
static const FName NAME_Comment(TEXT("Comment"));
static const FName NAME_ToolTip(TEXT("ToolTip"));
static const FName NAME_DocumentationPolicy(TEXT("DocumentationPolicy"));
static const FName NAME_AllowPrivateAccess(TEXT("AllowPrivateAccess"));
static const FName NAME_ExposeOnSpawn(TEXT("ExposeOnSpawn"));
static const FName NAME_NativeConst(TEXT("NativeConst"));
static const FName NAME_NativeConstTemplateArg(TEXT("NativeConstTemplateArg"));
static const FName NAME_BlueprintInternalUseOnly(TEXT("BlueprintInternalUseOnly"));
static const FName NAME_DeprecatedFunction(TEXT("DeprecatedFunction"));
static const FName NAME_BlueprintSetter(TEXT("BlueprintSetter"));
static const FName NAME_BlueprintGetter(TEXT("BlueprintGetter"));
static const FName NAME_Category(TEXT("Category"));
static const FName NAME_ReturnValue(TEXT("ReturnValue"));
static const FName NAME_CppFromBpEvent(TEXT("CppFromBpEvent"));
static const FName NAME_CustomThunk(TEXT("CustomThunk"));
static const FName NAME_EditInline(TEXT("EditInline"));
static const FName NAME_IncludePath(TEXT("IncludePath"));
static const FName NAME_ModuleRelativePath(TEXT("ModuleRelativePath"));
static const FName NAME_IsBlueprintBase(TEXT("IsBlueprintBase"));
static const FName NAME_CannotImplementInterfaceInBlueprint(TEXT("CannotImplementInterfaceInBlueprint"));
static const FName NAME_UIMin(TEXT("UIMin"));
static const FName NAME_UIMax(TEXT("UIMax"));
}
const FName FHeaderParserNames::NAME_HideCategories(TEXT("HideCategories"));
const FName FHeaderParserNames::NAME_ShowCategories(TEXT("ShowCategories"));
const FName FHeaderParserNames::NAME_SparseClassDataTypes(TEXT("SparseClassDataTypes"));
const FName FHeaderParserNames::NAME_IsConversionRoot(TEXT("IsConversionRoot"));
const FName FHeaderParserNames::NAME_BlueprintType(TEXT("BlueprintType"));
const FName FHeaderParserNames::NAME_AutoCollapseCategories(TEXT("AutoCollapseCategories"));
const FName FHeaderParserNames::NAME_HideFunctions(TEXT("HideFunctions"));
const FName FHeaderParserNames::NAME_AutoExpandCategories(TEXT("AutoExpandCategories"));
const FName FHeaderParserNames::NAME_PrioritizeCategories(TEXT("PrioritizeCategories"));
TArray<FString> FHeaderParser::PropertyCPPTypesRequiringUIRanges = { TEXT("float"), TEXT("double") };
TArray<FString> FHeaderParser::ReservedTypeNames = { TEXT("none") };
// Busy wait support for holes in the include graph issues
extern std::atomic<bool> GSourcesConcurrent;
extern std::atomic<int> GSourcesToParse;
extern std::atomic<int> GSourcesParsing;
extern std::atomic<int> GSourcesCompleted;
extern std::atomic<int> GSourcesStalled;
/*-----------------------------------------------------------------------------
Utility functions.
-----------------------------------------------------------------------------*/
namespace
{
bool ProbablyAMacro(const FStringView& Identifier)
{
if (Identifier.Len() == 0)
{
return false;
}
// Macros must start with a capitalized alphanumeric character or underscore
TCHAR FirstChar = Identifier[0];
if (FirstChar != TEXT('_') && (FirstChar < TEXT('A') || FirstChar > TEXT('Z')))
{
return false;
}
// Test for known delegate and event macros.
if (Identifier.StartsWith(TEXT("DECLARE_MULTICAST_DELEGATE")) ||
Identifier.StartsWith(TEXT("DECLARE_DELEGATE")) ||
Identifier.StartsWith(TEXT("DECLARE_EVENT")))
{
return true;
}
// Failing that, we'll guess about it being a macro based on it being a fully-capitalized identifier.
for (TCHAR Ch : Identifier.RightChop(1))
{
if (Ch != TEXT('_') && (Ch < TEXT('A') || Ch > TEXT('Z')) && (Ch < TEXT('0') || Ch > TEXT('9')))
{
return false;
}
}
return true;
}
/**
* Tests if an identifier looks like a macro which doesn't have a following open parenthesis.
*
* @param HeaderParser The parser to retrieve the next token.
* @param Token The token to test for being callable-macro-like.
*
* @return true if it looks like a non-callable macro, false otherwise.
*/
bool ProbablyAnUnknownObjectLikeMacro(FHeaderParser& HeaderParser, FToken Token)
{
// Non-identifiers are not macros
if (!Token.IsIdentifier())
{
return false;
}
// Macros must start with a capitalized alphanumeric character or underscore
TCHAR FirstChar = Token.Value[0];
if (FirstChar != TEXT('_') && (FirstChar < TEXT('A') || FirstChar > TEXT('Z')))
{
return false;
}
// We'll guess about it being a macro based on it being fully-capitalized with at least one underscore.
int32 UnderscoreCount = 0;
for (TCHAR Ch : Token.Value.RightChop(1))
{
if (Ch == TEXT('_'))
{
++UnderscoreCount;
}
else if ((Ch < TEXT('A') || Ch > TEXT('Z')) && (Ch < TEXT('0') || Ch > TEXT('9')))
{
return false;
}
}
// We look for at least one underscore as a convenient way of allowing many known macros
// like FORCEINLINE and CONSTEXPR, and non-macros like FPOV and TCHAR.
if (UnderscoreCount == 0)
{
return false;
}
// Identifiers which end in _API are known
if (Token.Value.Len() > 4 && Token.Value.EndsWith(FStringView(TEXT("_API"), 4)))
{
return false;
}
// Ignore certain known macros or identifiers that look like macros.
if (Token.IsValue(TEXT("FORCEINLINE_DEBUGGABLE"), ESearchCase::CaseSensitive) ||
Token.IsValue(TEXT("FORCEINLINE_STATS"), ESearchCase::CaseSensitive) ||
Token.IsValue(TEXT("SIZE_T"), ESearchCase::CaseSensitive))
{
return false;
}
// Check if there's an open parenthesis following the token.
//
// Rather than ungetting the bracket token, we unget the original identifier token,
// then get it again, so we don't lose any comments which may exist between the token
// and the non-bracket.
FToken PossibleBracketToken;
HeaderParser.GetToken(PossibleBracketToken);
HeaderParser.UngetToken(Token);
HeaderParser.GetToken(Token);
return !PossibleBracketToken.IsSymbol(TEXT('('));
}
/**
* Parse and validate an array of identifiers (inside FUNC_NetRequest, FUNC_NetResponse)
* @param FuncInfo function info for the current function
* @param Identifiers identifiers inside the net service declaration
*/
void ParseNetServiceIdentifiers(FHeaderParser& HeaderParser, FFuncInfo& FuncInfo, const TArray<FString>& Identifiers)
{
static const auto& IdTag = TEXT("Id");
static const auto& ResponseIdTag = TEXT("ResponseId");
static const auto& JSBridgePriTag = TEXT("Priority");
for (const FString& Identifier : Identifiers)
{
const TCHAR* IdentifierPtr = *Identifier;
if (const TCHAR* Equals = FCString::Strchr(IdentifierPtr, TEXT('=')))
{
// It's a tag with an argument
if (FCString::Strnicmp(IdentifierPtr, IdTag, UE_ARRAY_COUNT(IdTag) - 1) == 0)
{
int32 TempInt = FCString::Atoi(Equals + 1);
if (TempInt <= 0 || TempInt > MAX_uint16)
{
HeaderParser.Throwf(TEXT("Invalid network identifier %s for function"), IdentifierPtr);
}
FuncInfo.RPCId = (uint16)TempInt;
}
else if (FCString::Strnicmp(IdentifierPtr, ResponseIdTag, UE_ARRAY_COUNT(ResponseIdTag) - 1) == 0 ||
FCString::Strnicmp(IdentifierPtr, JSBridgePriTag, UE_ARRAY_COUNT(JSBridgePriTag) - 1) == 0)
{
int32 TempInt = FCString::Atoi(Equals + 1);
if (TempInt <= 0 || TempInt > MAX_uint16)
{
HeaderParser.Throwf(TEXT("Invalid network identifier %s for function"), IdentifierPtr);
}
FuncInfo.RPCResponseId = (uint16)TempInt;
}
}
else
{
// Assume it's an endpoint name
if (FuncInfo.EndpointName.Len())
{
HeaderParser.Throwf(TEXT("Function should not specify multiple endpoints - '%s' found but already using '%s'"), *Identifier);
}
FuncInfo.EndpointName = Identifier;
}
}
}
/**
* Processes a set of UFUNCTION or UDELEGATE specifiers into an FFuncInfo struct.
*
* @param FuncInfo - The FFuncInfo object to populate.
* @param Specifiers - The specifiers to process.
*/
void ProcessFunctionSpecifiers(FHeaderParser& HeaderParser, FFuncInfo& FuncInfo, const TArray<FPropertySpecifier>& Specifiers, TMap<FName, FString>& MetaData)
{
bool bSpecifiedUnreliable = false;
bool bSawPropertyAccessor = false;
for (const FPropertySpecifier& Specifier : Specifiers)
{
switch ((EFunctionSpecifier)Algo::FindSortedStringCaseInsensitive(*Specifier.Key, GFunctionSpecifierStrings))
{
default:
{
HeaderParser.Throwf(TEXT("Unknown function specifier '%s'"), *Specifier.Key);
}
break;
case EFunctionSpecifier::BlueprintNativeEvent:
{
if (FuncInfo.FunctionFlags & FUNC_Net)
{
HeaderParser.LogError(TEXT("BlueprintNativeEvent functions cannot be replicated!") );
}
else if ( (FuncInfo.FunctionFlags & FUNC_BlueprintEvent) && !(FuncInfo.FunctionFlags & FUNC_Native) )
{
// already a BlueprintImplementableEvent
HeaderParser.LogError(TEXT("A function cannot be both BlueprintNativeEvent and BlueprintImplementableEvent!") );
}
else if (bSawPropertyAccessor)
{
HeaderParser.LogError(TEXT("A function cannot be both BlueprintNativeEvent and a Blueprint Property accessor!"));
}
else if ( (FuncInfo.FunctionFlags & FUNC_Private) )
{
HeaderParser.LogError(TEXT("A Private function cannot be a BlueprintNativeEvent!") );
}
FuncInfo.FunctionFlags |= FUNC_Event;
FuncInfo.FunctionFlags |= FUNC_BlueprintEvent;
}
break;
case EFunctionSpecifier::BlueprintImplementableEvent:
{
if (FuncInfo.FunctionFlags & FUNC_Net)
{
HeaderParser.LogError(TEXT("BlueprintImplementableEvent functions cannot be replicated!") );
}
else if ( (FuncInfo.FunctionFlags & FUNC_BlueprintEvent) && (FuncInfo.FunctionFlags & FUNC_Native) )
{
// already a BlueprintNativeEvent
HeaderParser.LogError(TEXT("A function cannot be both BlueprintNativeEvent and BlueprintImplementableEvent!") );
}
else if (bSawPropertyAccessor)
{
HeaderParser.LogError(TEXT("A function cannot be both BlueprintImplementableEvent and a Blueprint Property accessor!"));
}
else if ( (FuncInfo.FunctionFlags & FUNC_Private) )
{
HeaderParser.LogError(TEXT("A Private function cannot be a BlueprintImplementableEvent!") );
}
FuncInfo.FunctionFlags |= FUNC_Event;
FuncInfo.FunctionFlags |= FUNC_BlueprintEvent;
FuncInfo.FunctionFlags &= ~FUNC_Native;
}
break;
case EFunctionSpecifier::Exec:
{
FuncInfo.FunctionFlags |= FUNC_Exec;
if( FuncInfo.FunctionFlags & FUNC_Net )
{
HeaderParser.LogError(TEXT("Exec functions cannot be replicated!") );
}
}
break;
case EFunctionSpecifier::SealedEvent:
{
FuncInfo.bSealedEvent = true;
}
break;
case EFunctionSpecifier::Server:
{
if ((FuncInfo.FunctionFlags & FUNC_BlueprintEvent) != 0)
{
HeaderParser.Throwf(TEXT("BlueprintImplementableEvent or BlueprintNativeEvent functions cannot be declared as Client or Server"));
}
FuncInfo.FunctionFlags |= FUNC_Net;
FuncInfo.FunctionFlags |= FUNC_NetServer;
if (Specifier.Values.Num())
{
FuncInfo.CppImplName = Specifier.Values[0];
}
if( FuncInfo.FunctionFlags & FUNC_Exec )
{
HeaderParser.LogError(TEXT("Exec functions cannot be replicated!") );
}
}
break;
case EFunctionSpecifier::Client:
{
if ((FuncInfo.FunctionFlags & FUNC_BlueprintEvent) != 0)
{
HeaderParser.Throwf(TEXT("BlueprintImplementableEvent or BlueprintNativeEvent functions cannot be declared as Client or Server"));
}
FuncInfo.FunctionFlags |= FUNC_Net;
FuncInfo.FunctionFlags |= FUNC_NetClient;
if (Specifier.Values.Num())
{
FuncInfo.CppImplName = Specifier.Values[0];
}
}
break;
case EFunctionSpecifier::NetMulticast:
{
if ((FuncInfo.FunctionFlags & FUNC_BlueprintEvent) != 0)
{
HeaderParser.Throwf(TEXT("BlueprintImplementableEvent or BlueprintNativeEvent functions cannot be declared as Multicast"));
}
FuncInfo.FunctionFlags |= FUNC_Net;
FuncInfo.FunctionFlags |= FUNC_NetMulticast;
}
break;
case EFunctionSpecifier::ServiceRequest:
{
if ((FuncInfo.FunctionFlags & FUNC_BlueprintEvent) != 0)
{
HeaderParser.Throwf(TEXT("BlueprintImplementableEvent or BlueprintNativeEvent functions cannot be declared as a ServiceRequest"));
}
FuncInfo.FunctionFlags |= FUNC_Net;
FuncInfo.FunctionFlags |= FUNC_NetReliable;
FuncInfo.FunctionFlags |= FUNC_NetRequest;
FuncInfo.FunctionExportFlags |= FUNCEXPORT_CustomThunk;
ParseNetServiceIdentifiers(HeaderParser, FuncInfo, Specifier.Values);
if (FuncInfo.EndpointName.Len() == 0)
{
HeaderParser.Throwf(TEXT("ServiceRequest needs to specify an endpoint name"));
}
}
break;
case EFunctionSpecifier::ServiceResponse:
{
if ((FuncInfo.FunctionFlags & FUNC_BlueprintEvent) != 0)
{
HeaderParser.Throwf(TEXT("BlueprintImplementableEvent or BlueprintNativeEvent functions cannot be declared as a ServiceResponse"));
}
FuncInfo.FunctionFlags |= FUNC_Net;
FuncInfo.FunctionFlags |= FUNC_NetReliable;
FuncInfo.FunctionFlags |= FUNC_NetResponse;
ParseNetServiceIdentifiers(HeaderParser, FuncInfo, Specifier.Values);
if (FuncInfo.EndpointName.Len() == 0)
{
HeaderParser.Throwf(TEXT("ServiceResponse needs to specify an endpoint name"));
}
}
break;
case EFunctionSpecifier::Reliable:
{
FuncInfo.FunctionFlags |= FUNC_NetReliable;
}
break;
case EFunctionSpecifier::Unreliable:
{
bSpecifiedUnreliable = true;
}
break;
case EFunctionSpecifier::CustomThunk:
{
FuncInfo.FunctionExportFlags |= FUNCEXPORT_CustomThunk;
}
break;
case EFunctionSpecifier::BlueprintCallable:
{
FuncInfo.FunctionFlags |= FUNC_BlueprintCallable;
}
break;
case EFunctionSpecifier::BlueprintGetter:
{
if (FuncInfo.FunctionFlags & FUNC_Event)
{
HeaderParser.LogError(TEXT("Function cannot be a blueprint event and a blueprint getter."));
}
bSawPropertyAccessor = true;
FuncInfo.FunctionFlags |= FUNC_BlueprintCallable;
FuncInfo.FunctionFlags |= FUNC_BlueprintPure;
MetaData.Add(NAME_BlueprintGetter);
}
break;
case EFunctionSpecifier::BlueprintSetter:
{
if (FuncInfo.FunctionFlags & FUNC_Event)
{
HeaderParser.LogError(TEXT("Function cannot be a blueprint event and a blueprint setter."));
}
bSawPropertyAccessor = true;
FuncInfo.FunctionFlags |= FUNC_BlueprintCallable;
MetaData.Add(NAME_BlueprintSetter);
}
break;
case EFunctionSpecifier::BlueprintPure:
{
bool bIsPure = true;
if (Specifier.Values.Num() == 1)
{
FString IsPureStr = Specifier.Values[0];
bIsPure = IsPureStr.ToBool();
}
// This function can be called, and is also pure.
FuncInfo.FunctionFlags |= FUNC_BlueprintCallable;
if (bIsPure)
{
FuncInfo.FunctionFlags |= FUNC_BlueprintPure;
}
else
{
FuncInfo.bForceBlueprintImpure = true;
}
}
break;
case EFunctionSpecifier::BlueprintAuthorityOnly:
{
FuncInfo.FunctionFlags |= FUNC_BlueprintAuthorityOnly;
}
break;
case EFunctionSpecifier::BlueprintCosmetic:
{
FuncInfo.FunctionFlags |= FUNC_BlueprintCosmetic;
}
break;
case EFunctionSpecifier::WithValidation:
{
FuncInfo.FunctionFlags |= FUNC_NetValidate;
if (Specifier.Values.Num())
{
FuncInfo.CppValidationImplName = Specifier.Values[0];
}
}
break;
}
}
if (FuncInfo.FunctionFlags & FUNC_Net)
{
// Network replicated functions are always events
FuncInfo.FunctionFlags |= FUNC_Event;
check(!(FuncInfo.FunctionFlags & (FUNC_BlueprintEvent | FUNC_Exec)));
bool bIsNetService = !!(FuncInfo.FunctionFlags & (FUNC_NetRequest | FUNC_NetResponse));
bool bIsNetReliable = !!(FuncInfo.FunctionFlags & FUNC_NetReliable);
if (FuncInfo.FunctionFlags & FUNC_Static)
{
HeaderParser.LogError(TEXT("Static functions can't be replicated"));
}
if (!bIsNetReliable && !bSpecifiedUnreliable && !bIsNetService)
{
HeaderParser.LogError(TEXT("Replicated function: 'reliable' or 'unreliable' is required"));
}
if (bIsNetReliable && bSpecifiedUnreliable && !bIsNetService)
{
HeaderParser.LogError(TEXT("'reliable' and 'unreliable' are mutually exclusive"));
}
}
else if (FuncInfo.FunctionFlags & FUNC_NetReliable)
{
HeaderParser.LogError(TEXT("'reliable' specified without 'client' or 'server'"));
}
else if (bSpecifiedUnreliable)
{
HeaderParser.LogError(TEXT("'unreliable' specified without 'client' or 'server'"));
}
if (FuncInfo.bSealedEvent && !(FuncInfo.FunctionFlags & FUNC_Event))
{
HeaderParser.LogError(TEXT("SealedEvent may only be used on events"));
}
if (FuncInfo.bSealedEvent && FuncInfo.FunctionFlags & FUNC_BlueprintEvent)
{
HeaderParser.LogError(TEXT("SealedEvent cannot be used on Blueprint events"));
}
if (FuncInfo.bForceBlueprintImpure && (FuncInfo.FunctionFlags & FUNC_BlueprintPure) != 0)
{
HeaderParser.LogError(TEXT("BlueprintPure (or BlueprintPure=true) and BlueprintPure=false should not both appear on the same function, they are mutually exclusive"));
}
}
const TCHAR* GetHintText(FHeaderParser& HeaderParser, EVariableCategory VariableCategory)
{
switch (VariableCategory)
{
case EVariableCategory::ReplicatedParameter:
case EVariableCategory::RegularParameter:
return TEXT("Function parameter");
case EVariableCategory::Return:
return TEXT("Function return type");
case EVariableCategory::Member:
return TEXT("Member variable declaration");
default:
HeaderParser.Throwf(TEXT("Unknown variable category"));
}
// Unreachable
check(false);
return nullptr;
}
void SkipAlignasIfNecessary(FBaseParser& Parser)
{
if (Parser.MatchIdentifier(TEXT("alignas"), ESearchCase::CaseSensitive))
{
Parser.RequireSymbol(TEXT('('), TEXT("'alignas'"));
Parser.RequireAnyConstInt(TEXT("'alignas'"));
Parser.RequireSymbol(TEXT(')'), TEXT("'alignas'"));
}
}
void SkipDeprecatedMacroIfNecessary(FBaseParser& Parser)
{
FToken MacroToken;
if (!Parser.GetToken(MacroToken))
{
return;
}
if (!MacroToken.IsIdentifier() ||
(!MacroToken.IsValue(TEXT("DEPRECATED"), ESearchCase::CaseSensitive) && !MacroToken.IsValue(TEXT("UE_DEPRECATED"), ESearchCase::CaseSensitive)))
{
Parser.UngetToken(MacroToken);
return;
}
auto ErrorMessageGetter = [&MacroToken]() { return FString::Printf(TEXT("%s macro"), *MacroToken.GetTokenValue()); };
Parser.RequireSymbol(TEXT('('), ErrorMessageGetter);
FToken Token;
if (Parser.GetToken(Token) && !Token.IsConstFloat())
{
Parser.Throwf(TEXT("Expected engine version in %s macro"), *MacroToken.GetTokenValue());
}
Parser.RequireSymbol(TEXT(','), ErrorMessageGetter);
if (Parser.GetToken(Token) && !Token.IsConstString())
{
Parser.Throwf(TEXT("Expected deprecation message in %s macro"), *MacroToken.GetTokenValue());
}
Parser.RequireSymbol(TEXT(')'), ErrorMessageGetter);
}
void SkipAlignasAndDeprecatedMacroIfNecessary(FBaseParser& Parser)
{
// alignas() can come before or after the deprecation macro.
// We can't have both, but the compiler will catch that anyway.
SkipAlignasIfNecessary(Parser);
SkipDeprecatedMacroIfNecessary(Parser);
SkipAlignasIfNecessary(Parser);
}
inline EPointerMemberBehavior ToPointerMemberBehavior(const FString& InString)
{
if (InString.Compare(TEXT("Disallow")) == 0)
{
return EPointerMemberBehavior::Disallow;
}
if (InString.Compare(TEXT("AllowSilently")) == 0)
{
return EPointerMemberBehavior::AllowSilently;
}
if (InString.Compare(TEXT("AllowAndLog")) == 0)
{
return EPointerMemberBehavior::AllowAndLog;
}
FUHTMessage(FString(TEXT("Configuration"))).Throwf(TEXT("Unrecognized native pointer member behavior: %s"), *InString);
return EPointerMemberBehavior::Disallow;
}
static const TCHAR* GLayoutMacroNames[] = {
TEXT("LAYOUT_ARRAY"),
TEXT("LAYOUT_ARRAY_EDITORONLY"),
TEXT("LAYOUT_BITFIELD"),
TEXT("LAYOUT_BITFIELD_EDITORONLY"),
TEXT("LAYOUT_FIELD"),
TEXT("LAYOUT_FIELD_EDITORONLY"),
TEXT("LAYOUT_FIELD_INITIALIZED"),
};
FCriticalSection GlobalDelegatesLock;
TMap<FString, FUnrealFunctionDefinitionInfo*> GlobalDelegates;
template <typename LambdaType>
bool BusyWait(LambdaType&& Lambda)
{
constexpr double TimeoutInSeconds = 5.0;
// Try it first
if (Lambda())
{
return true;
}
// If we aren't doing concurrent processing, then there isn't any reason to continue, we will never make any progress.
if (!GSourcesConcurrent)
{
return false;
}
// Mark that we are stalled
ON_SCOPE_EXIT
{
--GSourcesStalled;
};
++GSourcesStalled;
int SourcesToParse = GSourcesToParse;
int SourcesParsing = GSourcesParsing;
int SourcesCompleted = GSourcesCompleted;
int SourcesStalled = GSourcesStalled;
double StartTime = FPlatformTime::Seconds();
for (;;)
{
FPlatformProcess::YieldCycles(10000);
if (Lambda())
{
return true;
}
int NewSourcesParsing = GSourcesParsing;
int NewSourcesCompleted = GSourcesCompleted;
int NewSourcesStalled = GSourcesStalled;
// If everything is stalled, then we have no hope
if (NewSourcesStalled + NewSourcesCompleted == SourcesToParse)
{
return false;
}
// If something has completed, then reset the timeout
double CurrentTime = FPlatformTime::Seconds();
if (NewSourcesCompleted != SourcesCompleted || NewSourcesParsing != SourcesParsing)
{
StartTime = CurrentTime;
}
// Otherwise, check for long timeout
else if (CurrentTime - StartTime > TimeoutInSeconds)
{
return false;
}
// Remember state
SourcesParsing = NewSourcesParsing;
SourcesCompleted = NewSourcesCompleted;
SourcesStalled = NewSourcesStalled;
}
}
}
void AddEditInlineMetaData(TMap<FName, FString>& MetaData)
{
MetaData.Add(NAME_EditInline, TEXT("true"));
}
FUHTConfig::FUHTConfig()
{
// Read Ini options, GConfig must exist by this point
check(GConfig);
const FName TypeRedirectsKey(TEXT("TypeRedirects"));
const FName StructsWithNoPrefixKey(TEXT("StructsWithNoPrefix"));
const FName StructsWithTPrefixKey(TEXT("StructsWithTPrefix"));
const FName DelegateParameterCountStringsKey(TEXT("DelegateParameterCountStrings"));
const FName GeneratedCodeVersionKey(TEXT("GeneratedCodeVersion"));
const FName EngineNativePointerMemberBehaviorKey(TEXT("EngineNativePointerMemberBehavior"));
const FName EngineObjectPtrMemberBehaviorKey(TEXT("EngineObjectPtrMemberBehavior"));
const FName NonEngineNativePointerMemberBehaviorKey(TEXT("NonEngineNativePointerMemberBehavior"));
const FName NonEngineObjectPtrMemberBehaviorKey(TEXT("NonEngineObjectPtrMemberBehavior"));
const FName AutomaticSettersAndGettersKey(TEXT("AutomaticSettersAndGetters"));
FConfigSection* ConfigSection = GConfig->GetSectionPrivate(TEXT("UnrealHeaderTool"), false, true, GEngineIni);
if (ConfigSection)
{
for (FConfigSection::TIterator It(*ConfigSection); It; ++It)
{
if (It.Key() == TypeRedirectsKey)
{
FString OldType;
FString NewType;
FParse::Value(*It.Value().GetValue(), TEXT("OldType="), OldType);
FParse::Value(*It.Value().GetValue(), TEXT("NewType="), NewType);
TypeRedirectMap.Add(MoveTemp(OldType), MoveTemp(NewType));
}
else if (It.Key() == StructsWithNoPrefixKey)
{
StructsWithNoPrefix.Add(It.Value().GetValue());
}
else if (It.Key() == StructsWithTPrefixKey)
{
StructsWithTPrefix.Add(It.Value().GetValue());
}
else if (It.Key() == DelegateParameterCountStringsKey)
{
DelegateParameterCountStrings.Add(It.Value().GetValue());
}
else if (It.Key() == GeneratedCodeVersionKey)
{
DefaultGeneratedCodeVersion = ToGeneratedCodeVersion(It.Value().GetValue());
}
else if (It.Key() == EngineNativePointerMemberBehaviorKey)
{
EngineNativePointerMemberBehavior = ToPointerMemberBehavior(It.Value().GetValue());
}
else if (It.Key() == EngineObjectPtrMemberBehaviorKey)
{
EngineObjectPtrMemberBehavior = ToPointerMemberBehavior(It.Value().GetValue());
}
else if (It.Key() == NonEngineNativePointerMemberBehaviorKey)
{
NonEngineNativePointerMemberBehavior = ToPointerMemberBehavior(It.Value().GetValue());
}
else if (It.Key() == NonEngineObjectPtrMemberBehaviorKey)
{
NonEngineObjectPtrMemberBehavior = ToPointerMemberBehavior(It.Value().GetValue());
}
else if (It.Key() == AutomaticSettersAndGettersKey)
{
bAllowAutomaticSettersAndGetters = It.Value().GetValue().ToBool();
}
}
}
}
const FUHTConfig& FUHTConfig::Get()
{
static FUHTConfig UHTConfig;
return UHTConfig;
}
/////////////////////////////////////////////////////
// FHeaderParser
/*-----------------------------------------------------------------------------
Code emitting.
-----------------------------------------------------------------------------*/
//
// Get a qualified class.
//
FUnrealClassDefinitionInfo* FHeaderParser::GetQualifiedClass(const TCHAR* Thing)
{
FToken Token;
if (GetIdentifier(Token))
{
RedirectTypeIdentifier(Token);
return FUnrealClassDefinitionInfo::FindScriptClassOrThrow(*this, FString(Token.Value));
}
else
{
Throwf(TEXT("%s: Missing class name"), Thing);
}
}
/*-----------------------------------------------------------------------------
Fields.
-----------------------------------------------------------------------------*/
/**
* Find a function in the specified context. Starts with the specified scope, then iterates
* through the Outer chain until the field is found.
*
* @param InScope scope to start searching for the field in
* @param InIdentifier name of the field we're searching for
* @param bIncludeParents whether to allow searching in the scope of a parent struct
* @param Thing hint text that will be used in the error message if an error is encountered
*
* @return a pointer to a UField with a name matching InIdentifier, or NULL if it wasn't found
*/
FUnrealFunctionDefinitionInfo* FHeaderParser::FindFunction
(
const FUnrealStructDefinitionInfo& InScope,
const TCHAR* InIdentifier,
bool bIncludeParents,
const TCHAR* Thing
)
{
check(InIdentifier);
FName InName(InIdentifier, FNAME_Find);
if (InName != NAME_None)
{
for (const FUnrealStructDefinitionInfo* Scope = &InScope; Scope; Scope = UHTCast<FUnrealStructDefinitionInfo>(Scope->GetOuter()))
{
for (FUnrealPropertyDefinitionInfo* PropertyDef : TUHTFieldRange<FUnrealPropertyDefinitionInfo>(*Scope))
{
if (PropertyDef->GetFName() == InName)
{
if (Thing)
{
InScope.Throwf(TEXT("%s: expecting function or delegate, got a property"), Thing);
}
return nullptr;
}
}
for (FUnrealFunctionDefinitionInfo* FunctionDef : TUHTFieldRange<FUnrealFunctionDefinitionInfo>(*Scope))
{
if (FunctionDef->GetFName() == InName)
{
return FunctionDef;
}
}
if (!bIncludeParents)
{
break;
}
}
}
return nullptr;
}
FUnrealPropertyDefinitionInfo* FHeaderParser::FindProperty(const FUnrealStructDefinitionInfo& InScope, const TCHAR* InIdentifier, bool bIncludeParents, const TCHAR* Thing)
{
check(InIdentifier);
FName InName(InIdentifier, FNAME_Find);
if (InName != NAME_None)
{
for (const FUnrealStructDefinitionInfo* Scope = &InScope; Scope; Scope = UHTCast<FUnrealStructDefinitionInfo>(Scope->GetOuter()))
{
for (FUnrealFunctionDefinitionInfo* FunctionDef : TUHTFieldRange<FUnrealFunctionDefinitionInfo>(*Scope))
{
if (FunctionDef->GetFName() == InName)
{
if (Thing)
{
InScope.Throwf(TEXT("%s: expecting a property, got a function or delegate"), Thing);
}
return nullptr;
}
}
for (FUnrealPropertyDefinitionInfo* PropertyDef : TUHTFieldRange<FUnrealPropertyDefinitionInfo>(*Scope))
{
if (PropertyDef->GetFName() == InName)
{
return PropertyDef;
}
}
if (!bIncludeParents)
{
break;
}
}
}
return nullptr;
}
/**
* Adds source file's include path to given metadata.
*
* @param Type Type for which to add include path.
* @param MetaData Meta data to fill the information.
*/
void AddIncludePathToMetadata(FUnrealFieldDefinitionInfo& FieldDef, TMap<FName, FString> &MetaData)
{
// Add metadata for the include path.
MetaData.Add(NAME_IncludePath, FieldDef.GetUnrealSourceFile().GetIncludePath());
}
/**
* Adds module's relative path from given file.
*
* @param SourceFile Given source file.
* @param MetaData Meta data to fill the information.
*/
void AddModuleRelativePathToMetadata(FUnrealSourceFile& SourceFile, TMap<FName, FString> &MetaData)
{
MetaData.Add(NAME_ModuleRelativePath, SourceFile.GetModuleRelativePath());
}
/**
* Adds module's relative path to given metadata.
*
* @param Type Type for which to add module's relative path.
* @param MetaData Meta data to fill the information.
*/
void AddModuleRelativePathToMetadata(FUnrealTypeDefinitionInfo& Type, TMap<FName, FString> &MetaData)
{
// Don't add module relative paths to functions.
if (Type.AsFunction() == nullptr && Type.HasSource())
{
MetaData.Add(NAME_ModuleRelativePath, Type.GetUnrealSourceFile().GetModuleRelativePath());
}
}
/*-----------------------------------------------------------------------------
Variables.
-----------------------------------------------------------------------------*/
//
// Compile an enumeration definition.
//
FUnrealEnumDefinitionInfo& FHeaderParser::CompileEnum()
{
TSharedPtr<FFileScope> Scope = SourceFile.GetScope();
CheckAllow( TEXT("'Enum'"), ENestAllowFlags::TypeDecl );
// Get the enum specifier list
FToken EnumToken;
FMetaData EnumMetaData;
TArray<FPropertySpecifier> SpecifiersFound;
ReadSpecifierSetInsideMacro(SpecifiersFound, TEXT("Enum"), EnumMetaData);
// Check enum type. This can be global 'enum', 'namespace' or 'enum class' enums.
bool bReadEnumName = false;
UEnum::ECppForm CppForm = UEnum::ECppForm::Regular;
EEnumFlags Flags = EEnumFlags::None;
if (!GetIdentifier(EnumToken))
{
Throwf(TEXT("Missing identifier after UENUM()") );
}
if (EnumToken.IsValue(TEXT("namespace"), ESearchCase::CaseSensitive))
{
CppForm = UEnum::ECppForm::Namespaced;
SkipDeprecatedMacroIfNecessary(*this);
bReadEnumName = GetIdentifier(EnumToken);
}
else if (EnumToken.IsValue(TEXT("enum"), ESearchCase::CaseSensitive))
{
if (!GetIdentifier(EnumToken))
{
Throwf(TEXT("Missing identifier after enum") );
}
if (EnumToken.IsValue(TEXT("class"), ESearchCase::CaseSensitive) || EnumToken.IsValue(TEXT("struct"), ESearchCase::CaseSensitive))
{
CppForm = UEnum::ECppForm::EnumClass;
}
else
{
// Put whatever token we found back so that we can correctly skip below
UngetToken(EnumToken);
CppForm = UEnum::ECppForm::Regular;
}
SkipAlignasAndDeprecatedMacroIfNecessary(*this);
bReadEnumName = GetIdentifier(EnumToken);
}
else
{
Throwf(TEXT("UENUM() should be followed by \'enum\' or \'namespace\' keywords.") );
}
// Get enumeration name.
if (!bReadEnumName)
{
Throwf(TEXT("Missing enumeration name") );
}
FTokenValue EnumIdentifier = EnumToken.GetTokenValue();
ParseFieldMetaData(EnumMetaData, *EnumIdentifier);
// Create enum definition.
FUnrealEnumDefinitionInfo& EnumDef = GTypeDefinitionInfoMap.FindByNameChecked<FUnrealEnumDefinitionInfo>(*EnumIdentifier);
Scope->AddType(EnumDef);
if (CppForm != EnumDef.GetCppForm())
{
Throwf(TEXT("ICE: Mismatch of enum CPP form between pre-parser and parser"));
}
for (const FPropertySpecifier& Specifier : SpecifiersFound)
{
switch ((EEnumSpecifier)Algo::FindSortedStringCaseInsensitive(*Specifier.Key, GEnumSpecifierStrings))
{
default:
Throwf(TEXT("Unknown enum specifier '%s'"), *Specifier.Key);
case EEnumSpecifier::Flags:
Flags |= EEnumFlags::Flags;
break;
}
}
if ((GetCurrentCompilerDirective() & ECompilerDirective::WithEditorOnlyData) != 0)
{
EnumDef.MakeEditorOnly();
}
// Validate the metadata for the enum
EnumDef.ValidateMetaDataFormat(EnumMetaData);
// Read base for enum class
EUnderlyingEnumType UnderlyingType = EUnderlyingEnumType::uint8;
if (CppForm == UEnum::ECppForm::EnumClass)
{
UnderlyingType = ParseUnderlyingEnumType();
if (UnderlyingType != EnumDef.GetUnderlyingType())
{
Throwf(TEXT("ICE: Mismatch of underlying enum type between pre-parser and parser"));
}
}
else
{
if (EnumHasAnyFlags(Flags, EEnumFlags::Flags))
{
Throwf(TEXT("The 'Flags' specifier can only be used on enum classes"));
}
}
if (UnderlyingType != EUnderlyingEnumType::uint8 && EnumMetaData.Contains(FHeaderParserNames::NAME_BlueprintType))
{
Throwf(TEXT("Invalid BlueprintType enum base - currently only uint8 supported"));
}
EnumDef.GetDefinitionRange().Start = &Input[InputPos];
// Get opening brace.
RequireSymbol( TEXT('{'), TEXT("'Enum'") );
switch (CppForm)
{
case UEnum::ECppForm::Namespaced:
{
// Now handle the inner true enum portion
RequireIdentifier(TEXT("enum"), ESearchCase::CaseSensitive, TEXT("'Enum'"));
SkipAlignasAndDeprecatedMacroIfNecessary(*this);
FToken InnerEnumToken;
if (!GetIdentifier(InnerEnumToken))
{
Throwf(TEXT("Missing enumeration name") );
}
EnumDef.SetCppType(FString::Printf(TEXT("%s::%s"), *EnumIdentifier, *InnerEnumToken.GetTokenValue()));
RequireSymbol( TEXT('{'), TEXT("'Enum'") );
}
break;
case UEnum::ECppForm::Regular:
case UEnum::ECppForm::EnumClass:
{
EnumDef.SetCppType(*EnumIdentifier);
}
break;
}
// List of all metadata generated for this enum
TMap<FName,FString> EnumValueMetaData = EnumMetaData;
AddModuleRelativePathToMetadata(EnumDef, EnumValueMetaData);
AddFormattedPrevCommentAsTooltipMetaData(EnumValueMetaData);
// Parse all enums tags.
FToken TagToken;
FMetaData TagMetaData;
TArray<TMap<FName, FString>> EntryMetaData;
bool bHasUnparsedValue = false;
TArray<TPair<FName, int64>> EnumNames;
int64 CurrentEnumValue = 0;
while (GetIdentifier(TagToken))
{
FTokenValue TagIdentifier = TagToken.GetTokenValue();
AddFormattedPrevCommentAsTooltipMetaData(TagMetaData);
// Try to read an optional explicit enum value specification
if (MatchSymbol(TEXT('=')))
{
FToken InitToken;
if (!GetToken(InitToken))
{
Throwf(TEXT("UENUM: missing enumerator initializer"));
}
int64 NewEnumValue = -1;
if (!InitToken.GetConstInt64(NewEnumValue))
{
// We didn't parse a literal, so set an invalid value
NewEnumValue = -1;
bHasUnparsedValue = true;
}
// Skip tokens until we encounter a comma, a closing brace or a UMETA declaration
for (;;)
{
if (!GetToken(InitToken))
{
Throwf(TEXT("Enumerator: end of file encountered while parsing the initializer"));
}
if (InitToken.IsSymbol(TEXT(',')) || InitToken.IsSymbol(TEXT('}')) || InitToken.IsIdentifier(TEXT("UMETA"), ESearchCase::IgnoreCase))
{
UngetToken(InitToken);
break;
}
// There are tokens after the initializer so it's not a standalone literal,
// so set it to an invalid value.
NewEnumValue = -1;
bHasUnparsedValue = true;
}
CurrentEnumValue = NewEnumValue;
}
FName NewTag;
switch (CppForm)
{
case UEnum::ECppForm::Namespaced:
case UEnum::ECppForm::EnumClass:
{
NewTag = FName(*FString::Printf(TEXT("%s::%s"), *EnumIdentifier, *TagIdentifier), FNAME_Add);
}
break;
case UEnum::ECppForm::Regular:
{
NewTag = FName(TagToken.Value, FNAME_Add);
}
break;
}
// Save the new tag
EnumNames.Emplace(NewTag, CurrentEnumValue);
// Autoincrement the current enumeration value
if (CurrentEnumValue != -1)
{
++CurrentEnumValue;
}
TagMetaData.Add(NAME_Name, NewTag.ToString());
EntryMetaData.Add(TagMetaData);
// check for metadata on this enum value
ParseFieldMetaData(TagMetaData, *TagIdentifier);
if (TagMetaData.Num() > 0)
{
// special case for enum value metadata - we need to prepend the key name with the enum value name
const FString TokenString = *TagIdentifier;
for (const auto& MetaData : TagMetaData)
{
FString KeyString = TokenString + TEXT(".") + MetaData.Key.ToString();
EnumValueMetaData.Emplace(*KeyString, MetaData.Value);
}
// now clear the metadata because we're going to reuse this token for parsing the next enum value
TagMetaData.Empty();
}
if (!MatchSymbol(TEXT(',')))
{
FToken ClosingBrace;
if (!GetToken(ClosingBrace))
{
Throwf(TEXT("UENUM: end of file encountered"));
}
if (ClosingBrace.IsSymbol(TEXT('}')))
{
UngetToken(ClosingBrace);
break;
}
}
}
// Trailing brace and semicolon for the enum
RequireSymbol( TEXT('}'), TEXT("'Enum'") );
MatchSemi();
if (CppForm == UEnum::ECppForm::Namespaced)
{
// Trailing brace for the namespace.
RequireSymbol( TEXT('}'), TEXT("'Enum'") );
}
EnumDef.GetDefinitionRange().End = &Input[InputPos];
// Register the list of enum names.
if (!EnumDef.SetEnums(EnumNames, CppForm, Flags))
{
//@TODO - Two issues with this, first the SetEnums always returns true. That was the case even with the original code.
// Second issue is that the code to generate the max string doesn't work in many cases (i.e. EPixelFormat)
const FName MaxEnumItem = *(EnumDef.GenerateEnumPrefix() + TEXT("_MAX"));
const int32 MaxEnumItemIndex = EnumDef.GetIndexByName(MaxEnumItem);
if (MaxEnumItemIndex != INDEX_NONE)
{
Throwf(TEXT("Illegal enumeration tag specified. Conflicts with auto-generated tag '%s'"), *MaxEnumItem.ToString());
}
Throwf(TEXT("Unable to generate enum MAX entry '%s' due to name collision"), *MaxEnumItem.ToString());
}
CheckDocumentationPolicyForEnum(EnumDef, EnumValueMetaData, EntryMetaData);
if (!EnumDef.IsValidEnumValue(0) && EnumMetaData.Contains(FHeaderParserNames::NAME_BlueprintType) && !bHasUnparsedValue)
{
EnumDef.LogWarning(TEXT("'%s' does not have a 0 entry! (This is a problem when the enum is initalized by default)"), *EnumDef.GetName());
}
// Add the metadata gathered for the enum to the package
if (EnumValueMetaData.Num() > 0)
{
EnumDef.AddMetaData(MoveTemp(EnumValueMetaData));
}
return EnumDef;
}
/**
* Checks if a string is made up of all the same character.
*
* @param Str The string to check for all
* @param Ch The character to check for
*
* @return True if the string is made up only of Ch characters.
*/
bool IsAllSameChar(const TCHAR* Str, TCHAR Ch)
{
check(Str);
while (TCHAR StrCh = *Str++)
{
if (StrCh != Ch)
{
return false;
}
}
return true;
}
/**
* @param Input An input string, expected to be a script comment.
* @return The input string, reformatted in such a way as to be appropriate for use as a tooltip.
*/
FString FHeaderParser::FormatCommentForToolTip(const FString& Input)
{
// Return an empty string if there are no alpha-numeric characters or a Unicode characters above 0xFF
// (which would be the case for pure CJK comments) in the input string.
bool bFoundAlphaNumericChar = false;
for ( int32 i = 0 ; i < Input.Len() ; ++i )
{
if ( FChar::IsAlnum(Input[i]) || (Input[i] > 0xFF) )
{
bFoundAlphaNumericChar = true;
break;
}
}
if ( !bFoundAlphaNumericChar )
{
return FString();
}
FString Result(Input);
// Sweep out comments marked to be ignored.
{
int32 CommentStart, CommentEnd;
// Block comments go first
for (CommentStart = Result.Find(TEXT("/*~"), ESearchCase::CaseSensitive); CommentStart != INDEX_NONE; CommentStart = Result.Find(TEXT("/*~"), ESearchCase::CaseSensitive))
{
CommentEnd = Result.Find(TEXT("*/"), ESearchCase::CaseSensitive, ESearchDir::FromStart, CommentStart);
if (CommentEnd != INDEX_NONE)
{
Result.RemoveAt(CommentStart, (CommentEnd + 2) - CommentStart, false);
}
else
{
// This looks like an error - an unclosed block comment.
break;
}
}
// Leftover line comments go next
for (CommentStart = Result.Find(TEXT("//~"), ESearchCase::CaseSensitive); CommentStart != INDEX_NONE; CommentStart = Result.Find(TEXT("//~"), ESearchCase::CaseSensitive))
{
CommentEnd = Result.Find(TEXT("\n"), ESearchCase::CaseSensitive, ESearchDir::FromStart, CommentStart);
if (CommentEnd != INDEX_NONE)
{
Result.RemoveAt(CommentStart, (CommentEnd + 1) - CommentStart, false);
}
else
{
Result.RemoveAt(CommentStart, Result.Len() - CommentStart, false);
break;
}
}
// Finish by shrinking if anything was removed, since we deferred this during the search.
Result.Shrink();
}
// Check for known commenting styles.
const bool bJavaDocStyle = Result.Contains(TEXT("/**"), ESearchCase::CaseSensitive);
const bool bCStyle = Result.Contains(TEXT("/*"), ESearchCase::CaseSensitive);
const bool bCPPStyle = Result.StartsWith(TEXT("//"), ESearchCase::CaseSensitive);
if ( bJavaDocStyle || bCStyle)
{
// Remove beginning and end markers.
if (bJavaDocStyle)
{
Result.ReplaceInline(TEXT("/**"), TEXT(""), ESearchCase::CaseSensitive);
}
if (bCStyle)
{
Result.ReplaceInline(TEXT("/*"), TEXT(""), ESearchCase::CaseSensitive);
}
Result.ReplaceInline(TEXT("*/"), TEXT(""), ESearchCase::CaseSensitive);
}
if ( bCPPStyle )
{
// Remove c++-style comment markers. Also handle javadoc-style comments
Result.ReplaceInline(TEXT("///"), TEXT(""), ESearchCase::CaseSensitive);
Result.ReplaceInline(TEXT("//"), TEXT(""), ESearchCase::CaseSensitive);
// Parser strips cpptext and replaces it with "// (cpptext)" -- prevent
// this from being treated as a comment on variables declared below the
// cpptext section
Result.ReplaceInline(TEXT("(cpptext)"), TEXT(""));
}
// Get rid of carriage return or tab characters, which mess up tooltips.
Result.ReplaceInline(TEXT( "\r" ), TEXT( "" ), ESearchCase::CaseSensitive);
//wx widgets has a hard coded tab size of 8
{
const int32 SpacesPerTab = 8;
Result.ConvertTabsToSpacesInline(SpacesPerTab);
}
// get rid of uniform leading whitespace and all trailing whitespace, on each line
TArray<FString> Lines;
Result.ParseIntoArray(Lines, TEXT("\n"), false);
for (FString& Line : Lines)
{
// Remove trailing whitespace
Line.TrimEndInline();
// Remove leading "*" and "* " in javadoc comments.
if (bJavaDocStyle)
{
// Find first non-whitespace character
int32 Pos = 0;
while (Pos < Line.Len() && FChar::IsWhitespace(Line[Pos]))
{
++Pos;
}
// Is it a *?
if (Pos < Line.Len() && Line[Pos] == '*')
{
// Eat next space as well
if (Pos+1 < Line.Len() && FChar::IsWhitespace(Line[Pos+1]))
{
++Pos;
}
Line.RightChopInline(Pos + 1, false);
}
}
}
auto IsWhitespaceOrLineSeparator = [](const FString& Line)
{
int32 LineLength = Line.Len();
int32 WhitespaceCount = 0;
while (WhitespaceCount < LineLength && FChar::IsWhitespace(Line[WhitespaceCount]))
{
++WhitespaceCount;
}
if (WhitespaceCount == LineLength)
{
return true;
}
const TCHAR* Str = (*Line) + WhitespaceCount;
return IsAllSameChar(Str, TEXT('-')) || IsAllSameChar(Str, TEXT('=')) || IsAllSameChar(Str, TEXT('*'));
};
// Find first meaningful line
int32 FirstIndex = 0;
for (const FString& Line : Lines)
{
if (!IsWhitespaceOrLineSeparator(Line))
{
break;
}
++FirstIndex;
}
int32 LastIndex = Lines.Num();
while (LastIndex != FirstIndex)
{
const FString& Line = Lines[LastIndex - 1];
if (!IsWhitespaceOrLineSeparator(Line))
{
break;
}
--LastIndex;
}
Result.Reset();
if (FirstIndex != LastIndex)
{
FString& FirstLine = Lines[FirstIndex];
// Figure out how much whitespace is on the first line
int32 MaxNumWhitespaceToRemove;
for (MaxNumWhitespaceToRemove = 0; MaxNumWhitespaceToRemove < FirstLine.Len(); MaxNumWhitespaceToRemove++)
{
if (!FChar::IsLinebreak(FirstLine[MaxNumWhitespaceToRemove]) && !FChar::IsWhitespace(FirstLine[MaxNumWhitespaceToRemove]))
{
break;
}
}
for (int32 Index = FirstIndex; Index != LastIndex; ++Index)
{
FString& Line = Lines[Index];
int32 TemporaryMaxWhitespace = MaxNumWhitespaceToRemove;
// Allow eating an extra tab on subsequent lines if it's present
if ((Index > FirstIndex) && (Line.Len() > 0) && (Line[0] == '\t'))
{
TemporaryMaxWhitespace++;
}
// Advance past whitespace
int32 Pos = 0;
while (Pos < TemporaryMaxWhitespace && Pos < Line.Len() && FChar::IsWhitespace(Line[Pos]))
{
++Pos;
}
if (Pos > 0)
{
Line.RightChopInline(Pos, false);
}
if (Index > FirstIndex)
{
Result += TEXT("\n");
}
if (Line.Len() && !IsAllSameChar(*Line, TEXT('=')))
{
Result += Line;
}
}
}
//@TODO: UCREMOVAL: Really want to trim an arbitrary number of newlines above and below, but keep multiple newlines internally
// Make sure it doesn't start with a newline
if (!Result.IsEmpty() && FChar::IsLinebreak(Result[0]))
{
Result.RightChopInline(1, false);
}
// Make sure it doesn't end with a dead newline
if (!Result.IsEmpty() && FChar::IsLinebreak(Result[Result.Len() - 1]))
{
Result.LeftInline(Result.Len() - 1, false);
}
// Done.
return Result;
}
TMap<FName, FString> FHeaderParser::GetParameterToolTipsFromFunctionComment(const FString& Input)
{
SCOPE_SECONDS_COUNTER_UHT(DocumentationPolicy);
TMap<FName, FString> Map;
if (Input.IsEmpty())
{
return Map;
}
TArray<FString> Params;
static const auto& ParamTag = TEXT("@param");
static const auto& ReturnTag = TEXT("@return");
static const auto& ReturnParamPrefix = TEXT("ReturnValue ");
/**
* Search for @param / @return followed by a section until a line break.
* For example: "@param Test MyTest Variable" becomes "Test", "MyTest Variable"
* These pairs are then later split and stored as the parameter tooltips.
* Once we don't find either @param or @return we break from the loop.
*/
int32 Offset = 0;
while (Offset < Input.Len())
{
const TCHAR* ParamPrefix = TEXT("");
int32 ParamStart = Input.Find(ParamTag, ESearchCase::CaseSensitive, ESearchDir::FromStart, Offset);
if(ParamStart != INDEX_NONE)
{
ParamStart = ParamStart + UE_ARRAY_COUNT(ParamTag);
Offset = ParamStart;
}
else
{
ParamStart = Input.Find(ReturnTag, ESearchCase::CaseSensitive, ESearchDir::FromStart, Offset);
if (ParamStart != INDEX_NONE)
{
ParamStart = ParamStart + UE_ARRAY_COUNT(ReturnTag);
Offset = ParamStart;
ParamPrefix = ReturnParamPrefix;
}
else
{
// no @param, no @return?
break;
}
}
int32 ParamEnd = Input.Find(TEXT("\n"), ESearchCase::CaseSensitive, ESearchDir::FromStart, ParamStart);
if (ParamEnd == INDEX_NONE)
{
ParamEnd = Input.Len();
}
Offset = ParamEnd;
Params.Add(ParamPrefix + Input.Mid(ParamStart, ParamEnd - ParamStart - 1));
}
for (FString& Param : Params)
{
Param.ConvertTabsToSpacesInline(4);
Param.TrimStartAndEndInline();
int32 FirstSpaceIndex = -1;
if (!Param.FindChar(TEXT(' '), FirstSpaceIndex))
{
continue;
}
FString ParamToolTip = Param.Mid(FirstSpaceIndex + 1);
ParamToolTip.TrimStartInline();
Param.LeftInline(FirstSpaceIndex);
Map.Add(*Param, MoveTemp(ParamToolTip));
}
return Map;
}
void FHeaderParser::AddFormattedPrevCommentAsTooltipMetaData(TMap<FName, FString>& MetaData)
{
// Don't add a tooltip if one already exists.
if (MetaData.Find(NAME_ToolTip))
{
return;
}
// Add the comment if it is not empty
if (!PrevComment.IsEmpty())
{
MetaData.Add(NAME_Comment, *PrevComment);
}
// Don't add a tooltip if the comment is empty after formatting.
FString FormattedComment = FormatCommentForToolTip(PrevComment);
if (!FormattedComment.Len())
{
return;
}
MetaData.Add(NAME_ToolTip, *FormattedComment);
// We've already used this comment as a tooltip, so clear it so that it doesn't get used again
PrevComment.Empty();
}
static const TCHAR* GetAccessSpecifierName(EAccessSpecifier AccessSpecifier)
{
switch (AccessSpecifier)
{
case ACCESS_Public:
return TEXT("public");
case ACCESS_Protected:
return TEXT("protected");
case ACCESS_Private:
return TEXT("private");
default:
check(0);
}
return TEXT("");
}
// Tries to parse the token as an access protection specifier (public:, protected:, or private:)
EAccessSpecifier FHeaderParser::ParseAccessProtectionSpecifier(const FToken& Token)
{
if (!Token.IsIdentifier())
{
return ACCESS_NotAnAccessSpecifier;
}
for (EAccessSpecifier Test = EAccessSpecifier(ACCESS_NotAnAccessSpecifier + 1); Test != ACCESS_Num; Test = EAccessSpecifier(Test + 1))
{
if (Token.IsValue(GetAccessSpecifierName(Test), ESearchCase::CaseSensitive))
{
auto ErrorMessageGetter = [&Token]() { return FString::Printf(TEXT("after %s"), *Token.GetTokenValue()); };
// Consume the colon after the specifier
RequireSymbol(TEXT(':'), ErrorMessageGetter);
return Test;
}
}
return ACCESS_NotAnAccessSpecifier;
}
/**
* Compile a struct definition.
*/
FUnrealScriptStructDefinitionInfo& FHeaderParser::CompileStructDeclaration()
{
int32 DeclInputLine = InputLine;
TSharedPtr<FFileScope> Scope = SourceFile.GetScope();
// Make sure structs can be declared here.
CheckAllow( TEXT("'struct'"), ENestAllowFlags::TypeDecl );
bool IsNative = false;
bool IsExport = false;
bool IsTransient = false;
TMap<FName, FString> MetaData;
// Get the struct specifier list
TArray<FPropertySpecifier> SpecifiersFound;
ReadSpecifierSetInsideMacro(SpecifiersFound, TEXT("Struct"), MetaData);
// Consume the struct keyword
RequireIdentifier(TEXT("struct"), ESearchCase::CaseSensitive, TEXT("Struct declaration specifier"));
// The struct name as parsed in script and stripped of it's prefix
FString StructNameInScript;
// The struct name stripped of it's prefix
FString StructNameStripped;
// The required API module for this struct, if any
FString RequiredAPIMacroIfPresent;
SkipAlignasAndDeprecatedMacroIfNecessary(*this);
// Read the struct name
ParseNameWithPotentialAPIMacroPrefix(/*out*/ StructNameInScript, /*out*/ RequiredAPIMacroIfPresent, TEXT("struct"));
StructNameStripped = GetClassNameWithPrefixRemoved(StructNameInScript);
// Effective struct name
const FString EffectiveStructName = *StructNameStripped;
// Locate the structure
FUnrealScriptStructDefinitionInfo& StructDef = GTypeDefinitionInfoMap.FindByNameChecked<FUnrealScriptStructDefinitionInfo>(*StructNameStripped);
if (StructDef.HasAnyStructFlags(STRUCT_Immutable))
{
if (!FPaths::IsSamePath(Filename, GUObjectDef->GetUnrealSourceFile().GetFilename()))
{
LogError(TEXT("Immutable is being phased out in favor of SerializeNative, and is only legal on the mirror structs declared in UObject"));
}
}
// Parse the inheritance list
ParseInheritance(TEXT("struct"), [](const TCHAR* StructName, bool bIsSuperClass) {}); // Eat the results, already been parsed
// if we have a base struct, propagate inherited struct flags now
if (!StructDef.GetSuperStructInfo().Name.IsEmpty())
{
FUnrealScriptStructDefinitionInfo* BaseStructDef = nullptr;
const FString& ParentStructNameInScript = StructDef.GetSuperStructInfo().Name;
FString ParentStructNameStripped;
bool bOverrideParentStructName = false;
if (!UHTConfig.StructsWithNoPrefix.Contains(ParentStructNameInScript))
{
bOverrideParentStructName = true;
ParentStructNameStripped = GetClassNameWithPrefixRemoved(ParentStructNameInScript);
}
// If we're expecting a prefix, first try finding the correct field with the stripped struct name
if (bOverrideParentStructName)
{
BaseStructDef = UHTCast<FUnrealScriptStructDefinitionInfo>(Scope->FindTypeByName(*ParentStructNameStripped));
}
// If it wasn't found, try to find the literal name given
if (BaseStructDef == nullptr)
{
BaseStructDef = UHTCast<FUnrealScriptStructDefinitionInfo>(Scope->FindTypeByName(*ParentStructNameInScript));
}
// Try to locate globally //@TODO: UCREMOVAL: This seems extreme
if (BaseStructDef == nullptr)
{
if (bOverrideParentStructName)
{
BaseStructDef = GTypeDefinitionInfoMap.FindByName<FUnrealScriptStructDefinitionInfo>(*ParentStructNameStripped);
}
if (BaseStructDef == nullptr)
{
BaseStructDef = GTypeDefinitionInfoMap.FindByName<FUnrealScriptStructDefinitionInfo>(*ParentStructNameInScript);
}
}
// If the struct still wasn't found, throw an error
if (BaseStructDef == nullptr)
{
Throwf(TEXT("'struct': Can't find struct '%s'"), *ParentStructNameInScript);
}
// If the struct was found, confirm it adheres to the correct syntax. This should always fail if we were expecting an override that was not found.
if (bOverrideParentStructName)
{
const TCHAR* PrefixCPP = UHTConfig.StructsWithTPrefix.Contains(ParentStructNameStripped) ? TEXT("T") : BaseStructDef->GetPrefixCPP();
if (ParentStructNameInScript != FString::Printf(TEXT("%s%s"), PrefixCPP, *ParentStructNameStripped))
{
Throwf(TEXT("Parent Struct '%s' is missing a valid Unreal prefix, expecting '%s'"), *ParentStructNameInScript, *FString::Printf(TEXT("%s%s"), PrefixCPP, *BaseStructDef->GetName()));
}
}
StructDef.GetSuperStructInfo().Struct = BaseStructDef->AsStruct();
StructDef.SetStructFlags(EStructFlags(BaseStructDef->GetStructFlags() & STRUCT_Inherit));
if (StructDef.GetScriptStructSafe() != nullptr && BaseStructDef->GetScriptStructSafe())
{
StructDef.GetScriptStructSafe()->SetSuperStruct(BaseStructDef->GetScriptStructSafe());
}
}
Scope->AddType(StructDef);
AddModuleRelativePathToMetadata(StructDef, MetaData);
// Check to make sure the syntactic native prefix was set-up correctly.
// If this check results in a false positive, it will be flagged as an identifier failure.
FString DeclaredPrefix = GetClassPrefix( StructNameInScript );
if( DeclaredPrefix == StructDef.GetPrefixCPP() || DeclaredPrefix == TEXT("T") )
{
// Found a prefix, do a basic check to see if it's valid
const TCHAR* ExpectedPrefixCPP = UHTConfig.StructsWithTPrefix.Contains(StructNameStripped) ? TEXT("T") : StructDef.GetPrefixCPP();
FString ExpectedStructName = FString::Printf(TEXT("%s%s"), ExpectedPrefixCPP, *StructNameStripped);
if (StructNameInScript != ExpectedStructName)
{
Throwf(TEXT("Struct '%s' has an invalid Unreal prefix, expecting '%s'"), *StructNameInScript, *ExpectedStructName);
}
}
else
{
const TCHAR* ExpectedPrefixCPP = UHTConfig.StructsWithTPrefix.Contains(StructNameInScript) ? TEXT("T") : StructDef.GetPrefixCPP();
FString ExpectedStructName = FString::Printf(TEXT("%s%s"), ExpectedPrefixCPP, *StructNameInScript);
Throwf(TEXT("Struct '%s' is missing a valid Unreal prefix, expecting '%s'"), *StructNameInScript, *ExpectedStructName);
}
AddFormattedPrevCommentAsTooltipMetaData(MetaData);
// Register the metadata
FUHTMetaData::RemapAndAddMetaData(StructDef, MoveTemp(MetaData));
StructDef.GetDefinitionRange().Start = &Input[InputPos];
// Get opening brace.
RequireSymbol( TEXT('{'), TEXT("'struct'") );
// Members of structs have a default public access level in c++
// Assume that, but restore the parser state once we finish parsing this struct
TGuardValue<EAccessSpecifier> HoldFromClass(CurrentAccessSpecifier, ACCESS_Public);
int32 SavedLineNumber = InputLine;
// Clear comment before parsing body of the struct.
// Parse all struct variables.
FToken Token;
while (1)
{
ClearComment();
GetToken( Token );
int StartingLine = InputLine;
if (EAccessSpecifier AccessSpecifier = ParseAccessProtectionSpecifier(Token))
{
CurrentAccessSpecifier = AccessSpecifier;
}
else if (Token.IsIdentifier(TEXT("UPROPERTY"), ESearchCase::CaseSensitive))
{
CompileVariableDeclaration(StructDef);
}
else if (Token.IsIdentifier(TEXT("UFUNCTION"), ESearchCase::CaseSensitive))
{
Throwf(TEXT("USTRUCTs cannot contain UFUNCTIONs."));
}
else if (Token.IsIdentifier(TEXT("RIGVM_METHOD"), ESearchCase::CaseSensitive))
{
CompileRigVMMethodDeclaration(StructDef);
}
else if (Token.IsIdentifier(TEXT("GENERATED_USTRUCT_BODY"), ESearchCase::CaseSensitive) || Token.IsIdentifier(TEXT("GENERATED_BODY"), ESearchCase::CaseSensitive))
{
// Match 'GENERATED_USTRUCT_BODY' '(' [StructName] ')' or 'GENERATED_BODY' '(' [StructName] ')'
if (CurrentAccessSpecifier != ACCESS_Public)
{
Throwf(TEXT("%s must be in the public scope of '%s', not private or protected."), *Token.GetTokenValue(), *StructNameInScript);
}
if (StructDef.GetMacroDeclaredLineNumber() != INDEX_NONE)
{
Throwf(TEXT("Multiple %s declarations found in '%s'"), *Token.GetTokenValue(), *StructNameInScript);
}
StructDef.SetMacroDeclaredLineNumber(InputLine);
RequireSymbol(TEXT('('), TEXT("'struct'"));
CompileVersionDeclaration(StructDef);
RequireSymbol(TEXT(')'), TEXT("'struct'"));
// Eat a semicolon if present (not required)
SafeMatchSymbol(TEXT(';'));
}
else if ( Token.IsSymbol(TEXT('#')) && MatchIdentifier(TEXT("ifdef"), ESearchCase::CaseSensitive) )
{
PushCompilerDirective(ECompilerDirective::Insignificant);
}
else if ( Token.IsSymbol(TEXT('#')) && MatchIdentifier(TEXT("ifndef"), ESearchCase::CaseSensitive) )
{
PushCompilerDirective(ECompilerDirective::Insignificant);
}
else if (Token.IsSymbol(TEXT('#')) && MatchIdentifier(TEXT("endif"), ESearchCase::CaseSensitive))
{
PopCompilerDirective();
// Do nothing and hope that the if code below worked out OK earlier
// skip it and skip over the text, it is not recorded or processed
if (StartingLine == InputLine)
{
TCHAR c;
while (!IsEOL(c = GetChar()))
{
}
ClearComment();
}
}
else if ( Token.IsSymbol(TEXT('#')) && MatchIdentifier(TEXT("if"), ESearchCase::CaseSensitive) )
{
//@TODO: This parsing should be combined with CompileDirective and probably happen much much higher up!
bool bInvertConditional = MatchSymbol(TEXT('!'));
bool bConsumeAsCppText = false;
if (MatchIdentifier(TEXT("WITH_EDITORONLY_DATA"), ESearchCase::CaseSensitive) )
{
if (bInvertConditional)
{
Throwf(TEXT("Cannot use !WITH_EDITORONLY_DATA"));
}
PushCompilerDirective(ECompilerDirective::WithEditorOnlyData);
}
else if (MatchIdentifier(TEXT("WITH_EDITOR"), ESearchCase::CaseSensitive) )
{
if (bInvertConditional)
{
Throwf(TEXT("Cannot use !WITH_EDITOR"));
}
PushCompilerDirective(ECompilerDirective::WithEditor);
}
else if (MatchIdentifier(TEXT("CPP"), ESearchCase::CaseSensitive) || MatchConstInt(TEXT("0")) || MatchConstInt(TEXT("1")) || MatchIdentifier(TEXT("WITH_HOT_RELOAD"), ESearchCase::CaseSensitive) || MatchIdentifier(TEXT("WITH_HOT_RELOAD_CTORS"), ESearchCase::CaseSensitive))
{
bConsumeAsCppText = !bInvertConditional;
PushCompilerDirective(ECompilerDirective::Insignificant);
}
else
{
Throwf(TEXT("'struct': Unsupported preprocessor directive inside a struct.") );
}
if (bConsumeAsCppText)
{
// Skip over the text, it is not recorded or processed
int32 nest = 1;
while (nest > 0)
{
TCHAR ch = GetChar(1);
if ( ch==0 )
{
Throwf(TEXT("Unexpected end of struct definition %s"), *StructDef.GetName());
}
else if ( ch=='{' || (ch=='#' && (PeekIdentifier(TEXT("if"), ESearchCase::CaseSensitive) || PeekIdentifier(TEXT("ifdef"), ESearchCase::CaseSensitive))) )
{
nest++;
}
else if ( ch=='}' || (ch=='#' && PeekIdentifier(TEXT("endif"), ESearchCase::CaseSensitive)) )
{
nest--;
}
if (nest==0)
{
RequireIdentifier(TEXT("endif"), ESearchCase::CaseSensitive, TEXT("'if'"));
}
}
}
}
else if (Token.IsSymbol(TEXT('#')) && MatchIdentifier(TEXT("pragma"), ESearchCase::CaseSensitive))
{
// skip it and skip over the text, it is not recorded or processed
TCHAR c;
while (!IsEOL(c = GetChar()))
{
}
}
else if (ProbablyAnUnknownObjectLikeMacro(*this, Token))
{
// skip it
}
else
{
if (!Token.IsSymbol( TEXT('}')))
{
// Skip declaration will destroy data in Token, so cache off the identifier in case we need to provfide an error
FToken FirstToken = Token;
if (!SkipDeclaration(Token))
{
Throwf(TEXT("'struct': Unexpected '%s'"), *FirstToken.GetTokenValue());
}
}
else
{
MatchSemi();
break;
}
}
}
StructDef.GetDefinitionRange().End = &Input[InputPos];
// Validation
bool bStructBodyFound = StructDef.GetMacroDeclaredLineNumber() != INDEX_NONE;
if (!bStructBodyFound && StructDef.HasAnyStructFlags(STRUCT_Native))
{
// Roll the line number back to the start of the struct body and error out
InputLine = SavedLineNumber;
Throwf(TEXT("Expected a GENERATED_BODY() at the start of struct"));
}
// Validate sparse class data
CheckSparseClassData(StructDef);
return StructDef;
}
/*-----------------------------------------------------------------------------
Retry management.
-----------------------------------------------------------------------------*/
/**
* Remember the current compilation points, both in the source being
* compiled and the object code being emitted.
*
* @param Retry [out] filled in with current compiler position information
*/
void FHeaderParser::InitScriptLocation( FScriptLocation& Retry )
{
Retry.Input = Input;
Retry.InputPos = InputPos;
Retry.InputLine = InputLine;
}
/**
* Return to a previously-saved retry point.
*
* @param Retry the point to return to
* @param Binary whether to modify the compiled bytecode
* @param bText whether to modify the compiler's current location in the text
*/
void FHeaderParser::ReturnToLocation(const FScriptLocation& Retry, bool Binary, bool bText)
{
if (bText)
{
Input = Retry.Input;
InputPos = Retry.InputPos;
InputLine = Retry.InputLine;
}
}
/*-----------------------------------------------------------------------------
Nest information.
-----------------------------------------------------------------------------*/
//
// Return the name for a nest type.
//
const TCHAR *FHeaderParser::NestTypeName( ENestType NestType )
{
switch( NestType )
{
case ENestType::GlobalScope:
return TEXT("Global Scope");
case ENestType::Class:
return TEXT("Class");
case ENestType::NativeInterface:
case ENestType::Interface:
return TEXT("Interface");
case ENestType::FunctionDeclaration:
return TEXT("Function");
default:
check(false);
return TEXT("Unknown");
}
}
// Checks to see if a particular kind of command is allowed on this nesting level.
bool FHeaderParser::IsAllowedInThisNesting(ENestAllowFlags AllowFlags)
{
return (TopNest->Allow & AllowFlags) != ENestAllowFlags::None;
}
//
// Make sure that a particular kind of command is allowed on this nesting level.
// If it's not, issues a compiler error referring to the token and the current
// nesting level.
//
void FHeaderParser::CheckAllow( const TCHAR* Thing, ENestAllowFlags AllowFlags )
{
if (!IsAllowedInThisNesting(AllowFlags))
{
if (TopNest->NestType == ENestType::GlobalScope)
{
Throwf(TEXT("%s is not allowed before the Class definition"), Thing );
}
else
{
Throwf(TEXT("%s is not allowed here"), Thing );
}
}
}
/*-----------------------------------------------------------------------------
Nest management.
-----------------------------------------------------------------------------*/
void FHeaderParser::PushNest(ENestType NestType, FUnrealStructDefinitionInfo* InNodeDef, FUnrealSourceFile* InSourceFile)
{
// Update pointer to top nesting level.
TopNest = &Nest[NestLevel++];
TopNest->SetScope(NestType == ENestType::GlobalScope ? &InSourceFile->GetScope().Get() : &InNodeDef->GetScope().Get());
TopNest->NestType = NestType;
// Prevent overnesting.
if (NestLevel >= MAX_NEST_LEVELS)
{
Throwf(TEXT("Maximum nesting limit exceeded"));
}
// Inherit info from stack node above us.
if (NestLevel > 1 && NestType == ENestType::GlobalScope)
{
// Use the existing stack node.
TopNest->SetScope(TopNest[-1].GetScope());
}
// NestType specific logic.
switch (NestType)
{
case ENestType::GlobalScope:
TopNest->Allow = ENestAllowFlags::Class | ENestAllowFlags::TypeDecl | ENestAllowFlags::ImplicitDelegateDecl;
break;
case ENestType::Class:
TopNest->Allow = ENestAllowFlags::VarDecl | ENestAllowFlags::Function | ENestAllowFlags::ImplicitDelegateDecl;
break;
case ENestType::NativeInterface:
case ENestType::Interface:
TopNest->Allow = ENestAllowFlags::Function;
break;
case ENestType::FunctionDeclaration:
TopNest->Allow = ENestAllowFlags::VarDecl;
break;
default:
Throwf(TEXT("Internal error in PushNest, type %i"), (uint8)NestType);
break;
}
}
/**
* Decrease the nesting level and handle any errors that result.
*
* @param NestType nesting type of the current node
* @param Descr text to use in error message if any errors are encountered
*/
void FHeaderParser::PopNest(ENestType NestType, const TCHAR* Descr)
{
// Validate the nesting state.
if (NestLevel <= 0)
{
Throwf(TEXT("Unexpected '%s' at global scope"), Descr, NestTypeName(NestType));
}
else if (TopNest->NestType != NestType)
{
Throwf(TEXT("Unexpected end of %s in '%s' block"), Descr, NestTypeName(TopNest->NestType));
}
if (NestType != ENestType::GlobalScope && NestType != ENestType::Class && NestType != ENestType::Interface && NestType != ENestType::NativeInterface && NestType != ENestType::FunctionDeclaration)
{
Throwf(TEXT("Bad first pass NestType %i"), (uint8)NestType);
}
// Pop the nesting level.
NestType = TopNest->NestType;
NestLevel--;
if (NestLevel == 0)
{
TopNest = nullptr;
}
else
{
TopNest--;
check(TopNest >= Nest);
}
}
void FHeaderParser::FixupDelegateProperties(FUnrealStructDefinitionInfo& StructDef, FScope& Scope, TMap<FName, FUnrealFunctionDefinitionInfo*>& DelegateCache )
{
for (TSharedRef<FUnrealPropertyDefinitionInfo> PropertyDef : StructDef.GetProperties())
{
FPropertyBase& PropertyBase = PropertyDef->GetPropertyBase();
// Ignore any containers types but static arrays
if (PropertyBase.ArrayType != EArrayType::None && PropertyBase.ArrayType != EArrayType::Static)
{
continue;
}
// At this point, we can check to see if we are a delegate or multicast delegate directly off the property definition
bool bIsDelegate = PropertyBase.Type == CPT_Delegate;
bool bIsMulticastDelegate = PropertyBase.Type == CPT_MulticastDelegate;
if (!bIsDelegate && !bIsMulticastDelegate)
{
continue;
}
// this FDelegateProperty corresponds to an actual delegate variable (i.e. delegate<SomeDelegate> Foo); we need to lookup the token data for
// this property and verify that the delegate property's "type" is an actual delegate function
FPropertyBase& DelegatePropertyBase = PropertyDef->GetPropertyBase();
// attempt to find the delegate function in the map of functions we've already found
FUnrealFunctionDefinitionInfo* SourceDelegateFunctionDef = DelegateCache.FindRef(DelegatePropertyBase.DelegateName);
if (SourceDelegateFunctionDef == nullptr)
{
FString NameOfDelegateFunction = DelegatePropertyBase.DelegateName.ToString() + FString( HEADER_GENERATED_DELEGATE_SIGNATURE_SUFFIX );
if ( !NameOfDelegateFunction.Contains(TEXT(".")) )
{
// an unqualified delegate function name - search for a delegate function by this name within the current scope
if (FUnrealFieldDefinitionInfo* FoundDef = Scope.FindTypeByName(*NameOfDelegateFunction))
{
SourceDelegateFunctionDef = FoundDef->AsFunction();
}
if (SourceDelegateFunctionDef == nullptr && DelegatePropertyBase.DelegateSignatureOwnerClassDef != nullptr)
{
SourceDelegateFunctionDef = FindFunction(*DelegatePropertyBase.DelegateSignatureOwnerClassDef, *NameOfDelegateFunction, true, nullptr);
}
if (SourceDelegateFunctionDef == nullptr)
{
FScopeLock Lock(&GlobalDelegatesLock);
SourceDelegateFunctionDef = GlobalDelegates.FindRef(NameOfDelegateFunction);
}
if (SourceDelegateFunctionDef == nullptr)
{
// convert this into a fully qualified path name for the error message.
NameOfDelegateFunction = Scope.GetName().ToString() + TEXT(".") + NameOfDelegateFunction;
}
}
else
{
FString DelegateClassName, DelegateName;
NameOfDelegateFunction.Split(TEXT("."), &DelegateClassName, &DelegateName);
// verify that we got a valid string for the class name
if ( DelegateClassName.Len() == 0 )
{
UngetToken(PropertyDef->GetLineNumber(), PropertyDef->GetParsePosition());
Throwf(TEXT("Invalid scope specified in delegate property function reference: '%s'"), *NameOfDelegateFunction);
}
// verify that we got a valid string for the name of the function
if ( DelegateName.Len() == 0 )
{
UngetToken(PropertyDef->GetLineNumber(), PropertyDef->GetParsePosition());
Throwf(TEXT("Invalid delegate name specified in delegate property function reference '%s'"), *NameOfDelegateFunction);
}
// make sure that the class that contains the delegate can be referenced here
FUnrealClassDefinitionInfo* DelegateOwnerClassDef = FUnrealClassDefinitionInfo::FindScriptClassOrThrow(*this, DelegateClassName);
if (DelegateOwnerClassDef->GetScope()->FindTypeByName(*DelegateName) != nullptr)
{
Throwf(TEXT("Inaccessible type: '%s'"), *DelegateOwnerClassDef->GetPathName());
}
SourceDelegateFunctionDef = FindFunction(*DelegateOwnerClassDef, *DelegateName, false, nullptr);
}
if (SourceDelegateFunctionDef == NULL )
{
UngetToken(PropertyDef->GetLineNumber(), PropertyDef->GetParsePosition());
Throwf(TEXT("Failed to find delegate function '%s'"), *NameOfDelegateFunction);
}
else if (!SourceDelegateFunctionDef->HasAnyFunctionFlags(FUNC_Delegate))
{
UngetToken(PropertyDef->GetLineNumber(), PropertyDef->GetParsePosition());
Throwf(TEXT("Only delegate functions can be used as the type for a delegate property; '%s' is not a delegate."), *NameOfDelegateFunction);
}
}
// successfully found the delegate function that this delegate property corresponds to
// save this into the delegate cache for faster lookup later
DelegateCache.Add(DelegatePropertyBase.DelegateName, SourceDelegateFunctionDef);
// bind it to the delegate property
if (PropertyBase.Type == CPT_Delegate)
{
if( !SourceDelegateFunctionDef->HasAnyFunctionFlags( FUNC_MulticastDelegate ) )
{
PropertyDef->SetDelegateFunctionSignature(*SourceDelegateFunctionDef);
}
else
{
Throwf(TEXT("Unable to declare a single-cast delegate property for a multi-cast delegate type '%s'. Either add a 'multicast' qualifier to the property or change the delegate type to be single-cast as well."), *SourceDelegateFunctionDef->GetName());
}
}
else if (PropertyBase.Type == CPT_MulticastDelegate)
{
if (SourceDelegateFunctionDef->HasAnyFunctionFlags( FUNC_MulticastDelegate ))
{
PropertyDef->SetDelegateFunctionSignature(*SourceDelegateFunctionDef);
}
else
{
Throwf(TEXT("Unable to declare a multi-cast delegate property for a single-cast delegate type '%s'. Either remove the 'multicast' qualifier from the property or change the delegate type to be 'multicast' as well."), *SourceDelegateFunctionDef->GetName());
}
}
}
// Functions might have their own delegate properties which need to be validated
for (TSharedRef<FUnrealFunctionDefinitionInfo> Function : StructDef.GetFunctions())
{
FixupDelegateProperties(*Function, Scope, DelegateCache);
}
CheckDocumentationPolicyForStruct(StructDef);
ParseRigVMMethodParameters(StructDef);
}
void FHeaderParser::CheckSparseClassData(const FUnrealStructDefinitionInfo& StructDef)
{
// we're looking for classes that have sparse class data structures
const FUnrealClassDefinitionInfo* ClassDef = UHTCast<FUnrealClassDefinitionInfo>(StructDef);
// make sure we don't try to have sparse class data inside of a struct instead of a class
if (StructDef.HasMetaData(FHeaderParserNames::NAME_SparseClassDataTypes))
{
if (ClassDef == nullptr)
{
StructDef.Throwf(TEXT("%s contains sparse class data but is not a class."), *StructDef.GetName());
}
}
else
{
return;
}
TArray<FString> SparseClassDataTypes;
ClassDef->GetSparseClassDataTypes(SparseClassDataTypes);
// for now we only support one sparse class data structure per class
if (SparseClassDataTypes.Num() > 1)
{
StructDef.Throwf(TEXT("Class %s contains multiple sparse class data types."), *ClassDef->GetName());
return;
}
if (SparseClassDataTypes.Num() == 0)
{
StructDef.Throwf(TEXT("Class %s has sparse class metadata but does not specify a type."), *ClassDef->GetName());
return;
}
for (const FString& SparseClassDataTypeName : SparseClassDataTypes)
{
FUnrealScriptStructDefinitionInfo* SparseClassDataStructDef = GTypeDefinitionInfoMap.FindByName<FUnrealScriptStructDefinitionInfo>(*SparseClassDataTypeName);
// make sure the sparse class data struct actually exists
if (!SparseClassDataStructDef)
{
StructDef.Throwf(TEXT("Unable to find sparse data type %s for class %s."), *SparseClassDataTypeName, *ClassDef->GetName());
return;
}
// check the data struct for invalid properties
for (FUnrealPropertyDefinitionInfo* PropertyDef : TUHTFieldRange<FUnrealPropertyDefinitionInfo>(*SparseClassDataStructDef))
{
if (PropertyDef->HasAnyPropertyFlags(CPF_BlueprintAssignable))
{
StructDef.Throwf(TEXT("Sparse class data types can not contain blueprint assignable delegates. Type '%s' Delegate '%s'"), *SparseClassDataStructDef->GetName(), *PropertyDef->GetName());
}
// all sparse properties should have EditDefaultsOnly
if (!PropertyDef->HasAllPropertyFlags(CPF_Edit | CPF_DisableEditOnInstance))
{
StructDef.Throwf(TEXT("Sparse class data types must be VisibleDefaultsOnly or EditDefaultsOnly. Type '%s' Property '%s'"), *SparseClassDataStructDef->GetName(), *PropertyDef->GetName());
}
// no sparse properties should have BlueprintReadWrite
if (PropertyDef->HasAllPropertyFlags(CPF_BlueprintVisible) && !PropertyDef->HasAllPropertyFlags(CPF_BlueprintReadOnly))
{
StructDef.Throwf(TEXT("Sparse class data types must not be BlueprintReadWrite. Type '%s' Property '%s'"), *SparseClassDataStructDef->GetName(), *PropertyDef->GetName());
}
}
// if the class's parent has a sparse class data struct then the current class must also use the same struct or one that inherits from it
const FUnrealClassDefinitionInfo* ParentClassDef = ClassDef->GetSuperClass();
TArray<FString> ParentSparseClassDataTypeNames;
ClassDef->GetSuperClass()->GetSparseClassDataTypes(ParentSparseClassDataTypeNames);
for (FString& ParentSparseClassDataTypeName : ParentSparseClassDataTypeNames)
{
if (FUnrealScriptStructDefinitionInfo* ParentSparseClassDataStructDef = GTypeDefinitionInfoMap.FindByName<FUnrealScriptStructDefinitionInfo>(*ParentSparseClassDataTypeName))
{
if (!SparseClassDataStructDef->IsChildOf(*ParentSparseClassDataStructDef))
{
StructDef.Throwf(TEXT("Class %s is a child of %s but its sparse class data struct, %s, does not inherit from %s."), *ClassDef->GetName(), *ParentClassDef->GetName(), *SparseClassDataStructDef->GetName(), *ParentSparseClassDataStructDef->GetName());
}
}
}
}
}
void FHeaderParser::ValidateClassFlags(const FUnrealClassDefinitionInfo& ToValidate)
{
if (ToValidate.HasAnyClassFlags(CLASS_NeedsDeferredDependencyLoading) && !ToValidate.IsChildOf(*GUClassDef))
{
// CLASS_NeedsDeferredDependencyLoading can only be set on classes derived from UClass
ToValidate.Throwf(TEXT("NeedsDeferredDependencyLoading is set on %s but the flag can only be used with classes derived from UClass."), *ToValidate.GetName());
}
}
void FHeaderParser::VerifyBlueprintPropertyGetter(FUnrealPropertyDefinitionInfo& PropertyDef, FUnrealFunctionDefinitionInfo* TargetFuncDef)
{
check(TargetFuncDef);
const TArray<TSharedRef<FUnrealPropertyDefinitionInfo>>& Properties = TargetFuncDef->GetProperties();
FUnrealPropertyDefinitionInfo* ReturnPropDef = TargetFuncDef->GetReturn();
if (Properties.Num() > 1 || (Properties.Num() == 1 && ReturnPropDef == nullptr))
{
LogError(TEXT("Blueprint Property getter function %s must not have parameters."), *TargetFuncDef->GetName());
}
if (ReturnPropDef == nullptr || !PropertyDef.SameType(*ReturnPropDef))
{
FString ExtendedCPPType;
FString CPPType = PropertyDef.GetCPPType(&ExtendedCPPType);
LogError(TEXT("Blueprint Property getter function %s must have return value of type %s%s."), *TargetFuncDef->GetName(), *CPPType, *ExtendedCPPType);
}
if (TargetFuncDef->HasAnyFunctionFlags(FUNC_Event))
{
LogError(TEXT("Blueprint Property setter function cannot be a blueprint event."));
}
else if (!TargetFuncDef->HasAnyFunctionFlags(FUNC_BlueprintPure))
{
LogError(TEXT("Blueprint Property getter function must be pure."));
}
}
void FHeaderParser::VerifyBlueprintPropertySetter(FUnrealPropertyDefinitionInfo& PropertyDef, FUnrealFunctionDefinitionInfo* TargetFuncDef)
{
check(TargetFuncDef);
FUnrealPropertyDefinitionInfo* ReturnPropDef = TargetFuncDef->GetReturn();
if (ReturnPropDef)
{
LogError(TEXT("Blueprint Property setter function %s must not have a return value."), *TargetFuncDef->GetName());
}
else
{
const TArray<TSharedRef<FUnrealPropertyDefinitionInfo>>& Properties = TargetFuncDef->GetProperties();
if (TargetFuncDef->GetProperties().Num() != 1 || !PropertyDef.SameType(*Properties[0]))
{
FString ExtendedCPPType;
FString CPPType = PropertyDef.GetCPPType(&ExtendedCPPType);
LogError(TEXT("Blueprint Property setter function %s must have exactly one parameter of type %s%s."), *TargetFuncDef->GetName(), *CPPType, *ExtendedCPPType);
}
}
if (TargetFuncDef->HasAnyFunctionFlags(FUNC_Event))
{
LogError(TEXT("Blueprint Property setter function cannot be a blueprint event."));
}
else if (!TargetFuncDef->HasAnyFunctionFlags(FUNC_BlueprintCallable))
{
LogError(TEXT("Blueprint Property setter function must be blueprint callable."));
}
else if (TargetFuncDef->HasAnyFunctionFlags(FUNC_BlueprintPure))
{
LogError(TEXT("Blueprint Property setter function must not be pure."));
}
}
void FHeaderParser::VerifyRepNotifyCallback(FUnrealPropertyDefinitionInfo& PropertyDef, FUnrealFunctionDefinitionInfo* TargetFuncDef)
{
if (TargetFuncDef)
{
if (TargetFuncDef->GetReturn())
{
LogError(TEXT("Replication notification function %s must not have return value."), *TargetFuncDef->GetName());
}
const bool bIsArrayProperty = PropertyDef.IsStaticArray() || PropertyDef.IsDynamicArray();
const int32 MaxParms = bIsArrayProperty ? 2 : 1;
const TArray<TSharedRef<FUnrealPropertyDefinitionInfo>>& Properties = TargetFuncDef->GetProperties();
if (Properties.Num() > MaxParms)
{
LogError(TEXT("Replication notification function %s has too many parameters."), *TargetFuncDef->GetName());
}
if (Properties.Num() >= 1)
{
const FUnrealPropertyDefinitionInfo* ParmDef = &*Properties[0];
// First parameter is always the old value:
if (!PropertyDef.SameType(*ParmDef))
{
FString ExtendedCPPType;
FString CPPType = ParmDef->GetCPPType(&ExtendedCPPType);
LogError(TEXT("Replication notification function %s has invalid parameter for property %s. First (optional) parameter must be of type %s%s."), *TargetFuncDef->GetName(), *ParmDef->GetName(), *CPPType, *ExtendedCPPType);
}
}
if (TargetFuncDef->GetProperties().Num() >= 2)
{
FUnrealPropertyDefinitionInfo* ParmDef = &*Properties[1];
FPropertyBase& ParmBase = ParmDef->GetPropertyBase();
// A 2nd parameter for arrays can be specified as a const TArray<uint8>&. This is a list of element indices that have changed
if (!(ParmBase.Type == CPT_Byte && ParmBase.ArrayType == EArrayType::Dynamic && ParmDef->HasAllPropertyFlags(CPF_ConstParm | CPF_ReferenceParm)))
{
LogError(TEXT("Replication notification function %s (optional) second parameter must be of type 'const TArray<uint8>&'"), *TargetFuncDef->GetName());
}
}
}
else
{
// Couldn't find a valid function...
LogError(TEXT("Replication notification function %s not found"), *PropertyDef.GetRepNotifyFunc().ToString() );
}
}
void FHeaderParser::VerifyPropertyMarkups(FUnrealClassDefinitionInfo& TargetClassDef)
{
// Iterate over all properties, looking for those flagged as CPF_RepNotify
for (TSharedRef<FUnrealPropertyDefinitionInfo> PropertyDef : TargetClassDef.GetProperties())
{
auto FindTargetFunction = [&](const FName FuncName) -> FUnrealFunctionDefinitionInfo*
{
// Search through this class and its super classes looking for the specified callback
for (FUnrealClassDefinitionInfo* SearchClassDef = &TargetClassDef; SearchClassDef; SearchClassDef = SearchClassDef->GetSuperClass())
{
// Since the function map is not valid yet, we have to iterate over the fields to look for the function
for (TSharedRef<FUnrealFunctionDefinitionInfo> FunctionDef : SearchClassDef->GetFunctions())
{
if (FunctionDef->GetFName() == FuncName)
{
return &*FunctionDef;
}
}
}
return nullptr;
};
TGuardValue<int32> GuardedInputPos(InputPos, PropertyDef->GetParsePosition());
TGuardValue<int32> GuardedInputLine(InputLine, PropertyDef->GetLineNumber());
if (PropertyDef->HasAnyPropertyFlags(CPF_RepNotify))
{
VerifyRepNotifyCallback(*PropertyDef, FindTargetFunction(PropertyDef->GetRepNotifyFunc()));
}
if (PropertyDef->HasAnyPropertyFlags(CPF_BlueprintVisible))
{
const FString& GetterFuncName = PropertyDef->GetMetaData(NAME_BlueprintGetter);
if (!GetterFuncName.IsEmpty())
{
if (FUnrealFunctionDefinitionInfo* TargetFuncDef = FindTargetFunction(*GetterFuncName))
{
VerifyBlueprintPropertyGetter(*PropertyDef, TargetFuncDef);
}
else
{
// Couldn't find a valid function...
LogError(TEXT("Blueprint Property getter function %s not found"), *GetterFuncName);
}
}
if (!PropertyDef->HasAnyPropertyFlags(CPF_BlueprintReadOnly))
{
const FString& SetterFuncName = PropertyDef->GetMetaData(NAME_BlueprintSetter);
if (!SetterFuncName.IsEmpty())
{
if (FUnrealFunctionDefinitionInfo* TargetFuncDef = FindTargetFunction(*SetterFuncName))
{
VerifyBlueprintPropertySetter(*PropertyDef, TargetFuncDef);
}
else
{
// Couldn't find a valid function...
LogError(TEXT("Blueprint Property setter function %s not found"), *SetterFuncName);
}
}
}
}
// Verify if native property setter and getter functions could actually be found while parsing class header
FPropertyBase& PropertyToken = PropertyDef->GetPropertyBase();
if (!PropertyToken.SetterName.IsEmpty())
{
if (PropertyToken.SetterName != TEXT("None") && !PropertyToken.bSetterFunctionFound)
{
LogError(TEXT("Property %s setter function %s not found"), *PropertyDef->GetName(), *PropertyToken.SetterName);
}
}
if (!PropertyToken.GetterName.IsEmpty())
{
if (PropertyToken.GetterName != TEXT("None") && !PropertyToken.bGetterFunctionFound)
{
LogError(TEXT("Property %s getter function %s not found"), *PropertyDef->GetName(), *PropertyToken.GetterName);
}
}
}
}
/*-----------------------------------------------------------------------------
Compiler directives.
-----------------------------------------------------------------------------*/
//
// Process a compiler directive.
//
void FHeaderParser::CompileDirective()
{
FToken Directive;
int32 LineAtStartOfDirective = InputLine;
// Define directive are skipped but they can be multiline.
bool bDefineDirective = false;
if (!GetIdentifier(Directive))
{
Throwf(TEXT("Missing compiler directive after '#'") );
}
else if (Directive.IsValue(TEXT("error"), ESearchCase::CaseSensitive))
{
Throwf(TEXT("#error directive encountered") );
}
else if (Directive.IsValue(TEXT("pragma"), ESearchCase::CaseSensitive))
{
// Ignore all pragmas
}
else if (Directive.IsValue(TEXT("linenumber"), ESearchCase::CaseSensitive))
{
FToken Number;
if (!GetToken(Number) || !Number.IsConstInt())
{
Throwf(TEXT("Missing line number in line number directive"));
}
int32 newInputLine;
if ( Number.GetConstInt(newInputLine) )
{
InputLine = newInputLine;
}
}
else if (Directive.IsValue(TEXT("include"), ESearchCase::CaseSensitive))
{
FToken IncludeName;
if (GetToken(IncludeName) && IncludeName.IsConstString())
{
FTokenString InludeNameString = IncludeName.GetTokenString();
const FString& ExpectedHeaderName = SourceFile.GetGeneratedHeaderFilename();
if (FCString::Stricmp(*InludeNameString, *ExpectedHeaderName) == 0)
{
bSpottedAutogeneratedHeaderInclude = true;
}
}
}
else if (Directive.IsValue(TEXT("if"), ESearchCase::CaseSensitive))
{
// Eat the ! if present
bool bNotDefined = MatchSymbol(TEXT('!'));
int32 TempInt;
const bool bParsedInt = GetConstInt(TempInt);
if (bParsedInt && (TempInt == 0 || TempInt == 1))
{
PushCompilerDirective(ECompilerDirective::Insignificant);
}
else
{
FToken Define;
if (!GetIdentifier(Define))
{
Throwf(TEXT("Missing define name '#if'") );
}
if ( Define.IsValue(TEXT("WITH_EDITORONLY_DATA"), ESearchCase::CaseSensitive) )
{
PushCompilerDirective(ECompilerDirective::WithEditorOnlyData);
}
else if ( Define.IsValue(TEXT("WITH_EDITOR"), ESearchCase::CaseSensitive) )
{
PushCompilerDirective(ECompilerDirective::WithEditor);
}
else if (Define.IsValue(TEXT("WITH_HOT_RELOAD"), ESearchCase::CaseSensitive) || Define.IsValue(TEXT("WITH_HOT_RELOAD_CTORS"), ESearchCase::CaseSensitive) || Define.IsSymbol(TEXT('1'))) // @TODO: This symbol test can never work after a GetIdentifier
{
PushCompilerDirective(ECompilerDirective::Insignificant);
}
else if ( Define.IsValue(TEXT("CPP"), ESearchCase::CaseSensitive) && bNotDefined)
{
PushCompilerDirective(ECompilerDirective::Insignificant);
}
else
{
Throwf(TEXT("Unknown define '#if %s' in class or global scope"), *Define.GetTokenValue());
}
}
}
else if (Directive.IsValue(TEXT("endif"), ESearchCase::CaseSensitive))
{
PopCompilerDirective();
}
else if (Directive.IsValue(TEXT("define"), ESearchCase::CaseSensitive))
{
// Ignore the define directive (can be multiline).
bDefineDirective = true;
}
else if (Directive.IsValue(TEXT("ifdef"), ESearchCase::CaseSensitive) || Directive.IsValue(TEXT("ifndef"), ESearchCase::CaseSensitive))
{
PushCompilerDirective(ECompilerDirective::Insignificant);
}
else if (Directive.IsValue(TEXT("undef"), ESearchCase::CaseSensitive) || Directive.IsValue(TEXT("else"), ESearchCase::CaseSensitive))
{
// Ignore. UHT can only handle #if directive
}
else
{
Throwf(TEXT("Unrecognized compiler directive %s"), *Directive.GetTokenValue() );
}
// Skip to end of line (or end of multiline #define).
if (LineAtStartOfDirective == InputLine)
{
TCHAR LastCharacter = TEXT('\0');
TCHAR c;
do
{
while ( !IsEOL( c=GetChar() ) )
{
LastCharacter = c;
}
}
// Continue until the entire multiline directive has been skipped.
while (LastCharacter == '\\' && bDefineDirective);
if (c == 0)
{
UngetChar();
}
// Remove any comments parsed to prevent any trailing comments from being picked up
ClearComment();
}
}
/*-----------------------------------------------------------------------------
Variable declaration parser.
-----------------------------------------------------------------------------*/
void FHeaderParser::GetVarType(
FScope* Scope,
EGetVarTypeOptions Options,
FPropertyBase& VarProperty,
EPropertyFlags Disallow,
EUHTPropertyType OuterPropertyType,
EPropertyFlags OuterPropertyFlags,
EPropertyDeclarationStyle::Type PropertyDeclarationStyle,
EVariableCategory VariableCategory,
FIndexRange* ParsedVarIndexRange,
ELayoutMacroType* OutLayoutMacroType
)
{
FUnrealStructDefinitionInfo* OwnerStructDef = Scope->IsFileScope() ? nullptr : &((FStructScope*)Scope)->GetStructDef();
FUnrealScriptStructDefinitionInfo* OwnerScriptStructDef = UHTCast<FUnrealScriptStructDefinitionInfo>(OwnerStructDef);
FUnrealClassDefinitionInfo* OwnerClassDef = UHTCast<FUnrealClassDefinitionInfo>(OwnerStructDef);
FName RepCallbackName = FName(NAME_None);
FString SetterName;
FString GetterName;
bool bHasSetterTag = false;
bool bHasGetterTag = false;
// Get flags.
EPropertyFlags Flags = CPF_None;
EPropertyFlags ImpliedFlags = CPF_None;
// force members to be 'blueprint read only' if in a const class
if (VariableCategory == EVariableCategory::Member)
{
if (OwnerClassDef)
{
if (OwnerClassDef->HasAnyClassFlags(CLASS_Const))
{
ImpliedFlags |= CPF_BlueprintReadOnly;
}
}
}
uint32 ExportFlags = PROPEXPORT_Public;
// Build up a list of specifiers
TArray<FPropertySpecifier> SpecifiersFound;
TMap<FName, FString> MetaDataFromNewStyle;
bool bNativeConst = false;
bool bNativeConstTemplateArg = false;
const bool bIsParamList = (VariableCategory != EVariableCategory::Member) && MatchIdentifier(TEXT("UPARAM"), ESearchCase::CaseSensitive);
// No specifiers are allowed inside a TArray
if (OuterPropertyType != EUHTPropertyType::DynamicArray)
{
// New-style UPROPERTY() syntax
if (PropertyDeclarationStyle == EPropertyDeclarationStyle::UPROPERTY || bIsParamList)
{
ReadSpecifierSetInsideMacro(SpecifiersFound, TEXT("Variable"), MetaDataFromNewStyle);
}
}
if (VariableCategory != EVariableCategory::Member)
{
// const before the variable type support (only for params)
if (MatchIdentifier(TEXT("const"), ESearchCase::CaseSensitive))
{
Flags |= CPF_ConstParm;
bNativeConst = true;
}
}
uint32 CurrentCompilerDirective = GetCurrentCompilerDirective();
if ((CurrentCompilerDirective & ECompilerDirective::WithEditorOnlyData) != 0)
{
Flags |= CPF_EditorOnly;
}
else if ((CurrentCompilerDirective & ECompilerDirective::WithEditor) != 0)
{
// Checking for this error is a bit tricky given legacy code.
// 1) If already wrapped in WITH_EDITORONLY_DATA (see above), then we ignore the error via the else
// 2) Ignore any module that is an editor module
const FManifestModule& Module = Scope->GetFileScope()->GetSourceFile()->GetPackageDef().GetModule();
const bool bIsEditorModule =
Module.ModuleType == EBuildModuleType::EngineEditor ||
Module.ModuleType == EBuildModuleType::GameEditor ||
Module.ModuleType == EBuildModuleType::EngineUncooked ||
Module.ModuleType == EBuildModuleType::GameUncooked;
if (VariableCategory == EVariableCategory::Member && !bIsEditorModule)
{
LogError(TEXT("UProperties should not be wrapped by WITH_EDITOR, use WITH_EDITORONLY_DATA instead."));
}
}
// Validate if we are using editor only data in a class or struct definition
if ((Flags & CPF_EditorOnly) != 0)
{
if (OwnerClassDef && OwnerClassDef->HasAnyClassFlags(CLASS_Optional))
{
LogError(TEXT("Cannot specify an editor only property inside an optional class."));
}
}
// Store the start and end positions of the parsed type
if (ParsedVarIndexRange)
{
ParsedVarIndexRange->StartIndex = InputPos;
}
// Process the list of specifiers
bool bSeenEditSpecifier = false;
bool bSeenBlueprintWriteSpecifier = false;
bool bSeenBlueprintReadOnlySpecifier = false;
bool bSeenBlueprintGetterSpecifier = false;
for (const FPropertySpecifier& Specifier : SpecifiersFound)
{
EVariableSpecifier SpecID = (EVariableSpecifier)Algo::FindSortedStringCaseInsensitive(*Specifier.Key, GVariableSpecifierStrings);
if (VariableCategory == EVariableCategory::Member)
{
switch (SpecID)
{
case EVariableSpecifier::EditAnywhere:
{
if (bSeenEditSpecifier)
{
LogError(TEXT("Found more than one edit/visibility specifier (%s), only one is allowed"), *Specifier.Key);
}
Flags |= CPF_Edit;
bSeenEditSpecifier = true;
}
break;
case EVariableSpecifier::EditInstanceOnly:
{
if (bSeenEditSpecifier)
{
LogError(TEXT("Found more than one edit/visibility specifier (%s), only one is allowed"), *Specifier.Key);
}
Flags |= CPF_Edit | CPF_DisableEditOnTemplate;
bSeenEditSpecifier = true;
}
break;
case EVariableSpecifier::EditDefaultsOnly:
{
if (bSeenEditSpecifier)
{
LogError(TEXT("Found more than one edit/visibility specifier (%s), only one is allowed"), *Specifier.Key);
}
Flags |= CPF_Edit | CPF_DisableEditOnInstance;
bSeenEditSpecifier = true;
}
break;
case EVariableSpecifier::VisibleAnywhere:
{
if (bSeenEditSpecifier)
{
LogError(TEXT("Found more than one edit/visibility specifier (%s), only one is allowed"), *Specifier.Key);
}
Flags |= CPF_Edit | CPF_EditConst;
bSeenEditSpecifier = true;
}
break;
case EVariableSpecifier::VisibleInstanceOnly:
{
if (bSeenEditSpecifier)
{
LogError(TEXT("Found more than one edit/visibility specifier (%s), only one is allowed"), *Specifier.Key);
}
Flags |= CPF_Edit | CPF_EditConst | CPF_DisableEditOnTemplate;
bSeenEditSpecifier = true;
}
break;
case EVariableSpecifier::VisibleDefaultsOnly:
{
if (bSeenEditSpecifier)
{
LogError(TEXT("Found more than one edit/visibility specifier (%s), only one is allowed"), *Specifier.Key);
}
Flags |= CPF_Edit | CPF_EditConst | CPF_DisableEditOnInstance;
bSeenEditSpecifier = true;
}
break;
case EVariableSpecifier::BlueprintReadWrite:
{
if (bSeenBlueprintReadOnlySpecifier)
{
LogError(TEXT("Cannot specify a property as being both BlueprintReadOnly and BlueprintReadWrite."));
}
const FString* PrivateAccessMD = MetaDataFromNewStyle.Find(NAME_AllowPrivateAccess); // FBlueprintMetadata::MD_AllowPrivateAccess
const bool bAllowPrivateAccess = PrivateAccessMD ? (*PrivateAccessMD != TEXT("false")) : false;
if (CurrentAccessSpecifier == ACCESS_Private && !bAllowPrivateAccess)
{
LogError(TEXT("BlueprintReadWrite should not be used on private members"));
}
if ((Flags & CPF_EditorOnly) != 0 && OwnerScriptStructDef != nullptr)
{
LogError(TEXT("Blueprint exposed struct members cannot be editor only"));
}
Flags |= CPF_BlueprintVisible;
bSeenBlueprintWriteSpecifier = true;
}
break;
case EVariableSpecifier::BlueprintSetter:
{
if (bSeenBlueprintReadOnlySpecifier)
{
LogError(TEXT("Cannot specify a property as being both BlueprintReadOnly and having a BlueprintSetter."));
}
if (OwnerScriptStructDef != nullptr)
{
LogError(TEXT("Cannot specify BlueprintSetter for a struct member."));
}
const FString BlueprintSetterFunction = RequireExactlyOneSpecifierValue(*this, Specifier);
MetaDataFromNewStyle.Add(NAME_BlueprintSetter, BlueprintSetterFunction);
Flags |= CPF_BlueprintVisible;
bSeenBlueprintWriteSpecifier = true;
}
break;
case EVariableSpecifier::BlueprintReadOnly:
{
if (bSeenBlueprintWriteSpecifier)
{
LogError(TEXT("Cannot specify both BlueprintReadOnly and BlueprintReadWrite or BlueprintSetter."), *Specifier.Key);
}
const FString* PrivateAccessMD = MetaDataFromNewStyle.Find(NAME_AllowPrivateAccess); // FBlueprintMetadata::MD_AllowPrivateAccess
const bool bAllowPrivateAccess = PrivateAccessMD ? (*PrivateAccessMD != TEXT("false")) : false;
if (CurrentAccessSpecifier == ACCESS_Private && !bAllowPrivateAccess)
{
LogError(TEXT("BlueprintReadOnly should not be used on private members"));
}
if ((Flags & CPF_EditorOnly) != 0 && OwnerScriptStructDef != nullptr)
{
LogError(TEXT("Blueprint exposed struct members cannot be editor only"));
}
Flags |= CPF_BlueprintVisible | CPF_BlueprintReadOnly;
ImpliedFlags &= ~CPF_BlueprintReadOnly;
bSeenBlueprintReadOnlySpecifier = true;
}
break;
case EVariableSpecifier::BlueprintGetter:
{
if (OwnerScriptStructDef != nullptr)
{
LogError(TEXT("Cannot specify BlueprintGetter for a struct member."));
}
const FString BlueprintGetterFunction = RequireExactlyOneSpecifierValue(*this, Specifier);
MetaDataFromNewStyle.Add(NAME_BlueprintGetter, BlueprintGetterFunction);
Flags |= CPF_BlueprintVisible;
bSeenBlueprintGetterSpecifier = true;
}
break;
case EVariableSpecifier::Config:
{
Flags |= CPF_Config;
}
break;
case EVariableSpecifier::GlobalConfig:
{
Flags |= CPF_GlobalConfig | CPF_Config;
}
break;
case EVariableSpecifier::Localized:
{
LogError(TEXT("The Localized specifier is deprecated"));
}
break;
case EVariableSpecifier::Transient:
{
Flags |= CPF_Transient;
}
break;
case EVariableSpecifier::DuplicateTransient:
{
Flags |= CPF_DuplicateTransient;
}
break;
case EVariableSpecifier::TextExportTransient:
{
Flags |= CPF_TextExportTransient;
}
break;
case EVariableSpecifier::NonPIETransient:
{
LogWarning(TEXT("NonPIETransient is deprecated - NonPIEDuplicateTransient should be used instead"));
Flags |= CPF_NonPIEDuplicateTransient;
}
break;
case EVariableSpecifier::NonPIEDuplicateTransient:
{
Flags |= CPF_NonPIEDuplicateTransient;
}
break;
case EVariableSpecifier::Export:
{
Flags |= CPF_ExportObject;
}
break;
case EVariableSpecifier::EditInline:
{
LogError(TEXT("EditInline is deprecated. Remove it, or use Instanced instead."));
}
break;
case EVariableSpecifier::NoClear:
{
Flags |= CPF_NoClear;
}
break;
case EVariableSpecifier::EditFixedSize:
{
Flags |= CPF_EditFixedSize;
}
break;
case EVariableSpecifier::Replicated:
case EVariableSpecifier::ReplicatedUsing:
{
if (OwnerScriptStructDef != nullptr)
{
LogError(TEXT("Struct members cannot be replicated"));
}
Flags |= CPF_Net;
// See if we've specified a rep notification function
if (SpecID == EVariableSpecifier::ReplicatedUsing)
{
RepCallbackName = FName(*RequireExactlyOneSpecifierValue(*this, Specifier));
Flags |= CPF_RepNotify;
}
}
break;
case EVariableSpecifier::NotReplicated:
{
if (OwnerScriptStructDef == nullptr)
{
LogError(TEXT("Only Struct members can be marked NotReplicated"));
}
Flags |= CPF_RepSkip;
}
break;
case EVariableSpecifier::RepRetry:
{
LogError(TEXT("'RepRetry' is deprecated."));
}
break;
case EVariableSpecifier::Interp:
{
Flags |= CPF_Edit;
Flags |= CPF_BlueprintVisible;
Flags |= CPF_Interp;
}
break;
case EVariableSpecifier::NonTransactional:
{
Flags |= CPF_NonTransactional;
}
break;
case EVariableSpecifier::Instanced:
{
Flags |= CPF_PersistentInstance | CPF_ExportObject | CPF_InstancedReference;
AddEditInlineMetaData(MetaDataFromNewStyle);
}
break;
case EVariableSpecifier::BlueprintAssignable:
{
Flags |= CPF_BlueprintAssignable;
}
break;
case EVariableSpecifier::BlueprintCallable:
{
Flags |= CPF_BlueprintCallable;
}
break;
case EVariableSpecifier::BlueprintAuthorityOnly:
{
Flags |= CPF_BlueprintAuthorityOnly;
}
break;
case EVariableSpecifier::AssetRegistrySearchable:
{
Flags |= CPF_AssetRegistrySearchable;
}
break;
case EVariableSpecifier::SimpleDisplay:
{
Flags |= CPF_SimpleDisplay;
}
break;
case EVariableSpecifier::AdvancedDisplay:
{
Flags |= CPF_AdvancedDisplay;
}
break;
case EVariableSpecifier::SaveGame:
{
Flags |= CPF_SaveGame;
}
break;
case EVariableSpecifier::SkipSerialization:
{
Flags |= CPF_SkipSerialization;
}
break;
case EVariableSpecifier::Setter:
{
bHasSetterTag = true;
if (Specifier.Values.Num() == 1)
{
SetterName = Specifier.Values[0];
}
else if (Specifier.Values.Num() > 1)
{
Throwf(TEXT("The specifier '%s' must be given exactly one value"), *Specifier.Key);
}
}
break;
case EVariableSpecifier::Getter:
{
bHasGetterTag = true;
if (Specifier.Values.Num() == 1)
{
GetterName = Specifier.Values[0];
}
else if (Specifier.Values.Num() > 1)
{
Throwf(TEXT("The specifier '%s' must be given exactly one value"), *Specifier.Key);
}
}
break;
default:
{
LogError(TEXT("Unknown variable specifier '%s'"), *Specifier.Key);
}
break;
}
}
else
{
switch (SpecID)
{
case EVariableSpecifier::Const:
{
Flags |= CPF_ConstParm;
}
break;
case EVariableSpecifier::Ref:
{
Flags |= CPF_OutParm | CPF_ReferenceParm;
}
break;
case EVariableSpecifier::NotReplicated:
{
if (VariableCategory == EVariableCategory::ReplicatedParameter)
{
VariableCategory = EVariableCategory::RegularParameter;
Flags |= CPF_RepSkip;
}
else
{
LogError(TEXT("Only parameters in service request functions can be marked NotReplicated"));
}
}
break;
default:
{
LogError(TEXT("Unknown variable specifier '%s'"), *Specifier.Key);
}
break;
}
}
}
// If we saw a BlueprintGetter but did not see BlueprintSetter or
// or BlueprintReadWrite then treat as BlueprintReadOnly
if (bSeenBlueprintGetterSpecifier && !bSeenBlueprintWriteSpecifier)
{
Flags |= CPF_BlueprintReadOnly;
ImpliedFlags &= ~CPF_BlueprintReadOnly;
}
{
const FString* ExposeOnSpawnStr = MetaDataFromNewStyle.Find(NAME_ExposeOnSpawn);
const bool bExposeOnSpawn = (NULL != ExposeOnSpawnStr);
if (bExposeOnSpawn)
{
if (0 != (CPF_DisableEditOnInstance & Flags))
{
LogWarning(TEXT("Property cannot have both 'DisableEditOnInstance' and 'ExposeOnSpawn' flags"));
}
if (0 == (CPF_BlueprintVisible & Flags))
{
LogWarning(TEXT("Property cannot have 'ExposeOnSpawn' without 'BlueprintVisible' flag."));
}
Flags |= CPF_ExposeOnSpawn;
}
}
if (CurrentAccessSpecifier == ACCESS_Public || VariableCategory != EVariableCategory::Member)
{
Flags &= ~CPF_Protected;
ExportFlags |= PROPEXPORT_Public;
ExportFlags &= ~(PROPEXPORT_Private|PROPEXPORT_Protected);
Flags &= ~CPF_NativeAccessSpecifiers;
Flags |= CPF_NativeAccessSpecifierPublic;
}
else if (CurrentAccessSpecifier == ACCESS_Protected)
{
Flags |= CPF_Protected;
ExportFlags |= PROPEXPORT_Protected;
ExportFlags &= ~(PROPEXPORT_Public|PROPEXPORT_Private);
Flags &= ~CPF_NativeAccessSpecifiers;
Flags |= CPF_NativeAccessSpecifierProtected;
}
else if (CurrentAccessSpecifier == ACCESS_Private)
{
Flags &= ~CPF_Protected;
ExportFlags |= PROPEXPORT_Private;
ExportFlags &= ~(PROPEXPORT_Public|PROPEXPORT_Protected);
Flags &= ~CPF_NativeAccessSpecifiers;
Flags |= CPF_NativeAccessSpecifierPrivate;
}
else
{
Throwf(TEXT("Unknown access level"));
}
// Swallow inline keywords
if (VariableCategory == EVariableCategory::Return)
{
FToken InlineToken;
if (!GetIdentifier(InlineToken, true))
{
Throwf(TEXT("%s: Missing variable type"), GetHintText(*this, VariableCategory));
}
if (!InlineToken.IsValue(TEXT("inline"), ESearchCase::CaseSensitive) &&
!InlineToken.IsValue(TEXT("FORCENOINLINE"), ESearchCase::CaseSensitive) &&
!InlineToken.ValueStartsWith(TEXT("FORCEINLINE"), ESearchCase::CaseSensitive))
{
UngetToken(InlineToken);
}
}
// Get variable type.
bool bUnconsumedStructKeyword = false;
bool bUnconsumedClassKeyword = false;
bool bUnconsumedEnumKeyword = false;
bool bUnconsumedConstKeyword = false;
// Handle MemoryLayout.h macros
ELayoutMacroType LayoutMacroType = ELayoutMacroType::None;
bool bHasWrapperBrackets = false;
ON_SCOPE_EXIT
{
if (OutLayoutMacroType)
{
*OutLayoutMacroType = LayoutMacroType;
if (bHasWrapperBrackets)
{
RequireSymbol(TEXT(')'), GLayoutMacroNames[(int32)LayoutMacroType]);
}
}
};
if (OutLayoutMacroType)
{
*OutLayoutMacroType = ELayoutMacroType::None;
FToken LayoutToken;
if (GetToken(LayoutToken))
{
if (LayoutToken.IsIdentifier())
{
FTokenValue Name = LayoutToken.GetTokenValue();
LayoutMacroType = (ELayoutMacroType)Algo::FindSortedStringCaseInsensitive(*Name, GLayoutMacroNames, UE_ARRAY_COUNT(GLayoutMacroNames));
if (LayoutMacroType != ELayoutMacroType::None)
{
RequireSymbol(TEXT('('), GLayoutMacroNames[(int32)LayoutMacroType]);
if (LayoutMacroType == ELayoutMacroType::ArrayEditorOnly || LayoutMacroType == ELayoutMacroType::FieldEditorOnly || LayoutMacroType == ELayoutMacroType::BitfieldEditorOnly)
{
Flags |= CPF_EditorOnly;
}
bHasWrapperBrackets = MatchSymbol(TEXT("("));
}
else
{
UngetToken(LayoutToken);
}
}
}
}
if (MatchIdentifier(TEXT("mutable"), ESearchCase::CaseSensitive))
{
//@TODO: Should flag as settable from a const context, but this is at least good enough to allow use for C++ land
}
const int32 VarStartPos = InputPos;
if (MatchIdentifier(TEXT("const"), ESearchCase::CaseSensitive))
{
//@TODO: UCREMOVAL: Should use this to set the new (currently non-existent) CPF_Const flag appropriately!
bUnconsumedConstKeyword = true;
bNativeConst = true;
}
if (MatchIdentifier(TEXT("struct"), ESearchCase::CaseSensitive))
{
bUnconsumedStructKeyword = true;
}
else if (MatchIdentifier(TEXT("class"), ESearchCase::CaseSensitive))
{
bUnconsumedClassKeyword = true;
}
else if (MatchIdentifier(TEXT("enum"), ESearchCase::CaseSensitive))
{
if (VariableCategory == EVariableCategory::Member)
{
Throwf(TEXT("%s: Cannot declare enum at variable declaration"), GetHintText(*this, VariableCategory));
}
bUnconsumedEnumKeyword = true;
}
//
FToken VarType;
if ( !GetIdentifier(VarType, true) )
{
Throwf(TEXT("%s: Missing variable type"), GetHintText(*this, VariableCategory));
}
RedirectTypeIdentifier(VarType);
if ( VariableCategory == EVariableCategory::Return && VarType.IsValue(TEXT("void"), ESearchCase::CaseSensitive) )
{
VarProperty = FPropertyBase(CPT_None);
}
else if ( VarType.IsValue(TEXT("int8"), ESearchCase::CaseSensitive) )
{
VarProperty = FPropertyBase(CPT_Int8);
}
else if ( VarType.IsValue(TEXT("int16"), ESearchCase::CaseSensitive) )
{
VarProperty = FPropertyBase(CPT_Int16);
}
else if ( VarType.IsValue(TEXT("int32"), ESearchCase::CaseSensitive) )
{
VarProperty = FPropertyBase(CPT_Int);
}
else if ( VarType.IsValue(TEXT("int64"), ESearchCase::CaseSensitive) )
{
VarProperty = FPropertyBase(CPT_Int64);
}
else if ( VarType.IsValue(TEXT("uint64"), ESearchCase::CaseSensitive) && IsBitfieldProperty(LayoutMacroType) )
{
// 64-bit bitfield (bool) type, treat it like 8 bit type
VarProperty = FPropertyBase(CPT_Bool8);
}
else if ( VarType.IsValue(TEXT("uint32"), ESearchCase::CaseSensitive) && IsBitfieldProperty(LayoutMacroType) )
{
// 32-bit bitfield (bool) type, treat it like 8 bit type
VarProperty = FPropertyBase(CPT_Bool8);
}
else if ( VarType.IsValue(TEXT("uint16"), ESearchCase::CaseSensitive) && IsBitfieldProperty(LayoutMacroType) )
{
// 16-bit bitfield (bool) type, treat it like 8 bit type.
VarProperty = FPropertyBase(CPT_Bool8);
}
else if ( VarType.IsValue(TEXT("uint8"), ESearchCase::CaseSensitive) && IsBitfieldProperty(LayoutMacroType) )
{
// 8-bit bitfield (bool) type
VarProperty = FPropertyBase(CPT_Bool8);
}
else if ( VarType.IsValue(TEXT("int"), ESearchCase::CaseSensitive) )
{
VarProperty = FPropertyBase(CPT_Int, EIntType::Unsized);
}
else if ( VarType.IsValue(TEXT("signed"), ESearchCase::CaseSensitive) )
{
MatchIdentifier(TEXT("int"), ESearchCase::CaseSensitive);
VarProperty = FPropertyBase(CPT_Int, EIntType::Unsized);
}
else if (VarType.IsValue(TEXT("unsigned"), ESearchCase::CaseSensitive))
{
MatchIdentifier(TEXT("int"), ESearchCase::CaseSensitive);
VarProperty = FPropertyBase(CPT_UInt32, EIntType::Unsized);
}
else if ( VarType.IsValue(TEXT("bool"), ESearchCase::CaseSensitive) )
{
if (IsBitfieldProperty(LayoutMacroType))
{
LogError(TEXT("bool bitfields are not supported."));
}
// C++ bool type
VarProperty = FPropertyBase(CPT_Bool);
}
else if ( VarType.IsValue(TEXT("uint8"), ESearchCase::CaseSensitive) )
{
// Intrinsic Byte type.
VarProperty = FPropertyBase(CPT_Byte);
}
else if ( VarType.IsValue(TEXT("uint16"), ESearchCase::CaseSensitive) )
{
VarProperty = FPropertyBase(CPT_UInt16);
}
else if ( VarType.IsValue(TEXT("uint32"), ESearchCase::CaseSensitive) )
{
VarProperty = FPropertyBase(CPT_UInt32);
}
else if ( VarType.IsValue(TEXT("uint64"), ESearchCase::CaseSensitive) )
{
VarProperty = FPropertyBase(CPT_UInt64);
}
else if ( VarType.IsValue(TEXT("float"), ESearchCase::CaseSensitive) )
{
// Intrinsic single precision floating point type.
VarProperty = FPropertyBase(CPT_Float);
}
else if ( VarType.IsValue(TEXT("double"), ESearchCase::CaseSensitive) )
{
// Intrinsic double precision floating point type type.
VarProperty = FPropertyBase(CPT_Double);
}
else if (VarType.IsValue(TEXT("FLargeWorldCoordinatesReal"), ESearchCase::CaseSensitive)) // LWC_TODO: Remove FLargeWorldCoordinatesReal?
{
if (!SourceFile.IsNoExportTypes())
{
Throwf(TEXT("FLargeWorldCoordinatesReal is intended for LWC support only and should not be used outside of NoExportTypes.h"));
}
VarProperty = FPropertyBase(CPT_FLargeWorldCoordinatesReal);
}
else if ( VarType.IsValue(TEXT("FName"), ESearchCase::CaseSensitive) )
{
// Intrinsic Name type.
VarProperty = FPropertyBase(CPT_Name);
}
else if ( VarType.IsValue(TEXT("TArray"), ESearchCase::CaseSensitive) )
{
RequireSymbol( TEXT('<'), TEXT("'tarray'") );
GetVarType(Scope, Options, VarProperty, Disallow, EUHTPropertyType::DynamicArray, Flags, EPropertyDeclarationStyle::None, VariableCategory);
if (VarProperty.IsContainer())
{
Throwf(TEXT("Nested containers are not supported.") );
}
// TODO: Prevent sparse delegate types from being used in a container
if (VarProperty.MetaData.Find(NAME_NativeConst))
{
bNativeConstTemplateArg = true;
}
VarProperty.ArrayType = EArrayType::Dynamic;
FToken CloseTemplateToken;
if (!GetToken(CloseTemplateToken, /*bNoConsts=*/ true, ESymbolParseOption::CloseTemplateBracket))
{
Throwf(TEXT("Missing token while parsing TArray."));
}
if (!CloseTemplateToken.IsSymbol(TEXT('>')))
{
// If we didn't find a comma, report it
if (!CloseTemplateToken.IsSymbol(TEXT(',')))
{
Throwf(TEXT("Expected '>' but found '%s'"), *CloseTemplateToken.GetTokenValue());
}
// If we found a comma, read the next thing, assume it's an allocator, and report that
FToken AllocatorToken;
if (!GetToken(AllocatorToken, /*bNoConsts=*/ true, ESymbolParseOption::CloseTemplateBracket))
{
Throwf(TEXT("Unexpected end of file when parsing TArray allocator."));
}
if (!AllocatorToken.IsIdentifier())
{
Throwf(TEXT("Found '%s' - expected a '>' or ','."), *AllocatorToken.GetTokenValue());
}
if (AllocatorToken.IsValue(TEXT("FMemoryImageAllocator"), ESearchCase::CaseSensitive))
{
if (EnumHasAnyFlags(Flags, CPF_Net))
{
Throwf(TEXT("Replicated arrays with MemoryImageAllocators are not yet supported"));
}
RequireSymbol(TEXT('>'), TEXT("TArray template arguments"), ESymbolParseOption::CloseTemplateBracket);
VarProperty.AllocatorType = EAllocatorType::MemoryImage;
}
else if (AllocatorToken.IsValue(TEXT("TMemoryImageAllocator"), ESearchCase::CaseSensitive))
{
if (EnumHasAnyFlags(Flags, CPF_Net))
{
Throwf(TEXT("Replicated arrays with MemoryImageAllocators are not yet supported"));
}
RequireSymbol(TEXT('<'), TEXT("TMemoryImageAllocator template arguments"));
FToken SkipToken;
for (;;)
{
if (!GetToken(SkipToken, /*bNoConsts=*/ false, ESymbolParseOption::CloseTemplateBracket))
{
Throwf(TEXT("Unexpected end of file when parsing TMemoryImageAllocator template arguments."));
}
if (SkipToken.IsSymbol(TEXT('>')))
{
RequireSymbol(TEXT('>'), TEXT("TArray template arguments"), ESymbolParseOption::CloseTemplateBracket);
VarProperty.AllocatorType = EAllocatorType::MemoryImage;
break;
}
}
}
else
{
Throwf(TEXT("Found '%s' - explicit allocators are not supported in TArray properties."), *AllocatorToken.GetTokenValue());
}
}
}
else if ( VarType.IsValue(TEXT("TMap"), ESearchCase::CaseSensitive) )
{
RequireSymbol( TEXT('<'), TEXT("'tmap'") );
FPropertyBase MapKeyType(CPT_None);
GetVarType(Scope, Options, MapKeyType, Disallow, EUHTPropertyType::Map, Flags, EPropertyDeclarationStyle::None, VariableCategory);
if (MapKeyType.IsContainer())
{
Throwf(TEXT("Nested containers are not supported.") );
}
// TODO: Prevent sparse delegate types from being used in a container
if (MapKeyType.Type == CPT_Interface)
{
Throwf(TEXT("UINTERFACEs are not currently supported as key types."));
}
if (MapKeyType.Type == CPT_Text)
{
Throwf(TEXT("FText is not currently supported as a key type."));
}
if (EnumHasAnyFlags(Flags, CPF_Net))
{
LogError(TEXT("Replicated maps are not supported."));
}
FToken CommaToken;
if (!GetToken(CommaToken, /*bNoConsts=*/ true) || !CommaToken.IsSymbol(TEXT(',')))
{
Throwf(TEXT("Missing value type while parsing TMap."));
}
GetVarType(Scope, Options, VarProperty, Disallow, EUHTPropertyType::Map, Flags, EPropertyDeclarationStyle::None, VariableCategory);
if (VarProperty.IsContainer())
{
Throwf(TEXT("Nested containers are not supported.") );
}
// TODO: Prevent sparse delegate types from being used in a container
MapKeyType.PropertyFlags = (MapKeyType.PropertyFlags & CPF_UObjectWrapper); // Make sure the 'UObjectWrapper' flag is maintained so that 'TMap<TSubclassOf<...>, ...>' works
VarProperty.MapKeyProp = MakeShared<FPropertyBase>(MoveTemp(MapKeyType));
VarProperty.MapKeyProp->DisallowFlags = ~(CPF_ContainsInstancedReference | CPF_InstancedReference | CPF_UObjectWrapper);
FToken CloseTemplateToken;
if (!GetToken(CloseTemplateToken, /*bNoConsts=*/ true, ESymbolParseOption::CloseTemplateBracket))
{
Throwf(TEXT("Missing token while parsing TMap."));
}
if (!CloseTemplateToken.IsSymbol(TEXT('>')))
{
// If we didn't find a comma, report it
if (!CloseTemplateToken.IsSymbol(TEXT(',')))
{
Throwf(TEXT("Expected '>' but found '%s'"), *CloseTemplateToken.GetTokenValue());
}
// If we found a comma, read the next thing, assume it's an allocator, and report that
FToken AllocatorToken;
if (!GetToken(AllocatorToken, /*bNoConsts=*/ true, ESymbolParseOption::CloseTemplateBracket))
{
Throwf(TEXT("Unexpected end of file when parsing TArray allocator."));
}
if (!AllocatorToken.IsIdentifier())
{
Throwf(TEXT("Found '%s' - expected a '>' or ','."), *AllocatorToken.GetTokenValue());
}
if (AllocatorToken.IsIdentifier(TEXT("FMemoryImageSetAllocator"), ESearchCase::CaseSensitive))
{
RequireSymbol(TEXT('>'), TEXT("TMap template arguments"), ESymbolParseOption::CloseTemplateBracket);
VarProperty.AllocatorType = EAllocatorType::MemoryImage;
}
else
{
Throwf(TEXT("Found '%s' - explicit allocators are not supported in TMap properties."), *AllocatorToken.GetTokenValue());
}
}
}
else if ( VarType.IsValue(TEXT("TSet"), ESearchCase::CaseSensitive) )
{
RequireSymbol( TEXT('<'), TEXT("'tset'") );
GetVarType(Scope, Options, VarProperty, Disallow, EUHTPropertyType::Set, Flags, EPropertyDeclarationStyle::None, VariableCategory);
if (VarProperty.IsContainer())
{
Throwf(TEXT("Nested containers are not supported.") );
}
// TODO: Prevent sparse delegate types from being used in a container
if (VarProperty.Type == CPT_Interface)
{
Throwf(TEXT("UINTERFACEs are not currently supported as element types."));
}
if (VarProperty.Type == CPT_Text)
{
Throwf(TEXT("FText is not currently supported as an element type."));
}
if (EnumHasAnyFlags(Flags, CPF_Net))
{
LogError(TEXT("Replicated sets are not supported."));
}
VarProperty.ArrayType = EArrayType::Set;
FToken CloseTemplateToken;
if (!GetToken(CloseTemplateToken, /*bNoConsts=*/ true, ESymbolParseOption::CloseTemplateBracket))
{
Throwf(TEXT("Missing token while parsing TArray."));
}
if (!CloseTemplateToken.IsSymbol(TEXT('>')))
{
// If we didn't find a comma, report it
if (!CloseTemplateToken.IsSymbol(TEXT(',')))
{
Throwf(TEXT("Expected '>' but found '%s'"), *CloseTemplateToken.GetTokenValue());
}
// If we found a comma, read the next thing, assume it's a keyfuncs, and report that
FToken AllocatorToken;
if (!GetToken(AllocatorToken, /*bNoConsts=*/ true, ESymbolParseOption::CloseTemplateBracket))
{
Throwf(TEXT("Expected '>' but found '%s'"), *CloseTemplateToken.GetTokenValue());
}
Throwf(TEXT("Found '%s' - explicit KeyFuncs are not supported in TSet properties."), *AllocatorToken.GetTokenValue());
}
}
else if ( VarType.IsValue(TEXT("FString"), ESearchCase::CaseSensitive) || VarType.IsValue(TEXT("FMemoryImageString"), ESearchCase::CaseSensitive))
{
VarProperty = FPropertyBase(CPT_String);
if (VariableCategory != EVariableCategory::Member)
{
if (MatchSymbol(TEXT('&')))
{
if (Flags & CPF_ConstParm)
{
// 'const FString& Foo' came from 'FString' in .uc, no flags
Flags &= ~CPF_ConstParm;
// We record here that we encountered a const reference, because we need to remove that information from flags for code generation purposes.
VarProperty.RefQualifier = ERefQualifier::ConstRef;
}
else
{
// 'FString& Foo' came from 'out FString' in .uc
Flags |= CPF_OutParm;
// And we record here that we encountered a non-const reference here too.
VarProperty.RefQualifier = ERefQualifier::NonConstRef;
}
}
}
}
else if ( VarType.IsValue(TEXT("Text"), ESearchCase::IgnoreCase) )
{
Throwf(TEXT("%s' is missing a prefix, expecting 'FText'"), *VarType.GetTokenValue());
}
else if ( VarType.IsValue(TEXT("FText"), ESearchCase::CaseSensitive) )
{
VarProperty = FPropertyBase(CPT_Text);
}
else if (VarType.IsValue(TEXT("TEnumAsByte"), ESearchCase::CaseSensitive))
{
RequireSymbol(TEXT('<'), VarType.Value);
// Eat the forward declaration enum text if present
MatchIdentifier(TEXT("enum"), ESearchCase::CaseSensitive);
bool bFoundEnum = false;
FToken InnerEnumType;
if (GetIdentifier(InnerEnumType, true))
{
if (FUnrealEnumDefinitionInfo* EnumDef = GTypeDefinitionInfoMap.FindByName<FUnrealEnumDefinitionInfo>(*InnerEnumType.GetTokenValue()))
{
// In-scope enumeration.
VarProperty = FPropertyBase(*EnumDef, CPT_Byte);
bFoundEnum = true;
}
}
// Try to handle namespaced enums
// Note: We do not verify the scoped part is correct, and trust in the C++ compiler to catch that sort of mistake
if (MatchSymbol(TEXT("::")))
{
FToken ScopedTrueEnumName;
if (!GetIdentifier(ScopedTrueEnumName, true))
{
Throwf(TEXT("Expected a namespace scoped enum name.") );
}
}
if (!bFoundEnum)
{
Throwf(TEXT("Expected the name of a previously defined enum"));
}
RequireSymbol(TEXT('>'), VarType.Value, ESymbolParseOption::CloseTemplateBracket);
}
else if (VarType.IsValue(TEXT("TFieldPath"), ESearchCase::CaseSensitive ))
{
RequireSymbol( TEXT('<'), TEXT("'TFieldPath'") );
FName FieldClassName = NAME_None;
FToken PropertyTypeToken;
if (!GetToken(PropertyTypeToken, /* bNoConsts = */ true))
{
Throwf(TEXT("Expected the property type"));
}
else
{
FieldClassName = FName(PropertyTypeToken.Value.RightChop(1), FNAME_Add);
if (!FPropertyTraits::IsValidFieldClass(FieldClassName))
{
Throwf(TEXT("Undefined property type: %s"), *PropertyTypeToken.GetTokenValue());
}
}
RequireSymbol(TEXT('>'), VarType.Value, ESymbolParseOption::CloseTemplateBracket);
VarProperty = FPropertyBase(FieldClassName, CPT_FieldPath);
}
else if (FUnrealEnumDefinitionInfo* EnumDef = GTypeDefinitionInfoMap.FindByName<FUnrealEnumDefinitionInfo>(*VarType.GetTokenValue()))
{
EPropertyType UnderlyingType = CPT_Byte;
if (VariableCategory == EVariableCategory::Member)
{
if (EnumDef->GetCppForm() != UEnum::ECppForm::EnumClass)
{
Throwf(TEXT("You cannot use the raw enum name as a type for member variables, instead use TEnumAsByte or a C++11 enum class with an explicit underlying type."), *EnumDef->GetCppType());
}
}
// Try to handle namespaced enums
// Note: We do not verify the scoped part is correct, and trust in the C++ compiler to catch that sort of mistake
if (MatchSymbol(TEXT("::")))
{
FToken ScopedTrueEnumName;
if (!GetIdentifier(ScopedTrueEnumName, true))
{
Throwf(TEXT("Expected a namespace scoped enum name.") );
}
}
// In-scope enumeration.
VarProperty = FPropertyBase(*EnumDef, UnderlyingType);
bUnconsumedEnumKeyword = false;
}
else
{
// Check for structs/classes
bool bHandledType = false;
FString IdentifierStripped = GetClassNameWithPrefixRemoved(FString(VarType.Value));
bool bStripped = false;
FUnrealScriptStructDefinitionInfo* StructDef = GTypeDefinitionInfoMap.FindByName<FUnrealScriptStructDefinitionInfo>(*VarType.GetTokenValue());
if (!StructDef)
{
StructDef = GTypeDefinitionInfoMap.FindByName<FUnrealScriptStructDefinitionInfo>(*IdentifierStripped);
bStripped = true;
}
auto SetDelegateType = [&](FUnrealFunctionDefinitionInfo& InFunctionDef, const FString& InIdentifierStripped)
{
bHandledType = true;
VarProperty = FPropertyBase(InFunctionDef.HasAnyFunctionFlags(FUNC_MulticastDelegate) ? CPT_MulticastDelegate : CPT_Delegate);
VarProperty.DelegateName = *InIdentifierStripped;
VarProperty.FunctionDef = &InFunctionDef;
};
if (!StructDef && MatchSymbol(TEXT("::")))
{
FToken DelegateName;
if (GetIdentifier(DelegateName))
{
if (FUnrealClassDefinitionInfo* LocalOwnerClassDef = FUnrealClassDefinitionInfo::FindClass(*IdentifierStripped))
{
if (!BusyWait([LocalOwnerClassDef]()
{
return LocalOwnerClassDef->IsParsed() || LocalOwnerClassDef->GetUnrealSourceFile().IsParsed();
}))
{
Throwf(TEXT("Timeout waiting for '%s' to be parsed. Make sure that '%s' directly references the include file or that all intermediate include files contain some type of UCLASS, USTRUCT, or UENUM."),
*LocalOwnerClassDef->GetUnrealSourceFile().GetFilename(), *SourceFile.GetFilename());
}
const FString DelegateIdentifierStripped = GetClassNameWithPrefixRemoved(FString(DelegateName.Value));
if (FUnrealFieldDefinitionInfo* FoundDef = LocalOwnerClassDef->GetScope()->FindTypeByName(*(DelegateIdentifierStripped + HEADER_GENERATED_DELEGATE_SIGNATURE_SUFFIX)))
{
if (FUnrealFunctionDefinitionInfo* DelegateFuncDef = FoundDef->AsFunction())
{
SetDelegateType(*DelegateFuncDef, DelegateIdentifierStripped);
VarProperty.DelegateSignatureOwnerClassDef = LocalOwnerClassDef;
}
}
}
else
{
Throwf(TEXT("Cannot find class '%s', to resolve delegate '%s'"), *IdentifierStripped, *DelegateName.GetTokenValue());
}
}
}
if (bHandledType)
{
}
else if (StructDef)
{
if (bStripped)
{
const TCHAR* PrefixCPP = UHTConfig.StructsWithTPrefix.Contains(IdentifierStripped) ? TEXT("T") : StructDef->GetPrefixCPP();
FString ExpectedStructName = FString::Printf(TEXT("%s%s"), PrefixCPP, *StructDef->GetName());
if (!VarType.IsValue(*ExpectedStructName, ESearchCase::CaseSensitive))
{
Throwf(TEXT("Struct '%s' is missing or has an incorrect prefix, expecting '%s'"), *VarType.GetTokenValue(), *ExpectedStructName);
}
}
else if (!UHTConfig.StructsWithNoPrefix.Contains(VarType.Value))
{
const TCHAR* PrefixCPP = UHTConfig.StructsWithTPrefix.Contains(VarType.Value) ? TEXT("T") : StructDef->GetPrefixCPP();
Throwf(TEXT("Struct '%s' is missing a prefix, expecting '%s'"), *VarType.GetTokenValue(), *FString::Printf(TEXT("%s%s"), PrefixCPP, *StructDef->GetName()));
}
bHandledType = true;
VarProperty = FPropertyBase(*StructDef);
// Struct keyword in front of a struct is legal, we 'consume' it
bUnconsumedStructKeyword = false;
}
else if (GTypeDefinitionInfoMap.FindByName<FUnrealScriptStructDefinitionInfo>(*IdentifierStripped) != nullptr)
{
bHandledType = true;
// Struct keyword in front of a struct is legal, we 'consume' it
bUnconsumedStructKeyword = false;
}
else
{
FUnrealFunctionDefinitionInfo* DelegateFuncDef = UHTCast<FUnrealFunctionDefinitionInfo>(Scope->FindTypeByName(*(IdentifierStripped + HEADER_GENERATED_DELEGATE_SIGNATURE_SUFFIX)));
if (DelegateFuncDef)
{
SetDelegateType(*DelegateFuncDef, IdentifierStripped);
}
else
{
// An object reference of some type (maybe a restricted class?)
FUnrealClassDefinitionInfo* TempClassDef = nullptr;
EPropertyType PropertyType = CPT_ObjectReference;
const bool bIsSoftObjectPtrTemplate = VarType.IsValue(TEXT("TSoftObjectPtr"), ESearchCase::CaseSensitive);
bool bWeakIsAuto = false;
if (VarType.IsValue(TEXT("TSubclassOf"), ESearchCase::CaseSensitive))
{
TempClassDef = GUClassDef;
}
else if (VarType.IsValue(TEXT("FScriptInterface"), ESearchCase::CaseSensitive))
{
TempClassDef = GUInterfaceDef;
Flags |= CPF_UObjectWrapper;
}
else if (VarType.IsValue(TEXT("TSoftClassPtr"), ESearchCase::CaseSensitive))
{
TempClassDef = GUClassDef;
PropertyType = CPT_SoftObjectReference;
}
else
{
const bool bIsLazyPtrTemplate = VarType.IsValue(TEXT("TLazyObjectPtr"), ESearchCase::CaseSensitive);
const bool bIsObjectPtrTemplate = VarType.IsValue(TEXT("TObjectPtr"), ESearchCase::CaseSensitive);
const bool bIsWeakPtrTemplate = VarType.IsValue(TEXT("TWeakObjectPtr"), ESearchCase::CaseSensitive);
const bool bIsAutoweakPtrTemplate = VarType.IsValue(TEXT("TAutoWeakObjectPtr"), ESearchCase::CaseSensitive);
const bool bIsScriptInterfaceWrapper = VarType.IsValue(TEXT("TScriptInterface"), ESearchCase::CaseSensitive);
if (bIsLazyPtrTemplate || bIsObjectPtrTemplate || bIsWeakPtrTemplate || bIsAutoweakPtrTemplate || bIsScriptInterfaceWrapper || bIsSoftObjectPtrTemplate)
{
RequireSymbol(TEXT('<'), VarType.Value);
// Also consume const
bNativeConstTemplateArg |= MatchIdentifier(TEXT("const"), ESearchCase::CaseSensitive);
// Consume a forward class declaration 'class' if present
MatchIdentifier(TEXT("class"), ESearchCase::CaseSensitive);
// Find the lazy/weak class
FToken InnerClass;
if (GetIdentifier(InnerClass))
{
RedirectTypeIdentifier(InnerClass);
TempClassDef = FUnrealClassDefinitionInfo::FindScriptClass(FString(InnerClass.Value));
if (TempClassDef == nullptr)
{
FTokenValue InnerClassName = InnerClass.GetTokenValue();
Throwf(TEXT("Unrecognized type '%s' (in expression %s<%s>) - type must be a UCLASS"), *InnerClassName, *VarType.GetTokenValue(), *InnerClassName);
}
if (bIsAutoweakPtrTemplate)
{
PropertyType = CPT_WeakObjectReference;
bWeakIsAuto = true;
}
else if (bIsLazyPtrTemplate)
{
PropertyType = CPT_LazyObjectReference;
}
else if (bIsObjectPtrTemplate)
{
PropertyType = CPT_ObjectPtrReference;
}
else if (bIsWeakPtrTemplate)
{
PropertyType = CPT_WeakObjectReference;
}
else if (bIsSoftObjectPtrTemplate)
{
PropertyType = CPT_SoftObjectReference;
}
Flags |= CPF_UObjectWrapper;
}
else
{
Throwf(TEXT("%s: Missing template type"), *VarType.GetTokenValue());
}
// Const after template argument type but before end of template symbol
bNativeConstTemplateArg |= MatchIdentifier(TEXT("const"), ESearchCase::CaseSensitive);
RequireSymbol(TEXT('>'), VarType.Value, ESymbolParseOption::CloseTemplateBracket);
}
else
{
TempClassDef = FUnrealClassDefinitionInfo::FindScriptClass(*VarType.GetTokenValue());
}
}
if (TempClassDef != NULL)
{
bHandledType = true;
if ((PropertyType == CPT_WeakObjectReference) && (Disallow & CPF_AutoWeak)) // if it is not allowing anything, force it strong. this is probably a function arg
{
PropertyType = CPT_ObjectReference;
}
// if this is an interface class, we use the FInterfaceProperty class instead of FObjectProperty
if ((PropertyType == CPT_ObjectReference) && TempClassDef->HasAnyClassFlags(CLASS_Interface))
{
PropertyType = CPT_Interface;
}
VarProperty = FPropertyBase(*TempClassDef, PropertyType, bWeakIsAuto);
if (TempClassDef->IsChildOf(*GUClassDef))
{
if (MatchSymbol(TEXT('<')))
{
Flags |= CPF_UObjectWrapper;
// Consume a forward class declaration 'class' if present
MatchIdentifier(TEXT("class"), ESearchCase::CaseSensitive);
// Get the actual class type to restrict this to
FToken Limitor;
if (!GetIdentifier(Limitor))
{
Throwf(TEXT("'class': Missing class limitor"));
}
RedirectTypeIdentifier(Limitor);
VarProperty.MetaClassDef = FUnrealClassDefinitionInfo::FindScriptClassOrThrow(*this, FString(Limitor.Value));
RequireSymbol(TEXT('>'), TEXT("'class limitor'"), ESymbolParseOption::CloseTemplateBracket);
}
else
{
VarProperty.MetaClassDef = GUObjectDef;
}
if (PropertyType == CPT_WeakObjectReference)
{
Throwf(TEXT("Class variables cannot be weak, they are always strong."));
}
else if (PropertyType == CPT_LazyObjectReference)
{
Throwf(TEXT("Class variables cannot be lazy, they are always strong."));
}
if (bIsSoftObjectPtrTemplate)
{
Throwf(TEXT("Class variables cannot be stored in TSoftObjectPtr, use TSoftClassPtr instead."));
}
}
// Eat the star that indicates this is a pointer to the UObject
if (!(Flags & CPF_UObjectWrapper))
{
// Const after variable type but before pointer symbol
bNativeConst |= MatchIdentifier(TEXT("const"), ESearchCase::CaseSensitive);
RequireSymbol(TEXT('*'), TEXT("Expected a pointer type"));
// Optionally emit messages about native pointer members and swallow trailing 'const' after pointer properties
if (VariableCategory == EVariableCategory::Member)
{
// TODO: Remove exclusion for plugins under engine when all plugins have had their raw pointers converted.
ConditionalLogPointerUsage(bIsCurrentModulePartOfEngine && !PackageDef.GetModule().BaseDirectory.Contains(TEXT("/Plugins/")) ? UHTConfig.EngineNativePointerMemberBehavior : UHTConfig.NonEngineNativePointerMemberBehavior,
TEXT("Native pointer"), FString(InputPos - VarStartPos, Input + VarStartPos).TrimStartAndEnd().ReplaceCharWithEscapedChar(), TEXT("TObjectPtr"));
MatchIdentifier(TEXT("const"), ESearchCase::CaseSensitive);
}
VarProperty.PointerType = EPointerType::Native;
}
else if ((PropertyType == CPT_ObjectPtrReference) && (VariableCategory == EVariableCategory::Member))
{
// TODO: Remove exclusion for plugins under engine when all plugins have had their raw pointers converted.
ConditionalLogPointerUsage(bIsCurrentModulePartOfEngine && !PackageDef.GetModule().BaseDirectory.Contains(TEXT("/Plugins/")) ? UHTConfig.EngineObjectPtrMemberBehavior : UHTConfig.NonEngineObjectPtrMemberBehavior,
TEXT("ObjectPtr"), FString(InputPos - VarStartPos, Input + VarStartPos).TrimStartAndEnd().ReplaceCharWithEscapedChar(), nullptr);
}
// Imply const if it's a parameter that is a pointer to a const class
// NOTE: We shouldn't be automatically adding const param because in some cases with functions and blueprint native event, the
// generated code won't match. For now, just disabled the auto add in that case and check for the error in the validation code.
// Otherwise, the user is not warned and they will get compile errors.
if (VariableCategory != EVariableCategory::Member && (TempClassDef != NULL) && (TempClassDef->HasAnyClassFlags(CLASS_Const)))
{
if (!EnumHasAnyFlags(Options, EGetVarTypeOptions::NoAutoConst))
{
Flags |= CPF_ConstParm;
}
}
// Class keyword in front of a class is legal, we 'consume' it
bUnconsumedClassKeyword = false;
bUnconsumedConstKeyword = false;
}
}
// Resolve delegates declared in another class //@TODO: UCREMOVAL: This seems extreme
if (!bHandledType)
{
FString FullName = IdentifierStripped + HEADER_GENERATED_DELEGATE_SIGNATURE_SUFFIX;
bHandledType = BusyWait([&SetDelegateType, &IdentifierStripped, &FullName]()
{
FUnrealFunctionDefinitionInfo* DelegateDef = nullptr;
{
FScopeLock Lock(&GlobalDelegatesLock);
DelegateDef = GlobalDelegates.FindRef(FullName);
}
if (DelegateDef)
{
SetDelegateType(*DelegateDef, IdentifierStripped);
return true;
}
return false;
});
if (!bHandledType)
{
Throwf(TEXT("Unrecognized type '%s' - type must be a UCLASS, USTRUCT, UENUM, or global delegate."), *VarType.GetTokenValue());
}
}
}
}
if (VariableCategory != EVariableCategory::Member)
{
// const after the variable type support (only for params)
if (MatchIdentifier(TEXT("const"), ESearchCase::CaseSensitive))
{
Flags |= CPF_ConstParm;
bNativeConst = true;
}
}
if (bUnconsumedConstKeyword)
{
if (VariableCategory == EVariableCategory::Member)
{
Throwf(TEXT("Const properties are not supported."));
}
else
{
Throwf(TEXT("Inappropriate keyword 'const' on variable of type '%s'"), *VarType.GetTokenValue());
}
}
if (bUnconsumedClassKeyword)
{
Throwf(TEXT("Inappropriate keyword 'class' on variable of type '%s'"), *VarType.GetTokenValue());
}
if (bUnconsumedStructKeyword)
{
Throwf(TEXT("Inappropriate keyword 'struct' on variable of type '%s'"), *VarType.GetTokenValue());
}
if (bUnconsumedEnumKeyword)
{
Throwf(TEXT("Inappropriate keyword 'enum' on variable of type '%s'"), *VarType.GetTokenValue());
}
if (MatchSymbol(TEXT('*')))
{
Throwf(TEXT("Inappropriate '*' on variable of type '%s', cannot have an exposed pointer to this type."), *VarType.GetTokenValue());
}
//@TODO: UCREMOVAL: 'const' member variables that will get written post-construction by defaultproperties
if (VariableCategory == EVariableCategory::Member && OwnerClassDef != nullptr && OwnerClassDef->HasAnyClassFlags(CLASS_Const))
{
// Eat a 'not quite truthful' const after the type; autogenerated for member variables of const classes.
bNativeConst |= MatchIdentifier(TEXT("const"), ESearchCase::CaseSensitive);
}
// Arrays are passed by reference but are only implicitly so; setting it explicitly could cause a problem with replicated functions
if (MatchSymbol(TEXT('&')))
{
switch (VariableCategory)
{
case EVariableCategory::RegularParameter:
case EVariableCategory::Return:
{
Flags |= CPF_OutParm;
//@TODO: UCREMOVAL: How to determine if we have a ref param?
if (Flags & CPF_ConstParm)
{
Flags |= CPF_ReferenceParm;
}
}
break;
case EVariableCategory::ReplicatedParameter:
{
if (!(Flags & CPF_ConstParm))
{
Throwf(TEXT("Replicated %s parameters cannot be passed by non-const reference"), *VarType.GetTokenValue());
}
Flags |= CPF_ReferenceParm;
}
break;
default:
{
}
break;
}
if (Flags & CPF_ConstParm)
{
VarProperty.RefQualifier = ERefQualifier::ConstRef;
}
else
{
VarProperty.RefQualifier = ERefQualifier::NonConstRef;
}
}
// Set FPropertyBase info.
VarProperty.PropertyFlags |= Flags | ImpliedFlags;
VarProperty.ImpliedPropertyFlags |= ImpliedFlags;
VarProperty.PropertyExportFlags = ExportFlags;
VarProperty.DisallowFlags = Disallow;
// Set the RepNotify name, if the variable needs it
if( VarProperty.PropertyFlags & CPF_RepNotify )
{
if( RepCallbackName != NAME_None )
{
VarProperty.RepNotifyName = RepCallbackName;
}
else
{
Throwf(TEXT("Must specify a valid function name for replication notifications"));
}
}
VarProperty.bSetterTagFound = bHasSetterTag;
VarProperty.SetterName = SetterName;
VarProperty.bGetterTagFound = bHasGetterTag;
VarProperty.GetterName = GetterName;
// Perform some more specific validation on the property flags
if (VarProperty.PropertyFlags & CPF_PersistentInstance)
{
if ((VarProperty.Type == CPT_ObjectReference) || (VarProperty.Type == CPT_ObjectPtrReference))
{
if (VarProperty.ClassDef->IsChildOf(*GUClassDef))
{
Throwf(TEXT("'Instanced' cannot be applied to class properties (UClass* or TSubclassOf<>)"));
}
}
else
{
Throwf(TEXT("'Instanced' is only allowed on an object property, an array of objects, a set of objects, or a map with an object value type."));
}
}
if ( VarProperty.IsObjectOrInterface() && VarProperty.Type != CPT_SoftObjectReference && VarProperty.MetaClassDef == nullptr && (VarProperty.PropertyFlags&CPF_Config) != 0 )
{
Throwf(TEXT("Not allowed to use 'config' with object variables"));
}
if ((VarProperty.PropertyFlags & CPF_BlueprintAssignable) && VarProperty.Type != CPT_MulticastDelegate)
{
Throwf(TEXT("'BlueprintAssignable' is only allowed on multicast delegate properties"));
}
if ((VarProperty.PropertyFlags & CPF_BlueprintCallable) && VarProperty.Type != CPT_MulticastDelegate)
{
Throwf(TEXT("'BlueprintCallable' is only allowed on a property when it is a multicast delegate"));
}
if ((VarProperty.PropertyFlags & CPF_BlueprintAuthorityOnly) && VarProperty.Type != CPT_MulticastDelegate)
{
Throwf(TEXT("'BlueprintAuthorityOnly' is only allowed on a property when it is a multicast delegate"));
}
if (VariableCategory != EVariableCategory::Member)
{
// These conditions are checked externally for struct/member variables where the flag can be inferred later on from the variable name itself
ValidatePropertyIsDeprecatedIfNecessary(EnumHasAnyFlags(Options, EGetVarTypeOptions::OuterTypeDeprecated), VariableCategory, VarProperty, OuterPropertyType, OuterPropertyFlags);
}
// Check for invalid transients
EPropertyFlags Transients = VarProperty.PropertyFlags & (CPF_DuplicateTransient | CPF_TextExportTransient | CPF_NonPIEDuplicateTransient);
if (Transients && OwnerClassDef == nullptr)
{
TArray<const TCHAR*> FlagStrs = ParsePropertyFlags(Transients);
Throwf(TEXT("'%s' specifier(s) are only allowed on class member variables"), *FString::Join(FlagStrs, TEXT(", ")));
}
// Make sure the overrides are allowed here.
if( VarProperty.PropertyFlags & Disallow )
{
Throwf(TEXT("Specified type modifiers not allowed here") );
}
// For now, copy the flags that a TMap value has to the key
if (FPropertyBase* KeyProp = VarProperty.MapKeyProp.Get())
{
// Make sure the 'UObjectWrapper' flag is maintained so that both 'TMap<TSubclassOf<...>, ...>' and 'TMap<UClass*, TSubclassOf<...>>' works correctly
KeyProp->PropertyFlags = (VarProperty.PropertyFlags & ~CPF_UObjectWrapper) | (KeyProp->PropertyFlags & CPF_UObjectWrapper);
}
VarProperty.MetaData = MetaDataFromNewStyle;
if (bNativeConst)
{
VarProperty.MetaData.Add(NAME_NativeConst, FString());
}
if (bNativeConstTemplateArg)
{
VarProperty.MetaData.Add(NAME_NativeConstTemplateArg, FString());
}
if (ParsedVarIndexRange)
{
ParsedVarIndexRange->Count = InputPos - ParsedVarIndexRange->StartIndex;
}
}
FUnrealPropertyDefinitionInfo& FHeaderParser::GetVarNameAndDim
(
FUnrealStructDefinitionInfo& ParentStruct,
FPropertyBase& VarProperty,
EVariableCategory VariableCategory,
ELayoutMacroType LayoutMacroType
)
{
const TCHAR* HintText = GetHintText(*this, VariableCategory);
AddModuleRelativePathToMetadata(ParentStruct, VarProperty.MetaData);
FStringView Identifier;
// Get variable name.
if (VariableCategory == EVariableCategory::Return)
{
// Hard-coded variable name, such as with return value.
Identifier = FStringView(TEXT("ReturnValue"));
}
else
{
FToken VarToken;
if (!GetIdentifier(VarToken))
{
Throwf(TEXT("Missing variable name") );
}
switch (LayoutMacroType)
{
case ELayoutMacroType::Array:
case ELayoutMacroType::ArrayEditorOnly:
case ELayoutMacroType::Bitfield:
case ELayoutMacroType::BitfieldEditorOnly:
case ELayoutMacroType::FieldInitialized:
RequireSymbol(TEXT(','), GLayoutMacroNames[(int32)LayoutMacroType]);
break;
default:
break;
}
Identifier = VarToken.Value;
}
// Check to see if the variable is deprecated, and if so set the flag
{
const int32 DeprecatedIndex = Identifier.Find(TEXT("_DEPRECATED"));
const int32 NativizedPropertyPostfixIndex = Identifier.Find(TEXT("__pf")); //TODO: check OverrideNativeName in Meta Data, to be sure it's not a random occurrence of the "__pf" string.
bool bIgnoreDeprecatedWord = (NativizedPropertyPostfixIndex != INDEX_NONE) && (NativizedPropertyPostfixIndex > DeprecatedIndex);
if ((DeprecatedIndex != INDEX_NONE) && !bIgnoreDeprecatedWord)
{
if (DeprecatedIndex != Identifier.Len() - 11)
{
Throwf(TEXT("Deprecated variables must end with _DEPRECATED"));
}
// We allow deprecated properties in blueprints that have getters and setters assigned as they may be part of a backwards compatibility path
const bool bBlueprintVisible = (VarProperty.PropertyFlags & CPF_BlueprintVisible) > 0;
const bool bWarnOnGetter = bBlueprintVisible && !VarProperty.MetaData.Contains(NAME_BlueprintGetter);
const bool bWarnOnSetter = bBlueprintVisible && !(VarProperty.PropertyFlags & CPF_BlueprintReadOnly) && !VarProperty.MetaData.Contains(NAME_BlueprintSetter);
if (bWarnOnGetter)
{
LogWarning(TEXT("%s: Deprecated property '%s' should not be marked as blueprint visible without having a BlueprintGetter"), HintText, *FString(Identifier));
}
if (bWarnOnSetter)
{
LogWarning(TEXT("%s: Deprecated property '%s' should not be marked as blueprint writeable without having a BlueprintSetter"), HintText, *FString(Identifier));
}
// Warn if a deprecated property is visible
if (VarProperty.PropertyFlags & (CPF_Edit | CPF_EditConst) || // Property is marked as editable
(!bBlueprintVisible && (VarProperty.PropertyFlags & CPF_BlueprintReadOnly) && !(VarProperty.ImpliedPropertyFlags & CPF_BlueprintReadOnly)) ) // Is BPRO, but not via Implied Flags and not caught by Getter/Setter path above
{
LogWarning(TEXT("%s: Deprecated property '%s' should not be marked as visible or editable"), HintText, *FString(Identifier));
}
VarProperty.PropertyFlags |= CPF_Deprecated;
Identifier.MidInline(0, DeprecatedIndex);
}
}
// Make sure it doesn't conflict.
FString VarName(Identifier);
int32 OuterContextCount = 0;
FUnrealFunctionDefinitionInfo* ExistingFunctionDef = FindFunction(ParentStruct, *VarName, true, nullptr);
FUnrealPropertyDefinitionInfo* ExistingPropertyDef = FindProperty(ParentStruct, *VarName, true);
if (ExistingFunctionDef != nullptr || ExistingPropertyDef != nullptr)
{
bool bErrorDueToShadowing = true;
if (ExistingFunctionDef && (VariableCategory != EVariableCategory::Member))
{
// A function parameter with the same name as a method is allowed
bErrorDueToShadowing = false;
}
//@TODO: This exception does not seem sound either, but there is enough existing code that it will need to be
// fixed up first before the exception it is removed.
if (ExistingPropertyDef)
{
const bool bExistingPropDeprecated = ExistingPropertyDef->HasAnyPropertyFlags(CPF_Deprecated);
const bool bNewPropDeprecated = (VariableCategory == EVariableCategory::Member) && ((VarProperty.PropertyFlags & CPF_Deprecated) != 0);
if (bNewPropDeprecated || bExistingPropDeprecated)
{
// if this is a property and one of them is deprecated, ignore it since it will be removed soon
bErrorDueToShadowing = false;
}
}
if (bErrorDueToShadowing)
{
Throwf(TEXT("%s: '%s' cannot be defined in '%s' as it is already defined in scope '%s' (shadowing is not allowed)"),
HintText,
*FString(Identifier),
*ParentStruct.GetName(),
ExistingFunctionDef ? *ExistingFunctionDef->GetOuter()->GetName() : *ExistingPropertyDef->GetFullName());
}
}
// Get optional dimension immediately after name.
FTokenString Dimensions;
if ((LayoutMacroType == ELayoutMacroType::None && MatchSymbol(TEXT('['))) || LayoutMacroType == ELayoutMacroType::Array || LayoutMacroType == ELayoutMacroType::ArrayEditorOnly)
{
switch (VariableCategory)
{
case EVariableCategory::Return:
{
Throwf(TEXT("Arrays aren't allowed as return types"));
}
case EVariableCategory::RegularParameter:
case EVariableCategory::ReplicatedParameter:
{
Throwf(TEXT("Arrays aren't allowed as function parameters"));
}
}
if (VarProperty.IsContainer())
{
Throwf(TEXT("Static arrays of containers are not allowed"));
}
if (VarProperty.IsBool())
{
Throwf(TEXT("Bool arrays are not allowed") );
}
if (LayoutMacroType == ELayoutMacroType::None)
{
// Ignore how the actual array dimensions are actually defined - we'll calculate those with the compiler anyway.
if (!GetRawString(Dimensions, TEXT(']')))
{
Throwf(TEXT("%s %s: Missing ']'"), HintText, *FString(Identifier));
}
}
else
{
// Ignore how the actual array dimensions are actually defined - we'll calculate those with the compiler anyway.
if (!GetRawString(Dimensions, TEXT(')')))
{
Throwf(TEXT("%s %s: Missing ']'"), HintText, *FString(Identifier));
}
}
// Only static arrays are declared with []. Dynamic arrays use TArray<> instead.
VarProperty.ArrayType = EArrayType::Static;
if (LayoutMacroType == ELayoutMacroType::None)
{
MatchSymbol(TEXT(']'));
}
}
// Try gathering metadata for member fields
if (VariableCategory == EVariableCategory::Member)
{
ParseFieldMetaData(VarProperty.MetaData, *VarName);
AddFormattedPrevCommentAsTooltipMetaData(VarProperty.MetaData);
}
// validate UFunction parameters
else
{
// UFunctions with a smart pointer as input parameter wont compile anyway, because of missing P_GET_... macro.
// UFunctions with a smart pointer as return type will crash when called via blueprint, because they are not supported in VM.
// WeakPointer is supported by VM as return type (see UObject::execLetWeakObjPtr), but there is no P_GET_... macro for WeakPointer.
if (VarProperty.Type == CPT_LazyObjectReference)
{
Throwf(TEXT("UFunctions cannot take a lazy pointer as a parameter."));
}
else if (VarProperty.Type == CPT_ObjectPtrReference)
{
// @TODO: OBJPTR: Investigate TObjectPtr support for UFunction parameters.
Throwf(TEXT("UFunctions cannot take a TObjectPtr as a parameter."));
}
}
// Create the property
FName PropertyName(Identifier);
TSharedRef<FUnrealPropertyDefinitionInfo> PropDefRef = FPropertyTraits::CreateProperty(VarProperty, ParentStruct, PropertyName, VariableCategory, CurrentAccessSpecifier, Dimensions.String, SourceFile, InputLine, InputPos);
// Add the property to the parent
ParentStruct.AddProperty(PropDefRef);
return *PropDefRef;
}
/*-----------------------------------------------------------------------------
Statement compiler.
-----------------------------------------------------------------------------*/
//
// Compile a declaration in Token. Returns 1 if compiled, 0 if not.
//
bool FHeaderParser::CompileDeclaration(TArray<FUnrealFunctionDefinitionInfo*>& DelegatesToFixup, FToken& Token)
{
EAccessSpecifier AccessSpecifier = ParseAccessProtectionSpecifier(Token);
if (AccessSpecifier)
{
if (!IsAllowedInThisNesting(ENestAllowFlags::VarDecl) && !IsAllowedInThisNesting(ENestAllowFlags::Function))
{
Throwf(TEXT("Access specifier %s not allowed here."), *Token.GetTokenValue());
}
check(TopNest->NestType == ENestType::Class || TopNest->NestType == ENestType::Interface || TopNest->NestType == ENestType::NativeInterface);
CurrentAccessSpecifier = AccessSpecifier;
return true;
}
if (Token.IsIdentifier(TEXT("class"), ESearchCase::CaseSensitive) && (TopNest->NestType == ENestType::GlobalScope))
{
// Make sure the previous class ended with valid nesting.
if (bEncounteredNewStyleClass_UnmatchedBrackets)
{
Throwf(TEXT("Missing } at end of class"));
}
// Start parsing the second class
bEncounteredNewStyleClass_UnmatchedBrackets = true;
CurrentAccessSpecifier = ACCESS_Private;
if (!TryParseIInterfaceClass())
{
bEncounteredNewStyleClass_UnmatchedBrackets = false;
UngetToken(Token);
return SkipDeclaration(Token);
}
return true;
}
if (Token.IsIdentifier(TEXT("GENERATED_IINTERFACE_BODY"), ESearchCase::CaseSensitive) || (Token.IsIdentifier(TEXT("GENERATED_BODY"), ESearchCase::CaseSensitive) && TopNest->NestType == ENestType::NativeInterface))
{
if (TopNest->NestType != ENestType::NativeInterface)
{
Throwf(TEXT("%s must occur inside the native interface definition"), *Token.GetTokenValue());
}
RequireSymbol(TEXT('('), Token.Value);
CompileVersionDeclaration(GetCurrentClassDef());
RequireSymbol(TEXT(')'), Token.Value);
FUnrealClassDefinitionInfo& ClassDef = GetCurrentClassDef();
if (ClassDef.GetParsedInterfaceState() == EParsedInterface::NotAnInterface)
{
FString CurrentClassName = ClassDef.GetName();
Throwf(TEXT("Could not find the associated 'U%s' class while parsing 'I%s' - it could be missing or malformed"), *CurrentClassName, *CurrentClassName);
}
if (ClassDef.GetParsedInterfaceState() == EParsedInterface::ParsedIInterface)
{
FString CurrentClassName = ClassDef.GetName();
Throwf(TEXT("Duplicate IInterface definition found while parsing 'I%s'"), *CurrentClassName);
}
check(ClassDef.GetParsedInterfaceState() == EParsedInterface::ParsedUInterface);
ClassDef.SetParsedInterfaceState(EParsedInterface::ParsedIInterface);
ClassDef.SetGeneratedBodyMacroAccessSpecifier(CurrentAccessSpecifier);
ClassDef.SetInterfaceGeneratedBodyLine(InputLine);
bClassHasGeneratedIInterfaceBody = true;
if (Token.IsIdentifier(TEXT("GENERATED_IINTERFACE_BODY"), ESearchCase::CaseSensitive))
{
CurrentAccessSpecifier = ACCESS_Public;
}
if (Token.IsIdentifier(TEXT("GENERATED_BODY"), ESearchCase::CaseSensitive))
{
GetCurrentClassDef().MarkGeneratedBody();
}
return true;
}
if (Token.IsIdentifier(TEXT("GENERATED_UINTERFACE_BODY"), ESearchCase::CaseSensitive) || (Token.IsIdentifier(TEXT("GENERATED_BODY"), ESearchCase::CaseSensitive) && TopNest->NestType == ENestType::Interface))
{
if (TopNest->NestType != ENestType::Interface)
{
Throwf(TEXT("%s must occur inside the interface definition"), *Token.GetTokenValue());
}
RequireSymbol(TEXT('('), Token.Value);
CompileVersionDeclaration(GetCurrentClassDef());
RequireSymbol(TEXT(')'), Token.Value);
FUnrealClassDefinitionInfo& ClassDef = GetCurrentClassDef();
ClassDef.SetGeneratedBodyMacroAccessSpecifier(CurrentAccessSpecifier);
ClassDef.SetGeneratedBodyLine(InputLine);
bClassHasGeneratedUInterfaceBody = true;
if (Token.IsIdentifier(TEXT("GENERATED_UINTERFACE_BODY"), ESearchCase::CaseSensitive))
{
CurrentAccessSpecifier = ACCESS_Public;
}
return true;
}
if (Token.IsIdentifier(TEXT("GENERATED_UCLASS_BODY"), ESearchCase::CaseSensitive) || (Token.IsIdentifier(TEXT("GENERATED_BODY"), ESearchCase::CaseSensitive) && TopNest->NestType == ENestType::Class))
{
if (TopNest->NestType != ENestType::Class)
{
Throwf(TEXT("%s must occur inside the class definition"), *Token.GetTokenValue());
}
FUnrealClassDefinitionInfo& ClassDef = GetCurrentClassDef();
if (Token.IsIdentifier(TEXT("GENERATED_BODY"), ESearchCase::CaseSensitive))
{
GetCurrentClassDef().MarkGeneratedBody();
ClassDef.SetGeneratedBodyMacroAccessSpecifier(CurrentAccessSpecifier);
}
else
{
CurrentAccessSpecifier = ACCESS_Public;
}
RequireSymbol(TEXT('('), Token.Value);
CompileVersionDeclaration(GetCurrentClassDef());
RequireSymbol(TEXT(')'), Token.Value);
ClassDef.SetGeneratedBodyLine(InputLine);
bClassHasGeneratedBody = true;
return true;
}
if (Token.IsIdentifier(TEXT("UCLASS"), ESearchCase::CaseSensitive))
{
bHaveSeenUClass = true;
bEncounteredNewStyleClass_UnmatchedBrackets = true;
CompileClassDeclaration();
return true;
}
if (Token.IsIdentifier(TEXT("UINTERFACE"), ESearchCase::CaseSensitive))
{
bHaveSeenUClass = true;
bEncounteredNewStyleClass_UnmatchedBrackets = true;
CompileInterfaceDeclaration();
return true;
}
if (Token.IsIdentifier(TEXT("UFUNCTION"), ESearchCase::CaseSensitive))
{
FUnrealFunctionDefinitionInfo& FunctionDef = CompileFunctionDeclaration();
return true;
}
if (Token.IsIdentifier(TEXT("UDELEGATE"), ESearchCase::CaseSensitive))
{
FUnrealFunctionDefinitionInfo& DelegateDef = CompileDelegateDeclaration(Token.Value, EDelegateSpecifierAction::Parse);
DelegatesToFixup.Add(&DelegateDef);
return true;
}
if (IsValidDelegateDeclaration(Token)) // Legacy delegate parsing - it didn't need a UDELEGATE
{
FUnrealFunctionDefinitionInfo& DelegateDef = CompileDelegateDeclaration(Token.Value);
DelegatesToFixup.Add(&DelegateDef);
return true;
}
if (Token.IsIdentifier(TEXT("UPROPERTY"), ESearchCase::CaseSensitive))
{
CheckAllow(TEXT("'Member variable declaration'"), ENestAllowFlags::VarDecl);
check(TopNest->NestType == ENestType::Class);
CompileVariableDeclaration(GetCurrentClassDef());
return true;
}
if (Token.IsIdentifier(TEXT("UENUM"), ESearchCase::CaseSensitive))
{
// Enumeration definition.
CompileEnum();
return true;
}
if (Token.IsIdentifier(TEXT("USTRUCT"), ESearchCase::CaseSensitive))
{
// Struct definition.
CompileStructDeclaration();
return true;
}
if (Token.IsSymbol(TEXT('#')))
{
// Compiler directive.
CompileDirective();
return true;
}
if (bEncounteredNewStyleClass_UnmatchedBrackets && Token.IsSymbol(TEXT('}')))
{
FUnrealClassDefinitionInfo& CurrentClassDef = GetCurrentClassDef();
CurrentClassDef.GetDefinitionRange().End = &Input[InputPos];
MatchSemi();
// Closing brace for class declaration
//@TODO: This is a very loose approximation of what we really need to do
// Instead, the whole statement-consumer loop should be in a nest
bEncounteredNewStyleClass_UnmatchedBrackets = false;
// Pop nesting here to allow other non UClass declarations in the header file.
// Make sure we treat UInterface as a class and not an interface
if (CurrentClassDef.HasAllClassFlags(CLASS_Interface) && &CurrentClassDef != GUInterfaceDef)
{
checkf(TopNest->NestType == ENestType::Interface || TopNest->NestType == ENestType::NativeInterface, TEXT("Unexpected end of interface block."));
PopNest(TopNest->NestType, TEXT("'Interface'"));
PostPopNestInterface(CurrentClassDef);
// Ensure the UINTERFACE classes have a GENERATED_BODY declaration
if (bHaveSeenUClass && !bClassHasGeneratedUInterfaceBody)
{
Throwf(TEXT("Expected a GENERATED_BODY() at the start of class"));
}
// Ensure the non-UINTERFACE interface classes have a GENERATED_BODY declaration
if (!bHaveSeenUClass && !bClassHasGeneratedIInterfaceBody)
{
Throwf(TEXT("Expected a GENERATED_BODY() at the start of class"));
}
}
else
{
if (&CurrentClassDef == GUInterfaceDef && TopNest->NestType == ENestType::NativeInterface)
{
PopNest(TopNest->NestType, TEXT("'Interface'"));
}
else
{
PopNest(ENestType::Class, TEXT("'Class'"));
}
PostPopNestClass(CurrentClassDef);
// Ensure classes have a GENERATED_BODY declaration
if (bHaveSeenUClass && !bClassHasGeneratedBody)
{
Throwf(TEXT("Expected a GENERATED_BODY() at the start of class"));
}
}
bHaveSeenUClass = false;
bClassHasGeneratedBody = false;
bClassHasGeneratedUInterfaceBody = false;
bClassHasGeneratedIInterfaceBody = false;
GetCurrentScope()->AddType(CurrentClassDef);
return true;
}
if (Token.IsSymbol(TEXT(';')))
{
if (GetToken(Token))
{
Throwf(TEXT("Extra ';' before '%s'"), *Token.GetTokenValue());
}
else
{
Throwf(TEXT("Extra ';' before end of file"));
}
}
// Skip anything that looks like a macro followed by no bracket that we don't know about
if (ProbablyAnUnknownObjectLikeMacro(*this, Token))
{
return true;
}
FRecordTokens RecordTokens(*this, bEncounteredNewStyleClass_UnmatchedBrackets && IsInAClass() ? &GetCurrentClassDef() : nullptr, &Token);
bool Result = SkipDeclaration(Token);
if (RecordTokens.Stop())
{
FUnrealStructDefinitionInfo& StructDef = GetCurrentClassDef();
const FDeclaration& Declaration = StructDef.GetDeclarations().Last();
if (CheckForConstructor(StructDef, Declaration))
{
}
else if (TopNest->NestType == ENestType::Class)
{
if (CheckForSerialize(StructDef, Declaration))
{
}
else if (CheckForPropertySetterFunction(StructDef, Declaration))
{
}
else if (CheckForPropertyGetterFunction(StructDef, Declaration))
{
}
}
}
return Result;
}
bool FHeaderParser::CheckForConstructor(FUnrealStructDefinitionInfo& StructDef, const FDeclaration& Declaration)
{
FTokenReplay Tokens(Declaration.Tokens);
FUnrealClassDefinitionInfo& ClassDef = StructDef.AsClassChecked();
FToken Token;
if (!Tokens.GetToken(Token))
{
return false;
}
// Allow explicit constructors
bool bFoundExplicit = Token.IsIdentifier(TEXT("explicit"), ESearchCase::CaseSensitive);
if (bFoundExplicit)
{
Tokens.GetToken(Token);
}
bool bSkippedAPIToken = false;
if (Token.Value.EndsWith(TEXT("_API"), ESearchCase::CaseSensitive))
{
if (!bFoundExplicit)
{
// Explicit can come before or after an _API
Tokens.MatchIdentifier(TEXT("explicit"), ESearchCase::CaseSensitive);
}
Tokens.GetToken(Token);
bSkippedAPIToken = true;
}
if (!Token.IsIdentifier(*ClassDef.GetAlternateNameCPP(), ESearchCase::IgnoreCase))
{
return false;
}
Tokens.GetToken(Token);
if (!Token.IsSymbol(TEXT('(')))
{
return false;
}
bool bOICtor = false;
bool bVTCtor = false;
if (!ClassDef.IsDefaultConstructorDeclared() && Tokens.MatchSymbol(TEXT(')')))
{
ClassDef.MarkDefaultConstructorDeclared();
}
else if (!ClassDef.IsObjectInitializerConstructorDeclared()
|| !ClassDef.IsCustomVTableHelperConstructorDeclared())
{
bool bIsConst = false;
bool bIsRef = false;
int32 ParenthesesNestingLevel = 1;
while (ParenthesesNestingLevel && Tokens.GetToken(Token))
{
// Template instantiation or additional parameter excludes ObjectInitializer constructor.
if (Token.IsSymbol(TEXT(',')) || Token.IsSymbol(TEXT('<')))
{
bOICtor = false;
bVTCtor = false;
break;
}
if (Token.IsSymbol(TEXT('(')))
{
ParenthesesNestingLevel++;
continue;
}
if (Token.IsSymbol(TEXT(')')))
{
ParenthesesNestingLevel--;
continue;
}
if (Token.IsIdentifier(TEXT("const"), ESearchCase::CaseSensitive))
{
bIsConst = true;
continue;
}
if (Token.IsSymbol(TEXT('&')))
{
bIsRef = true;
continue;
}
if (Token.IsIdentifier(TEXT("FObjectInitializer"), ESearchCase::CaseSensitive)
|| Token.IsIdentifier(TEXT("FPostConstructInitializeProperties"), ESearchCase::CaseSensitive) // Deprecated, but left here, so it won't break legacy code.
)
{
bOICtor = true;
}
if (Token.IsIdentifier(TEXT("FVTableHelper"), ESearchCase::CaseSensitive))
{
bVTCtor = true;
}
}
// Parse until finish.
while (ParenthesesNestingLevel && Tokens.GetToken(Token))
{
if (Token.IsSymbol(TEXT('(')))
{
ParenthesesNestingLevel++;
continue;
}
if (Token.IsSymbol(TEXT(')')))
{
ParenthesesNestingLevel--;
continue;
}
}
if (bOICtor && bIsRef && bIsConst)
{
ClassDef.MarkObjectInitializerConstructorDeclared();
}
if (bVTCtor && bIsRef)
{
ClassDef.MarkCustomVTableHelperConstructorDeclared();
}
}
if (!bVTCtor)
{
ClassDef.MarkConstructorDeclared();
}
return false;
}
bool FHeaderParser::CheckForSerialize(FUnrealStructDefinitionInfo& StructDef, const FDeclaration& Declaration)
{
FTokenReplay Tokens(Declaration.Tokens);
FUnrealClassDefinitionInfo& ClassDef = StructDef.AsClassChecked();
FToken Token;
if (!Tokens.GetToken(Token))
{
return false;
}
while (Token.IsIdentifier(TEXT("virtual"), ESearchCase::CaseSensitive) || Token.Value.EndsWith(TEXT("_API"), ESearchCase::CaseSensitive))
{
Tokens.GetToken(Token);
}
if (!Token.IsIdentifier(TEXT("void"), ESearchCase::CaseSensitive))
{
return false;
}
Tokens.GetToken(Token);
if (!Token.IsIdentifier(TEXT("Serialize"), ESearchCase::CaseSensitive))
{
return false;
}
Tokens.GetToken(Token);
if (!Token.IsSymbol(TEXT('(')))
{
return false;
}
Tokens.GetToken(Token);
ESerializerArchiveType ArchiveType = ESerializerArchiveType::None;
if (Token.IsIdentifier(TEXT("FArchive"), ESearchCase::CaseSensitive))
{
Tokens.GetToken(Token);
if (Token.IsSymbol(TEXT('&')))
{
Tokens.GetToken(Token);
// Allow the declaration to not define a name for the archive parameter
if (!Token.IsSymbol(TEXT(')')))
{
Tokens.GetToken(Token);
}
if (Token.IsSymbol(TEXT(')')))
{
ArchiveType = ESerializerArchiveType::Archive;
}
}
}
else if (Token.IsIdentifier(TEXT("FStructuredArchive"), ESearchCase::CaseSensitive))
{
Tokens.GetToken(Token);
if (Token.IsSymbol(TEXT("::"), ESearchCase::CaseSensitive))
{
Tokens.GetToken(Token);
if (Token.IsIdentifier(TEXT("FRecord"), ESearchCase::CaseSensitive))
{
Tokens.GetToken(Token);
// Allow the declaration to not define a name for the slot parameter
if (!Token.IsSymbol(TEXT(')')))
{
Tokens.GetToken(Token);
}
if (Token.IsSymbol(TEXT(')')))
{
ArchiveType = ESerializerArchiveType::StructuredArchiveRecord;
}
}
}
}
else if (Token.IsIdentifier(TEXT("FStructuredArchiveRecord"), ESearchCase::CaseSensitive))
{
Tokens.GetToken(Token);
// Allow the declaration to not define a name for the slot parameter
if (!Token.IsSymbol(TEXT(')')))
{
Tokens.GetToken(Token);
}
if (Token.IsSymbol(TEXT(')')))
{
ArchiveType = ESerializerArchiveType::StructuredArchiveRecord;
}
}
if (ArchiveType != ESerializerArchiveType::None)
{
// Found what we want!
if (Declaration.CurrentCompilerDirective == 0 || Declaration.CurrentCompilerDirective == ECompilerDirective::WithEditorOnlyData)
{
FString EnclosingDefine = Declaration.CurrentCompilerDirective != 0 ? TEXT("WITH_EDITORONLY_DATA") : TEXT("");
ClassDef.AddArchiveType(ArchiveType);
ClassDef.SetEnclosingDefine(MoveTemp(EnclosingDefine));
}
else
{
FUHTMessage(StructDef.GetUnrealSourceFile(), Token.InputLine).Throwf(TEXT("Serialize functions must not be inside preprocessor blocks, except for WITH_EDITORONLY_DATA"));
}
return true;
}
return false;
}
static const TArray<FStringView> GSkipDeclarationWarningStrings =
{
TEXT("GENERATED_BODY"),
TEXT("GENERATED_IINTERFACE_BODY"),
TEXT("GENERATED_UCLASS_BODY"),
TEXT("GENERATED_UINTERFACE_BODY"),
TEXT("GENERATED_USTRUCT_BODY"),
// Leaving these disabled ATM since they can exist in the code without causing compile issues
//TEXT("RIGVM_METHOD"),
//TEXT("UCLASS"),
//TEXT("UDELEGATE"),
//TEXT("UENUM"),
//TEXT("UFUNCTION"),
//TEXT("UINTERFACE"),
//TEXT("UPROPERTY"),
//TEXT("USTRUCT"),
};
static bool ParseAccessorType(FString& OutAccessorType, FTokenReplay& Tokens)
{
FToken Token;
Tokens.GetToken(Token);
if (!Token.IsIdentifier())
{
return false;
}
if (Token.IsIdentifier(TEXT("const"), ESearchCase::CaseSensitive))
{
// skip const. We allow const in paramater and return values even if the property is not const
Tokens.GetToken(Token);
if (!Token.IsIdentifier())
{
return false;
}
}
OutAccessorType = Token.Value;
Tokens.GetToken(Token);
if (Token.IsSymbol(TEXT("<"), ESearchCase::CaseSensitive))
{
// template type - add everything until the matching '>' into the type string
OutAccessorType += Token.Value;
int32 TemplateNestCount = 1;
while (TemplateNestCount > 0)
{
Tokens.GetToken(Token);
if (Token.TokenType == ETokenType::None)
{
return false;
}
OutAccessorType += Token.Value;
if (Token.IsSymbol(TEXT("<"), ESearchCase::CaseSensitive))
{
TemplateNestCount++;
}
else if (Token.IsSymbol(TEXT(">"), ESearchCase::CaseSensitive))
{
TemplateNestCount--;
}
}
Tokens.GetToken(Token);
}
// Skip '&' and add any '*'
for (; !Token.IsIdentifier(); Tokens.GetToken(Token))
{
if (Token.TokenType == ETokenType::None)
{
return false;
}
// Support for passing values by ref for setters
if (!Token.IsSymbol(TEXT("&"), ESearchCase::CaseSensitive))
{
OutAccessorType += Token.Value;
}
}
// Return the last token since it's either ')' in case of setters or the getter name which will be parsed by the caller
Tokens.UngetToken(Token);
return true;
}
bool FHeaderParser::CheckForPropertySetterFunction(FUnrealStructDefinitionInfo& StructDef, const FDeclaration& Declaration)
{
FTokenReplay Tokens(Declaration.Tokens);
FUnrealClassDefinitionInfo& ClassDef = StructDef.AsClassChecked();
FToken Token;
if (!Tokens.GetToken(Token))
{
return false;
}
// Skip virtual keyword or any API macros
while (Token.IsIdentifier(TEXT("virtual"), ESearchCase::CaseSensitive) || Token.Value.EndsWith(TEXT("_API"), ESearchCase::CaseSensitive))
{
Tokens.GetToken(Token);
}
// Setters can only return void
if (!Token.IsIdentifier(TEXT("void"), ESearchCase::CaseSensitive))
{
return false;
}
FToken SetterNameToken;
Tokens.GetToken(SetterNameToken);
if (!SetterNameToken.IsIdentifier())
{
return false;
}
if (SetterNameToken.Value.Compare(TEXT("SetHardwareCursors"), ESearchCase::CaseSensitive) == 0)
{
static volatile int32 xx = 0;
xx++;
}
FUnrealPropertyDefinitionInfo* PropertyWithSetter = nullptr;
FString SetterFunctionName;
TArray<TSharedRef<FUnrealPropertyDefinitionInfo>>& Properties = ClassDef.GetProperties();
bool bExplicitSetter = false;
// First check if there are any properties that already specify this function name as a setter
for (TSharedRef<FUnrealPropertyDefinitionInfo>& Prop : Properties)
{
FPropertyBase& BaseProp = Prop->GetPropertyBase();
if (!BaseProp.bSetterFunctionFound && !BaseProp.SetterName.IsEmpty() && BaseProp.SetterName != TEXT("None"))
{
if (SetterNameToken.Value.Compare(Prop->GetPropertyBase().SetterName, ESearchCase::CaseSensitive) == 0)
{
SetterFunctionName = Prop->GetPropertyBase().SetterName;
PropertyWithSetter = &Prop.Get();
bExplicitSetter = true;
break;
}
}
}
// If not, try with autogenerated setter names
if (!PropertyWithSetter && SetterNameToken.ValueStartsWith(TEXT("Set"), ESearchCase::CaseSensitive))
{
for (TSharedRef<FUnrealPropertyDefinitionInfo>& Prop : Properties)
{
FPropertyBase& BaseProp = Prop->GetPropertyBase();
if (!BaseProp.bSetterFunctionFound && BaseProp.SetterName.IsEmpty() &&
(BaseProp.bSetterTagFound || FUHTConfig::Get().bAllowAutomaticSettersAndGetters))
{
SetterFunctionName.Reset();
SetterFunctionName += TEXT("Set");
SetterFunctionName += Prop->GetName();
if (SetterNameToken.Value.Compare(SetterFunctionName, ESearchCase::CaseSensitive) == 0)
{
// We found a matching function name so we can move onto verifying if the declaration matches
bExplicitSetter = BaseProp.bSetterTagFound;
PropertyWithSetter = &Prop.Get();
break;
}
}
}
}
if (!PropertyWithSetter)
{
return false;
}
Tokens.GetToken(Token);
if (!Token.IsSymbol(TEXT('(')))
{
if (bExplicitSetter)
{
LogError(TEXT("Expected '(' when parsing property %s setter function %s"), *PropertyWithSetter->GetName(), *SetterFunctionName);
}
return false;
}
FString ParameterType;
if (!ParseAccessorType(ParameterType, Tokens))
{
if (bExplicitSetter)
{
LogError(TEXT("Error when parsing parameter type for property %s setter function %s"), *PropertyWithSetter->GetName(), *SetterFunctionName);
}
return false;
}
// Skip parameter name
Tokens.GetToken(Token);
if (!Token.IsIdentifier())
{
if (bExplicitSetter)
{
LogError(TEXT("Expected parameter name when parsing property %s setter function %s"), *PropertyWithSetter->GetName(), *SetterFunctionName);
}
return false;
}
// Setter has only one parameter
Tokens.GetToken(Token);
if (!Token.IsSymbol(TEXT(')')))
{
if (bExplicitSetter)
{
LogError(TEXT("Expected ')' when parsing property %s setter function %s"), *PropertyWithSetter->GetName(), *SetterFunctionName);
}
return false;
}
// Parameter type and property type must match
FString ExtendedPropertyType;
FString PropertyType = PropertyWithSetter->GetCPPType(&ExtendedPropertyType, CPPF_Implementation | CPPF_ArgumentOrReturnValue | CPPF_NoRef);
ExtendedPropertyType.RemoveSpacesInline();
PropertyType += ExtendedPropertyType;
if (ParameterType.Compare(PropertyType, ESearchCase::CaseSensitive) != 0)
{
if (bExplicitSetter)
{
LogError(TEXT("Paramater type %s unsupported for property %s setter function %s (expected: %s)"),
*ParameterType, *PropertyWithSetter->GetName(), *SetterFunctionName, *PropertyType);
}
return false;
}
if ((GetCurrentCompilerDirective() & ECompilerDirective::WithEditor) != 0)
{
if (bExplicitSetter)
{
LogError(TEXT("Property %s setter function %s cannot be declared within WITH_EDITOR block. Use WITH_EDITORONLY_DATA instead."),
*PropertyWithSetter->GetName(), *SetterFunctionName);
}
return false;
}
if ((GetCurrentCompilerDirective() & ECompilerDirective::WithEditorOnlyData) != 0 && (PropertyWithSetter->GetPropertyFlags() & CPF_EditorOnly) == 0)
{
if (bExplicitSetter)
{
LogError(TEXT("Property %s is not editor-only but its setter function %s is."),
*PropertyWithSetter->GetName(), *SetterFunctionName);
}
return false;
}
PropertyWithSetter->GetPropertyBase().SetterName = *SetterFunctionName;
PropertyWithSetter->GetPropertyBase().bSetterFunctionFound = true;
return true;
}
bool FHeaderParser::CheckForPropertyGetterFunction(FUnrealStructDefinitionInfo& StructDef, const FDeclaration& Declaration)
{
FTokenReplay Tokens(Declaration.Tokens);
FUnrealClassDefinitionInfo& ClassDef = StructDef.AsClassChecked();
FToken Token;
if (!Tokens.GetToken(Token))
{
return false;
}
// Skip virtual keyword and any API macros
while (Token.IsIdentifier(TEXT("virtual"), ESearchCase::CaseSensitive) || Token.Value.EndsWith(TEXT("_API"), ESearchCase::CaseSensitive))
{
Tokens.GetToken(Token);
}
Tokens.UngetToken(Token);
FString GetterType;
if (!ParseAccessorType(GetterType, Tokens))
{
// Unable to print a meaningful error before parsing the Getter name
return false;
}
FToken GetterNameToken;
Tokens.GetToken(GetterNameToken);
if (!GetterNameToken.IsIdentifier())
{
// Unable to print a meaningful error before parsing the Getter name
return false;
}
FUnrealPropertyDefinitionInfo* PropertyWithGetter = nullptr;
TArray<TSharedRef<FUnrealPropertyDefinitionInfo>>& Properties = ClassDef.GetProperties();
FString GetterFunctionName;
bool bExplicitGetter = false;
// First check if there are any properties that already specify this function name as a getter
for (TSharedRef<FUnrealPropertyDefinitionInfo>& Prop : Properties)
{
FPropertyBase& BaseProp = Prop->GetPropertyBase();
if (!BaseProp.bGetterFunctionFound && !BaseProp.GetterName.IsEmpty() && BaseProp.GetterName != TEXT("None"))
{
if (GetterNameToken.Value.Compare(Prop->GetPropertyBase().GetterName, ESearchCase::CaseSensitive) == 0)
{
GetterFunctionName = Prop->GetPropertyBase().GetterName;
PropertyWithGetter = &Prop.Get();
bExplicitGetter = true;
break;
}
}
}
// If not, try with autogenerated getter names
if (!PropertyWithGetter && GetterNameToken.ValueStartsWith(TEXT("Get"), ESearchCase::CaseSensitive))
{
for (TSharedRef<FUnrealPropertyDefinitionInfo>& Prop : Properties)
{
FPropertyBase& BaseProp = Prop->GetPropertyBase();
if (!BaseProp.bGetterFunctionFound && BaseProp.GetterName != TEXT("None") &&
(BaseProp.bGetterTagFound || FUHTConfig::Get().bAllowAutomaticSettersAndGetters))
{
GetterFunctionName.Reset();
GetterFunctionName += TEXT("Get");
GetterFunctionName += Prop->GetName();
if (GetterNameToken.Value.Compare(GetterFunctionName, ESearchCase::CaseSensitive) == 0)
{
bExplicitGetter = BaseProp.bGetterTagFound;
PropertyWithGetter = &Prop.Get();
break;
}
}
}
}
if (!PropertyWithGetter)
{
return false;
}
FString ExtendedPropertyType;
FString PropertyType = PropertyWithGetter->GetCPPType(&ExtendedPropertyType, CPPF_Implementation | CPPF_ArgumentOrReturnValue);
PropertyType += ExtendedPropertyType;
if (GetterType.Compare(PropertyType, ESearchCase::CaseSensitive) != 0)
{
if (bExplicitGetter)
{
LogError(TEXT("Paramater type %s unsupported for property %s getter function %s (expected: %s)"),
*GetterType, *PropertyWithGetter->GetName(), *GetterFunctionName, *PropertyType);
}
return false;
}
// Getter is a function that takes no arguments
Tokens.GetToken(Token);
if (!Token.IsSymbol(TEXT('(')))
{
if (bExplicitGetter)
{
LogError(TEXT("Expected '(' when parsing property %s getter function %s"), *PropertyWithGetter->GetName(), *GetterFunctionName);
}
return false;
}
Tokens.GetToken(Token);
if (!Token.IsSymbol(TEXT(')')))
{
if (bExplicitGetter)
{
LogError(TEXT("Expected ')' when parsing property %s getter function %s"), *PropertyWithGetter->GetName(), *GetterFunctionName);
}
return false;
}
// Getters should be const functions
Tokens.GetToken(Token);
if (!Token.IsIdentifier(TEXT("const"), ESearchCase::CaseSensitive))
{
if (bExplicitGetter)
{
LogError(TEXT("Property %s getter function %s must be const"), *PropertyWithGetter->GetName(), *GetterFunctionName);
}
return false;
}
if ((GetCurrentCompilerDirective() & ECompilerDirective::WithEditor) != 0)
{
if (bExplicitGetter)
{
LogError(TEXT("Property %s setter function %s cannot be declared within WITH_EDITOR block. Use WITH_EDITORONLY_DATA instead."),
*PropertyWithGetter->GetName(), *GetterFunctionName);
}
return false;
}
if ((GetCurrentCompilerDirective() & ECompilerDirective::WithEditorOnlyData) != 0 && (PropertyWithGetter->GetPropertyFlags() & CPF_EditorOnly) == 0)
{
if (bExplicitGetter)
{
LogError(TEXT("Property %s is not editor-only but its getter function %s is."),
*PropertyWithGetter->GetName(), *GetterFunctionName);
}
return false;
}
PropertyWithGetter->GetPropertyBase().GetterName = GetterFunctionName;
PropertyWithGetter->GetPropertyBase().bGetterFunctionFound = true;
return true;
}
bool FHeaderParser::SkipDeclaration(FToken& Token)
{
// Store the current value of PrevComment so it can be restored after we parsed everything.
FString OldPrevComment(PrevComment);
// Consume all tokens until the end of declaration/definition has been found.
int32 NestedScopes = 0;
// Check if this is a class/struct declaration in which case it can be followed by member variable declaration.
bool bPossiblyClassDeclaration = Token.IsIdentifier(TEXT("class"), ESearchCase::CaseSensitive) || Token.IsIdentifier(TEXT("struct"), ESearchCase::CaseSensitive);
// (known) macros can end without ; or } so use () to find the end of the declaration.
// However, we don't want to use it with DECLARE_FUNCTION, because we need it to be treated like a function.
bool bMacroDeclaration = ProbablyAMacro(Token.Value) && !Token.IsIdentifier(TEXT("DECLARE_FUNCTION"), ESearchCase::CaseSensitive);
bool bEndOfDeclarationFound = false;
bool bDefinitionFound = false;
TCHAR OpeningBracket = bMacroDeclaration ? TEXT('(') : TEXT('{');
TCHAR ClosingBracket = bMacroDeclaration ? TEXT(')') : TEXT('}');
bool bRetestCurrentToken = false;
while (bRetestCurrentToken || GetToken(Token))
{
// If we find parentheses at top-level and we think it's a class declaration then it's more likely
// to be something like: class UThing* GetThing();
if (bPossiblyClassDeclaration && NestedScopes == 0 && Token.IsSymbol(TEXT('(')))
{
bPossiblyClassDeclaration = false;
}
bRetestCurrentToken = false;
if (Token.IsSymbol(TEXT(';')) && NestedScopes == 0)
{
bEndOfDeclarationFound = true;
break;
}
if (Token.IsIdentifier())
{
// Use a trivial prefilter to avoid doing the search on things that aren't UE keywords we care about
if (Token.Value[0] == 'G' || Token.Value[0] == 'R' || Token.Value[0] == 'U')
{
if (Algo::BinarySearch(GSkipDeclarationWarningStrings, Token.Value,
[](const FStringView& Lhs, const FStringView& Rhs) { return Lhs.Compare(Rhs, ESearchCase::CaseSensitive) < 0; }) >= 0)
{
LogWarning(FString::Printf(TEXT("The identifier \'%s\' was detected in a block being skipped. Was this intentional?"), *FString(Token.Value)));
}
}
}
if (!bMacroDeclaration && Token.IsIdentifier(TEXT("PURE_VIRTUAL"), ESearchCase::CaseSensitive) && NestedScopes == 0)
{
OpeningBracket = TEXT('(');
ClosingBracket = TEXT(')');
}
if (Token.IsSymbol(OpeningBracket))
{
// This is a function definition or class declaration.
bDefinitionFound = true;
NestedScopes++;
}
else if (Token.IsSymbol(ClosingBracket))
{
NestedScopes--;
if (NestedScopes == 0)
{
// Could be a class declaration in all capitals, and not a macro
bool bReallyEndDeclaration = true;
if (bMacroDeclaration)
{
FToken PossibleBracketToken;
GetToken(PossibleBracketToken);
UngetToken(Token);
GetToken(Token);
// If Strcmp returns 0, it is probably a class, else a macro.
bReallyEndDeclaration = !PossibleBracketToken.IsSymbol(TEXT('{'));
}
if (bReallyEndDeclaration)
{
bEndOfDeclarationFound = true;
break;
}
}
if (NestedScopes < 0)
{
Throwf(TEXT("Unexpected '}'. Did you miss a semi-colon?"));
}
}
else if (bMacroDeclaration && NestedScopes == 0)
{
bMacroDeclaration = false;
OpeningBracket = TEXT('{');
ClosingBracket = TEXT('}');
bRetestCurrentToken = true;
}
}
if (bEndOfDeclarationFound)
{
// Member variable declaration after class declaration (see bPossiblyClassDeclaration).
if (bPossiblyClassDeclaration && bDefinitionFound)
{
// Should syntax errors be also handled when someone declares a variable after function definition?
// Consume the variable name.
FToken VariableName;
if( !GetToken(VariableName, true) )
{
return false;
}
if (!VariableName.IsIdentifier())
{
// Not a variable name.
UngetToken(VariableName);
}
else if (!SafeMatchSymbol(TEXT(';')))
{
Throwf(TEXT("Unexpected '%s'. Did you miss a semi-colon?"), *VariableName.GetTokenValue());
}
}
// C++ allows any number of ';' after member declaration/definition.
while (SafeMatchSymbol(TEXT(';')));
}
PrevComment = OldPrevComment;
// clear the current value for comment
//ClearComment();
// Successfully consumed C++ declaration unless mismatched pair of brackets has been found.
return NestedScopes == 0 && bEndOfDeclarationFound;
}
bool FHeaderParser::SafeMatchSymbol( const TCHAR Match )
{
// Remember the position before the next token (this can include comments before the next symbol).
FScriptLocation LocationBeforeNextSymbol;
InitScriptLocation(LocationBeforeNextSymbol);
FToken Token;
if (GetToken(Token, /*bNoConsts=*/ true))
{
if (Token.IsSymbol(Match))
{
return true;
}
UngetToken(Token);
}
// Return to the stored position.
ReturnToLocation(LocationBeforeNextSymbol);
return false;
}
FUnrealClassDefinitionInfo& FHeaderParser::ParseClassNameDeclaration(FString& DeclaredClassName, FString& RequiredAPIMacroIfPresent)
{
ParseNameWithPotentialAPIMacroPrefix(/*out*/ DeclaredClassName, /*out*/ RequiredAPIMacroIfPresent, TEXT("class"));
FUnrealClassDefinitionInfo* ClassDef = FUnrealClassDefinitionInfo::FindClass(*GetClassNameWithPrefixRemoved(*DeclaredClassName));
check(ClassDef);
ClassDef->SetClassCastFlags(ClassCastFlagMap::Get().GetCastFlag(DeclaredClassName));
ClassDef->SetClassCastFlags(ClassCastFlagMap::Get().GetCastFlag(FString(TEXT("F")) + DeclaredClassName.RightChop(1))); // For properties, check alternate name
// Skip optional final keyword
MatchIdentifier(TEXT("final"), ESearchCase::CaseSensitive);
// Parse the inheritance list
ParseInheritance(TEXT("class"), [](const TCHAR* ClassName, bool bIsSuperClass) {}); // Eat the results, already been parsed
// Collect data about the super and base classes
if (FUnrealClassDefinitionInfo* SuperClassDef = UHTCast<FUnrealClassDefinitionInfo>(ClassDef->GetSuperStructInfo().Struct))
{
ClassDef->SetClassCastFlags(SuperClassDef->GetClassCastFlags());
}
// Add the class flags from the interfaces
for (FUnrealStructDefinitionInfo::FBaseStructInfo& BaseClassInfo : ClassDef->GetBaseStructInfos())
{
if (FUnrealClassDefinitionInfo* BaseClassDef = UHTCast<FUnrealClassDefinitionInfo>(BaseClassInfo.Struct))
{
if (!BaseClassDef->HasAnyClassFlags(CLASS_Interface))
{
Throwf(TEXT("Implements: Class %s is not an interface; Can only inherit from non-UObjects or UInterface derived interfaces"), *BaseClassDef->GetName());
}
// Propagate the inheritable ClassFlags
ClassDef->SetClassFlags(BaseClassDef->GetClassFlags() & ClassDef->GetInheritClassFlags());
}
}
return *ClassDef;
}
/**
* Setups basic class settings after parsing.
*/
void PostParsingClassSetup(FUnrealClassDefinitionInfo& ClassDef)
{
//@TODO: Move to post parse finalization?
// Since this flag is computed in this method, we have to re-propagate the flag from the super
// just in case they were defined in this source file.
if (FUnrealClassDefinitionInfo* SuperClassDef = ClassDef.GetSuperClass())
{
ClassDef.SetClassFlags(SuperClassDef->GetClassFlags() & CLASS_Config);
}
// Set the class config flag if any properties have config
for (TSharedRef<FUnrealPropertyDefinitionInfo> PropertyDef : ClassDef.GetProperties())
{
if (PropertyDef->HasAnyPropertyFlags(CPF_Config))
{
ClassDef.SetClassFlags(CLASS_Config);
break;
}
}
// Class needs to specify which ini file is going to be used if it contains config variables.
if (ClassDef.HasAnyClassFlags(CLASS_Config) && ClassDef.GetClassConfigName() == NAME_None)
{
// Inherit config setting from base class.
ClassDef.SetClassConfigName(ClassDef.GetSuperClass() ? ClassDef.GetSuperClass()->GetClassConfigName() : NAME_None);
if (ClassDef.GetClassConfigName() == NAME_None)
{
ClassDef.Throwf(TEXT("Classes with config / globalconfig member variables need to specify config file."));
ClassDef.SetClassConfigName(NAME_Engine);
}
}
}
/**
* Compiles a class declaration.
*/
FUnrealClassDefinitionInfo& FHeaderParser::CompileClassDeclaration()
{
// Start of a class block.
CheckAllow(TEXT("'class'"), ENestAllowFlags::Class);
// New-style UCLASS() syntax
TMap<FName, FString> MetaData;
TArray<FPropertySpecifier> SpecifiersFound;
ReadSpecifierSetInsideMacro(SpecifiersFound, TEXT("Class"), MetaData);
const int32 PrologFinishLine = InputLine;
// Members of classes have a default private access level in c++
// Setting this directly should be ok as we don't support nested classes, so the outer scope access should not need restoring
CurrentAccessSpecifier = ACCESS_Private;
AddFormattedPrevCommentAsTooltipMetaData(MetaData);
// New style files have the class name / extends afterwards
RequireIdentifier(TEXT("class"), ESearchCase::CaseSensitive, TEXT("Class declaration"));
SkipAlignasAndDeprecatedMacroIfNecessary(*this);
FString DeclaredClassName;
FString RequiredAPIMacroIfPresent;
FUnrealClassDefinitionInfo& ClassDef = ParseClassNameDeclaration(/*out*/ DeclaredClassName, /*out*/ RequiredAPIMacroIfPresent);
ClassDef.GetDefinitionRange().Start = &Input[InputPos];
// If we have existing class flags (preexisting engine objects), make sure we have some shared flags.
check(ClassDef.GetClassFlags() == 0 || (ClassDef.GetClassFlags() & ClassDef.GetParsedClassFlags()) != 0);
ClassDef.MarkParsed();
PushNest(ENestType::Class, &ClassDef);
ResetClassData();
// Make sure our parent classes is parsed.
for (FUnrealClassDefinitionInfo* TempDef = ClassDef.GetSuperClass(); TempDef; TempDef = TempDef->GetSuperClass())
{
bool bIsParsed = TempDef->IsParsed();
bool bIsIntrinsic = TempDef->HasAnyClassFlags(CLASS_Intrinsic);
if (!(bIsParsed || bIsIntrinsic))
{
Throwf(TEXT("'%s' can't be compiled: Parent class '%s' has errors"), *ClassDef.GetName(), *TempDef->GetName());
}
}
// Merge with categories inherited from the parent.
ClassDef.MergeClassCategories();
// Class attributes.
ClassDef.SetPrologLine(PrologFinishLine);
ClassDef.MergeAndValidateClassFlags(DeclaredClassName);
ClassDef.SetInternalFlags(EInternalObjectFlags::Native);
// Class metadata
MetaData.Append(ClassDef.GetParsedMetaData());
ClassDef.MergeCategoryMetaData(MetaData);
AddIncludePathToMetadata(ClassDef, MetaData);
AddModuleRelativePathToMetadata(ClassDef, MetaData);
// Register the metadata
FUHTMetaData::RemapAndAddMetaData(ClassDef, MoveTemp(MetaData));
// Handle the start of the rest of the class
RequireSymbol( TEXT('{'), TEXT("'Class'") );
// Copy properties from parent class.
if (FUnrealClassDefinitionInfo* SuperClassDef = ClassDef.GetSuperClass())
{
ClassDef.SetPropertiesSize(SuperClassDef->GetPropertiesSize());
}
// Validate sparse class data
CheckSparseClassData(ClassDef);
return ClassDef;
}
FUnrealClassDefinitionInfo* FHeaderParser::ParseInterfaceNameDeclaration(FString& DeclaredInterfaceName, FString& RequiredAPIMacroIfPresent)
{
ParseNameWithPotentialAPIMacroPrefix(/*out*/ DeclaredInterfaceName, /*out*/ RequiredAPIMacroIfPresent, TEXT("interface"));
FUnrealClassDefinitionInfo* ClassDef = FUnrealClassDefinitionInfo::FindClass(*GetClassNameWithPrefixRemoved(*DeclaredInterfaceName));
if (ClassDef == nullptr)
{
return nullptr;
}
// Get super interface
bool bSpecifiesParentClass = MatchSymbol(TEXT(':'));
if (!bSpecifiesParentClass)
{
return ClassDef;
}
RequireIdentifier(TEXT("public"), ESearchCase::CaseSensitive, TEXT("class inheritance"));
// verify if our super class is an interface class
// the super class should have been marked as CLASS_Interface at the importing stage, if it were an interface
FUnrealClassDefinitionInfo* TempClassDef = GetQualifiedClass(TEXT("'extends'"));
check(TempClassDef);
if (!TempClassDef->HasAnyClassFlags(CLASS_Interface))
{
// UInterface is special and actually extends from UObject, which isn't an interface
if (DeclaredInterfaceName != TEXT("UInterface"))
{
Throwf(TEXT("Interface class '%s' cannot inherit from non-interface class '%s'"), *DeclaredInterfaceName, *TempClassDef->GetName());
}
}
// The class super should have already been set by now
FUnrealClassDefinitionInfo* SuperClassDef = ClassDef->GetSuperClass();
check(SuperClassDef);
if (SuperClassDef != TempClassDef)
{
Throwf(TEXT("%s's superclass must be %s, not %s"), *ClassDef->GetPathName(), *SuperClassDef->GetPathName(), *TempClassDef->GetPathName());
}
return ClassDef;
}
bool FHeaderParser::TryParseIInterfaceClass()
{
// 'class' was already matched by the caller
// Get a class name
FString DeclaredInterfaceName;
FString RequiredAPIMacroIfPresent;
if (ParseInterfaceNameDeclaration(/*out*/ DeclaredInterfaceName, /*out*/ RequiredAPIMacroIfPresent) == nullptr)
{
return false;
}
if (MatchSymbol(TEXT(';')))
{
// Forward declaration.
return false;
}
if (DeclaredInterfaceName[0] != 'I')
{
return false;
}
FUnrealClassDefinitionInfo* FoundClassDef = nullptr;
if ((FoundClassDef = FUnrealClassDefinitionInfo::FindClass(*DeclaredInterfaceName.Mid(1))) == nullptr)
{
return false;
}
// Continue parsing the second class as if it were a part of the first (for reflection data purposes, it is)
RequireSymbol(TEXT('{'), TEXT("C++ interface mix-in class declaration"));
// Push the interface class nesting again.
PushNest(ENestType::NativeInterface, FoundClassDef);
CurrentAccessSpecifier = ACCESS_Private;
return true;
}
/**
* compiles Java or C# style interface declaration
*/
void FHeaderParser::CompileInterfaceDeclaration()
{
// Start of an interface block. Since Interfaces and Classes are always at the same nesting level,
// whereever a class declaration is allowed, an interface declaration is also allowed.
CheckAllow( TEXT("'interface'"), ENestAllowFlags::Class );
CurrentAccessSpecifier = ACCESS_Private;
FString DeclaredInterfaceName;
FString RequiredAPIMacroIfPresent;
TMap<FName, FString> MetaData;
// Build up a list of interface specifiers
TArray<FPropertySpecifier> SpecifiersFound;
// New-style UINTERFACE() syntax
ReadSpecifierSetInsideMacro(SpecifiersFound, TEXT("Interface"), MetaData);
int32 PrologFinishLine = InputLine;
// New style files have the interface name / extends afterwards
RequireIdentifier(TEXT("class"), ESearchCase::CaseSensitive, TEXT("Interface declaration"));
FUnrealClassDefinitionInfo* InterfaceClassDef = ParseInterfaceNameDeclaration(/*out*/ DeclaredInterfaceName, /*out*/ RequiredAPIMacroIfPresent);
check(InterfaceClassDef);
InterfaceClassDef->GetDefinitionRange().Start = &Input[InputPos];
// Record that this interface is RequiredAPI if the CORE_API style macro was present
if (!RequiredAPIMacroIfPresent.IsEmpty())
{
InterfaceClassDef->SetClassFlags(CLASS_RequiredAPI);
}
// Set the appropriate interface class flags
InterfaceClassDef->SetClassFlags(CLASS_Interface | CLASS_Abstract);
if (FUnrealClassDefinitionInfo* InterfaceSuperClassDef = InterfaceClassDef->GetSuperClass())
{
InterfaceClassDef->SetClassCastFlags(InterfaceSuperClassDef->GetClassCastFlags());
// All classes that are parsed are expected to be native
if (!InterfaceSuperClassDef->HasAnyClassFlags(CLASS_Native))
{
Throwf(TEXT("Native classes cannot extend non-native classes"));
}
InterfaceClassDef->SetClassWithin(InterfaceSuperClassDef->GetClassWithin());
}
else
{
InterfaceClassDef->SetClassWithin(GUObjectDef);
}
InterfaceClassDef->SetInternalFlags(EInternalObjectFlags::Native);
InterfaceClassDef->SetClassFlags(CLASS_Native);
// Process all of the interface specifiers
for (const FPropertySpecifier& Specifier : SpecifiersFound)
{
switch ((EInterfaceSpecifier)Algo::FindSortedStringCaseInsensitive(*Specifier.Key, GInterfaceSpecifierStrings))
{
default:
{
Throwf(TEXT("Unknown interface specifier '%s'"), *Specifier.Key);
}
break;
case EInterfaceSpecifier::DependsOn:
{
Throwf(TEXT("The dependsOn specifier is deprecated. Please use #include \"ClassHeaderFilename.h\" instead."));
}
break;
case EInterfaceSpecifier::MinimalAPI:
{
InterfaceClassDef->SetClassFlags(CLASS_MinimalAPI);
}
break;
case EInterfaceSpecifier::ConversionRoot:
{
MetaData.Add(FHeaderParserNames::NAME_IsConversionRoot, TEXT("true"));
}
break;
}
}
// All classes must start with a valid Unreal prefix
const FString ExpectedInterfaceName = InterfaceClassDef->GetNameWithPrefix(EEnforceInterfacePrefix::U);
if (DeclaredInterfaceName != ExpectedInterfaceName)
{
Throwf(TEXT("Interface name '%s' is invalid, the first class should be identified as '%s'"), *DeclaredInterfaceName, *ExpectedInterfaceName );
}
// Try parsing metadata for the interface
InterfaceClassDef->SetPrologLine(PrologFinishLine);
// Register the metadata
AddModuleRelativePathToMetadata(*InterfaceClassDef, MetaData);
FUHTMetaData::RemapAndAddMetaData(*InterfaceClassDef, MoveTemp(MetaData));
// Handle the start of the rest of the interface
RequireSymbol( TEXT('{'), TEXT("'Class'") );
// Push the interface class nesting.
// we need a more specific set of allow flags for ENestType::Interface, only function declaration is allowed, no other stuff are allowed
PushNest(ENestType::Interface, InterfaceClassDef);
}
void FHeaderParser::CompileRigVMMethodDeclaration(FUnrealStructDefinitionInfo& StructDef)
{
if (!MatchSymbol(TEXT("(")))
{
Throwf(TEXT("Bad RIGVM_METHOD definition"));
}
// find the next close brace
while (!MatchSymbol(TEXT(")")))
{
FToken Token;
if (!GetToken(Token))
{
break;
}
}
FToken PrefixToken, ReturnTypeToken, NameToken, PostfixToken;
if (!GetToken(PrefixToken))
{
return;
}
if (PrefixToken.IsIdentifier(TEXT("virtual"), ESearchCase::CaseSensitive))
{
if (!GetToken(ReturnTypeToken))
{
return;
}
}
else
{
ReturnTypeToken = PrefixToken;
}
if (!GetToken(NameToken))
{
return;
}
if (!MatchSymbol(TEXT("(")))
{
Throwf(TEXT("Bad RIGVM_METHOD definition"));
}
TArray<FString> ParamsContent;
while (!MatchSymbol(TEXT(")")))
{
FToken Token;
if (!GetToken(Token))
{
break;
}
ParamsContent.Add(FString(Token.Value));
}
while (!PostfixToken.IsSymbol(TEXT(';')))
{
if (!GetToken(PostfixToken))
{
return;
}
}
FRigVMMethodInfo MethodInfo;
MethodInfo.ReturnType = FString(ReturnTypeToken.Value);
MethodInfo.Name = FString(NameToken.Value);
FString ParamString = FString::Join(ParamsContent, TEXT(" "));
if (!ParamString.IsEmpty())
{
FString ParamPrev, ParamLeft, ParamRight;
ParamPrev = ParamString;
while (ParamPrev.Contains(TEXT(",")))
{
ParamPrev.Split(TEXT(","), &ParamLeft, &ParamRight);
FRigVMParameter Parameter;
Parameter.Name = ParamLeft.TrimStartAndEnd();
MethodInfo.Parameters.Add(Parameter);
ParamPrev = ParamRight;
}
ParamPrev = ParamPrev.TrimStartAndEnd();
if (!ParamPrev.IsEmpty())
{
FRigVMParameter Parameter;
Parameter.Name = ParamPrev.TrimStartAndEnd();
MethodInfo.Parameters.Add(Parameter);
}
}
for (FRigVMParameter& Parameter : MethodInfo.Parameters)
{
FString FullParameter = Parameter.Name;
int32 LastEqual = INDEX_NONE;
if (FullParameter.FindLastChar(TCHAR('='), LastEqual))
{
FullParameter = FullParameter.Mid(0, LastEqual);
}
FullParameter.TrimStartAndEndInline();
FString ParameterType = FullParameter;
FString ParameterName = FullParameter;
int32 LastSpace = INDEX_NONE;
if (FullParameter.FindLastChar(TCHAR(' '), LastSpace))
{
Parameter.Type = FullParameter.Mid(0, LastSpace);
Parameter.Name = FullParameter.Mid(LastSpace + 1);
Parameter.Type.TrimStartAndEndInline();
Parameter.Name.TrimStartAndEndInline();
}
}
FRigVMStructInfo& StructRigVMInfo = StructDef.GetRigVMInfo();
StructRigVMInfo.bHasRigVM = true;
StructRigVMInfo.Name = StructDef.GetName();
StructRigVMInfo.Methods.Add(MethodInfo);
}
const FName FHeaderParser::NAME_InputText(TEXT("Input"));
const FName FHeaderParser::NAME_OutputText(TEXT("Output"));
const FName FHeaderParser::NAME_ConstantText(TEXT("Constant"));
const FName FHeaderParser::NAME_VisibleText(TEXT("Visible"));
const FName FHeaderParser::NAME_SingletonText(TEXT("Singleton"));
const TCHAR* FHeaderParser::TArrayText = TEXT("TArray");
const TCHAR* FHeaderParser::TEnumAsByteText = TEXT("TEnumAsByte");
const TCHAR* FHeaderParser::GetRefText = TEXT("GetRef");
const TCHAR* FHeaderParser::FTArrayText = TEXT("TArray");
const TCHAR* FHeaderParser::FTArrayViewText = TEXT("TArrayView");
const TCHAR* FHeaderParser::GetArrayText = TEXT("GetArray");
const TCHAR* FHeaderParser::GetArrayViewText = TEXT("GetArrayView");
void FHeaderParser::ParseRigVMMethodParameters(FUnrealStructDefinitionInfo& StructDef)
{
FRigVMStructInfo& StructRigVMInfo = StructDef.GetRigVMInfo();
if (!StructRigVMInfo.bHasRigVM)
{
return;
}
// validate the property types for this struct
for (FUnrealPropertyDefinitionInfo* PropertyDef : TUHTFieldRange<FUnrealPropertyDefinitionInfo>(StructDef))
{
FString MemberCPPType;
FString ExtendedCPPType;
MemberCPPType = PropertyDef->GetCPPType(&ExtendedCPPType);
if (ExtendedCPPType.IsEmpty() && MemberCPPType.StartsWith(TEnumAsByteText))
{
MemberCPPType = MemberCPPType.LeftChop(1).RightChop(12);
}
FRigVMParameter Parameter;
Parameter.Name = PropertyDef->GetName();
Parameter.Type = MemberCPPType + ExtendedCPPType;
Parameter.bConstant = PropertyDef->HasMetaData(NAME_ConstantText);
Parameter.bInput = PropertyDef->HasMetaData(NAME_InputText);
Parameter.bOutput = PropertyDef->HasMetaData(NAME_OutputText);
Parameter.Getter = GetRefText;
Parameter.bEditorOnly = PropertyDef->IsEditorOnlyProperty();
Parameter.bSingleton = PropertyDef->HasMetaData(NAME_SingletonText);
Parameter.bIsEnum = PropertyDef->GetPropertyBase().IsEnum();
if (PropertyDef->HasMetaData(NAME_VisibleText))
{
Parameter.bConstant = true;
Parameter.bInput = true;
Parameter.bOutput = false;
}
if (Parameter.bEditorOnly)
{
LogError(TEXT("RigVM Struct '%s' - Member '%s' is editor only - WITH_EDITORONLY_DATA not allowed on structs with RIGVM_METHOD."), *StructDef.GetName(), *Parameter.Name, *MemberCPPType);
}
if (!ExtendedCPPType.IsEmpty())
{
// we only support arrays - no maps or similar data structures
if (MemberCPPType != TArrayText && MemberCPPType != TEnumAsByteText)
{
LogError(TEXT("RigVM Struct '%s' - Member '%s' type '%s' not supported by RigVM."), *StructDef.GetName(), *Parameter.Name, *MemberCPPType);
continue;
}
}
if (MemberCPPType.StartsWith(TArrayText, ESearchCase::CaseSensitive))
{
ExtendedCPPType = FString::Printf(TEXT("<%s>"), *ExtendedCPPType.LeftChop(1).RightChop(1));
if(Parameter.IsConst())
{
ExtendedCPPType = FString::Printf(TEXT("<const %s>"), *ExtendedCPPType.LeftChop(1).RightChop(1));
Parameter.CastName = FString::Printf(TEXT("%s_%d_Array"), *Parameter.Name, StructRigVMInfo.Members.Num());
Parameter.CastType = FString::Printf(TEXT("%s%s"), FTArrayViewText, *ExtendedCPPType);
}
}
StructRigVMInfo.Members.Add(MoveTemp(Parameter));
}
if (StructRigVMInfo.Members.Num() == 0)
{
LogError(TEXT("RigVM Struct '%s' - has zero members - invalid RIGVM_METHOD."), *StructDef.GetName());
}
if (StructRigVMInfo.Members.Num() > 64)
{
LogError(TEXT("RigVM Struct '%s' - has %d members (64 is the limit)."), *StructDef.GetName(), StructRigVMInfo.Members.Num());
}
}
// Returns true if the token is a dynamic delegate declaration
bool FHeaderParser::IsValidDelegateDeclaration(const FToken& Token) const
{
return Token.IsIdentifier() && Token.ValueStartsWith(TEXT("DECLARE_DYNAMIC_"), ESearchCase::CaseSensitive);
}
// Parse the parameter list of a function or delegate declaration
void FHeaderParser::ParseParameterList(FUnrealFunctionDefinitionInfo& FunctionDef, bool bExpectCommaBeforeName, TMap<FName, FString>* MetaData, EGetVarTypeOptions Options)
{
// Get parameter list.
if (MatchSymbol(TEXT(')')))
{
return;
}
FAdvancedDisplayParameterHandler AdvancedDisplay(MetaData);
do
{
// Get parameter type.
FPropertyBase Property(CPT_None);
EVariableCategory VariableCategory = FunctionDef.HasAnyFunctionFlags(FUNC_Net) ? EVariableCategory::ReplicatedParameter : EVariableCategory::RegularParameter;
GetVarType(GetCurrentScope(), Options, Property, ~(CPF_ParmFlags | CPF_AutoWeak | CPF_RepSkip | CPF_UObjectWrapper | CPF_NativeAccessSpecifiers), EUHTPropertyType::None, CPF_None, EPropertyDeclarationStyle::None, VariableCategory);
Property.PropertyFlags |= CPF_Parm;
if (bExpectCommaBeforeName)
{
RequireSymbol(TEXT(','), TEXT("Delegate definitions require a , between the parameter type and parameter name"));
}
FUnrealPropertyDefinitionInfo& PropDef = GetVarNameAndDim(FunctionDef, Property, VariableCategory);
if( AdvancedDisplay.CanMarkMore() && AdvancedDisplay.ShouldMarkParameter(PropDef.GetName()) )
{
PropDef.SetPropertyFlags(CPF_AdvancedDisplay);
}
// Check parameters.
if (FunctionDef.HasAnyFunctionFlags(FUNC_Net))
{
if (Property.MapKeyProp.IsValid())
{
if (!FunctionDef.HasAnyFunctionFlags(FUNC_NetRequest | FUNC_NetResponse))
{
LogError(TEXT("Maps are not supported in an RPC."));
}
}
else if (Property.ArrayType == EArrayType::Set)
{
if (!FunctionDef.HasAnyFunctionFlags(FUNC_NetRequest | FUNC_NetResponse))
{
LogError(TEXT("Sets are not supported in an RPC."));
}
}
if (Property.Type == CPT_Struct)
{
if (!FunctionDef.HasAnyFunctionFlags(FUNC_NetRequest | FUNC_NetResponse))
{
ValidateScriptStructOkForNet(Property.ScriptStructDef->GetName(), *Property.ScriptStructDef);
}
}
if (!FunctionDef.HasAnyFunctionFlags(FUNC_NetRequest))
{
if (Property.PropertyFlags & CPF_OutParm)
{
LogError(TEXT("Replicated functions cannot contain out parameters"));
}
if (Property.PropertyFlags & CPF_RepSkip)
{
LogError(TEXT("Only service request functions cannot contain NoReplication parameters"));
}
if (PropDef.GetPropertyBase().IsDelegateOrDelegateStaticArray())
{
LogError(TEXT("Replicated functions cannot contain delegate parameters (this would be insecure)"));
}
if (Property.Type == CPT_String && Property.RefQualifier != ERefQualifier::ConstRef && !PropDef.IsStaticArray())
{
LogError(TEXT("Replicated FString parameters must be passed by const reference"));
}
if (Property.ArrayType == EArrayType::Dynamic && Property.RefQualifier != ERefQualifier::ConstRef && !PropDef.IsStaticArray())
{
LogError(TEXT("Replicated TArray parameters must be passed by const reference"));
}
}
else
{
if (!(Property.PropertyFlags & CPF_RepSkip) && (Property.PropertyFlags & CPF_OutParm))
{
LogError(TEXT("Service request functions cannot contain out parameters, unless marked NotReplicated"));
}
if (!(Property.PropertyFlags & CPF_RepSkip) && PropDef.GetPropertyBase().IsDelegateOrDelegateStaticArray())
{
LogError(TEXT("Service request functions cannot contain delegate parameters, unless marked NotReplicated"));
}
}
}
if (FunctionDef.HasAnyFunctionFlags(FUNC_BlueprintEvent|FUNC_BlueprintCallable))
{
if (Property.Type == CPT_Byte && Property.IsPrimitiveOrPrimitiveStaticArray())
{
if (FUnrealEnumDefinitionInfo* EnumDef = Property.AsEnum())
{
if (EnumDef->GetUnderlyingType() != EUnderlyingEnumType::uint8 &&
EnumDef->GetUnderlyingType() != EUnderlyingEnumType::Unspecified)
{
Throwf(TEXT("Invalid enum param for Blueprints - currently only uint8 supported"));
}
}
}
// Check that the parameter name is valid and does not conflict with pre-defined types
{
const static TArray<FString> InvalidParamNames =
{
TEXT("self"),
};
for (const FString& InvalidName : InvalidParamNames)
{
if (FCString::Stricmp(*PropDef.GetNameCPP(), *InvalidName) == 0)
{
LogError(TEXT("Paramater name '%s' in function is invalid, '%s' is a reserved name."), *InvalidName, *InvalidName);
}
}
}
}
// Default value.
if (MatchSymbol(TEXT('=')))
{
// Skip past the native specified default value; we make no attempt to parse it
FToken SkipToken;
int32 ParenthesisNestCount=0;
int32 StartPos=-1;
int32 EndPos=-1;
while ( GetToken(SkipToken) )
{
if (StartPos == -1)
{
StartPos = SkipToken.StartPos;
}
if ( ParenthesisNestCount == 0
&& (SkipToken.IsSymbol(TEXT(')')) || SkipToken.IsSymbol(TEXT(','))) )
{
// went too far
UngetToken(SkipToken);
break;
}
EndPos = InputPos;
if ( SkipToken.IsSymbol(TEXT('(')) )
{
ParenthesisNestCount++;
}
else if ( SkipToken.IsSymbol(TEXT(')')) )
{
ParenthesisNestCount--;
}
}
// allow exec functions to be added to the metaData, this is so we can have default params for them.
const bool bStoreCppDefaultValueInMetaData = FunctionDef.HasAnyFunctionFlags(FUNC_BlueprintCallable | FUNC_Exec);
if((EndPos > -1) && bStoreCppDefaultValueInMetaData)
{
FString DefaultArgText(EndPos - StartPos, Input + StartPos);
FString Key(TEXT("CPP_Default_"));
Key += PropDef.GetName();
FName KeyName = FName(*Key);
if (!MetaData->Contains(KeyName))
{
FString InnerDefaultValue;
const bool bDefaultValueParsed = FPropertyTraits::DefaultValueStringCppFormatToInnerFormat(PropDef, DefaultArgText, InnerDefaultValue);
if (!bDefaultValueParsed)
{
Throwf(TEXT("C++ Default parameter not parsed: %s \"%s\" "), *PropDef.GetName(), *DefaultArgText);
}
MetaData->Add(KeyName, InnerDefaultValue);
UE_LOG(LogCompile, Verbose, TEXT("C++ Default parameter parsed: %s \"%s\" -> \"%s\" "), *PropDef.GetName(), *DefaultArgText, *InnerDefaultValue );
}
}
}
} while( MatchSymbol(TEXT(',')) );
RequireSymbol( TEXT(')'), TEXT("parameter list") );
}
FUnrealFunctionDefinitionInfo& FHeaderParser::CompileDelegateDeclaration(const FStringView& DelegateIdentifier, EDelegateSpecifierAction::Type SpecifierAction)
{
const TCHAR* CurrentScopeName = TEXT("Delegate Declaration");
TMap<FName, FString> MetaData;
AddModuleRelativePathToMetadata(SourceFile, MetaData);
FFuncInfo FuncInfo;
// If this is a UDELEGATE, parse the specifiers first
FString DelegateMacro;
if (SpecifierAction == EDelegateSpecifierAction::Parse)
{
TArray<FPropertySpecifier> SpecifiersFound;
ReadSpecifierSetInsideMacro(SpecifiersFound, TEXT("Delegate"), MetaData);
ProcessFunctionSpecifiers(*this, FuncInfo, SpecifiersFound, MetaData);
// Get the next token and ensure it looks like a delegate
FToken Token;
GetToken(Token);
if (!IsValidDelegateDeclaration(Token))
{
Throwf(TEXT("Unexpected token following UDELEGATE(): %s"), *Token.GetTokenValue());
}
DelegateMacro = FString(Token.Value);
CheckAllow(CurrentScopeName, ENestAllowFlags::TypeDecl);
}
else
{
DelegateMacro = DelegateIdentifier;
CheckAllow(CurrentScopeName, ENestAllowFlags::ImplicitDelegateDecl);
}
EGetVarTypeOptions Options = EGetVarTypeOptions::None;
if (MetaData.Contains(NAME_DeprecatedFunction))
{
Options |= EGetVarTypeOptions::OuterTypeDeprecated;
}
// Break the delegate declaration macro down into parts
const bool bHasReturnValue = DelegateMacro.Contains(TEXT("_RetVal"), ESearchCase::CaseSensitive);
const bool bDeclaredConst = DelegateMacro.Contains(TEXT("_Const"), ESearchCase::CaseSensitive);
const bool bIsMulticast = DelegateMacro.Contains(TEXT("_MULTICAST"), ESearchCase::CaseSensitive);
const bool bIsSparse = DelegateMacro.Contains(TEXT("_SPARSE"), ESearchCase::CaseSensitive);
// Determine the parameter count
const FString* FoundParamCount = UHTConfig.DelegateParameterCountStrings.FindByPredicate([&](const FString& Str){ return DelegateMacro.Contains(Str); });
// Try reconstructing the string to make sure it matches our expectations
FString ExpectedOriginalString = FString::Printf(TEXT("DECLARE_DYNAMIC%s%s_DELEGATE%s%s%s"),
bIsMulticast ? TEXT("_MULTICAST") : TEXT(""),
bIsSparse ? TEXT("_SPARSE") : TEXT(""),
bHasReturnValue ? TEXT("_RetVal") : TEXT(""),
FoundParamCount ? **FoundParamCount : TEXT(""),
bDeclaredConst ? TEXT("_Const") : TEXT(""));
if (DelegateMacro != ExpectedOriginalString)
{
Throwf(TEXT("Unable to parse delegate declaration; expected '%s' but found '%s'."), *ExpectedOriginalString, *DelegateMacro);
}
// Multi-cast delegate function signatures are not allowed to have a return value
if (bHasReturnValue && bIsMulticast)
{
LogError(TEXT("Multi-cast delegates function signatures must not return a value"));
}
// Delegate signature
FuncInfo.FunctionFlags |= FUNC_Public | FUNC_Delegate;
if (bIsMulticast)
{
FuncInfo.FunctionFlags |= FUNC_MulticastDelegate;
}
const TCHAR* StartPos = &Input[InputPos];
// Now parse the macro body
RequireSymbol(TEXT('('), CurrentScopeName);
// Parse the return value type
FPropertyBase ReturnType( CPT_None );
if (bHasReturnValue)
{
GetVarType(GetCurrentScope(), Options, ReturnType, CPF_None, EUHTPropertyType::None, CPF_None, EPropertyDeclarationStyle::None, EVariableCategory::Return);
RequireSymbol(TEXT(','), CurrentScopeName);
}
// Skip whitespaces to get InputPos exactly on beginning of function name.
SkipWhitespaceAndComments();
FuncInfo.InputPos = InputPos;
// Get the delegate name
FToken FuncNameToken;
if (!GetIdentifier(FuncNameToken))
{
Throwf(TEXT("Missing name for %s"), CurrentScopeName );
}
FString FuncName(FuncNameToken.Value);
// If this is a delegate function then go ahead and mangle the name so we don't collide with
// actual functions or properties
{
//@TODO: UCREMOVAL: Eventually this mangling shouldn't occur
// Remove the leading F
if (!FuncName.StartsWith(TEXT("F"), ESearchCase::CaseSensitive))
{
Throwf(TEXT("Delegate type declarations must start with F"));
}
FuncName.RightChopInline(1, false);
// Append the signature goo
FuncName += HEADER_GENERATED_DELEGATE_SIGNATURE_SUFFIX;
}
FUnrealFunctionDefinitionInfo& DelegateSignatureFunctionDef = CreateFunction(*FuncName, MoveTemp(FuncInfo), bIsSparse ? EFunctionType::SparseDelegate : EFunctionType::Delegate);
DelegateSignatureFunctionDef.GetDefinitionRange().Start = StartPos;
// determine whether this function should be 'const'
if (bDeclaredConst)
{
DelegateSignatureFunctionDef.SetFunctionFlags(FUNC_Const);
}
if (bIsSparse)
{
FToken OwningClass;
RequireSymbol(TEXT(','), TEXT("Delegate Declaration"));
if (!GetIdentifier(OwningClass))
{
Throwf(TEXT("Missing OwningClass specifier."));
}
RequireSymbol(TEXT(','), TEXT("Delegate Declaration"));
FToken DelegateName;
if (!GetIdentifier(DelegateName))
{
Throwf(TEXT("Missing Delegate Name."));
}
DelegateSignatureFunctionDef.SetSparseOwningClassName(*GetClassNameWithoutPrefix(FString(OwningClass.Value)));
DelegateSignatureFunctionDef.SetSparseDelegateName(FName(DelegateName.Value, FNAME_Add));
}
DelegateSignatureFunctionDef.SetLineNumber(InputLine);
// Get parameter list.
if (FoundParamCount)
{
RequireSymbol(TEXT(','), CurrentScopeName);
ParseParameterList(DelegateSignatureFunctionDef, /*bExpectCommaBeforeName=*/ true);
// Check the expected versus actual number of parameters
int32 ParamCount = UE_PTRDIFF_TO_INT32(FoundParamCount - UHTConfig.DelegateParameterCountStrings.GetData()) + 1;
if (DelegateSignatureFunctionDef.GetProperties().Num() != ParamCount)
{
Throwf(TEXT("Expected %d parameters but found %d parameters"), ParamCount, DelegateSignatureFunctionDef.GetProperties().Num());
}
}
else
{
// Require the closing paren even with no parameter list
RequireSymbol(TEXT(')'), TEXT("Delegate Declaration"));
}
DelegateSignatureFunctionDef.GetDefinitionRange().End = &Input[InputPos];
// The macro line must be set here
DelegateSignatureFunctionDef.GetFunctionData().MacroLine = InputLine;
// Create the return value property
if (bHasReturnValue)
{
ReturnType.PropertyFlags |= CPF_Parm | CPF_OutParm | CPF_ReturnParm;
FUnrealPropertyDefinitionInfo& PropDef = GetVarNameAndDim(DelegateSignatureFunctionDef, ReturnType, EVariableCategory::Return);
}
// Try parsing metadata for the function
ParseFieldMetaData(MetaData, *DelegateSignatureFunctionDef.GetName());
AddFormattedPrevCommentAsTooltipMetaData(MetaData);
FUHTMetaData::RemapAndAddMetaData(DelegateSignatureFunctionDef, MoveTemp(MetaData));
// Optionally consume a semicolon, it's not required for the delegate macro since it contains one internally
MatchSemi();
// End the nesting
PostPopFunctionDeclaration(DelegateSignatureFunctionDef);
// Don't allow delegate signatures to be redefined.
auto FunctionIterator = GetCurrentScope()->GetTypeIterator<FUnrealFunctionDefinitionInfo>();
while (FunctionIterator.MoveNext())
{
FUnrealFunctionDefinitionInfo* TestFuncDef = *FunctionIterator;
if (TestFuncDef != &DelegateSignatureFunctionDef && TestFuncDef->GetFName() == DelegateSignatureFunctionDef.GetFName())
{
Throwf(TEXT("Can't override delegate signature function '%s'"), *DelegateSignatureFunctionDef.GetNameCPP());
}
}
return DelegateSignatureFunctionDef;
}
/**
* Parses and compiles a function declaration
*/
FUnrealFunctionDefinitionInfo& FHeaderParser::CompileFunctionDeclaration()
{
CheckAllow(TEXT("'Function'"), ENestAllowFlags::Function);
TMap<FName, FString> MetaData;
AddModuleRelativePathToMetadata(SourceFile, MetaData);
// New-style UFUNCTION() syntax
TArray<FPropertySpecifier> SpecifiersFound;
ReadSpecifierSetInsideMacro(SpecifiersFound, TEXT("Function"), MetaData);
FUnrealClassDefinitionInfo& OuterClassDef = GetCurrentClassDef();
if (!OuterClassDef.HasAnyClassFlags(CLASS_Native))
{
Throwf(TEXT("Should only be here for native classes!"));
}
// Process all specifiers.
const TCHAR* TypeOfFunction = TEXT("function");
bool bAutomaticallyFinal = true;
FFuncInfo FuncInfo;
FuncInfo.MacroLine = InputLine;
FuncInfo.FunctionFlags = FUNC_Native;
// Infer the function's access level from the currently declared C++ access level
if (CurrentAccessSpecifier == ACCESS_Public)
{
FuncInfo.FunctionFlags |= FUNC_Public;
}
else if (CurrentAccessSpecifier == ACCESS_Protected)
{
FuncInfo.FunctionFlags |= FUNC_Protected;
}
else if (CurrentAccessSpecifier == ACCESS_Private)
{
FuncInfo.FunctionFlags |= FUNC_Private;
FuncInfo.FunctionFlags |= FUNC_Final;
// This is automatically final as well, but in a different way and for a different reason
bAutomaticallyFinal = false;
}
else
{
Throwf(TEXT("Unknown access level"));
}
// non-static functions in a const class must be const themselves
if (OuterClassDef.HasAnyClassFlags(CLASS_Const))
{
FuncInfo.FunctionFlags |= FUNC_Const;
}
if (MatchIdentifier(TEXT("static"), ESearchCase::CaseSensitive))
{
FuncInfo.FunctionFlags |= FUNC_Static;
FuncInfo.FunctionExportFlags |= FUNCEXPORT_CppStatic;
}
if (MetaData.Contains(NAME_CppFromBpEvent))
{
FuncInfo.FunctionFlags |= FUNC_Event;
}
if ((GetCurrentCompilerDirective() & ECompilerDirective::WithEditor) != 0)
{
FuncInfo.FunctionFlags |= FUNC_EditorOnly;
}
ProcessFunctionSpecifiers(*this, FuncInfo, SpecifiersFound, MetaData);
if ((0 != (FuncInfo.FunctionExportFlags & FUNCEXPORT_CustomThunk)) && !MetaData.Contains(NAME_CustomThunk))
{
MetaData.Add(NAME_CustomThunk, TEXT("true"));
}
if ((FuncInfo.FunctionFlags & FUNC_BlueprintPure) && OuterClassDef.HasAnyClassFlags(CLASS_Interface))
{
// Until pure interface casts are supported, we don't allow pures in interfaces
LogError(TEXT("BlueprintPure specifier is not allowed for interface functions"));
}
if (FuncInfo.FunctionFlags & FUNC_Net)
{
// Network replicated functions are always events, and are only final if sealed
TypeOfFunction = TEXT("event");
bAutomaticallyFinal = false;
}
if (FuncInfo.FunctionFlags & FUNC_BlueprintEvent)
{
TypeOfFunction = (FuncInfo.FunctionFlags & FUNC_Native) ? TEXT("BlueprintNativeEvent") : TEXT("BlueprintImplementableEvent");
bAutomaticallyFinal = false;
}
// Record the tokens so we can detect this function as a declaration later (i.e. RPC)
FRecordTokens RecordTokens(*this, &GetCurrentClassDef(), nullptr);
bool bSawVirtual = false;
if (MatchIdentifier(TEXT("virtual"), ESearchCase::CaseSensitive))
{
bSawVirtual = true;
}
FString* InternalPtr = MetaData.Find(NAME_BlueprintInternalUseOnly); // FBlueprintMetadata::MD_BlueprintInternalUseOnly
const bool bInternalOnly = InternalPtr && *InternalPtr == TEXT("true");
const bool bDeprecated = MetaData.Contains(NAME_DeprecatedFunction); // FBlueprintMetadata::MD_DeprecatedFunction
// If this function is blueprint callable or blueprint pure, require a category
if ((FuncInfo.FunctionFlags & (FUNC_BlueprintCallable | FUNC_BlueprintPure)) != 0)
{
const bool bBlueprintAccessor = MetaData.Contains(NAME_BlueprintSetter) || MetaData.Contains(NAME_BlueprintGetter); // FBlueprintMetadata::MD_BlueprintSetter, // FBlueprintMetadata::MD_BlueprintGetter
const bool bHasMenuCategory = MetaData.Contains(NAME_Category); // FBlueprintMetadata::MD_FunctionCategory
if (!bHasMenuCategory && !bInternalOnly && !bDeprecated && !bBlueprintAccessor)
{
// To allow for quick iteration, don't enforce the requirement that game functions have to be categorized
if (bIsCurrentModulePartOfEngine)
{
LogError(TEXT("An explicit Category specifier is required for Blueprint accessible functions in an Engine module."));
}
}
}
// Verify interfaces with respect to their blueprint accessible functions
if (OuterClassDef.HasAnyClassFlags(CLASS_Interface))
{
// Interface with blueprint data should declare explicitly Blueprintable or NotBlueprintable to be clear
// In the backward compatible case where they declare neither, both of these bools are false
const bool bCanImplementInBlueprints = OuterClassDef.GetBoolMetaData(NAME_IsBlueprintBase);
const bool bCannotImplementInBlueprints = (!bCanImplementInBlueprints && OuterClassDef.HasMetaData(NAME_IsBlueprintBase))
|| OuterClassDef.HasMetaData(NAME_CannotImplementInterfaceInBlueprint);
if((FuncInfo.FunctionFlags & FUNC_BlueprintEvent) != 0 && !bInternalOnly)
{
// Ensure that blueprint events are only allowed in implementable interfaces. Internal only functions allowed
if (bCannotImplementInBlueprints)
{
LogError(TEXT("Interfaces that are not implementable in blueprints cannot have Blueprint Event members."));
}
if (!bCanImplementInBlueprints)
{
// We do not currently warn about this case as there are a large number of existing interfaces that do not specify
// LogWarning(TEXT("Interfaces with Blueprint Events should declare Blueprintable on the interface."));
}
}
if (((FuncInfo.FunctionFlags & FUNC_BlueprintCallable) != 0) && (((~FuncInfo.FunctionFlags) & FUNC_BlueprintEvent) != 0))
{
// Ensure that if this interface contains blueprint callable functions that are not blueprint defined, that it must be implemented natively
if (bCanImplementInBlueprints)
{
LogError(TEXT("Blueprint implementable interfaces cannot contain BlueprintCallable functions that are not BlueprintImplementableEvents. Add NotBlueprintable to the interface if you wish to keep this function."));
}
if (!bCannotImplementInBlueprints)
{
// Lowered this case to a warning instead of error, they will not show up as blueprintable unless they also have events
LogWarning(TEXT("Interfaces with BlueprintCallable functions but no events should explicitly declare NotBlueprintable on the interface."));
}
}
}
// Peek ahead to look for a CORE_API style DLL import/export token if present
FString APIMacroIfPresent;
{
FToken Token;
if (GetToken(Token, true))
{
bool bThrowTokenBack = true;
if (Token.IsIdentifier())
{
FString RequiredAPIMacroIfPresent(Token.Value);
if (RequiredAPIMacroIfPresent.EndsWith(TEXT("_API"), ESearchCase::CaseSensitive))
{
//@TODO: Validate the module name for RequiredAPIMacroIfPresent
bThrowTokenBack = false;
if (OuterClassDef.HasAnyClassFlags(CLASS_RequiredAPI))
{
Throwf(TEXT("'%s' must not be used on methods of a class that is marked '%s' itself."), *RequiredAPIMacroIfPresent, *RequiredAPIMacroIfPresent);
}
FuncInfo.FunctionFlags |= FUNC_RequiredAPI;
FuncInfo.FunctionExportFlags |= FUNCEXPORT_RequiredAPI;
APIMacroIfPresent = RequiredAPIMacroIfPresent;
}
}
if (bThrowTokenBack)
{
UngetToken(Token);
}
}
}
// Look for static again, in case there was an ENGINE_API token first
if (!APIMacroIfPresent.IsEmpty() && MatchIdentifier(TEXT("static"), ESearchCase::CaseSensitive))
{
Throwf(TEXT("Unexpected API macro '%s'. Did you mean to put '%s' after the static keyword?"), *APIMacroIfPresent, *APIMacroIfPresent);
}
// Look for virtual again, in case there was an ENGINE_API token first
if (MatchIdentifier(TEXT("virtual"), ESearchCase::CaseSensitive))
{
bSawVirtual = true;
}
// Process the virtualness
if (bSawVirtual)
{
// Remove the implicit final, the user can still specifying an explicit final at the end of the declaration
bAutomaticallyFinal = false;
// if this is a BlueprintNativeEvent or BlueprintImplementableEvent in an interface, make sure it's not "virtual"
if (FuncInfo.FunctionFlags & FUNC_BlueprintEvent)
{
if (OuterClassDef.HasAnyClassFlags(CLASS_Interface))
{
Throwf(TEXT("BlueprintImplementableEvents in Interfaces must not be declared 'virtual'"));
}
// if this is a BlueprintNativeEvent, make sure it's not "virtual"
else if (FuncInfo.FunctionFlags & FUNC_Native)
{
LogError(TEXT("BlueprintNativeEvent functions must be non-virtual."));
}
else
{
LogWarning(TEXT("BlueprintImplementableEvents should not be virtual. Use BlueprintNativeEvent instead."));
}
}
}
else
{
// if this is a function in an Interface, it must be marked 'virtual' unless it's an event
if (OuterClassDef.HasAnyClassFlags(CLASS_Interface) && !(FuncInfo.FunctionFlags & FUNC_BlueprintEvent))
{
Throwf(TEXT("Interface functions that are not BlueprintImplementableEvents must be declared 'virtual'"));
}
}
// Handle the initial implicit/explicit final
// A user can still specify an explicit final after the parameter list as well.
if (bAutomaticallyFinal || FuncInfo.bSealedEvent)
{
FuncInfo.FunctionFlags |= FUNC_Final;
FuncInfo.FunctionExportFlags |= FUNCEXPORT_Final;
if (OuterClassDef.HasAnyClassFlags(CLASS_Interface))
{
LogError(TEXT("Interface functions cannot be declared 'final'"));
}
}
EGetVarTypeOptions Options = EGetVarTypeOptions::None;
if (bDeprecated)
{
Options |= EGetVarTypeOptions::OuterTypeDeprecated;
}
if (EnumHasAllFlags(FuncInfo.FunctionFlags, FUNC_BlueprintEvent | FUNC_Native))
{
Options |= EGetVarTypeOptions::NoAutoConst;
}
// Get return type.
FPropertyBase ReturnType( CPT_None );
// C++ style functions always have a return value type, even if it's void
GetVarType(GetCurrentScope(), Options, ReturnType, CPF_None, EUHTPropertyType::None, CPF_None, EPropertyDeclarationStyle::None, EVariableCategory::Return);
bool bHasReturnValue = ReturnType.Type != CPT_None;
// Skip whitespaces to get InputPos exactly on beginning of function name.
SkipWhitespaceAndComments();
FuncInfo.InputPos = InputPos;
// Get function or operator name.
FToken FuncNameToken;
if (!GetIdentifier(FuncNameToken))
{
Throwf(TEXT("Missing %s name"), TypeOfFunction);
}
FString FuncName(FuncNameToken.Value);
const TCHAR* StartPos = &Input[InputPos];
if ( !MatchSymbol(TEXT('(')) )
{
Throwf(TEXT("Bad %s definition"), TypeOfFunction);
}
if (FuncInfo.FunctionFlags & FUNC_Net)
{
bool bIsNetService = !!(FuncInfo.FunctionFlags & (FUNC_NetRequest | FUNC_NetResponse));
if (bHasReturnValue && !bIsNetService)
{
Throwf(TEXT("Replicated functions can't have return values"));
}
if (FuncInfo.RPCId > 0)
{
if (FString* ExistingFunc = UsedRPCIds.Find(FuncInfo.RPCId))
{
Throwf(TEXT("Function %s already uses identifier %d"), **ExistingFunc, FuncInfo.RPCId);
}
UsedRPCIds.Add(FuncInfo.RPCId, FuncName);
if (FuncInfo.FunctionFlags & FUNC_NetResponse)
{
// Look for another function expecting this response
if (FString* ExistingFunc = RPCsNeedingHookup.Find(FuncInfo.RPCId))
{
// If this list isn't empty at end of class, throw error
RPCsNeedingHookup.Remove(FuncInfo.RPCId);
}
}
}
if (FuncInfo.RPCResponseId > 0 && FuncInfo.EndpointName != TEXT("JSBridge"))
{
// Look for an existing response function
FString* ExistingFunc = UsedRPCIds.Find(FuncInfo.RPCResponseId);
if (ExistingFunc == NULL)
{
// If this list isn't empty at end of class, throw error
RPCsNeedingHookup.Add(FuncInfo.RPCResponseId, FuncName);
}
}
}
FUnrealFunctionDefinitionInfo& FuncDef = CreateFunction(*FuncName, MoveTemp(FuncInfo), EFunctionType::Function);
FuncDef.GetDefinitionRange().Start = StartPos;
const FFuncInfo& FuncDefFuncInfo = FuncDef.GetFunctionData();
FUnrealFunctionDefinitionInfo* SuperFuncDef = FuncDef.GetSuperFunction();
// Get parameter list.
ParseParameterList(FuncDef, false, &MetaData, Options);
// Get return type, if any.
if (bHasReturnValue)
{
ReturnType.PropertyFlags |= CPF_Parm | CPF_OutParm | CPF_ReturnParm;
FUnrealPropertyDefinitionInfo& PropDef = GetVarNameAndDim(FuncDef, ReturnType, EVariableCategory::Return);
}
// determine if there are any outputs for this function
bool bHasAnyOutputs = bHasReturnValue;
if (!bHasAnyOutputs)
{
for (TSharedRef<FUnrealPropertyDefinitionInfo> PropertyDef : FuncDef.GetProperties())
{
if (PropertyDef->HasSpecificPropertyFlags(CPF_ReturnParm | CPF_OutParm, CPF_OutParm))
{
bHasAnyOutputs = true;
break;
}
}
}
// Check to see if there is a function in the super class with the same name
FUnrealStructDefinitionInfo* SuperStructDef = &GetCurrentClassDef();
if (SuperStructDef)
{
SuperStructDef = SuperStructDef->GetSuperStruct();
}
if (SuperStructDef)
{
if (FUnrealFunctionDefinitionInfo* OverriddenFunctionDef = FindFunction(*SuperStructDef, *FuncDef.GetNameCPP(), true, nullptr))
{
// Native function overrides should be done in CPP text, not in a UFUNCTION() declaration (you can't change flags, and it'd otherwise be a burden to keep them identical)
LogError(TEXT("%s: Override of UFUNCTION in parent class (%s) cannot have a UFUNCTION() declaration above it; it will use the same parameters as the original declaration."), *FuncDef.GetNameCPP(), *OverriddenFunctionDef->GetOuter()->GetName());
}
}
if (!bHasAnyOutputs && FuncDef.HasAnyFunctionFlags(FUNC_BlueprintPure))
{
LogError(TEXT("BlueprintPure specifier is not allowed for functions with no return value and no output parameters."));
}
// determine whether this function should be 'const'
if ( MatchIdentifier(TEXT("const"), ESearchCase::CaseSensitive) )
{
if (FuncDef.HasAnyFunctionFlags(FUNC_Native))
{
// @TODO: UCREMOVAL Reconsider?
//Throwf(TEXT("'const' may only be used for native functions"));
}
FuncDef.SetFunctionFlags(FUNC_Const);
// @todo: the presence of const and one or more outputs does not guarantee that there are
// no side effects. On GCC and clang we could use __attribure__((pure)) or __attribute__((const))
// or we could just rely on the use marking things BlueprintPure. Either way, checking the C++
// const identifier to determine purity is not desirable. We should remove the following logic:
// If its a const BlueprintCallable function with some sort of output and is not being marked as an BlueprintPure=false function, mark it as BlueprintPure as well
if (bHasAnyOutputs && FuncDef.HasAnyFunctionFlags(FUNC_BlueprintCallable) && !FuncDefFuncInfo.bForceBlueprintImpure)
{
FuncDef.SetFunctionFlags(FUNC_BlueprintPure);
}
}
// Try parsing metadata for the function
ParseFieldMetaData(MetaData, *FuncDef.GetName());
AddFormattedPrevCommentAsTooltipMetaData(MetaData);
FUHTMetaData::RemapAndAddMetaData(FuncDef, MoveTemp(MetaData));
// 'final' and 'override' can appear in any order before an optional '= 0' pure virtual specifier
bool bFoundFinal = MatchIdentifier(TEXT("final"), ESearchCase::CaseSensitive);
bool bFoundOverride = MatchIdentifier(TEXT("override"), ESearchCase::CaseSensitive);
if (!bFoundFinal && bFoundOverride)
{
bFoundFinal = MatchIdentifier(TEXT("final"), ESearchCase::CaseSensitive);
}
// Handle C++ style functions being declared as abstract
if (MatchSymbol(TEXT('=')))
{
int32 ZeroValue = 1;
bool bGotZero = GetConstInt(/*out*/ZeroValue);
bGotZero = bGotZero && (ZeroValue == 0);
if (!bGotZero)
{
Throwf(TEXT("Expected 0 to indicate function is abstract"));
}
}
// Look for the final keyword to indicate this function is sealed
if (bFoundFinal)
{
// This is a final (prebinding, non-overridable) function
FuncDef.SetFunctionFlags(FUNC_Final);
FuncDef.SetFunctionExportFlags(FUNCEXPORT_Final);
if (GetCurrentClassDef().HasAnyClassFlags(CLASS_Interface))
{
Throwf(TEXT("Interface functions cannot be declared 'final'"));
}
else if (FuncDef.HasAnyFunctionFlags(FUNC_BlueprintEvent))
{
Throwf(TEXT("Blueprint events cannot be declared 'final'"));
}
}
// Make sure that the replication flags set on an overridden function match the parent function
if (SuperFuncDef != nullptr)
{
if ((FuncDef.GetFunctionFlags() & FUNC_NetFuncFlags) != (SuperFuncDef->GetFunctionFlags() & FUNC_NetFuncFlags))
{
Throwf(TEXT("Overridden function '%s': Cannot specify different replication flags when overriding a function."), *FuncDef.GetName());
}
}
// if this function is an RPC in state scope, verify that it is an override
// this is required because the networking code only checks the class for RPCs when initializing network data, not any states within it
if (FuncDef.HasAnyFunctionFlags(FUNC_Net) && SuperFuncDef == nullptr && UHTCast<FUnrealClassDefinitionInfo>(FuncDef.GetOuter()) == nullptr)
{
Throwf(TEXT("Function '%s': Base implementation of RPCs cannot be in a state. Add a stub outside state scope."), *FuncDef.GetName());
}
FuncDef.GetDefinitionRange().End = &Input[InputPos];
// Just declaring a function, so end the nesting.
PostPopFunctionDeclaration(FuncDef);
// See what's coming next
FToken Token;
if (!GetToken(Token))
{
Throwf(TEXT("Unexpected end of file"));
}
// Optionally consume a semicolon
// This is optional to allow inline function definitions
if (Token.IsSymbol(TEXT(';')))
{
// Do nothing (consume it)
}
else if (Token.IsSymbol(TEXT('{')))
{
// Skip inline function bodies
UngetToken(Token);
SkipDeclaration(Token);
}
else
{
// Put the token back so we can continue parsing as normal
UngetToken(Token);
}
// perform documentation policy tests
CheckDocumentationPolicyForFunc(GetCurrentClassDef(), FuncDef);
return FuncDef;
}
/** Parses optional metadata text. */
void FHeaderParser::ParseFieldMetaData(TMap<FName, FString>& MetaData, const TCHAR* FieldName)
{
FTokenString PropertyMetaData;
bool bMetadataPresent = false;
if (MatchIdentifier(TEXT("UMETA"), ESearchCase::CaseSensitive))
{
auto ErrorMessageGetter = [FieldName]() { return FString::Printf(TEXT("' %s metadata'"), FieldName); };
bMetadataPresent = true;
RequireSymbol( TEXT('('), ErrorMessageGetter );
if (!GetRawStringRespectingQuotes(PropertyMetaData, TCHAR(')')))
{
Throwf(TEXT("'%s': No metadata specified"), FieldName);
}
RequireSymbol( TEXT(')'), ErrorMessageGetter);
}
if (bMetadataPresent)
{
// parse apart the string
TArray<FString> Pairs;
//@TODO: UCREMOVAL: Convert to property token reading
// break apart on | to get to the key/value pairs
FString NewData(PropertyMetaData.String);
bool bInString = false;
int32 LastStartIndex = 0;
int32 CharIndex;
for (CharIndex = 0; CharIndex < NewData.Len(); ++CharIndex)
{
TCHAR Ch = NewData.GetCharArray()[CharIndex];
if (Ch == '"')
{
bInString = !bInString;
}
if ((Ch == ',') && !bInString)
{
if (LastStartIndex != CharIndex)
{
Pairs.Add(NewData.Mid(LastStartIndex, CharIndex - LastStartIndex));
}
LastStartIndex = CharIndex + 1;
}
}
if (LastStartIndex != CharIndex)
{
Pairs.Add(MoveTemp(NewData).Mid(LastStartIndex, CharIndex - LastStartIndex));
}
// go over all pairs
for (int32 PairIndex = 0; PairIndex < Pairs.Num(); PairIndex++)
{
// break the pair into a key and a value
FString Token = MoveTemp(Pairs[PairIndex]);
FString Key;
// by default, not value, just a key (allowed)
FString Value;
// look for a value after an =
const int32 Equals = Token.Find(TEXT("="), ESearchCase::CaseSensitive);
// if we have an =, break up the string
if (Equals != INDEX_NONE)
{
Key = Token.Left(Equals);
Value = MoveTemp(Token);
Value.RightInline((Value.Len() - Equals) - 1, false);
}
else
{
Key = MoveTemp(Token);
}
InsertMetaDataPair(MetaData, MoveTemp(Key), MoveTemp(Value));
}
}
}
bool FHeaderParser::IsBitfieldProperty(ELayoutMacroType LayoutMacroType)
{
if (LayoutMacroType == ELayoutMacroType::Bitfield || LayoutMacroType == ELayoutMacroType::BitfieldEditorOnly)
{
return true;
}
bool bIsBitfield = false;
// The current token is the property type (uin32, uint16, etc).
// Check the property name and then check for ':'
FToken TokenVarName;
if (GetToken(TokenVarName, /*bNoConsts=*/ true))
{
FToken Token;
if (GetToken(Token, /*bNoConsts=*/ true))
{
if (Token.IsSymbol(TEXT(':')))
{
bIsBitfield = true;
}
UngetToken(Token);
}
UngetToken(TokenVarName);
}
return bIsBitfield;
}
void FHeaderParser::ValidateTypeIsDeprecated(EVariableCategory VariableCategory, FUnrealTypeDefinitionInfo* TypeDef)
{
if (FUnrealClassDefinitionInfo* ClassDef = UHTCast<FUnrealClassDefinitionInfo>(TypeDef); ClassDef != nullptr && ClassDef->HasAnyClassFlags(CLASS_Deprecated))
{
if (VariableCategory == EVariableCategory::Member)
{
LogError(TEXT("Property is using a deprecated class: %s. Property should be marked deprecated as well."), *ClassDef->GetPathName());
}
else
{
LogError(TEXT("Function is using a deprecated class: %s. Function should be marked deprecated as well."), *ClassDef->GetPathName());
}
}
}
void FHeaderParser::ValidatePropertyIsDeprecatedIfNecessary(bool bOuterTypeDeprecated, EVariableCategory VariableCategory, const FPropertyBase& VarProperty, EUHTPropertyType OuterPropertyType, EPropertyFlags OuterPropertyFlags)
{
// If the outer object being parsed is deprecated, then we don't need to do any further tests
if (bOuterTypeDeprecated)
{
return;
}
// If the property is in a container that has been deprecated, then we don't need to do any further tests
if (OuterPropertyType != EUHTPropertyType::None && (OuterPropertyFlags & CPF_Deprecated) != 0)
{
return;
}
// If the property is already marked deprecated, then we don't need to do any further tests
if ((VarProperty.PropertyFlags & CPF_Deprecated) != 0)
{
return;
}
ValidateTypeIsDeprecated(VariableCategory, VarProperty.MetaClassDef);
ValidateTypeIsDeprecated(VariableCategory, VarProperty.TypeDef);
}
bool FHeaderParser::ValidateScriptStructOkForNet(const FString& OriginStructName, FUnrealScriptStructDefinitionInfo& InStructDef)
{
if (ScriptStructsValidForNet.Contains(&InStructDef))
{
return true;
}
bool bIsStructValid = true;
if (FUnrealScriptStructDefinitionInfo* SuperScriptStructDef = UHTCast<FUnrealScriptStructDefinitionInfo>(InStructDef.GetSuperStructInfo().Struct))
{
if (!ValidateScriptStructOkForNet(OriginStructName, *SuperScriptStructDef))
{
bIsStructValid = false;
}
}
for (TSharedRef<FUnrealPropertyDefinitionInfo> PropertyDef : InStructDef.GetProperties())
{
if (PropertyDef->IsSet())
{
if (!PropertyDef->HasAnyPropertyFlags(CPF_RepSkip))
{
bIsStructValid = false;
LogError(TEXT("Sets are not supported for Replication or RPCs. Set %s in %s. Origin %s"), *PropertyDef->GetName(), *PropertyDef->GetOuter()->GetName(), *OriginStructName);
}
}
else if (PropertyDef->IsMap())
{
if (!PropertyDef->HasAnyPropertyFlags(CPF_RepSkip))
{
bIsStructValid = false;
LogError(TEXT("Maps are not supported for Replication or RPCs. Map %s in %s. Origin %s"), *PropertyDef->GetName(), *PropertyDef->GetOuter()->GetName(), *OriginStructName);
}
}
else if (PropertyDef->IsStructOrStructStaticArray())
{
if (!ValidateScriptStructOkForNet(OriginStructName, *PropertyDef->GetPropertyBase().ScriptStructDef))
{
bIsStructValid = false;
}
}
}
if (bIsStructValid)
{
ScriptStructsValidForNet.Add(&InStructDef);
}
return bIsStructValid;
}
struct FExposeOnSpawnValidator
{
// Keep this function synced with UEdGraphSchema_K2::FindSetVariableByNameFunction
static bool IsSupported(const FPropertyBase& Property)
{
bool ProperNativeType = false;
switch (Property.Type)
{
case CPT_Int:
case CPT_Int64:
case CPT_Byte:
case CPT_Float:
case CPT_Bool:
case CPT_Bool8:
case CPT_ObjectReference:
case CPT_ObjectPtrReference:
case CPT_String:
case CPT_Text:
case CPT_Name:
case CPT_Interface:
case CPT_SoftObjectReference:
ProperNativeType = true;
}
if (!ProperNativeType && (CPT_Struct == Property.Type) && Property.ScriptStructDef)
{
ProperNativeType |= Property.ScriptStructDef->GetBoolMetaData(FHeaderParserNames::NAME_BlueprintType);
}
return ProperNativeType;
}
};
void FHeaderParser::CompileVariableDeclaration(FUnrealStructDefinitionInfo& StructDef)
{
EPropertyFlags DisallowFlags = CPF_ParmFlags;
EPropertyFlags EdFlags = CPF_None;
FUnrealScriptStructDefinitionInfo* ScriptStructDef = UHTCast< FUnrealScriptStructDefinitionInfo>(StructDef);
// Get variable type.
FPropertyBase OriginalPropertyBase(CPT_None);
FIndexRange TypeRange;
ELayoutMacroType LayoutMacroType = ELayoutMacroType::None;
GetVarType(&*StructDef.GetScope(), EGetVarTypeOptions::None, OriginalPropertyBase, DisallowFlags, EUHTPropertyType::None, CPF_None, EPropertyDeclarationStyle::UPROPERTY, EVariableCategory::Member, &TypeRange, &LayoutMacroType);
OriginalPropertyBase.PropertyFlags |= EdFlags;
FString* Category = OriginalPropertyBase.MetaData.Find(NAME_Category);
// First check if the category was specified at all and if the property was exposed to the editor.
if (!Category && (OriginalPropertyBase.PropertyFlags & (CPF_Edit|CPF_BlueprintVisible)))
{
if ((&StructDef.GetPackageDef() != nullptr) && !bIsCurrentModulePartOfEngine)
{
Category = &OriginalPropertyBase.MetaData.Add(NAME_Category, StructDef.GetName());
}
else
{
LogError(TEXT("An explicit Category specifier is required for any property exposed to the editor or Blueprints in an Engine module."));
}
}
// Validate that pointer properties are not interfaces (which are not GC'd and so will cause runtime errors)
if (OriginalPropertyBase.PointerType == EPointerType::Native && OriginalPropertyBase.ClassDef->AsClass() != nullptr && OriginalPropertyBase.ClassDef->IsInterface())
{
// Get the name of the type, removing the asterisk representing the pointer
FString TypeName = FString(TypeRange.Count, Input + TypeRange.StartIndex).TrimStartAndEnd().LeftChop(1).TrimEnd();
Throwf(TEXT("UPROPERTY pointers cannot be interfaces - did you mean TScriptInterface<%s>?"), *TypeName);
}
// If the category was specified explicitly, it wins
if (Category && !(OriginalPropertyBase.PropertyFlags & (CPF_Edit|CPF_BlueprintVisible|CPF_BlueprintAssignable|CPF_BlueprintCallable)))
{
LogWarning(TEXT("Property has a Category set but is not exposed to the editor or Blueprints with EditAnywhere, BlueprintReadWrite, VisibleAnywhere, BlueprintReadOnly, BlueprintAssignable, BlueprintCallable keywords.\r\n"));
}
// Make sure that editblueprint variables are editable
if(!(OriginalPropertyBase.PropertyFlags & CPF_Edit))
{
if (OriginalPropertyBase.PropertyFlags & CPF_DisableEditOnInstance)
{
LogError(TEXT("Property cannot have 'DisableEditOnInstance' without being editable"));
}
if (OriginalPropertyBase.PropertyFlags & CPF_DisableEditOnTemplate)
{
LogError(TEXT("Property cannot have 'DisableEditOnTemplate' without being editable"));
}
}
// Validate.
if (OriginalPropertyBase.PropertyFlags & CPF_ParmFlags)
{
Throwf(TEXT("Illegal type modifiers in member variable declaration") );
}
if (FString* ExposeOnSpawnValue = OriginalPropertyBase.MetaData.Find(NAME_ExposeOnSpawn))
{
if ((*ExposeOnSpawnValue == TEXT("true")) && !FExposeOnSpawnValidator::IsSupported(OriginalPropertyBase))
{
LogError(TEXT("ExposeOnSpawn - Property cannot be exposed"));
}
}
if (LayoutMacroType != ELayoutMacroType::None)
{
RequireSymbol(TEXT(','), GLayoutMacroNames[(int32)LayoutMacroType]);
}
// If Property is a Replicated Struct check to make sure there are no Properties that are not allowed to be Replicated in the Struct
if (OriginalPropertyBase.Type == CPT_Struct && OriginalPropertyBase.PropertyFlags & CPF_Net && OriginalPropertyBase.ScriptStructDef)
{
ValidateScriptStructOkForNet(OriginalPropertyBase.ScriptStructDef->GetName(), *OriginalPropertyBase.ScriptStructDef);
}
// Process all variables of this type.
TArray<FUnrealPropertyDefinitionInfo*> NewProperties;
for (;;)
{
FPropertyBase PropertyBase = OriginalPropertyBase;
FUnrealPropertyDefinitionInfo& NewPropDef = GetVarNameAndDim(StructDef, PropertyBase, EVariableCategory::Member, LayoutMacroType);
// Optionally consume the :1 at the end of a bitfield boolean declaration
if (PropertyBase.IsBool())
{
if (LayoutMacroType == ELayoutMacroType::Bitfield || LayoutMacroType == ELayoutMacroType::BitfieldEditorOnly || MatchSymbol(TEXT(':')))
{
int32 BitfieldSize = 0;
if (!GetConstInt(/*out*/ BitfieldSize) || (BitfieldSize != 1))
{
Throwf(TEXT("Bad or missing bitfield size for '%s', must be 1."), *NewPropDef.GetName());
}
}
}
// Deprecation validation
ValidatePropertyIsDeprecatedIfNecessary(false, EVariableCategory::Member, PropertyBase, EUHTPropertyType::None, CPF_None);
if (TopNest->NestType != ENestType::FunctionDeclaration)
{
if (NewProperties.Num())
{
Throwf(TEXT("Comma delimited properties cannot be converted %s.%s\n"), *StructDef.GetName(), *NewPropDef.GetName());
}
}
NewProperties.Add(&NewPropDef);
// we'll need any metadata tags we parsed later on when we call ConvertEOLCommentToTooltip() so the tags aren't clobbered
OriginalPropertyBase.MetaData = PropertyBase.MetaData;
if (LayoutMacroType != ELayoutMacroType::None || !MatchSymbol(TEXT(',')))
{
break;
}
}
// Optional member initializer.
if (LayoutMacroType == ELayoutMacroType::FieldInitialized)
{
// Skip past the specified member initializer; we make no attempt to parse it
FToken SkipToken;
int Nesting = 1;
while (GetToken(SkipToken))
{
if (SkipToken.IsSymbol(TEXT('(')))
{
++Nesting;
}
else if (SkipToken.IsSymbol(TEXT(')')))
{
--Nesting;
if (Nesting == 0)
{
UngetToken(SkipToken);
break;
}
}
}
}
else if (MatchSymbol(TEXT('=')))
{
// Skip past the specified member initializer; we make no attempt to parse it
FToken SkipToken;
while (GetToken(SkipToken))
{
if (SkipToken.IsSymbol(TEXT(';')))
{
// went too far
UngetToken(SkipToken);
break;
}
}
}
// Using Brace Initialization
else if (MatchSymbol(TEXT('{')))
{
FToken SkipToken;
int BraceLevel = 1;
while (GetToken(SkipToken))
{
if (SkipToken.IsSymbol(TEXT('{')))
{
++BraceLevel;
}
else if (SkipToken.IsSymbol(TEXT('}')))
{
--BraceLevel;
if (BraceLevel == 0)
{
break;
}
}
}
}
if (LayoutMacroType == ELayoutMacroType::None)
{
// Expect a semicolon.
RequireSymbol(TEXT(';'), TEXT("'variable declaration'"));
}
else
{
// Expect a close bracket.
RequireSymbol(TEXT(')'), GLayoutMacroNames[(int32)LayoutMacroType]);
}
// Skip redundant semi-colons
for (;;)
{
int32 CurrInputPos = InputPos;
int32 CurrInputLine = InputLine;
FToken Token;
if (!GetToken(Token, /*bNoConsts=*/ true))
{
break;
}
if (!Token.IsSymbol(TEXT(';')))
{
InputPos = CurrInputPos;
InputLine = CurrInputLine;
break;
}
}
}
//
// Compile a statement: Either a declaration or a command.
// Returns 1 if success, 0 if end of file.
//
bool FHeaderParser::CompileStatement(TArray<FUnrealFunctionDefinitionInfo*>& DelegatesToFixup)
{
// Get a token and compile it.
FToken Token;
if( !GetToken(Token, true) )
{
// End of file.
return false;
}
else if (!CompileDeclaration(DelegatesToFixup, Token))
{
Throwf(TEXT("'%s': Bad command or expression"), *Token.GetTokenValue() );
}
return true;
}
/*-----------------------------------------------------------------------------
Code skipping.
-----------------------------------------------------------------------------*/
/**
* Skip over code, honoring { and } pairs.
*
* @param NestCount number of nest levels to consume. if 0, consumes a single statement
* @param ErrorTag text to use in error message if EOF is encountered before we've done
*/
void FHeaderParser::SkipStatements( int32 NestCount, const TCHAR* ErrorTag )
{
FToken Token;
int32 OriginalNestCount = NestCount;
while( GetToken( Token, true ) )
{
if ( Token.IsSymbol(TEXT('{')) )
{
NestCount++;
}
else if ( Token.IsSymbol(TEXT('}')) )
{
NestCount--;
}
else if ( Token.IsSymbol(TEXT(';')) && OriginalNestCount == 0 )
{
break;
}
if ( NestCount < OriginalNestCount || NestCount < 0 )
break;
}
if( NestCount > 0 )
{
Throwf(TEXT("Unexpected end of file at end of %s"), ErrorTag );
}
else if ( NestCount < 0 )
{
Throwf(TEXT("Extraneous closing brace found in %s"), ErrorTag);
}
}
/*-----------------------------------------------------------------------------
Main script compiling routine.
-----------------------------------------------------------------------------*/
// Parse Class's annotated headers and optionally its child classes.
static const FString ObjectHeader(TEXT("NoExportTypes.h"));
//
// Parses the header associated with the specified class.
// Returns result enumeration.
//
void FHeaderParser::ParseHeader()
{
// Reset the parser to begin a new class
bEncounteredNewStyleClass_UnmatchedBrackets = false;
bSpottedAutogeneratedHeaderInclude = false;
bHaveSeenUClass = false;
bClassHasGeneratedBody = false;
bClassHasGeneratedUInterfaceBody = false;
bClassHasGeneratedIInterfaceBody = false;
// Message.
UE_LOG(LogCompile, Verbose, TEXT("Parsing %s"), *SourceFile.GetFilename());
// Make sure that all of the requried include files are added to the outer scope
{
TArray<FUnrealSourceFile*> SourceFilesRequired;
for (FHeaderProvider& Include : SourceFile.GetIncludes())
{
if (Include.GetId() == ObjectHeader)
{
continue;
}
if (FUnrealSourceFile* DepFile = Include.Resolve(SourceFile))
{
SourceFilesRequired.Add(DepFile);
}
}
for (const TSharedRef<FUnrealTypeDefinitionInfo>& ClassDataPair : SourceFile.GetDefinedClasses())
{
FUnrealClassDefinitionInfo& ClassDef = ClassDataPair->AsClassChecked();
for (FUnrealClassDefinitionInfo* ParentClassDef = ClassDef.GetSuperClass(); ParentClassDef; ParentClassDef = ParentClassDef->GetSuperClass())
{
if (ParentClassDef->IsParsed() || ParentClassDef->HasAnyClassFlags(CLASS_Intrinsic))
{
break;
}
SourceFilesRequired.Add(&ParentClassDef->GetUnrealSourceFile());
}
}
for (FUnrealSourceFile* RequiredFile : SourceFilesRequired)
{
SourceFile.GetScope()->IncludeScope(&RequiredFile->GetScope().Get());
}
}
// Init compiler variables.
ResetParser(*SourceFile.GetContent());
// Init nesting.
NestLevel = 0;
TopNest = NULL;
PushNest(ENestType::GlobalScope, nullptr, &SourceFile);
// C++ classes default to private access level
CurrentAccessSpecifier = ACCESS_Private;
// Try to compile it, and catch any errors.
bool bEmptyFile = true;
// Tells if this header defines no-export classes only.
bool bNoExportClassesOnly = true;
// Parse entire program.
TArray<FUnrealFunctionDefinitionInfo*> DelegatesToFixup;
while (CompileStatement(DelegatesToFixup))
{
bEmptyFile = false;
// Clear out the previous comment in anticipation of the next statement.
ClearComment();
StatementsParsed++;
}
PopNest(ENestType::GlobalScope, TEXT("Global scope"));
// now validate all delegate variables declared in the class
{
auto ScopeTypeIterator = SourceFile.GetScope()->GetTypeIterator();
while (ScopeTypeIterator.MoveNext())
{
FUnrealFieldDefinitionInfo* TypeDef = *ScopeTypeIterator;
if (TypeDef->AsScriptStruct() != nullptr || TypeDef->AsClass() != nullptr)
{
TMap<FName, FUnrealFunctionDefinitionInfo*> DelegateCache;
FixupDelegateProperties(TypeDef->AsStructChecked(), *TypeDef->GetScope(), DelegateCache);
}
}
}
// Fix up any delegates themselves, if they refer to other delegates
{
TMap<FName, FUnrealFunctionDefinitionInfo*> DelegateCache;
for (FUnrealFunctionDefinitionInfo* DelegateDef : DelegatesToFixup)
{
FixupDelegateProperties(*DelegateDef, SourceFile.GetScope().Get(), DelegateCache);
}
}
// Precompute info for runtime optimization.
LinesParsed += InputLine;
if (RPCsNeedingHookup.Num() > 0)
{
FString ErrorMsg(TEXT("Request functions missing response pairs:\r\n"));
for (TMap<int32, FString>::TConstIterator It(RPCsNeedingHookup); It; ++It)
{
ErrorMsg += FString::Printf(TEXT("%s missing id %d\r\n"), *It.Value(), It.Key());
}
RPCsNeedingHookup.Empty();
Throwf(TEXT("%s"), *ErrorMsg);
}
// Make sure the compilation ended with valid nesting.
if (bEncounteredNewStyleClass_UnmatchedBrackets)
{
Throwf(TEXT("Missing } at end of class") );
}
if (NestLevel == 1)
{
Throwf(TEXT("Internal nest inconsistency") );
}
else if (NestLevel > 2)
{
Throwf(TEXT("Unexpected end of script in '%s' block"), NestTypeName(TopNest->NestType) );
}
for (TSharedRef<FUnrealTypeDefinitionInfo>& TypeDef : SourceFile.GetDefinedClasses())
{
FUnrealClassDefinitionInfo& ClassDef = UHTCastChecked<FUnrealClassDefinitionInfo>(TypeDef);
PostParsingClassSetup(ClassDef);
bNoExportClassesOnly = bNoExportClassesOnly && ClassDef.HasAnyClassFlags(CLASS_NoExport);
}
if (!bSpottedAutogeneratedHeaderInclude && !bEmptyFile && !bNoExportClassesOnly)
{
const FString& ExpectedHeaderName = SourceFile.GetGeneratedHeaderFilename();
Throwf(TEXT("Expected an include at the top of the header: '#include \"%s\"'"), *ExpectedHeaderName);
}
for (const TSharedRef<FUnrealTypeDefinitionInfo>& ClassDef : SourceFile.GetDefinedClasses())
{
ClassDef->AsClassChecked().MarkParsed();
}
// Perform any final concurrent finalization
for (const TSharedRef<FUnrealTypeDefinitionInfo>& TypeDef : SourceFile.GetDefinedTypes())
{
TypeDef->ConcurrentPostParseFinalize();
}
// Remember stats about this file
SourceFile.SetLinesParsed(LinesParsed);
SourceFile.SetStatementsParsed(StatementsParsed);
}
/*-----------------------------------------------------------------------------
Global functions.
-----------------------------------------------------------------------------*/
FHeaderParser::FHeaderParser(FUnrealPackageDefinitionInfo& InPackageDef, FUnrealSourceFile& InSourceFile)
: FBaseParser(InSourceFile)
, Filename(IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*InSourceFile.GetFilename()))
, PackageDef(InPackageDef)
{
const FManifestModule& Module = PackageDef.GetModule();
// Determine if the current module is part of the engine or a game (we are more strict about things for Engine modules)
switch (Module.ModuleType)
{
case EBuildModuleType::Program:
{
const FString AbsoluteEngineDir = FPaths::ConvertRelativePathToFull(FPaths::EngineDir());
const FString ModuleDir = FPaths::ConvertRelativePathToFull(Module.BaseDirectory);
bIsCurrentModulePartOfEngine = ModuleDir.StartsWith(AbsoluteEngineDir);
}
break;
case EBuildModuleType::EngineRuntime:
case EBuildModuleType::EngineUncooked:
case EBuildModuleType::EngineDeveloper:
case EBuildModuleType::EngineEditor:
case EBuildModuleType::EngineThirdParty:
bIsCurrentModulePartOfEngine = true;
break;
case EBuildModuleType::GameRuntime:
case EBuildModuleType::GameUncooked:
case EBuildModuleType::GameDeveloper:
case EBuildModuleType::GameEditor:
case EBuildModuleType::GameThirdParty:
bIsCurrentModulePartOfEngine = false;
break;
default:
bIsCurrentModulePartOfEngine = true;
check(false);
}
}
// Throws if a specifier value wasn't provided
void FHeaderParser::RequireSpecifierValue(const FUHTMessageProvider& Context, const FPropertySpecifier& Specifier, bool bRequireExactlyOne)
{
if (Specifier.Values.Num() == 0)
{
Context.Throwf(TEXT("The specifier '%s' must be given a value"), *Specifier.Key);
}
else if ((Specifier.Values.Num() != 1) && bRequireExactlyOne)
{
Context.Throwf(TEXT("The specifier '%s' must be given exactly one value"), *Specifier.Key);
}
}
// Throws if a specifier value wasn't provided
FString FHeaderParser::RequireExactlyOneSpecifierValue(const FUHTMessageProvider& Context, const FPropertySpecifier& Specifier)
{
RequireSpecifierValue(Context, Specifier, /*bRequireExactlyOne*/ true);
return Specifier.Values[0];
}
void FHeaderParser::Parse(
FUnrealPackageDefinitionInfo& PackageDef,
FUnrealSourceFile& SourceFile)
{
SCOPE_SECONDS_COUNTER_UHT(Parse);
FHeaderParser(PackageDef, SourceFile).ParseHeader();
}
/**
* Returns True if the given class name includes a valid Unreal prefix and matches up with the given original class.
*
* @param InNameToCheck - Name w/ potential prefix to check
* @param OriginalClassName - Name of class w/ no prefix to check against
*/
bool FHeaderParser::ClassNameHasValidPrefix(const FString& InNameToCheck, const FString& OriginalClassName)
{
bool bIsLabledDeprecated;
const FString ClassPrefix = GetClassPrefix( InNameToCheck, bIsLabledDeprecated );
// If the class is labeled deprecated, don't try to resolve it during header generation, valid results can't be guaranteed.
if (bIsLabledDeprecated)
{
return true;
}
if (ClassPrefix.IsEmpty())
{
return false;
}
FString TestString = FString::Printf(TEXT("%s%s"), *ClassPrefix, *OriginalClassName);
const bool bNamesMatch = ( InNameToCheck == *TestString );
return bNamesMatch;
}
void FHeaderParser::ParseClassName(const TCHAR* Temp, FString& ClassName)
{
// Skip leading whitespace
while (FChar::IsWhitespace(*Temp))
{
++Temp;
}
// Run thru characters (note: relying on later code to reject the name for a leading number, etc...)
const TCHAR* StringStart = Temp;
while (FChar::IsAlnum(*Temp) || FChar::IsUnderscore(*Temp))
{
++Temp;
}
ClassName = FString(UE_PTRDIFF_TO_INT32(Temp - StringStart), StringStart);
if (ClassName.EndsWith(TEXT("_API"), ESearchCase::CaseSensitive))
{
// RequiresAPI token for a given module
//@TODO: UCREMOVAL: Validate the module name
FString RequiresAPISymbol = ClassName;
// Now get the real class name
ClassName.Empty();
ParseClassName(Temp, ClassName);
}
}
enum class EBlockDirectiveType
{
// We're in a CPP block
CPPBlock,
// We're in a !CPP block
NotCPPBlock,
// We're in a 0 block
ZeroBlock,
// We're in a 1 block
OneBlock,
// We're in a WITH_HOT_RELOAD block
WithHotReload,
// We're in a WITH_EDITOR block
WithEditor,
// We're in a WITH_EDITORONLY_DATA block
WithEditorOnlyData,
// We're in a block with an unrecognized directive
UnrecognizedBlock
};
bool ShouldKeepBlockContents(EBlockDirectiveType DirectiveType)
{
switch (DirectiveType)
{
case EBlockDirectiveType::NotCPPBlock:
case EBlockDirectiveType::OneBlock:
case EBlockDirectiveType::WithHotReload:
case EBlockDirectiveType::WithEditor:
case EBlockDirectiveType::WithEditorOnlyData:
return true;
case EBlockDirectiveType::CPPBlock:
case EBlockDirectiveType::ZeroBlock:
case EBlockDirectiveType::UnrecognizedBlock:
return false;
}
check(false);
UE_ASSUME(false);
}
bool ShouldKeepDirective(EBlockDirectiveType DirectiveType)
{
switch (DirectiveType)
{
case EBlockDirectiveType::WithHotReload:
case EBlockDirectiveType::WithEditor:
case EBlockDirectiveType::WithEditorOnlyData:
return true;
case EBlockDirectiveType::CPPBlock:
case EBlockDirectiveType::NotCPPBlock:
case EBlockDirectiveType::ZeroBlock:
case EBlockDirectiveType::OneBlock:
case EBlockDirectiveType::UnrecognizedBlock:
return false;
}
check(false);
UE_ASSUME(false);
}
EBlockDirectiveType ParseCommandToBlockDirectiveType(const TCHAR** Str)
{
if (FParse::Command(Str, TEXT("0")))
{
return EBlockDirectiveType::ZeroBlock;
}
if (FParse::Command(Str, TEXT("1")))
{
return EBlockDirectiveType::OneBlock;
}
if (FParse::Command(Str, TEXT("CPP")))
{
return EBlockDirectiveType::CPPBlock;
}
if (FParse::Command(Str, TEXT("!CPP")))
{
return EBlockDirectiveType::NotCPPBlock;
}
if (FParse::Command(Str, TEXT("WITH_HOT_RELOAD")))
{
return EBlockDirectiveType::WithHotReload;
}
if (FParse::Command(Str, TEXT("WITH_EDITOR")))
{
return EBlockDirectiveType::WithEditor;
}
if (FParse::Command(Str, TEXT("WITH_EDITORONLY_DATA")))
{
return EBlockDirectiveType::WithEditorOnlyData;
}
return EBlockDirectiveType::UnrecognizedBlock;
}
const TCHAR* GetBlockDirectiveTypeString(EBlockDirectiveType DirectiveType)
{
switch (DirectiveType)
{
case EBlockDirectiveType::CPPBlock: return TEXT("CPP");
case EBlockDirectiveType::NotCPPBlock: return TEXT("!CPP");
case EBlockDirectiveType::ZeroBlock: return TEXT("0");
case EBlockDirectiveType::OneBlock: return TEXT("1");
case EBlockDirectiveType::WithHotReload: return TEXT("WITH_HOT_RELOAD");
case EBlockDirectiveType::WithEditor: return TEXT("WITH_EDITOR");
case EBlockDirectiveType::WithEditorOnlyData: return TEXT("WITH_EDITORONLY_DATA");
case EBlockDirectiveType::UnrecognizedBlock: return TEXT("<unrecognized>");
}
check(false);
UE_ASSUME(false);
}
// Performs a preliminary parse of the text in the specified buffer, pulling out useful information for the header generation process
void FHeaderParser::SimplifiedClassParse(FUnrealSourceFile& SourceFile, const TCHAR* InBuffer, FStringOutputDevice& ClassHeaderTextStrippedOfCppText)
{
FHeaderPreParser Parser(SourceFile);
FString StrLine;
FString ClassName;
FString BaseClassName;
// Two passes, preprocessor, then looking for the class stuff
// The layer of multi-line comment we are in.
int32 CurrentLine = 0;
const TCHAR* Buffer = InBuffer;
// Preprocessor pass
while (FParse::Line(&Buffer, StrLine, true))
{
CurrentLine++;
const TCHAR* Str = *StrLine;
int32 BraceCount = 0;
bool bIf = FParse::Command(&Str,TEXT("#if"));
if( bIf || FParse::Command(&Str,TEXT("#ifdef")) || FParse::Command(&Str,TEXT("#ifndef")) )
{
EBlockDirectiveType RootDirective;
if (bIf)
{
RootDirective = ParseCommandToBlockDirectiveType(&Str);
}
else
{
// #ifdef or #ifndef are always treated as CPP
RootDirective = EBlockDirectiveType::UnrecognizedBlock;
}
TArray<EBlockDirectiveType, TInlineAllocator<8>> DirectiveStack;
DirectiveStack.Push(RootDirective);
bool bShouldKeepBlockContents = ShouldKeepBlockContents(RootDirective);
bool bIsZeroBlock = RootDirective == EBlockDirectiveType::ZeroBlock;
ClassHeaderTextStrippedOfCppText.Logf(TEXT("%s\r\n"), ShouldKeepDirective(RootDirective) ? *StrLine : TEXT(""));
while ((DirectiveStack.Num() > 0) && FParse::Line(&Buffer, StrLine, 1))
{
CurrentLine++;
Str = *StrLine;
bool bShouldKeepLine = bShouldKeepBlockContents;
bool bIsDirective = false;
if( FParse::Command(&Str,TEXT("#endif")) )
{
EBlockDirectiveType OldDirective = DirectiveStack.Pop();
bShouldKeepLine &= ShouldKeepDirective(OldDirective);
bIsDirective = true;
}
else if( FParse::Command(&Str,TEXT("#if")) || FParse::Command(&Str,TEXT("#ifdef")) || FParse::Command(&Str,TEXT("#ifndef")) )
{
EBlockDirectiveType Directive = ParseCommandToBlockDirectiveType(&Str);
DirectiveStack.Push(Directive);
bShouldKeepLine &= ShouldKeepDirective(Directive);
bIsDirective = true;
}
else if (FParse::Command(&Str,TEXT("#elif")))
{
EBlockDirectiveType NewDirective = ParseCommandToBlockDirectiveType(&Str);
EBlockDirectiveType OldDirective = DirectiveStack.Top();
// Check to see if we're mixing ignorable directive types - we don't support this
bool bKeepNewDirective = ShouldKeepDirective(NewDirective);
bool bKeepOldDirective = ShouldKeepDirective(OldDirective);
if (bKeepNewDirective != bKeepOldDirective)
{
FUHTMessage(SourceFile, CurrentLine).Throwf(
TEXT("Mixing %s with %s in an #elif preprocessor block is not supported"),
GetBlockDirectiveTypeString(OldDirective),
GetBlockDirectiveTypeString(NewDirective)
);
}
DirectiveStack.Top() = NewDirective;
bShouldKeepLine &= bKeepNewDirective;
bIsDirective = true;
}
else if (FParse::Command(&Str, TEXT("#else")))
{
switch (DirectiveStack.Top())
{
case EBlockDirectiveType::ZeroBlock:
DirectiveStack.Top() = EBlockDirectiveType::OneBlock;
break;
case EBlockDirectiveType::OneBlock:
DirectiveStack.Top() = EBlockDirectiveType::ZeroBlock;
break;
case EBlockDirectiveType::CPPBlock:
DirectiveStack.Top() = EBlockDirectiveType::NotCPPBlock;
break;
case EBlockDirectiveType::NotCPPBlock:
DirectiveStack.Top() = EBlockDirectiveType::CPPBlock;
break;
case EBlockDirectiveType::WithHotReload:
FUHTMessage(SourceFile, CurrentLine).Throwf(TEXT("Bad preprocessor directive in metadata declaration: %s; Only 'CPP', '1' and '0' can have #else directives"), *ClassName);
case EBlockDirectiveType::UnrecognizedBlock:
case EBlockDirectiveType::WithEditor:
case EBlockDirectiveType::WithEditorOnlyData:
// We allow unrecognized directives, WITH_EDITOR and WITH_EDITORONLY_DATA to have #else blocks.
// However, we don't actually change how UHT processes these #else blocks.
break;
}
bShouldKeepLine &= ShouldKeepDirective(DirectiveStack.Top());
bIsDirective = true;
}
else
{
// Check for UHT identifiers inside skipped blocks, unless it's a zero block, because the compiler is going to skip those anyway.
if (!bShouldKeepBlockContents && !bIsZeroBlock)
{
auto FindInitialStr = [](const TCHAR*& FoundSubstr, const FString& StrToSearch, const TCHAR* ConstructName) -> bool
{
if (StrToSearch.StartsWith(ConstructName, ESearchCase::CaseSensitive))
{
FoundSubstr = ConstructName;
return true;
}
return false;
};
FString TrimmedStrLine = StrLine;
TrimmedStrLine.TrimStartInline();
const TCHAR* FoundSubstr = nullptr;
if (FindInitialStr(FoundSubstr, TrimmedStrLine, TEXT("UPROPERTY"))
|| FindInitialStr(FoundSubstr, TrimmedStrLine, TEXT("UCLASS"))
|| FindInitialStr(FoundSubstr, TrimmedStrLine, TEXT("USTRUCT"))
|| FindInitialStr(FoundSubstr, TrimmedStrLine, TEXT("UENUM"))
|| FindInitialStr(FoundSubstr, TrimmedStrLine, TEXT("UINTERFACE"))
|| FindInitialStr(FoundSubstr, TrimmedStrLine, TEXT("UDELEGATE"))
|| FindInitialStr(FoundSubstr, TrimmedStrLine, TEXT("UFUNCTION")))
{
FUHTMessage(SourceFile, CurrentLine).Throwf(TEXT("%s must not be inside preprocessor blocks, except for WITH_EDITORONLY_DATA"), FoundSubstr);
}
// Try and determine if this line contains something like a serialize function
if (TrimmedStrLine.Len() > 0)
{
static const FString Str_Void = TEXT("void");
static const FString Str_Serialize = TEXT("Serialize(");
static const FString Str_FArchive = TEXT("FArchive");
static const FString Str_FStructuredArchive = TEXT("FStructuredArchive::FSlot");
int32 Pos = 0;
if ((Pos = TrimmedStrLine.Find(Str_Void, ESearchCase::CaseSensitive, ESearchDir::FromStart, Pos)) != -1)
{
Pos += Str_Void.Len();
if ((Pos = TrimmedStrLine.Find(Str_Serialize, ESearchCase::CaseSensitive, ESearchDir::FromStart, Pos)) != -1)
{
Pos += Str_Serialize.Len();
if (((TrimmedStrLine.Find(Str_FArchive, ESearchCase::CaseSensitive, ESearchDir::FromStart, Pos)) != -1) ||
((TrimmedStrLine.Find(Str_FStructuredArchive, ESearchCase::CaseSensitive, ESearchDir::FromStart, Pos)) != -1))
{
FUHTMessage(SourceFile, CurrentLine).Throwf(TEXT("'%s' must not be inside preprocessor blocks, except for WITH_EDITORONLY_DATA"), *TrimmedStrLine);
}
}
}
}
}
}
ClassHeaderTextStrippedOfCppText.Logf(TEXT("%s\r\n"), bShouldKeepLine ? *StrLine : TEXT(""));
if (bIsDirective)
{
bShouldKeepBlockContents = Algo::AllOf(DirectiveStack, &ShouldKeepBlockContents);
bIsZeroBlock = DirectiveStack.Contains(EBlockDirectiveType::ZeroBlock);
}
}
}
else if ( FParse::Command(&Str,TEXT("#include")) )
{
ClassHeaderTextStrippedOfCppText.Logf( TEXT("%s\r\n"), *StrLine );
}
else
{
ClassHeaderTextStrippedOfCppText.Logf( TEXT("%s\r\n"), *StrLine );
}
}
// now start over go look for the class
bool bInComment = false;
CurrentLine = 0;
Buffer = *ClassHeaderTextStrippedOfCppText;
bool bFoundGeneratedInclude = false;
bool bGeneratedIncludeRequired = false;
for (const TCHAR* StartOfLine = Buffer; FParse::Line(&Buffer, StrLine, true); StartOfLine = Buffer)
{
CurrentLine++;
const TCHAR* Str = *StrLine;
bool bProcess = !bInComment; // for skipping nested multi-line comments
int32 BraceCount = 0;
if( bProcess && FParse::Command(&Str,TEXT("#if")) )
{
}
else if ( bProcess && FParse::Command(&Str,TEXT("#include")) )
{
// Handle #include directives as if they were 'dependson' keywords.
const FString& DependsOnHeaderName = Str;
if (DependsOnHeaderName != TEXT("\"UObject/DefineUPropertyMacros.h\"") && DependsOnHeaderName != TEXT("\"UObject/UndefineUPropertyMacros.h\""))
{
if (bFoundGeneratedInclude)
{
FUHTMessage(SourceFile, CurrentLine).Throwf(TEXT("#include found after .generated.h file - the .generated.h file should always be the last #include in a header"));
}
bFoundGeneratedInclude = DependsOnHeaderName.Contains(TEXT(".generated.h"));
if (!bFoundGeneratedInclude && DependsOnHeaderName.Len())
{
bool bIsQuotedInclude = DependsOnHeaderName[0] == '\"';
int32 HeaderFilenameEnd = DependsOnHeaderName.Find(bIsQuotedInclude ? TEXT("\"") : TEXT(">"), ESearchCase::CaseSensitive, ESearchDir::FromStart, 1);
if (HeaderFilenameEnd != INDEX_NONE)
{
// Include the extension in the name so that we later know where this entry came from.
SourceFile.GetIncludes().AddUnique(FHeaderProvider(EHeaderProviderSourceType::FileName, FPaths::GetCleanFilename(DependsOnHeaderName.Mid(1, HeaderFilenameEnd - 1))));
}
}
}
}
else if ( bProcess && FParse::Command(&Str,TEXT("#else")) )
{
}
else if ( bProcess && FParse::Command(&Str,TEXT("#elif")) )
{
}
else if ( bProcess && FParse::Command(&Str,TEXT("#endif")) )
{
}
else
{
int32 Pos = INDEX_NONE;
int32 EndPos = INDEX_NONE;
int32 StrBegin = INDEX_NONE;
int32 StrEnd = INDEX_NONE;
bool bEscaped = false;
for ( int32 CharPos = 0; CharPos < StrLine.Len(); CharPos++ )
{
if ( bEscaped )
{
bEscaped = false;
}
else if ( StrLine[CharPos] == TEXT('\\') )
{
bEscaped = true;
}
else if ( StrLine[CharPos] == TEXT('\"') )
{
if ( StrBegin == INDEX_NONE )
{
StrBegin = CharPos;
}
else
{
StrEnd = CharPos;
break;
}
}
}
// Find the first '/' and check for '//' or '/*' or '*/'
if (StrLine.FindChar(TEXT('/'), Pos))
{
if (Pos >= 0)
{
// Stub out the comments, ignoring anything inside literal strings.
Pos = StrLine.Find(TEXT("//"), ESearchCase::CaseSensitive, ESearchDir::FromStart, Pos);
// Check if first slash is end of multiline comment and adjust position if necessary.
if (Pos > 0 && StrLine[Pos - 1] == TEXT('*'))
{
++Pos;
}
if (Pos >= 0)
{
if (StrBegin == INDEX_NONE || Pos < StrBegin || Pos > StrEnd)
{
StrLine.LeftInline(Pos, false);
}
if (StrLine.IsEmpty())
{
continue;
}
}
// look for a / * ... * / block, ignoring anything inside literal strings
Pos = StrLine.Find(TEXT("/*"), ESearchCase::CaseSensitive, ESearchDir::FromStart, Pos);
EndPos = StrLine.Find(TEXT("*/"), ESearchCase::CaseSensitive, ESearchDir::FromStart, FMath::Max(0, Pos - 1));
if (Pos >= 0)
{
if (StrBegin == INDEX_NONE || Pos < StrBegin || Pos > StrEnd)
{
if (EndPos != INDEX_NONE && (EndPos < StrBegin || EndPos > StrEnd))
{
StrLine = StrLine.Left(Pos) + StrLine.Mid(EndPos + 2);
EndPos = INDEX_NONE;
bInComment = false;
}
else
{
StrLine.LeftInline(Pos, false);
bInComment = true;
}
}
bProcess = !bInComment;
}
if (EndPos >= 0)
{
if (StrBegin == INDEX_NONE || EndPos < StrBegin || EndPos > StrEnd)
{
StrLine.MidInline(EndPos + 2, MAX_int32, false);
bInComment = false;
}
bProcess = !bInComment;
}
}
}
StrLine.TrimStartInline();
if (!bProcess || StrLine.IsEmpty())
{
continue;
}
Str = *StrLine;
// Get class or interface name
if (const TCHAR* UInterfaceMacroDecl = FCString::Strfind(Str, TEXT("UINTERFACE")))
{
if (UInterfaceMacroDecl == FCString::Strspn(Str, TEXT("\t ")) + Str)
{
if (UInterfaceMacroDecl[10] != TEXT('('))
{
FUHTMessage(SourceFile, CurrentLine).Throwf(TEXT("Missing open parenthesis after UINTERFACE"));
}
TSharedRef<FUnrealTypeDefinitionInfo> ClassDecl = Parser.ParseClassDeclaration(StartOfLine + (UInterfaceMacroDecl - Str), CurrentLine, true, TEXT("UINTERFACE"));
bGeneratedIncludeRequired |= !ClassDecl->AsClassChecked().HasAnyClassFlags(CLASS_NoExport);
SourceFile.AddDefinedClass(MoveTemp(ClassDecl));
}
}
else if (const TCHAR* UClassMacroDecl = FCString::Strfind(Str, TEXT("UCLASS")))
{
if (UClassMacroDecl == FCString::Strspn(Str, TEXT("\t ")) + Str)
{
if (UClassMacroDecl[6] != TEXT('('))
{
FUHTMessage(SourceFile, CurrentLine).Throwf(TEXT("Missing open parenthesis after UCLASS"));
}
TSharedRef<FUnrealTypeDefinitionInfo> ClassDecl = Parser.ParseClassDeclaration(StartOfLine + (UClassMacroDecl - Str), CurrentLine, false, TEXT("UCLASS"));
bGeneratedIncludeRequired |= !ClassDecl->AsClassChecked().HasAnyClassFlags(CLASS_NoExport);
SourceFile.AddDefinedClass(MoveTemp(ClassDecl));
}
}
else if (const TCHAR* UEnumMacroDecl = FCString::Strfind(Str, TEXT("UENUM")))
{
if (UEnumMacroDecl == FCString::Strspn(Str, TEXT("\t ")) + Str)
{
if (UEnumMacroDecl[5] != TEXT('('))
{
FUHTMessage(SourceFile, CurrentLine).Throwf(TEXT("Missing open parenthesis after UENUM"));
}
TSharedRef<FUnrealTypeDefinitionInfo> EnumDecl = Parser.ParseEnumDeclaration(StartOfLine + (UEnumMacroDecl - Str), CurrentLine);
SourceFile.AddDefinedEnum(MoveTemp(EnumDecl));
}
}
else if (const TCHAR* UDelegateMacroDecl = FCString::Strfind(Str, TEXT("UDELEGATE")))
{
if (UDelegateMacroDecl == FCString::Strspn(Str, TEXT("\t ")) + Str)
{
if (UDelegateMacroDecl[9] != TEXT('('))
{
FUHTMessage(SourceFile, CurrentLine).Throwf(TEXT("Missing open parenthesis after UDELEGATE"));
}
// We don't preparse the delegates, but we need to make sure the include is there
bGeneratedIncludeRequired = true;
}
}
else if (const TCHAR* UStructMacroDecl = FCString::Strfind(Str, TEXT("USTRUCT")))
{
if (UStructMacroDecl == FCString::Strspn(Str, TEXT("\t ")) + Str)
{
if (UStructMacroDecl[7] != TEXT('('))
{
FUHTMessage(SourceFile, CurrentLine).Throwf(TEXT("Missing open parenthesis after USTRUCT"));
}
TSharedRef<FUnrealTypeDefinitionInfo> StructDecl = Parser.ParseStructDeclaration(StartOfLine + (UStructMacroDecl - Str), CurrentLine);
bGeneratedIncludeRequired |= !StructDecl->AsScriptStructChecked().HasAnyStructFlags(STRUCT_NoExport);
SourceFile.AddDefinedStruct(MoveTemp(StructDecl));
}
}
}
}
if (bGeneratedIncludeRequired && !bFoundGeneratedInclude)
{
Parser.Throwf(TEXT("No #include found for the .generated.h file - the .generated.h file should always be the last #include in a header"));
}
}
/////////////////////////////////////////////////////
// FHeaderPreParser
TSharedRef<FUnrealTypeDefinitionInfo> FHeaderPreParser::ParseClassDeclaration(const TCHAR* InputText, int32 InLineNumber, bool bClassIsAnInterface, const TCHAR* StartingMatchID)
{
const TCHAR* ErrorMsg = TEXT("Class declaration");
ResetParser(InputText, InLineNumber);
// Require 'UCLASS' or 'UINTERFACE'
RequireIdentifier(StartingMatchID, ESearchCase::CaseSensitive, ErrorMsg);
// New-style UCLASS() syntax
TMap<FName, FString> MetaData;
TArray<FPropertySpecifier> SpecifiersFound;
ReadSpecifierSetInsideMacro(SpecifiersFound, ErrorMsg, MetaData);
// Require 'class'
RequireIdentifier(TEXT("class"), ESearchCase::CaseSensitive, ErrorMsg);
SkipAlignasAndDeprecatedMacroIfNecessary(*this);
// Read the class name
FString RequiredAPIMacroIfPresent;
FString ClassName;
ParseNameWithPotentialAPIMacroPrefix(/*out*/ ClassName, /*out*/ RequiredAPIMacroIfPresent, StartingMatchID);
FString ClassNameWithoutPrefixStr = GetClassNameWithPrefixRemoved(ClassName);
if (ClassNameWithoutPrefixStr.IsEmpty())
{
Throwf(TEXT("When compiling class definition for '%s', attempting to strip prefix results in an empty name. Did you leave off a prefix?"), *ClassNameWithoutPrefixStr);
}
// Skip optional final keyword
MatchIdentifier(TEXT("final"), ESearchCase::CaseSensitive);
// Create the definition
TSharedRef<FUnrealClassDefinitionInfo> ClassDef = MakeShareable(new FUnrealClassDefinitionInfo(SourceFile, InLineNumber, MoveTemp(ClassName), FName(ClassNameWithoutPrefixStr, FNAME_Add), bClassIsAnInterface));
// Parse the inheritance list
ParseInheritance(TEXT("class"), [this, &ClassNameWithoutPrefixStr, &ClassDef = *ClassDef](const TCHAR* ClassName, bool bIsSuperClass)
{
FString ClassNameStr(ClassName);
SourceFile.AddClassIncludeIfNeeded(*this, ClassNameWithoutPrefixStr, ClassNameStr);
if (bIsSuperClass)
{
ClassDef.GetSuperStructInfo().Name = MoveTemp(ClassNameStr);
}
else
{
ClassDef.GetBaseStructInfos().Emplace(FUnrealStructDefinitionInfo::FBaseStructInfo{ MoveTemp(ClassNameStr) });
}
}
);
ClassDef->GetParsedMetaData() = MoveTemp(MetaData);
ClassDef->ParseClassProperties(MoveTemp(SpecifiersFound), RequiredAPIMacroIfPresent);
return ClassDef;
}
//
// Compile an enumeration definition.
//
TSharedRef<FUnrealTypeDefinitionInfo> FHeaderPreParser::ParseEnumDeclaration(const TCHAR* InputText, int32 InLineNumber)
{
const TCHAR* ErrorMsg = TEXT("Enum declaration");
ResetParser(InputText, InLineNumber);
// Require 'UCLASS' or 'UINTERFACE'
RequireIdentifier(TEXT("UENUM"), ESearchCase::CaseSensitive, ErrorMsg);
// Get the enum specifier list
FToken EnumToken;
FMetaData EnumMetaData;
TArray<FPropertySpecifier> SpecifiersFound;
ReadSpecifierSetInsideMacro(SpecifiersFound, ErrorMsg, EnumMetaData);
// Check enum type. This can be global 'enum', 'namespace' or 'enum class' enums.
bool bReadEnumName = false;
UEnum::ECppForm CppForm = UEnum::ECppForm::Regular;
if (!GetIdentifier(EnumToken))
{
Throwf(TEXT("Missing identifier after UENUM()"));
}
if (EnumToken.IsValue(TEXT("namespace"), ESearchCase::CaseSensitive))
{
CppForm = UEnum::ECppForm::Namespaced;
SkipDeprecatedMacroIfNecessary(*this);
bReadEnumName = GetIdentifier(EnumToken);
}
else if (EnumToken.IsValue(TEXT("enum"), ESearchCase::CaseSensitive))
{
if (!GetIdentifier(EnumToken))
{
Throwf(TEXT("Missing identifier after enum"));
}
if (EnumToken.IsValue(TEXT("class"), ESearchCase::CaseSensitive) || EnumToken.IsValue(TEXT("struct"), ESearchCase::CaseSensitive))
{
CppForm = UEnum::ECppForm::EnumClass;
}
else
{
// Put whatever token we found back so that we can correctly skip below
UngetToken(EnumToken);
CppForm = UEnum::ECppForm::Regular;
}
SkipAlignasAndDeprecatedMacroIfNecessary(*this);
bReadEnumName = GetIdentifier(EnumToken);
}
else
{
Throwf(TEXT("UENUM() should be followed by \'enum\' or \'namespace\' keywords."));
}
// Get enumeration name.
if (!bReadEnumName)
{
Throwf(TEXT("Missing enumeration name"));
}
// Read base for enum class
EUnderlyingEnumType UnderlyingType = EUnderlyingEnumType::uint8;
if (CppForm == UEnum::ECppForm::EnumClass)
{
UnderlyingType = ParseUnderlyingEnumType();
}
TSharedRef<FUnrealEnumDefinitionInfo> EnumDef = MakeShareable(new FUnrealEnumDefinitionInfo(SourceFile, InLineNumber, FString(EnumToken.Value), FName(EnumToken.Value, FNAME_Add), CppForm, UnderlyingType));
return EnumDef;
}
//
// Compile an structure definition.
//
TSharedRef<FUnrealTypeDefinitionInfo> FHeaderPreParser::ParseStructDeclaration(const TCHAR* InputText, int32 InLineNumber)
{
const TCHAR* ErrorMsg = TEXT("Struct declaration");
ResetParser(InputText, InLineNumber);
// Require 'UCLASS' or 'UINTERFACE'
RequireIdentifier(TEXT("USTRUCT"), ESearchCase::CaseSensitive, ErrorMsg);
// Get the structure specifier list
FToken StructToken;
FMetaData StructMetaData;
TArray<FPropertySpecifier> SpecifiersFound;
ReadSpecifierSetInsideMacro(SpecifiersFound, ErrorMsg, StructMetaData);
// Consume the struct keyword
RequireIdentifier(TEXT("struct"), ESearchCase::CaseSensitive, TEXT("Struct declaration specifier"));
SkipAlignasAndDeprecatedMacroIfNecessary(*this);
// The struct name as parsed in script and stripped of it's prefix
FString StructNameInScript;
// The required API module for this struct, if any
FString RequiredAPIMacroIfPresent;
// Read the struct name
ParseNameWithPotentialAPIMacroPrefix(/*out*/ StructNameInScript, /*out*/ RequiredAPIMacroIfPresent, TEXT("struct"));
// The struct name stripped of it's prefix
FString StructNameStripped = GetClassNameWithPrefixRemoved(StructNameInScript);
if (StructNameStripped.IsEmpty())
{
Throwf(TEXT("When compiling struct definition for '%s', attempting to strip prefix results in an empty name. Did you leave off a prefix?"), *StructNameInScript);
}
// Create the structure definition
TSharedRef<FUnrealScriptStructDefinitionInfo> StructDef = MakeShareable(new FUnrealScriptStructDefinitionInfo(SourceFile, InLineNumber, *StructNameInScript, FName(StructNameStripped, FNAME_Add)));
// Parse the inheritance list
ParseInheritance(TEXT("struct"), [this, &StructNameStripped, &StructDef = *StructDef](const TCHAR* StructName, bool bIsSuperClass)
{
FString StructNameStr(StructName);
SourceFile.AddScriptStructIncludeIfNeeded(*this, StructNameStripped, StructNameStr);
if (bIsSuperClass)
{
StructDef.GetSuperStructInfo().Name = MoveTemp(StructNameStr);
}
else
{
StructDef.GetBaseStructInfos().Emplace(FUnrealStructDefinitionInfo::FBaseStructInfo{ MoveTemp(StructNameStr) });
}
}
);
// Initialize the structure flags
StructDef->SetStructFlags(STRUCT_Native);
// Record that this struct is RequiredAPI if the CORE_API style macro was present
if (!RequiredAPIMacroIfPresent.IsEmpty())
{
StructDef->SetStructFlags(STRUCT_RequiredAPI);
}
// Process the list of specifiers
for (const FPropertySpecifier& Specifier : SpecifiersFound)
{
switch ((EStructSpecifier)Algo::FindSortedStringCaseInsensitive(*Specifier.Key, GStructSpecifierStrings))
{
default:
{
Throwf(TEXT("Unknown struct specifier '%s'"), *Specifier.Key);
}
break;
case EStructSpecifier::NoExport:
{
StructDef->SetStructFlags(STRUCT_NoExport);
StructDef->ClearStructFlags(STRUCT_Native);
}
break;
case EStructSpecifier::Atomic:
{
StructDef->SetStructFlags(STRUCT_Atomic);
}
break;
case EStructSpecifier::Immutable:
{
StructDef->SetStructFlags(STRUCT_Immutable);
StructDef->SetStructFlags(STRUCT_Atomic);
}
break;
case EStructSpecifier::HasDefaults:
if (!SourceFile.IsNoExportTypes())
{
LogError(TEXT("The 'HasDefaults' struct specifier is only valid in the NoExportTypes.h file"));
}
break;
case EStructSpecifier::HasNoOpConstructor:
if (!SourceFile.IsNoExportTypes())
{
LogError(TEXT("The 'HasNoOpConstructor' struct specifier is only valid in the NoExportTypes.h file"));
}
break;
case EStructSpecifier::IsAlwaysAccessible:
if (!SourceFile.IsNoExportTypes())
{
LogError(TEXT("The 'IsAlwaysAccessible' struct specifier is only valid in the NoExportTypes.h file"));
}
break;
case EStructSpecifier::IsCoreType:
if (!SourceFile.IsNoExportTypes())
{
LogError(TEXT("The 'IsCoreType' struct specifier is only valid in the NoExportTypes.h file"));
}
break;
}
}
// Check to make sure the syntactic native prefix was set-up correctly.
// If this check results in a false positive, it will be flagged as an identifier failure.
FString DeclaredPrefix = GetClassPrefix(StructNameInScript);
if (DeclaredPrefix == StructDef->GetPrefixCPP() || DeclaredPrefix == TEXT("T"))
{
// Found a prefix, do a basic check to see if it's valid
const TCHAR* ExpectedPrefixCPP = UHTConfig.StructsWithTPrefix.Contains(StructNameStripped) ? TEXT("T") : StructDef->GetPrefixCPP();
FString ExpectedStructName = FString::Printf(TEXT("%s%s"), ExpectedPrefixCPP, *StructNameStripped);
if (StructNameInScript != ExpectedStructName)
{
StructDef->Throwf(TEXT("Struct '%s' has an invalid Unreal prefix, expecting '%s'"), *StructNameInScript, *ExpectedStructName);
}
}
else
{
const TCHAR* ExpectedPrefixCPP = UHTConfig.StructsWithTPrefix.Contains(StructNameInScript) ? TEXT("T") : StructDef->GetPrefixCPP();
FString ExpectedStructName = FString::Printf(TEXT("%s%s"), ExpectedPrefixCPP, *StructNameInScript);
StructDef->Throwf(TEXT("Struct '%s' is missing a valid Unreal prefix, expecting '%s'"), *StructNameInScript, *ExpectedStructName);
}
return StructDef;
}
bool FHeaderParser::TryToMatchConstructorParameterList(FToken Token)
{
FToken PotentialParenthesisToken;
if (!GetToken(PotentialParenthesisToken))
{
return false;
}
if (!PotentialParenthesisToken.IsSymbol(TEXT('(')))
{
UngetToken(PotentialParenthesisToken);
return false;
}
FUnrealClassDefinitionInfo& ClassDef = GetCurrentClassDef();
bool bOICtor = false;
bool bVTCtor = false;
if (!ClassDef.IsDefaultConstructorDeclared() && MatchSymbol(TEXT(')')))
{
ClassDef.MarkDefaultConstructorDeclared();
}
else if (!ClassDef.IsObjectInitializerConstructorDeclared()
|| !ClassDef.IsCustomVTableHelperConstructorDeclared())
{
FToken ObjectInitializerParamParsingToken;
bool bIsConst = false;
bool bIsRef = false;
int32 ParenthesesNestingLevel = 1;
while (ParenthesesNestingLevel && GetToken(ObjectInitializerParamParsingToken))
{
// Template instantiation or additional parameter excludes ObjectInitializer constructor.
if (ObjectInitializerParamParsingToken.IsSymbol(TEXT(',')) || ObjectInitializerParamParsingToken.IsSymbol(TEXT('<')))
{
bOICtor = false;
bVTCtor = false;
break;
}
if (ObjectInitializerParamParsingToken.IsSymbol(TEXT('(')))
{
ParenthesesNestingLevel++;
continue;
}
if (ObjectInitializerParamParsingToken.IsSymbol(TEXT(')')))
{
ParenthesesNestingLevel--;
continue;
}
if (ObjectInitializerParamParsingToken.IsIdentifier(TEXT("const"), ESearchCase::CaseSensitive))
{
bIsConst = true;
continue;
}
if (ObjectInitializerParamParsingToken.IsSymbol(TEXT('&')))
{
bIsRef = true;
continue;
}
if (ObjectInitializerParamParsingToken.IsIdentifier(TEXT("FObjectInitializer"), ESearchCase::CaseSensitive)
|| ObjectInitializerParamParsingToken.IsIdentifier(TEXT("FPostConstructInitializeProperties"), ESearchCase::CaseSensitive) // Deprecated, but left here, so it won't break legacy code.
)
{
bOICtor = true;
}
if (ObjectInitializerParamParsingToken.IsIdentifier(TEXT("FVTableHelper"), ESearchCase::CaseSensitive))
{
bVTCtor = true;
}
}
// Parse until finish.
while (ParenthesesNestingLevel && GetToken(ObjectInitializerParamParsingToken))
{
if (ObjectInitializerParamParsingToken.IsSymbol(TEXT('(')))
{
ParenthesesNestingLevel++;
continue;
}
if (ObjectInitializerParamParsingToken.IsSymbol(TEXT(')')))
{
ParenthesesNestingLevel--;
continue;
}
}
if (bOICtor && bIsRef && bIsConst)
{
ClassDef.MarkObjectInitializerConstructorDeclared();
}
if (bVTCtor && bIsRef)
{
ClassDef.MarkCustomVTableHelperConstructorDeclared();
}
}
if (!bVTCtor)
{
ClassDef.MarkConstructorDeclared();
}
// Optionally match semicolon.
if (!MatchSymbol(TEXT(';')))
{
// If not matched a semicolon, this is inline constructor definition. We have to skip it.
UngetToken(Token); // Resets input stream to the initial token.
GetToken(Token); // Re-gets the initial token to start constructor definition skip.
return SkipDeclaration(Token);
}
return true;
}
void FHeaderParser::CompileVersionDeclaration(FUnrealStructDefinitionInfo& StructDef)
{
// Do nothing if we're at the end of file.
FToken Token;
if (!GetToken(Token, true, ESymbolParseOption::Normal))
{
return;
}
// Default version based on config file.
EGeneratedCodeVersion Version = UHTConfig.DefaultGeneratedCodeVersion;
// Overwrite with module-specific value if one was specified.
if (PackageDef.GetModule().GeneratedCodeVersion != EGeneratedCodeVersion::None)
{
Version = PackageDef.GetModule().GeneratedCodeVersion;
}
if (Token.IsSymbol(TEXT(')')))
{
StructDef.SetGeneratedCodeVersion(Version);
UngetToken(Token);
return;
}
// Overwrite with version specified by macro.
Version = ToGeneratedCodeVersion(Token.Value);
StructDef.SetGeneratedCodeVersion(Version);
}
void FHeaderParser::ResetClassData()
{
FUnrealClassDefinitionInfo& CurrentClassDef = GetCurrentClassDef();
check(CurrentClassDef.GetClassWithin() == nullptr);
// Set class flags and within.
CurrentClassDef.ClearClassFlags(CLASS_RecompilerClear);
if (FUnrealClassDefinitionInfo* SuperClassDef = CurrentClassDef.GetSuperClass())
{
CurrentClassDef.SetClassFlags(SuperClassDef->GetClassFlags() & CurrentClassDef.GetInheritClassFlags());
CurrentClassDef.SetClassConfigName(SuperClassDef->GetClassConfigName());
check(SuperClassDef->GetClassWithin());
CurrentClassDef.SetClassWithin(SuperClassDef->GetClassWithin());
// Copy special categories from parent
if (SuperClassDef->HasMetaData(FHeaderParserNames::NAME_HideCategories))
{
CurrentClassDef.SetMetaData(FHeaderParserNames::NAME_HideCategories, *SuperClassDef->GetMetaData(FHeaderParserNames::NAME_HideCategories));
}
if (SuperClassDef->HasMetaData(FHeaderParserNames::NAME_ShowCategories))
{
CurrentClassDef.SetMetaData(FHeaderParserNames::NAME_ShowCategories, *SuperClassDef->GetMetaData(FHeaderParserNames::NAME_ShowCategories));
}
if (SuperClassDef->HasMetaData(FHeaderParserNames::NAME_SparseClassDataTypes))
{
CurrentClassDef.SetMetaData(FHeaderParserNames::NAME_SparseClassDataTypes, *SuperClassDef->GetMetaData(FHeaderParserNames::NAME_SparseClassDataTypes));
}
if (SuperClassDef->HasMetaData(FHeaderParserNames::NAME_HideFunctions))
{
CurrentClassDef.SetMetaData(FHeaderParserNames::NAME_HideFunctions, *SuperClassDef->GetMetaData(FHeaderParserNames::NAME_HideFunctions));
}
if (SuperClassDef->HasMetaData(FHeaderParserNames::NAME_AutoExpandCategories))
{
CurrentClassDef.SetMetaData(FHeaderParserNames::NAME_AutoExpandCategories, *SuperClassDef->GetMetaData(FHeaderParserNames::NAME_AutoExpandCategories));
}
if (SuperClassDef->HasMetaData(FHeaderParserNames::NAME_AutoCollapseCategories))
{
CurrentClassDef.SetMetaData(FHeaderParserNames::NAME_AutoCollapseCategories, *SuperClassDef->GetMetaData(FHeaderParserNames::NAME_AutoCollapseCategories));
}
if (SuperClassDef->HasMetaData(FHeaderParserNames::NAME_PrioritizeCategories))
{
CurrentClassDef.SetMetaData(FHeaderParserNames::NAME_PrioritizeCategories, *SuperClassDef->GetMetaData(FHeaderParserNames::NAME_PrioritizeCategories));
}
}
else
{
CurrentClassDef.SetClassWithin(GUObjectDef);
}
check(CurrentClassDef.GetClassWithin());
}
void FHeaderParser::PostPopNestClass(FUnrealClassDefinitionInfo& CurrentClassDef)
{
// Validate all the rep notify events here, to make sure they're implemented
VerifyPropertyMarkups(CurrentClassDef);
// Iterate over all the interfaces we claim to implement
for (const FUnrealStructDefinitionInfo::FBaseStructInfo& BaseClassInfo : CurrentClassDef.GetBaseStructInfos())
{
// Skip unknown base classes or things that aren't interfaces
if (BaseClassInfo.Struct == nullptr)
{
continue;
}
FUnrealClassDefinitionInfo* InterfaceDef = UHTCast<FUnrealClassDefinitionInfo>(BaseClassInfo.Struct);
if (InterfaceDef == nullptr || !InterfaceDef->IsInterface())
{
continue;
}
// And their super-classes
for (; InterfaceDef; InterfaceDef = InterfaceDef->GetSuperClass())
{
// If this interface is a common ancestor, skip it
if (CurrentClassDef.IsChildOf(*InterfaceDef))
{
continue;
}
// So iterate over all functions this interface declares
for (FUnrealFunctionDefinitionInfo* InterfaceFunctionDef : TUHTFieldRange<FUnrealFunctionDefinitionInfo>(*InterfaceDef, EFieldIteratorFlags::ExcludeSuper))
{
bool bImplemented = false;
// And try to find one that matches
for (FUnrealFunctionDefinitionInfo* ClassFunctionDef : TUHTFieldRange<FUnrealFunctionDefinitionInfo>(CurrentClassDef))
{
if (ClassFunctionDef->GetFName() != InterfaceFunctionDef->GetFName())
{
continue;
}
if (InterfaceFunctionDef->HasAnyFunctionFlags(FUNC_Event) && !ClassFunctionDef->HasAnyFunctionFlags(FUNC_Event))
{
Throwf(TEXT("Implementation of function '%s::%s' must be declared as 'event' to match declaration in interface '%s'"), *ClassFunctionDef->GetOuter()->GetName(), *ClassFunctionDef->GetName(), *InterfaceDef->GetName());
}
if (InterfaceFunctionDef->HasAnyFunctionFlags(FUNC_Delegate) && !ClassFunctionDef->HasAnyFunctionFlags(FUNC_Delegate))
{
Throwf(TEXT("Implementation of function '%s::%s' must be declared as 'delegate' to match declaration in interface '%s'"), *ClassFunctionDef->GetOuter()->GetName(), *ClassFunctionDef->GetName(), *InterfaceDef->GetName());
}
// Making sure all the parameters match up correctly
bImplemented = true;
if (ClassFunctionDef->GetProperties().Num() != InterfaceFunctionDef->GetProperties().Num())
{
Throwf(TEXT("Implementation of function '%s' conflicts with interface '%s' - different number of parameters (%i/%i)"), *InterfaceFunctionDef->GetName(), *InterfaceDef->GetName(), ClassFunctionDef->GetProperties().Num(), InterfaceFunctionDef->GetProperties().Num());
}
for (int32 Index = 0, End = ClassFunctionDef->GetProperties().Num(); Index != End; ++Index)
{
FUnrealPropertyDefinitionInfo& InterfaceParamDef = *InterfaceFunctionDef->GetProperties()[Index];
FUnrealPropertyDefinitionInfo& ClassParamDef = *ClassFunctionDef->GetProperties()[Index];
if (!InterfaceParamDef.MatchesType(ClassParamDef, true))
{
if (InterfaceParamDef.HasAnyPropertyFlags(CPF_ReturnParm))
{
Throwf(TEXT("Implementation of function '%s' conflicts only by return type with interface '%s'"), *InterfaceFunctionDef->GetName(), *InterfaceDef->GetName());
}
else
{
Throwf(TEXT("Implementation of function '%s' conflicts with interface '%s' - parameter %i '%s'"), *InterfaceFunctionDef->GetName(), *InterfaceDef->GetName(), End, *InterfaceParamDef.GetName());
}
}
}
}
// Delegate signature functions are simple stubs and aren't required to be implemented (they are not callable)
if (InterfaceFunctionDef->HasAnyFunctionFlags(FUNC_Delegate))
{
bImplemented = true;
}
// Verify that if this has blueprint-callable functions that are not implementable events, we've implemented them as a UFunction in the target class
if (!bImplemented
&& InterfaceFunctionDef->HasAnyFunctionFlags(FUNC_BlueprintCallable)
&& !InterfaceFunctionDef->HasAnyFunctionFlags(FUNC_BlueprintEvent)
&& !InterfaceDef->HasMetaData(NAME_CannotImplementInterfaceInBlueprint)) // FBlueprintMetadata::MD_CannotImplementInterfaceInBlueprint
{
Throwf(TEXT("Missing UFunction implementation of function '%s' from interface '%s'. This function needs a UFUNCTION() declaration."), *InterfaceFunctionDef->GetName(), *InterfaceDef->GetName());
}
}
}
}
}
void FHeaderParser::PostPopFunctionDeclaration(FUnrealFunctionDefinitionInfo& PoppedFunctionDef)
{
//@TODO: UCREMOVAL: Move this code to occur at delegate var declaration, and force delegates to be declared before variables that use them
if (!GetCurrentScope()->IsFileScope() && GetCurrentClassDef().ContainsDelegates())
{
// now validate all delegate variables declared in the class
TMap<FName, FUnrealFunctionDefinitionInfo*> DelegateCache;
FixupDelegateProperties(PoppedFunctionDef, *GetCurrentScope(), DelegateCache);
}
}
void FHeaderParser::PostPopNestInterface(FUnrealClassDefinitionInfo& CurrentInterfaceDef)
{
if (CurrentInterfaceDef.ContainsDelegates())
{
TMap<FName, FUnrealFunctionDefinitionInfo*> DelegateCache;
FixupDelegateProperties(CurrentInterfaceDef, *CurrentInterfaceDef.GetScope(), DelegateCache);
}
}
FDocumentationPolicy FHeaderParser::GetDocumentationPolicyFromName(const FUHTMessageProvider& Context, const FString& PolicyName)
{
FDocumentationPolicy DocumentationPolicy;
if (FCString::Strcmp(*PolicyName, TEXT("Strict")) == 0)
{
DocumentationPolicy.bClassOrStructCommentRequired = true;
DocumentationPolicy.bFunctionToolTipsRequired = true;
DocumentationPolicy.bMemberToolTipsRequired = true;
DocumentationPolicy.bParameterToolTipsRequired = true;
DocumentationPolicy.bFloatRangesRequired = true;
}
else
{
Context.Throwf(TEXT("Documentation Policy '%s' not yet supported"), *PolicyName);
}
return DocumentationPolicy;
}
FDocumentationPolicy FHeaderParser::GetDocumentationPolicyForStruct(FUnrealStructDefinitionInfo& StructDef)
{
SCOPE_SECONDS_COUNTER_UHT(DocumentationPolicy);
FDocumentationPolicy DocumentationPolicy;
FString DocumentationPolicyName;
if (StructDef.GetStringMetaDataHierarchical(NAME_DocumentationPolicy, &DocumentationPolicyName))
{
DocumentationPolicy = GetDocumentationPolicyFromName(StructDef, DocumentationPolicyName);
}
return DocumentationPolicy;
}
void FHeaderParser::CheckDocumentationPolicyForEnum(FUnrealEnumDefinitionInfo& EnumDef, const TMap<FName, FString>& MetaData, const TArray<TMap<FName, FString>>& Entries)
{
SCOPE_SECONDS_COUNTER_UHT(DocumentationPolicy);
const FString* DocumentationPolicyName = MetaData.Find(NAME_DocumentationPolicy);
if (DocumentationPolicyName == nullptr)
{
return;
}
check(!DocumentationPolicyName->IsEmpty());
FDocumentationPolicy DocumentationPolicy = GetDocumentationPolicyFromName(EnumDef, *DocumentationPolicyName);
if (DocumentationPolicy.bClassOrStructCommentRequired)
{
const FString* EnumToolTip = MetaData.Find(NAME_ToolTip);
if (EnumToolTip == nullptr)
{
LogError(TEXT("Enum '%s' does not provide a tooltip / comment (DocumentationPolicy)."), *EnumDef.GetName());
}
}
TMap<FString, FString> ToolTipToEntry;
for (const TMap<FName, FString>& Entry : Entries)
{
const FString* EntryName = Entry.Find(NAME_Name);
if (EntryName == nullptr)
{
continue;
}
const FString* ToolTip = Entry.Find(NAME_ToolTip);
if (ToolTip == nullptr)
{
LogError(TEXT("Enum entry '%s::%s' does not provide a tooltip / comment (DocumentationPolicy)."), *EnumDef.GetName(), *(*EntryName));
continue;
}
const FString* ExistingEntry = ToolTipToEntry.Find(*ToolTip);
if (ExistingEntry != nullptr)
{
LogError(TEXT("Enum entries '%s::%s' and '%s::%s' have identical tooltips / comments (DocumentationPolicy)."), *EnumDef.GetName(), *(*ExistingEntry), *EnumDef.GetName(), *(*EntryName));
}
ToolTipToEntry.Add(*ToolTip, *EntryName);
}
}
void FHeaderParser::CheckDocumentationPolicyForStruct(FUnrealStructDefinitionInfo& StructDef)
{
SCOPE_SECONDS_COUNTER_UHT(DocumentationPolicy);
FDocumentationPolicy DocumentationPolicy = GetDocumentationPolicyForStruct(StructDef);
if (DocumentationPolicy.bClassOrStructCommentRequired)
{
FString ClassTooltip;
if (const FString* ClassTooltipPtr = StructDef.FindMetaData(NAME_ToolTip))
{
ClassTooltip = *ClassTooltipPtr;
}
if (ClassTooltip.IsEmpty() || ClassTooltip.Equals(StructDef.GetName()))
{
LogError(TEXT("Struct '%s' does not provide a tooltip / comment (DocumentationPolicy)."), *StructDef.GetName());
}
}
if (DocumentationPolicy.bMemberToolTipsRequired)
{
TMap<FString, FName> ToolTipToPropertyName;
for (TSharedRef<FUnrealPropertyDefinitionInfo> PropertyDef : StructDef.GetProperties())
{
FString ToolTip = PropertyDef->GetToolTipText().ToString();
if (ToolTip.IsEmpty() || ToolTip.Equals(PropertyDef->GetDisplayNameText().ToString()))
{
LogError(TEXT("Property '%s::%s' does not provide a tooltip / comment (DocumentationPolicy)."), *StructDef.GetName(), *PropertyDef->GetName());
continue;
}
const FName* ExistingPropertyName = ToolTipToPropertyName.Find(ToolTip);
if (ExistingPropertyName != nullptr)
{
LogError(TEXT("Property '%s::%s' and '%s::%s' are using identical tooltips (DocumentationPolicy)."), *StructDef.GetName(), *ExistingPropertyName->ToString(), *StructDef.GetName(), *PropertyDef->GetName());
}
ToolTipToPropertyName.Add(MoveTemp(ToolTip), PropertyDef->GetFName());
}
}
if (DocumentationPolicy.bFloatRangesRequired)
{
for (TSharedRef<FUnrealPropertyDefinitionInfo> PropertyDef : StructDef.GetProperties())
{
if(DoesCPPTypeRequireDocumentation(PropertyDef->GetCPPType()))
{
const FString& UIMin = PropertyDef->GetMetaData(NAME_UIMin);
const FString& UIMax = PropertyDef->GetMetaData(NAME_UIMax);
if(!CheckUIMinMaxRangeFromMetaData(UIMin, UIMax))
{
LogError(TEXT("Property '%s::%s' does not provide a valid UIMin / UIMax (DocumentationPolicy)."), *StructDef.GetName(), *PropertyDef->GetName());
}
}
}
}
// also compare all tooltips to see if they are unique
if (DocumentationPolicy.bFunctionToolTipsRequired)
{
if (FUnrealClassDefinitionInfo* ClassDef = UHTCast<FUnrealClassDefinitionInfo>(StructDef))
{
TMap<FString, FName> ToolTipToFunc;
for (TSharedRef<FUnrealFunctionDefinitionInfo> FunctionDef : ClassDef->GetFunctions())
{
FString ToolTip = FunctionDef->GetToolTipText().ToString();
if (ToolTip.IsEmpty())
{
LogError(TEXT("Function '%s::%s' does not provide a tooltip / comment (DocumentationPolicy)."), *ClassDef->GetName(), *FunctionDef->GetName());
continue;
}
const FName* ExistingFuncName = ToolTipToFunc.Find(ToolTip);
if (ExistingFuncName != nullptr)
{
LogError(TEXT("Functions '%s::%s' and '%s::%s' uses identical tooltips / comments (DocumentationPolicy)."), *ClassDef->GetName(), *(*ExistingFuncName).ToString(), *ClassDef->GetName(), *FunctionDef->GetName());
}
ToolTipToFunc.Add(MoveTemp(ToolTip), FunctionDef->GetFName());
}
}
}
}
bool FHeaderParser::DoesCPPTypeRequireDocumentation(const FString& CPPType)
{
return PropertyCPPTypesRequiringUIRanges.Contains(CPPType);
}
// Validates the documentation for a given method
void FHeaderParser::CheckDocumentationPolicyForFunc(FUnrealClassDefinitionInfo& ClassDef, FUnrealFunctionDefinitionInfo& FunctionDef)
{
SCOPE_SECONDS_COUNTER_UHT(DocumentationPolicy);
FDocumentationPolicy DocumentationPolicy = GetDocumentationPolicyForStruct(ClassDef);
if (DocumentationPolicy.bFunctionToolTipsRequired)
{
const FString* FunctionTooltip = FunctionDef.FindMetaData(NAME_ToolTip);
if (FunctionTooltip == nullptr)
{
LogError(TEXT("Function '%s::%s' does not provide a tooltip / comment (DocumentationPolicy)."), *ClassDef.GetName(), *FunctionDef.GetName());
}
}
if (DocumentationPolicy.bParameterToolTipsRequired)
{
const FString* FunctionComment = FunctionDef.FindMetaData(NAME_Comment);
if (FunctionComment == nullptr)
{
LogError(TEXT("Function '%s::%s' does not provide a comment (DocumentationPolicy)."), *ClassDef.GetName(), *FunctionDef.GetName());
return;
}
TMap<FName, FString> ParamToolTips = GetParameterToolTipsFromFunctionComment(*FunctionComment);
bool HasAnyParamToolTips = ParamToolTips.Num() > 0;
if (ParamToolTips.Num() == 0)
{
const FString* ReturnValueToolTip = ParamToolTips.Find(NAME_ReturnValue);
if (ReturnValueToolTip != nullptr)
{
HasAnyParamToolTips = false;
}
}
// only apply the validation for parameter tooltips if a function has any @param statements at all.
if (HasAnyParamToolTips)
{
// ensure each parameter has a tooltip
TSet<FName> ExistingFields;
for (TSharedRef<FUnrealPropertyDefinitionInfo> PropertyDef : FunctionDef.GetProperties())
{
FName ParamName = PropertyDef->GetFName();
if (ParamName == NAME_ReturnValue)
{
continue;
}
const FString* ParamToolTip = ParamToolTips.Find(ParamName);
if (ParamToolTip == nullptr)
{
LogError(TEXT("Function '%s::%s' doesn't provide a tooltip for parameter '%s' (DocumentationPolicy)."), *ClassDef.GetName(), *FunctionDef.GetName(), *ParamName.ToString());
}
ExistingFields.Add(ParamName);
}
// ensure we don't have parameter tooltips for parameters that don't exist
for (TPair<FName, FString>& Pair : ParamToolTips)
{
const FName& ParamName = Pair.Key;
if (ParamName == NAME_ReturnValue)
{
continue;
}
if (!ExistingFields.Contains(ParamName))
{
LogError(TEXT("Function '%s::%s' provides a tooltip for an unknown parameter '%s' (DocumentationPolicy)."), *ClassDef.GetName(), *FunctionDef.GetName(), *Pair.Key.ToString());
}
}
// check for duplicate tooltips
TMap<FString, FName> ToolTipToParam;
for (TPair<FName, FString>& Pair : ParamToolTips)
{
const FName& ParamName = Pair.Key;
if (ParamName == NAME_ReturnValue)
{
continue;
}
const FName* ExistingParam = ToolTipToParam.Find(Pair.Value);
if (ExistingParam != nullptr)
{
LogError(TEXT("Function '%s::%s' uses identical tooltips for parameters '%s' and '%s' (DocumentationPolicy)."), *ClassDef.GetName(), *FunctionDef.GetName(), *ExistingParam->ToString(), *Pair.Key.ToString());
}
ToolTipToParam.Add(MoveTemp(Pair.Value), Pair.Key);
}
}
}
}
bool FHeaderParser::IsReservedTypeName(const FString& TypeName)
{
for(const FString& ReservedName : ReservedTypeNames)
{
if(TypeName == ReservedName)
{
return true;
}
}
return false;
}
bool FHeaderParser::CheckUIMinMaxRangeFromMetaData(const FString& UIMin, const FString& UIMax)
{
if (UIMin.IsEmpty() || UIMax.IsEmpty())
{
return false;
}
double UIMinValue = FCString::Atod(*UIMin);
double UIMaxValue = FCString::Atod(*UIMax);
if (UIMin > UIMax) // note that we actually allow UIMin == UIMax to disable the range manually.
{
return false;
}
return true;
}
void FHeaderParser::ConditionalLogPointerUsage(EPointerMemberBehavior PointerMemberBehavior, const TCHAR* PointerTypeDesc, FString&& PointerTypeDecl, const TCHAR* AlternativeTypeDesc)
{
switch (PointerMemberBehavior)
{
case EPointerMemberBehavior::Disallow:
{
if (AlternativeTypeDesc)
{
LogError(TEXT("%s usage in member declaration detected [[[%s]]]. This is disallowed for the target/module, consider %s as an alternative."), PointerTypeDesc, *PointerTypeDecl, AlternativeTypeDesc);
}
else
{
LogError(TEXT("%s usage in member declaration detected [[[%s]]]."), PointerTypeDesc, *PointerTypeDecl);
}
}
break;
case EPointerMemberBehavior::AllowAndLog:
{
if (AlternativeTypeDesc)
{
UE_LOG(LogCompile, Log, TEXT("%s(%d): %s usage in member declaration detected [[[%s]]]. Consider %s as an alternative."), *Filename, InputLine, PointerTypeDesc, *PointerTypeDecl, AlternativeTypeDesc);
}
else
{
UE_LOG(LogCompile, Log, TEXT("%s(%d): usage in member declaration detected [[[%s]]]."), *Filename, InputLine, PointerTypeDesc, *PointerTypeDecl);
}
}
break;
case EPointerMemberBehavior::AllowSilently:
{
// Do nothing
}
break;
default:
{
checkf(false, TEXT("Unhandled case"));
}
break;
}
}
FUnrealFunctionDefinitionInfo& FHeaderParser::CreateFunction(const TCHAR* FuncName, FFuncInfo&& FuncInfo, EFunctionType InFunctionType) const
{
FScope* CurrentScope = GetCurrentScope();
FUnrealObjectDefinitionInfo& OuterDef = IsInAClass() ? static_cast<FUnrealObjectDefinitionInfo&>(GetCurrentClassDef()) : SourceFile.GetPackageDef();
// Allocate local property frame, push nesting level and verify
// uniqueness at this scope level.
{
auto TypeIterator = CurrentScope->GetTypeIterator();
while (TypeIterator.MoveNext())
{
FUnrealFieldDefinitionInfo* Type = *TypeIterator;
if (Type->GetFName() == FuncName)
{
Throwf(TEXT("'%s' conflicts with '%s'"), FuncName, *Type->GetFullName());
}
}
}
TSharedRef<FUnrealFunctionDefinitionInfo> FuncDefRef = MakeShared<FUnrealFunctionDefinitionInfo>(SourceFile, InputLine, FString(FuncName), FName(FuncName, FNAME_Add), OuterDef, MoveTemp(FuncInfo), InFunctionType);
FUnrealFunctionDefinitionInfo& FuncDef = *FuncDefRef;
FuncDef.GetFunctionData().SetFunctionNames(FuncDef);
CurrentScope->AddType(FuncDef);
if (!CurrentScope->IsFileScope())
{
FUnrealStructDefinitionInfo& StructDef = ((FStructScope*)CurrentScope)->GetStructDef();
StructDef.AddFunction(FuncDefRef);
}
else
{
SourceFile.AddDefinedFunction(FuncDefRef);
FScopeLock Lock(&GlobalDelegatesLock);
GlobalDelegates.Add(FuncDef.GetName(), &FuncDef);
}
return FuncDef;
}
FRecordTokens::FRecordTokens(FHeaderParser& InParser, FUnrealStructDefinitionInfo* InStructDef, FToken* InToken)
: Parser(InParser)
, StructDef(InStructDef)
, CurrentCompilerDirective(Parser.GetCurrentCompilerDirective())
{
if (StructDef != nullptr)
{
Parser.bRecordTokens = true;
if (InToken != nullptr)
{
Parser.RecordedTokens.Push(*InToken);
}
}
}
FRecordTokens::~FRecordTokens()
{
Stop();
}
bool FRecordTokens::Stop()
{
if (StructDef != nullptr)
{
Parser.bRecordTokens = false;
StructDef->AddDeclaration(CurrentCompilerDirective, MoveTemp(Parser.RecordedTokens));
StructDef = nullptr;
return true;
}
return false;
}