// Copyright Epic Games, Inc. All Rights Reserved. #include "uLang/Semantics/AvailableAttributeUtils.h" #include "uLang/Common/Common.h" #include "uLang/Common/Text/Symbol.h" #include "uLang/Semantics/Definition.h" #include "uLang/Semantics/Expression.h" #include "uLang/Semantics/SemanticProgram.h" #include "uLang/Semantics/SemanticScope.h" #include "uLang/Semantics/SemanticTypes.h" namespace uLang { namespace { TOptional GetIntegerDefinitionValue(TSPtr ExprDefinition, CSemanticProgram& SemanticProgram) { const CNormalType& ArgType = ExprDefinition->GetResultType(SemanticProgram)->GetNormalType(); if (const CIntType* IntArg = ArgType.AsNullable()) { if (TSPtr ValueInvokeExpr = AsNullable(ExprDefinition->Value())) { if (TSPtr NumberExpr = AsNullable(ValueInvokeExpr->_Argument)) { if (!NumberExpr->IsFloat()) { return static_cast(NumberExpr->GetIntValue()); } } } } return TOptional(); } CSymbol GetArgumentName(TSPtr ExprDefinition) { TSPtr ElementExpr = ExprDefinition->Element(); if (TSPtr IdentifierData = AsNullable(ElementExpr)) { return IdentifierData->GetName(); } return CSymbol(); } } // namespace anonymous TOptional GetAvailableAttributeVersion(const CDefinition& Definition, CSemanticProgram& SemanticProgram) { ULANG_ASSERTF(SemanticProgram._availableClass, "Available class definition not found"); if (const CClass* AvailableClass = SemanticProgram._availableClass) { if (TOptional AvailabieAttribute = Definition.FindAttribute(AvailableClass, SemanticProgram)) { if (const CExprArchetypeInstantiation* AvailableArchInst = AsNullable(AvailabieAttribute->_Expression)) { TOptional MinVersion; for (TSRef Argument : AvailableArchInst->Arguments()) { if (TSPtr ArgDefinition = AsNullable(Argument)) { if (GetArgumentName(ArgDefinition) == SemanticProgram._IntrinsicSymbols._MinUploadedAtFNVersion) { MinVersion = GetIntegerDefinitionValue(ArgDefinition, SemanticProgram); break; } } } return MinVersion; } } } // No @available attribute return TOptional{}; } // Combine the available-attribute version with any available-attributes found on the parent scopes. // A likely case: // @available{MinUploadedAtFNVersion:=3000} // C := class { Value:int=42 } // The combined available-version for Value is 3000 given it's parent context. This also applies if there // are multiple versions at different containing scopes - the final applicable version is the most-restrictive one. TOptional CalculateCombinedAvailableAttributeVersion(const CDefinition& Definition, CSemanticProgram& SemanticProgram) { auto CombineResultsHelper = [&SemanticProgram](const CDefinition* Definition, const TOptional& CurrentResult) -> TOptional { TOptional Result = CurrentResult; if (TOptional AttributeValue = GetAvailableAttributeVersion(*Definition, SemanticProgram)) { Result = CMath::Max(CurrentResult.Get(0), AttributeValue.GetValue()); } return Result; }; TOptional CombinedResult = CombineResultsHelper(&Definition, TOptional()); // TODO: @available isn't applied to CModulePart correctly - CModuleParts cannot themselves hold attributes, so this snippet becomes a problem: // // @available{ MinUploadedAtFNVersion: = 3000 } // M: = module {...} // // @available{ MinUploadedAtFNVersion: = 4000 } // M: = module {...} // // The first module-M gets an available version of 3000. The second @available attribute is processed, but isn't applied to the CModule type. // This kind of attribute should be held on the CModulePart instead. const CScope* Scope = &Definition._EnclosingScope; while (Scope != nullptr) { if (const CDefinition* ScopeDefinition = Scope->ScopeAsDefinition()) { CombinedResult = CombineResultsHelper(ScopeDefinition, CombinedResult.Get(0)); } Scope = Scope->GetParentScope(); } return CombinedResult; } bool IsDefinitionAvailableAtVersion(const CDefinition& Definition, uint64_t Version, CSemanticProgram& SemanticProgram) { if (TOptional AttributeVersion = CalculateCombinedAvailableAttributeVersion(Definition, SemanticProgram)) { return AttributeVersion.GetValue() <= Version; } // Not version-filtered return true; } } // namespace uLang