// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Diagnostics; using EpicGames.Core; using EpicGames.UHT.Parsers; using EpicGames.UHT.Tables; using EpicGames.UHT.Tokenizer; using EpicGames.UHT.Types; namespace EpicGames.UHT.Utils { /// /// The phase of UHT where the property is being resolved /// public enum UhtPropertyResolvePhase { /// /// Resolved during the source processing phase. Immediate property types only. /// Parsing, /// /// Resolved during the resolve phase. Non-immediate property types only. /// Resolving, } /// /// Controls what is returned from the parsing of a template object /// public enum UhtTemplateObjectMode { /// /// If the specified type. If the type is a native interface, return the UInterface /// Normal, /// /// If a native interface is specified, return it /// PreserveNativeInterface, /// /// The specified type must be an interface proxy /// NativeInterfaceProxy, } /// /// Structure defining the arguments used in the delegate to resolve property types /// /// Specifies if this is being resolved during the parsing phase or the resolution phase. Type lookups can not happen during the parsing phase /// The configuration of the property /// The token reader containing the type public record struct UhtPropertyResolveArgs(UhtPropertyResolvePhase ResolvePhase, UhtPropertySettings PropertySettings, IUhtTokenReader TokenReader) { /// /// Currently running UHT session /// public readonly UhtSession Session => PropertySettings.Outer.Session; /// /// UHT configuration /// public readonly IUhtConfig Config => Session.Config!; /// /// /// public readonly UhtTypeTokensRef TypeTokens => PropertySettings.TypeTokens; /// /// If true, a template argument is being resolved /// public readonly bool IsTemplate => TypeTokens.DeclarationSegmentIndex != 0; /// /// When processing type, make sure that the next token is the expected token /// /// true if there could be more header to process, false if the end was reached. public readonly bool SkipExpectedType() { if (PropertySettings.PropertyCategory == UhtPropertyCategory.Member && TokenReader.TryOptional("const")) { TokenReader.LogError("Const properties are not supported."); } foreach (UhtToken token in TypeTokens.TypeTokens.Span) { if (!TokenReader.TryOptional(token.Value)) { TokenReader.LogError($"Inappropriate keyword '{TokenReader.PeekToken().Value}' on variable of type '{token.Value}'"); return false; } } return true; } /// /// Resolve the given property. This method will resolve any immediate property during the parsing phase or /// resolve any previously parsed property to the final version. /// /// public readonly UhtProperty? ResolveProperty() { if (ResolvePropertyType(out UhtPropertyType propertyType)) { if (ResolvePhase == UhtPropertyResolvePhase.Resolving || propertyType.Options.HasAnyFlags(UhtPropertyTypeOptions.Immediate)) { return UhtPropertyParser.ResolvePropertyType(this, propertyType); } } // Try the default processor. We only use the default processor when trying to resolve something post parsing phase. if (ResolvePhase == UhtPropertyResolvePhase.Resolving) { return UhtPropertyParser.ResolvePropertyType(this, Session.DefaultPropertyType); } return null; } /// /// Parse a template parameter /// /// Name of the template parameter /// Parsed property public readonly UhtProperty? ParseTemplateParam(StringView paramName) { using UhtThreadBorrower borrower = new(true); UhtPropertySpecifierContext specifierContext = borrower.Instance; specifierContext.Type = PropertySettings.Outer; specifierContext.TokenReader = TokenReader; specifierContext.AccessSpecifier = UhtAccessSpecifier.None; specifierContext.MessageSite = TokenReader; specifierContext.PropertySettings.Reset(PropertySettings, paramName.ToString(), TokenReader); specifierContext.PropertySettings.TypeTokens = new(TypeTokens, GetTypeTokenSegment()); specifierContext.MetaData = specifierContext.PropertySettings.MetaData; specifierContext.MetaNameIndex = UhtMetaData.IndexNone; specifierContext.SeenEditSpecifier = false; specifierContext.SeenBlueprintWriteSpecifier = false; specifierContext.SeenBlueprintReadOnlySpecifier = false; specifierContext.SeenBlueprintGetterSpecifier = false; // Pre-parse any UPARAM UhtPropertyParser.PreParseType(specifierContext, true); // Parse the type UhtPropertyResolveArgs templateArgs = new(ResolvePhase, specifierContext.PropertySettings, TokenReader); return templateArgs.ResolveProperty(); } /// /// For types such as TPtr or TVal, this method parses the template parameter and extracts the type /// /// public readonly UhtProperty? ParseWrappedType() { using UhtThreadBorrower borrower = new(true); UhtPropertySpecifierContext specifierContext = borrower.Instance; specifierContext.Type = PropertySettings.Outer; specifierContext.TokenReader = TokenReader; specifierContext.AccessSpecifier = UhtAccessSpecifier.None; specifierContext.MessageSite = TokenReader; specifierContext.PropertySettings.Copy(PropertySettings); specifierContext.PropertySettings.TypeTokens = new(TypeTokens, GetTypeTokenSegment(), TypeTokens.FullDeclarationSegmentIndex); specifierContext.MetaData = PropertySettings.MetaData; specifierContext.MetaNameIndex = UhtMetaData.IndexNone; specifierContext.SeenEditSpecifier = false; specifierContext.SeenBlueprintWriteSpecifier = false; specifierContext.SeenBlueprintReadOnlySpecifier = false; specifierContext.SeenBlueprintGetterSpecifier = false; // Pre-parse any UPARAM UhtPropertyParser.PreParseType(specifierContext, true); // Parse the type UhtPropertyResolveArgs templateArgs = new(ResolvePhase, specifierContext.PropertySettings, TokenReader); return templateArgs.ResolveProperty(); } /// /// For types such as TPtr or TVal, this method parses the template parameter and extracts the type /// /// Additional flags to be applied to the property /// Additional export flags to be applied to the property /// public readonly UhtProperty? ParseWrappedType(EPropertyFlags additionalFlags, UhtPropertyExportFlags additionalExportFlags) { if (!SkipExpectedType()) { return null; } PropertySettings.PropertyFlags |= additionalFlags; PropertySettings.PropertyExportFlags |= additionalExportFlags; TokenReader.Require('<'); UhtProperty? inner = ParseWrappedType(); if (inner == null) { return null; } TokenReader.Require('>'); return inner; } /// /// Parse a template type /// /// Adjust what is returned and/or what is searched /// Referenced class public readonly UhtClass? ParseTemplateObject(UhtTemplateObjectMode mode) { if (TokenReader.TryOptional("const")) { PropertySettings.MetaData.Add(UhtNames.NativeConst, ""); } if (!SkipExpectedType()) { return null; } bool isNativeConstTemplateArg = false; UhtToken identifier = new(); TokenReader .Require('<') .Optional("const", () => { isNativeConstTemplateArg = true; }) .Optional("class") .RequireIdentifier((ref UhtToken token) => { identifier = token; }) .ExceptionIf("::", "Template arguments to object types do not currently support namespaces") .Optional("const", () => { isNativeConstTemplateArg = true; }) .Require('>'); if (isNativeConstTemplateArg) { PropertySettings.MetaData.Add(UhtNames.NativeConstTemplateArg, ""); } Config.RedirectTypeIdentifier(ref identifier); string scratchString; if (mode == UhtTemplateObjectMode.NativeInterfaceProxy) { if (!identifier.Value.Span.StartsWith("I") || !identifier.Value.Span.EndsWith(UhtNames.VerseProxySuffix)) { TokenReader.LogError(identifier.InputLine, $"Expected a Verse interface proxy. Must begin with 'I' and end with '{UhtNames.VerseProxySuffix}'"); return null; } scratchString = $"U{identifier.Value.Span[1..(identifier.Value.Length - UhtNames.VerseProxySuffix.Length)]}"; identifier.Value = scratchString; } UhtClass? returnClass = PropertySettings.Outer.FindType(UhtFindOptions.SourceName | UhtFindOptions.Class, ref identifier, TokenReader) as UhtClass; if (returnClass != null && returnClass.AlternateObject != null && mode == UhtTemplateObjectMode.Normal) { returnClass = returnClass.AlternateObject as UhtClass; } return returnClass; } /// /// Parse the token stream for a class type and then invoke the factory to create the property /// /// Adjust what is returned and/or what is searched /// Factory invoked to create the property /// Created property or null on error public readonly UhtProperty? ParseTemplateObjectProperty(UhtTemplateObjectMode mode, Func propertyFactory) { int typeStartPos = TokenReader.PeekToken().InputStartPos; UhtClass? referencedClass = ParseTemplateObject(mode); if (referencedClass == null) { return null; } return propertyFactory(referencedClass, typeStartPos); } /// /// Parse a template type /// /// Referenced class public readonly UhtClass? ParseTemplateClass() { UhtToken identifier = new(); if (TokenReader.TryOptional("const")) { PropertySettings.MetaData.Add(UhtNames.NativeConst, ""); } if (!SkipExpectedType()) { return null; } TokenReader .Require('<') .Optional("class") .RequireIdentifier((ref UhtToken token) => { identifier = token; }) .ExceptionIf("::", "Template arguments to object types do not currently support namespaces") .Require('>'); Config.RedirectTypeIdentifier(ref identifier); UhtClass? returnClass = PropertySettings.Outer.FindType(UhtFindOptions.SourceName | UhtFindOptions.Class, ref identifier, TokenReader) as UhtClass; if (returnClass != null && returnClass.AlternateObject != null) { returnClass = returnClass.AlternateObject as UhtClass; } return returnClass; } /// /// Parse a template type /// /// Referenced class public readonly UhtScriptStruct? ParseTemplateScriptStruct() { UhtToken identifier = new(); if (TokenReader.TryOptional("const")) { PropertySettings.MetaData.Add(UhtNames.NativeConst, ""); } if (!SkipExpectedType()) { return null; } TokenReader .Require('<') .Optional("struct") .RequireIdentifier((ref UhtToken token) => { identifier = token; }) .ExceptionIf("::", "Template arguments to object types do not currently support namespaces") .Require('>'); Config.RedirectTypeIdentifier(ref identifier); return PropertySettings.Outer.FindType(UhtFindOptions.SourceName | UhtFindOptions.ScriptStruct, ref identifier, TokenReader) as UhtScriptStruct; } /// /// Logs message for Object pointers to convert UObject* to TObjectPtr or the reverse /// /// Expected behavior for engine types /// Expected behavior for engine plugin types /// Expected behavior for non-engine types /// Description of the pointer type /// Starting character position of the type /// Suggested alternate declaration /// Thrown if the behavior type is unexpected public readonly void ConditionalLogPointerUsage(UhtIssueBehavior engineBehavior, UhtIssueBehavior enginePluginBehavior, UhtIssueBehavior nonEngineBehavior, string pointerTypeDesc, int typeStartPos, string? alternativeTypeDesc) { if (PropertySettings.PropertyCategory != UhtPropertyCategory.Member) { return; } UhtModule module = PropertySettings.Outer.Module; UhtIssueBehavior behavior = nonEngineBehavior; if (module.IsPartOfEngine) { if (module.IsPlugin) { behavior = enginePluginBehavior; } else { behavior = engineBehavior; } } // HACK for CoreUObject tests - allow raw pointers in non-intrinsic classes there. if (module.ShortName == "CoreUObject" && PropertySettings.Outer.HeaderFile.ModuleRelativeFilePath.StartsWith("Tests", StringComparison.InvariantCultureIgnoreCase)) { behavior = UhtIssueBehavior.AllowSilently; } if (behavior == UhtIssueBehavior.AllowSilently) { return; } string type = TokenReader.GetStringView(typeStartPos, TokenReader.InputPos - typeStartPos).ToString(); type = type.Replace("\n", "\\n", StringComparison.Ordinal); type = type.Replace("\r", "\\r", StringComparison.Ordinal); type = type.Replace("\t", "\\t", StringComparison.Ordinal); switch (behavior) { case UhtIssueBehavior.Disallow: if (!String.IsNullOrEmpty(alternativeTypeDesc)) { TokenReader.LogError($"{pointerTypeDesc} usage in member declaration detected [[[{type}]]]. This is disallowed for the target/module, consider {alternativeTypeDesc} as an alternative."); } else { TokenReader.LogError($"{pointerTypeDesc} usage in member declaration detected [[[{type}]]]."); } break; case UhtIssueBehavior.AllowAndLog: if (!String.IsNullOrEmpty(alternativeTypeDesc)) { TokenReader.LogInfo($"{pointerTypeDesc} usage in member declaration detected [[[{type}]]]. Consider {alternativeTypeDesc} as an alternative."); } else { TokenReader.LogInfo("{PointerTypeDesc} usage in member declaration detected [[[{Type}]]]."); } break; default: throw new UhtIceException("Unknown enum value"); } } /// /// Using the current type in the type tokens, try to locate a property type /// /// The referenced property type or null if we don't have a parser for the type in question. private readonly bool ResolvePropertyType(out UhtPropertyType propertyType) { ReadOnlySpan type = TypeTokens.TypeTokens.Span; // Remove any leading 'class', 'struct', or 'enum' keyword if (TypeTokens.DeclarationSegment.FindFilter != UhtFindOptions.None) { type = type[1..]; } // Remove any leading '::' if (type.Length > 0 && type[0].IsSymbol("::")) { type = type[1..]; } // If we have no types remaining, then just ignore if (type.Length == 0) { propertyType = new(); return false; } // If we have only one token defining the type, there is no reason to building a lookup string. if (type.Length == 1) { UhtToken copy = type[0]; Config.RedirectTypeIdentifier(ref copy); return Session.TryGetPropertyType(copy.Value, out propertyType); } // Otherwise we need to generate a string UhtToken[] copiedArray = new UhtToken[type.Length]; for (int i = 0; i < type.Length; i++) { copiedArray[i] = type[i]; if (copiedArray[i].IsIdentifier()) { Config.RedirectTypeIdentifier(ref copiedArray[i]); } } string fullType = UhtTypeTokens.ToString(copiedArray); return Session.TryGetPropertyType(fullType, out propertyType); } /// /// Given the current position of the token reader, return the segment being parsed /// /// Segment index being parsed /// private readonly int GetTypeTokenSegment() { // From the current segment index, look forward for the template segment being parsed int tokenIndex = TokenReader.GetReplayTokenIndex(); int templateSegmentIndex = TypeTokens.DeclarationSegmentIndex + 1; foreach (UhtTypeSegment segment in TypeTokens.Segments[(TypeTokens.DeclarationSegmentIndex + 1)..].Span) { if (tokenIndex < segment.Start) { throw new UhtIceException("Error parsing template argument"); } if (tokenIndex < segment.End) { break; } templateSegmentIndex++; } if (templateSegmentIndex == TypeTokens.Segments.Length) { throw new UhtIceException("Error parsing template argument"); } return templateSegmentIndex; } } }