Files
UnrealEngineUWP/Engine/Source/Programs/UnrealHeaderTool/Private/ClassDeclarationMetaData.cpp
Robert Manuszewski d80262a45e Fixing UHT not propagating class flags/properties properly
- Small refactor of how and where class flags are parsed
- 'Placeable' is no longer deprecated (it never really was)
- Restoring 'Placeable' keyword on ALandscape

UE-14035

[CL 2518912 by Robert Manuszewski in Main branch]
2015-04-21 05:03:44 -04:00

512 lines
16 KiB
C++

// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
#include "UnrealHeaderTool.h"
#include "ClassDeclarationMetaData.h"
#include "ParserClass.h"
#include "HeaderParser.h"
#include "Classes.h"
// Utility functions
namespace
{
bool IsActorClass(UClass *Class)
{
static FName ActorName = FName(TEXT("Actor"));
while (Class)
{
if (Class->GetFName() == ActorName)
{
return true;
}
Class = Class->GetSuperClass();
}
return false;
}
}
FClassDeclarationMetaData::FClassDeclarationMetaData()
: ClassFlags(0)
, WantsToBePlaceable(false)
{
}
void FClassDeclarationMetaData::ParseClassProperties(const TArray<FPropertySpecifier>& InClassSpecifiers, const FString& InRequiredAPIMacroIfPresent)
{
ClassFlags = 0;
// Record that this class is RequiredAPI if the CORE_API style macro was present
if (!InRequiredAPIMacroIfPresent.IsEmpty())
{
ClassFlags |= CLASS_RequiredAPI;
}
ClassFlags |= CLASS_Native;
// Process all of the class specifiers
bool bWithinSpecified = false;
bool bDeclaresConfigFile = false;
for (const FPropertySpecifier& PropSpecifier : InClassSpecifiers)
{
const FString& Specifier = PropSpecifier.Key;
if (Specifier == TEXT("noexport"))
{
// Don't export to C++ header.
ClassFlags |= CLASS_NoExport;
}
else if (Specifier == TEXT("intrinsic"))
{
ClassFlags |= CLASS_Intrinsic;
}
else if (Specifier == TEXT("ComponentWrapperClass"))
{
MetaData.Add(TEXT("IgnoreCategoryKeywordsInSubclasses"), TEXT("true"));
}
else if (Specifier == TEXT("within"))
{
ClassWithin = FHeaderParser::RequireExactlyOneSpecifierValue(PropSpecifier);
}
else if (Specifier == TEXT("editinlinenew"))
{
// Class can be constructed from the New button in editinline
ClassFlags |= CLASS_EditInlineNew;
}
else if (Specifier == TEXT("noteditinlinenew"))
{
// Class cannot be constructed from the New button in editinline
ClassFlags &= ~CLASS_EditInlineNew;
}
else if (Specifier == TEXT("placeable"))
{
WantsToBePlaceable = true;
ClassFlags &= ~CLASS_NotPlaceable;
}
else if (Specifier == TEXT("defaulttoinstanced"))
{
// these classed default to instanced.
ClassFlags |= CLASS_DefaultToInstanced;
}
else if (Specifier == TEXT("notplaceable"))
{
// Don't allow the class to be placed in the editor.
ClassFlags |= CLASS_NotPlaceable;
}
else if (Specifier == TEXT("hidedropdown"))
{
// Prevents class from appearing in class comboboxes in the property window
ClassFlags |= CLASS_HideDropDown;
}
else if (Specifier == TEXT("dependsOn"))
{
FError::Throwf(TEXT("The dependsOn specifier is deprecated. Please use #include \"ClassHeaderFilename.h\" instead."));
}
else if (Specifier == TEXT("MinimalAPI"))
{
ClassFlags |= CLASS_MinimalAPI;
}
else if (Specifier == TEXT("const"))
{
ClassFlags |= CLASS_Const;
}
else if (Specifier == TEXT("perObjectConfig"))
{
ClassFlags |= CLASS_PerObjectConfig;
}
else if (Specifier == TEXT("configdonotcheckdefaults"))
{
ClassFlags |= CLASS_ConfigDoNotCheckDefaults;
}
else if (Specifier == TEXT("abstract"))
{
// Hide all editable properties.
ClassFlags |= CLASS_Abstract;
}
else if (Specifier == TEXT("deprecated"))
{
ClassFlags |= CLASS_Deprecated;
// Don't allow the class to be placed in the editor.
ClassFlags |= CLASS_NotPlaceable;
}
else if (Specifier == TEXT("transient"))
{
// Transient class.
ClassFlags |= CLASS_Transient;
}
else if (Specifier == TEXT("nonTransient"))
{
// this child of a transient class is not transient - remove the transient flag
ClassFlags &= ~CLASS_Transient;
}
else if (Specifier == TEXT("customConstructor"))
{
// we will not export a constructor for this class, assuming it is in the CPP block
ClassFlags |= CLASS_CustomConstructor;
}
else if (Specifier == TEXT("config"))
{
// Class containing config properties - parse the name of the config file to use
ConfigName = FHeaderParser::RequireExactlyOneSpecifierValue(PropSpecifier);
}
else if (Specifier == TEXT("defaultconfig"))
{
// Save object config only to Default INIs, never to local INIs.
ClassFlags |= CLASS_DefaultConfig;
}
else if (Specifier == TEXT("globaluserconfig"))
{
// Save object config only to global user overrides, never to local INIs
ClassFlags |= CLASS_GlobalUserConfig;
}
else if (Specifier == TEXT("showCategories"))
{
FHeaderParser::RequireSpecifierValue(PropSpecifier);
for (const FString& Value : PropSpecifier.Values)
{
// if we didn't find this specific category path in the HideCategories metadata
if (HideCategories.Remove(Value) == 0)
{
TArray<FString> SubCategoryList;
Value.ParseIntoArray(SubCategoryList, TEXT("|"), true);
FString SubCategoryPath;
// look to see if any of the parent paths are excluded in the HideCategories list
for (int32 CategoryPathIndex = 0; CategoryPathIndex < SubCategoryList.Num() - 1; ++CategoryPathIndex)
{
SubCategoryPath += SubCategoryList[CategoryPathIndex];
// if we're hiding a parent category, then we need to flag this sub category for show
if (HideCategories.Contains(SubCategoryPath))
{
ShowSubCatgories.AddUnique(Value);
break;
}
SubCategoryPath += "|";
}
}
}
}
else if (Specifier == TEXT("hideCategories"))
{
FHeaderParser::RequireSpecifierValue(PropSpecifier);
for (const FString& Value : PropSpecifier.Values)
{
HideCategories.AddUnique(Value);
}
}
else if (Specifier == TEXT("showFunctions"))
{
FHeaderParser::RequireSpecifierValue(PropSpecifier);
for (const FString& Value : PropSpecifier.Values)
{
HideFunctions.Remove(Value);
}
}
else if (Specifier == TEXT("hideFunctions"))
{
FHeaderParser::RequireSpecifierValue(PropSpecifier);
for (const FString& Value : PropSpecifier.Values)
{
HideFunctions.AddUnique(Value);
}
}
else if (Specifier == TEXT("classGroup"))
{
FHeaderParser::RequireSpecifierValue(PropSpecifier);
for (const FString& Value : PropSpecifier.Values)
{
ClassGroupNames.Add(Value);
}
}
else if (Specifier == TEXT("autoExpandCategories"))
{
FHeaderParser::RequireSpecifierValue(PropSpecifier);
for (const FString& Value : PropSpecifier.Values)
{
AutoCollapseCategories.Remove(Value);
AutoExpandCategories.AddUnique(Value);
}
}
else if (Specifier == TEXT("autoCollapseCategories"))
{
FHeaderParser::RequireSpecifierValue(PropSpecifier);
for (const FString& Value : PropSpecifier.Values)
{
AutoExpandCategories.Remove(Value);
AutoCollapseCategories.AddUnique(Value);
}
}
else if (Specifier == TEXT("dontAutoCollapseCategories"))
{
FHeaderParser::RequireSpecifierValue(PropSpecifier);
for (const FString& Value : PropSpecifier.Values)
{
AutoCollapseCategories.Remove(Value);
}
}
else if (Specifier == TEXT("collapseCategories"))
{
// Class' properties should not be shown categorized in the editor.
ClassFlags |= CLASS_CollapseCategories;
}
else if (Specifier == TEXT("dontCollapseCategories"))
{
// Class' properties should be shown categorized in the editor.
ClassFlags &= ~CLASS_CollapseCategories;
}
else if (Specifier == TEXT("AdvancedClassDisplay"))
{
// By default the class properties are shown in advanced sections in UI
ClassFlags |= CLASS_AdvancedDisplay;
}
else if (Specifier == TEXT("ConversionRoot"))
{
MetaData.Add(FName(TEXT("IsConversionRoot")), "true");
}
else
{
FError::Throwf(TEXT("Unknown class specifier '%s'"), *Specifier);
}
}
}
void FClassDeclarationMetaData::MergeShowCategories()
{
for (const FString& Value : ShowCategories)
{
// if we didn't find this specific category path in the HideCategories metadata
if (HideCategories.Remove(Value) == 0)
{
TArray<FString> SubCategoryList;
Value.ParseIntoArray(SubCategoryList, TEXT("|"), true);
FString SubCategoryPath;
// look to see if any of the parent paths are excluded in the HideCategories list
for (int32 CategoryPathIndex = 0; CategoryPathIndex < SubCategoryList.Num() - 1; ++CategoryPathIndex)
{
SubCategoryPath += SubCategoryList[CategoryPathIndex];
// if we're hiding a parent category, then we need to flag this sub category for show
if (HideCategories.Contains(SubCategoryPath))
{
ShowSubCatgories.AddUnique(Value);
break;
}
SubCategoryPath += "|";
}
}
}
// Once the categories have been merged, empty the array as we will no longer need it nor should we use it
ShowCategories.Empty();
}
void FClassDeclarationMetaData::MergeClassCategories(FClass* Class)
{
static FName DEBUG_Name(TEXT("StaticMeshComponent"));
if (Class->GetFName() == DEBUG_Name)
{
static volatile int32 xx = 0;
xx++;
}
TArray<FString> ParentHideCategories;
TArray<FString> ParentShowSubCatgories;
TArray<FString> ParentHideFunctions;
TArray<FString> ParentAutoExpandCategories;
TArray<FString> ParentAutoCollapseCategories;
Class->GetHideCategories(ParentHideCategories);
Class->GetShowCategories(ParentShowSubCatgories);
Class->GetHideFunctions(ParentHideFunctions);
Class->GetAutoExpandCategories(ParentAutoExpandCategories);
Class->GetAutoCollapseCategories(ParentAutoCollapseCategories);
// Add parent categories. We store the opposite of HideCategories and HideFunctions in a separate array anyway.
HideCategories.Append(ParentHideCategories);
ShowSubCatgories.Append(ParentShowSubCatgories);
HideFunctions.Append(ParentHideFunctions);
MergeShowCategories();
// Merge ShowFunctions and HideFunctions
for (const FString& Value : ShowFunctions)
{
HideFunctions.Remove(Value);
}
ShowFunctions.Empty();
// Merge DontAutoCollapseCategories and AutoCollapseCategories
for (const FString& Value : DontAutoCollapseCategories)
{
AutoCollapseCategories.Remove(Value);
}
DontAutoCollapseCategories.Empty();
// Merge ShowFunctions and HideFunctions
for (const FString& Value : ShowFunctions)
{
HideFunctions.Remove(Value);
}
ShowFunctions.Empty();
// Merge AutoExpandCategories and AutoCollapseCategories (we still want to keep AutoExpandCategories though!)
for (const FString& Value : AutoExpandCategories)
{
AutoCollapseCategories.Remove(Value);
ParentAutoCollapseCategories.Remove(Value);
}
// Do the same as above but the other way around
for (const FString& Value : AutoCollapseCategories)
{
AutoExpandCategories.Remove(Value);
ParentAutoExpandCategories.Remove(Value);
}
// Once AutoExpandCategories and AutoCollapseCategories for THIS class have been parsed, add the parent inherited categories
AutoCollapseCategories.Append(ParentAutoCollapseCategories);
AutoExpandCategories.Append(ParentAutoExpandCategories);
}
void FClassDeclarationMetaData::MergeAndValidateClassFlags(const FString& DeclaredClassName, uint32 PreviousClassFlags, FClass* Class, const FClasses& AllClasses)
{
if (WantsToBePlaceable)
{
if (!(Class->ClassFlags & CLASS_NotPlaceable))
{
FError::Throwf(TEXT("The 'placeable' specifier is only allowed on classes which have a base class that's marked as not placeable. Classes are assumed to be placeable by default."));
}
Class->ClassFlags &= ~CLASS_NotPlaceable;
WantsToBePlaceable = false; // Reset this flag after it's been merged
}
// Now merge all remaining flags/properties
Class->ClassFlags |= ClassFlags;
Class->ClassConfigName = FName(*ConfigName);
SetAndValidateWithinClass(Class, AllClasses);
SetAndValidateConfigName(Class);
if (!!(Class->ClassFlags & CLASS_EditInlineNew))
{
// don't allow actor classes to be declared editinlinenew
if (IsActorClass(Class))
{
FError::Throwf(TEXT("Invalid class attribute: Creating actor instances via the property window is not allowed"));
}
}
// Make sure both RequiredAPI and MinimalAPI aren't specified
if (Class->HasAllClassFlags(CLASS_MinimalAPI | CLASS_RequiredAPI))
{
FError::Throwf(TEXT("MinimalAPI cannot be specified when the class is fully exported using a MODULENAME_API macro"));
}
// All classes must start with a valid Unreal prefix
const FString ExpectedClassName = Class->GetNameWithPrefix();
if (DeclaredClassName != ExpectedClassName)
{
FError::Throwf(TEXT("Class name '%s' is invalid, should be identified as '%s'"), *DeclaredClassName, *ExpectedClassName);
}
if ((Class->ClassFlags&CLASS_NoExport))
{
// if the class's class flags didn't contain CLASS_NoExport before it was parsed, it means either:
// a) the DECLARE_CLASS macro for this native class doesn't contain the CLASS_NoExport flag (this is an error)
// b) this is a new native class, which isn't yet hooked up to static registration (this is OK)
if (!(Class->ClassFlags&CLASS_Intrinsic) && (PreviousClassFlags & CLASS_NoExport) == 0 &&
(PreviousClassFlags&CLASS_Native) != 0) // a new native class (one that hasn't been compiled into C++ yet) won't have this set
{
FError::Throwf(TEXT("'noexport': Must include CLASS_NoExport in native class declaration"));
}
}
if (!Class->HasAnyClassFlags(CLASS_Abstract) && ((PreviousClassFlags & CLASS_Abstract) != 0))
{
if (Class->HasAnyClassFlags(CLASS_NoExport))
{
FError::Throwf(TEXT("'abstract': NoExport class missing abstract keyword from class declaration (must change C++ version first)"));
Class->ClassFlags |= CLASS_Abstract;
}
else if (Class->HasAnyFlags(RF_Native))
{
FError::Throwf(TEXT("'abstract': missing abstract keyword from class declaration - class will no longer be exported as abstract"));
}
}
}
void FClassDeclarationMetaData::SetAndValidateConfigName(FClass* Class)
{
if (ConfigName.IsEmpty() == false)
{
// if the user specified "inherit", we're just going to use the parent class's config filename
// this is not actually necessary but it can be useful for explicitly communicating config-ness
if (ConfigName == TEXT("inherit"))
{
UClass* SuperClass = Class->GetSuperClass();
if (!SuperClass)
{
FError::Throwf(TEXT("Cannot inherit config filename: %s has no super class"), *Class->GetName());
}
if (SuperClass->ClassConfigName == NAME_None)
{
FError::Throwf(TEXT("Cannot inherit config filename: parent class %s is not marked config."), *SuperClass->GetPathName());
}
}
else
{
// otherwise, set the config name to the parsed identifier
Class->ClassConfigName = FName(*ConfigName);
}
}
else
{
// Invalidate config name if not specifically declared.
Class->ClassConfigName = NAME_None;
}
}
void FClassDeclarationMetaData::SetAndValidateWithinClass(FClass* Class, const FClasses& AllClasses)
{
// Process all of the class specifiers
if (ClassWithin.IsEmpty() == false)
{
UClass* RequiredWithinClass = AllClasses.FindClass(*ClassWithin);
if (!RequiredWithinClass)
{
FError::Throwf(TEXT("Within class '%s' not found."), *ClassWithin);
}
if (RequiredWithinClass->IsChildOf(UInterface::StaticClass()))
{
FError::Throwf(TEXT("Classes cannot be 'within' interfaces"));
}
else if (Class->ClassWithin == NULL || Class->ClassWithin == UObject::StaticClass() || RequiredWithinClass->IsChildOf(Class->ClassWithin))
{
Class->ClassWithin = RequiredWithinClass;
}
else if (Class->ClassWithin != RequiredWithinClass)
{
FError::Throwf(TEXT("%s must be within %s, not %s"), *Class->GetPathName(), *Class->ClassWithin->GetPathName(), *RequiredWithinClass->GetPathName());
}
}
else
{
// Make sure there is a valid within
Class->ClassWithin = Class->GetSuperClass()
? Class->GetSuperClass()->ClassWithin
: UObject::StaticClass();
}
UClass* ExpectedWithin = Class->GetSuperClass()
? Class->GetSuperClass()->ClassWithin
: UObject::StaticClass();
if (!Class->ClassWithin->IsChildOf(ExpectedWithin))
{
FError::Throwf(TEXT("Parent class declared within '%s'. Cannot override within class with '%s' since it isn't a child"), *ExpectedWithin->GetName(), *Class->ClassWithin->GetName());
}
}