Files
UnrealEngineUWP/Engine/Source/Programs/UnrealHeaderTool/Private/HeaderParser.cpp
Marc Audy 5ce407d808 Copying //UE4/Dev-Framework to //UE4/Dev-Main (Source: //UE4/Dev-Framework @ 3358467)
#rb none
#lockdown Nick.Penwarden

==========================
MAJOR FEATURES + CHANGES
==========================

Change 3297108 on 2017/02/10 by Mieszko.Zielinski

	Added AISight's peripherial vision angle claming as well as marked up UI to not allow values from outside of [0,180] range #UE4

	#jira UE-41114

Change 3299467 on 2017/02/13 by Marc.Audy

	Don't try to update active sounds on audio thread if the audio component is not active. If these functions are callled from a constructor on an async loading thread it can cause a crash

Change 3300692 on 2017/02/13 by Marc.Audy

	no auto

Change 3301424 on 2017/02/14 by Marc.Audy

	Handle gateway expansion before the node matching loop
	#jira UE-41858

Change 3301547 on 2017/02/14 by Marc.Audy

	PR #3246: Added BindDelegate that supports functions with custom (static) arguments (Contributed by PhoenixBlack)
	#jira UE-41926

Change 3301557 on 2017/02/14 by Marc.Audy

	When passing null to Rename for the new name, maintain the OldName is possible
	#jira UE-41937

Change 3301676 on 2017/02/14 by Marc.Audy

	Fix pending occlusion async traces from crashing during shutdown
	#jira UE-41939

Change 3302705 on 2017/02/14 by Mieszko.Zielinski

	Removed 'PRAGMA_DISABLE_OPTIMIZATION' uccurences from AIModule #UE4

Change 3302898 on 2017/02/14 by Dan.Oconnor

	Fix double negative

Change 3302954 on 2017/02/14 by Dan.Oconnor

	Make sure we use a good version of the class

Change 3302977 on 2017/02/14 by Dan.Oconnor

	Optimization in reinstancer turned back on - 3302898 has fixed the regression

Change 3302984 on 2017/02/14 by Dan.Oconnor

	Relink classes that were not recompiled in a wave of the compilation manager - currently only happens for data only blueprints.

	This fixes issues in Odin when using the compilation  manager

Change 3303824 on 2017/02/15 by Richard.Hinckley

	Updating URL for FABRIK system information.

Change 3304284 on 2017/02/15 by Dan.Oconnor

	Build fix

Change 3304297 on 2017/02/15 by Dan.Oconnor

	Shadow variable fix

Change 3304465 on 2017/02/15 by Lukasz.Furman

	fixed handling pathfollowing's requests by FloatingPawnMovement
	#jira UE-41884

Change 3305031 on 2017/02/15 by Marc.Audy

	All objects should get PostLoadSubobjects calls, regardless of whether they are outered to a CDO or not
	#jira UE-41708

Change 3305505 on 2017/02/15 by Michael.Noland

	Blueprints: Fix a crash when opening a BP with a parent class that no longer exists (unguarded access to the parent class)

Change 3305506 on 2017/02/15 by Michael.Noland

	QAGame: Created some assets that reference a non-existent type to test 'gracefully' handling missing native class types

Change 3306091 on 2017/02/16 by Marc.Audy

	PR #3263: Fixed duplicate comment from OnAudioFinished (Contributed by FrostByteGER)
	#jira UE-42027

Change 3306574 on 2017/02/16 by Marc.Audy

	Linked To pins can belong to invalid nodes and fail to load, this shouldn't be considered fatal

Change 3307160 on 2017/02/16 by Marc.Audy

	Rename(null, null ... is sometimes used to just force a name out of the way, so in that case don't try and maintain old name.

Change 3307982 on 2017/02/16 by Michael.Noland

	QAGame: Added another test asset for missing classes (this time a missing node class placed in a BP)

Change 3308097 on 2017/02/16 by Michael.Noland

	Graph Editor: Instantly clear GraphNodeForMenu and GraphPinForMenu as soon as the menu is dismissed, fixing crashes and other odd issues after deleting pins
	#jira UE-41789

Change 3308303 on 2017/02/16 by Dan.Oconnor

	Make sure we don't call GetDefaultObject while compiling on a non-native class

Change 3308850 on 2017/02/17 by Mieszko.Zielinski

	Fully exposed NavModifierVolume as ENGINE_API #UE4

Change 3309624 on 2017/02/17 by Phillip.Kavan

	[UE-40443] Recursively emit ctor initialization code for nested default subobjects when nativizing a Blueprint class.

	change summary:
	- modified FEmitDefaultValueHelper::OuterGenerate() to recursively detect and handle nested default subobjects.

	#jira UE-40443

Change 3310475 on 2017/02/17 by Dan.Oconnor

	Split bluepint compilation into CompileClassLayout and CompileFunctions, fix class hierarchy after creating reinstancers in blueprintcompilation manager. Together this means we don't need to run RecompileBlueprintBytecode

Change 3310487 on 2017/02/17 by Dan.Oconnor

	Fix build error missed by preflgiht

Change 3310497 on 2017/02/17 by Dan.Oconnor

	More build fixes for things missed by preflight...

Change 3310635 on 2017/02/17 by Dan.Oconnor

	Remove unused parameter and add comment to blueprint compilation manager explaining abuse of bBeingCompiled

Change 3310639 on 2017/02/17 by Dan.Oconnor

	Shadow variable fixes, not sure why these are being detected now

Change 3311855 on 2017/02/20 by Marc.Audy

	Fix UChildActorComponent::ParentComponent being null on the client
	#jira UE-42140

Change 3312444 on 2017/02/20 by Marc.Audy

	Add a bAutoDestroy pin to BP Spawn Sound and Force Feedback nodes to allow users to reuse the created component
	#jira UE-41267

Change 3312691 on 2017/02/20 by mason.seay

	Deleting map now that bug has been fixed

Change 3312709 on 2017/02/20 by Phillip.Kavan

	[UE-39705] Fix broken collision shapes when cooking with optimized BP component data option.

	change summary:
	- modified FComponentInstancingDataUtils::RecursivePropertyGather() to exclude deprecated properties, since they won't be serialized on save.
	- modified FBlueprintCookedComponentInstancingData::LoadCachedPropertyDataForSerialization() to remove the PPF_UseDeprecatedProperties flag (these are no longer included in the delta).
	- modified UBlueprintGeneratedClass::CheckAndApplyComponentTemplateOverrides() to remove the PPF_UseDeprecatedProperties flag (was being incorrectly used here).
	- modified AActor::CreateComponentFromTemplateData() to remove the PPF_UseDeprecatedProperties flag (was being incorrectly used here; this caused deprecated property defaults to be copied out to the instance).
	- modified AActor::CreateComponentFromTemplateData() to append RF_PostLoad/RF_NeedPostLoadSubobjects and call PostDuplicate()/ConditionalPostLoad() on the new instance (needed to mirror what SDO does in the unoptimized case - for proper physics RB setup specifically, but may be other areas where that's needed).

	#jira UE-39705

Change 3313161 on 2017/02/20 by Mieszko.Zielinski

	PR #3272: Use Pawn for GetNavAgentPropertiesRef(). (Contributed by drelidan7)

Change 3314151 on 2017/02/21 by Mieszko.Zielinski

	fix to hlods complaining about missing nav collision in cooked builds #UE4

	Made sure hlod-generated StaticMeshes are marked as not having navigation data

	#jira UE-42034

Change 3314355 on 2017/02/21 by Marc.Audy

	Set error message back to be correctly about mobility
	#jira UE-42209

Change 3314566 on 2017/02/21 by Phillip.Kavan

	[UE-40801] Switch to an ensure() to potentially help diagnose a one-off assertion crash in the SCS editor if encountered again in a future release.

	#jira UE-40801

Change 3315459 on 2017/02/21 by Mike.Beach

	Updated marquee selection in graph editors. Ctrl dragging now inverts nodes' selection state (not only deselects them - holding alt is now for only deselection).

	#jira UE-16359

Change 3315546 on 2017/02/21 by Mike.Beach

	Mirroring CL 3294552

	Count "GeneratedStub" as a success for cooked file generation - ensures the saved asset gets recorded in the asset registry.

	#jira ODIN-5869

Change 3315554 on 2017/02/21 by Mike.Beach

	Do not generate NativizedAsset plugin files if no Blueprints were nativized (cut down on mod generate/cook time).

	#jira ODIN-6211

Change 3317225 on 2017/02/22 by mason.seay

	Enable Net Use Owner Frequency on blueprints.  This allows the client to use different weapons. Doesn't fix UE-42017 though.

Change 3317495 on 2017/02/22 by Marc.Audy

	Expose raw input device configurations to other modules by request

	#jira UE-42204

Change 3319966 on 2017/02/23 by Nick.Atamas

	Polished up the material reroute node:
	 - Removed some unnecessary widgets
	 - Centered the pin node

Change 3320099 on 2017/02/23 by Mike.Beach

	Guarding against passing self pins to referance parameters (it is not a property that is referencable, and would crash). Notifying the user through pin connection messages, and providing a script exception.

	#jira UE-40861

Change 3321227 on 2017/02/24 by Marc.Audy

	Just use name rather than going Name -> String -> TCHAR -> Name

Change 3321425 on 2017/02/24 by Marc.Audy

	Minor optimizations to avoid string construction when doing StaticFindObject and ResolveName

Change 3321630 on 2017/02/24 by Mike.Beach

	Removing reference notation from actor pointer param - allowing you to pass 'self' to Blueprint exposed function.

Change 3321845 on 2017/02/24 by Lukasz.Furman

	fixed navlink processor trace accepting only components with WorldStatic object type
	#ue4

Change 3322474 on 2017/02/24 by Aaron.McLeran

	UE-42345 Rewriting thumbnail renderer

Change 3322490 on 2017/02/24 by Aaron.McLeran

	UE-42345  Forgot to take abs of sample before averaging

Change 3323562 on 2017/02/27 by Mike.Beach

	Fixing bad merge, copying loop from //UE4/Main that accidently got replaced.

Change 3323685 on 2017/02/27 by Mike.Beach

	Preventing us from cross-binding editor & PIE actors when we fixup level script actor bindings (on duplicate for PIE).

	#jira UE-30816

Change 3323776 on 2017/02/27 by Marc.Audy

	Coding standard clean up pass

Change 3324050 on 2017/02/27 by Ben.Zeigler

	Fix issue with a StreamableHandle being cancelled while in progress leaving the in progress flag active. Added and improved error messages when streaming goes wrong
	Port of 3317217, 3315540, and 3314374 from UE4-Fortnite

Change 3324294 on 2017/02/27 by Ben.Zeigler

	Engine changes needed to support "Asset Management" UI:
	Add concept of "Manage" dependency to the Asset Registry, to represent that an asset like a texture is managed by a Primary Asset. This will be used to compute usage statistics and chunking
	Add ability for AssetManager to override the PrimaryAssetType/Id on a asset data loaded off disk. Needed so the asset audit tools work properly
	Significant performance improvements to the asset registry dependency gather, and correctly report as in progress while dependencies are still being gathered. On Fortnite it now finishes in 10 seconds instead of 100
	Add bUpdateDiskCacheAfterLoad option for the asset registry, if true (default) this will update the Asset Registry's disk cache when an object is loaded, only in the editor. This is so changes made in PostLoad are correctly mirrored in the disk cache
	Add PrimaryAssetType as a wrapper struct around FName to allow customizations and blueprint usage, clean up the noexport definitions for a few related classes
	Add Asset Manager code to create and query "Manage" references used for auditing and chunking
	Add code to read AssetManager scanning rules out of the AssetManagerSettings object, also settable in editor
	Made it so UWorlds are now PrimaryAssets of the type Map, and enable the AssetManager by default for all games
	Port of CL #3323720 and related fixes from Fortnite

Change 3324295 on 2017/02/27 by Ben.Zeigler

	Add AssetManagerEditor which contains the editor interface for the AssetManager system, and engine code needed to support it
	Add support for Management references to the Reference Viewer, and add ability to extend that context menu from plugins/games
	Add struct customizations for PrimaryAssetId and PrimaryAssetType
	Add AssetAuditBrowser window that shows a specialized asset picker for auditing, accessible from content browser, reference viewer, and main windows pane
	Add AssetAuditContext, which is a cleaned up port of the one from Paragon. This needs some more work before being final
	Expose PropertyCustomizationHelpers::MakePropertyComboBox which allows making an "enum-like" combo box for struct customizations, it now works much like the PropertyEditorAsset UI
	Add Custom Column support to AssetView/AssetPicker. This can be used to show runtime-generated column data
	Fix bug in SAssetView where column view did not work with a filter predicate, because the column view was generated before the deferred filter predicate run, leading to an empty filter
	Port of CL #3323722 and related fixes from Fortnite

Change 3324398 on 2017/02/27 by Ben.Zeigler

	CIS fix

Change 3324442 on 2017/02/27 by Ben.Zeigler

	Nonunity fix discovered while testing my nonunity fix

Change 3325465 on 2017/02/28 by Marc.Audy

	Expand RawInput to support up to 20 buttons

Change 3325468 on 2017/02/28 by Marc.Audy

	Fix CIS

Change 3325887 on 2017/02/28 by Phillip.Kavan

	[UE-41893] Implicitly nativize child Blueprints that override one or more BlueprintCallable functions from a parent Blueprint.

	change summary:
	- added FBlueprintEditorUtils::ShouldNativizeImplicitly()
	- modified FBlueprintGlobalOptionsDetails::IsNativizeEnabled() to disable the 'Nativize' checkbox when the BP is implicitly enabled
	- modified FBlueprintGlobalOptionsDetails::GetNativizeState() to set the 'Checked' state when the BP is implicitly enabled
	- modified FBlueprintGlobalOptionsDetails::GetNativizeTooltip() to set an alternate tooltip for the disabled state (when the BP is implicitly enabled)
	- modified FBlueprintNativeCodeGenModule::IsTargetedForReplacement() to ensure that implicitly-enabled BPs are flagged as selected for nativization

	#jira UE-41893

Change 3326713 on 2017/02/28 by Marc.Audy

	Update MAX_NUM_CONTROLLER_BUTTONS to match number of keys created

Change 3327688 on 2017/03/01 by Marc.Audy

	Fix spelling, remove autos

Change 3328139 on 2017/03/01 by Marc.Audy

	Win32 doesn't report the DeviceData in the same way that Win64 does, removing filtered check for now so that Win32 packaged games can use RawInput (4.15.1)

	#jira UE-42375

Change 3328550 on 2017/03/01 by Mike.Beach

	Typo fix in cast node tooltip.

Change 3328575 on 2017/03/01 by Nicholas.Blackford

	Submitting Tick Interval Functional Test

Change 3328972 on 2017/03/02 by Jack.Porter

	Fix for crash entering Landscape mode

	#jira UE-42497

Change 3329224 on 2017/03/02 by Nick.Bullard

	Removing Redirector from EngineTest project

Change 3330093 on 2017/03/02 by Mike.Beach

	Modified fix from Marc.Audy - Guarding against malformed graphs (missing their schema), which can happen in the middle of an undo transaction (removing the graph). Returning the graph's path name in this situation (instead of the display name), so we atleast have some semblance of context.

	#jira UE-42166

Change 3330306 on 2017/03/02 by Mike.Beach

	Replacing ArrayLibrary Get() calls in blueprints with a custom node, which can be toggled back and forth from returning by reference or by value.

	#jira UE-6451

Change 3330626 on 2017/03/02 by samuel.proctor

	Functional Test for Blueprint Containers

Change 3330690 on 2017/03/02 by Mike.Beach

	Modified the fix from CL 3308097 - cannot clear the edgraph pin context since many menu actions expect it be available still as the menu is clossing (menu's dismiss gets triggered before the action is executed).

	#jira UE-42500

Change 3330704 on 2017/03/02 by Mike.Beach

	CIS fix - fallout from CL 3330306

Change 3330875 on 2017/03/02 by Dan.Oconnor

	Iteration on compile manager - removed skeleton compile pass in favor of FastGenerateSkeletonClass (directly generate reflection data from blueprint source data - no graph cloning)

Change 3330892 on 2017/03/02 by Mike.Beach

	CIS fix for linux builds - include filename is case sensitive.

Change 3331585 on 2017/03/03 by Mike.Beach

	Fix for CIS issues (fallout from CL 3330306) - had success/failure return value flipped. Spuriously failing on deprecated node fixup.

Change 3333455 on 2017/03/06 by Ben.Zeigler

	Cleaned up version of CL #3332060, fixes crashes when calling StreamableManager::SynchronousLoad from inside a async PostLoad callback
	Also disable the "do sync load as async load" code in EDL, as EDL basically already does that internally
	Move the recursion guard inside async load tick outside of the EDL section, it's just as unsafe with EDL off

Change 3333484 on 2017/03/06 by Ben.Zeigler

	#jira UE-42312 Fix crash trying to read Searchable Name references to objects in the same package, now guess at package/object name

Change 3333553 on 2017/03/06 by Ben.Zeigler

	#jira UE-42387 Don't write out empty generated ini files for config files that are empty in both source and destination, this stops plugins without configs from ending up in cache

Change 3333697 on 2017/03/06 by Mike.Beach

	Resolving some CIS errors - fix for missed handling of split-struct pins (fallout from CL 3330306) on deprecated node conversion (mapping old pins to new pins).

Change 3334047 on 2017/03/06 by Ben.Zeigler

	#jira UE-42587 Now that we handle Add gameplay cues correctly by deferring them until after load, we also need to handle Remove cues, to avoid cues being stuck on permanently.

Change 3334228 on 2017/03/06 by Ben.Zeigler

	#jira UE-42153 Fix several crashes with gameplay tag query structs
	#jira UE-39760 Fix it to display tag query description on creation

Change 3335221 on 2017/03/07 by Lukasz.Furman

	fixed compilation errors for macros: UE_VLOG_MESH, UE_CVLOG_MESH
	#ue4

Change 3335733 on 2017/03/07 by dan.reynolds

	Fixing Attenuation Shape Material Reference

Change 3335918 on 2017/03/07 by Mike.Beach

	More deeply nesting an active world check in UMeshComponent::CacheMaterialParameterNameIndices(). Only guarding the parts that use the world (prior to this, we were blocking material parameter discovery, which was causing cooked content to loose material settings).

	#jira UE-42480

Change 3336053 on 2017/03/07 by zack.letters

	Moved and renamed test to meet naming convention and proper location

Change 3336087 on 2017/03/07 by Phillip.Kavan

	[UE-18618] Fix an ensure() misfire on PIE exit for listen server mode.

	change summary:
	- Modified UWorld::TransferBlueprintDebugReferences() to allow the LevelScript BP's target debug object reference to be reset to NULL when CreatePIEWorldBySavingToTemp() has recompiled it during the PIE startup process and autosaved the BP as a temporary.

	#jira UE-18618

Change 3336118 on 2017/03/07 by Phillip.Kavan

	Ensure that BP class component templates are included as preload dependencies where appropriate.

Change 3336418 on 2017/03/07 by Marc.Audy

	Set the PIEInstanceID before calling ConvertToPIEPackageName (4.15.1)
	#jira UE-42507

Change 3336529 on 2017/03/07 by dan.reynolds

	AEOverview UMG Interface

Change 3336729 on 2017/03/07 by Michael.Noland

	Blueprints: Changed a checkSlow() followed by unguarded access to an if and ensure() in BlueprintActionFilterImpl::IsDeprecated to prevent a potential crash in release if the node class is invalid for some reason
	#jira UE-42519

Change 3337054 on 2017/03/08 by Mieszko.Zielinski

	Fixed UGameplayTaskResource::AutoResourceID getting cleared on hot reload #UE4

Change 3337605 on 2017/03/08 by Mieszko.Zielinski

	PR #3345: Fix reversed comparison in FGameplayResourceSet::HasAllIDs (Contributed by hoelzl)

Change 3337612 on 2017/03/08 by Lina.Halper

	Commenting out ensure as this doesn't cause any harm and fix it up later by itself.
	- adding ticket for further investigation

	#rb: Martin.Wilson
	#jira: UE-42062

Change 3338353 on 2017/03/08 by Mike.Beach

	Undoing CL 3320099, and instead allowing self nodes to be plugged into const ref inputs. Now auto-generating ref terms for the self node (the input param expects an addressable UProperty). Skipping this for native functions, as UHT already does something similar.

	#jira UE-40861

Change 3340052 on 2017/03/09 by Marc.Audy

	Don't mark a blueprint dirty if the default value isn't actually set
	#jira UE-42511

Change 3340211 on 2017/03/09 by samuel.proctor

	Adding TMap/TSet tests for Containers Functional Test

Change 3340272 on 2017/03/09 by Marc.Audy

	auto removals
	small optimizations

Change 3340341 on 2017/03/09 by Marc.Audy

	Fortnite fixes for blueprint exposed editor only struct members
	#jira UE-42430

Change 3340356 on 2017/03/09 by Marc.Audy

	Do not allow blueprint exposed editor only struct members
	#jira UE-42430

Change 3340369 on 2017/03/09 by Mike.Beach

	Certain operations expect set/map elements to be constructed, instead of using an 'uninitialized' value (like with FStrings, previously this would blow up attempting to assign a value to an FString that hadn't been constructed). Fix is to construct the member when we make space in the container (emulating execSetArray).

	#jira UE-42572

Change 3340445 on 2017/03/09 by mason.seay

	Renamed and updated test map.  Also disabled tests until reviewed

Change 3340627 on 2017/03/09 by Marc.Audy

	Remove autos

Change 3340639 on 2017/03/09 by Dan.Oconnor

	Avoid CDO creation when asking if an object IsDefaultSubobject

Change 3340642 on 2017/03/09 by Marc.Audy

	Correctly maintain removed items from arrays when duplicating actors via T3D
	#jira UE-42278

Change 3340689 on 2017/03/09 by Dan.Oconnor

	Avoid UObject::Modify calls when renaming edgraph nodes as part of UEdGraphNode::PostLoad() or UEdGraph::MoveNodesToAnotherGraph

Change 3340709 on 2017/03/09 by Dan.Oconnor

	Remove misplace dClassDefaultObject null check for now

Change 3340710 on 2017/03/09 by Dan.Oconnor

	Avoid FindRedirectedPropertyName when performing StaticDuplicateObject

Change 3340728 on 2017/03/09 by Dan.Oconnor

	Null checking CDO so that we can duplicate a class with no CDO

Change 3342184 on 2017/03/10 by mason.seay

	Nav mesh generation test - not finished

Change 3342930 on 2017/03/13 by Mieszko.Zielinski

	Added missing undefining of local macros in VisualLoggerAutomationTests.cpp #UE4

Change 3343739 on 2017/03/13 by Marc.Audy

	Protect against ChildActorClass becoming null while ChildActorTemplate remains valid.

Change 3343758 on 2017/03/13 by Marc.Audy

	Ensure that when you change visibility, children also get marked dirty as needed.
	SetVisibility is no longer virtual, use OnVisibilityChanged in subclasses instead
	#jira UE-42240

Change 3343816 on 2017/03/13 by Mike.Beach

	Making sure we build CrashReporter for nativized clients.

	#jira UE-42056

Change 3343858 on 2017/03/13 by Phillip.Kavan

	Back out changelist 3336118 (per discussion) - did not solve the issue.

Change 3344218 on 2017/03/13 by Mike.Beach

	Patching some holes in the wildcard pin logic for our new array GetItem node (making sure the node properly retains its type).

Change 3344388 on 2017/03/13 by Mike.Beach

	Preventing make/break nodes from being in the context menu for structs that are not labeled 'BlueprintType' (still available if you drag off a node with a struct pin of that type).

	#jira UE-37971

Change 3344411 on 2017/03/13 by dan.reynolds

	AEOverviewMain update

	- Organized Variables

	- Added comments on level interface with UI script

Change 3344956 on 2017/03/14 by Marc.Audy

	Remove autos
	Slight optimization

Change 3345365 on 2017/03/14 by Mike.Beach

	In the Blueprint diff tool, no longer assuming that graph names are unique (using the outer path to find matching graphs between diff panels).

	#jira UE-42787

Change 3345565 on 2017/03/14 by Marc.Audy

	auto removal

Change 3345654 on 2017/03/14 by Marc.Audy

	Allow hierarchical metadata querying when HACK_HEADER_GENERATION is true

Change 3345771 on 2017/03/14 by Zak.Middleton

	#ue4 - Refactored CharacterMovementComponent determination of net send rate when combining moves into a virtual function GetClientNetSendDeltaTime(). Added configurable values to GameNetworkManager under [/Script/Engine.GameNetworkManager]:

	ClientNetSendMoveDeltaTime=0.0111f
	ClientNetSendMoveDeltaTime=0.0222f
	ClientNetSendMoveThrottleAtNetSpeed = 10000
	ClientNetSendMoveThrottleOverPlayerCount=10

	These are the default values maintained for backwards compat.

	Related to OR-36422.

Change 3346314 on 2017/03/14 by Dan.Oconnor

	Add two features to FBlueprintCompileReinstancer. Exposing it's CPFUO extensions and add a flag to avoid potentially unneeded CDO duplication.

Change 3346329 on 2017/03/14 by Dan.Oconnor

	Avoid CDO creation in UBlueprintGeneratedClass::PostLoad - rely instead on compiler

Change 3346436 on 2017/03/14 by Dan.Oconnor

	Compilation Manager iteration - improvements to reinstancing logic and postponement of reinstancing reference replacement until after loading has finished (done strictly to reduce the number of 'find references' calls). Behavior change is behind the GMinimalCompileOnLoad flag

Change 3346632 on 2017/03/14 by Ben.Zeigler

	Change StringClassReference customization to use MustImplement and BlueprintBaseOnly metadata, to match the metadata used by SubclassOf customization
	Add missing Class Property metadata to the metadata list

Change 3347525 on 2017/03/15 by Marc.Audy

	PR #3371: Fix for binding ability action to input component (Contributed by ryanjon2040)
	#jira UE-42810

Change 3347562 on 2017/03/15 by Phillip.Kavan

	[UE-32816] Support for value-based bitfield enum associations in the editor.

	notes:
	- default mode is still index-based, so there are no backwards-compatibility issues

	change summary:
	- new metadata key for flagging enums as value-based (UseEnumValuesAsMaskValuesInEditor)
	- modified SPropertyEditorNumeric::Construct() to include logic for handling value-based enum associations
	- modified SGraphPinInteger::Construct() to include logic for handling value-based enum associations
	- added default value fixup to UK2Node_BitmaskLiteral, so that changed/removed values get masked out on load
	- switched UK2Node_BitmaskLiteral::PostLoad() to Serialize(), so that default value fixup occurs before compilation

	#jira UE-32816

Change 3348030 on 2017/03/15 by Marc.Audy

	Remove experimental blueprintable components setting, they are supported fully

Change 3348034 on 2017/03/15 by Phillip.Kavan

	CIS fix.

Change 3348054 on 2017/03/15 by Marc.Audy

	Fix shadow error

Change 3348063 on 2017/03/15 by mason.seay

	Updateed bp logic to use asserts.  Added scenarios to descriptions of tests

Change 3348131 on 2017/03/15 by mason.seay

	Updating maps and reorganizing content

Change 3348146 on 2017/03/15 by Mike.Beach

	Making it so we can use DataTable variables as inputs in the GetDataTableRow node. The output pin is now a wildcard when the row type is undefined, and we throw an access error at runtime if the table and output type don't match.

Change 3348213 on 2017/03/15 by dan.reynolds

	AEOverview UMG Update

	- Added level selection persistence between categories (so you can pick and choose from multiple categories)

	- Added a clear all selections button

	- Added comments to the UMG BP

Change 3348344 on 2017/03/15 by Lukasz.Furman

	fixed missing path following result flag descriptions
	#ue4

Change 3348489 on 2017/03/15 by mason.seay

	Moved content and updated test descriptions

Change 3348496 on 2017/03/15 by Mike.Beach

	Keeping the new version of the GetArrayItem node from causing a stack overflow with wildcard reroute nodes.

Change 3348502 on 2017/03/15 by Ben.Zeigler

	#jira UE-42935 Fix several issues with GameplayTag and Container switch nodes crashing. Container didn't handling having multiple empty nodes correctly
	Fix general issue with Switch nodes where removing an execution pin with right click was not synchronizing the pin list properly
	Change it so the Container switch shows the simple tag string instead of Case 0, and change it to not quote by default for Container display strings

Change 3348504 on 2017/03/15 by Ben.Zeigler

	#jira UE-41554 Add GameplayTag initialization to InitializeObjectReferences if it hasn't been initialized yet, this is important so it gets initialized before being initialized from unsafe areas like Serialize

Change 3348512 on 2017/03/15 by Mike.Beach

	Reroute nodes connected to a new output, will propagate the type through its inputs (was previously treating the input's wildcard type as authoritative).

Change 3348513 on 2017/03/15 by Phillip.Kavan

	[UE-38979] Error out on an attempt to nativize a Blueprint class that also implements a native C++ interface with a pure virtual function declaration.

	change summary:
	- added TIsAbstract<T> for traits testing to see if native C++ types are abstract (in terms of C++, not UE4)
	- changed TCppStructOps::IsAbstract() to use TIsAbstract<T>
	- added UClass::CppClassOps to capture class-specific traits info for the underlying C++ class type
	- modified UClass::PurgeClass() to clean up class-specific traits info (if valid)
	- modified FNativeClassHeaderGenerator::ExportNativeGeneratedInitCode() to generate code to initialize class-specific traits info for compiled-in class types
	- modified FBlueprintNativeCodeGenModule::IsTargetedForReplacement() to throw an error during nativization if a target BP class is found to implement a native interface class that's also abstract (i.e. an interface class that declares one or more of its methods as pure virtual)
	- modified BlueprintActionFilterImpl::IsExtraneousInterfaceCall() to initially exclude any native interface class that is also abstract
	- modified FKismetEditorUtilities::CanBlueprintImplementInterface() to additionally exclude any native class that is also abstract
	- modified FBlueprintInterfaceFilter::IsClassAllowed() to additionally exclude any native class that is also abstract

	#jira UE-38979

Change 3348651 on 2017/03/15 by Mike.Beach

	Fixing the new GetDataTableRow node so that it'll give you the option of reroute nodes.

Change 3348684 on 2017/03/15 by Michael.Noland

	Blueprints: Allow string and text variables to be marked as multi-line

	PR #3294: UE-42147: Add multiline to BP view details (Contributed by projectgheist)

	#jira UE-42275

Change 3348691 on 2017/03/15 by Michael.Noland

	Cameras: Added support for specifying a default aspect ratio and whether or not to constrain to it in a camera manager subclass; useful when using custom view logic that doesn't source from a camera component as the view target
	PR #2593: Finish implementing aspect ratio handling for PlayerCameraManager (Contributed by CleanCut)
	#jira UE-33052

Change 3348698 on 2017/03/15 by Michael.Noland

	Removed a sprite reference from trigger shape classes and excluded some component references from camera rigs in cooked builds
	PR #2922: Ensuring editor data is not accessed when excluded from cook (Contributed by moritz-wundke)

	#jira UE-38484

Change 3348722 on 2017/03/15 by Dan.Oconnor

	Fix replacement bug - due to last minute refactor of this reference replacer call

Change 3348736 on 2017/03/15 by Michael.Noland

	Blueprints: Added missing include for UTextProperty (compiled fine locally both with the file checked out and the file unmodified)

Change 3348810 on 2017/03/15 by Michael.Noland

	Blueprints: Added support for seeing the user defined tooltip on get/set nodes for local variables
	PR #3256: UE-41098: Added UFunction argument (Contributed by projectgheist)

Change 3348811 on 2017/03/15 by Michael.Noland

	PR #3380: Added CancelAbility Blueprint node (Contributed by ryanjon2040)

	#jira UE-42904

Change 3348969 on 2017/03/15 by Dan.Oconnor

	Build fix

Change 3349023 on 2017/03/16 by Aaron.McLeran

	Copying //Tasks/UE4/Private-GDC17-Audio to Dev-Framework (//UE4/Dev-Framework)

Change 3349389 on 2017/03/16 by mason.seay

	Finished up Navigation map.  Improved Navmesh map (still needs some work before review)

Change 3349575 on 2017/03/16 by Marc.Audy

	Emit ScriptMacros.h in addition to ObjectMacros.h in generated headers

Change 3349628 on 2017/03/16 by Ben.Zeigler

	Add direct support for Chunk setting to AssetManager. If AssetManager exists and no game callback is set it uses the new, much faster method. Otherwise it falls back to the old one
	Fix some memory corruption issues in ChunkManifestGenerator where it was modifying a map while iterating it, could lead to assets ending up in multiple chunks accidentally
	Remove the "Old Cooker" entirely, it hasn't functioned since around 4.9
	Various fixes to AssetManagerEditorModule
	Convert ShooterGame to use the AssetManager for chunking

Change 3349629 on 2017/03/16 by Ben.Zeigler

	Change Fortnite to use the AssetManager chunking system, which simplifies the chunk 1 setup significantly
	Also includes changes made on Fortnite Branch as  CL #3323724:
	Fortnite changes to take advantage of the Manage dependency in the asset manager
	Move definition of asset types to ini from native, and simplify it so all zone themes are scanned, even if not used
	Make FeedbackBank a primary asset type. It's currently editor only as there are some outdated banks we don't want to cook

Change 3350043 on 2017/03/16 by Marc.Audy

	Fix Audio compile errors

Change 3350092 on 2017/03/16 by Dan.Oconnor

	Fix missing output parameters when the function result node is pruned

Change 3350190 on 2017/03/16 by Ben.Zeigler

	CIS fix

Change 3350707 on 2017/03/16 by Dan.Oconnor

	Add means of enabling BlueprintCompilationManager via editor ini. Wedging the check into LaunchEngineLoop because of assets that are loaded during engine initialization

Change 3350820 on 2017/03/16 by Joe.Conley

	Xenakis project: Setting GameMode to GameMode instead of None so the game doesn't crash on Play

Change 3350893 on 2017/03/16 by Dan.Oconnor

	Build fix

Change 3351017 on 2017/03/16 by Dan.Oconnor

	Using ordered arguments instead of named arguments improves load time in BP heavy projects

Change 3351056 on 2017/03/16 by Dan.Oconnor

	Avoiding Copies

Change 3351062 on 2017/03/16 by Dan.Oconnor

	Enable BlueprintCompilationManager by default - this is a major change in code path when loading uassets that contain blueprints

Change 3351770 on 2017/03/17 by Marc.Audy

	Fix CIS warnings

Change 3351818 on 2017/03/17 by Mike.Beach

	CopyPropertiesForUnrelatedObjects() will now only copy tagged data when the two objects truly are unrelated (different native base classes). We have to do this because the two native base classes may have different serialization methods that add/expect different data, which is not compatible with the other.

	#jira UE-35970

Change 3351918 on 2017/03/17 by Mike.Beach

	CIS fix - renaming local so it doesn't conflict with the one in the outer scope.

Change 3351931 on 2017/03/17 by Ben.Zeigler

	Make CoreRedirects a proper Automated Test, and fix a test failure with not handling : in the output string
	Fix legitimate regression where doing a package -> package rename would clear Outer, this was a result of a fix I made in Main a few weeks ago

Change 3351956 on 2017/03/17 by Dan.Oconnor

	Make sure result element is emptied when calling Intersect, Union, or Difference
	#jira UE-42993

Change 3352049 on 2017/03/17 by Ben.Zeigler

	#Jira UE-42118 Add RemoveGameplayTag to the tag blueprint library
	Delete (with redirector) redundant AddGameplayTagToContainer function that got accidentally added in parallel on Fortnite. Decided to keep the shorter TagContainer parameter name for both though

Change 3352065 on 2017/03/17 by Aaron.McLeran

	Fixing compile errors

	- deleting unused files
	- removing #pragma once in SSynthKnob.cpp
	- Making phonon have win64 whitelist to avoid compiling on other platforms

Change 3352100 on 2017/03/17 by Aaron.McLeran

	Fixing compile errors

	- Moving header file to public folder since it's used outside of module

Change 3352182 on 2017/03/17 by Ben.Zeigler

	#jira UE-39815 Fix several issues with renaming tags in the tag settings view, it now deletes redirectors properly when renaming or making a new tag that matches an existing redirector

Change 3352286 on 2017/03/17 by Ben.Zeigler

	#jira UE-39519 Add error messages when only one of GameMode/GameState is derived from the outdated parent classes
	Modified version of PR #3285: Add error log messages if the GameMode/GameState are mis-matched (Contributed by jwatte)

Change 3352299 on 2017/03/17 by Ben.Zeigler

	#jira UE-40544
	PR #3130: UE-40544: Check pause state if state change is allowed (Contributed by projectgheist)

Change 3352303 on 2017/03/17 by Ben.Zeigler

	#jira UE-40856
	Commit PR #3147:  Remove unnecessary directory separator for GetSaveGamePath  (Contributed by projectgheist)
	Remove unnecessary FString casting and in OpenGLDebugFrameDump.cpp there were FString multiplications that would never compile

Change 3352320 on 2017/03/17 by Ben.Zeigler

	#jira UE-40087 Fix it so console keybind can be used in shipping games with console enabled
	Commit PR #3079: Fix ALLOW_CONSOLE define usage (Contributed by KrisRedbeard)

Change 3352338 on 2017/03/17 by Ben.Zeigler

	#jira UE-42800
	PR #3367: Made CheatManager more useful for non-FPShooters (Contributed by crumblycake)

Change 3352352 on 2017/03/17 by Dan.Oconnor

	Emptying map instead of trying to remove an element when conversion of a value type fails - can't remove a single element until the map is rehashed
	#jira UE-42937

Change 3352581 on 2017/03/17 by Lukasz.Furman

	fixed memory leak in navmesh generators
	copy of CL# 3352356
	#ue4

Change 3352665 on 2017/03/17 by Aaron.McLeran

	Fixing build error

	- Adding virtual destructor to FSoundWaveSoundWaveAssetActionExtender
	- Also renamed the class to only include SoundWave once!
	- Fixing static analysis warning on null deref.

Change 3352685 on 2017/03/17 by Dan.Oconnor

	Fix for bad behavior of GetValues and GetKeys functions when there are gaps in a TMap (e.g. due to Remove calls)
	#jira UE-42547

Change 3352706 on 2017/03/17 by Aaron.McLeran

	Fixing build error

	Changing TSharedPtr<FSoundWaveSoundWaveAssetActionExtender> to TSharedPtr<ISoundWaveAssetActionExtensions>

Change 3352708 on 2017/03/17 by Dan.Oconnor

	Data only and interface blueprints need SkeletonGeneratedClass set on load so that they can be used by the BlueprintEditor

	#jira UE-43023

Change 3352860 on 2017/03/17 by Lukasz.Furman

	fixed memory leak in navmesh generators
	copy of CL# 3352849
	#ue4

Change 3352967 on 2017/03/17 by Dan.Oconnor

	Avoid tagging blueprints as modified while compiling with the new compilation manager. Leaving old code path unaffected, although it may benefit from this change.

	#jira UE-43027

Change 3352979 on 2017/03/17 by Dan.Oconnor

	Static analysis driven fixes

	#jira UE-43044

Change 3352987 on 2017/03/17 by Aaron.McLeran

	Fixing build error

	- Removing myo from other platforms, win64 only

Change 3353234 on 2017/03/18 by Marc.Audy

	Fix Win32 build

Change 3353344 on 2017/03/19 by Marc.Audy

	Fix cyclic includes in new Audio code

Change 3353350 on 2017/03/19 by Marc.Audy

	Disable static analysis for myo third party code

Change 3353750 on 2017/03/20 by Marc.Audy

	Fix additional cyclic include

Change 3353926 on 2017/03/20 by Mieszko.Zielinski

	Made FNavAgentProperties::GetExtent return INVALID_NAVEXTENT if prop's AgentRadius is not set #UE4

	This results in using FNavAgentProperties::DefaultProperties in navigation system queries to fallback to default query extent.

	#jira UE-18493

Change 3354249 on 2017/03/20 by Mike.Beach

	Raising a UHT error if you use a non-byte enum type in a Blueprint function. Blueprints currently only support uint8 enums (already an error if you tag the enum with 'BlueprintType', this error just emulates/extends that one).

	#jira UE-42479

Change 3354464 on 2017/03/20 by Dan.Oconnor

	Fix missing source path when using compilation manager

Change 3354499 on 2017/03/20 by Dan.Oconnor

	Disable compilation manager

Change 3354620 on 2017/03/20 by Ben.Zeigler

	#jira UE-43087 Fix crash when calling HasGPUEmitter on a Server build, this is newly an issue because it is calling GetAssetRegistryTags in more places than it used to

Change 3354714 on 2017/03/20 by Michael.Noland

	PR #3352: Fixed issue with diffed Blueprints being searchable (Contributed by MichaelSchoell)

	#jira UE-42655

Change 3354718 on 2017/03/20 by Michael.Noland

	Engine: Change FViewport::IsGameRenderingEnabled to be static
	PR #3317: FViewport::IsGameRenderingEnabled (Contributed by tomix1024)

	#jira UE-42471

Change 3354721 on 2017/03/20 by Michael.Noland

	PR #3293: Made GetDefaultLocale accessible in blueprint (Contributed by derekvanvliet)

	#jira UE-42274

Change 3354907 on 2017/03/20 by Aaron.McLeran

	Fixing content in xenakis map

Change 3355223 on 2017/03/20 by Ben.Zeigler

	#jira UE-43096 Fix crash when trying to ResolveName a path that ends in . (apparently when you LoadObject empty string, it ends up trying to load "." before giving up

Change 3355297 on 2017/03/20 by Dan.Oconnor

	Fix incorrect flag settings from fast skeleton path.. this is part of the fix for UE-43083

Change 3355373 on 2017/03/20 by Michael.Noland

	PR #3222: Allow Blueprint Variables to be Readonly (Contributed by FrostByteGER)

	#jira UE-41640

Change 3355417 on 2017/03/20 by Ben.Zeigler

	Fix formatting bug where I forgot some braces

Change 3355462 on 2017/03/20 by Aaron.McLeran

	UE-43046 Property type changed with no possible conversion

	Resaved asset in question

Change 3355629 on 2017/03/20 by Dan.Oconnor

	Don't warn the user when their return node that has no pins (other than an exec pin). These return nodes cannot be deleted and connecting them does nothing. Prior to recent changes the warning never fired because the return node would be pruned and not validated.

Change 3355631 on 2017/03/20 by Dan.Oconnor

	Fix compilation results spam in compilation manager. Scoped compiler events (e.g. BP_SCOPED_COMPILER_EVENT_STAT(EKismetCompilerStats_CompileTime);) will flush the results log if no 'event' has been started. Timing data collected via this mechanism will not be useful (can only measure entire call to ::Flush in compilation manager)

Change 3356127 on 2017/03/21 by Richard.Hinckley

	#jira UEDOC-4711
	Updated an invalid/old URL in a comment to a valid/current URL.

Change 3356193 on 2017/03/21 by Marc.Audy

	Temporarily remove editor only properties in FCameraFocusSettings until we correctly no longer create pins for struct properties that are not exposed to blueprints
	#jira UE-43420

Change 3356222 on 2017/03/21 by Marc.Audy

	Expose new attenuation settings to blueprints to resolve cook warnings.

Change 3356286 on 2017/03/21 by Richard.Hinckley

	#jira UEDOC-4711
	Selected a different URL for the update.

Change 3356339 on 2017/03/21 by Marc.Audy

	Delete unconnected return nodes to fix fortnite cook warnings

Change 3356827 on 2017/03/21 by Ben.Zeigler

	Explicitly disable copy operations for streamable manager objects. This may be causing some obscure crashes like WEX-5182 but I am not sure how the copy constructor would be getting called. Either way it's unsafe
	Put in protection against passing in duplicate items to RequestAsyncLoad, which is another possible cause of internal data corruption
	Add some more ensures to track down possible issues with handle corruption

Change 3356920 on 2017/03/21 by Ben.Zeigler

	Fix ensure just checked in to not go off when handles are halfway through being cancelled

Change 3358152 on 2017/03/22 by Phillip.Kavan

	#jira UE-43102 - Fix an occasional crash on load in nativized EDL-enabled builds with non-nativized child BPs.

	Change summary:
	- Modified AActor::PostLoadSubobjects() to skip the CheckAndApplyComponentTemplateOverrides() call in the CDO case; at that point the ICH may not be fully loaded, but we don't require the non-nativized child BP's CDO to be fixed up anyway.

[CL 3358685 by Marc Audy in Main branch]
2017-03-22 12:57:30 -04:00

8827 lines
276 KiB
C++

// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
#include "HeaderParser.h"
#include "UnrealHeaderTool.h"
#include "HAL/FileManager.h"
#include "Misc/CommandLine.h"
#include "Misc/ConfigCacheIni.h"
#include "Misc/FeedbackContext.h"
#include "UObject/Interface.h"
#include "ParserClass.h"
#include "GeneratedCodeVersion.h"
#include "ClassDeclarationMetaData.h"
#include "ProfilingDebugging/ScopedTimers.h"
#include "NativeClassExporter.h"
#include "Classes.h"
#include "StringUtils.h"
#include "Misc/DefaultValueHelper.h"
#include "Manifest.h"
#include "Math/UnitConversion.h"
#include "FileLineException.h"
#include "Containers/EnumAsByte.h"
#include "Containers/Algo/FindSortedStringCaseInsensitive.h"
#include "Specifiers/CheckedMetadataSpecifiers.h"
#include "Specifiers/FunctionSpecifiers.h"
#include "Specifiers/InterfaceSpecifiers.h"
#include "Specifiers/StructSpecifiers.h"
#include "Specifiers/VariableSpecifiers.h"
#include "UHTMakefile/UHTMakefile.h"
double GPluginOverheadTime = 0.0;
double GHeaderCodeGenTime = 0.0;
/*-----------------------------------------------------------------------------
Constants & declarations.
-----------------------------------------------------------------------------*/
/**
* Data struct that annotates source files that failed during parsing.
*/
class FFailedFilesAnnotation
{
public:
/**
* Gets annotation state for given source file.
*/
bool Get(FUnrealSourceFile* SourceFile) const
{
return AnnotatedSet.Contains(SourceFile);
}
/**
* Sets annotation state to true for given source file.
*/
void Set(FUnrealSourceFile* SourceFile)
{
AnnotatedSet.Add(SourceFile);
}
private:
// Annotation set.
TSet<FUnrealSourceFile*> AnnotatedSet;
} static FailedFilesAnnotation;
enum {MAX_ARRAY_SIZE=2048};
static const FName NAME_ToolTip(TEXT("ToolTip"));
EGeneratedCodeVersion FHeaderParser::DefaultGeneratedCodeVersion = EGeneratedCodeVersion::V1;
TMap<UClass*, ClassDefinitionRange> ClassDefinitionRanges;
/**
* Dirty hack global variable to allow different result codes passed through
* exceptions. Needs to be fixed in future versions of UHT.
*/
extern ECompilationResult::Type GCompilationResult;
/*-----------------------------------------------------------------------------
Utility functions.
-----------------------------------------------------------------------------*/
namespace
{
bool ProbablyAMacro(const TCHAR* Identifier)
{
// 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.
TCHAR MulticastDelegateStart[] = TEXT("DECLARE_MULTICAST_DELEGATE");
if (!FCString::Strncmp(Identifier, MulticastDelegateStart, ARRAY_COUNT(MulticastDelegateStart) - 1))
{
return true;
}
TCHAR DelegateStart[] = TEXT("DECLARE_DELEGATE");
if (!FCString::Strncmp(Identifier, DelegateStart, ARRAY_COUNT(DelegateStart) - 1))
{
return true;
}
TCHAR DelegateEvent[] = TEXT("DECLARE_EVENT");
if (!FCString::Strncmp(Identifier, DelegateEvent, ARRAY_COUNT(DelegateEvent) - 1))
{
return true;
}
// Failing that, we'll guess about it being a macro based on it being a fully-capitalized identifier.
while (TCHAR Ch = *++Identifier)
{
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.TokenType != TOKEN_Identifier)
{
return false;
}
// Macros must start with a capitalized alphanumeric character or underscore
TCHAR FirstChar = Token.Identifier[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.
const TCHAR* IdentPtr = Token.Identifier;
int32 UnderscoreCount = 0;
while (TCHAR Ch = *++IdentPtr)
{
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 whitelisting 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 (IdentPtr - Token.Identifier > 4 && IdentPtr[-4] == TEXT('_') && IdentPtr[-3] == TEXT('A') && IdentPtr[-2] == TEXT('P') && IdentPtr[-1] == TEXT('I'))
{
return false;
}
// Ignore certain known macros or identifiers that look like macros.
// IMPORTANT: needs to be in lexicographical order.
static const TCHAR* Whitelist[] =
{
TEXT("FORCEINLINE_DEBUGGABLE"),
TEXT("FORCEINLINE_STATS"),
TEXT("SIZE_T")
};
if (Algo::FindSortedStringCaseInsensitive(Token.Identifier, Whitelist, ARRAY_COUNT(Whitelist)) >= 0)
{
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);
bool bResult = PossibleBracketToken.TokenType != TOKEN_Symbol || FCString::Strcmp(PossibleBracketToken.Identifier, TEXT("("));
return bResult;
}
/**
* 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(FFuncInfo& FuncInfo, const TArray<FString>& Identifiers)
{
static const TCHAR IdTag [] = TEXT("Id");
static const TCHAR ResponseIdTag[] = TEXT("ResponseId");
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, ARRAY_COUNT(IdTag) - 1) == 0)
{
int32 TempInt = FCString::Atoi(Equals + 1);
if (TempInt <= 0 || TempInt > MAX_uint16)
{
FError::Throwf(TEXT("Invalid network identifier %s for function"), IdentifierPtr);
}
FuncInfo.RPCId = TempInt;
}
else if (FCString::Strnicmp(IdentifierPtr, ResponseIdTag, ARRAY_COUNT(ResponseIdTag) - 1) == 0)
{
int32 TempInt = FCString::Atoi(Equals + 1);
if (TempInt <= 0 || TempInt > MAX_uint16)
{
FError::Throwf(TEXT("Invalid network identifier %s for function"), IdentifierPtr);
}
FuncInfo.RPCResponseId = TempInt;
}
}
else
{
// Assume it's an endpoint name
if (FuncInfo.EndpointName.Len())
{
FError::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(FFuncInfo& FuncInfo, const TArray<FPropertySpecifier>& Specifiers)
{
bool bSpecifiedUnreliable = false;
for (const FPropertySpecifier& Specifier : Specifiers)
{
switch ((EFunctionSpecifier)Algo::FindSortedStringCaseInsensitive(*Specifier.Key, GFunctionSpecifierStrings))
{
default:
{
FError::Throwf(TEXT("Unknown function specifier '%s'"), *Specifier.Key);
}
break;
case EFunctionSpecifier::BlueprintNativeEvent:
{
if (FuncInfo.FunctionFlags & FUNC_Net)
{
FError::Throwf(TEXT("BlueprintNativeEvent functions cannot be replicated!") );
}
else if ( (FuncInfo.FunctionFlags & FUNC_BlueprintEvent) && !(FuncInfo.FunctionFlags & FUNC_Native) )
{
// already a BlueprintImplementableEvent
FError::Throwf(TEXT("A function cannot be both BlueprintNativeEvent and BlueprintImplementableEvent!") );
}
else if ( (FuncInfo.FunctionFlags & FUNC_Private) )
{
FError::Throwf(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)
{
FError::Throwf(TEXT("BlueprintImplementableEvent functions cannot be replicated!") );
}
else if ( (FuncInfo.FunctionFlags & FUNC_BlueprintEvent) && (FuncInfo.FunctionFlags & FUNC_Native) )
{
// already a BlueprintNativeEvent
FError::Throwf(TEXT("A function cannot be both BlueprintNativeEvent and BlueprintImplementableEvent!") );
}
else if ( (FuncInfo.FunctionFlags & FUNC_Private) )
{
FError::Throwf(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 )
{
FError::Throwf(TEXT("Exec functions cannot be replicated!") );
}
}
break;
case EFunctionSpecifier::SealedEvent:
{
FuncInfo.bSealedEvent = true;
}
break;
case EFunctionSpecifier::Server:
{
if ((FuncInfo.FunctionFlags & FUNC_BlueprintEvent) != 0)
{
FError::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 )
{
FError::Throwf(TEXT("Exec functions cannot be replicated!") );
}
}
break;
case EFunctionSpecifier::Client:
{
if ((FuncInfo.FunctionFlags & FUNC_BlueprintEvent) != 0)
{
FError::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)
{
FError::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)
{
FError::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(FuncInfo, Specifier.Values);
if (FuncInfo.EndpointName.Len() == 0)
{
FError::Throwf(TEXT("ServiceRequest needs to specify an endpoint name"));
}
}
break;
case EFunctionSpecifier::ServiceResponse:
{
if ((FuncInfo.FunctionFlags & FUNC_BlueprintEvent) != 0)
{
FError::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(FuncInfo, Specifier.Values);
if (FuncInfo.EndpointName.Len() == 0)
{
FError::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::BlueprintPure:
{
bool bIsPure = true;
if (Specifier.Values.Num() == 1)
{
FString IsPureStr = Specifier.Values[0];
bIsPure = IsPureStr.ToBool();
}
// This function can be called, and is also pure.
FuncInfo.FunctionFlags |= FUNC_BlueprintCallable;
if (bIsPure)
{
FuncInfo.FunctionFlags |= FUNC_BlueprintPure;
}
else
{
FuncInfo.bForceBlueprintImpure = true;
}
}
break;
case EFunctionSpecifier::BlueprintAuthorityOnly:
{
FuncInfo.FunctionFlags |= FUNC_BlueprintAuthorityOnly;
}
break;
case EFunctionSpecifier::BlueprintCosmetic:
{
FuncInfo.FunctionFlags |= FUNC_BlueprintCosmetic;
}
break;
case EFunctionSpecifier::WithValidation:
{
FuncInfo.FunctionFlags |= FUNC_NetValidate;
if (Specifier.Values.Num())
{
FuncInfo.CppValidationImplName = Specifier.Values[0];
}
}
break;
}
}
if (FuncInfo.FunctionFlags & FUNC_Net)
{
// Network replicated functions are always events
FuncInfo.FunctionFlags |= FUNC_Event;
check(!(FuncInfo.FunctionFlags & (FUNC_BlueprintEvent | FUNC_Exec)));
bool bIsNetService = !!(FuncInfo.FunctionFlags & (FUNC_NetRequest | FUNC_NetResponse));
bool bIsNetReliable = !!(FuncInfo.FunctionFlags & FUNC_NetReliable);
if ( FuncInfo.FunctionFlags & FUNC_Static )
FError::Throwf(TEXT("Static functions can't be replicated") );
if (!bIsNetReliable && !bSpecifiedUnreliable && !bIsNetService)
FError::Throwf(TEXT("Replicated function: 'reliable' or 'unreliable' is required"));
if (bIsNetReliable && bSpecifiedUnreliable && !bIsNetService)
FError::Throwf(TEXT("'reliable' and 'unreliable' are mutually exclusive"));
}
else if (FuncInfo.FunctionFlags & FUNC_NetReliable)
{
FError::Throwf(TEXT("'reliable' specified without 'client' or 'server'"));
}
else if (bSpecifiedUnreliable)
{
FError::Throwf(TEXT("'unreliable' specified without 'client' or 'server'"));
}
if (FuncInfo.bSealedEvent && !(FuncInfo.FunctionFlags & FUNC_Event))
{
FError::Throwf(TEXT("SealedEvent may only be used on events"));
}
if (FuncInfo.bSealedEvent && FuncInfo.FunctionFlags & FUNC_BlueprintEvent)
{
FError::Throwf(TEXT("SealedEvent cannot be used on Blueprint events"));
}
if (FuncInfo.bForceBlueprintImpure && (FuncInfo.FunctionFlags & FUNC_BlueprintPure) != 0)
{
FError::Throwf(TEXT("BlueprintPure (or BlueprintPure=true) and BlueprintPure=false should not both appear on the same function, they are mutually exclusive"));
}
}
void AddEditInlineMetaData(TMap<FName, FString>& MetaData)
{
MetaData.Add(TEXT("EditInline"), TEXT("true"));
}
const TCHAR* GetHintText(EVariableCategory::Type 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:
FError::Throwf(TEXT("Unknown variable category"));
}
// Unreachable
check(false);
return nullptr;
}
// Check to see if anything in the class hierarchy passed in has CLASS_DefaultToInstanced
bool DoesAnythingInHierarchyHaveDefaultToInstanced(UClass* TestClass)
{
bool bDefaultToInstanced = false;
UClass* Search = TestClass;
while (!bDefaultToInstanced && (Search != NULL))
{
bDefaultToInstanced = Search->HasAnyClassFlags(CLASS_DefaultToInstanced);
if (!bDefaultToInstanced && !Search->HasAnyClassFlags(CLASS_Intrinsic | CLASS_Parsed))
{
// The class might not have been parsed yet, look for declaration data.
TSharedRef<FClassDeclarationMetaData>* ClassDeclarationDataPtr = GClassDeclarations.Find(Search->GetFName());
if (ClassDeclarationDataPtr)
{
bDefaultToInstanced = !!((*ClassDeclarationDataPtr)->ClassFlags & CLASS_DefaultToInstanced);
}
}
Search = Search->GetSuperClass();
}
return bDefaultToInstanced;
}
UProperty* CreateVariableProperty(FPropertyBase& VarProperty, UObject* Scope, FName Name, EObjectFlags ObjectFlags, EVariableCategory::Type VariableCategory, FUHTMakefile& UHTMakefile, FUnrealSourceFile* UnrealSourceFile)
{
// Check if it's an enum class property
if (const EUnderlyingEnumType* EnumPropType = GEnumUnderlyingTypes.Find(VarProperty.Enum))
{
FPropertyBase UnderlyingProperty = VarProperty;
UnderlyingProperty.Enum = nullptr;
switch (*EnumPropType)
{
case EUnderlyingEnumType::int8: UnderlyingProperty.Type = CPT_Int8; break;
case EUnderlyingEnumType::int16: UnderlyingProperty.Type = CPT_Int16; break;
case EUnderlyingEnumType::int32: UnderlyingProperty.Type = CPT_Int; break;
case EUnderlyingEnumType::int64: UnderlyingProperty.Type = CPT_Int64; break;
case EUnderlyingEnumType::uint8: UnderlyingProperty.Type = CPT_Byte; break;
case EUnderlyingEnumType::uint16: UnderlyingProperty.Type = CPT_UInt16; break;
case EUnderlyingEnumType::uint32: UnderlyingProperty.Type = CPT_UInt32; break;
case EUnderlyingEnumType::uint64: UnderlyingProperty.Type = CPT_UInt64; break;
case EUnderlyingEnumType::Unspecified: UnderlyingProperty.Type = CPT_Int; break;
default:
check(false);
}
if (*EnumPropType == EUnderlyingEnumType::Unspecified)
{
UnderlyingProperty.IntType = EIntType::Unsized;
}
UEnumProperty* Result = new (EC_InternalUseOnlyConstructor, Scope, Name, ObjectFlags) UEnumProperty(FObjectInitializer());
UNumericProperty* UnderlyingProp = CastChecked<UNumericProperty>(CreateVariableProperty(UnderlyingProperty, Result, TEXT("UnderlyingType"), ObjectFlags, VariableCategory, UHTMakefile, UnrealSourceFile));
Result->UnderlyingProp = UnderlyingProp;
Result->Enum = VarProperty.Enum;
UHTMakefile.AddEnumProperty(UnrealSourceFile, Result);
return Result;
}
switch (VarProperty.Type)
{
case CPT_Byte:
{
UByteProperty* Result = new (EC_InternalUseOnlyConstructor, Scope, Name, ObjectFlags) UByteProperty(FObjectInitializer());
Result->Enum = VarProperty.Enum;
UHTMakefile.AddByteProperty(UnrealSourceFile, Result);
check(VarProperty.IntType == EIntType::Sized);
return Result;
}
case CPT_Int8:
{
UInt8Property* Result = new (EC_InternalUseOnlyConstructor, Scope, Name, ObjectFlags) UInt8Property(FObjectInitializer());
UHTMakefile.AddInt8Property(UnrealSourceFile, Result);
check(VarProperty.IntType == EIntType::Sized);
return Result;
}
case CPT_Int16:
{
UInt16Property* Result = new (EC_InternalUseOnlyConstructor, Scope, Name, ObjectFlags) UInt16Property(FObjectInitializer());
UHTMakefile.AddInt16Property(UnrealSourceFile, Result);
check(VarProperty.IntType == EIntType::Sized);
return Result;
}
case CPT_Int:
{
UIntProperty* Result = new (EC_InternalUseOnlyConstructor, Scope, Name, ObjectFlags) UIntProperty(FObjectInitializer());
UHTMakefile.AddIntProperty(UnrealSourceFile, Result);
if (VarProperty.IntType == EIntType::Unsized)
{
GUnsizedProperties.Add(Result);
}
return Result;
}
case CPT_Int64:
{
UInt64Property* Result = new (EC_InternalUseOnlyConstructor, Scope, Name, ObjectFlags) UInt64Property(FObjectInitializer());
UHTMakefile.AddInt64Property(UnrealSourceFile, Result);
check(VarProperty.IntType == EIntType::Sized);
return Result;
}
case CPT_UInt16:
{
UUInt16Property* Result = new (EC_InternalUseOnlyConstructor, Scope, Name, ObjectFlags) UUInt16Property(FObjectInitializer());
UHTMakefile.AddUInt16Property(UnrealSourceFile, Result);
check(VarProperty.IntType == EIntType::Sized);
return Result;
}
case CPT_UInt32:
{
UUInt32Property* Result = new (EC_InternalUseOnlyConstructor, Scope, Name, ObjectFlags) UUInt32Property(FObjectInitializer());
UHTMakefile.AddUInt32Property(UnrealSourceFile, Result);
if (VarProperty.IntType == EIntType::Unsized)
{
GUnsizedProperties.Add(Result);
}
return Result;
}
case CPT_UInt64:
{
UUInt64Property* Result = new (EC_InternalUseOnlyConstructor, Scope, Name, ObjectFlags) UUInt64Property(FObjectInitializer());
UHTMakefile.AddUInt64Property(UnrealSourceFile, Result);
check(VarProperty.IntType == EIntType::Sized);
return Result;
}
case CPT_Bool:
{
UBoolProperty* Result = new (EC_InternalUseOnlyConstructor, Scope, Name, ObjectFlags) UBoolProperty(FObjectInitializer());
Result->SetBoolSize(sizeof(bool), true);
UHTMakefile.AddBoolProperty(UnrealSourceFile, Result);
return Result;
}
case CPT_Bool8:
{
UBoolProperty* Result = new (EC_InternalUseOnlyConstructor, Scope, Name, ObjectFlags) UBoolProperty(FObjectInitializer());
Result->SetBoolSize((VariableCategory == EVariableCategory::Return) ? sizeof(bool) : sizeof(uint8), VariableCategory == EVariableCategory::Return);
UHTMakefile.AddBoolProperty(UnrealSourceFile, Result);
return Result;
}
case CPT_Bool16:
{
UBoolProperty* Result = new (EC_InternalUseOnlyConstructor, Scope, Name, ObjectFlags) UBoolProperty(FObjectInitializer());
Result->SetBoolSize((VariableCategory == EVariableCategory::Return) ? sizeof(bool) : sizeof(uint16), VariableCategory == EVariableCategory::Return);
UHTMakefile.AddBoolProperty(UnrealSourceFile, Result);
return Result;
}
case CPT_Bool32:
{
UBoolProperty* Result = new (EC_InternalUseOnlyConstructor, Scope, Name, ObjectFlags) UBoolProperty(FObjectInitializer());
Result->SetBoolSize((VariableCategory == EVariableCategory::Return) ? sizeof(bool) : sizeof(uint32), VariableCategory == EVariableCategory::Return);
UHTMakefile.AddBoolProperty(UnrealSourceFile, Result);
return Result;
}
case CPT_Bool64:
{
UBoolProperty* Result = new (EC_InternalUseOnlyConstructor, Scope, Name, ObjectFlags) UBoolProperty(FObjectInitializer());
Result->SetBoolSize((VariableCategory == EVariableCategory::Return) ? sizeof(bool) : sizeof(uint64), VariableCategory == EVariableCategory::Return);
UHTMakefile.AddBoolProperty(UnrealSourceFile, Result);
return Result;
}
case CPT_Float:
{
UFloatProperty* Result = new (EC_InternalUseOnlyConstructor, Scope, Name, ObjectFlags) UFloatProperty(FObjectInitializer());
UHTMakefile.AddFloatProperty(UnrealSourceFile, Result);
return Result;
}
case CPT_Double:
{
UDoubleProperty* Result = new (EC_InternalUseOnlyConstructor, Scope, Name, ObjectFlags) UDoubleProperty(FObjectInitializer());
UHTMakefile.AddDoubleProperty(UnrealSourceFile, Result);
return Result;
}
case CPT_ObjectReference:
check(VarProperty.PropertyClass);
if (VarProperty.PropertyClass->IsChildOf(UClass::StaticClass()))
{
UClassProperty* Result = new (EC_InternalUseOnlyConstructor, Scope, Name, ObjectFlags) UClassProperty(FObjectInitializer());
Result->MetaClass = VarProperty.MetaClass;
Result->PropertyClass = VarProperty.PropertyClass;
UHTMakefile.AddClassProperty(UnrealSourceFile, Result);
return Result;
}
else
{
if (DoesAnythingInHierarchyHaveDefaultToInstanced(VarProperty.PropertyClass))
{
VarProperty.PropertyFlags |= CPF_InstancedReference;
AddEditInlineMetaData(VarProperty.MetaData);
}
UObjectProperty* Result = new (EC_InternalUseOnlyConstructor, Scope, Name, ObjectFlags) UObjectProperty(FObjectInitializer());
Result->PropertyClass = VarProperty.PropertyClass;
UHTMakefile.AddObjectProperty(UnrealSourceFile, Result);
return Result;
}
case CPT_WeakObjectReference:
{
check(VarProperty.PropertyClass);
UWeakObjectProperty* Result = new (EC_InternalUseOnlyConstructor, Scope, Name, ObjectFlags) UWeakObjectProperty(FObjectInitializer());
Result->PropertyClass = VarProperty.PropertyClass;
UHTMakefile.AddWeakObjectProperty(UnrealSourceFile, Result);
return Result;
}
case CPT_LazyObjectReference:
{
check(VarProperty.PropertyClass);
ULazyObjectProperty* Result = new (EC_InternalUseOnlyConstructor, Scope, Name, ObjectFlags) ULazyObjectProperty(FObjectInitializer());
Result->PropertyClass = VarProperty.PropertyClass;
UHTMakefile.AddLazyObjectProperty(UnrealSourceFile, Result);
return Result;
}
case CPT_AssetObjectReference:
check(VarProperty.PropertyClass);
if (VarProperty.PropertyClass->IsChildOf(UClass::StaticClass()))
{
UAssetClassProperty* Result = new (EC_InternalUseOnlyConstructor, Scope, Name, ObjectFlags) UAssetClassProperty(FObjectInitializer());
Result->MetaClass = VarProperty.MetaClass;
Result->PropertyClass = VarProperty.PropertyClass;
UHTMakefile.AddAssetClassProperty(UnrealSourceFile, Result);
return Result;
}
else
{
UAssetObjectProperty* Result = new (EC_InternalUseOnlyConstructor, Scope, Name, ObjectFlags) UAssetObjectProperty(FObjectInitializer());
Result->PropertyClass = VarProperty.PropertyClass;
UHTMakefile.AddAssetObjectProperty(UnrealSourceFile, Result);
return Result;
}
case CPT_Interface:
{
check(VarProperty.PropertyClass);
check(VarProperty.PropertyClass->HasAnyClassFlags(CLASS_Interface));
UInterfaceProperty* Result = new (EC_InternalUseOnlyConstructor, Scope, Name, ObjectFlags) UInterfaceProperty(FObjectInitializer());
Result->InterfaceClass = VarProperty.PropertyClass;
UHTMakefile.AddInterfaceProperty(UnrealSourceFile, Result);
return Result;
}
case CPT_Name:
{
UNameProperty* Result = new (EC_InternalUseOnlyConstructor, Scope, Name, ObjectFlags) UNameProperty(FObjectInitializer());
UHTMakefile.AddNameProperty(UnrealSourceFile, Result);
return Result;
}
case CPT_String:
{
UStrProperty* Result = new (EC_InternalUseOnlyConstructor, Scope, Name, ObjectFlags) UStrProperty(FObjectInitializer());
UHTMakefile.AddStrProperty(UnrealSourceFile, Result);
return Result;
}
case CPT_Text:
{
UTextProperty* Result = new (EC_InternalUseOnlyConstructor, Scope, Name, ObjectFlags) UTextProperty(FObjectInitializer());
UHTMakefile.AddTextProperty(UnrealSourceFile, Result);
return Result;
}
case CPT_Struct:
{
if (VarProperty.Struct->StructFlags & STRUCT_HasInstancedReference)
{
VarProperty.PropertyFlags |= CPF_ContainsInstancedReference;
}
UStructProperty* Result = new (EC_InternalUseOnlyConstructor, Scope, Name, ObjectFlags) UStructProperty(FObjectInitializer());
Result->Struct = VarProperty.Struct;
UHTMakefile.AddStructProperty(UnrealSourceFile, Result);
return Result;
}
case CPT_Delegate:
{
UDelegateProperty* Result = new (EC_InternalUseOnlyConstructor, Scope, Name, ObjectFlags) UDelegateProperty(FObjectInitializer());
UHTMakefile.AddDelegateProperty(UnrealSourceFile, Result);
return Result;
}
case CPT_MulticastDelegate:
{
UMulticastDelegateProperty* Result = new (EC_InternalUseOnlyConstructor, Scope, Name, ObjectFlags) UMulticastDelegateProperty(FObjectInitializer());
UHTMakefile.AddMulticastDelegateProperty(UnrealSourceFile, Result);
return Result;
}
default:
FError::Throwf(TEXT("Unknown property type %i"), (uint8)VarProperty.Type);
}
// Unreachable
check(false);
return nullptr;
}
/**
* Ensures at script compile time that the metadata formatting is correct
* @param InKey the metadata key being added
* @param InValue the value string that will be associated with the InKey
*/
void ValidateMetaDataFormat(UField* Field, const FString& InKey, const FString& InValue)
{
switch ((ECheckedMetadataSpecifier)Algo::FindSortedStringCaseInsensitive(*InKey, GCheckedMetadataSpecifierStrings))
{
default:
{
// Don't need to validate this specifier
}
break;
case ECheckedMetadataSpecifier::UIMin:
case ECheckedMetadataSpecifier::UIMax:
case ECheckedMetadataSpecifier::ClampMin:
case ECheckedMetadataSpecifier::ClampMax:
{
if (!InValue.IsNumeric())
{
FError::Throwf(TEXT("Metadata value for '%s' is non-numeric : '%s'"), *InKey, *InValue);
}
}
break;
case ECheckedMetadataSpecifier::BlueprintProtected:
{
if (UFunction* Function = Cast<UFunction>(Field))
{
if (Function->HasAnyFunctionFlags(FUNC_Static))
{
// Determine if it's a function library
UClass* Class = Cast<UClass>(Function->GetOuterUClass());
while (Class != nullptr && Class->GetSuperClass() != UObject::StaticClass())
{
Class = Class->GetSuperClass();
}
if (Class != nullptr && Class->GetName() == TEXT("BlueprintFunctionLibrary"))
{
FError::Throwf(TEXT("%s doesn't make sense on static method '%s' in a blueprint function library"), *InKey, *Function->GetName());
}
}
}
}
break;
case ECheckedMetadataSpecifier::DevelopmentStatus:
{
const FString EarlyAccessValue(TEXT("EarlyAccess"));
const FString ExperimentalValue(TEXT("Experimental"));
if ((InValue != EarlyAccessValue) && (InValue != ExperimentalValue))
{
FError::Throwf(TEXT("'%s' metadata was '%s' but it must be %s or %s"), *InKey, *InValue, *ExperimentalValue, *EarlyAccessValue);
}
}
break;
case ECheckedMetadataSpecifier::Units:
{
// Check for numeric property
if (!Cast<UNumericProperty>(Field))
{
FError::Throwf(TEXT("'Units' meta data can only be applied to numeric properties"));
}
if (!FUnitConversion::UnitFromString(*InValue))
{
FError::Throwf(TEXT("Unrecognized units (%s) specified for numeric property '%s'"), *InValue, *Field->GetDisplayNameText().ToString());
}
}
break;
}
}
// Ensures at script compile time that the metadata formatting is correct
void ValidateMetaDataFormat(UField* Field, const TMap<FName, FString>& MetaData)
{
for (const auto& Pair : MetaData)
{
ValidateMetaDataFormat(Field, Pair.Key.ToString(), Pair.Value);
}
}
// Validates the metadata, then adds it to the class data
void AddMetaDataToClassData(UField* Field, const TMap<FName, FString>& InMetaData)
{
// Evaluate any key redirects on the passed in pairs
TMap<FName, FString> RemappedPairs;
RemappedPairs.Empty(InMetaData.Num());
for (const auto& Pair : InMetaData)
{
FName CurrentKey = Pair.Key;
FName NewKey = UMetaData::GetRemappedKeyName(CurrentKey);
if (NewKey != NAME_None)
{
UE_LOG_WARNING_UHT(TEXT("Remapping old metadata key '%s' to new key '%s', please update the declaration."), *CurrentKey.ToString(), *NewKey.ToString());
CurrentKey = NewKey;
}
RemappedPairs.Add(CurrentKey, Pair.Value);
}
// Finish validating and associate the metadata with the field
ValidateMetaDataFormat(Field, RemappedPairs);
FClassMetaData::AddMetaData(Field, RemappedPairs);
}
bool IsPropertySupportedByBlueprint(const UProperty* Property, bool bMemberVariable)
{
if (Property == NULL)
{
return false;
}
if (const UArrayProperty* ArrayProperty = Cast<const UArrayProperty>(Property))
{
// Script VM doesn't support array of weak ptrs.
return IsPropertySupportedByBlueprint(ArrayProperty->Inner, false);
}
else if (const USetProperty* SetProperty = Cast<const USetProperty>(Property))
{
return IsPropertySupportedByBlueprint(SetProperty->ElementProp, false);
}
else if (const UMapProperty* MapProperty = Cast<const UMapProperty>(Property))
{
return IsPropertySupportedByBlueprint(MapProperty->KeyProp, false) &&
IsPropertySupportedByBlueprint(MapProperty->ValueProp, false);
}
const bool bSupportedType = Property->IsA<UInterfaceProperty>()
|| Property->IsA<UClassProperty>()
|| Property->IsA<UAssetObjectProperty>()
|| Property->IsA<UObjectProperty>()
|| Property->IsA<UStructProperty>()
|| Property->IsA<UFloatProperty>()
|| Property->IsA<UIntProperty>()
|| Property->IsA<UByteProperty>()
|| Property->IsA<UNameProperty>()
|| Property->IsA<UBoolProperty>()
|| Property->IsA<UStrProperty>()
|| Property->IsA<UTextProperty>()
|| Property->IsA<UDelegateProperty>()
|| Property->IsA<UEnumProperty>();
const bool bIsSupportedMemberVariable = Property->IsA<UWeakObjectProperty>() || Property->IsA<UMulticastDelegateProperty>();
return bSupportedType || (bIsSupportedMemberVariable && bMemberVariable);
}
}
/////////////////////////////////////////////////////
// FScriptLocation
FHeaderParser* FScriptLocation::Compiler = NULL;
FScriptLocation::FScriptLocation()
{
if ( Compiler != NULL )
{
Compiler->InitScriptLocation(*this);
}
}
/////////////////////////////////////////////////////
// FHeaderParser
FString FHeaderParser::GetContext()
{
FFileScope* FileScope = GetCurrentFileScope();
FUnrealSourceFile* SourceFile = FileScope ? FileScope->GetSourceFile() : GetCurrentSourceFile();
FString ScopeFilename = SourceFile
? IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*SourceFile->GetFilename())
: TEXT("UNKNOWN");
return FString::Printf(TEXT("%s(%i)"), *ScopeFilename, InputLine);
}
/*-----------------------------------------------------------------------------
Code emitting.
-----------------------------------------------------------------------------*/
//
// Get a qualified class.
//
FClass* FHeaderParser::GetQualifiedClass(const FClasses& AllClasses, const TCHAR* Thing)
{
TCHAR ClassName[256]=TEXT("");
FToken Token;
if (GetIdentifier(Token))
{
FCString::Strncat( ClassName, Token.Identifier, ARRAY_COUNT(ClassName) );
}
if (!ClassName[0])
{
FError::Throwf(TEXT("%s: Missing class name"), Thing );
}
return AllClasses.FindScriptClassOrThrow(ClassName);
}
/*-----------------------------------------------------------------------------
Fields.
-----------------------------------------------------------------------------*/
/**
* Find a field 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 FieldClass class of the field to search for. used to e.g. search for functions only
* @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
*/
UField* FHeaderParser::FindField
(
UStruct* Scope,
const TCHAR* InIdentifier,
bool bIncludeParents,
UClass* FieldClass,
const TCHAR* Thing
)
{
check(InIdentifier);
FName InName(InIdentifier, FNAME_Find);
if (InName != NAME_None)
{
for( ; Scope; Scope = Cast<UStruct>(Scope->GetOuter()) )
{
for( TFieldIterator<UField> It(Scope); It; ++It )
{
if (It->GetFName() == InName)
{
if (!It->IsA(FieldClass))
{
if (Thing)
{
FError::Throwf(TEXT("%s: expecting %s, got %s"), Thing, *FieldClass->GetName(), *It->GetClass()->GetName() );
}
return NULL;
}
return *It;
}
}
if (!bIncludeParents)
{
break;
}
}
}
return NULL;
}
/**
* @return true if Scope has UProperty objects in its list of fields
*/
bool FHeaderParser::HasMemberProperties( const UStruct* Scope )
{
// it's safe to pass a NULL Scope to TFieldIterator, but this function shouldn't be called with a NULL Scope
checkSlow(Scope);
TFieldIterator<UProperty> It(Scope,EFieldIteratorFlags::ExcludeSuper);
return It ? true : false;
}
/**
* Get the parent struct specified.
*
* @param CurrentScope scope to start in
* @param SearchName parent scope to search for
*
* @return a pointer to the parent struct with the specified name, or NULL if the parent couldn't be found
*/
UStruct* FHeaderParser::GetSuperScope( UStruct* CurrentScope, const FName& SearchName )
{
UStruct* SuperScope = CurrentScope;
while (SuperScope && !SuperScope->GetInheritanceSuper())
{
SuperScope = CastChecked<UStruct>(SuperScope->GetOuter());
}
if (SuperScope != NULL)
{
// iterate up the inheritance chain looking for one that has the desired name
do
{
UStruct* NextScope = SuperScope->GetInheritanceSuper();
if (NextScope)
{
SuperScope = NextScope;
}
else
{
// otherwise we've failed
SuperScope = NULL;
}
} while (SuperScope != NULL && SuperScope->GetFName() != SearchName);
}
return SuperScope;
}
/**
* 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(UField* Type, TMap<FName, FString> &MetaData)
{
// Add metadata for the include path.
TSharedRef<FUnrealTypeDefinitionInfo>* TypeDefinitionPtr = GTypeDefinitionInfoMap.Find(Type);
if (TypeDefinitionPtr != nullptr)
{
MetaData.Add(TEXT("IncludePath"), *(*TypeDefinitionPtr)->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(TEXT("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(UField* Type, TMap<FName, FString> &MetaData)
{
// Add metadata for the module relative path.
TSharedRef<FUnrealTypeDefinitionInfo>* TypeDefinitionPtr = GTypeDefinitionInfoMap.Find(Type);
if (TypeDefinitionPtr != nullptr)
{
MetaData.Add(TEXT("ModuleRelativePath"), *(*TypeDefinitionPtr)->GetUnrealSourceFile().GetModuleRelativePath());
}
}
/*-----------------------------------------------------------------------------
Variables.
-----------------------------------------------------------------------------*/
//
// Compile an enumeration definition.
//
UEnum* FHeaderParser::CompileEnum()
{
FUnrealSourceFile* CurrentSrcFile = GetCurrentSourceFile();
TSharedPtr<FFileScope> Scope = CurrentSrcFile->GetScope();
CheckAllow( TEXT("'Enum'"), ENestAllowFlags::TypeDecl );
// Get the enum specifier list
FToken EnumToken;
TArray<FPropertySpecifier> SpecifiersFound;
ReadSpecifierSetInsideMacro(SpecifiersFound, TEXT("Enum"), EnumToken.MetaData);
// We don't handle any non-metadata enum specifiers at the moment
if (SpecifiersFound.Num() != 0)
{
FError::Throwf(TEXT("Unknown enum specifier '%s'"), *SpecifiersFound[0].Key);
}
FScriptLocation DeclarationPosition;
// 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))
{
FError::Throwf(TEXT("Missing identifier after UENUM()") );
}
if (EnumToken.Matches(TEXT("namespace"), ESearchCase::CaseSensitive))
{
CppForm = UEnum::ECppForm::Namespaced;
bReadEnumName = GetIdentifier(EnumToken);
}
else if (EnumToken.Matches(TEXT("enum"), ESearchCase::CaseSensitive))
{
if (!GetIdentifier(EnumToken))
{
FError::Throwf(TEXT("Missing identifier after enum") );
}
if (EnumToken.Matches(TEXT("class"), ESearchCase::CaseSensitive) || EnumToken.Matches(TEXT("struct"), ESearchCase::CaseSensitive))
{
CppForm = UEnum::ECppForm::EnumClass;
bReadEnumName = GetIdentifier(EnumToken);
}
else
{
CppForm = UEnum::ECppForm::Regular;
bReadEnumName = true;
}
}
else
{
FError::Throwf(TEXT("UENUM() should be followed by \'enum\' or \'namespace\' keywords.") );
}
// Get enumeration name.
if (!bReadEnumName)
{
FError::Throwf(TEXT("Missing enumeration name") );
}
// Verify that the enumeration definition is unique within this scope.
UField* Existing = Scope->FindTypeByName(EnumToken.Identifier);
if (Existing)
{
FError::Throwf(TEXT("enum: '%s' already defined here"), *EnumToken.TokenName.ToString());
}
ParseFieldMetaData(EnumToken.MetaData, EnumToken.Identifier);
// Create enum definition.
UEnum* Enum = new(EC_InternalUseOnlyConstructor, CurrentSrcFile->GetPackage(), EnumToken.Identifier, RF_Public) UEnum(FObjectInitializer());
Scope->AddType(Enum);
AddTypeDefinition(UHTMakefile, CurrentSrcFile, Enum, InputLine);
UHTMakefile.AddEnum(CurrentSrcFile, Enum);
// Validate the metadata for the enum
ValidateMetaDataFormat(Enum, EnumToken.MetaData);
// Read base for enum class
EUnderlyingEnumType UnderlyingType = EUnderlyingEnumType::uint8;
if (CppForm == UEnum::ECppForm::EnumClass)
{
if (MatchSymbol(TEXT(":")))
{
FToken BaseToken;
if (!GetIdentifier(BaseToken))
{
FError::Throwf(TEXT("Missing enum base") );
}
if (!FCString::Strcmp(BaseToken.Identifier, TEXT("uint8")))
{
UnderlyingType = EUnderlyingEnumType::uint8;
}
else if (!FCString::Strcmp(BaseToken.Identifier, TEXT("uint16")))
{
UnderlyingType = EUnderlyingEnumType::uint16;
}
else if (!FCString::Strcmp(BaseToken.Identifier, TEXT("uint32")))
{
UnderlyingType = EUnderlyingEnumType::uint32;
}
else if (!FCString::Strcmp(BaseToken.Identifier, TEXT("uint64")))
{
UnderlyingType = EUnderlyingEnumType::uint64;
}
else if (!FCString::Strcmp(BaseToken.Identifier, TEXT("int8")))
{
UnderlyingType = EUnderlyingEnumType::int8;
}
else if (!FCString::Strcmp(BaseToken.Identifier, TEXT("int16")))
{
UnderlyingType = EUnderlyingEnumType::int16;
}
else if (!FCString::Strcmp(BaseToken.Identifier, TEXT("int32")))
{
UnderlyingType = EUnderlyingEnumType::int32;
}
else if (!FCString::Strcmp(BaseToken.Identifier, TEXT("int64")))
{
UnderlyingType = EUnderlyingEnumType::int64;
}
else
{
FError::Throwf(TEXT("Unsupported enum class base type: %s"), BaseToken.Identifier);
}
}
else
{
UnderlyingType = EUnderlyingEnumType::Unspecified;
}
GEnumUnderlyingTypes.Add(Enum, UnderlyingType);
UHTMakefile.AddGEnumUnderlyingType(CurrentSrcFile, Enum, UnderlyingType);
}
static const FName BlueprintTypeName = TEXT("BlueprintType");
if (UnderlyingType != EUnderlyingEnumType::uint8 && EnumToken.MetaData.Contains(BlueprintTypeName))
{
FError::Throwf(TEXT("Invalid BlueprintType enum base - currently only uint8 supported"));
}
// Get opening brace.
RequireSymbol( TEXT("{"), TEXT("'Enum'") );
switch (CppForm)
{
case UEnum::ECppForm::Namespaced:
{
// Now handle the inner true enum portion
RequireIdentifier(TEXT("enum"), TEXT("'Enum'"));
FToken InnerEnumToken;
if (!GetIdentifier(InnerEnumToken))
{
FError::Throwf(TEXT("Missing enumeration name") );
}
Enum->CppType = FString::Printf(TEXT("%s::%s"), EnumToken.Identifier, InnerEnumToken.Identifier);
RequireSymbol( TEXT("{"), TEXT("'Enum'") );
}
break;
case UEnum::ECppForm::Regular:
case UEnum::ECppForm::EnumClass:
{
Enum->CppType = EnumToken.Identifier;
}
break;
}
// List of all metadata generated for this enum
TMap<FName,FString> EnumValueMetaData = EnumToken.MetaData;
AddModuleRelativePathToMetadata(Enum, EnumValueMetaData);
AddFormattedPrevCommentAsTooltipMetaData(EnumValueMetaData);
// Parse all enums tags.
FToken TagToken;
TArray<FScriptLocation> EnumTagLocations;
TArray<TPair<FName, int64>> EnumNames;
int64 CurrentEnumValue = 0;
while (GetIdentifier(TagToken))
{
AddFormattedPrevCommentAsTooltipMetaData(TagToken.MetaData);
FScriptLocation* ValueDeclarationPos = new(EnumTagLocations) FScriptLocation();
// Try to read an optional explicit enum value specification
if (MatchSymbol(TEXT("=")))
{
int64 NewEnumValue = 0;
GetConstInt64(/*out*/ NewEnumValue, TEXT("Enumerator value"));
if (EnumNames.Num() > 0 && NewEnumValue < CurrentEnumValue)
{
FError::Throwf(TEXT("Explicitly specified enum value (%d) must be greater than the previous value (%d)"), NewEnumValue, CurrentEnumValue);
}
if (UnderlyingType == EUnderlyingEnumType::Unspecified || UnderlyingType == EUnderlyingEnumType::int8 || UnderlyingType == EUnderlyingEnumType::int16 || UnderlyingType == EUnderlyingEnumType::int32 || UnderlyingType == EUnderlyingEnumType::int64)
{
int64 Min = 0;
int64 Max = 0;
switch (UnderlyingType)
{
case EUnderlyingEnumType::Unspecified: Min = TNumericLimits<int> ::Min(); Max = TNumericLimits<int> ::Max(); break;
case EUnderlyingEnumType::int8: Min = TNumericLimits<int8> ::Min(); Max = TNumericLimits<int8> ::Max(); break;
case EUnderlyingEnumType::int16: Min = TNumericLimits<int16>::Min(); Max = TNumericLimits<int16>::Max(); break;
case EUnderlyingEnumType::int32: Min = TNumericLimits<int32>::Min(); Max = TNumericLimits<int32>::Max(); break;
case EUnderlyingEnumType::int64: Min = TNumericLimits<int64>::Min(); Max = TNumericLimits<int64>::Max(); break;
default:
check(false);
}
if (NewEnumValue < Min || NewEnumValue > Max)
{
FError::Throwf(TEXT("Explicitly specified enum value (%lld) must be in the range of the underlying type of the enum (%lld to %lld)"), NewEnumValue, Min, Max);
}
}
else
{
uint64 Min = 0;
uint64 Max = 0;
switch (UnderlyingType)
{
case EUnderlyingEnumType::uint8: Min = TNumericLimits<uint8> ::Min(); Max = TNumericLimits<uint8> ::Max(); break;
case EUnderlyingEnumType::uint16: Min = TNumericLimits<uint16>::Min(); Max = TNumericLimits<uint16>::Max(); break;
case EUnderlyingEnumType::uint32: Min = TNumericLimits<uint32>::Min(); Max = TNumericLimits<uint32>::Max(); break;
case EUnderlyingEnumType::uint64: Min = TNumericLimits<uint64>::Min(); Max = TNumericLimits<uint64>::Max(); break;
default:
check(false);
}
if (NewEnumValue < 0)
{
FError::Throwf(TEXT("Explicitly specified enum value (%lld) must be in the range of the underlying type of the enum (%llu to %llu)"), NewEnumValue, Min, Max);
}
else if ((uint64)NewEnumValue < Min || (uint64)NewEnumValue > Max)
{
FError::Throwf(TEXT("Explicitly specified enum value (%llu) must be in the range of the underlying type of the enum (%llu to %llu)"), (uint64)NewEnumValue, Min, Max);
}
}
CurrentEnumValue = NewEnumValue;
}
int32 iFound;
FName NewTag;
switch (CppForm)
{
case UEnum::ECppForm::Namespaced:
case UEnum::ECppForm::EnumClass:
{
NewTag = FName(*FString::Printf(TEXT("%s::%s"), EnumToken.Identifier, TagToken.Identifier), FNAME_Add);
}
break;
case UEnum::ECppForm::Regular:
{
NewTag = FName(TagToken.Identifier, FNAME_Add);
}
break;
}
TPair<FName, int64> CurrentEnum(NewTag, CurrentEnumValue);
if (EnumNames.Find(CurrentEnum, iFound))
{
FError::Throwf(TEXT("Duplicate enumeration tag %s"), TagToken.Identifier );
}
UEnum* FoundEnum = NULL;
if (UEnum::LookupEnumName(NewTag, &FoundEnum) != INDEX_NONE)
{
FError::Throwf(TEXT("Enumeration tag '%s' already in use by enum '%s'"), TagToken.Identifier, *FoundEnum->GetPathName());
}
// Save the new tag
EnumNames.Add(CurrentEnum);
// Autoincrement the current enumerant value
CurrentEnumValue++;
// check for metadata on this enum value
ParseFieldMetaData(TagToken.MetaData, TagToken.Identifier);
if (TagToken.MetaData.Num() > 0)
{
// special case for enum value metadata - we need to prepend the key name with the enum value name
const FString TokenString = TagToken.Identifier;
for (const auto& MetaData : TagToken.MetaData)
{
FString KeyString = TokenString + TEXT(".") + MetaData.Key.ToString();
EnumValueMetaData.Add(FName(*KeyString), MetaData.Value);
}
// now clear the metadata because we're going to reuse this token for parsing the next enum value
TagToken.MetaData.Empty();
}
if (!MatchSymbol(TEXT(",")))
{
break;
}
}
// Add the metadata gathered for the enum to the package
if (EnumValueMetaData.Num() > 0)
{
UMetaData* PackageMetaData = Enum->GetOutermost()->GetMetaData();
checkSlow(PackageMetaData);
PackageMetaData->SetObjectValues(Enum, EnumValueMetaData);
}
if (!EnumNames.Num())
{
FError::Throwf(TEXT("Enumeration must contain at least one enumerator") );
}
// 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'") );
}
// Register the list of enum names.
if (!Enum->SetEnums(EnumNames, CppForm, !FClass::IsDynamic(Enum)))
{
const FName MaxEnumItem = *(Enum->GenerateEnumPrefix() + TEXT("_MAX"));
const int32 MaxEnumItemIndex = Enum->GetIndexByName(MaxEnumItem);
if (MaxEnumItemIndex != INDEX_NONE)
{
ReturnToLocation(EnumTagLocations[MaxEnumItemIndex], false, true);
FError::Throwf(TEXT("Illegal enumeration tag specified. Conflicts with auto-generated tag '%s'"), *MaxEnumItem.ToString());
}
FError::Throwf(TEXT("Unable to generate enum MAX entry '%s' due to name collision"), *MaxEnumItem.ToString());
}
return Enum;
}
/**
* 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;
}
/**
* 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 IsLineSeparator(const TCHAR* Str)
{
check(Str);
return IsAllSameChar(Str, TEXT('-')) || IsAllSameChar(Str, TEXT('=')) || IsAllSameChar(Str, TEXT('*'));
}
/**
* @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( TEXT("") );
}
FString Result(Input);
// Sweep out comments marked to be ignored.
{
int32 CommentStart, CommentEnd;
// Block comments go first
for (CommentStart = Result.Find(TEXT("/*~")); CommentStart != INDEX_NONE; CommentStart = Result.Find(TEXT("/*~")))
{
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("//~")); CommentStart != INDEX_NONE; CommentStart = Result.Find(TEXT("//~")))
{
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("/**"));
const bool bCStyle = Result.Contains(TEXT("/*"));
const bool bCPPStyle = Result.StartsWith(TEXT("//"));
if ( bJavaDocStyle || bCStyle)
{
// Remove beginning and end markers.
Result = Result.Replace( TEXT("/**"), TEXT("") );
Result = Result.Replace( TEXT("/*"), TEXT("") );
Result = Result.Replace( TEXT("*/"), TEXT("") );
}
if ( bCPPStyle )
{
// Remove c++-style comment markers. Also handle javadoc-style comments by replacing
// all triple slashes with double-slashes
Result = Result.Replace(TEXT("///"), TEXT("//")).Replace( TEXT("//"), TEXT("") );
// Parser strips cpptext and replaces it with "// (cpptext)" -- prevent
// this from being treated as a comment on variables declared below the
// cpptext section
Result = Result.Replace( TEXT("(cpptext)"), TEXT("") );
}
// Get rid of carriage return or tab characters, which mess up tooltips.
Result = Result.Replace( TEXT( "\r" ), TEXT( "" ) );
//wx widgets has a hard coded tab size of 8
{
const int32 SpacesPerTab = 8;
Result = Result.ConvertTabsToSpaces (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.TrimTrailing();
// 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 = Line.RightChop(Pos + 1);
}
}
}
// Find first meaningful line
int32 FirstIndex = 0;
for (FString Line : Lines)
{
Line.Trim();
if (Line.Len() && !IsLineSeparator(*Line))
break;
++FirstIndex;
}
int32 LastIndex = Lines.Num();
while (LastIndex != FirstIndex)
{
FString Line = Lines[LastIndex - 1];
Line.Trim();
if (Line.Len() && !IsLineSeparator(*Line))
break;
--LastIndex;
}
Result.Empty();
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 > 0) && (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 = Line.Mid(Pos);
}
if (Index > 0)
{
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 = Result.Mid(1);
}
// Make sure it doesn't end with a dead newline
if (!Result.IsEmpty() && FChar::IsLinebreak(Result[Result.Len() - 1]))
{
Result = Result.Left(Result.Len() - 1);
}
// Done.
return Result;
}
void FHeaderParser::AddFormattedPrevCommentAsTooltipMetaData(TMap<FName, FString>& MetaData)
{
// Don't add a tooltip if one already exists.
if (MetaData.Find(NAME_ToolTip))
{
return;
}
// 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(FToken& Token)
{
EAccessSpecifier ResultAccessSpecifier = ACCESS_NotAnAccessSpecifier;
for (EAccessSpecifier Test = EAccessSpecifier(ACCESS_NotAnAccessSpecifier + 1); Test != ACCESS_Num; Test = EAccessSpecifier(Test + 1))
{
if (Token.Matches(GetAccessSpecifierName(Test)) || (Token.Matches(TEXT("private_subobject")) && Test == ACCESS_Public))
{
// Consume the colon after the specifier
RequireSymbol(TEXT(":"), *FString::Printf(TEXT("after %s"), Token.Identifier));
return Test;
}
}
return ACCESS_NotAnAccessSpecifier;
}
/**
* Compile a struct definition.
*/
UScriptStruct* FHeaderParser::CompileStructDeclaration(FClasses& AllClasses)
{
FUnrealSourceFile* CurrentSrcFile = GetCurrentSourceFile();
TSharedPtr<FFileScope> Scope = CurrentSrcFile->GetScope();
// Make sure structs can be declared here.
CheckAllow( TEXT("'struct'"), ENestAllowFlags::TypeDecl );
FScriptLocation StructDeclaration;
bool IsNative = false;
bool IsExport = false;
bool IsTransient = false;
uint32 StructFlags = STRUCT_Native;
TMap<FName, FString> MetaData;
// Get the struct specifier list
TArray<FPropertySpecifier> SpecifiersFound;
ReadSpecifierSetInsideMacro(SpecifiersFound, TEXT("Struct"), MetaData);
// Consume the struct keyword
RequireIdentifier(TEXT("struct"), 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;
SkipDeprecatedMacroIfNecessary();
// Read the struct name
ParseNameWithPotentialAPIMacroPrefix(/*out*/ StructNameInScript, /*out*/ RequiredAPIMacroIfPresent, TEXT("struct"));
// Record that this struct is RequiredAPI if the CORE_API style macro was present
if (!RequiredAPIMacroIfPresent.IsEmpty())
{
StructFlags |= STRUCT_RequiredAPI;
}
StructNameStripped = GetClassNameWithPrefixRemoved(StructNameInScript);
// Effective struct name
const FString EffectiveStructName = *StructNameStripped;
// Process the list of specifiers
for (const FPropertySpecifier& Specifier : SpecifiersFound)
{
switch ((EStructSpecifier)Algo::FindSortedStringCaseInsensitive(*Specifier.Key, GStructSpecifierStrings))
{
default:
{
FError::Throwf(TEXT("Unknown struct specifier '%s'"), *Specifier.Key);
}
break;
case EStructSpecifier::NoExport:
{
//UE_LOG_WARNING_UHT(TEXT("Struct named %s in %s is still marked noexport"), *EffectiveStructName, *(Class->GetName()));//@TODO: UCREMOVAL: Debug printing
StructFlags &= ~STRUCT_Native;
StructFlags |= STRUCT_NoExport;
}
break;
case EStructSpecifier::Atomic:
{
StructFlags |= STRUCT_Atomic;
}
break;
case EStructSpecifier::Immutable:
{
StructFlags |= STRUCT_Immutable | STRUCT_Atomic;
if (!FPaths::IsSamePath(Filename, GTypeDefinitionInfoMap[UObject::StaticClass()]->GetUnrealSourceFile().GetFilename()))
{
FError::Throwf(TEXT("Immutable is being phased out in favor of SerializeNative, and is only legal on the mirror structs declared in UObject"));
}
}
break;
}
}
// Verify uniqueness (if declared within UClass).
{
UField* Existing = Scope->FindTypeByName(*EffectiveStructName);
if (Existing)
{
FError::Throwf(TEXT("struct: '%s' already defined here"), *EffectiveStructName);
}
if (FindObject<UStruct>(ANY_PACKAGE, *EffectiveStructName) != NULL)
{
FError::Throwf(TEXT("struct: '%s' conflicts with class name"), *EffectiveStructName);
}
}
// Get optional superstruct.
bool bExtendsBaseStruct = false;
if (MatchSymbol(TEXT(":")))
{
RequireIdentifier(TEXT("public"), TEXT("struct inheritance"));
bExtendsBaseStruct = true;
}
UScriptStruct* BaseStruct = NULL;
if (bExtendsBaseStruct)
{
FToken ParentScope, ParentName;
if (GetIdentifier( ParentScope ))
{
TSharedPtr<FScope> StructScope = Scope;
FString ParentStructNameInScript = FString(ParentScope.Identifier);
if (MatchSymbol(TEXT(".")))
{
if (GetIdentifier(ParentName))
{
ParentStructNameInScript = FString(ParentName.Identifier);
FString ParentNameStripped = GetClassNameWithPrefixRemoved(ParentScope.Identifier);
FClass* StructClass = AllClasses.FindClass(*ParentNameStripped);
if( !StructClass )
{
// If we find the literal class name, the user didn't use a prefix
StructClass = AllClasses.FindClass(ParentScope.Identifier);
if( StructClass )
{
FError::Throwf(TEXT("'struct': Parent struct class '%s' is missing a prefix, expecting '%s'"), ParentScope.Identifier, *FString::Printf(TEXT("%s%s"),StructClass->GetPrefixCPP(),ParentScope.Identifier) );
}
else
{
FError::Throwf(TEXT("'struct': Can't find parent struct class '%s'"), ParentScope.Identifier );
}
}
StructScope = FScope::GetTypeScope(StructClass);
}
else
{
FError::Throwf( TEXT("'struct': Missing parent struct type after '%s.'"), ParentScope.Identifier );
}
}
FString ParentStructNameStripped;
const UField* Type = nullptr;
bool bOverrideParentStructName = false;
if( !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)
{
Type = StructScope->FindTypeByName(*ParentStructNameStripped);
}
// If it wasn't found, try to find the literal name given
if (Type == NULL)
{
Type = StructScope->FindTypeByName(*ParentStructNameInScript);
}
// Resolve structs declared in another class //@TODO: UCREMOVAL: This seems extreme
if (Type == NULL)
{
if (bOverrideParentStructName)
{
Type = FindObject<UScriptStruct>(ANY_PACKAGE, *ParentStructNameStripped);
}
if (Type == NULL)
{
Type = FindObject<UScriptStruct>(ANY_PACKAGE, *ParentStructNameInScript);
}
}
// If the struct still wasn't found, throw an error
if (Type == NULL)
{
FError::Throwf(TEXT("'struct': Can't find struct '%s'"), *ParentStructNameInScript );
}
else
{
// 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.
BaseStruct = ((UScriptStruct*)Type);
if( bOverrideParentStructName )
{
const TCHAR* PrefixCPP = StructsWithTPrefix.Contains(ParentStructNameStripped) ? TEXT("T") : BaseStruct->GetPrefixCPP();
if( ParentStructNameInScript != FString::Printf(TEXT("%s%s"), PrefixCPP, *ParentStructNameStripped) )
{
BaseStruct = NULL;
FError::Throwf(TEXT("Parent Struct '%s' is missing a valid Unreal prefix, expecting '%s'"), *ParentStructNameInScript, *FString::Printf(TEXT("%s%s"), PrefixCPP, *Type->GetName()));
}
}
}
}
else
{
FError::Throwf(TEXT("'struct': Missing parent struct after ': public'") );
}
}
// if we have a base struct, propagate inherited struct flags now
if (BaseStruct != NULL)
{
StructFlags |= (BaseStruct->StructFlags&STRUCT_Inherit);
}
// Create.
UScriptStruct* Struct = new(EC_InternalUseOnlyConstructor, CurrentSrcFile->GetPackage(), *EffectiveStructName, RF_Public) UScriptStruct(FObjectInitializer(), BaseStruct);
UHTMakefile.AddScriptStruct(CurrentSrcFile, Struct);
Scope->AddType(Struct);
FScope::AddTypeScope(Struct, &CurrentSrcFile->GetScope().Get(), CurrentSrcFile, UHTMakefile);
AddTypeDefinition(UHTMakefile, CurrentSrcFile, Struct, InputLine);
AddModuleRelativePathToMetadata(Struct, 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 == Struct->GetPrefixCPP() || DeclaredPrefix == TEXT("T") )
{
// Found a prefix, do a basic check to see if it's valid
const TCHAR* ExpectedPrefixCPP = StructsWithTPrefix.Contains(StructNameStripped) ? TEXT("T") : Struct->GetPrefixCPP();
FString ExpectedStructName = FString::Printf(TEXT("%s%s"), ExpectedPrefixCPP, *StructNameStripped);
if (StructNameInScript != ExpectedStructName)
{
FError::Throwf(TEXT("Struct '%s' has an invalid Unreal prefix, expecting '%s'"), *StructNameInScript, *ExpectedStructName);
}
}
else
{
const TCHAR* ExpectedPrefixCPP = StructsWithTPrefix.Contains(StructNameInScript) ? TEXT("T") : Struct->GetPrefixCPP();
FString ExpectedStructName = FString::Printf(TEXT("%s%s"), ExpectedPrefixCPP, *StructNameInScript);
FError::Throwf(TEXT("Struct '%s' is missing a valid Unreal prefix, expecting '%s'"), *StructNameInScript, *ExpectedStructName);
}
Struct->StructFlags = EStructFlags(Struct->StructFlags | StructFlags);
AddFormattedPrevCommentAsTooltipMetaData(MetaData);
// Register the metadata
AddMetaDataToClassData(Struct, MetaData);
// 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);
{
FToken StructToken;
StructToken.Struct = Struct;
// add this struct to the compiler's persistent tracking system
FClassMetaData* ClassMetaData = GScriptHelper.AddClassData(StructToken.Struct, UHTMakefile, CurrentSrcFile);
UHTMakefile.AddGScriptHelperEntry(CurrentSrcFile, Struct, ClassMetaData);
}
int32 SavedLineNumber = InputLine;
// Clear comment before parsing body of the struct.
// Parse all struct variables.
FToken Token;
while (1)
{
ClearComment();
GetToken( Token );
if (EAccessSpecifier AccessSpecifier = ParseAccessProtectionSpecifier(Token))
{
CurrentAccessSpecifier = AccessSpecifier;
}
else if (Token.Matches(TEXT("UPROPERTY"), ESearchCase::CaseSensitive))
{
CompileVariableDeclaration(AllClasses, Struct);
}
else if (Token.Matches(TEXT("UFUNCTION"), ESearchCase::CaseSensitive))
{
FError::Throwf(TEXT("USTRUCTs cannot contain UFUNCTIONs."));
}
else if (Token.Matches(TEXT("GENERATED_USTRUCT_BODY")) || Token.Matches(TEXT("GENERATED_BODY")))
{
// Match 'GENERATED_USTRUCT_BODY' '(' [StructName] ')' or 'GENERATED_BODY' '(' [StructName] ')'
if (CurrentAccessSpecifier != ACCESS_Public)
{
FError::Throwf(TEXT("%s must be in the public scope of '%s', not private or protected."), Token.Identifier, *StructNameInScript);
}
if (Struct->StructMacroDeclaredLineNumber != INDEX_NONE)
{
FError::Throwf(TEXT("Multiple %s declarations found in '%s'"), Token.Identifier, *StructNameInScript);
}
Struct->StructMacroDeclaredLineNumber = InputLine;
RequireSymbol(TEXT("("), TEXT("'struct'"));
CompileVersionDeclaration(Struct);
RequireSymbol(TEXT(")"), TEXT("'struct'"));
// Eat a semicolon if present (not required)
SafeMatchSymbol(TEXT(";"));
}
else if ( Token.Matches(TEXT("#")) && MatchIdentifier(TEXT("ifdef")) )
{
PushCompilerDirective(ECompilerDirective::Insignificant);
}
else if ( Token.Matches(TEXT("#")) && MatchIdentifier(TEXT("ifndef")) )
{
PushCompilerDirective(ECompilerDirective::Insignificant);
}
else if (Token.Matches(TEXT("#")) && MatchIdentifier(TEXT("endif")))
{
if (CompilerDirectiveStack.Num() < 1)
{
FError::Throwf(TEXT("Unmatched '#endif' in class or global scope"));
}
CompilerDirectiveStack.Pop();
// Do nothing and hope that the if code below worked out OK earlier
}
else if ( Token.Matches(TEXT("#")) && MatchIdentifier(TEXT("if")) )
{
//@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")) )
{
if (bInvertConditional)
{
FError::Throwf(TEXT("Cannot use !WITH_EDITORONLY_DATA"));
}
PushCompilerDirective(ECompilerDirective::WithEditorOnlyData);
}
else if (MatchIdentifier(TEXT("WITH_EDITOR")) )
{
if (bInvertConditional)
{
FError::Throwf(TEXT("Cannot use !WITH_EDITOR"));
}
PushCompilerDirective(ECompilerDirective::WithEditor);
}
else if (MatchIdentifier(TEXT("CPP")) || MatchConstInt(TEXT("0")) || MatchConstInt(TEXT("1")) || MatchIdentifier(TEXT("WITH_HOT_RELOAD")) || MatchIdentifier(TEXT("WITH_HOT_RELOAD_CTORS")))
{
bConsumeAsCppText = !bInvertConditional;
PushCompilerDirective(ECompilerDirective::Insignificant);
}
else
{
FError::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 )
{
FError::Throwf(TEXT("Unexpected end of struct definition %s"), *Struct->GetName());
}
else if ( ch=='{' || (ch=='#' && (PeekIdentifier(TEXT("if")) || PeekIdentifier(TEXT("ifdef")))) )
{
nest++;
}
else if ( ch=='}' || (ch=='#' && PeekIdentifier(TEXT("endif"))) )
{
nest--;
}
if (nest==0)
{
RequireIdentifier(TEXT("endif"),TEXT("'if'"));
}
}
}
}
else if (Token.Matches(TEXT("#")) && MatchIdentifier(TEXT("pragma")))
{
// 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.Matches( TEXT("}") ) )
{
FToken DeclarationFirstToken = Token;
if (!SkipDeclaration(Token))
{
FError::Throwf(TEXT("'struct': Unexpected '%s'"), DeclarationFirstToken.Identifier );
}
}
else
{
MatchSemi();
break;
}
}
}
// Validation
bool bStructBodyFound = Struct->StructMacroDeclaredLineNumber != INDEX_NONE;
bool bExported = !!(StructFlags & STRUCT_Native);
if (!bStructBodyFound && bExported)
{
// Roll the line number back to the start of the struct body and error out
InputLine = SavedLineNumber;
FError::Throwf(TEXT("Expected a GENERATED_BODY() at the start of struct"));
}
// Link the properties within the struct
Struct->StaticLink(true);
return Struct;
}
/*-----------------------------------------------------------------------------
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)
{
FError::Throwf(TEXT("%s is not allowed before the Class definition"), Thing );
}
else
{
FError::Throwf(TEXT("%s is not allowed here"), Thing );
}
}
}
bool FHeaderParser::AllowReferenceToClass(UStruct* Scope, UClass* CheckClass) const
{
check(CheckClass);
return (Scope->GetOutermost() == CheckClass->GetOutermost())
|| ((CheckClass->ClassFlags&CLASS_Parsed) != 0)
|| ((CheckClass->ClassFlags&CLASS_Intrinsic) != 0);
}
/*-----------------------------------------------------------------------------
Nest management.
-----------------------------------------------------------------------------*/
void FHeaderParser::PushNest(ENestType NestType, UStruct* InNode, FUnrealSourceFile* SourceFile)
{
// Update pointer to top nesting level.
TopNest = &Nest[NestLevel++];
TopNest->SetScope(NestType == ENestType::GlobalScope ? &SourceFile->GetScope().Get() : &FScope::GetTypeScope(InNode).Get());
TopNest->NestType = NestType;
// Prevent overnesting.
if (NestLevel >= MAX_NEST_LEVELS)
{
FError::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:
FError::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)
{
FError::Throwf(TEXT("Unexpected '%s' at global scope"), Descr, NestTypeName(NestType));
}
else if (TopNest->NestType != NestType)
{
FError::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)
{
FError::Throwf(TEXT("Bad first pass NestType %i"), (uint8)NestType);
}
bool bLinkProps = true;
if (NestType == ENestType::Class)
{
UClass* TopClass = CastChecked<UClass>(GetCurrentClass());
bLinkProps = !TopClass->HasAnyClassFlags(CLASS_Intrinsic);
}
if (NestType != ENestType::GlobalScope)
{
GetCurrentClass()->StaticLink(bLinkProps);
}
// Pop the nesting level.
NestType = TopNest->NestType;
NestLevel--;
if (NestLevel == 0)
{
TopNest = nullptr;
}
else
{
TopNest--;
check(TopNest >= Nest);
}
}
void FHeaderParser::FixupDelegateProperties( FClasses& AllClasses, UStruct* Struct, FScope& Scope, TMap<FName, UFunction*>& DelegateCache )
{
check(Struct);
for ( UField* Field = Struct->Children; Field; Field = Field->Next )
{
UProperty* Property = Cast<UProperty>(Field);
if ( Property != NULL )
{
UDelegateProperty* DelegateProperty = Cast<UDelegateProperty>(Property);
UMulticastDelegateProperty* MulticastDelegateProperty = Cast<UMulticastDelegateProperty>(Property);
if ( DelegateProperty == NULL && MulticastDelegateProperty == NULL )
{
// if this is an array property, see if the array's type is a delegate
UArrayProperty* ArrayProp = Cast<UArrayProperty>(Property);
if ( ArrayProp != NULL )
{
DelegateProperty = Cast<UDelegateProperty>(ArrayProp->Inner);
MulticastDelegateProperty = Cast<UMulticastDelegateProperty>(ArrayProp->Inner);
}
}
if (DelegateProperty != nullptr || MulticastDelegateProperty != nullptr)
{
// this UDelegateProperty 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
FClassMetaData* StructData = GScriptHelper.FindClassData(Struct);
check(StructData);
FTokenData* DelegatePropertyToken = StructData->FindTokenData(Property);
check(DelegatePropertyToken);
// attempt to find the delegate function in the map of functions we've already found
UFunction* SourceDelegateFunction = DelegateCache.FindRef(DelegatePropertyToken->Token.DelegateName);
if (SourceDelegateFunction == nullptr)
{
FString NameOfDelegateFunction = DelegatePropertyToken->Token.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
SourceDelegateFunction = Cast<UFunction>(Scope.FindTypeByName(*NameOfDelegateFunction));
if (SourceDelegateFunction == nullptr)
{
// Try to find in other packages.
UObject* DelegateSignatureOuter = DelegatePropertyToken->Token.DelegateSignatureOwnerClass
? ((UObject*)DelegatePropertyToken->Token.DelegateSignatureOwnerClass)
: ((UObject*)ANY_PACKAGE);
SourceDelegateFunction = Cast<UFunction>(StaticFindObject(UFunction::StaticClass(), DelegateSignatureOuter, *NameOfDelegateFunction));
if (SourceDelegateFunction == 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(DelegatePropertyToken->Token);
FError::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(DelegatePropertyToken->Token);
FError::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
UClass* DelegateOwnerClass = AllClasses.FindScriptClassOrThrow(DelegateClassName);
if (FScope::GetTypeScope(DelegateOwnerClass)->FindTypeByName(*DelegateName) != nullptr)
{
FError::Throwf(TEXT("Inaccessible type: '%s'"), *DelegateOwnerClass->GetPathName());
}
SourceDelegateFunction = Cast<UFunction>(FindField(DelegateOwnerClass, *DelegateName, false, UFunction::StaticClass(), NULL));
}
if ( SourceDelegateFunction == NULL )
{
UngetToken(DelegatePropertyToken->Token);
FError::Throwf(TEXT("Failed to find delegate function '%s'"), *NameOfDelegateFunction);
}
else if ( (SourceDelegateFunction->FunctionFlags&FUNC_Delegate) == 0 )
{
UngetToken(DelegatePropertyToken->Token);
FError::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(DelegatePropertyToken->Token.DelegateName, SourceDelegateFunction);
// bind it to the delegate property
if( DelegateProperty != NULL )
{
if( !SourceDelegateFunction->HasAnyFunctionFlags( FUNC_MulticastDelegate ) )
{
DelegateProperty->SignatureFunction = DelegatePropertyToken->Token.Function = SourceDelegateFunction;
}
else
{
FError::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."), *SourceDelegateFunction->GetName());
}
}
else if( MulticastDelegateProperty != NULL )
{
if( SourceDelegateFunction->HasAnyFunctionFlags( FUNC_MulticastDelegate ) )
{
MulticastDelegateProperty->SignatureFunction = DelegatePropertyToken->Token.Function = SourceDelegateFunction;
if(MulticastDelegateProperty->HasAnyPropertyFlags(CPF_BlueprintAssignable | CPF_BlueprintCallable))
{
for (TFieldIterator<UProperty> PropIt(SourceDelegateFunction); PropIt && (PropIt->PropertyFlags & CPF_Parm); ++PropIt)
{
UProperty* FuncParam = *PropIt;
if(FuncParam->HasAllPropertyFlags(CPF_OutParm) && !FuncParam->HasAllPropertyFlags(CPF_ConstParm) )
{
const bool bClassGeneratedFromBP = FClass::IsDynamic(Struct);
const bool bAllowedArrayRefFromBP = bClassGeneratedFromBP && FuncParam->IsA<UArrayProperty>();
if (!bAllowedArrayRefFromBP)
{
FError::Throwf(TEXT("BlueprintAssignable delegates do not support non-const references at the moment. Function: %s Parameter: '%s'"), *SourceDelegateFunction->GetName(), *FuncParam->GetName());
}
}
}
}
}
else
{
FError::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."), *SourceDelegateFunction->GetName());
}
}
}
}
else
{
// if this is a state, function, or script struct, it might have its own delegate properties which need to be validated
UStruct* InternalStruct = Cast<UStruct>(Field);
if ( InternalStruct != NULL )
{
FixupDelegateProperties(AllClasses, InternalStruct, Scope, DelegateCache);
}
}
}
}
/**
* Verifies that all specified class's UProperties with CFG_RepNotify have valid callback targets with no parameters nor return values
*
* @param TargetClass class to verify rep notify properties for
*/
void FHeaderParser::VerifyRepNotifyCallbacks( UClass* TargetClass )
{
// Iterate over all properties, looking for those flagged as CPF_RepNotify
for ( UField* Field = TargetClass->Children; Field; Field = Field->Next )
{
UProperty* Prop = Cast<UProperty>(Field);
if( Prop && (Prop->GetPropertyFlags() & CPF_RepNotify) )
{
FClassMetaData* TargetClassData = GScriptHelper.FindClassData(TargetClass);
check(TargetClassData);
FTokenData* PropertyToken = TargetClassData->FindTokenData(Prop);
check(PropertyToken);
// Search through this class and its superclasses looking for the specified callback
UFunction* TargetFunc = NULL;
UClass* SearchClass = TargetClass;
while( SearchClass && !TargetFunc )
{
// Since the function map is not valid yet, we have to iterate over the fields to look for the function
for( UField* TestField = SearchClass->Children; TestField; TestField = TestField->Next )
{
UFunction* TestFunc = Cast<UFunction>(TestField);
if (TestFunc && FNativeClassHeaderGenerator::GetOverriddenFName(TestFunc) == Prop->RepNotifyFunc)
{
TargetFunc = TestFunc;
break;
}
}
SearchClass = SearchClass->GetSuperClass();
}
if( TargetFunc )
{
if (TargetFunc->GetReturnProperty())
{
UngetToken(PropertyToken->Token);
FError::Throwf(TEXT("Replication notification function %s must not have return values"), *Prop->RepNotifyFunc.ToString());
break;
}
bool IsArrayProperty = ( Prop->ArrayDim > 1 || Cast<UArrayProperty>(Prop) );
int32 MaxParms = IsArrayProperty ? 2 : 1;
if ( TargetFunc->NumParms > MaxParms)
{
UngetToken(PropertyToken->Token);
FError::Throwf(TEXT("Replication notification function %s has too many parameters"), *Prop->RepNotifyFunc.ToString());
break;
}
TFieldIterator<UProperty> Parm(TargetFunc);
if ( TargetFunc->NumParms >= 1 && Parm)
{
// First parameter is always the old value:
if ( Parm->GetClass() != Prop->GetClass() )
{
UngetToken(PropertyToken->Token);
FError::Throwf(TEXT("Replication notification function %s has invalid parameter for property $%s. First (optional) parameter must be a const reference of the same property type."), *Prop->RepNotifyFunc.ToString(), *Prop->GetName());
break;
}
++Parm;
}
if ( TargetFunc->NumParms >= 2 && Parm)
{
// A 2nd parameter for arrays can be specified as a const TArray<uint8>&. This is a list of element indices that have changed
UArrayProperty *ArrayProp = Cast<UArrayProperty>(*Parm);
if (!(ArrayProp && Cast<UByteProperty>(ArrayProp->Inner)) || !(Parm->GetPropertyFlags() & CPF_ConstParm) || !(Parm->GetPropertyFlags() & CPF_ReferenceParm))
{
UngetToken(PropertyToken->Token);
FError::Throwf(TEXT("Replication notification function %s (optional) parameter must be of type 'const TArray<uint8>&'"), *Prop->RepNotifyFunc.ToString());
break;
}
}
}
else
{
// Couldn't find a valid function...
UngetToken(PropertyToken->Token);
FError::Throwf(TEXT("Replication notification function %s not found"), *Prop->RepNotifyFunc.ToString() );
}
}
}
}
/*-----------------------------------------------------------------------------
Compiler directives.
-----------------------------------------------------------------------------*/
//
// Process a compiler directive.
//
void FHeaderParser::CompileDirective(FClasses& AllClasses)
{
FUnrealSourceFile* CurrentSourceFilePtr = GetCurrentSourceFile();
TSharedRef<FUnrealSourceFile> CurrentSrcFile = CurrentSourceFilePtr->AsShared();
FToken Directive;
int32 LineAtStartOfDirective = InputLine;
// Define directive are skipped but they can be multiline.
bool bDefineDirective = false;
if (!GetIdentifier(Directive))
{
FError::Throwf(TEXT("Missing compiler directive after '#'") );
}
else if (Directive.Matches(TEXT("Error")))
{
FError::Throwf(TEXT("#Error directive encountered") );
}
else if (Directive.Matches(TEXT("pragma")))
{
// Ignore all pragmas
}
else if (Directive.Matches(TEXT("linenumber")))
{
FToken Number;
if (!GetToken(Number) || (Number.TokenType != TOKEN_Const) || (Number.Type != CPT_Int && Number.Type != CPT_Int64))
{
FError::Throwf(TEXT("Missing line number in line number directive"));
}
int32 newInputLine;
if ( Number.GetConstInt(newInputLine) )
{
InputLine = newInputLine;
}
}
else if (Directive.Matches(TEXT("include")))
{
FString ExpectedHeaderName = CurrentSrcFile->GetGeneratedHeaderFilename();
FToken IncludeName;
if (GetToken(IncludeName) && (IncludeName.TokenType == TOKEN_Const) && (IncludeName.Type == CPT_String))
{
if (FCString::Stricmp(IncludeName.String, *ExpectedHeaderName) == 0)
{
bSpottedAutogeneratedHeaderInclude = true;
}
}
}
else if (Directive.Matches(TEXT("if")))
{
// 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))
{
FError::Throwf(TEXT("Missing define name '#if'") );
}
if ( Define.Matches(TEXT("WITH_EDITORONLY_DATA")) )
{
PushCompilerDirective(ECompilerDirective::WithEditorOnlyData);
}
else if ( Define.Matches(TEXT("WITH_EDITOR")) )
{
PushCompilerDirective(ECompilerDirective::WithEditor);
}
else if (Define.Matches(TEXT("WITH_HOT_RELOAD")) || Define.Matches(TEXT("WITH_HOT_RELOAD_CTORS")) || Define.Matches(TEXT("1")))
{
PushCompilerDirective(ECompilerDirective::Insignificant);
}
else if ( Define.Matches(TEXT("CPP")) && bNotDefined)
{
PushCompilerDirective(ECompilerDirective::Insignificant);
}
else
{
FError::Throwf(TEXT("Unknown define '#if %s' in class or global scope"), Define.Identifier);
}
}
}
else if (Directive.Matches(TEXT("endif")))
{
if (CompilerDirectiveStack.Num() < 1)
{
FError::Throwf(TEXT("Unmatched '#endif' in class or global scope"));
}
CompilerDirectiveStack.Pop();
}
else if (Directive.Matches(TEXT("define")))
{
// Ignore the define directive (can be multiline).
bDefineDirective = true;
}
else if (Directive.Matches(TEXT("ifdef")) || Directive.Matches(TEXT("ifndef")))
{
PushCompilerDirective(ECompilerDirective::Insignificant);
}
else if (Directive.Matches(TEXT("undef")) || Directive.Matches(TEXT("else")))
{
// Ignore. UHT can only handle #if directive
}
else
{
FError::Throwf(TEXT("Unrecognized compiler directive %s"), Directive.Identifier );
}
// Skip to end of line (or end of multiline #define).
if (LineAtStartOfDirective == InputLine)
{
TCHAR LastCharacter = '\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();
}
}
}
/*-----------------------------------------------------------------------------
Variable declaration parser.
-----------------------------------------------------------------------------*/
void FHeaderParser::GetVarType(
FClasses& AllClasses,
FScope* Scope,
FPropertyBase& VarProperty,
uint64 Disallow,
FToken* OuterPropertyType,
EPropertyDeclarationStyle::Type PropertyDeclarationStyle,
EVariableCategory::Type VariableCategory,
FIndexRange* ParsedVarIndexRange
)
{
UStruct* OwnerStruct = Scope->IsFileScope() ? nullptr : ((FStructScope*)Scope)->GetStruct();
FName RepCallbackName = FName(NAME_None);
// Get flags.
uint64 Flags = 0;
uint64 ImpliedFlags = 0;
// force members to be 'blueprint read only' if in a const class
if (VariableCategory == EVariableCategory::Member)
{
if (UClass* OwnerClass = Cast<UClass>(OwnerStruct))
{
if (OwnerClass->ClassFlags & 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"));
// No specifiers are allowed inside a TArray
if ((OuterPropertyType == NULL) || !OuterPropertyType->Matches(TEXT("TArray")))
{
// 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")))
{
Flags |= CPF_ConstParm;
bNativeConst = true;
}
}
if (CompilerDirectiveStack.Num() > 0 && (CompilerDirectiveStack.Last()&ECompilerDirective::WithEditorOnlyData) != 0)
{
Flags |= CPF_EditorOnly;
}
// Store the start and end positions of the parsed type
if (ParsedVarIndexRange)
{
ParsedVarIndexRange->StartIndex = InputPos;
}
// Process the list of specifiers
bool bSeenEditSpecifier = false;
bool bSeenBlueprintEditSpecifier = 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)
{
FError::Throwf(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)
{
FError::Throwf(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)
{
FError::Throwf(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)
{
FError::Throwf(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)
{
FError::Throwf(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)
{
FError::Throwf(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 (bSeenBlueprintEditSpecifier)
{
FError::Throwf(TEXT("Found more than one Blueprint read/write specifier (%s), only one is allowed"), *Specifier.Key);
}
const FString* PrivateAccessMD = MetaDataFromNewStyle.Find(TEXT("AllowPrivateAccess")); // FBlueprintMetadata::MD_AllowPrivateAccess
const bool bAllowPrivateAccess = PrivateAccessMD ? (*PrivateAccessMD == TEXT("true")) : false;
if (CurrentAccessSpecifier == ACCESS_Private && !bAllowPrivateAccess)
{
FError::Throwf(TEXT("BlueprintReadWrite should not be used on private members"));
}
if ((Flags & CPF_EditorOnly) != 0 && OwnerStruct->IsA<UScriptStruct>())
{
FError::Throwf(TEXT("Blueprint exposed struct members cannot be editor only"));
}
Flags |= CPF_BlueprintVisible;
bSeenBlueprintEditSpecifier = true;
}
break;
case EVariableSpecifier::BlueprintReadOnly:
{
if (bSeenBlueprintEditSpecifier)
{
FError::Throwf(TEXT("Found more than one Blueprint read/write specifier (%s), only one is allowed"), *Specifier.Key);
}
const FString* PrivateAccessMD = MetaDataFromNewStyle.Find(TEXT("AllowPrivateAccess")); // FBlueprintMetadata::MD_AllowPrivateAccess
const bool bAllowPrivateAccess = PrivateAccessMD ? (*PrivateAccessMD == TEXT("true")) : false;
if (CurrentAccessSpecifier == ACCESS_Private && !bAllowPrivateAccess)
{
FError::Throwf(TEXT("BlueprintReadOnly should not be used on private members"));
}
if ((Flags & CPF_EditorOnly) != 0 && OwnerStruct->IsA<UScriptStruct>())
{
FError::Throwf(TEXT("Blueprint exposed struct members cannot be editor only"));
}
Flags |= CPF_BlueprintVisible | CPF_BlueprintReadOnly;
ImpliedFlags &= ~CPF_BlueprintReadOnly;
bSeenBlueprintEditSpecifier = true;
}
break;
case EVariableSpecifier::Config:
{
Flags |= CPF_Config;
}
break;
case EVariableSpecifier::GlobalConfig:
{
Flags |= CPF_GlobalConfig | CPF_Config;
}
break;
case EVariableSpecifier::Localized:
{
FError::Throwf(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:
{
UE_LOG_WARNING_UHT(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:
{
FError::Throwf(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 (OwnerStruct->IsA<UScriptStruct>())
{
FError::Throwf(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(Specifier));
Flags |= CPF_RepNotify;
}
}
break;
case EVariableSpecifier::NotReplicated:
{
if (!OwnerStruct->IsA<UScriptStruct>())
{
FError::Throwf(TEXT("Only Struct members can be marked NotReplicated"));
}
Flags |= CPF_RepSkip;
}
break;
case EVariableSpecifier::RepRetry:
{
FError::Throwf(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;
default:
{
FError::Throwf(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
{
FError::Throwf(TEXT("Only parameters in service request functions can be marked NotReplicated"));
}
}
break;
default:
{
FError::Throwf(TEXT("Unknown variable specifier '%s'"), *Specifier.Key);
}
break;
}
}
}
{
const FString* ExposeOnSpawnStr = MetaDataFromNewStyle.Find(TEXT("ExposeOnSpawn"));
const bool bExposeOnSpawn = (NULL != ExposeOnSpawnStr);
if (bExposeOnSpawn)
{
if (0 != (CPF_DisableEditOnInstance & Flags))
{
UE_LOG_WARNING_UHT(TEXT("Property cannot have 'DisableEditOnInstance' or 'BlueprintReadOnly' and 'ExposeOnSpawn' flags"));
}
if (0 == (CPF_BlueprintVisible & Flags))
{
UE_LOG_WARNING_UHT(TEXT("Property cannot have 'ExposeOnSpawn' with '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
{
FError::Throwf(TEXT("Unknown access level"));
}
// Swallow inline keywords
if (VariableCategory == EVariableCategory::Return)
{
FToken InlineToken;
if (!GetIdentifier(InlineToken, true))
{
FError::Throwf(TEXT("%s: Missing variable type"), GetHintText(VariableCategory));
}
if (FCString::Strcmp(InlineToken.Identifier, TEXT("inline")) != 0
&& FCString::Strcmp(InlineToken.Identifier, TEXT("FORCENOINLINE")) != 0
&& FCString::Strncmp(InlineToken.Identifier, TEXT("FORCEINLINE"), 11) != 0)
{
UngetToken(InlineToken);
}
}
// Get variable type.
bool bUnconsumedStructKeyword = false;
bool bUnconsumedClassKeyword = false;
bool bUnconsumedEnumKeyword = false;
bool bUnconsumedConstKeyword = false;
if (MatchIdentifier(TEXT("const")))
{
//@TODO: UCREMOVAL: Should use this to set the new (currently non-existent) CPF_Const flag appropriately!
bUnconsumedConstKeyword = true;
bNativeConst = true;
}
if (MatchIdentifier(TEXT("mutable")))
{
//@TODO: Should flag as settable from a const context, but this is at least good enough to allow use for C++ land
}
if (MatchIdentifier(TEXT("struct")))
{
bUnconsumedStructKeyword = true;
}
else if (MatchIdentifier(TEXT("class")))
{
bUnconsumedClassKeyword = true;
}
else if (MatchIdentifier(TEXT("enum")))
{
if (VariableCategory == EVariableCategory::Member)
{
FError::Throwf(TEXT("%s: Cannot declare enum at variable declaration"), GetHintText(VariableCategory));
}
bUnconsumedEnumKeyword = true;
}
//
FToken VarType;
if ( !GetIdentifier(VarType,1) )
{
FError::Throwf(TEXT("%s: Missing variable type"), GetHintText(VariableCategory));
}
if ( VarType.Matches(TEXT("int8")) )
{
VarProperty = FPropertyBase(CPT_Int8);
}
else if ( VarType.Matches(TEXT("int16")) )
{
VarProperty = FPropertyBase(CPT_Int16);
}
else if ( VarType.Matches(TEXT("int32")) )
{
VarProperty = FPropertyBase(CPT_Int);
}
else if ( VarType.Matches(TEXT("int64")) )
{
VarProperty = FPropertyBase(CPT_Int64);
}
else if ( VarType.Matches(TEXT("uint32")) && IsBitfieldProperty() )
{
// 32-bit bitfield (bool) type, treat it like 8 bit type
VarProperty = FPropertyBase(CPT_Bool8);
}
else if ( VarType.Matches(TEXT("uint16")) && IsBitfieldProperty() )
{
// 16-bit bitfield (bool) type, treat it like 8 bit type.
VarProperty = FPropertyBase(CPT_Bool8);
}
else if ( VarType.Matches(TEXT("uint8")) && IsBitfieldProperty() )
{
// 8-bit bitfield (bool) type
VarProperty = FPropertyBase(CPT_Bool8);
}
else if ( VarType.Matches(TEXT("int")) )
{
VarProperty = FPropertyBase(CPT_Int, EIntType::Unsized);
}
else if ( VarType.Matches(TEXT("signed")) )
{
MatchIdentifier(TEXT("int"));
VarProperty = FPropertyBase(CPT_Int, EIntType::Unsized);
}
else if (VarType.Matches(TEXT("unsigned")))
{
MatchIdentifier(TEXT("int"));
VarProperty = FPropertyBase(CPT_UInt32, EIntType::Unsized);
}
else if ( VarType.Matches(TEXT("bool")) )
{
if (IsBitfieldProperty())
{
FError::Throwf(TEXT("bool bitfields are not supported."));
}
// C++ bool type
VarProperty = FPropertyBase(CPT_Bool);
}
else if ( VarType.Matches(TEXT("uint8")) )
{
// Intrinsic Byte type.
VarProperty = FPropertyBase(CPT_Byte);
}
else if ( VarType.Matches(TEXT("uint16")) )
{
VarProperty = FPropertyBase(CPT_UInt16);
}
else if ( VarType.Matches(TEXT("uint32")) )
{
VarProperty = FPropertyBase(CPT_UInt32);
}
else if ( VarType.Matches(TEXT("uint64")) )
{
VarProperty = FPropertyBase(CPT_UInt64);
}
else if ( VarType.Matches(TEXT("float")) )
{
// Intrinsic single precision floating point type.
VarProperty = FPropertyBase(CPT_Float);
}
else if ( VarType.Matches(TEXT("double")) )
{
// Intrinsic double precision floating point type type.
VarProperty = FPropertyBase(CPT_Double);
}
else if ( VarType.Matches(TEXT("FName")) )
{
// Intrinsic Name type.
VarProperty = FPropertyBase(CPT_Name);
}
else if ( VarType.Matches(TEXT("TArray")) )
{
RequireSymbol( TEXT("<"), TEXT("'tarray'") );
// GetVarType() clears the property flags of the array var, so use dummy
// flags when getting the inner property
uint64 OriginalVarTypeFlags = VarType.PropertyFlags;
VarType.PropertyFlags |= Flags;
GetVarType(AllClasses, Scope, VarProperty, Disallow, &VarType, EPropertyDeclarationStyle::None, VariableCategory);
if (VarProperty.IsContainer())
{
FError::Throwf(TEXT("Nested containers are not supported.") );
}
if (VarProperty.MetaData.Find(TEXT("NativeConst")))
{
bNativeConstTemplateArg = true;
}
OriginalVarTypeFlags |= VarProperty.PropertyFlags & (CPF_ContainsInstancedReference | CPF_InstancedReference); // propagate these to the array, we will fix them later
VarType.PropertyFlags = OriginalVarTypeFlags;
VarProperty.ArrayType = EArrayType::Dynamic;
FToken CloseTemplateToken;
if (!GetToken(CloseTemplateToken, /*bNoConsts=*/ true, ESymbolParseOption::CloseTemplateBracket))
{
FError::Throwf(TEXT("Missing token while parsing TArray."));
}
if (CloseTemplateToken.TokenType != TOKEN_Symbol || FCString::Stricmp(CloseTemplateToken.Identifier, TEXT(">")))
{
// If we didn't find a comma, report it
if (FCString::Stricmp(CloseTemplateToken.Identifier, TEXT(",")))
{
FError::Throwf(TEXT("Expected '>' but found '%s'"), CloseTemplateToken.Identifier);
}
// 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))
{
FError::Throwf(TEXT("Expected '>' but found '%s'"), CloseTemplateToken.Identifier);
}
FError::Throwf(TEXT("Found '%s' - explicit allocators are not supported in TArray properties."), AllocatorToken.Identifier);
}
}
else if ( VarType.Matches(TEXT("TMap")) )
{
RequireSymbol( TEXT("<"), TEXT("'tmap'") );
// GetVarType() clears the property flags of the array var, so use dummy
// flags when getting the inner property
uint64 OriginalVarTypeFlags = VarType.PropertyFlags;
VarType.PropertyFlags |= Flags;
FToken MapKeyType;
GetVarType(AllClasses, Scope, MapKeyType, Disallow, &VarType, EPropertyDeclarationStyle::None, VariableCategory);
if (MapKeyType.IsContainer())
{
FError::Throwf(TEXT("Nested containers are not supported.") );
}
if (MapKeyType.Type == CPT_Interface)
{
FError::Throwf(TEXT("UINTERFACEs are not currently supported as key types."));
}
if (MapKeyType.Type == CPT_Text)
{
FError::Throwf(TEXT("FText is not currently supported as a key type."));
}
FToken CommaToken;
if (!GetToken(CommaToken, /*bNoConsts=*/ true) || CommaToken.TokenType != TOKEN_Symbol || FCString::Stricmp(CommaToken.Identifier, TEXT(",")))
{
FError::Throwf(TEXT("Missing value type while parsing TMap."));
}
GetVarType(AllClasses, Scope, VarProperty, Disallow, &VarType, EPropertyDeclarationStyle::None, VariableCategory);
if (VarProperty.IsContainer())
{
FError::Throwf(TEXT("Nested containers are not supported.") );
}
OriginalVarTypeFlags |= VarProperty.PropertyFlags & (CPF_ContainsInstancedReference | CPF_InstancedReference); // propagate these to the map value, we will fix them later
OriginalVarTypeFlags |= MapKeyType .PropertyFlags & (CPF_ContainsInstancedReference | CPF_InstancedReference); // propagate these to the map key, we will fix them later
VarType.PropertyFlags = OriginalVarTypeFlags;
FToken* MapKeyProp = new FToken(MapKeyType);
VarProperty.MapKeyProp = MakeShareable<FToken>(MapKeyProp);
UHTMakefile.AddToken(GetCurrentSourceFile(), MapKeyProp);
VarProperty.MapKeyProp->PropertyFlags = OriginalVarTypeFlags;
FToken CloseTemplateToken;
if (!GetToken(CloseTemplateToken, /*bNoConsts=*/ true, ESymbolParseOption::CloseTemplateBracket))
{
FError::Throwf(TEXT("Missing token while parsing TMap."));
}
if (CloseTemplateToken.TokenType != TOKEN_Symbol || FCString::Stricmp(CloseTemplateToken.Identifier, TEXT(">")))
{
// If we didn't find a comma, report it
if (FCString::Stricmp(CloseTemplateToken.Identifier, TEXT(",")))
{
FError::Throwf(TEXT("Expected '>' but found '%s'"), CloseTemplateToken.Identifier);
}
// 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))
{
FError::Throwf(TEXT("Expected '>' but found '%s'"), CloseTemplateToken.Identifier);
}
FError::Throwf(TEXT("Found '%s' - explicit allocators are not supported in TMap properties."), AllocatorToken.Identifier);
}
}
else if ( VarType.Matches(TEXT("TSet")) )
{
RequireSymbol( TEXT("<"), TEXT("'tset'") );
// GetVarType() clears the property flags of the array var, so use dummy
// flags when getting the inner property
uint64 OriginalVarTypeFlags = VarType.PropertyFlags;
VarType.PropertyFlags |= Flags;
GetVarType(AllClasses, Scope, VarProperty, Disallow, &VarType, EPropertyDeclarationStyle::None, VariableCategory);
if (VarProperty.IsContainer())
{
FError::Throwf(TEXT("Nested containers are not supported.") );
}
if (VarProperty.Type == CPT_Interface)
{
FError::Throwf(TEXT("UINTERFACEs are not currently supported as element types."));
}
if (VarProperty.Type == CPT_Text)
{
FError::Throwf(TEXT("FText is not currently supported as an element type."));
}
OriginalVarTypeFlags |= VarProperty.PropertyFlags & (CPF_ContainsInstancedReference | CPF_InstancedReference); // propagate these to the set, we will fix them later
VarType.PropertyFlags = OriginalVarTypeFlags;
VarProperty.ArrayType = EArrayType::Set;
FToken CloseTemplateToken;
if (!GetToken(CloseTemplateToken, /*bNoConsts=*/ true, ESymbolParseOption::CloseTemplateBracket))
{
FError::Throwf(TEXT("Missing token while parsing TArray."));
}
if (CloseTemplateToken.TokenType != TOKEN_Symbol || FCString::Stricmp(CloseTemplateToken.Identifier, TEXT(">")))
{
// If we didn't find a comma, report it
if (FCString::Stricmp(CloseTemplateToken.Identifier, TEXT(",")))
{
FError::Throwf(TEXT("Expected '>' but found '%s'"), CloseTemplateToken.Identifier);
}
// 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))
{
FError::Throwf(TEXT("Expected '>' but found '%s'"), CloseTemplateToken.Identifier);
}
FError::Throwf(TEXT("Found '%s' - explicit allocators are not supported in TSet properties."), AllocatorToken.Identifier);
}
}
else if ( VarType.Matches(TEXT("FString")) )
{
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.Matches(TEXT("Text") ) )
{
FError::Throwf(TEXT("%s' is missing a prefix, expecting 'FText'"), VarType.Identifier);
}
else if ( VarType.Matches(TEXT("FText") ) )
{
VarProperty = FPropertyBase(CPT_Text);
}
else if (VarType.Matches(TEXT("TEnumAsByte")))
{
RequireSymbol(TEXT("<"), VarType.Identifier);
// Eat the forward declaration enum text if present
MatchIdentifier(TEXT("enum"));
bool bFoundEnum = false;
FToken InnerEnumType;
if (GetIdentifier(InnerEnumType, true))
{
if (UEnum* Enum = FindObject<UEnum>(ANY_PACKAGE, InnerEnumType.Identifier))
{
// In-scope enumeration.
VarProperty = FPropertyBase(Enum, 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))
{
FError::Throwf(TEXT("Expected a namespace scoped enum name.") );
}
}
if (!bFoundEnum)
{
FError::Throwf(TEXT("Expected the name of a previously defined enum"));
}
RequireSymbol(TEXT(">"), VarType.Identifier, ESymbolParseOption::CloseTemplateBracket);
}
else if (UEnum* Enum = FindObject<UEnum>(ANY_PACKAGE, VarType.Identifier))
{
EPropertyType UnderlyingType = CPT_Byte;
if (VariableCategory == EVariableCategory::Member)
{
EUnderlyingEnumType* EnumUnderlyingType = GEnumUnderlyingTypes.Find(Enum);
if (!EnumUnderlyingType)
{
FError::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."), *Enum->CppType);
}
}
// 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))
{
FError::Throwf(TEXT("Expected a namespace scoped enum name.") );
}
}
// In-scope enumeration.
VarProperty = FPropertyBase(Enum, UnderlyingType);
bUnconsumedEnumKeyword = false;
}
else
{
// Check for structs/classes
bool bHandledType = false;
FString IdentifierStripped = GetClassNameWithPrefixRemoved(VarType.Identifier);
bool bStripped = false;
UScriptStruct* Struct = FindObject<UScriptStruct>( ANY_PACKAGE, VarType.Identifier );
if (!Struct)
{
Struct = FindObject<UScriptStruct>( ANY_PACKAGE, *IdentifierStripped );
bStripped = true;
}
auto SetDelegateType = [&](UFunction* InFunction, const FString& InIdentifierStripped)
{
bHandledType = true;
VarProperty = FPropertyBase(InFunction->HasAnyFunctionFlags(FUNC_MulticastDelegate) ? CPT_MulticastDelegate : CPT_Delegate);
VarProperty.DelegateName = *InIdentifierStripped;
if (!(Disallow & CPF_InstancedReference))
{
Flags |= CPF_InstancedReference;
}
};
if (!Struct && MatchSymbol(TEXT("::")))
{
FToken DelegateName;
if (GetIdentifier(DelegateName))
{
UClass* LocalOwnerClass = AllClasses.FindClass(*IdentifierStripped);
if (LocalOwnerClass)
{
TSharedRef<FScope> LocScope = FScope::GetTypeScope(LocalOwnerClass);
const FString DelegateIdentifierStripped = GetClassNameWithPrefixRemoved(DelegateName.Identifier);
if (UFunction* DelegateFunc = Cast<UFunction>(LocScope->FindTypeByName(*(DelegateIdentifierStripped + HEADER_GENERATED_DELEGATE_SIGNATURE_SUFFIX))))
{
SetDelegateType(DelegateFunc, DelegateIdentifierStripped);
VarProperty.DelegateSignatureOwnerClass = LocalOwnerClass;
}
}
else
{
FError::Throwf(TEXT("Cannot find class '%s', to resolve delegate '%s'"), *IdentifierStripped, DelegateName.Identifier);
}
}
}
if (bHandledType)
{
}
else if (Struct)
{
if (bStripped)
{
const TCHAR* PrefixCPP = StructsWithTPrefix.Contains(IdentifierStripped) ? TEXT("T") : Struct->GetPrefixCPP();
FString ExpectedStructName = FString::Printf(TEXT("%s%s"), PrefixCPP, *Struct->GetName() );
if( FString(VarType.Identifier) != ExpectedStructName )
{
FError::Throwf( TEXT("Struct '%s' is missing or has an incorrect prefix, expecting '%s'"), VarType.Identifier, *ExpectedStructName );
}
}
else if( !StructsWithNoPrefix.Contains(VarType.Identifier) )
{
const TCHAR* PrefixCPP = StructsWithTPrefix.Contains(VarType.Identifier) ? TEXT("T") : Struct->GetPrefixCPP();
FError::Throwf(TEXT("Struct '%s' is missing a prefix, expecting '%s'"), VarType.Identifier, *FString::Printf(TEXT("%s%s"), PrefixCPP, *Struct->GetName()) );
}
bHandledType = true;
VarProperty = FPropertyBase( Struct );
if((Struct->StructFlags & STRUCT_HasInstancedReference) && !(Disallow & CPF_ContainsInstancedReference))
{
Flags |= CPF_ContainsInstancedReference;
}
// Struct keyword in front of a struct is legal, we 'consume' it
bUnconsumedStructKeyword = false;
}
else if ( FindObject<UScriptStruct>( ANY_PACKAGE, *IdentifierStripped ) != nullptr)
{
bHandledType = true;
// Struct keyword in front of a struct is legal, we 'consume' it
bUnconsumedStructKeyword = false;
}
else if (UFunction* DelegateFunc = Cast<UFunction>(Scope->FindTypeByName(*(IdentifierStripped + HEADER_GENERATED_DELEGATE_SIGNATURE_SUFFIX))))
{
SetDelegateType(DelegateFunc, IdentifierStripped);
}
else
{
// An object reference of some type (maybe a restricted class?)
UClass* TempClass = NULL;
const bool bIsLazyPtrTemplate = VarType.Matches(TEXT("TLazyObjectPtr"));
const bool bIsAssetPtrTemplate = VarType.Matches(TEXT("TAssetPtr"));
const bool bIsAssetClassTemplate = VarType.Matches(TEXT("TAssetSubclassOf"));
const bool bIsWeakPtrTemplate = VarType.Matches(TEXT("TWeakObjectPtr"));
const bool bIsAutoweakPtrTemplate = VarType.Matches(TEXT("TAutoWeakObjectPtr"));
const bool bIsScriptInterfaceWrapper = VarType.Matches(TEXT("TScriptInterface"));
const bool bIsSubobjectPtrTemplate = VarType.Matches(TEXT("TSubobjectPtr"));
bool bIsWeak = false;
bool bIsLazy = false;
bool bIsAsset = false;
bool bWeakIsAuto = false;
if (VarType.Matches(TEXT("TSubclassOf")))
{
TempClass = UClass::StaticClass();
}
else if (VarType.Matches(TEXT("FScriptInterface")))
{
TempClass = UInterface::StaticClass();
Flags |= CPF_UObjectWrapper;
}
else if (bIsAssetClassTemplate)
{
TempClass = UClass::StaticClass();
bIsAsset = true;
}
else if (bIsLazyPtrTemplate || bIsWeakPtrTemplate || bIsAutoweakPtrTemplate || bIsScriptInterfaceWrapper || bIsAssetPtrTemplate || bIsSubobjectPtrTemplate)
{
RequireSymbol(TEXT("<"), VarType.Identifier);
// Consume a forward class declaration 'class' if present
MatchIdentifier(TEXT("class"));
// Also consume const
bNativeConstTemplateArg |= MatchIdentifier(TEXT("const"));
// Find the lazy/weak class
FToken InnerClass;
if (GetIdentifier(InnerClass))
{
TempClass = AllClasses.FindScriptClass(InnerClass.Identifier);
if (TempClass == nullptr)
{
FError::Throwf(TEXT("Unrecognized type '%s' (in expression %s<%s>) - type must be a UCLASS"), InnerClass.Identifier, VarType.Identifier, InnerClass.Identifier);
}
if (bIsAutoweakPtrTemplate)
{
bIsWeak = true;
bWeakIsAuto = true;
}
else if (bIsLazyPtrTemplate)
{
bIsLazy = true;
}
else if (bIsWeakPtrTemplate)
{
bIsWeak = true;
}
else if (bIsAssetPtrTemplate)
{
bIsAsset = true;
}
else if (bIsSubobjectPtrTemplate)
{
Flags |= CPF_SubobjectReference | CPF_InstancedReference;
}
Flags |= CPF_UObjectWrapper;
}
else
{
FError::Throwf(TEXT("%s: Missing template type"), VarType.Identifier);
}
RequireSymbol(TEXT(">"), VarType.Identifier, ESymbolParseOption::CloseTemplateBracket);
}
else
{
TempClass = AllClasses.FindScriptClass(VarType.Identifier);
}
if (TempClass != NULL)
{
bHandledType = true;
bool bAllowWeak = !(Disallow & CPF_AutoWeak); // if it is not allowing anything, force it strong. this is probably a function arg
VarProperty = FPropertyBase( TempClass, NULL, bAllowWeak, bIsWeak, bWeakIsAuto, bIsLazy, bIsAsset );
if (TempClass->IsChildOf(UClass::StaticClass()))
{
if ( MatchSymbol(TEXT("<")) )
{
Flags |= CPF_UObjectWrapper;
// Consume a forward class declaration 'class' if present
MatchIdentifier(TEXT("class"));
// Get the actual class type to restrict this to
FToken Limitor;
if( !GetIdentifier(Limitor) )
{
FError::Throwf(TEXT("'class': Missing class limitor"));
}
VarProperty.MetaClass = AllClasses.FindScriptClassOrThrow(Limitor.Identifier);
RequireSymbol( TEXT(">"), TEXT("'class limitor'"), ESymbolParseOption::CloseTemplateBracket );
}
else
{
VarProperty.MetaClass = UObject::StaticClass();
}
if (bIsWeak)
{
FError::Throwf(TEXT("Class variables cannot be weak, they are always strong."));
}
if (bIsLazy)
{
FError::Throwf(TEXT("Class variables cannot be lazy, they are always strong."));
}
if (bIsAssetPtrTemplate)
{
FError::Throwf(TEXT("Class variables cannot be stored in TAssetPtr, use TAssetSubclassOf instead."));
}
}
// Inherit instancing flags
if (DoesAnythingInHierarchyHaveDefaultToInstanced(TempClass))
{
Flags |= ((CPF_InstancedReference|CPF_ExportObject) & (~Disallow));
}
// 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"));
RequireSymbol(TEXT("*"), TEXT("Expected a pointer type"));
VarProperty.PointerType = EPointerType::Native;
}
// Imply const if it's a parameter that is a pointer to a const class
if (VariableCategory != EVariableCategory::Member && (TempClass != NULL) && (TempClass->HasAnyClassFlags(CLASS_Const)))
{
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)
{
if (UFunction* DelegateFunc = (UFunction*)StaticFindObject(UFunction::StaticClass(), ANY_PACKAGE, *(IdentifierStripped + HEADER_GENERATED_DELEGATE_SIGNATURE_SUFFIX)))
{
SetDelegateType(DelegateFunc, IdentifierStripped);
}
if (!bHandledType)
{
FError::Throwf(TEXT("Unrecognized type '%s' - type must be a UCLASS, USTRUCT or UENUM"), VarType.Identifier );
}
}
}
if (VariableCategory != EVariableCategory::Member)
{
// const after the variable type support (only for params)
if (MatchIdentifier(TEXT("const")))
{
Flags |= CPF_ConstParm;
bNativeConst = true;
}
}
if (bUnconsumedConstKeyword)
{
if (VariableCategory == EVariableCategory::Member)
{
FError::Throwf(TEXT("Const properties are not supported."));
}
else
{
FError::Throwf(TEXT("Inappropriate keyword 'const' on variable of type '%s'"), VarType.Identifier);
}
}
if (bUnconsumedClassKeyword)
{
FError::Throwf(TEXT("Inappropriate keyword 'class' on variable of type '%s'"), VarType.Identifier );
}
if (bUnconsumedStructKeyword)
{
FError::Throwf(TEXT("Inappropriate keyword 'struct' on variable of type '%s'"), VarType.Identifier );
}
if (bUnconsumedEnumKeyword)
{
FError::Throwf(TEXT("Inappropriate keyword 'enum' on variable of type '%s'"), VarType.Identifier );
}
if (MatchSymbol(TEXT("*")))
{
FError::Throwf(TEXT("Inappropriate '*' on variable of type '%s', cannot have an exposed pointer to this type."), VarType.Identifier );
}
//@TODO: UCREMOVAL: 'const' member variables that will get written post-construction by defaultproperties
if (VariableCategory == EVariableCategory::Member && OwnerStruct->IsA<UClass>() && ((UClass*)OwnerStruct)->HasAnyClassFlags(CLASS_Const))
{
// Eat a 'not quite truthful' const after the type; autogenerated for member variables of const classes.
bNativeConst |= MatchIdentifier(TEXT("const"));
}
// 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))
{
FError::Throwf(TEXT("Replicated %s parameters cannot be passed by non-const reference"), VarType.Identifier);
}
Flags |= CPF_ReferenceParm;
}
break;
default:
{
}
break;
}
if (Flags & CPF_ConstParm)
{
VarProperty.RefQualifier = ERefQualifier::ConstRef;
}
else
{
VarProperty.RefQualifier = ERefQualifier::NonConstRef;
}
}
VarProperty.PropertyExportFlags = ExportFlags;
// Set FPropertyBase info.
VarProperty.PropertyFlags |= Flags | ImpliedFlags;
VarProperty.ImpliedPropertyFlags |= ImpliedFlags;
// Set the RepNotify name, if the variable needs it
if( VarProperty.PropertyFlags & CPF_RepNotify )
{
if( RepCallbackName != NAME_None )
{
VarProperty.RepNotifyName = RepCallbackName;
}
else
{
FError::Throwf(TEXT("Must specify a valid function name for replication notifications"));
}
}
// Perform some more specific validation on the property flags
if (VarProperty.PropertyFlags & CPF_PersistentInstance)
{
if (VarProperty.Type == CPT_ObjectReference)
{
if (VarProperty.PropertyClass->IsChildOf<UClass>())
{
FError::Throwf(TEXT("'Instanced' cannot be applied to class properties (UClass* or TSubclassOf<>)"));
}
}
else
{
FError::Throwf(TEXT("'Instanced' is only allowed on object property (or array of objects)"));
}
}
if ( VarProperty.IsObject() && VarProperty.MetaClass == NULL && (VarProperty.PropertyFlags&CPF_Config) != 0 )
{
FError::Throwf(TEXT("Not allowed to use 'config' with object variables"));
}
if ((VarProperty.PropertyFlags & CPF_BlueprintAssignable) && VarProperty.Type != CPT_MulticastDelegate)
{
FError::Throwf(TEXT("'BlueprintAssignable' is only allowed on multicast delegate properties"));
}
if ((VarProperty.PropertyFlags & CPF_BlueprintCallable) && VarProperty.Type != CPT_MulticastDelegate)
{
FError::Throwf(TEXT("'BlueprintCallable' is only allowed on a property when it is a multicast delegate"));
}
if ((VarProperty.PropertyFlags & CPF_BlueprintAuthorityOnly) && VarProperty.Type != CPT_MulticastDelegate)
{
FError::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(VarProperty, OuterPropertyType);
}
// Check for invalid transients
uint64 Transients = VarProperty.PropertyFlags & (CPF_DuplicateTransient | CPF_TextExportTransient | CPF_NonPIEDuplicateTransient);
if (Transients && !Cast<UClass>(OwnerStruct))
{
TArray<const TCHAR*> FlagStrs = ParsePropertyFlags(Transients);
FError::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 )
{
FError::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())
{
KeyProp->PropertyFlags = VarProperty.PropertyFlags;
}
VarProperty.MetaData = MetaDataFromNewStyle;
if (bNativeConst)
{
VarProperty.MetaData.Add(TEXT("NativeConst"), TEXT(""));
}
if (bNativeConstTemplateArg)
{
VarProperty.MetaData.Add(TEXT("NativeConstTemplateArg"), TEXT(""));
}
if (ParsedVarIndexRange)
{
ParsedVarIndexRange->Count = InputPos - ParsedVarIndexRange->StartIndex;
}
}
/**
* If the property has already been seen during compilation, then return add. If not,
* then return replace so that INI files don't mess with header exporting
*
* @param PropertyName the string token for the property
*
* @return FNAME_Replace_Not_Safe_For_Threading or FNAME_Add
*/
EFindName FHeaderParser::GetFindFlagForPropertyName(const TCHAR* PropertyName)
{
static TMap<FString,int32> PreviousNames;
FString PropertyStr(PropertyName);
FString UpperPropertyStr = PropertyStr.ToUpper();
// See if it's in the list already
if (PreviousNames.Find(UpperPropertyStr))
{
return FNAME_Add;
}
// Add it to the list for future look ups
PreviousNames.Add(UpperPropertyStr,1);
FName CurrentText(PropertyName,FNAME_Find); // keep generating this FName in case it has been affecting the case of future FNames.
return FNAME_Replace_Not_Safe_For_Threading;
}
UProperty* FHeaderParser::GetVarNameAndDim
(
UStruct* Scope,
FToken& VarProperty,
EVariableCategory::Type VariableCategory
)
{
check(Scope);
FUnrealSourceFile* CurrentSrcFile = GetCurrentSourceFile();
EObjectFlags ObjectFlags = RF_Public;
if (VariableCategory == EVariableCategory::Member && CurrentAccessSpecifier == ACCESS_Private)
{
ObjectFlags = RF_NoFlags;
}
const TCHAR* HintText = GetHintText(VariableCategory);
AddModuleRelativePathToMetadata(Scope, VarProperty.MetaData);
// Get variable name.
if (VariableCategory == EVariableCategory::Return)
{
// Hard-coded variable name, such as with return value.
VarProperty.TokenType = TOKEN_Identifier;
FCString::Strcpy( VarProperty.Identifier, TEXT("ReturnValue") );
}
else
{
FToken VarToken;
if (!GetIdentifier(VarToken))
{
FError::Throwf(TEXT("Missing variable name") );
}
VarProperty.TokenType = TOKEN_Identifier;
FCString::Strcpy(VarProperty.Identifier, VarToken.Identifier);
}
// Check to see if the variable is deprecated, and if so set the flag
{
FString VarName(VarProperty.Identifier);
const int32 DeprecatedIndex = VarName.Find(TEXT("_DEPRECATED"));
const int32 NativizedPropertyPostfixIndex = VarName.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 != VarName.Len() - 11)
{
FError::Throwf(TEXT("Deprecated variables must end with _DEPRECATED"));
}
// Warn if a deprecated property is visible
if (VarProperty.PropertyFlags & (CPF_Edit | CPF_EditConst | CPF_BlueprintVisible | CPF_BlueprintReadOnly) && !(VarProperty.ImpliedPropertyFlags & CPF_BlueprintReadOnly))
{
UE_LOG_WARNING_UHT(TEXT("%s: Deprecated property '%s' should not be marked as visible or editable"), HintText, *VarName);
}
VarProperty.PropertyFlags |= CPF_Deprecated;
VarName = VarName.Mid(0, DeprecatedIndex);
FCString::Strcpy(VarProperty.Identifier, *VarName);
}
}
// Make sure it doesn't conflict.
int32 OuterContextCount = 0;
UField* Existing = FindField(Scope, VarProperty.Identifier, true, UField::StaticClass(), NULL);
if (Existing != nullptr)
{
bool bErrorDueToShadowing = true;
if (Existing->IsA(UFunction::StaticClass()) && (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.
{
UProperty* ExistingProp = Cast<UProperty>(Existing);
const bool bExistingPropDeprecated = (ExistingProp != nullptr) && ExistingProp->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)
{
FError::Throwf(TEXT("%s: '%s' cannot be defined in '%s' as it is already defined in scope '%s' (shadowing is not allowed)"), HintText, VarProperty.Identifier, *Scope->GetName(), *Existing->GetOuter()->GetName());
}
}
// Get optional dimension immediately after name.
FToken Dimensions;
if (MatchSymbol(TEXT("[")))
{
switch (VariableCategory)
{
case EVariableCategory::Return:
{
FError::Throwf(TEXT("Arrays aren't allowed as return types"));
}
case EVariableCategory::RegularParameter:
case EVariableCategory::ReplicatedParameter:
{
FError::Throwf(TEXT("Arrays aren't allowed as function parameters"));
}
}
if (VarProperty.IsContainer())
{
FError::Throwf(TEXT("Static arrays of containers are not allowed"));
}
if (VarProperty.IsBool())
{
FError::Throwf(TEXT("Bool arrays are not allowed") );
}
// Ignore how the actual array dimensions are actually defined - we'll calculate those with the compiler anyway.
if (!GetRawToken(Dimensions, TEXT(']')))
{
FError::Throwf(TEXT("%s %s: Missing ']'"), HintText, VarProperty.Identifier );
}
// Only static arrays are declared with []. Dynamic arrays use TArray<> instead.
VarProperty.ArrayType = EArrayType::Static;
UEnum* Enum = nullptr;
if (*Dimensions.String)
{
FString Temp = Dimensions.String;
bool bAgain;
do
{
bAgain = false;
// Remove any casts
static const TCHAR* Casts[] = {
TEXT("(uint32)"),
TEXT("(int32)"),
TEXT("(uint16)"),
TEXT("(int16)"),
TEXT("(uint8)"),
TEXT("(int8)"),
TEXT("(int)"),
TEXT("(unsigned)"),
TEXT("(signed)"),
TEXT("(unsigned int)"),
TEXT("(signed int)")
};
// Remove any brackets
if (Temp[0] == TEXT('('))
{
int32 TempLen = Temp.Len();
int32 ClosingParen = FindMatchingClosingParenthesis(Temp);
if (ClosingParen == TempLen - 1)
{
Temp = Temp.Mid(1, TempLen - 2);
bAgain = true;
}
}
for (const TCHAR* Cast : Casts)
{
if (Temp.StartsWith(Cast))
{
Temp = Temp.RightChop(FCString::Strlen(Cast));
bAgain = true;
}
}
}
while (bAgain);
UEnum::LookupEnumNameSlow(*Temp, &Enum);
}
if (!Enum)
{
// If the enum wasn't declared in this scope, then try to find it anywhere we can
Enum = FindObject<UEnum>(ANY_PACKAGE, Dimensions.String);
}
if (Enum)
{
// set the ArraySizeEnum if applicable
VarProperty.MetaData.Add("ArraySizeEnum", Enum->GetPathName());
}
MatchSymbol(TEXT("]"));
}
// Try gathering metadata for member fields
if (VariableCategory == EVariableCategory::Member)
{
ParseFieldMetaData(VarProperty.MetaData, VarProperty.Identifier);
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)
{
FError::Throwf(TEXT("UFunctions cannot take a lazy pointer as a parameter."));
}
}
// If this is the first time seeing the property name, then flag it for replace instead of add
const EFindName FindFlag = VarProperty.PropertyFlags & CPF_Config ? GetFindFlagForPropertyName(VarProperty.Identifier) : FNAME_Add;
// create the FName for the property, splitting (ie Unnamed_3 -> Unnamed,3)
FName PropertyName(VarProperty.Identifier, FindFlag);
// Add property.
UProperty* NewProperty = nullptr;
{
UProperty* Prev = nullptr;
for (TFieldIterator<UProperty> It(Scope, EFieldIteratorFlags::ExcludeSuper); It; ++It)
{
Prev = *It;
}
UArrayProperty* Array = nullptr;
UMapProperty* Map = nullptr;
USetProperty* Set = nullptr; // TODO: Set Property
UProperty* NewMapKeyProperty = nullptr;
UObject* NewScope = Scope;
int32 ArrayDim = 1; // 1 = not a static array, 2 = static array
if (VarProperty.ArrayType == EArrayType::Dynamic)
{
Array = new (EC_InternalUseOnlyConstructor, Scope, PropertyName, ObjectFlags) UArrayProperty(FObjectInitializer());
UHTMakefile.AddArrayProperty(CurrentSrcFile, Array);
NewScope = Array;
ObjectFlags = RF_Public;
}
else if (VarProperty.ArrayType == EArrayType::Static)
{
ArrayDim = 2;
}
else if (VarProperty.ArrayType == EArrayType::Set)
{
Set = new (EC_InternalUseOnlyConstructor, Scope, PropertyName, ObjectFlags) USetProperty(FObjectInitializer());
UHTMakefile.AddSetProperty(CurrentSrcFile, Set);
NewScope = Set;
ObjectFlags = RF_Public;
}
else if (VarProperty.MapKeyProp.IsValid())
{
Map = new (EC_InternalUseOnlyConstructor, Scope, PropertyName, ObjectFlags) UMapProperty(FObjectInitializer());
UHTMakefile.AddMapProperty(CurrentSrcFile, Map);
NewScope = Map;
ObjectFlags = RF_Public;
NewMapKeyProperty = CreateVariableProperty(*VarProperty.MapKeyProp, NewScope, *(PropertyName.ToString() + TEXT("_Key")), ObjectFlags, VariableCategory, UHTMakefile, CurrentSrcFile);
}
NewProperty = CreateVariableProperty(VarProperty, NewScope, PropertyName, ObjectFlags, VariableCategory, UHTMakefile, CurrentSrcFile);
auto PropagateFlags = [](uint64 FlagsToPropagate, FPropertyBase& From, UProperty* To) {
// Copy some of the property flags to the inner property.
To->PropertyFlags |= (From.PropertyFlags & FlagsToPropagate);
// Copy some of the property flags to the array property.
if (To->PropertyFlags & (CPF_ContainsInstancedReference | CPF_InstancedReference))
{
From.PropertyFlags |= CPF_ContainsInstancedReference;
From.PropertyFlags &= ~(CPF_InstancedReference | CPF_PersistentInstance); //this was propagated to the inner
if (To->PropertyFlags & CPF_PersistentInstance)
{
TMap<FName, FString> MetaData;
AddEditInlineMetaData(MetaData);
AddMetaDataToClassData(To, From.MetaData);
}
}
};
if( Array )
{
Array->Inner = NewProperty;
PropagateFlags(CPF_PropagateToArrayInner, VarProperty, NewProperty);
NewProperty = Array;
}
if (Map)
{
Map->KeyProp = NewMapKeyProperty;
Map->ValueProp = NewProperty;
PropagateFlags(CPF_PropagateToMapKey, *VarProperty.MapKeyProp, NewMapKeyProperty);
PropagateFlags(CPF_PropagateToMapValue, VarProperty, NewProperty);
NewProperty = Map;
}
if (Set)
{
Set->ElementProp = NewProperty;
PropagateFlags(CPF_PropagateToSetElement, VarProperty, NewProperty);
NewProperty = Set;
}
NewProperty->ArrayDim = ArrayDim;
if (ArrayDim == 2)
{
GArrayDimensions.Add(NewProperty, Dimensions.String);
}
NewProperty->PropertyFlags = VarProperty.PropertyFlags;
if (Prev != nullptr)
{
NewProperty->Next = Prev->Next;
Prev->Next = NewProperty;
}
else
{
NewProperty->Next = Scope->Children;
Scope->Children = NewProperty;
}
}
VarProperty.TokenProperty = NewProperty;
FClassMetaData* ScopeData = GScriptHelper.FindClassData(Scope);
check(ScopeData);
ScopeData->AddProperty(VarProperty, UHTMakefile, CurrentSrcFile);
// if we had any metadata, add it to the class
AddMetaDataToClassData(VarProperty.TokenProperty, VarProperty.MetaData);
return NewProperty;
}
/*-----------------------------------------------------------------------------
Statement compiler.
-----------------------------------------------------------------------------*/
//
// Compile a declaration in Token. Returns 1 if compiled, 0 if not.
//
bool FHeaderParser::CompileDeclaration(FClasses& AllClasses, TArray<UDelegateFunction*>& DelegatesToFixup, FToken& Token)
{
EAccessSpecifier AccessSpecifier = ParseAccessProtectionSpecifier(Token);
if (AccessSpecifier)
{
if (!IsAllowedInThisNesting(ENestAllowFlags::VarDecl) && !IsAllowedInThisNesting(ENestAllowFlags::Function))
{
FError::Throwf(TEXT("Access specifier %s not allowed here."), Token.Identifier);
}
check(TopNest->NestType == ENestType::Class || TopNest->NestType == ENestType::Interface || TopNest->NestType == ENestType::NativeInterface);
CurrentAccessSpecifier = AccessSpecifier;
return true;
}
if (Token.Matches(TEXT("class")) && (TopNest->NestType == ENestType::GlobalScope))
{
// Make sure the previous class ended with valid nesting.
if (bEncounteredNewStyleClass_UnmatchedBrackets)
{
FError::Throwf(TEXT("Missing } at end of class"));
}
// Start parsing the second class
bEncounteredNewStyleClass_UnmatchedBrackets = true;
CurrentAccessSpecifier = ACCESS_Private;
if (!TryParseIInterfaceClass(AllClasses))
{
bEncounteredNewStyleClass_UnmatchedBrackets = false;
UngetToken(Token);
return SkipDeclaration(Token);
}
return true;
}
if (Token.Matches(TEXT("GENERATED_IINTERFACE_BODY")) || (Token.Matches(TEXT("GENERATED_BODY")) && TopNest->NestType == ENestType::NativeInterface))
{
if (TopNest->NestType != ENestType::NativeInterface)
{
FError::Throwf(TEXT("%s must occur inside the native interface definition"), Token.Identifier);
}
RequireSymbol(TEXT("("), Token.Identifier);
CompileVersionDeclaration(GetCurrentClass());
RequireSymbol(TEXT(")"), Token.Identifier);
FClassMetaData* ClassData = GetCurrentClassData();
ClassData->GeneratedBodyMacroAccessSpecifier = CurrentAccessSpecifier;
ClassData->SetInterfaceGeneratedBodyLine(InputLine);
bClassHasGeneratedIInterfaceBody = true;
if (Token.Matches(TEXT("GENERATED_IINTERFACE_BODY")))
{
CurrentAccessSpecifier = ACCESS_Public;
}
if (Token.Matches(TEXT("GENERATED_BODY")))
{
ClassDefinitionRanges[GetCurrentClass()].bHasGeneratedBody = true;
}
return true;
}
if (Token.Matches(TEXT("GENERATED_UINTERFACE_BODY")) || (Token.Matches(TEXT("GENERATED_BODY")) && TopNest->NestType == ENestType::Interface))
{
if (TopNest->NestType != ENestType::Interface)
{
FError::Throwf(TEXT("%s must occur inside the interface definition"), Token.Identifier);
}
RequireSymbol(TEXT("("), Token.Identifier);
CompileVersionDeclaration(GetCurrentClass());
RequireSymbol(TEXT(")"), Token.Identifier);
FClassMetaData* ClassData = GetCurrentClassData();
ClassData->GeneratedBodyMacroAccessSpecifier = CurrentAccessSpecifier;
ClassData->SetGeneratedBodyLine(InputLine);
bClassHasGeneratedUInterfaceBody = true;
if (Token.Matches(TEXT("GENERATED_UINTERFACE_BODY")))
{
CurrentAccessSpecifier = ACCESS_Public;
}
return true;
}
if (Token.Matches(TEXT("GENERATED_UCLASS_BODY")) || (Token.Matches(TEXT("GENERATED_BODY")) && TopNest->NestType == ENestType::Class))
{
if (TopNest->NestType != ENestType::Class)
{
FError::Throwf(TEXT("%s must occur inside the class definition"), Token.Identifier);
}
FClassMetaData* ClassData = GetCurrentClassData();
if (Token.Matches(TEXT("GENERATED_BODY")))
{
if (!ClassDefinitionRanges.Contains(GetCurrentClass()))
{
ClassDefinitionRanges.Add(GetCurrentClass(), ClassDefinitionRange());
}
ClassDefinitionRanges[GetCurrentClass()].bHasGeneratedBody = true;
ClassData->GeneratedBodyMacroAccessSpecifier = CurrentAccessSpecifier;
}
else
{
CurrentAccessSpecifier = ACCESS_Public;
}
RequireSymbol(TEXT("("), Token.Identifier);
CompileVersionDeclaration(GetCurrentClass());
RequireSymbol(TEXT(")"), Token.Identifier);
ClassData->SetGeneratedBodyLine(InputLine);
bClassHasGeneratedBody = true;
return true;
}
if (Token.Matches(TEXT("UCLASS"), ESearchCase::CaseSensitive))
{
bHaveSeenUClass = true;
bEncounteredNewStyleClass_UnmatchedBrackets = true;
CompileClassDeclaration(AllClasses);
return true;
}
if (Token.Matches(TEXT("UINTERFACE")))
{
bHaveSeenUClass = true;
bEncounteredNewStyleClass_UnmatchedBrackets = true;
CompileInterfaceDeclaration(AllClasses);
return true;
}
if (Token.Matches(TEXT("UFUNCTION"), ESearchCase::CaseSensitive))
{
CompileFunctionDeclaration(AllClasses);
return true;
}
if (Token.Matches(TEXT("UDELEGATE")))
{
UDelegateFunction* Delegate = CompileDelegateDeclaration(AllClasses, Token.Identifier, EDelegateSpecifierAction::Parse);
DelegatesToFixup.Add(Delegate);
return true;
}
if (IsValidDelegateDeclaration(Token)) // Legacy delegate parsing - it didn't need a UDELEGATE
{
UDelegateFunction* Delegate = CompileDelegateDeclaration(AllClasses, Token.Identifier);
DelegatesToFixup.Add(Delegate);
return true;
}
if (Token.Matches(TEXT("UPROPERTY"), ESearchCase::CaseSensitive))
{
CheckAllow(TEXT("'Member variable declaration'"), ENestAllowFlags::VarDecl);
check(TopNest->NestType == ENestType::Class);
CompileVariableDeclaration(AllClasses, GetCurrentClass());
return true;
}
if (Token.Matches(TEXT("UENUM")))
{
// Enumeration definition.
CompileEnum();
return true;
}
if (Token.Matches(TEXT("USTRUCT")))
{
// Struct definition.
CompileStructDeclaration(AllClasses);
return true;
}
if (Token.Matches(TEXT("#")))
{
// Compiler directive.
CompileDirective(AllClasses);
return true;
}
if (bEncounteredNewStyleClass_UnmatchedBrackets && Token.Matches(TEXT("}")))
{
if (ClassDefinitionRanges.Contains(GetCurrentClass()))
{
ClassDefinitionRanges[GetCurrentClass()].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;
UClass* CurrentClass = GetCurrentClass();
// Pop nesting here to allow other non UClass declarations in the header file.
if (CurrentClass->ClassFlags & CLASS_Interface)
{
checkf(TopNest->NestType == ENestType::Interface || TopNest->NestType == ENestType::NativeInterface, TEXT("Unexpected end of interface block."));
PopNest(TopNest->NestType, TEXT("'Interface'"));
PostPopNestInterface(AllClasses, CurrentClass);
// Ensure the UINTERFACE classes have a GENERATED_BODY declaration
if (bHaveSeenUClass && !bClassHasGeneratedUInterfaceBody)
{
FError::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)
{
FError::Throwf(TEXT("Expected a GENERATED_BODY() at the start of class"));
}
}
else
{
PopNest(ENestType::Class, TEXT("'Class'"));
PostPopNestClass(CurrentClass);
// Ensure classes have a GENERATED_BODY declaration
if (bHaveSeenUClass && !bClassHasGeneratedBody)
{
FError::Throwf(TEXT("Expected a GENERATED_BODY() at the start of class"));
}
}
bHaveSeenUClass = false;
bClassHasGeneratedBody = false;
bClassHasGeneratedUInterfaceBody = false;
bClassHasGeneratedIInterfaceBody = false;
GetCurrentScope()->AddType(CurrentClass);
return true;
}
if (Token.Matches(TEXT(";")))
{
if (GetToken(Token))
{
FError::Throwf(TEXT("Extra ';' before '%s'"), Token.Identifier);
}
else
{
FError::Throwf(TEXT("Extra ';' before end of file"));
}
}
if (bEncounteredNewStyleClass_UnmatchedBrackets && IsInAClass())
{
if (UClass* Class = GetCurrentClass())
{
FToken ConstructorToken = Token;
// Allow explicit constructors
bool bFoundExplicit = ConstructorToken.Matches(TEXT("explicit"));
if (bFoundExplicit)
{
GetToken(ConstructorToken);
}
if (FString(ConstructorToken.Identifier).EndsWith("_API"))
{
if (!bFoundExplicit)
{
// Explicit can come before or after an _API
MatchIdentifier(TEXT("explicit"));
}
GetToken(ConstructorToken);
}
if (ConstructorToken.Matches(NameLookupCPP.GetNameCPP(Class)) && TryToMatchConstructorParameterList(ConstructorToken))
{
return true;
}
}
}
// Skip anything that looks like a macro followed by no bracket that we don't know about
if (ProbablyAnUnknownObjectLikeMacro(*this, Token))
{
return true;
}
// Ignore C++ declaration / function definition.
return SkipDeclaration(Token);
}
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.Matches(TEXT("class")) || Token.Matches(TEXT("struct"));
// (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.Identifier) && !Token.Matches("DECLARE_FUNCTION");
bool bEndOfDeclarationFound = false;
bool bDefinitionFound = false;
const TCHAR* OpeningBracket = bMacroDeclaration ? TEXT("(") : TEXT("{");
const 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.Matches(TEXT("(")))
{
bPossiblyClassDeclaration = false;
}
bRetestCurrentToken = false;
if (Token.Matches(TEXT(";")) && NestedScopes == 0)
{
bEndOfDeclarationFound = true;
break;
}
if (Token.Matches(OpeningBracket))
{
// This is a function definition or class declaration.
bDefinitionFound = true;
NestedScopes++;
}
else if (Token.Matches(ClosingBracket))
{
NestedScopes--;
if (NestedScopes == 0)
{
bEndOfDeclarationFound = true;
break;
}
if (NestedScopes < 0)
{
FError::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.TokenType != TOKEN_Identifier)
{
// Not a variable name.
UngetToken(VariableName);
}
else if (!SafeMatchSymbol(TEXT(";")))
{
FError::Throwf(*FString::Printf(TEXT("Unexpected '%s'. Did you miss a semi-colon?"), VariableName.Identifier));
}
}
// 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 )
{
FToken Token;
// Remember the position before the next token (this can include comments before the next symbol).
FScriptLocation LocationBeforeNextSymbol;
InitScriptLocation(LocationBeforeNextSymbol);
if (GetToken(Token, /*bNoConsts=*/ true))
{
if (Token.TokenType==TOKEN_Symbol && !FCString::Stricmp(Token.Identifier, Match))
{
return true;
}
UngetToken(Token);
}
// Return to the stored position.
ReturnToLocation(LocationBeforeNextSymbol);
return false;
}
FClass* FHeaderParser::ParseClassNameDeclaration(FClasses& AllClasses, FString& DeclaredClassName, FString& RequiredAPIMacroIfPresent)
{
FUnrealSourceFile* CurrentSrcFile = GetCurrentSourceFile();
ParseNameWithPotentialAPIMacroPrefix(/*out*/ DeclaredClassName, /*out*/ RequiredAPIMacroIfPresent, TEXT("class"));
FClass* FoundClass = AllClasses.FindClass(*GetClassNameWithPrefixRemoved(*DeclaredClassName));
check(FoundClass);
FClassMetaData* ClassMetaData = GScriptHelper.AddClassData(FoundClass, UHTMakefile, CurrentSrcFile);
UHTMakefile.AddGScriptHelperEntry(CurrentSrcFile, FoundClass, ClassMetaData);
// Get parent class.
bool bSpecifiesParentClass = false;
// Skip optional final keyword
MatchIdentifier(TEXT("final"));
if (MatchSymbol(TEXT(":")))
{
RequireIdentifier(TEXT("public"), TEXT("class inheritance"));
bSpecifiesParentClass = true;
}
// Add class cast flag
FoundClass->ClassCastFlags |= ClassCastFlagMap::Get().GetCastFlag(DeclaredClassName);
if (bSpecifiesParentClass)
{
// Set the base class.
UClass* TempClass = GetQualifiedClass(AllClasses, TEXT("'extends'"));
check(TempClass);
// a class cannot 'extends' an interface, use 'implements'
if (TempClass->ClassFlags & CLASS_Interface)
{
FError::Throwf(TEXT("Class '%s' cannot extend interface '%s', use 'implements'"), *FoundClass->GetName(), *TempClass->GetName());
}
UClass* SuperClass = FoundClass->GetSuperClass();
if( SuperClass == NULL )
{
FoundClass->SetSuperStruct(TempClass);
}
else if( SuperClass != TempClass )
{
FError::Throwf(TEXT("%s's superclass must be %s, not %s"), *FoundClass->GetPathName(), *SuperClass->GetPathName(), *TempClass->GetPathName());
}
FoundClass->ClassCastFlags |= FoundClass->GetSuperClass()->ClassCastFlags;
// Handle additional inherited interface classes
while (MatchSymbol(TEXT(",")))
{
RequireIdentifier(TEXT("public"), TEXT("Interface inheritance must be public"));
FToken Token;
if (!GetIdentifier(Token, true))
FError::Throwf(TEXT("Failed to get interface class identifier"));
FString InterfaceName = Token.Identifier;
// Handle templated native classes
if (MatchSymbol(TEXT("<")))
{
InterfaceName += TEXT('<');
int32 NestedScopes = 1;
while (NestedScopes)
{
if (!GetToken(Token))
FError::Throwf(TEXT("Unexpected end of file"));
if (Token.TokenType == TOKEN_Symbol)
{
if (!FCString::Strcmp(Token.Identifier, TEXT("<")))
{
++NestedScopes;
}
else if (!FCString::Strcmp(Token.Identifier, TEXT(">")))
{
--NestedScopes;
}
}
InterfaceName += Token.Identifier;
}
}
HandleOneInheritedClass(AllClasses, FoundClass, *InterfaceName);
}
}
else if (FoundClass->GetSuperClass())
{
FError::Throwf(TEXT("class: missing 'Extends %s'"), *FoundClass->GetSuperClass()->GetName());
}
return FoundClass;
}
void FHeaderParser::HandleOneInheritedClass(FClasses& AllClasses, UClass* Class, FString InterfaceName)
{
FUnrealSourceFile* CurrentSrcFile = GetCurrentSourceFile();
// Check for UInterface derived interface inheritance
if (UClass* Interface = AllClasses.FindScriptClass(InterfaceName))
{
// Try to find the interface
if ( !Interface->HasAnyClassFlags(CLASS_Interface) )
{
FError::Throwf(TEXT("Implements: Class %s is not an interface; Can only inherit from non-UObjects or UInterface derived interfaces"), *Interface->GetName() );
}
// Propagate the inheritable ClassFlags
Class->ClassFlags |= (Interface->ClassFlags) & CLASS_ScriptInherit;
new (Class->Interfaces) FImplementedInterface(Interface, 0, false);
if (Interface->HasAnyClassFlags(CLASS_Native))
{
FClassMetaData* ClassData = GScriptHelper.FindClassData(Class);
check(ClassData);
ClassData->AddInheritanceParent(Interface, UHTMakefile, CurrentSrcFile);
}
}
else
{
// Non-UObject inheritance
FClassMetaData* ClassData = GScriptHelper.FindClassData(Class);
check(ClassData);
ClassData->AddInheritanceParent(InterfaceName, UHTMakefile, CurrentSrcFile);
}
}
/**
* Setups basic class settings after parsing.
*/
void PostParsingClassSetup(UClass* Class)
{
// Cleanup after first pass.
FHeaderParser::ComputeFunctionParametersSize(Class);
// Set all optimization ClassFlags based on property types
for (TFieldIterator<UProperty> It(Class, EFieldIteratorFlags::ExcludeSuper); It; ++It)
{
if ((It->PropertyFlags & CPF_Config) != 0)
{
Class->ClassFlags |= CLASS_Config;
}
if (It->ContainsInstancedObjectProperty())
{
Class->ClassFlags |= CLASS_HasInstancedReference;
}
}
// Class needs to specify which ini file is going to be used if it contains config variables.
if ((Class->ClassFlags & CLASS_Config) && (Class->ClassConfigName == NAME_None))
{
// Inherit config setting from base class.
Class->ClassConfigName = Class->GetSuperClass() ? Class->GetSuperClass()->ClassConfigName : NAME_None;
if (Class->ClassConfigName == NAME_None)
{
FError::Throwf(TEXT("Classes with config / globalconfig member variables need to specify config file."));
Class->ClassConfigName = NAME_Engine;
}
}
}
/**
* Compiles a class declaration.
*/
void FHeaderParser::CompileClassDeclaration(FClasses& AllClasses)
{
// 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"), TEXT("Class declaration"));
SkipDeprecatedMacroIfNecessary();
FString DeclaredClassName;
FString RequiredAPIMacroIfPresent;
FClass* Class = ParseClassNameDeclaration(AllClasses, /*out*/ DeclaredClassName, /*out*/ RequiredAPIMacroIfPresent);
check(Class);
TSharedRef<FClassDeclarationMetaData> ClassDeclarationData = GClassDeclarations.FindChecked(Class->GetFName());
ClassDefinitionRanges.Add(Class, ClassDefinitionRange(&Input[InputPos], nullptr));
check(Class->ClassFlags == 0 || (Class->ClassFlags & ClassDeclarationData->ClassFlags) != 0);
Class->ClassFlags |= CLASS_Parsed;
PushNest(ENestType::Class, Class);
const uint32 PrevClassFlags = Class->ClassFlags;
ResetClassData();
// Verify class variables haven't been filled in
check(Class->Children == NULL);
check(Class->Next == NULL);
check(Class->NetFields.Num() == 0);
// Make sure our parent classes is parsed.
for (UClass* Temp = Class->GetSuperClass(); Temp; Temp = Temp->GetSuperClass())
{
bool bIsParsed = !!(Temp->ClassFlags & CLASS_Parsed);
bool bIsIntrinsic = !!(Temp->ClassFlags & CLASS_Intrinsic);
if (!(bIsParsed || bIsIntrinsic))
{
FError::Throwf(TEXT("'%s' can't be compiled: Parent class '%s' has errors"), *Class->GetName(), *Temp->GetName());
}
}
// Merge with categories inherited from the parent.
ClassDeclarationData->MergeClassCategories(Class);
// Class attributes.
FClassMetaData* ClassData = GScriptHelper.FindClassData(Class);
check(ClassData);
ClassData->SetPrologLine(PrologFinishLine);
ClassDeclarationData->MergeAndValidateClassFlags(DeclaredClassName, PrevClassFlags, Class, AllClasses);
Class->SetInternalFlags(EInternalObjectFlags::Native);
// Class metadata
MetaData.Append(ClassDeclarationData->MetaData);
if (ClassDeclarationData->ClassGroupNames.Num()) { MetaData.Add("ClassGroupNames", FString::Join(ClassDeclarationData->ClassGroupNames, TEXT(" "))); }
if (ClassDeclarationData->AutoCollapseCategories.Num()) { MetaData.Add("AutoCollapseCategories", FString::Join(ClassDeclarationData->AutoCollapseCategories, TEXT(" "))); }
if (ClassDeclarationData->HideCategories.Num()) { MetaData.Add("HideCategories", FString::Join(ClassDeclarationData->HideCategories, TEXT(" "))); }
if (ClassDeclarationData->ShowSubCatgories.Num()) { MetaData.Add("ShowCategories", FString::Join(ClassDeclarationData->ShowSubCatgories, TEXT(" "))); }
if (ClassDeclarationData->HideFunctions.Num()) { MetaData.Add("HideFunctions", FString::Join(ClassDeclarationData->HideFunctions, TEXT(" "))); }
if (ClassDeclarationData->AutoExpandCategories.Num()) { MetaData.Add("AutoExpandCategories", FString::Join(ClassDeclarationData->AutoExpandCategories, TEXT(" "))); }
AddIncludePathToMetadata(Class, MetaData);
AddModuleRelativePathToMetadata(Class, MetaData);
// Register the metadata
AddMetaDataToClassData(Class, MetaData);
// Handle the start of the rest of the class
RequireSymbol( TEXT("{"), TEXT("'Class'") );
// Make visible outside the package.
Class->ClearFlags(RF_Transient);
check(Class->HasAnyFlags(RF_Public));
check(Class->HasAnyFlags(RF_Standalone));
// Copy properties from parent class.
if (Class->GetSuperClass())
{
Class->SetPropertiesSize(Class->GetSuperClass()->GetPropertiesSize());
}
// auto-create properties for all of the VFTables needed for the multiple inheritances
// get the inheritance parents
const TArray<FMultipleInheritanceBaseClass*>& InheritanceParents = ClassData->GetInheritanceParents();
// for all base class types, make a VfTable property
for (int32 ParentIndex = InheritanceParents.Num() - 1; ParentIndex >= 0; ParentIndex--)
{
// if this base class corresponds to an interface class, assign the vtable UProperty in the class's Interfaces map now...
if (UClass* InheritedInterface = InheritanceParents[ParentIndex]->InterfaceClass)
{
FImplementedInterface* Found = Class->Interfaces.FindByPredicate([=](const FImplementedInterface& Impl) { return Impl.Class == InheritedInterface; });
if (Found)
{
Found->PointerOffset = 1;
}
else
{
Class->Interfaces.Add(FImplementedInterface(InheritedInterface, 1, false));
}
}
}
}
FClass* FHeaderParser::ParseInterfaceNameDeclaration(FClasses& AllClasses, FString& DeclaredInterfaceName, FString& RequiredAPIMacroIfPresent)
{
ParseNameWithPotentialAPIMacroPrefix(/*out*/ DeclaredInterfaceName, /*out*/ RequiredAPIMacroIfPresent, TEXT("interface"));
FClass* FoundClass = AllClasses.FindClass(*GetClassNameWithPrefixRemoved(*DeclaredInterfaceName));
if (FoundClass == nullptr)
{
return nullptr;
}
// Get super interface
bool bSpecifiesParentClass = MatchSymbol(TEXT(":"));
if (!bSpecifiesParentClass)
{
return FoundClass;
}
RequireIdentifier(TEXT("public"), 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
UClass* TempClass = GetQualifiedClass(AllClasses, TEXT("'extends'"));
check(TempClass);
if( !(TempClass->ClassFlags & CLASS_Interface) )
{
// UInterface is special and actually extends from UObject, which isn't an interface
if (DeclaredInterfaceName != TEXT("UInterface"))
FError::Throwf(TEXT("Interface class '%s' cannot inherit from non-interface class '%s'"), *DeclaredInterfaceName, *TempClass->GetName() );
}
UClass* SuperClass = FoundClass->GetSuperClass();
if (SuperClass == NULL)
{
FoundClass->SetSuperStruct(TempClass);
}
else if (SuperClass != TempClass)
{
FError::Throwf(TEXT("%s's superclass must be %s, not %s"), *FoundClass->GetPathName(), *SuperClass->GetPathName(), *TempClass->GetPathName());
}
return FoundClass;
}
bool FHeaderParser::TryParseIInterfaceClass(FClasses& AllClasses)
{
FString ErrorMsg(TEXT("C++ interface mix-in class declaration"));
// 'class' was already matched by the caller
// Get a class name
FString DeclaredInterfaceName;
FString RequiredAPIMacroIfPresent;
if (ParseInterfaceNameDeclaration(AllClasses, /*out*/ DeclaredInterfaceName, /*out*/ RequiredAPIMacroIfPresent) == nullptr)
{
return false;
}
if (MatchSymbol(TEXT(";")))
{
// Forward declaration.
return false;
}
if (DeclaredInterfaceName[0] != 'I')
{
return false;
}
UClass* FoundClass = nullptr;
if ((FoundClass = AllClasses.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("{"), *ErrorMsg);
// Push the interface class nesting again.
PushNest(ENestType::NativeInterface, FoundClass);
return true;
}
/**
* compiles Java or C# style interface declaration
*/
void FHeaderParser::CompileInterfaceDeclaration(FClasses& AllClasses)
{
FUnrealSourceFile* CurrentSrcFile = GetCurrentSourceFile();
// 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 );
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"), TEXT("Interface declaration"));
FClass* InterfaceClass = ParseInterfaceNameDeclaration(AllClasses, /*out*/ DeclaredInterfaceName, /*out*/ RequiredAPIMacroIfPresent);
ClassDefinitionRanges.Add(InterfaceClass, ClassDefinitionRange(&Input[InputPos], nullptr));
// Record that this interface is RequiredAPI if the CORE_API style macro was present
if (!RequiredAPIMacroIfPresent.IsEmpty())
{
InterfaceClass->ClassFlags |= CLASS_RequiredAPI;
}
// Set the appropriate interface class flags
InterfaceClass->ClassFlags |= CLASS_Interface | CLASS_Abstract;
if (InterfaceClass->GetSuperClass() != NULL)
{
InterfaceClass->ClassCastFlags |= InterfaceClass->GetSuperClass()->ClassCastFlags;
}
// All classes that are parsed are expected to be native
if (InterfaceClass->GetSuperClass() && !InterfaceClass->GetSuperClass()->HasAnyClassFlags(CLASS_Native))
{
FError::Throwf(TEXT("Native classes cannot extend non-native classes") );
}
InterfaceClass->SetInternalFlags(EInternalObjectFlags::Native);
InterfaceClass->ClassFlags |= CLASS_Native;
// Process all of the interface specifiers
for (const FPropertySpecifier& Specifier : SpecifiersFound)
{
switch ((EInterfaceSpecifier)Algo::FindSortedStringCaseInsensitive(*Specifier.Key, GInterfaceSpecifierStrings))
{
default:
{
FError::Throwf(TEXT("Unknown interface specifier '%s'"), *Specifier.Key);
}
break;
case EInterfaceSpecifier::DependsOn:
{
FError::Throwf(TEXT("The dependsOn specifier is deprecated. Please use #include \"ClassHeaderFilename.h\" instead."));
}
break;
case EInterfaceSpecifier::MinimalAPI:
{
InterfaceClass->ClassFlags |= CLASS_MinimalAPI;
}
break;
case EInterfaceSpecifier::ConversionRoot:
{
MetaData.Add(FName(TEXT("IsConversionRoot")), "true");
}
break;
}
}
// All classes must start with a valid Unreal prefix
const FString ExpectedInterfaceName = InterfaceClass->GetNameWithPrefix(EEnforceInterfacePrefix::U);
if (DeclaredInterfaceName != ExpectedInterfaceName)
{
FError::Throwf(TEXT("Interface name '%s' is invalid, the first class should be identified as '%s'"), *DeclaredInterfaceName, *ExpectedInterfaceName );
}
// Try parsing metadata for the interface
FClassMetaData* ClassData = GScriptHelper.AddClassData(InterfaceClass, UHTMakefile, CurrentSrcFile);
UHTMakefile.AddGScriptHelperEntry(CurrentSrcFile, InterfaceClass, ClassData);
check(ClassData);
ClassData->SetPrologLine(PrologFinishLine);
// Register the metadata
AddModuleRelativePathToMetadata(InterfaceClass, MetaData);
AddMetaDataToClassData(InterfaceClass, MetaData);
// Handle the start of the rest of the interface
RequireSymbol( TEXT("{"), TEXT("'Class'") );
// Make visible outside the package.
InterfaceClass->ClearFlags(RF_Transient);
check(InterfaceClass->HasAnyFlags(RF_Public));
check(InterfaceClass->HasAnyFlags(RF_Standalone));
// 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, InterfaceClass);
}
// Returns true if the token is a dynamic delegate declaration
bool FHeaderParser::IsValidDelegateDeclaration(const FToken& Token) const
{
FString TokenStr(Token.Identifier);
return (Token.TokenType == TOKEN_Identifier) && TokenStr.StartsWith(TEXT("DECLARE_DYNAMIC_"));
}
// Parse the parameter list of a function or delegate declaration
void FHeaderParser::ParseParameterList(FClasses& AllClasses, UFunction* Function, bool bExpectCommaBeforeName, TMap<FName, FString>* MetaData)
{
// Get parameter list.
if ( MatchSymbol(TEXT(")")) )
return;
FAdvancedDisplayParameterHandler AdvancedDisplay(MetaData);
do
{
// Get parameter type.
FToken Property(CPT_None);
EVariableCategory::Type VariableCategory = (Function->FunctionFlags & FUNC_Net) ? EVariableCategory::ReplicatedParameter : EVariableCategory::RegularParameter;
GetVarType(AllClasses, GetCurrentScope(), Property, ~(CPF_ParmFlags | CPF_AutoWeak | CPF_RepSkip | CPF_UObjectWrapper | CPF_NativeAccessSpecifiers), NULL, EPropertyDeclarationStyle::None, VariableCategory);
Property.PropertyFlags |= CPF_Parm;
if (bExpectCommaBeforeName)
{
RequireSymbol(TEXT(","), TEXT("Delegate definitions require a , between the parameter type and parameter name"));
}
UProperty* Prop = GetVarNameAndDim(Function, Property, VariableCategory);
Function->NumParms++;
if( AdvancedDisplay.CanMarkMore() && AdvancedDisplay.ShouldMarkParameter(Prop->GetName()) )
{
Prop->PropertyFlags |= CPF_AdvancedDisplay;
}
// Check parameters.
if ((Function->FunctionFlags & FUNC_Net))
{
if (!(Function->FunctionFlags & FUNC_NetRequest))
{
if (Property.PropertyFlags & CPF_OutParm)
FError::Throwf(TEXT("Replicated functions cannot contain out parameters"));
if (Property.PropertyFlags & CPF_RepSkip)
FError::Throwf(TEXT("Only service request functions cannot contain NoReplication parameters"));
if ((Prop->GetClass()->ClassCastFlags & CASTCLASS_UDelegateProperty) != 0)
FError::Throwf(TEXT("Replicated functions cannot contain delegate parameters (this would be insecure)"));
if (Property.Type == CPT_String && Property.RefQualifier != ERefQualifier::ConstRef && Prop->ArrayDim == 1)
FError::Throwf(TEXT("Replicated FString parameters must be passed by const reference"));
if (Property.ArrayType == EArrayType::Dynamic && Property.RefQualifier != ERefQualifier::ConstRef && Prop->ArrayDim == 1)
FError::Throwf(TEXT("Replicated TArray parameters must be passed by const reference"));
}
else
{
if (!(Property.PropertyFlags & CPF_RepSkip) && (Property.PropertyFlags & CPF_OutParm))
FError::Throwf(TEXT("Service request functions cannot contain out parameters, unless marked NotReplicated"));
if (!(Property.PropertyFlags & CPF_RepSkip) && (Prop->GetClass()->ClassCastFlags & CASTCLASS_UDelegateProperty) != 0)
FError::Throwf(TEXT("Service request functions cannot contain delegate parameters, unless marked NotReplicated"));
}
}
if ((Function->FunctionFlags & (FUNC_BlueprintEvent|FUNC_BlueprintCallable)) != 0)
{
if (Property.Type == CPT_Byte)
{
if (UEnumProperty* EnumProperty = Cast<UEnumProperty>(Prop))
{
UProperty* InnerType = EnumProperty->GetUnderlyingProperty();
if (InnerType && !InnerType->IsA<UByteProperty>())
{
FError::Throwf(TEXT("Invalid enum param for Blueprints - currently only uint8 supported"));
}
}
}
}
// 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.Matches(TEXT(")")) || SkipToken.Matches(TEXT(","))) )
{
EndPos = SkipToken.StartPos;
// went too far
UngetToken(SkipToken);
break;
}
if ( SkipToken.Matches(TEXT("(")) )
{
ParenthesisNestCount++;
}
else if ( SkipToken.Matches(TEXT(")")) )
{
ParenthesisNestCount--;
}
}
// allow exec functions to be added to the metaData, this is so we can have default params for them.
const bool bStoreCppDefaultValueInMetaData = Function->HasAnyFunctionFlags(FUNC_BlueprintCallable | FUNC_Exec);
if((EndPos > -1) && bStoreCppDefaultValueInMetaData)
{
FString DefaultArgText(EndPos - StartPos, Input + StartPos);
FString Key(TEXT("CPP_Default_"));
Key += Prop->GetName();
FName KeyName = FName(*Key);
if (!MetaData->Contains(KeyName))
{
FString InnerDefaultValue;
const bool bDefaultValueParsed = DefaultValueStringCppFormatToInnerFormat(Prop, DefaultArgText, InnerDefaultValue);
if (!bDefaultValueParsed)
{
FError::Throwf(TEXT("C++ Default parameter not parsed: %s \"%s\" "), *Prop->GetName(), *DefaultArgText);
}
if (InnerDefaultValue.IsEmpty())
{
static int32 SkippedCounter = 0;
UE_LOG(LogCompile, Verbose, TEXT("C++ Default parameter skipped/empty [%i]: %s \"%s\" "), SkippedCounter, *Prop->GetName(), *DefaultArgText );
++SkippedCounter;
}
else
{
MetaData->Add(KeyName, InnerDefaultValue);
UE_LOG(LogCompile, Verbose, TEXT("C++ Default parameter parsed: %s \"%s\" -> \"%s\" "), *Prop->GetName(), *DefaultArgText, *InnerDefaultValue );
}
}
}
}
} while( MatchSymbol(TEXT(",")) );
RequireSymbol( TEXT(")"), TEXT("parameter list") );
}
UDelegateFunction* FHeaderParser::CompileDelegateDeclaration(FClasses& AllClasses, const TCHAR* DelegateIdentifier, EDelegateSpecifierAction::Type SpecifierAction)
{
const TCHAR* CurrentScopeName = TEXT("Delegate Declaration");
FUnrealSourceFile* CurrentSrcFile = GetCurrentSourceFile();
TMap<FName, FString> MetaData;
AddModuleRelativePathToMetadata(*CurrentSrcFile, 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(FuncInfo, SpecifiersFound);
// Get the next token and ensure it looks like a delegate
FToken Token;
GetToken(Token);
if (!IsValidDelegateDeclaration(Token))
FError::Throwf(TEXT("Unexpected token following UDELEGATE(): %s"), Token.Identifier);
DelegateMacro = Token.Identifier;
//Workaround for UE-28897
const FStructScope* CurrentStructScope = TopNest->GetScope() ? TopNest->GetScope()->AsStructScope() : nullptr;
const bool bDynamicClassScope = CurrentStructScope && CurrentStructScope->GetStruct() && FClass::IsDynamic(CurrentStructScope->GetStruct());
CheckAllow(CurrentScopeName, bDynamicClassScope ? ENestAllowFlags::ImplicitDelegateDecl : ENestAllowFlags::TypeDecl);
}
else
{
DelegateMacro = DelegateIdentifier;
CheckAllow(CurrentScopeName, ENestAllowFlags::ImplicitDelegateDecl);
}
// Break the delegate declaration macro down into parts
const bool bHasReturnValue = DelegateMacro.Contains(TEXT("_RetVal"));
const bool bDeclaredConst = DelegateMacro.Contains(TEXT("_Const"));
const bool bIsMulticast = DelegateMacro.Contains(TEXT("_MULTICAST"));
// Determine the parameter count
const FString* FoundParamCount = 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_DELEGATE%s%s%s"),
bIsMulticast ? TEXT("_MULTICAST") : TEXT(""),
bHasReturnValue ? TEXT("_RetVal") : TEXT(""),
FoundParamCount ? **FoundParamCount : TEXT(""),
bDeclaredConst ? TEXT("_Const") : TEXT(""));
if (DelegateMacro != ExpectedOriginalString)
{
FError::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)
{
FError::Throwf(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;
}
// Now parse the macro body
RequireSymbol(TEXT("("), CurrentScopeName);
// Parse the return value type
FToken ReturnType( CPT_None );
if (bHasReturnValue)
{
GetVarType(AllClasses, GetCurrentScope(), ReturnType, 0, NULL, EPropertyDeclarationStyle::None, EVariableCategory::Return);
RequireSymbol(TEXT(","), CurrentScopeName);
}
// Skip whitespaces to get InputPos exactly on beginning of function name.
while (FChar::IsWhitespace(PeekChar())) { GetChar(); }
FuncInfo.InputPos = InputPos;
// Get the delegate name
if (!GetIdentifier(FuncInfo.Function))
{
FError::Throwf(TEXT("Missing name for %s"), CurrentScopeName );
}
// 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
FString Name(FuncInfo.Function.Identifier);
if (!Name.StartsWith(TEXT("F")))
{
FError::Throwf(TEXT("Delegate type declarations must start with F"));
}
Name = Name.Mid(1);
// Append the signature goo
Name += HEADER_GENERATED_DELEGATE_SIGNATURE_SUFFIX;
// Replace the name
FCString::Strcpy( FuncInfo.Function.Identifier, *Name );
}
FuncInfo.MacroLine = InputLine;
UDelegateFunction* DelegateSignatureFunction = CreateDelegateFunction(FuncInfo);
UHTMakefile.AddDelegateFunction(CurrentSrcFile, DelegateSignatureFunction);
FClassMetaData* ClassMetaData = GScriptHelper.AddClassData(DelegateSignatureFunction, UHTMakefile, CurrentSrcFile);
UHTMakefile.AddGScriptHelperEntry(CurrentSrcFile, DelegateSignatureFunction, ClassMetaData);
DelegateSignatureFunction->FunctionFlags |= FuncInfo.FunctionFlags;
FuncInfo.FunctionReference = DelegateSignatureFunction;
FuncInfo.SetFunctionNames();
FFunctionData::Add(FuncInfo);
if (FuncInfo.FunctionReference->HasAnyFunctionFlags(FUNC_Delegate) && !GetCurrentScope()->IsFileScope())
{
GetCurrentClassData()->MarkContainsDelegate();
}
GetCurrentScope()->AddType(DelegateSignatureFunction);
// determine whether this function should be 'const'
if (bDeclaredConst)
{
DelegateSignatureFunction->FunctionFlags |= FUNC_Const;
}
// Get parameter list.
if (FoundParamCount)
{
RequireSymbol(TEXT(","), CurrentScopeName);
ParseParameterList(AllClasses, DelegateSignatureFunction, /*bExpectCommaBeforeName=*/ true);
// Check the expected versus actual number of parameters
int32 ParamCount = FoundParamCount - DelegateParameterCountStrings.GetData() + 1;
if (DelegateSignatureFunction->NumParms != ParamCount)
{
FError::Throwf(TEXT("Expected %d parameters but found %d parameters"), ParamCount, DelegateSignatureFunction->NumParms);
}
}
else
{
// Require the closing paren even with no parameter list
RequireSymbol(TEXT(")"), TEXT("Delegate Declaration"));
}
// Create the return value property
if (bHasReturnValue)
{
ReturnType.PropertyFlags |= CPF_Parm | CPF_OutParm | CPF_ReturnParm;
UProperty* ReturnProp = GetVarNameAndDim(DelegateSignatureFunction, ReturnType, EVariableCategory::Return);
DelegateSignatureFunction->NumParms++;
}
// Try parsing metadata for the function
ParseFieldMetaData(MetaData, *(DelegateSignatureFunction->GetName()));
AddFormattedPrevCommentAsTooltipMetaData(MetaData);
AddMetaDataToClassData(DelegateSignatureFunction, MetaData);
// Optionally consume a semicolon, it's not required for the delegate macro since it contains one internally
MatchSemi();
// Bind the function.
DelegateSignatureFunction->Bind();
// End the nesting
PostPopFunctionDeclaration(AllClasses, DelegateSignatureFunction);
// Don't allow delegate signatures to be redefined.
auto FunctionIterator = GetCurrentScope()->GetTypeIterator<UFunction>();
while (FunctionIterator.MoveNext())
{
UFunction* TestFunc = *FunctionIterator;
if ((TestFunc->GetFName() == DelegateSignatureFunction->GetFName()) && (TestFunc != DelegateSignatureFunction))
{
FError::Throwf(TEXT("Can't override delegate signature function '%s'"), FuncInfo.Function.Identifier);
}
}
return DelegateSignatureFunction;
}
/**
* Parses and compiles a function declaration
*/
void FHeaderParser::CompileFunctionDeclaration(FClasses& AllClasses)
{
CheckAllow(TEXT("'Function'"), ENestAllowFlags::Function);
FUnrealSourceFile* CurrentSrcFile = GetCurrentSourceFile();
TMap<FName, FString> MetaData;
AddModuleRelativePathToMetadata(*CurrentSrcFile, MetaData);
// New-style UFUNCTION() syntax
TArray<FPropertySpecifier> SpecifiersFound;
ReadSpecifierSetInsideMacro(SpecifiersFound, TEXT("Function"), MetaData);
FScriptLocation FuncNameRetry;
InitScriptLocation(FuncNameRetry);
if (!GetCurrentClass()->HasAnyClassFlags(CLASS_Native))
{
FError::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
{
FError::Throwf(TEXT("Unknown access level"));
}
// non-static functions in a const class must be const themselves
if (GetCurrentClass()->HasAnyClassFlags(CLASS_Const))
{
FuncInfo.FunctionFlags |= FUNC_Const;
}
if (MatchIdentifier(TEXT("static")))
{
FuncInfo.FunctionFlags |= FUNC_Static;
FuncInfo.FunctionExportFlags |= FUNCEXPORT_CppStatic;
}
if (MetaData.Contains("CppFromBpEvent"))
{
FuncInfo.FunctionFlags |= FUNC_Event;
}
ProcessFunctionSpecifiers(FuncInfo, SpecifiersFound);
const bool bClassGeneratedFromBP = FClass::IsDynamic(GetCurrentClass());
if ((FuncInfo.FunctionFlags & FUNC_NetServer) && !(FuncInfo.FunctionFlags & FUNC_NetValidate) && !bClassGeneratedFromBP)
{
FError::Throwf(TEXT("Server RPC missing 'WithValidation' keyword in the UPROPERTY() declaration statement. Required for security purposes."));
}
if ((0 != (FuncInfo.FunctionExportFlags & FUNCEXPORT_CustomThunk)) && !MetaData.Contains("CustomThunk"))
{
MetaData.Add(TEXT("CustomThunk"), TEXT("true"));
}
if ((FuncInfo.FunctionFlags & FUNC_BlueprintPure) && GetCurrentClass()->HasAnyClassFlags(CLASS_Interface))
{
// Until pure interface casts are supported, we don't allow pures in interfaces
FError::Throwf(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;
}
bool bSawVirtual = false;
if (MatchIdentifier(TEXT("virtual")))
{
bSawVirtual = true;
}
FString* InternalPtr = MetaData.Find("BlueprintInternalUseOnly"); // FBlueprintMetadata::MD_BlueprintInternalUseOnly
const bool bDeprecated = MetaData.Contains("DeprecatedFunction"); // FBlueprintMetadata::MD_DeprecatedFunction
const bool bHasMenuCategory = MetaData.Contains("Category"); // FBlueprintMetadata::MD_FunctionCategory
const bool bInternalOnly = InternalPtr && *InternalPtr == TEXT("true");
// If this function is blueprint callable or blueprint pure, require a category
if ((FuncInfo.FunctionFlags & (FUNC_BlueprintCallable | FUNC_BlueprintPure)) != 0)
{
if (!bHasMenuCategory && !bInternalOnly && !bDeprecated)
{
const bool bModuleIsGame = CurrentlyParsedModule && (
CurrentlyParsedModule->ModuleType == EBuildModuleType::GameDeveloper ||
CurrentlyParsedModule->ModuleType == EBuildModuleType::GameEditor ||
CurrentlyParsedModule->ModuleType == EBuildModuleType::GameRuntime ||
CurrentlyParsedModule->ModuleType == EBuildModuleType::GameThirdParty);
// To allow for quick iteration, don't enforce the requirement that game functions have to be categorized
if (!bModuleIsGame)
{
FError::Throwf(TEXT("Blueprint accessible functions must have a category specified"));
}
}
}
// Verify interfaces with respect to their blueprint accessible functions
if (GetCurrentClass()->HasAnyClassFlags(CLASS_Interface))
{
const bool bCanImplementInBlueprints = !GetCurrentClass()->HasMetaData(TEXT("CannotImplementInterfaceInBlueprint")); //FBlueprintMetadata::MD_CannotImplementInterfaceInBlueprint
if((FuncInfo.FunctionFlags & FUNC_BlueprintEvent) != 0)
{
// Ensure that blueprint events are only allowed in implementable interfaces. Internal only functions allowed
if (!bCanImplementInBlueprints && !bInternalOnly)
{
FError::Throwf(TEXT("Interfaces that are not implementable in blueprints cannot have BlueprintImplementableEvent members."));
}
}
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)
{
FError::Throwf(TEXT("Blueprint implementable interfaces cannot contain BlueprintCallable functions that are not BlueprintImplementableEvents. Use CannotImplementInterfaceInBlueprint on the interface if you wish to keep this function."));
}
}
}
// Peek ahead to look for a CORE_API style DLL import/export token if present
{
FToken Token;
if (GetToken(Token, true))
{
bool bThrowTokenBack = true;
if (Token.TokenType == TOKEN_Identifier)
{
FString RequiredAPIMacroIfPresent(Token.Identifier);
if (RequiredAPIMacroIfPresent.EndsWith(TEXT("_API")))
{
//@TODO: Validate the module name for RequiredAPIMacroIfPresent
bThrowTokenBack = false;
if (GetCurrentClass()->HasAnyClassFlags(CLASS_RequiredAPI))
{
FError::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;
}
}
if (bThrowTokenBack)
{
UngetToken(Token);
}
}
}
// Look for virtual again, in case there was an ENGINE_API token first
if (MatchIdentifier(TEXT("virtual")))
{
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 (GetCurrentClass()->HasAnyClassFlags(CLASS_Interface))
{
FError::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)
{
FError::Throwf(TEXT("BlueprintNativeEvent functions must be non-virtual."));
}
else
{
UE_LOG_WARNING_UHT(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 (GetCurrentClass()->HasAnyClassFlags(CLASS_Interface) && !(FuncInfo.FunctionFlags & FUNC_BlueprintEvent))
{
FError::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 (GetCurrentClass()->HasAnyClassFlags(CLASS_Interface))
{
FError::Throwf(TEXT("Interface functions cannot be declared 'final'"));
}
}
// Get return type.
FToken ReturnType( CPT_None );
// C++ style functions always have a return value type, even if it's void
bool bHasReturnValue = !MatchIdentifier(TEXT("void"));
if (bHasReturnValue)
{
GetVarType(AllClasses, GetCurrentScope(), ReturnType, 0, NULL, EPropertyDeclarationStyle::None, EVariableCategory::Return);
}
// Skip whitespaces to get InputPos exactly on beginning of function name.
while (FChar::IsWhitespace(PeekChar())) { GetChar(); }
FuncInfo.InputPos = InputPos;
// Get function or operator name.
if (!GetIdentifier(FuncInfo.Function))
{
FError::Throwf(TEXT("Missing %s name"), TypeOfFunction);
}
if ( !MatchSymbol(TEXT("(")) )
{
FError::Throwf(TEXT("Bad %s definition"), TypeOfFunction);
}
if (FuncInfo.FunctionFlags & FUNC_Net)
{
bool bIsNetService = !!(FuncInfo.FunctionFlags & (FUNC_NetRequest | FUNC_NetResponse));
if (bHasReturnValue && !bIsNetService)
FError::Throwf(TEXT("Replicated functions can't have return values"));
if (FuncInfo.RPCId > 0)
{
if (FString* ExistingFunc = UsedRPCIds.Find(FuncInfo.RPCId))
FError::Throwf(TEXT("Function %s already uses identifier %d"), **ExistingFunc, FuncInfo.RPCId);
UsedRPCIds.Add(FuncInfo.RPCId, FuncInfo.Function.Identifier);
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)
{
// 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, FuncInfo.Function.Identifier);
}
}
}
UFunction* TopFunction = CreateFunction(FuncInfo);
UHTMakefile.AddFunction(CurrentSrcFile, TopFunction);
FClassMetaData* ClassMetaData = GScriptHelper.AddClassData(TopFunction, UHTMakefile, CurrentSrcFile);
UHTMakefile.AddGScriptHelperEntry(CurrentSrcFile, TopFunction, ClassMetaData);
TopFunction->FunctionFlags |= FuncInfo.FunctionFlags;
FuncInfo.FunctionReference = TopFunction;
FuncInfo.SetFunctionNames();
GetCurrentScope()->AddType(TopFunction);
FFunctionData* StoredFuncData = FFunctionData::Add(FuncInfo);
if (FuncInfo.FunctionReference->HasAnyFunctionFlags(FUNC_Delegate))
{
GetCurrentClassData()->MarkContainsDelegate();
}
// Get parameter list.
ParseParameterList(AllClasses, TopFunction, false, &MetaData);
// Get return type, if any.
if (bHasReturnValue)
{
ReturnType.PropertyFlags |= CPF_Parm | CPF_OutParm | CPF_ReturnParm;
UProperty* ReturnProp = GetVarNameAndDim(TopFunction, ReturnType, EVariableCategory::Return);
TopFunction->NumParms++;
}
// determine if there are any outputs for this function
bool bHasAnyOutputs = bHasReturnValue;
if (bHasAnyOutputs == false)
{
for (TFieldIterator<UProperty> It(TopFunction); It; ++It)
{
UProperty const* const Param = *It;
if (!(Param->PropertyFlags & CPF_ReturnParm) && (Param->PropertyFlags & CPF_OutParm))
{
bHasAnyOutputs = true;
break;
}
}
}
if ( (bHasAnyOutputs == false) && (FuncInfo.FunctionFlags & (FUNC_BlueprintPure)) )
{
// This bad behavior would be treated as a warning in the Blueprint editor, so when converted assets generates these bad functions
// we don't want to prevent compilation:
if (!bClassGeneratedFromBP)
{
FError::Throwf(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")) )
{
if( (TopFunction->FunctionFlags & (FUNC_Native)) == 0 )
{
// @TODO: UCREMOVAL Reconsider?
//FError::Throwf(TEXT("'const' may only be used for native functions"));
}
FuncInfo.FunctionFlags |= 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 && ((FuncInfo.FunctionFlags & FUNC_BlueprintCallable) != 0) && !FuncInfo.bForceBlueprintImpure)
{
FuncInfo.FunctionFlags |= FUNC_BlueprintPure;
}
}
// Try parsing metadata for the function
ParseFieldMetaData(MetaData, *(TopFunction->GetName()));
AddFormattedPrevCommentAsTooltipMetaData(MetaData);
AddMetaDataToClassData(TopFunction, MetaData);
// 'final' and 'override' can appear in any order before an optional '= 0' pure virtual specifier
bool bFoundFinal = MatchIdentifier(TEXT("final"));
bool bFoundOverride = MatchIdentifier(TEXT("override"));
if (!bFoundFinal && bFoundOverride)
{
bFoundFinal = MatchIdentifier(TEXT("final"));
}
// 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)
{
FError::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
FuncInfo.FunctionFlags |= FUNC_Final;
FuncInfo.FunctionExportFlags |= FUNCEXPORT_Final;
if (GetCurrentClass()->HasAnyClassFlags(CLASS_Interface))
{
FError::Throwf(TEXT("Interface functions cannot be declared 'final'"));
}
else if (FuncInfo.FunctionFlags & FUNC_BlueprintEvent)
{
FError::Throwf(TEXT("Blueprint events cannot be declared 'final'"));
}
}
// Make sure any new flags made it to the function
//@TODO: UCREMOVAL: Ideally the flags didn't get copied midway thru parsing the function declaration, and we could avoid this
TopFunction->FunctionFlags |= FuncInfo.FunctionFlags;
StoredFuncData->UpdateFunctionData(FuncInfo);
// Verify parameter list and return type compatibility within the
// function, if any, that it overrides.
auto FunctionIterator = GetCurrentScope()->GetTypeIterator<UFunction>();
while (FunctionIterator.MoveNext())
{
UFunction* Function = *FunctionIterator;
if (Function->GetFName() != TopFunction->GetFName() || Function == TopFunction)
continue;
// Don't allow private functions to be redefined.
if (Function->FunctionFlags & FUNC_Private)
FError::Throwf(TEXT("Can't override private function '%s'"), FuncInfo.Function.Identifier);
// see if they both either have a return value or don't
if ((TopFunction->GetReturnProperty() != NULL) != (Function->GetReturnProperty() != NULL))
{
ReturnToLocation(FuncNameRetry);
FError::Throwf(TEXT("Redefinition of '%s %s' differs from original: return value mismatch"), TypeOfFunction, FuncInfo.Function.Identifier );
}
// See if all parameters match.
if (TopFunction->NumParms!=Function->NumParms)
{
ReturnToLocation(FuncNameRetry);
FError::Throwf(TEXT("Redefinition of '%s %s' differs from original; different number of parameters"), TypeOfFunction, FuncInfo.Function.Identifier );
}
// Check all individual parameters.
int32 Count=0;
for( TFieldIterator<UProperty> CurrentFuncParam(TopFunction),SuperFuncParam(Function); Count<Function->NumParms; ++CurrentFuncParam,++SuperFuncParam,++Count )
{
if( !FPropertyBase(*CurrentFuncParam).MatchesType(FPropertyBase(*SuperFuncParam), 1) )
{
if( CurrentFuncParam->PropertyFlags & CPF_ReturnParm )
{
ReturnToLocation(FuncNameRetry);
FError::Throwf(TEXT("Redefinition of %s %s differs only by return type"), TypeOfFunction, FuncInfo.Function.Identifier );
}
else
{
ReturnToLocation(FuncNameRetry);
FError::Throwf(TEXT("Redefinition of '%s %s' differs from original"), TypeOfFunction, FuncInfo.Function.Identifier );
}
break;
}
else if ( CurrentFuncParam->HasAnyPropertyFlags(CPF_OutParm) != SuperFuncParam->HasAnyPropertyFlags(CPF_OutParm) )
{
ReturnToLocation(FuncNameRetry);
FError::Throwf(TEXT("Redefinition of '%s %s' differs from original - 'out' mismatch on parameter %i"), TypeOfFunction, FuncInfo.Function.Identifier, Count + 1);
}
else if ( CurrentFuncParam->HasAnyPropertyFlags(CPF_ReferenceParm) != SuperFuncParam->HasAnyPropertyFlags(CPF_ReferenceParm) )
{
ReturnToLocation(FuncNameRetry);
FError::Throwf(TEXT("Redefinition of '%s %s' differs from original - 'ref' mismatch on parameter %i"), TypeOfFunction, FuncInfo.Function.Identifier, Count + 1);
}
}
if( Count<TopFunction->NumParms )
{
continue;
}
// if super version is event, overridden version must be defined as event (check before inheriting FUNC_Event)
if ( (Function->FunctionFlags & FUNC_Event) && !(FuncInfo.FunctionFlags & FUNC_Event) )
{
FError::Throwf(TEXT("Superclass version is defined as an event so '%s' should be!"), FuncInfo.Function.Identifier);
}
// Function flags to copy from parent.
FuncInfo.FunctionFlags |= (Function->FunctionFlags & FUNC_FuncInherit);
// Make sure the replication conditions aren't being redefined
if ((FuncInfo.FunctionFlags & FUNC_NetFuncFlags) != (Function->FunctionFlags & FUNC_NetFuncFlags))
{
FError::Throwf(TEXT("Redefinition of replication conditions for function '%s'"), FuncInfo.Function.Identifier);
}
FuncInfo.FunctionFlags |= (Function->FunctionFlags & FUNC_NetFuncFlags);
// Are we overriding a function?
if (TopFunction == Function->GetOuter())
{
// Duplicate.
ReturnToLocation( FuncNameRetry );
FError::Throwf(TEXT("Duplicate function '%s'"), *Function->GetName() );
}
// Overriding an existing function.
else if( Function->FunctionFlags & FUNC_Final )
{
ReturnToLocation(FuncNameRetry);
FError::Throwf(TEXT("%s: Can't override a 'final' function"), *Function->GetName() );
}
// 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)
else if( Cast<UClass>(TopFunction->GetOuter()) != NULL )
{
//ReturnToLocation(FuncNameRetry);
FError::Throwf(TEXT("%s: An override of a function cannot have a UFUNCTION() declaration above it; it will use the same parameters as the original base declaration."), *Function->GetName() );
}
// Balk if required specifiers differ.
if ((Function->FunctionFlags & FUNC_FuncOverrideMatch) != (FuncInfo.FunctionFlags & FUNC_FuncOverrideMatch))
{
FError::Throwf(TEXT("Function '%s' specifiers differ from original"), *Function->GetName());
}
// Here we have found the original.
TopFunction->SetSuperStruct(Function);
break;
}
// Bind the function.
TopFunction->Bind();
// Make sure that the replication flags set on an overridden function match the parent function
if (UFunction* SuperFunc = TopFunction->GetSuperFunction())
{
if ((TopFunction->FunctionFlags & FUNC_NetFuncFlags) != (SuperFunc->FunctionFlags & FUNC_NetFuncFlags))
{
FError::Throwf(TEXT("Overridden function '%s': Cannot specify different replication flags when overriding a function."), *TopFunction->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 ((TopFunction->FunctionFlags & FUNC_Net) && (TopFunction->GetSuperFunction() == NULL) && Cast<UClass>(TopFunction->GetOuter()) == NULL)
{
FError::Throwf(TEXT("Function '%s': Base implementation of RPCs cannot be in a state. Add a stub outside state scope."), *TopFunction->GetName());
}
if (TopFunction->FunctionFlags & (FUNC_BlueprintCallable | FUNC_BlueprintEvent))
{
for (TFieldIterator<UProperty> It(TopFunction); It; ++It)
{
UProperty const* const Param = *It;
if (Param->ArrayDim > 1)
{
FError::Throwf(TEXT("Static array cannot be exposed to blueprint. Function: %s Parameter %s\n"), *TopFunction->GetName(), *Param->GetName());
}
if (!IsPropertySupportedByBlueprint(Param, false))
{
FError::Throwf(TEXT("Type '%s' is not supported by blueprint. Function: %s Parameter %s\n"), *Param->GetCPPType(), *TopFunction->GetName(), *Param->GetName());
}
}
}
// Just declaring a function, so end the nesting.
PostPopFunctionDeclaration(AllClasses, TopFunction);
// See what's coming next
FToken Token;
if (!GetToken(Token))
{
FError::Throwf(TEXT("Unexpected end of file"));
}
// Optionally consume a semicolon
// This is optional to allow inline function definitions
if (Token.TokenType == TOKEN_Symbol && !FCString::Stricmp(Token.Identifier, TEXT(";")))
{
// Do nothing (consume it)
}
else if (Token.TokenType == TOKEN_Symbol && !FCString::Stricmp(Token.Identifier, TEXT("{")))
{
// Skip inline function bodies
UngetToken(Token);
SkipDeclaration(Token);
}
else
{
// Put the token back so we can continue parsing as normal
UngetToken(Token);
}
}
/** Parses optional metadata text. */
void FHeaderParser::ParseFieldMetaData(TMap<FName, FString>& MetaData, const TCHAR* FieldName)
{
FToken PropertyMetaData;
bool bMetadataPresent = false;
if (MatchIdentifier(TEXT("UMETA")))
{
bMetadataPresent = true;
RequireSymbol( TEXT("("),*FString::Printf(TEXT("' %s metadata'"), FieldName) );
if (!GetRawTokenRespectingQuotes(PropertyMetaData, TCHAR(')')))
{
FError::Throwf(TEXT("'%s': No metadata specified"), FieldName);
}
RequireSymbol( TEXT(")"),*FString::Printf(TEXT("' %s metadata'"), FieldName) );
}
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(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 = Pairs[PairIndex];
FString Key = Token;
// by default, not value, just a key (allowed)
FString Value;
// look for a value after an =
int32 Equals = Token.Find(TEXT("="));
// if we have an =, break up the string
if (Equals != -1)
{
Key = Token.Left(Equals);
Value = Token.Right((Token.Len() - Equals) - 1);
}
InsertMetaDataPair(MetaData, Key, Value);
}
}
}
bool FHeaderParser::IsBitfieldProperty()
{
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.TokenType == TOKEN_Symbol && FCString::Stricmp(Token.Identifier, TEXT(":")) == 0)
{
bIsBitfield = true;
}
UngetToken(Token);
}
UngetToken(TokenVarName);
}
return bIsBitfield;
}
void FHeaderParser::ValidatePropertyIsDeprecatedIfNecessary(FPropertyBase& VarProperty, FToken* OuterPropertyType)
{
// check to see if we have a UClassProperty using a deprecated class
if ( VarProperty.MetaClass != NULL && VarProperty.MetaClass->HasAnyClassFlags(CLASS_Deprecated) && !(VarProperty.PropertyFlags & CPF_Deprecated) &&
(OuterPropertyType == NULL || !(OuterPropertyType->PropertyFlags & CPF_Deprecated)) )
{
FError::Throwf(TEXT("Property is using a deprecated class: %s. Property should be marked deprecated as well."), *VarProperty.MetaClass->GetPathName());
}
// check to see if we have a UObjectProperty using a deprecated class.
// PropertyClass is part of a union, so only check PropertyClass if this token represents an object property
if ( (VarProperty.Type == CPT_ObjectReference || VarProperty.Type == CPT_WeakObjectReference || VarProperty.Type == CPT_LazyObjectReference || VarProperty.Type == CPT_AssetObjectReference) && VarProperty.PropertyClass != NULL
&& VarProperty.PropertyClass->HasAnyClassFlags(CLASS_Deprecated) // and the object class being used has been deprecated
&& (VarProperty.PropertyFlags&CPF_Deprecated) == 0 // and this property isn't marked deprecated as well
&& (OuterPropertyType == NULL || !(OuterPropertyType->PropertyFlags & CPF_Deprecated)) ) // and this property isn't in an array that was marked deprecated either
{
FError::Throwf(TEXT("Property is using a deprecated class: %s. Property should be marked deprecated as well."), *VarProperty.PropertyClass->GetPathName());
}
}
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_Byte:
case CPT_Float:
case CPT_Bool:
case CPT_ObjectReference:
case CPT_String:
case CPT_Text:
case CPT_Name:
case CPT_Vector:
case CPT_Rotation:
case CPT_Interface:
ProperNativeType = true;
}
if (!ProperNativeType && (CPT_Struct == Property.Type) && Property.Struct)
{
static const FName BlueprintTypeName(TEXT("BlueprintType"));
ProperNativeType |= Property.Struct->GetBoolMetaData(BlueprintTypeName);
}
return ProperNativeType;
}
};
void FHeaderParser::CompileVariableDeclaration(FClasses& AllClasses, UStruct* Struct)
{
uint64 DisallowFlags = CPF_ParmFlags;
uint64 EdFlags = 0;
// Get variable type.
FPropertyBase OriginalProperty(CPT_None);
FIndexRange TypeRange;
GetVarType( AllClasses, &FScope::GetTypeScope(Struct).Get(), OriginalProperty, DisallowFlags, /*OuterPropertyType=*/ NULL, EPropertyDeclarationStyle::UPROPERTY, EVariableCategory::Member, &TypeRange );
OriginalProperty.PropertyFlags |= EdFlags;
FString* Category = OriginalProperty.MetaData.Find("Category");
// First check if the category was specified at all and if the property was exposed to the editor.
if (!Category && (OriginalProperty.PropertyFlags & (CPF_Edit|CPF_BlueprintVisible)))
{
static const FString AbsoluteEngineDir = FPaths::ConvertRelativePathToFull(FPaths::EngineDir());
FString SourceFilename = GetCurrentSourceFile()->GetFilename();
FPaths::NormalizeFilename(SourceFilename);
if (Struct->GetOutermost() != nullptr && !SourceFilename.StartsWith(AbsoluteEngineDir))
{
OriginalProperty.MetaData.Add("Category", Struct->GetFName().ToString());
Category = OriginalProperty.MetaData.Find("Category");
}
else
{
FError::Throwf(TEXT("Property is exposed to the editor or blueprints but has no Category specified."));
}
}
// Validate that pointer properties are not interfaces (which are not GC'd and so will cause runtime errors)
if (OriginalProperty.PointerType == EPointerType::Native && OriginalProperty.Struct->IsChildOf(UInterface::StaticClass()))
{
// Get the name of the type, removing the asterisk representing the pointer
FString TypeName = FString(TypeRange.Count, Input + TypeRange.StartIndex).Trim().TrimTrailing().LeftChop(1).TrimTrailing();
FError::Throwf(TEXT("UPROPERTY pointers cannot be interfaces - did you mean TScriptInterface<%s>?"), *TypeName);
}
// If the category was specified explicitly, it wins
if (Category && !(OriginalProperty.PropertyFlags & (CPF_Edit|CPF_BlueprintVisible|CPF_BlueprintAssignable|CPF_BlueprintCallable)))
{
FError::Throwf(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(!(OriginalProperty.PropertyFlags & CPF_Edit))
{
if (OriginalProperty.PropertyFlags & CPF_DisableEditOnInstance)
{
FError::Throwf(TEXT("Property cannot have 'DisableEditOnInstance' without being editable"));
}
if (OriginalProperty.PropertyFlags & CPF_DisableEditOnTemplate)
{
FError::Throwf(TEXT("Property cannot have 'DisableEditOnTemplate' without being editable"));
}
}
// Validate.
if (OriginalProperty.PropertyFlags & CPF_ParmFlags)
{
FError::Throwf(TEXT("Illegal type modifiers in member variable declaration") );
}
if (FString* ExposeOnSpawnValue = OriginalProperty.MetaData.Find(TEXT("ExposeOnSpawn")))
{
if ((*ExposeOnSpawnValue == TEXT("true")) && !FExposeOnSpawnValidator::IsSupported(OriginalProperty))
{
FError::Throwf(TEXT("ExposeOnSpawn - Property cannot be exposed"));
}
}
// Process all variables of this type.
TArray<UProperty*> NewProperties;
do
{
FToken Property = OriginalProperty;
UProperty* NewProperty = GetVarNameAndDim(Struct, Property, EVariableCategory::Member);
// Optionally consume the :1 at the end of a bitfield boolean declaration
if (Property.IsBool() && MatchSymbol(TEXT(":")))
{
int32 BitfieldSize = 0;
if (!GetConstInt(/*out*/ BitfieldSize) || (BitfieldSize != 1))
{
FError::Throwf(TEXT("Bad or missing bitfield size for '%s', must be 1."), *NewProperty->GetName());
}
}
// Deprecation validation
ValidatePropertyIsDeprecatedIfNecessary(Property, NULL);
if (TopNest->NestType != ENestType::FunctionDeclaration)
{
if (NewProperties.Num())
{
FError::Throwf(TEXT("Comma delimited properties cannot be converted %s.%s\n"), *Struct->GetName(), *NewProperty->GetName());
}
}
NewProperties.Add( NewProperty );
// we'll need any metadata tags we parsed later on when we call ConvertEOLCommentToTooltip() so the tags aren't clobbered
OriginalProperty.MetaData = Property.MetaData;
if (NewProperty->HasAnyPropertyFlags(CPF_RepNotify))
{
NewProperty->RepNotifyFunc = OriginalProperty.RepNotifyName;
}
if (UScriptStruct* StructBeingBuilt = Cast<UScriptStruct>(Struct))
{
if (NewProperty->ContainsInstancedObjectProperty())
{
StructBeingBuilt->StructFlags = EStructFlags(StructBeingBuilt->StructFlags | STRUCT_HasInstancedReference);
}
}
if (NewProperty->HasAnyPropertyFlags(CPF_BlueprintVisible) && (NewProperty->ArrayDim > 1))
{
FError::Throwf(TEXT("Static array cannot be exposed to blueprint %s.%s"), *Struct->GetName(), *NewProperty->GetName());
}
if (NewProperty->HasAnyPropertyFlags(CPF_BlueprintVisible) && !IsPropertySupportedByBlueprint(NewProperty, true))
{
FError::Throwf(TEXT("Type '%s' is not supported by blueprint. %s.%s"), *NewProperty->GetCPPType(), *Struct->GetName(), *NewProperty->GetName());
}
} while( MatchSymbol(TEXT(",")) );
// Optional member initializer.
if (MatchSymbol(TEXT("=")))
{
// Skip past the specified member initializer; we make no attempt to parse it
FToken SkipToken;
while (GetToken(SkipToken))
{
if (SkipToken.Matches(TEXT(";")))
{
// went too far
UngetToken(SkipToken);
break;
}
}
}
// Expect a semicolon.
RequireSymbol( TEXT(";"), TEXT("'variable declaration'") );
}
//
// Compile a statement: Either a declaration or a command.
// Returns 1 if success, 0 if end of file.
//
bool FHeaderParser::CompileStatement(FClasses& AllClasses, TArray<UDelegateFunction*>& DelegatesToFixup)
{
// Get a token and compile it.
FToken Token;
if( !GetToken(Token, true) )
{
// End of file.
return false;
}
else if (!CompileDeclaration(AllClasses, DelegatesToFixup, Token))
{
FError::Throwf(TEXT("'%s': Bad command or expression"), Token.Identifier );
}
return true;
}
//
// Compute the function parameter size and save the return offset
//
//@TODO: UCREMOVAL: Need to rename ComputeFunctionParametersSize to reflect the additional work it's doing
void FHeaderParser::ComputeFunctionParametersSize( UClass* Class )
{
// Recurse with all child states in this class.
for (TFieldIterator<UFunction> FuncIt(Class, EFieldIteratorFlags::ExcludeSuper); FuncIt; ++FuncIt)
{
UFunction* ThisFunction = *FuncIt;
// Fix up any structs that were used as a parameter in a delegate before being defined
if (ThisFunction->HasAnyFunctionFlags(FUNC_Delegate))
{
for (TFieldIterator<UProperty> It(ThisFunction); It; ++It)
{
UProperty* Param = *It;
if (UStructProperty* StructProp = Cast<UStructProperty>(Param))
{
if (StructProp->Struct->StructFlags & STRUCT_HasInstancedReference)
{
StructProp->PropertyFlags |= CPF_ContainsInstancedReference;
}
}
}
ThisFunction->StaticLink(true);
}
// Compute the function parameter size, propagate some flags to the outer function, and save the return offset
// Must be done in a second phase, as StaticLink resets various fields again!
ThisFunction->ParmsSize = 0;
for (TFieldIterator<UProperty> It(ThisFunction); It; ++It)
{
UProperty* Param = *It;
if (!(Param->PropertyFlags & CPF_ReturnParm) && (Param->PropertyFlags & CPF_OutParm))
{
ThisFunction->FunctionFlags |= FUNC_HasOutParms;
}
if (UStructProperty* StructProp = Cast<UStructProperty>(Param))
{
if (StructProp->Struct->HasDefaults())
{
ThisFunction->FunctionFlags |= FUNC_HasDefaults;
}
}
}
}
}
/*-----------------------------------------------------------------------------
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.Matches(TEXT("{")) )
{
NestCount++;
}
else if ( Token.Matches(TEXT("}")) )
{
NestCount--;
}
else if ( Token.Matches(TEXT(";")) && OriginalNestCount == 0 )
{
break;
}
if ( NestCount < OriginalNestCount || NestCount < 0 )
break;
}
if( NestCount > 0 )
{
FError::Throwf(TEXT("Unexpected end of file at end of %s"), ErrorTag );
}
else if ( NestCount < 0 )
{
FError::Throwf(TEXT("Extraneous closing brace found in %s"), ErrorTag);
}
}
/*-----------------------------------------------------------------------------
Main script compiling routine.
-----------------------------------------------------------------------------*/
//
// Finalize any script-exposed functions in the specified class
//
void FHeaderParser::FinalizeScriptExposedFunctions(UClass* Class)
{
// Finalize all of the children introduced in this class
for (TFieldIterator<UStruct> ChildIt(Class, EFieldIteratorFlags::ExcludeSuper); ChildIt; ++ChildIt)
{
UStruct* ChildStruct = *ChildIt;
if (UFunction* Function = Cast<UFunction>(ChildStruct))
{
// Add this function to the function map of it's parent class
Class->AddFunctionToFunctionMap(Function);
}
else if (ChildStruct->IsA(UScriptStruct::StaticClass()))
{
// Ignore embedded structs
}
else
{
UE_LOG_WARNING_UHT(TEXT("Unknown and unexpected child named %s of type %s in %s\n"), *ChildStruct->GetName(), *ChildStruct->GetClass()->GetName(), *Class->GetName());
check(false);
}
}
}
//
// Parses the header associated with the specified class.
// Returns result enumeration.
//
ECompilationResult::Type FHeaderParser::ParseHeader(FClasses& AllClasses, FUnrealSourceFile* SourceFile)
{
SetCurrentSourceFile(SourceFile);
UHTMakefile.AddToHeaderOrder(SourceFile);
NameLookupCPP.SetCurrentSourceFile(SourceFile);
FUnrealSourceFile* CurrentSrcFile = SourceFile;
if (CurrentSrcFile->IsParsed())
{
return ECompilationResult::Succeeded;
}
CurrentSrcFile->MarkAsParsed();
// Early-out if this class has previously failed some aspect of parsing
if (FailedFilesAnnotation.Get(CurrentSrcFile))
{
return ECompilationResult::OtherCompilationError;
}
// Reset the parser to begin a new class
bEncounteredNewStyleClass_UnmatchedBrackets = false;
bSpottedAutogeneratedHeaderInclude = false;
bHaveSeenUClass = false;
bClassHasGeneratedBody = false;
bClassHasGeneratedUInterfaceBody = false;
bClassHasGeneratedIInterfaceBody = false;
ECompilationResult::Type Result = ECompilationResult::OtherCompilationError;
// Message.
UE_LOG(LogCompile, Verbose, TEXT("Parsing %s"), *CurrentSrcFile->GetFilename());
// Init compiler variables.
ResetParser(*CurrentSrcFile->GetContent());
// Init nesting.
NestLevel = 0;
TopNest = NULL;
PushNest(ENestType::GlobalScope, nullptr, CurrentSrcFile);
// 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;
#if !PLATFORM_EXCEPTIONS_DISABLED
try
#endif
{
// Parse entire program.
TArray<UDelegateFunction*> DelegatesToFixup;
while (CompileStatement(AllClasses, DelegatesToFixup))
{
bEmptyFile = false;
// Clear out the previous comment in anticipation of the next statement.
ClearComment();
StatementsParsed++;
}
PopNest(ENestType::GlobalScope, TEXT("Global scope"));
auto ScopeTypeIterator = CurrentSrcFile->GetScope()->GetTypeIterator();
while (ScopeTypeIterator.MoveNext())
{
UField* Type = *ScopeTypeIterator;
if (!Type->IsA<UScriptStruct>() && !Type->IsA<UClass>())
{
continue;
}
UStruct* Struct = Cast<UStruct>(Type);
// now validate all delegate variables declared in the class
TMap<FName, UFunction*> DelegateCache;
FixupDelegateProperties(AllClasses, Struct, FScope::GetTypeScope(Struct).Get(), DelegateCache);
}
// Fix up any delegates themselves, if they refer to other delegates
{
TMap<FName, UFunction*> DelegateCache;
for (UDelegateFunction* Delegate : DelegatesToFixup)
{
FixupDelegateProperties(AllClasses, Delegate, CurrentSrcFile->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();
FError::Throwf(*ErrorMsg);
}
// Make sure the compilation ended with valid nesting.
if (bEncounteredNewStyleClass_UnmatchedBrackets)
{
FError::Throwf(TEXT("Missing } at end of class") );
}
if (NestLevel == 1)
{
FError::Throwf(TEXT("Internal nest inconsistency") );
}
else if (NestLevel > 2)
{
FError::Throwf(TEXT("Unexpected end of script in '%s' block"), NestTypeName(TopNest->NestType) );
}
// First-pass success.
Result = ECompilationResult::Succeeded;
for (UClass* Class : CurrentSrcFile->GetDefinedClasses())
{
PostParsingClassSetup(Class);
// Clean up and exit.
Class->Bind();
// Finalize functions
if (Result == ECompilationResult::Succeeded)
{
FinalizeScriptExposedFunctions(Class);
}
bNoExportClassesOnly = bNoExportClassesOnly && Class->HasAnyClassFlags(CLASS_NoExport);
}
check(CurrentSrcFile->IsParsed());
if (!bSpottedAutogeneratedHeaderInclude && !bEmptyFile && !bNoExportClassesOnly)
{
const FString ExpectedHeaderName = CurrentSrcFile->GetGeneratedHeaderFilename();
FError::Throwf(TEXT("Expected an include at the top of the header: '#include \"%s\"'"), *ExpectedHeaderName);
}
}
#if !PLATFORM_EXCEPTIONS_DISABLED
catch( TCHAR* ErrorMsg )
{
if (NestLevel == 0)
{
// Pushing nest so there is a file context for this error.
PushNest(ENestType::GlobalScope, nullptr, CurrentSrcFile);
}
// Handle compiler error.
{
TGuardValue<ELogTimes::Type> DisableLogTimes(GPrintLogTimes, ELogTimes::None);
FString FormattedErrorMessageWithContext = FString::Printf(TEXT("%s: Error: %s"), *GetContext(), ErrorMsg);
UE_LOG(LogCompile, Log, TEXT("%s"), *FormattedErrorMessageWithContext );
Warn->Log(ELogVerbosity::Error, *FString::Printf(TEXT("Error: %s"), ErrorMsg));
}
FailedFilesAnnotation.Set(CurrentSrcFile);
Result = GCompilationResult;
}
#endif
return Result; //@TODO: UCREMOVAL: This function is always returning succeeded even on a compiler error; should this continue?
}
/*-----------------------------------------------------------------------------
Global functions.
-----------------------------------------------------------------------------*/
ECompilationResult::Type FHeaderParser::ParseRestOfModulesSourceFiles(FClasses& AllClasses, UPackage* ModulePackage, FHeaderParser& HeaderParser, FUHTMakefile& UHTMakefile)
{
for (auto& Pair : GUnrealSourceFilesMap)
{
FUnrealSourceFile* SourceFile = &Pair.Value.Get();
if (SourceFile->GetPackage() == ModulePackage && (!SourceFile->IsParsed() || SourceFile->GetDefinedClassesCount() == 0))
{
ECompilationResult::Type Result;
if ((Result = ParseHeaders(AllClasses, HeaderParser, SourceFile, UHTMakefile)) != ECompilationResult::Succeeded)
{
return Result;
}
}
}
return ECompilationResult::Succeeded;
}
// Parse Class's annotated headers and optionally its child classes.
ECompilationResult::Type FHeaderParser::ParseHeaders(FClasses& AllClasses, FHeaderParser& HeaderParser, FUnrealSourceFile* SourceFile, FUHTMakefile& UHTMakefile)
{
ECompilationResult::Type Result = ECompilationResult::Succeeded;
if (SourceFile->AreDependenciesResolved())
{
return Result;
}
SourceFile->MarkDependenciesResolved();
TArray<FUnrealSourceFile*> SourceFilesRequired;
static const FString ObjectHeader = FString(TEXT("NoExportTypes.h"));
for (FHeaderProvider& Include : SourceFile->GetIncludes())
{
if (Include.GetId() == ObjectHeader)
{
continue;
}
FUnrealSourceFile* DepFile = Include.Resolve();
if (DepFile)
{
SourceFilesRequired.Add(DepFile);
}
}
const TArray<UClass*>& Classes = SourceFile->GetDefinedClasses();
for (UClass* Class : Classes)
{
for (UClass* ParentClass = Class->GetSuperClass(); ParentClass && !ParentClass->HasAnyClassFlags(CLASS_Parsed | CLASS_Intrinsic); ParentClass = ParentClass->GetSuperClass())
{
SourceFilesRequired.Add(&GTypeDefinitionInfoMap[ParentClass]->GetUnrealSourceFile());
}
}
UHTMakefile.GetHeaderDescriptor(SourceFile).AddPrerequesites(SourceFilesRequired);
for (FUnrealSourceFile* RequiredFile : SourceFilesRequired)
{
SourceFile->GetScope()->IncludeScope(&RequiredFile->GetScope().Get());
ECompilationResult::Type ParseResult = ParseHeaders(AllClasses, HeaderParser, RequiredFile, UHTMakefile);
if (ParseResult != ECompilationResult::Succeeded)
{
return ParseResult;
}
}
// Parse the file
{
ECompilationResult::Type OneFileResult = HeaderParser.ParseHeader(AllClasses, SourceFile);
for (UClass* Class : Classes)
{
Class->ClassFlags |= CLASS_Parsed;
}
if (OneFileResult != ECompilationResult::Succeeded)
{
// if we couldn't parse this file fail.
return OneFileResult;
}
}
// Success.
return Result;
}
bool FHeaderParser::DependentClassNameFromHeader(const TCHAR* HeaderFilename, FString& OutClassName)
{
FString DependentClassName(HeaderFilename);
const int32 ExtensionIndex = DependentClassName.Find(TEXT("."));
if (ExtensionIndex != INDEX_NONE)
{
// Generate UHeaderName name for this header.
OutClassName = FString(TEXT("U")) + FPaths::GetBaseFilename(*DependentClassName);
return true;
}
return false;
}
/**
* Gets source files ordered by UCLASSes inheritance.
*
* @param CurrentPackage Current package.
* @param AllClasses Current class tree.
*
* @returns Array of source files.
*/
TArray<FUnrealSourceFile*> GetSourceFilesWithInheritanceOrdering(UPackage* CurrentPackage, FClasses& AllClasses)
{
TArray<FUnrealSourceFile*> SourceFiles;
TArray<FClass*> Classes = AllClasses.GetClassesInPackage();
// First add source files with the inheritance order.
for (UClass* Class : Classes)
{
TSharedRef<FUnrealTypeDefinitionInfo>* DefinitionInfoPtr = GTypeDefinitionInfoMap.Find(Class);
if (DefinitionInfoPtr == nullptr)
{
continue;
}
FUnrealSourceFile& SourceFile = (*DefinitionInfoPtr)->GetUnrealSourceFile();
if (!SourceFiles.Contains(&SourceFile)
&& SourceFile.GetScope()->ContainsTypes())
{
SourceFiles.Add(&SourceFile);
}
}
// Then add the rest.
for (auto& Pair : GUnrealSourceFilesMap)
{
auto& SourceFile = Pair.Value.Get();
if (SourceFile.GetPackage() == CurrentPackage
&& !SourceFiles.Contains(&SourceFile)
&& SourceFile.GetScope()->ContainsTypes())
{
SourceFiles.Add(&SourceFile);
}
}
return SourceFiles;
}
// Begins the process of exporting C++ class declarations for native classes in the specified package
void FHeaderParser::ExportNativeHeaders(
UPackage* CurrentPackage,
FClasses& AllClasses,
bool bAllowSaveExportedHeaders,
FUHTMakefile& UHTMakefile,
const FManifestModule& Module
)
{
// Build a list of header filenames
TArray<FString> ClassHeaderFilenames;
new (ClassHeaderFilenames) FString();
TArray<FUnrealSourceFile*> SourceFiles = GetSourceFilesWithInheritanceOrdering(CurrentPackage, AllClasses);
if (SourceFiles.Num() > 0)
{
if ( CurrentPackage != NULL )
{
UE_LOG(LogCompile, Verbose, TEXT("Exporting native class declarations for %s"), *CurrentPackage->GetName());
}
else
{
UE_LOG(LogCompile, Verbose, TEXT("Exporting native class declarations"));
}
UHTMakefile.StartExporting();
FName ModuleName = FName(*Module.Name);
bool bNeedsRegeneration = Module.NeedsRegeneration();
bool bUHTMakefileContainsModuleData = UHTMakefile.HasModule(ModuleName);
bool bLoadFromMakefile = !bNeedsRegeneration && bUHTMakefileContainsModuleData;
if (bLoadFromMakefile)
{
UHTMakefile.LoadModuleData(ModuleName, Module);
}
else
{
// Export native class definitions to package header files.
FNativeClassHeaderGenerator(
CurrentPackage,
SourceFiles,
AllClasses,
bAllowSaveExportedHeaders,
UHTMakefile
);
}
UHTMakefile.StopExporting();
}
}
FHeaderParser::FHeaderParser(FFeedbackContext* InWarn, FUHTMakefile& InUHTMakefile)
: FBaseParser ()
, Warn (InWarn)
, UHTMakefile(InUHTMakefile)
, bSpottedAutogeneratedHeaderInclude(false)
, NestLevel (0)
, TopNest (nullptr)
{
FScriptLocation::Compiler = this;
// This should be moved to some sort of config
StructsWithNoPrefix.Add("uint64");
StructsWithNoPrefix.Add("uint32");
StructsWithNoPrefix.Add("double");
StructsWithTPrefix.Add("IndirectArray");
StructsWithTPrefix.Add("BitArray");
StructsWithTPrefix.Add("SparseArray");
StructsWithTPrefix.Add("Set");
StructsWithTPrefix.Add("Map");
StructsWithTPrefix.Add("MultiMap");
StructsWithTPrefix.Add("SharedPtr");
// List of legal delegate parameter counts
DelegateParameterCountStrings.Add(TEXT("_OneParam"));
DelegateParameterCountStrings.Add(TEXT("_TwoParams"));
DelegateParameterCountStrings.Add(TEXT("_ThreeParams"));
DelegateParameterCountStrings.Add(TEXT("_FourParams"));
DelegateParameterCountStrings.Add(TEXT("_FiveParams"));
DelegateParameterCountStrings.Add(TEXT("_SixParams"));
DelegateParameterCountStrings.Add(TEXT("_SevenParams"));
DelegateParameterCountStrings.Add(TEXT("_EightParams"));
DelegateParameterCountStrings.Add(TEXT("_NineParams"));
FString Version;
if (GConfig->GetString(TEXT("GeneratedCodeVersion"), TEXT("UnrealHeaderTool"), Version, GEngineIni))
{
DefaultGeneratedCodeVersion = ToGeneratedCodeVersion(Version);
}
}
// Throws if a specifier value wasn't provided
void FHeaderParser::RequireSpecifierValue(const FPropertySpecifier& Specifier, bool bRequireExactlyOne)
{
if (Specifier.Values.Num() == 0)
{
FError::Throwf(TEXT("The specifier '%s' must be given a value"), *Specifier.Key);
}
else if ((Specifier.Values.Num() != 1) && bRequireExactlyOne)
{
FError::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 FPropertySpecifier& Specifier)
{
RequireSpecifierValue(Specifier, /*bRequireExactlyOne*/ true);
return Specifier.Values[0];
}
// Exports the class to all vailable plugins
void ExportClassToScriptPlugins(UClass* Class, const FManifestModule& Module, IScriptGeneratorPluginInterface& ScriptPlugin)
{
TSharedRef<FUnrealTypeDefinitionInfo>* DefinitionInfoRef = GTypeDefinitionInfoMap.Find(Class);
if (DefinitionInfoRef == nullptr)
{
const FString Empty = TEXT("");
ScriptPlugin.ExportClass(Class, Empty, Empty, false);
}
else
{
FUnrealSourceFile& SourceFile = (*DefinitionInfoRef)->GetUnrealSourceFile();
ScriptPlugin.ExportClass(Class, SourceFile.GetFilename(), SourceFile.GetGeneratedFilename(), SourceFile.HasChanged());
}
}
// Exports class tree to all available plugins
void ExportClassTreeToScriptPlugins(const FClassTree* Node, const FManifestModule& Module, IScriptGeneratorPluginInterface& ScriptPlugin)
{
for (int32 ChildIndex = 0; ChildIndex < Node->NumChildren(); ++ChildIndex)
{
const FClassTree* ChildNode = Node->GetChild(ChildIndex);
ExportClassToScriptPlugins(ChildNode->GetClass(), Module, ScriptPlugin);
}
for (int32 ChildIndex = 0; ChildIndex < Node->NumChildren(); ++ChildIndex)
{
const FClassTree* ChildNode = Node->GetChild(ChildIndex);
ExportClassTreeToScriptPlugins(ChildNode, Module, ScriptPlugin);
}
}
// Parse all headers for classes that are inside CurrentPackage.
ECompilationResult::Type FHeaderParser::ParseAllHeadersInside(
FClasses& ModuleClasses,
FFeedbackContext* Warn,
UPackage* CurrentPackage,
const FManifestModule& Module,
TArray<IScriptGeneratorPluginInterface*>& ScriptPlugins,
FUHTMakefile& UHTMakefile
)
{
// Disable loading of objects outside of this package (or more exactly, objects which aren't UFields, CDO, or templates)
TGuardValue<bool> AutoRestoreVerifyObjectRefsFlag(GVerifyObjectReferencesOnly, true);
UHTMakefile.SetCurrentModuleName(FName(*Module.Name));
// Create the header parser and register it as the warning context.
// Note: This must be declared outside the try block, since the catch block will log into it.
FHeaderParser HeaderParser(Warn, UHTMakefile);
HeaderParser.CurrentlyParsedModule = &Module;
Warn->SetContext(&HeaderParser);
// Hierarchically parse all classes.
ECompilationResult::Type Result = ECompilationResult::Succeeded;
#if !PLATFORM_EXCEPTIONS_DISABLED
try
#endif
{
UHTMakefile.StartLoading();
FName ModuleName = FName(*Module.Name);
UHTMakefile.SetCurrentModuleName(ModuleName);
bool bNeedsRegeneration = Module.NeedsRegeneration();
bool bUHTMakefileContainsModuleData = UHTMakefile.HasModule(ModuleName);
bool bLoadFromMakefile = !bNeedsRegeneration && bUHTMakefileContainsModuleData;
if (bLoadFromMakefile)
{
UHTMakefile.LoadModuleData(ModuleName, Module);
}
else
{
// Set up a filename for the error context if we don't even get as far parsing a class
FClass* RootClass = ModuleClasses.GetRootClass();
const TSharedRef<FUnrealTypeDefinitionInfo>& TypeDefinitionInfo = GTypeDefinitionInfoMap[RootClass];
const FUnrealSourceFile& RootSourceFile = TypeDefinitionInfo->GetUnrealSourceFile();
const FString& RootFilename = RootSourceFile.GetFilename();
HeaderParser.Filename = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RootFilename);
for (FUnrealSourceFile* SourceFile : GPublicSourceFileSet)
{
if (SourceFile->GetPackage() == CurrentPackage && (!SourceFile->IsParsed() || SourceFile->GetDefinedClassesCount() == 0))
{
Result = ParseHeaders(ModuleClasses, HeaderParser, SourceFile, UHTMakefile);
if (Result != ECompilationResult::Succeeded)
{
return Result;
}
}
}
if (Result == ECompilationResult::Succeeded)
{
Result = FHeaderParser::ParseRestOfModulesSourceFiles(ModuleClasses, CurrentPackage, HeaderParser, UHTMakefile);
}
}
UHTMakefile.StopLoading();
// Export the autogenerated code wrappers
if (Result == ECompilationResult::Succeeded)
{
// At this point all headers have been parsed and the header parser will
// no longer have up to date info about what's being done so unregister it
// from the feedback context.
Warn->SetContext(NULL);
double ExportTime = 0.0;
{
FScopedDurationTimer Timer(ExportTime);
ExportNativeHeaders(
CurrentPackage,
ModuleClasses,
Module.SaveExportedHeaders,
UHTMakefile,
Module
);
}
GHeaderCodeGenTime += ExportTime;
// Done with header generation
if (HeaderParser.LinesParsed > 0)
{
UE_LOG(LogCompile, Log, TEXT("Success: Parsed %i line(s), %i statement(s) in %.2f secs.\r\n"), HeaderParser.LinesParsed, HeaderParser.StatementsParsed, ExportTime);
}
else
{
UE_LOG(LogCompile, Log, TEXT("Success: Everything is up to date (in %.2f secs)"), ExportTime);
}
}
}
#if !PLATFORM_EXCEPTIONS_DISABLED
catch (TCHAR* ErrorMsg)
{
Warn->Log(ELogVerbosity::Error, ErrorMsg);
Result = GCompilationResult;
}
#endif
// Unregister the header parser from the feedback context
Warn->SetContext(NULL);
if (Result == ECompilationResult::Succeeded && ScriptPlugins.Num())
{
FScopedDurationTimer PluginTimeTracker(GPluginOverheadTime);
FClassTree* RootNode = &ModuleClasses.GetClassTree();
for (IScriptGeneratorPluginInterface* Plugin : ScriptPlugins)
{
if (Plugin->ShouldExportClassesForModule(Module.Name, Module.ModuleType, Module.GeneratedIncludeDirectory))
{
ExportClassToScriptPlugins(RootNode->GetClass(), Module, *Plugin);
ExportClassTreeToScriptPlugins(RootNode, Module, *Plugin);
}
}
}
return Result;
}
/**
* 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(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);
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;
}
// Performs a preliminary parse of the text in the specified buffer, pulling out useful information for the header generation process
void FHeaderParser::SimplifiedClassParse(const TCHAR* Filename, const TCHAR* InBuffer, TArray<FSimplifiedParsingClassInfo>& OutParsedClassArray, TArray<FHeaderProvider>& DependentOn, FStringOutputDevice& ClassHeaderTextStrippedOfCppText)
{
FHeaderPreParser Parser;
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 CommentDim = 0;
int32 CurrentLine = 0;
const TCHAR* Buffer = InBuffer;
// Preprocessor pass
while (FParse::Line(&Buffer, StrLine, true))
{
CurrentLine++;
const TCHAR* Str = *StrLine;
bool bProcess = CommentDim <= 0; // for skipping nested multi-line comments
int32 BraceCount = 0;
if( !bProcess )
{
ClassHeaderTextStrippedOfCppText.Logf( TEXT("%s\r\n"), *StrLine );
continue;
}
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"), bShouldKeepBlockContents ? *StrLine : TEXT(""));
while ((DirectiveStack.Num() > 0) && FParse::Line(&Buffer, StrLine, 1))
{
CurrentLine++;
Str = *StrLine;
bool bIsDirective = false;
if( FParse::Command(&Str,TEXT("#endif")) )
{
DirectiveStack.Pop();
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);
bIsDirective = true;
}
else if (FParse::Command(&Str,TEXT("#elif")))
{
EBlockDirectiveType Directive = ParseCommandToBlockDirectiveType(&Str);
DirectiveStack.Top() = Directive;
bIsDirective = true;
}
else if (FParse::Command(&Str, TEXT("#else")))
{
switch (DirectiveStack[0])
{
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:
FFileLineException::Throwf(Filename, CurrentLine, 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;
}
bIsDirective = true;
}
// 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.Trim();
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")))
{
FFileLineException::Throwf(Filename, CurrentLine, TEXT("%s inside this preprocessor block will be skipped"), FoundSubstr);
}
}
ClassHeaderTextStrippedOfCppText.Logf(TEXT("%s\r\n"), bShouldKeepBlockContents ? *StrLine : TEXT(""));
if (bIsDirective)
{
bShouldKeepBlockContents = !DirectiveStack.ContainsByPredicate([](EBlockDirectiveType Directive) { return !ShouldKeepBlockContents(Directive); });
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
CommentDim = 0;
CurrentLine = 0;
Buffer = *ClassHeaderTextStrippedOfCppText;
const TCHAR* StartOfLine = Buffer;
bool bFoundGeneratedInclude = false;
bool bFoundExportedClasses = false;
while (FParse::Line(&Buffer, StrLine, true))
{
CurrentLine++;
const TCHAR* Str = *StrLine;
bool bProcess = CommentDim <= 0; // for skipping nested multi-line comments
int32 BraceCount = 0;
if( bProcess && FParse::Command(&Str,TEXT("#if")) )
{
}
else if ( bProcess && FParse::Command(&Str,TEXT("#include")) )
{
if (bFoundGeneratedInclude)
{
FError::Throwf(TEXT("#include found after .generated.h file - the .generated.h file should always be the last #include in a header"));
}
// Handle #include directives as if they were 'dependson' keywords.
FString DependsOnHeaderName = Str;
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.
DependentOn.Add(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('/', 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 = StrLine.Left(Pos);
}
if (StrLine == TEXT(""))
{
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;
}
else
{
StrLine = StrLine.Left(Pos);
CommentDim++;
}
}
bProcess = CommentDim <= 1;
}
if (EndPos >= 0)
{
if (StrBegin == INDEX_NONE || EndPos < StrBegin || EndPos > StrEnd)
{
StrLine = StrLine.Mid(EndPos + 2);
CommentDim--;
}
bProcess = CommentDim <= 0;
}
}
}
StrLine.Trim();
if (!bProcess || StrLine == TEXT(""))
{
continue;
}
Str = *StrLine;
// Get class or interface name
if (const TCHAR* UInterfaceMacroDecl = FCString::Strfind(Str, TEXT("UINTERFACE(")))
{
FName StrippedInterfaceName;
Parser.ParseClassDeclaration(Filename, StartOfLine + (UInterfaceMacroDecl - Str), CurrentLine, TEXT("UINTERFACE"), /*out*/ StrippedInterfaceName, /*out*/ ClassName, /*out*/ BaseClassName, /*out*/ DependentOn, OutParsedClassArray);
OutParsedClassArray.Add(FSimplifiedParsingClassInfo(MoveTemp(ClassName), MoveTemp(BaseClassName), CurrentLine, true));
if (!bFoundExportedClasses)
{
if (const TSharedRef<FClassDeclarationMetaData>* Found = GClassDeclarations.Find(StrippedInterfaceName))
{
bFoundExportedClasses = !((*Found)->ClassFlags & CLASS_NoExport);
}
}
}
if (const TCHAR* UClassMacroDecl = FCString::Strfind(Str, TEXT("UCLASS(")))
{
FName StrippedClassName;
Parser.ParseClassDeclaration(Filename, StartOfLine + (UClassMacroDecl - Str), CurrentLine, TEXT("UCLASS"), /*out*/ StrippedClassName, /*out*/ ClassName, /*out*/ BaseClassName, /*out*/ DependentOn, OutParsedClassArray);
OutParsedClassArray.Add(FSimplifiedParsingClassInfo(MoveTemp(ClassName), MoveTemp(BaseClassName), CurrentLine, false));
if (!bFoundExportedClasses)
{
if (const TSharedRef<FClassDeclarationMetaData>* Found = GClassDeclarations.Find(StrippedClassName))
{
bFoundExportedClasses = !((*Found)->ClassFlags & CLASS_NoExport);
}
}
}
}
StartOfLine = Buffer;
}
if (bFoundExportedClasses && !bFoundGeneratedInclude)
{
FError::Throwf(TEXT("No #include found for the .generated.h file - the .generated.h file should always be the last #include in a header"));
}
}
/////////////////////////////////////////////////////
// FHeaderPreParser
void FHeaderPreParser::ParseClassDeclaration(const TCHAR* Filename, const TCHAR* InputText, int32 InLineNumber, const TCHAR* StartingMatchID, FName& out_StrippedClassName, FString& out_ClassName, FString& out_BaseClassName, TArray<FHeaderProvider>& out_RequiredIncludes, const TArray<FSimplifiedParsingClassInfo>& ParsedClassArray)
{
FString ErrorMsg = TEXT("Class declaration");
ResetParser(InputText, InLineNumber);
// Require 'UCLASS' or 'UINTERFACE'
RequireIdentifier(StartingMatchID, *ErrorMsg);
// New-style UCLASS() syntax
TMap<FName, FString> MetaData;
TArray<FPropertySpecifier> SpecifiersFound;
ReadSpecifierSetInsideMacro(SpecifiersFound, ErrorMsg, MetaData);
// Require 'class'
RequireIdentifier(TEXT("class"), *ErrorMsg);
// Read the class name
FString RequiredAPIMacroIfPresent;
ParseNameWithPotentialAPIMacroPrefix(/*out*/ out_ClassName, /*out*/ RequiredAPIMacroIfPresent, StartingMatchID);
FString ClassNameWithoutPrefixStr = GetClassNameWithPrefixRemoved(out_ClassName);
out_StrippedClassName = *ClassNameWithoutPrefixStr;
TSharedRef<FClassDeclarationMetaData>* DeclarationDataPtr = GClassDeclarations.Find(out_StrippedClassName);
if (!DeclarationDataPtr)
{
// Add class declaration meta data so that we can access class flags before the class is fully parsed
TSharedRef<FClassDeclarationMetaData> DeclarationData = MakeShareable(new FClassDeclarationMetaData());
DeclarationData->MetaData = MetaData;
DeclarationData->ParseClassProperties(SpecifiersFound, RequiredAPIMacroIfPresent);
GClassDeclarations.Add(out_StrippedClassName, DeclarationData);
}
// Skip optional final keyword
MatchIdentifier(TEXT("final"));
// Handle inheritance
if (MatchSymbol(TEXT(":")))
{
// Require 'public'
RequireIdentifier(TEXT("public"), *ErrorMsg);
// Inherits from something
FToken BaseClassNameToken;
if (!GetIdentifier(BaseClassNameToken, true))
{
FError::Throwf(TEXT("Expected a base class name"));
}
out_BaseClassName = BaseClassNameToken.Identifier;
int32 InputLineLocal = InputLine;
auto AddDependencyIfNeeded = [Filename, InputLineLocal, &ParsedClassArray, &out_RequiredIncludes, &out_ClassName, &ClassNameWithoutPrefixStr](const FString& DependencyClassName)
{
if (!ParsedClassArray.ContainsByPredicate([&DependencyClassName](const FSimplifiedParsingClassInfo& Info)
{
return Info.GetClassName() == DependencyClassName;
}))
{
if (out_ClassName == DependencyClassName)
{
FFileLineException::Throwf(Filename, InputLineLocal, TEXT("A class cannot inherit itself"));
}
FString StrippedDependencyName = DependencyClassName.Mid(1);
// Only add a stripped dependency if the stripped name differs from the stripped class name
// otherwise it's probably a class with a different prefix.
if (StrippedDependencyName != ClassNameWithoutPrefixStr)
{
out_RequiredIncludes.Add(FHeaderProvider(EHeaderProviderSourceType::ClassName, MoveTemp(StrippedDependencyName)));
}
}
};
AddDependencyIfNeeded(out_BaseClassName);
// Get additional inheritance links and rack them up as dependencies if they're UObject derived
while (MatchSymbol(TEXT(",")))
{
// Require 'public'
RequireIdentifier(TEXT("public"), *ErrorMsg);
FToken InterfaceClassNameToken;
if (!GetIdentifier(InterfaceClassNameToken, true))
{
FFileLineException::Throwf(Filename, InputLine, TEXT("Expected an interface class name"));
}
AddDependencyIfNeeded(FString(InterfaceClassNameToken.Identifier));
}
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool FHeaderParser::DefaultValueStringCppFormatToInnerFormat(const UProperty* Property, const FString& CppForm, FString &OutForm)
{
OutForm = FString();
if (!Property || CppForm.IsEmpty())
{
return false;
}
if (Property->IsA(UClassProperty::StaticClass()) || Property->IsA(UObjectPropertyBase::StaticClass()))
{
return FDefaultValueHelper::Is(CppForm, TEXT("NULL")) || FDefaultValueHelper::Is(CppForm, TEXT("nullptr")) || FDefaultValueHelper::Is(CppForm, TEXT("0"));
}
if( !Property->IsA(UStructProperty::StaticClass()) )
{
if( Property->IsA(UIntProperty::StaticClass()) )
{
int32 Value;
if( FDefaultValueHelper::ParseInt( CppForm, Value) )
{
OutForm = FString::FromInt(Value);
}
}
else if( Property->IsA(UByteProperty::StaticClass()) )
{
const UEnum* Enum = CastChecked<UByteProperty>(Property)->Enum;
if( NULL != Enum )
{
OutForm = FDefaultValueHelper::GetUnqualifiedEnumValue(FDefaultValueHelper::RemoveWhitespaces(CppForm));
return ( INDEX_NONE != Enum->GetIndexByName(*OutForm) );
}
int32 Value;
if( FDefaultValueHelper::ParseInt( CppForm, Value) )
{
OutForm = FString::FromInt(Value);
return ( 0 <= Value ) && ( 255 >= Value );
}
}
else if( Property->IsA(UEnumProperty::StaticClass()) )
{
const UEnumProperty* EnumProp = CastChecked<UEnumProperty>(Property);
if (const UEnum* Enum = CastChecked<UEnumProperty>(Property)->GetEnum())
{
OutForm = FDefaultValueHelper::GetUnqualifiedEnumValue(FDefaultValueHelper::RemoveWhitespaces(CppForm));
return Enum->GetIndexByName(*OutForm) != INDEX_NONE;
}
int64 Value;
if (FDefaultValueHelper::ParseInt64(CppForm, Value))
{
OutForm = Lex::ToString(Value);
return EnumProp->GetUnderlyingProperty()->CanHoldValue(Value);
}
}
else if( Property->IsA(UFloatProperty::StaticClass()) )
{
float Value;
if( FDefaultValueHelper::ParseFloat( CppForm, Value) )
{
OutForm = FString::Printf( TEXT("%f"), Value) ;
}
}
else if( Property->IsA(UDoubleProperty::StaticClass()) )
{
double Value;
if( FDefaultValueHelper::ParseDouble( CppForm, Value) )
{
OutForm = FString::Printf( TEXT("%f"), Value) ;
}
}
else if( Property->IsA(UBoolProperty::StaticClass()) )
{
if( FDefaultValueHelper::Is(CppForm, TEXT("true")) ||
FDefaultValueHelper::Is(CppForm, TEXT("false")) )
{
OutForm = FDefaultValueHelper::RemoveWhitespaces( CppForm );
}
}
else if( Property->IsA(UNameProperty::StaticClass()) )
{
if(FDefaultValueHelper::Is( CppForm, TEXT("NAME_None") ))
{
OutForm = TEXT("None");
return true;
}
return FDefaultValueHelper::StringFromCppString(CppForm, TEXT("FName"), OutForm);
}
else if( Property->IsA(UTextProperty::StaticClass()) )
{
return FDefaultValueHelper::StringFromCppString(CppForm, TEXT("FText"), OutForm);
}
else if( Property->IsA(UStrProperty::StaticClass()) )
{
return FDefaultValueHelper::StringFromCppString(CppForm, TEXT("FString"), OutForm);
}
}
else
{
// Cache off the struct types, in case we need them later
UPackage* CoreUObjectPackage = UObject::StaticClass()->GetOutermost();
static const UScriptStruct* VectorStruct = FindObjectChecked<UScriptStruct>(CoreUObjectPackage, TEXT("Vector"));
static const UScriptStruct* Vector2DStruct = FindObjectChecked<UScriptStruct>(CoreUObjectPackage, TEXT("Vector2D"));
static const UScriptStruct* RotatorStruct = FindObjectChecked<UScriptStruct>(CoreUObjectPackage, TEXT("Rotator"));
static const UScriptStruct* LinearColorStruct = FindObjectChecked<UScriptStruct>(CoreUObjectPackage, TEXT("LinearColor"));
static const UScriptStruct* ColorStruct = FindObjectChecked<UScriptStruct>(CoreUObjectPackage, TEXT("Color"));
const UStructProperty* StructProperty = CastChecked<UStructProperty>(Property);
if( StructProperty->Struct == VectorStruct )
{
FString Parameters;
if(FDefaultValueHelper::Is( CppForm, TEXT("FVector::ZeroVector") ))
{
return true;
}
else if(FDefaultValueHelper::Is(CppForm, TEXT("FVector::UpVector")))
{
OutForm = FString::Printf(TEXT("%f,%f,%f"),
FVector::UpVector.X, FVector::UpVector.Y, FVector::UpVector.Z);
}
else if(FDefaultValueHelper::Is(CppForm, TEXT("FVector::ForwardVector")))
{
OutForm = FString::Printf(TEXT("%f,%f,%f"),
FVector::ForwardVector.X, FVector::ForwardVector.Y, FVector::ForwardVector.Z);
}
else if(FDefaultValueHelper::Is(CppForm, TEXT("FVector::RightVector")))
{
OutForm = FString::Printf(TEXT("%f,%f,%f"),
FVector::RightVector.X, FVector::RightVector.Y, FVector::RightVector.Z);
}
else if( FDefaultValueHelper::GetParameters(CppForm, TEXT("FVector"), Parameters) )
{
if( FDefaultValueHelper::Is(Parameters, TEXT("ForceInit")) )
{
return true;
}
FVector Vector;
float Value;
if (FDefaultValueHelper::ParseVector(Parameters, Vector))
{
OutForm = FString::Printf(TEXT("%f,%f,%f"),
Vector.X, Vector.Y, Vector.Z);
}
else if (FDefaultValueHelper::ParseFloat(Parameters, Value))
{
OutForm = FString::Printf(TEXT("%f,%f,%f"),
Value, Value, Value);
}
}
}
else if( StructProperty->Struct == RotatorStruct )
{
if(FDefaultValueHelper::Is( CppForm, TEXT("FRotator::ZeroRotator") ))
{
return true;
}
FString Parameters;
if( FDefaultValueHelper::GetParameters(CppForm, TEXT("FRotator"), Parameters) )
{
if( FDefaultValueHelper::Is(Parameters, TEXT("ForceInit")) )
{
return true;
}
FRotator Rotator;
if(FDefaultValueHelper::ParseRotator(Parameters, Rotator))
{
OutForm = FString::Printf(TEXT("%f,%f,%f"),
Rotator.Pitch, Rotator.Yaw, Rotator.Roll);
}
}
}
else if( StructProperty->Struct == Vector2DStruct )
{
if(FDefaultValueHelper::Is( CppForm, TEXT("FVector2D::ZeroVector") ))
{
return true;
}
if(FDefaultValueHelper::Is(CppForm, TEXT("FVector2D::UnitVector")))
{
OutForm = FString::Printf(TEXT("(X=%3.3f,Y=%3.3f)"),
FVector2D::UnitVector.X, FVector2D::UnitVector.Y);
}
FString Parameters;
if( FDefaultValueHelper::GetParameters(CppForm, TEXT("FVector2D"), Parameters) )
{
if( FDefaultValueHelper::Is(Parameters, TEXT("ForceInit")) )
{
return true;
}
FVector2D Vector2D;
if(FDefaultValueHelper::ParseVector2D(Parameters, Vector2D))
{
OutForm = FString::Printf(TEXT("(X=%3.3f,Y=%3.3f)"),
Vector2D.X, Vector2D.Y);
}
}
}
else if( StructProperty->Struct == LinearColorStruct )
{
if( FDefaultValueHelper::Is( CppForm, TEXT("FLinearColor::White") ) )
{
OutForm = FLinearColor::White.ToString();
}
else if ( FDefaultValueHelper::Is( CppForm, TEXT("FLinearColor::Gray") ) )
{
OutForm = FLinearColor::Gray.ToString();
}
else if ( FDefaultValueHelper::Is( CppForm, TEXT("FLinearColor::Black") ) )
{
OutForm = FLinearColor::Black.ToString();
}
else if ( FDefaultValueHelper::Is( CppForm, TEXT("FLinearColor::Transparent") ) )
{
OutForm = FLinearColor::Transparent.ToString();
}
else if ( FDefaultValueHelper::Is( CppForm, TEXT("FLinearColor::Red") ) )
{
OutForm = FLinearColor::Red.ToString();
}
else if ( FDefaultValueHelper::Is( CppForm, TEXT("FLinearColor::Green") ) )
{
OutForm = FLinearColor::Green.ToString();
}
else if ( FDefaultValueHelper::Is( CppForm, TEXT("FLinearColor::Blue") ) )
{
OutForm = FLinearColor::Blue.ToString();
}
else if ( FDefaultValueHelper::Is( CppForm, TEXT("FLinearColor::Yellow") ) )
{
OutForm = FLinearColor::Yellow.ToString();
}
else
{
FString Parameters;
if( FDefaultValueHelper::GetParameters(CppForm, TEXT("FLinearColor"), Parameters) )
{
if( FDefaultValueHelper::Is(Parameters, TEXT("ForceInit")) )
{
return true;
}
FLinearColor Color;
if( FDefaultValueHelper::ParseLinearColor(Parameters, Color) )
{
OutForm = Color.ToString();
}
}
}
}
else if( StructProperty->Struct == ColorStruct )
{
if( FDefaultValueHelper::Is( CppForm, TEXT("FColor::White") ) )
{
OutForm = FColor::White.ToString();
}
else if ( FDefaultValueHelper::Is( CppForm, TEXT("FColor::Black") ) )
{
OutForm = FColor::Black.ToString();
}
else if ( FDefaultValueHelper::Is( CppForm, TEXT("FColor::Red") ) )
{
OutForm = FColor::Red.ToString();
}
else if ( FDefaultValueHelper::Is( CppForm, TEXT("FColor::Green") ) )
{
OutForm = FColor::Green.ToString();
}
else if ( FDefaultValueHelper::Is( CppForm, TEXT("FColor::Blue") ) )
{
OutForm = FColor::Blue.ToString();
}
else if (FDefaultValueHelper::Is(CppForm, TEXT("FColor::Yellow")))
{
OutForm = FColor::Yellow.ToString();
}
else if ( FDefaultValueHelper::Is( CppForm, TEXT("FColor::Cyan") ) )
{
OutForm = FColor::Cyan.ToString();
}
else if ( FDefaultValueHelper::Is( CppForm, TEXT("FColor::Magenta") ) )
{
OutForm = FColor::Magenta.ToString();
}
else
{
FString Parameters;
if( FDefaultValueHelper::GetParameters(CppForm, TEXT("FColor"), Parameters) )
{
if( FDefaultValueHelper::Is(Parameters, TEXT("ForceInit")) )
{
return true;
}
FColor Color;
if( FDefaultValueHelper::ParseColor(Parameters, Color) )
{
OutForm = Color.ToString();
}
}
}
}
}
return !OutForm.IsEmpty();
}
bool FHeaderParser::TryToMatchConstructorParameterList(FToken Token)
{
FToken PotentialParenthesisToken;
if (!GetToken(PotentialParenthesisToken))
{
return false;
}
if (!PotentialParenthesisToken.Matches(TEXT("(")))
{
UngetToken(PotentialParenthesisToken);
return false;
}
FClassMetaData* ClassData = GScriptHelper.FindClassData(GetCurrentClass());
check(ClassData);
bool bOICtor = false;
bool bVTCtor = false;
if (!ClassData->bDefaultConstructorDeclared && MatchSymbol(TEXT(")")))
{
ClassData->bDefaultConstructorDeclared = true;
}
else if (!ClassData->bObjectInitializerConstructorDeclared
|| !ClassData->bCustomVTableHelperConstructorDeclared
)
{
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.Matches(TEXT(",")) || ObjectInitializerParamParsingToken.Matches(TEXT("<")))
{
bOICtor = false;
bVTCtor = false;
break;
}
if (ObjectInitializerParamParsingToken.Matches(TEXT("(")))
{
ParenthesesNestingLevel++;
continue;
}
if (ObjectInitializerParamParsingToken.Matches(TEXT(")")))
{
ParenthesesNestingLevel--;
continue;
}
if (ObjectInitializerParamParsingToken.Matches(TEXT("const")))
{
bIsConst = true;
continue;
}
if (ObjectInitializerParamParsingToken.Matches(TEXT("&")))
{
bIsRef = true;
continue;
}
if (ObjectInitializerParamParsingToken.Matches(TEXT("FObjectInitializer"))
|| ObjectInitializerParamParsingToken.Matches(TEXT("FPostConstructInitializeProperties")) // Deprecated, but left here, so it won't break legacy code.
)
{
bOICtor = true;
}
if (ObjectInitializerParamParsingToken.Matches(TEXT("FVTableHelper")))
{
bVTCtor = true;
}
}
// Parse until finish.
while (ParenthesesNestingLevel && GetToken(ObjectInitializerParamParsingToken))
{
if (ObjectInitializerParamParsingToken.Matches(TEXT("(")))
{
ParenthesesNestingLevel++;
continue;
}
if (ObjectInitializerParamParsingToken.Matches(TEXT(")")))
{
ParenthesesNestingLevel--;
continue;
}
}
ClassData->bObjectInitializerConstructorDeclared = ClassData->bObjectInitializerConstructorDeclared || (bOICtor && bIsRef && bIsConst);
ClassData->bCustomVTableHelperConstructorDeclared = ClassData->bCustomVTableHelperConstructorDeclared || (bVTCtor && bIsRef);
}
ClassData->bConstructorDeclared = ClassData->bConstructorDeclared || !bVTCtor;
// 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::SkipDeprecatedMacroIfNecessary()
{
if (!MatchIdentifier(TEXT("DEPRECATED")))
{
return;
}
FToken Token;
// DEPRECATED(Version, "Message")
RequireSymbol(TEXT("("), TEXT("DEPRECATED macro"));
if (GetToken(Token) && (Token.Type != CPT_Float || Token.TokenType != TOKEN_Const))
{
FError::Throwf(TEXT("Expected engine version in DEPRECATED macro"));
}
RequireSymbol(TEXT(","), TEXT("DEPRECATED macro"));
if (GetToken(Token) && (Token.Type != CPT_String || Token.TokenType != TOKEN_Const))
{
FError::Throwf(TEXT("Expected deprecation message in DEPRECATED macro"));
}
RequireSymbol(TEXT(")"), TEXT("DEPRECATED macro"));
}
void FHeaderParser::CompileVersionDeclaration(UStruct* Struct)
{
FUnrealSourceFile* CurrentSourceFilePtr = GetCurrentSourceFile();
TSharedRef<FUnrealSourceFile> CurrentSrcFile = CurrentSourceFilePtr->AsShared();
// 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 = DefaultGeneratedCodeVersion;
// Overwrite with module-specific value if one was specified.
if (CurrentlyParsedModule->GeneratedCodeVersion != EGeneratedCodeVersion::None)
{
Version = CurrentlyParsedModule->GeneratedCodeVersion;
}
if (Token.TokenType == ETokenType::TOKEN_Symbol
&& !FCString::Stricmp(Token.Identifier, TEXT(")")))
{
CurrentSrcFile->GetGeneratedCodeVersions().FindOrAdd(Struct) = Version;
UngetToken(Token);
return;
}
// Overwrite with version specified by macro.
Version = ToGeneratedCodeVersion(Token.Identifier);
CurrentSrcFile->GetGeneratedCodeVersions().FindOrAdd(Struct) = Version;
}
void FHeaderParser::ResetClassData()
{
UClass* CurrentClass = GetCurrentClass();
CurrentClass->PropertiesSize = 0;
// Set class flags and within.
CurrentClass->ClassFlags &= ~CLASS_RecompilerClear;
UClass* SuperClass = CurrentClass->GetSuperClass();
if (SuperClass != NULL)
{
CurrentClass->ClassFlags |= (SuperClass->ClassFlags) & CLASS_ScriptInherit;
CurrentClass->ClassConfigName = SuperClass->ClassConfigName;
check(SuperClass->ClassWithin);
if (CurrentClass->ClassWithin == NULL)
{
CurrentClass->ClassWithin = SuperClass->ClassWithin;
}
// Copy special categories from parent
if (SuperClass->HasMetaData(TEXT("HideCategories")))
{
CurrentClass->SetMetaData(TEXT("HideCategories"), *SuperClass->GetMetaData("HideCategories"));
}
if (SuperClass->HasMetaData(TEXT("ShowCategories")))
{
CurrentClass->SetMetaData(TEXT("ShowCategories"), *SuperClass->GetMetaData("ShowCategories"));
}
if (SuperClass->HasMetaData(TEXT("HideFunctions")))
{
CurrentClass->SetMetaData(TEXT("HideFunctions"), *SuperClass->GetMetaData("HideFunctions"));
}
if (SuperClass->HasMetaData(TEXT("AutoExpandCategories")))
{
CurrentClass->SetMetaData(TEXT("AutoExpandCategories"), *SuperClass->GetMetaData("AutoExpandCategories"));
}
if (SuperClass->HasMetaData(TEXT("AutoCollapseCategories")))
{
CurrentClass->SetMetaData(TEXT("AutoCollapseCategories"), *SuperClass->GetMetaData("AutoCollapseCategories"));
}
}
check(CurrentClass->ClassWithin);
}
void FHeaderParser::PostPopNestClass(UClass* CurrentClass)
{
// Validate all the rep notify events here, to make sure they're implemented
VerifyRepNotifyCallbacks(CurrentClass);
// Iterate over all the interfaces we claim to implement
for (FImplementedInterface& Impl : CurrentClass->Interfaces)
{
// And their super-classes
for (UClass* Interface = Impl.Class; Interface; Interface = Interface->GetSuperClass())
{
// If this interface is a common ancestor, skip it
if (CurrentClass->IsChildOf(Interface))
continue;
// So iterate over all functions this interface declares
for (UFunction* InterfaceFunction : TFieldRange<UFunction>(Interface, EFieldIteratorFlags::ExcludeSuper))
{
bool Implemented = false;
// And try to find one that matches
for (UFunction* ClassFunction : TFieldRange<UFunction>(CurrentClass))
{
if (ClassFunction->GetFName() != InterfaceFunction->GetFName())
continue;
if ((InterfaceFunction->FunctionFlags & FUNC_Event) && !(ClassFunction->FunctionFlags & FUNC_Event))
FError::Throwf(TEXT("Implementation of function '%s' must be declared as 'event' to match declaration in interface '%s'"), *ClassFunction->GetName(), *Interface->GetName());
if ((InterfaceFunction->FunctionFlags & FUNC_Delegate) && !(ClassFunction->FunctionFlags & FUNC_Delegate))
FError::Throwf(TEXT("Implementation of function '%s' must be declared as 'delegate' to match declaration in interface '%s'"), *ClassFunction->GetName(), *Interface->GetName());
// Making sure all the parameters match up correctly
Implemented = true;
if (ClassFunction->NumParms != InterfaceFunction->NumParms)
FError::Throwf(TEXT("Implementation of function '%s' conflicts with interface '%s' - different number of parameters (%i/%i)"), *InterfaceFunction->GetName(), *Interface->GetName(), ClassFunction->NumParms, InterfaceFunction->NumParms);
int32 Count = 0;
for (TFieldIterator<UProperty> It1(InterfaceFunction), It2(ClassFunction); Count < ClassFunction->NumParms; ++It1, ++It2, Count++)
{
if (!FPropertyBase(*It1).MatchesType(FPropertyBase(*It2), 1))
{
if (It1->PropertyFlags & CPF_ReturnParm)
{
FError::Throwf(TEXT("Implementation of function '%s' conflicts only by return type with interface '%s'"), *InterfaceFunction->GetName(), *Interface->GetName());
}
else
{
FError::Throwf(TEXT("Implementation of function '%s' conflicts with interface '%s' - parameter %i '%s'"), *InterfaceFunction->GetName(), *Interface->GetName(), Count, *It1->GetName());
}
}
}
}
// Delegate signature functions are simple stubs and aren't required to be implemented (they are not callable)
if (InterfaceFunction->FunctionFlags & FUNC_Delegate)
{
Implemented = true;
}
// Verify that if this has blueprint-callable functions that are not implementable events, we've implemented them as a UFunction in the target class
if (!Implemented
&& !Interface->HasMetaData(TEXT("CannotImplementInterfaceInBlueprint")) // FBlueprintMetadata::MD_CannotImplementInterfaceInBlueprint
&& InterfaceFunction->HasAnyFunctionFlags(FUNC_BlueprintCallable)
&& !InterfaceFunction->HasAnyFunctionFlags(FUNC_BlueprintEvent))
{
FError::Throwf(TEXT("Missing UFunction implementation of function '%s' from interface '%s'. This function needs a UFUNCTION() declaration."), *InterfaceFunction->GetName(), *Interface->GetName());
}
}
}
}
}
void FHeaderParser::PostPopFunctionDeclaration(FClasses& AllClasses, UFunction* PoppedFunction)
{
//@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() && GetCurrentClassData()->ContainsDelegates())
{
// now validate all delegate variables declared in the class
TMap<FName, UFunction*> DelegateCache;
FixupDelegateProperties(AllClasses, PoppedFunction, *GetCurrentScope(), DelegateCache);
}
}
void FHeaderParser::PostPopNestInterface(FClasses& AllClasses, UClass* CurrentInterface)
{
FClassMetaData* ClassData = GScriptHelper.FindClassData(CurrentInterface);
check(ClassData);
if (ClassData->ContainsDelegates())
{
TMap<FName, UFunction*> DelegateCache;
FixupDelegateProperties(AllClasses, CurrentInterface, FScope::GetTypeScope(ExactCast<UClass>(CurrentInterface)).Get(), DelegateCache);
}
}
template <class TFunctionType>
TFunctionType* CreateFunctionImpl(const FFuncInfo& FuncInfo, UObject* Outer, FScope* CurrentScope)
{
// Allocate local property frame, push nesting level and verify
// uniqueness at this scope level.
{
auto TypeIterator = CurrentScope->GetTypeIterator();
while (TypeIterator.MoveNext())
{
UField* Type = *TypeIterator;
if (Type->GetFName() == FuncInfo.Function.Identifier)
{
FError::Throwf(TEXT("'%s' conflicts with '%s'"), FuncInfo.Function.Identifier, *Type->GetFullName());
}
}
}
TFunctionType* Function = new(EC_InternalUseOnlyConstructor, Outer, FuncInfo.Function.Identifier, RF_Public) TFunctionType(FObjectInitializer(), nullptr);
Function->RepOffset = MAX_uint16;
Function->ReturnValueOffset = MAX_uint16;
Function->FirstPropertyToInit = nullptr;
if (!CurrentScope->IsFileScope())
{
UStruct* Struct = ((FStructScope*)CurrentScope)->GetStruct();
Function->Next = Struct->Children;
Struct->Children = Function;
}
return Function;
}
UFunction* FHeaderParser::CreateFunction(const FFuncInfo &FuncInfo) const
{
return CreateFunctionImpl<UFunction>(FuncInfo, GetCurrentClass(), GetCurrentScope());
}
UDelegateFunction* FHeaderParser::CreateDelegateFunction(const FFuncInfo &FuncInfo) const
{
FFileScope* CurrentFileScope = GetCurrentFileScope();
FUnrealSourceFile* LocSourceFile = CurrentFileScope ? CurrentFileScope->GetSourceFile() : nullptr;
UObject* CurrentPackage = LocSourceFile ? LocSourceFile->GetPackage() : nullptr;
return CreateFunctionImpl<UDelegateFunction>(FuncInfo, IsInAClass() ? (UObject*)GetCurrentClass() : CurrentPackage, GetCurrentScope());
}