2022-05-27 10:12:44 -04:00
// 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 bool bGoWide ;
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 ;
case EFunctionSpecifier : : FieldNotify :
{
FuncInfo . bFieldNotify = true ;
}
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 ( ! bGoWide )
{
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 " ) ) ;
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 ( ) ) ;
}
}
}
}
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 ) ;
SkipDeprecatedMacroIfNecessary ( * this ) ;
// 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 " ) ) ;
}
// check if the struct is marked as deprecated and does not implement the upgrade path
if ( StructDef . HasMetaData ( TEXT ( " Deprecated " ) ) )
{
const FRigVMStructInfo & StructRigVMInfo = StructDef . GetRigVMInfo ( ) ;
if ( ! StructRigVMInfo . bHasGetUpgradeInfoMethod & & ! StructRigVMInfo . Methods . IsEmpty ( ) )
{
LogError ( TEXT (
" RigVMStruct '%s' is marked as deprecated but is missing GetUpgradeInfo method. \n "
" Please implement a method like below: \n \n "
" RIGVM_METHOD() \n "
" virtual FRigVMStructUpgradeInfo GetUpgradeInfo() const override; " ) ,
* StructDef . GetNameCPP ( ) ) ;
}
}
// 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 : : VerifyGetterSetterAccessorProperties ( const FUnrealClassDefinitionInfo & TargetClassDef , FUnrealPropertyDefinitionInfo & PropertyDef )
{
FPropertyBase & PropertyToken = PropertyDef . GetPropertyBase ( ) ;
if ( ! PropertyToken . SetterName . IsEmpty ( ) )
{
if ( PropertyToken . SetterName ! = TEXT ( " None " ) & & ! PropertyToken . bSetterFunctionFound )
{
// The function can be a UFunction (not parsed the same way as the other accessors)
const TSharedRef < FUnrealFunctionDefinitionInfo > * FoundFunction = TargetClassDef . GetFunctions ( ) . FindByPredicate ( [ & PropertyToken ] ( const TSharedRef < FUnrealFunctionDefinitionInfo > & Other ) { return Other - > GetName ( ) = = PropertyToken . SetterName ; } ) ;
if ( FoundFunction )
{
const FUnrealFunctionDefinitionInfo & Function = FoundFunction - > Get ( ) ;
if ( ! Function . HasAllFunctionFlags ( FUNC_Native ) )
{
LogError ( TEXT ( " Property %s setter function %s has to be native. " ) , * PropertyDef . GetName ( ) , * PropertyToken . SetterName ) ;
}
if ( Function . HasAnyFunctionFlags ( FUNC_Net | FUNC_Event ) )
{
LogError ( TEXT ( " Property %s setter function %s cannot be a net or an event " ) , * PropertyDef . GetName ( ) , * PropertyToken . SetterName ) ;
}
else if ( Function . HasAllFunctionFlags ( FUNC_EditorOnly ) ! = PropertyDef . HasAllPropertyFlags ( CPF_EditorOnly ) )
{
LogError ( TEXT ( " Property %s setter function %s must both be EditorOnly or both not EditorOnly " ) , * PropertyDef . GetName ( ) , * PropertyToken . SetterName ) ;
}
else if ( Function . GetProperties ( ) . Num ( ) ! = 1 )
{
LogError ( TEXT ( " Property %s setter function %s must have a single argument " ) , * PropertyDef . GetName ( ) , * PropertyToken . SetterName ) ;
}
else
{
TSharedPtr < FUnrealPropertyDefinitionInfo > FoundArgument ;
for ( const TSharedRef < FUnrealPropertyDefinitionInfo > & Arguments : Function . GetProperties ( ) )
{
if ( Arguments - > HasAllPropertyFlags ( CPF_Parm ) & & ! Arguments - > HasAnyPropertyFlags ( CPF_ReturnParm ) )
{
FoundArgument = Arguments ;
break ;
}
}
if ( ! FoundArgument . IsValid ( ) )
{
LogError ( TEXT ( " Property %s setter function %s does not have a valid argument. " ) , * PropertyDef . GetName ( ) , * PropertyToken . SetterName ) ;
}
else if ( ! FoundArgument - > SameType ( PropertyDef ) )
{
LogError ( TEXT ( " Property %s setter function %s argument is not the same type. " ) , * PropertyDef . GetName ( ) , * PropertyToken . SetterName ) ;
}
else
{
PropertyToken . bSetterFunctionFound = true ;
}
}
}
if ( ! 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 )
{
// The function can be a UFunction (not parsed the same way as the other accessors)
const TSharedRef < FUnrealFunctionDefinitionInfo > * FoundFunction = TargetClassDef . GetFunctions ( ) . FindByPredicate ( [ & PropertyToken ] ( const TSharedRef < FUnrealFunctionDefinitionInfo > & Other ) { return Other - > GetName ( ) = = PropertyToken . GetterName ; } ) ;
if ( FoundFunction )
{
const FUnrealFunctionDefinitionInfo & Function = FoundFunction - > Get ( ) ;
if ( ! Function . HasAllFunctionFlags ( FUNC_Native ) )
{
LogError ( TEXT ( " Property %s getter function %s has to be native. " ) , * PropertyDef . GetName ( ) , * PropertyToken . GetterName ) ;
}
if ( ! Function . HasAllFunctionFlags ( FUNC_Const ) )
{
LogError ( TEXT ( " Property %s getter function %s has to be const. " ) , * PropertyDef . GetName ( ) , * PropertyToken . GetterName ) ;
}
if ( Function . HasAnyFunctionFlags ( FUNC_Net | FUNC_Event ) )
{
LogError ( TEXT ( " Property %s getter function %s cannot be a net or an event " ) , * PropertyDef . GetName ( ) , * PropertyToken . GetterName ) ;
}
else if ( Function . HasAllFunctionFlags ( FUNC_EditorOnly ) ! = PropertyDef . HasAllPropertyFlags ( CPF_EditorOnly ) )
{
LogError ( TEXT ( " Property %s getter function %s must both be EditorOnly or both not EditorOnly " ) , * PropertyDef . GetName ( ) , * PropertyToken . GetterName ) ;
}
else if ( Function . GetProperties ( ) . Num ( ) ! = 1 )
{
LogError ( TEXT ( " Property %s getter function %s must have a single return value " ) , * PropertyDef . GetName ( ) , * PropertyToken . GetterName ) ;
}
else
{
TSharedPtr < FUnrealPropertyDefinitionInfo > FoundArgument ;
for ( const TSharedRef < FUnrealPropertyDefinitionInfo > & Arguments : Function . GetProperties ( ) )
{
if ( Arguments - > HasAllPropertyFlags ( CPF_Parm ) & & Arguments - > HasAnyPropertyFlags ( CPF_ReturnParm ) )
{
FoundArgument = Arguments ;
break ;
}
}
if ( ! FoundArgument . IsValid ( ) )
{
LogError ( TEXT ( " Property %s getter function %s does not have a return value. " ) , * PropertyDef . GetName ( ) , * PropertyToken . GetterName ) ;
}
else if ( ! FoundArgument - > SameType ( PropertyDef ) )
{
LogError ( TEXT ( " Property %s getter function %s return value is not the same type. " ) , * PropertyDef . GetName ( ) , * PropertyToken . GetterName ) ;
}
else
{
PropertyToken . bGetterFunctionFound = true ;
}
}
}
if ( ! PropertyToken . bGetterFunctionFound )
{
LogError ( TEXT ( " Property %s getter function %s not found " ) , * PropertyDef . GetName ( ) , * PropertyToken . GetterName ) ;
}
}
}
}
void FHeaderParser : : VerifyNotifyValueChangedProperties ( FUnrealPropertyDefinitionInfo & PropertyDef )
{
FUnrealTypeDefinitionInfo * Info = PropertyDef . GetOuter ( ) ;
if ( Info = = nullptr )
{
LogError ( TEXT ( " FieldNofity property %s does not have a outer. " ) , * PropertyDef . GetName ( ) ) ;
return ;
}
FUnrealClassDefinitionInfo * ClassInfo = Info - > AsClass ( ) ;
if ( ClassInfo = = nullptr )
{
LogError ( TEXT ( " FieldNofity property are only valid as UClass member variable. " ) ) ;
return ;
}
if ( ClassInfo - > IsInterface ( ) )
{
LogError ( TEXT ( " FieldNofity are not valid on UInterface. " ) ) ;
return ;
}
ClassInfo - > MarkHasFieldNotify ( ) ;
}
void FHeaderParser : : VerifyNotifyValueChangedFunction ( const FUnrealFunctionDefinitionInfo & TargetFuncDef )
{
FUnrealTypeDefinitionInfo * Info = TargetFuncDef . GetOuter ( ) ;
if ( Info = = nullptr )
{
LogError ( TEXT ( " FieldNofity function %s does not have a outer. " ) , * TargetFuncDef . GetName ( ) ) ;
return ;
}
FUnrealClassDefinitionInfo * ClassInfo = Info - > AsClass ( ) ;
if ( ClassInfo = = nullptr )
{
LogError ( TEXT ( " FieldNofity function %s are only valid as UClass member function. " ) , * TargetFuncDef . GetName ( ) ) ;
return ;
}
const TArray < TSharedRef < FUnrealPropertyDefinitionInfo > > & Properties = TargetFuncDef . GetProperties ( ) ;
FUnrealPropertyDefinitionInfo * ReturnPropDef = TargetFuncDef . GetReturn ( ) ;
if ( Properties . Num ( ) > 1 | | ( Properties . Num ( ) = = 1 & & ReturnPropDef = = nullptr ) )
{
LogError ( TEXT ( " FieldNotify function %s must not have parameters. " ) , * TargetFuncDef . GetName ( ) ) ;
return ;
}
if ( ReturnPropDef = = nullptr )
{
LogError ( TEXT ( " FieldNotify function %s must return a value. " ) , * TargetFuncDef . GetName ( ) ) ;
return ;
}
if ( TargetFuncDef . HasAnyFunctionFlags ( FUNC_Event ) )
{
LogError ( TEXT ( " FieldNotify function %s cannot be a blueprint event. " ) , * TargetFuncDef . GetName ( ) ) ;
return ;
}
if ( ! TargetFuncDef . HasAnyFunctionFlags ( FUNC_BlueprintPure ) )
{
LogError ( TEXT ( " FieldNotify function %s must be pure. " ) , * TargetFuncDef . GetName ( ) ) ;
return ;
}
ClassInfo - > MarkHasFieldNotify ( ) ;
}
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
VerifyGetterSetterAccessorProperties ( TargetClassDef , * PropertyDef ) ;
if ( PropertyDef - > GetPropertyBase ( ) . bFieldNotify )
{
VerifyNotifyValueChangedProperties ( * PropertyDef ) ;
}
}
}
void FHeaderParser : : VerifyFunctionsMarkups ( FUnrealClassDefinitionInfo & TargetClassDef )
{
for ( TSharedRef < FUnrealFunctionDefinitionInfo > FunctionDef : TargetClassDef . GetFunctions ( ) )
{
if ( FunctionDef - > GetFunctionData ( ) . bFieldNotify )
{
VerifyNotifyValueChangedFunction ( * FunctionDef ) ;
}
}
}
/*-----------------------------------------------------------------------------
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 ;
bool bFieldNotify = 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. " ) ) ;
}
}
// 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 ;
case EVariableSpecifier : : FieldNotify :
{
bFieldNotify = true ;
}
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 ;
VarProperty . bFieldNotify = bFieldNotify ;
// 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 ;
}
// Setup additional property as well as script struct def flags
// for structs / properties being used for the RigVM.
// The Input / Output / Constant metadata tokens can be used to mark
// up an input / output pin of a RigVMNode. To allow authoring of those
// nodes we'll mark up the property as accessible in Blueprint / Python
// as well as make the struct a blueprint type.
if ( OwnerScriptStructDef )
{
const EPropertyFlags OriginalFlags = VarProperty . PropertyFlags ;
if ( VarProperty . MetaData . Contains ( NAME_ConstantText ) )
{
VarProperty . PropertyFlags | = CPF_Edit | CPF_EditConst | CPF_BlueprintVisible ;
}
if ( VarProperty . MetaData . Contains ( NAME_InputText ) | |
VarProperty . MetaData . Contains ( NAME_VisibleText ) )
{
VarProperty . PropertyFlags | = CPF_Edit | CPF_BlueprintVisible ;
}
if ( VarProperty . MetaData . Contains ( NAME_OutputText ) )
{
if ( ( VarProperty . PropertyFlags & CPF_BlueprintVisible ) = = 0 )
{
VarProperty . PropertyFlags | = CPF_BlueprintVisible | CPF_BlueprintReadOnly ;
}
}
if ( OriginalFlags ! = VarProperty . PropertyFlags & &
( ( ( VarProperty . PropertyFlags & CPF_BlueprintVisible ) ! = 0 ) | |
( ( VarProperty . PropertyFlags & CPF_BlueprintReadOnly ) ! = 0 ) ) )
{
if ( ! OwnerScriptStructDef - > GetBoolMetaDataHierarchical ( FHeaderParserNames : : NAME_BlueprintType ) )
{
OwnerScriptStructDef - > SetMetaData ( FHeaderParserNames : : NAME_BlueprintType , TEXT ( " true " ) ) ;
}
if ( ! VarProperty . MetaData . Contains ( NAME_Category ) )
{
static constexpr TCHAR PinsCategory [ ] = TEXT ( " Pins " ) ;
VarProperty . MetaData . Add ( NAME_Category , PinsCategory ) ;
}
}
}
}
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 ;
// parse enum type declared as a namespace
Tokens . GetToken ( Token ) ;
while ( Token . IsSymbol ( TEXT ( " :: " ) , ESearchCase : : CaseSensitive ) )
{
OutAccessorType + = Token . Value ;
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 '&', 'const' and retain '*'
do
{
if ( Token . TokenType = = ETokenType : : None )
{
return false ;
}
if ( Token . IsIdentifier ( ) )
{
if ( ! Token . IsIdentifier ( TEXT ( " const " ) , ESearchCase : : CaseSensitive ) )
{
break ;
}
// else skip const
}
// Support for passing values by ref for setters
else if ( ! Token . IsSymbol ( TEXT ( " & " ) , ESearchCase : : CaseSensitive ) )
{
OutAccessorType + = Token . Value ;
}
} while ( Tokens . GetToken ( Token ) ) ;
// 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 ;
}
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 ( ! 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 ( PropertyWithSetter - > GetArrayDimensions ( ) )
{
PropertyType + = TEXT ( " * " ) ;
}
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 ( ! PropertyWithGetter )
{
return false ;
}
FString ExtendedPropertyType ;
FString PropertyType = PropertyWithGetter - > GetCPPType ( & ExtendedPropertyType , CPPF_Implementation | CPPF_ArgumentOrReturnValue ) ;
PropertyType + = ExtendedPropertyType ;
if ( PropertyWithGetter - > GetArrayDimensions ( ) )
{
PropertyType + = TEXT ( " * " ) ;
}
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 ) ;
// look out for the upgrade info method
static const FString GetUpgradeInfoString = TEXT ( " GetUpgradeInfo " ) ;
static const FString RigVMStructUpgradeInfoString = TEXT ( " FRigVMStructUpgradeInfo " ) ;
if ( MethodInfo . ReturnType = = RigVMStructUpgradeInfoString & & MethodInfo . Name = = GetUpgradeInfoString )
{
FRigVMStructInfo & StructRigVMInfo = StructDef . GetRigVMInfo ( ) ;
StructRigVMInfo . bHasGetUpgradeInfoMethod = true ;
return ;
}
// look out for the next aggregate name method
static const FString GetNextAggregateNameString = TEXT ( " GetNextAggregateName " ) ;
if ( MethodInfo . Name = = GetNextAggregateNameString )
{
FRigVMStructInfo & StructRigVMInfo = StructDef . GetRigVMInfo ( ) ;
StructRigVMInfo . bHasGetNextAggregateNameMethod = true ;
return ;
}
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 " ) ;
const TCHAR * FHeaderParser : : FTScriptInterfaceText = TEXT ( " TScriptInterface " ) ;
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 !UE_RIGVM_UOBJECT_PROPERTIES_ENABLED
if ( PropertyDef - > GetPropertyBase ( ) . IsObject ( ) )
{
LogError ( TEXT ( " RigVM Struct '%s' - Member '%s' is a UObject - object types are not allowed on structs with RIGVM_METHOD. " ) , * StructDef . GetName ( ) , * Parameter . Name ) ;
}
# endif
# if !UE_RIGVM_UINTERFACE_PROPERTIES_ENABLED
if ( PropertyDef - > GetPropertyBase ( ) . IsInterface ( ) )
{
LogError ( TEXT ( " RigVM Struct '%s' - Member '%s' is a UInterface - interface types are not allowed on structs with RIGVM_METHOD. " ) , * StructDef . GetName ( ) , * Parameter . Name ) ;
}
# endif
if ( ! ExtendedCPPType . IsEmpty ( ) )
{
// we only support arrays & script interfaces - no maps or similar data structures
if ( MemberCPPType ! = TArrayText & & MemberCPPType ! = TEnumAsByteText & & MemberCPPType ! = FTScriptInterfaceText )
{
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 ( ) ) ;
}
}
// If the accessor tag was found but need to be autogenerated.
if ( NewPropDef . GetPropertyBase ( ) . bSetterTagFound & & NewPropDef . GetPropertyBase ( ) . SetterName . IsEmpty ( ) )
{
NewPropDef . GetPropertyBase ( ) . SetterName = FString : : Printf ( TEXT ( " Set%s " ) , * NewPropDef . GetName ( ) ) ;
}
if ( NewPropDef . GetPropertyBase ( ) . bGetterTagFound & & NewPropDef . GetPropertyBase ( ) . GetterName . IsEmpty ( ) )
{
NewPropDef . GetPropertyBase ( ) . GetterName = FString : : Printf ( TEXT ( " Get%s " ) , * 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 . IsNoExport ( ) ;
}
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 ( 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 ( ) . IsNoExport ( ) ;
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 ( ) . IsNoExport ( ) ;
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 ) ;
VerifyFunctionsMarkups ( 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 ;
}
const bool bCanImplementInBlueprints = InterfaceDef - > GetBoolMetaData ( NAME_IsBlueprintBase ) ;
const bool bCannotImplementInBlueprints = ( ! bCanImplementInBlueprints & & InterfaceDef - > HasMetaData ( NAME_IsBlueprintBase ) )
| | InterfaceDef - > HasMetaData ( NAME_CannotImplementInterfaceInBlueprint ) ;
// 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
// This is only relevant for bp-implementable interfaces, for native interfaces the interface-defined function is sufficient
if ( ! bImplemented
& & InterfaceFunctionDef - > HasAnyFunctionFlags ( FUNC_BlueprintCallable )
& & ! InterfaceFunctionDef - > HasAnyFunctionFlags ( FUNC_BlueprintEvent )
& & ! bCannotImplementInBlueprints )
{
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 ;
}