// Copyright Epic Games, Inc. All Rights Reserved. #include "uLang/Semantics/SemanticScope.h" #include "uLang/Common/Algo/AnyOf.h" #include "uLang/Semantics/AvailableAttributeUtils.h" #include "uLang/Semantics/ControlScope.h" #include "uLang/Semantics/MemberOrigin.h" #include "uLang/Semantics/ModuleAlias.h" #include "uLang/Semantics/ScopedAccessLevelType.h" #include "uLang/Semantics/SemanticEnumeration.h" #include "uLang/Semantics/SemanticProgram.h" #include "uLang/Semantics/SmallDefinitionArray.h" #include "uLang/Semantics/StructOrClass.h" #include "uLang/Semantics/TypeAlias.h" #include "uLang/Semantics/TypeScope.h" #include "uLang/Semantics/TypeVariable.h" #include "uLang/Semantics/VisitStamp.h" #ifdef _MSC_VER // needed for alloca, with arm64 #include #endif namespace uLang { //======================================================================================= // CScope //======================================================================================= CScope::~CScope() { } const CScope* CScope::GetScopeOfKind(EKind Kind) const { for (const CScope* Scope = this; Scope; Scope = Scope->_Parent) { if (Scope->_Kind == Kind) { return Scope; } } return nullptr; } CUTF8String CScope::GetScopePath(uLang::UTF8Char SeparatorChar, EPathMode Mode) const { // If needed, determine scope of package const CScope* PackageRootScope = nullptr; const CScope* RelativeScope = nullptr; if (Mode == EPathMode::PackageRelative || Mode == EPathMode::PackageRelativeWithRoot) { // Is this scope underneath a package? if (const CAstPackage* Package = GetPackage()) { // Yes, use root module of the package PackageRootScope = Package->_RootModule; // Shall we include the root module itself in the scope path? if (PackageRootScope && Mode == EPathMode::PackageRelativeWithRoot) { // Yes, but not if it's the top level module const CScope* PackageRootParentScope = PackageRootScope->GetParentScope(); if (PackageRootParentScope && PackageRootParentScope->GetKind() == CScope::EKind::ModulePart) { // Safe to go one step up, we're not at the top level module yet PackageRootScope = PackageRootParentScope; } } } else { // This can happen for the built-in Verse definitions. Just use the module as the // package root. ULANG_ASSERTF(IsBuiltInScope(), "Did not expect null package for %s", GetScopePath('/', EPathMode::PrefixSeparator).AsCString()); PackageRootScope = GetModule(); } ULANG_ENSUREF(PackageRootScope, "Package-relative scope path for scope `%s` can not be determined.", GetScopeName().AsCString()); RelativeScope = PackageRootScope; } // Count symbols int32_t NumSymbols = 0; for (const CScope* Scope = this; Scope != RelativeScope; Scope = Scope->GetParametricTypeScope().GetParentScope()) { if (Scope->IsLogicalScope() && Scope->GetKind() != CScope::EKind::CompatConstraintRoot) { ++NumSymbols; } } // Gather symbols const CScope** Scopes = (const CScope**)alloca(NumSymbols * sizeof(const CScope*)); int32_t Index = NumSymbols; for (const CScope* Scope = this; Scope != RelativeScope; Scope = Scope->GetParametricTypeScope().GetParentScope()) { if (Scope->IsLogicalScope() && Scope->GetKind() != CScope::EKind::CompatConstraintRoot) { // Help out static analysis to realize that Scopes must be non-null here (because we shouldn't reach here unless NumSymbols>0). ULANG_ASSERT(Scopes); Scopes[--Index] = Scope; } } ULANG_ASSERT(Index == 0); // Build path CUTF8StringBuilder Path; for (Index = 0; Index < NumSymbols; ++Index) { const CScope* Scope = Scopes[Index]; if (Scope->GetKind() == CScope::EKind::CompatConstraintRoot) { continue; } // Use the parent function scope name of parametric types for display. Scope = &Scope->GetParametricTypeScope(); const CSymbol& ScopeName = Scope->GetScopeName(); if (ScopeName.IsNull()) { continue; } if (Path.IsFilled() || Mode == EPathMode::PrefixSeparator) { Path.Append(SeparatorChar); } Path.Append(ScopeName.AsStringView()); } return Path.MoveToString(); } const CModule* CScope::GetModule() const { for (const CScope* Scope = this; Scope; Scope = Scope->_Parent) { if (Scope->_Kind == EKind::Module) { return static_cast(Scope); } if (Scope->_Kind == EKind::ModulePart) { return static_cast(Scope)->CModulePart::GetModule(); } } return nullptr; } CModule* CScope::GetModule() { return const_cast(const_cast(this)->GetModule()); } const CModulePart* CScope::GetModulePart() const { return static_cast(GetScopeOfKind(EKind::ModulePart)); } CModulePart* CScope::GetModulePart() { return const_cast(const_cast(this)->GetModulePart()); } CAstPackage* CScope::GetPackage() const { for (const CScope* Scope = this; Scope; Scope = Scope->_Parent) { if (Scope->_Kind == EKind::Module) { return static_cast(Scope)->GetIrPackage(); } if (Scope->_Kind == EKind::ModulePart) { return static_cast(Scope)->GetIrPackage(); } } return nullptr; } CAstCompilationUnit* CScope::GetCompilationUnit() const { CAstPackage* Package = GetPackage(); return Package ? Package->_CompilationUnit : nullptr; } const CSnippet* CScope::GetSnippet() const { return static_cast(GetScopeOfKind(EKind::Snippet)); } const TSPtr& CScope::GetSymbols() const { return _Program.GetSymbols(); } const CScope& CScope::GetParametricTypeScope() const { const CScope* Scope = this; if ((GetKind() == EKind::Class || GetKind() == EKind::Interface) && (_Parent && _Parent->GetKind() == CScope::EKind::Function)) { Scope = _Parent; } return *Scope; } const CLogicalScope& CScope::GetLogicalScope() const { const CScope* Scope = this; while (true) { const CLogicalScope* LogicalScope = Scope->AsLogicalScopeNullable(); if (LogicalScope) { return *LogicalScope; } Scope = Scope->_Parent; } } uLang::CLogicalScope* CScope::GetEnclosingClassOrInterface() { for (CScope* Scope = this; Scope; Scope = Scope->_Parent) { if (Scope->_Kind == EKind::Class || Scope->_Kind == EKind::Interface) { return static_cast(Scope); } } return nullptr; } bool CScope::IsSameOrChildOf(const CScope* Other) const { for (const CScope* Scope = this; Scope; Scope = Scope->_Parent) { if (Scope == Other) { return true; } } return false; } bool CScope::IsInsideTypeScope() const { const CScope* Scope = this; while (Scope && Scope->_Kind == EKind::ControlScope) { Scope = Scope->_Parent; } return Scope && Scope->_Kind == EKind::Type; } bool CScope::IsBuiltInScope() const { const CAstPackage* Package = GetPackage(); return Package && Package == GetProgram()._BuiltInPackage; } CModule& CScope::CreateModule(const CSymbol& ModuleName) { // Modules are always nested directly inside their enclosing logical scope, only module parts know the exact scope hierarchy TSRef NewModule = TSRef::New(ModuleName, GetLogicalScope()); CModule& Module = *NewModule; GetLogicalScope()._Definitions.Add(Move(NewModule)); return Module; } CClassDefinition& CScope::CreateClass(const CSymbol& ClassName, CClass* Superclass, TArray&& SuperInterfaces, EStructOrClass StructOrClass) { TSRef NewClass = TSRef::New(ClassName, *this, Superclass, Move(SuperInterfaces), StructOrClass); CClassDefinition& Class = *NewClass; GetLogicalScope()._Definitions.Add(Move(NewClass)); return Class; } TSRef CScope::CreateScopedAccessLevelDefinition(TOptional ClassName) { TSRef NewClass = TSRef::New(ClassName, *this); if (!NewClass->_IsAnonymous) { GetLogicalScope()._Definitions.Add(NewClass); } return NewClass; } CInterface& CScope::CreateInterface(const CSymbol& InterfaceName, const TArray& SuperInterfaces) { TSRef NewInterface = TSRef::New(InterfaceName, *this, SuperInterfaces); CInterface& Interface = *NewInterface; GetLogicalScope()._Definitions.Add(Move(NewInterface)); return Interface; } CEnumeration& CScope::CreateEnumeration(const CSymbol& EnumerationName) { TSRef NewEnumeration = TSRef::New(EnumerationName, *this); CEnumeration& Enumeration = *NewEnumeration; GetLogicalScope()._Definitions.Add(Move(NewEnumeration)); return Enumeration; } TSRef CScope::CreateFunction(const CSymbol FunctionName) { TSRef NewFunction = TSRef::New( _Program.NextFunctionIndex(), FunctionName, *this); GetLogicalScope()._Definitions.Add(NewFunction); return NewFunction; } TSRef CScope::CreateDataDefinition(const CSymbol VarName) { TSRef NewDataDef = TSRef::New(VarName, *this); GetLogicalScope()._Definitions.Add(NewDataDef); return NewDataDef; } TSRef CScope::CreateDataDefinition(const CSymbol VarName, const CTypeBase* Type) { TSRef NewDataDef = TSRef::New(VarName, *this, Type); GetLogicalScope()._Definitions.Add(NewDataDef); return NewDataDef; } TSRef CScope::CreateTypeAlias(const CSymbol Name) { TSRef NewTypeAlias = TSRef::New(Name, *this); GetLogicalScope()._Definitions.Add(NewTypeAlias); return NewTypeAlias; } TSRef CScope::CreateTypeVariable(const CSymbol Name, const CTypeBase* NegativeType, const CTypeBase* PositiveType) { TSRef NewTypeVariable = TSRef::New(Name, NegativeType, PositiveType, *this); GetLogicalScope()._Definitions.Add(NewTypeVariable); return NewTypeVariable; } TSRef CScope::CreateModuleAlias(const CSymbol Name) { TSRef NewModuleAlias = TSRef::New(Name, *this); GetLogicalScope()._Definitions.Add(NewModuleAlias); return NewModuleAlias; } const CDataDefinition* CScope::AddUsingInstance(const CDataDefinition* UsingContext) { const CTypeBase* NewType = UsingContext->GetType(); for (const CDataDefinition* ExistingDef : _UsingInstances) { // Ensure existing types are not same type or subtype of the using context type. // - an unrelated type is best though a using context type which is a subtype is // permissible but it will need to qualify use of any overlapping conflict. // `CSemanticAnalyzerImpl::AnalyzeMacroCall_Using ()` uses a similar test mechanism. // [Note that `IsSubtype()` also matches for same type.] if (SemanticTypeUtils::IsSubtype(ExistingDef->GetType(), NewType)) { // Type being added already exists - do not add and return conflicting context return ExistingDef; } } _UsingInstances.Add(UsingContext); return nullptr; } void CScope::ResolvedDefnsAppend(SResolvedDefinitionArray* ResolvedDefns, const SmallDefinitionArray& Definitions) { ResolvedDefns->Reserve(ResolvedDefns->Num() + Definitions.Num()); for (CDefinition* Definition : Definitions) { ResolvedDefns->Emplace(Definition); } } void CScope::ResolvedDefnsAppendWithContext(SResolvedDefinitionArray* ResolvedDefns, const SmallDefinitionArray& Definitions, const CDataDefinition* Context) { ResolvedDefns->Reserve(ResolvedDefns->Num() + Definitions.Num()); for (CDefinition* Definition : Definitions) { ResolvedDefns->Emplace(Definition, Context); } } SResolvedDefinitionArray CScope::ResolveDefinition(const CSymbol& Name, const SQualifier& Qualifier, const CAstPackage* ContextPackage) const { ULANG_ASSERTF(!Name.IsNull(), "Null names are reserved for anonymous variables"); VisitStampType VisitStamp = GenerateNewVisitStamp(); SResolvedDefinitionArray Result; // NOTE: (yiliang.siew) For `(local:)`, we first figure out which function's scope will act as the point where we // stop considering any futher definitions. If we're not resolving a `(local:)` definition, this has no effect. const CScope* LimitingScope = nullptr; if (Qualifier._Type == SQualifier::EType::Local) { // NOTE: (yiliang.siew) In order to account for the special case of parametric classes // (which is really just a normal class nested inside a function), we can't just stop the search // once we hit the first function scope found; we keep going up the hierarchy until we've exhausted all // functions. for (const CScope* Scope = this; Scope; Scope = Scope->GetParentScope()) { if (Scope->GetKind() == CScope::EKind::Function) { LimitingScope = Scope->GetParentScope(); } } } // Every time you call FindInstanceMember it should use a new visit stamp, which ensures that // it will resolve ambiguous references to instance members from different using{Instance} // statements to multiple definitions instead of only finding the first. To make that work // correctly, it needs to walk the scope hierarchy twice: // 1. Generate a new visit stamp // 2. Walk up the parent chain, calling FindDefinitions on each scope and its used scopes, // passing in the visit stamp from step 1. // 3. Walk up the parent chain a second time, calling FindInstanceMember on each scope's used // instances, using a new visit stamp each time (which is the default if you don't pass // one in). // Traverse scope's parent chain to the root scope, trying to find definitions in each scope // and any `using` scopes. for (const CScope* Scope = this; Scope != LimitingScope; Scope = Scope->GetParentScope()) { const CLogicalScope* LogicalScope = Scope->AsLogicalScopeNullable(); if (LogicalScope && LogicalScope->TryMarkVisited(VisitStamp)) { SmallDefinitionArray FoundDefinitions = LogicalScope->FindDefinitions(Name, EMemberOrigin::InheritedOrOriginal, Qualifier, ContextPackage, VisitStamp); ResolvedDefnsAppend(&Result, FoundDefinitions); } if (Qualifier._Type != SQualifier::EType::Local) { // Check each of the using declarations for (const CLogicalScope* Using : Scope->GetUsingScopes()) { if (Using->TryMarkVisited(VisitStamp)) { ResolvedDefnsAppend(&Result, Using->FindDefinitions(Name, EMemberOrigin::InheritedOrOriginal, Qualifier, ContextPackage, VisitStamp)); } } } } if (Qualifier._Type != SQualifier::EType::Local) { // Traverse scope's parent chain to the root scope, finding definitions in any `using` instances. for (const CScope* Scope = this; Scope; Scope = Scope->GetParentScope()) { // Check each of the `using` instances for (const CDataDefinition* UsingContext : Scope->GetUsingInstances()) { const CTypeBase* ContextResultType = UsingContext->GetType(); const CNormalType& ContextNormalType = ContextResultType->GetNormalType(); const CReferenceType* ContextReferenceType = ContextNormalType.AsNullable(); const CNormalType* ContextNormalValueType; if (ContextReferenceType) { ContextNormalValueType = &ContextReferenceType->PositiveValueType()->GetNormalType(); } else { ContextNormalValueType = &ContextNormalType; } // Note that each call to `FindInstanceMember()` uses a new visit stamp and it returns // an array - so if a match is not found it only appends an empty array ResolvedDefnsAppendWithContext(&Result, ContextNormalValueType->FindInstanceMember(Name, EMemberOrigin::InheritedOrOriginal, Qualifier), UsingContext); } } } return Result; } TSRef CScope::CreateNestedControlScope(CSymbol Name) { _NestedControlScopes.AddNew(this, _Program, Name); return _NestedControlScopes.Last(); } TSRef CScope::CreateNestedTypeScope() { _NestedTypeScopes.AddNew(*this); return _NestedTypeScopes.Last(); } VisitStampType CScope::GenerateNewVisitStamp() { static VisitStampType CurrentStamp = 0; return ++CurrentStamp; } namespace { const CModule& GetRootModule(const CModule& Module) { const CModule* Result = &Module; const CScope* Scope = &Module; for (;;) { const CScope* Parent = Scope->GetParentScope(); if (!Parent) { return *Result; } if (Parent->GetKind() == CScope::EKind::Module) { Result = static_cast(Parent); } Scope = Parent; } } template bool OrConstrained(const CModule& Module, TPredicate Predicate) { if (uLang::Invoke(Predicate, Module)) { return true; } const CDefinition* ConstrainedDefinition = Module.GetConstrainedDefinition(); if (!ConstrainedDefinition) { return false; } return uLang::Invoke(Predicate, ConstrainedDefinition->AsChecked()); } bool CheckScopedAccessLevelHelper(const CDefinition& Definition, const SAccessLevel& DefinitionAccessLevel, const CModule& ReferenceModule) { const CScope& DefinitionScope = Definition._EnclosingScope; // If the definition is scoped, then we need to check each of those to see if they can see the reference scope if (DefinitionAccessLevel._Kind == SAccessLevel::EKind::Scoped) { for (const CScope* Scope : DefinitionAccessLevel._Scopes) { if (!Scope) { continue; } if (OrConstrained(ReferenceModule, [=](auto&& Module) { return Module.IsSameOrChildOf(Scope); })) { return true; } } } // If the definition site is internal, but the reference site is scoped to the definition, then that's also ok // Walk up the parent scopes for the Definition and look for any scoped access levels. We want to know if any of those // parents of the definition game access to the reference site for (const CScope* S = &DefinitionScope; S != nullptr; S = S->GetParentScope()) { if (const CDefinition* ScopeDefinition = S->ScopeAsDefinition()) { const SAccessLevel& ScopeAccessLevel = ScopeDefinition->DerivedAccessLevel(); if (ScopeAccessLevel._Kind == SAccessLevel::EKind::Scoped) { for (const CScope* TestScope : ScopeAccessLevel._Scopes) { if (!TestScope) { continue; } if (OrConstrained(ReferenceModule, [=](auto&& Module) { return Module.IsSameOrChildOf(TestScope); })) { return true; } } } } } return false; } } bool CScope::CanAccess(const CDefinition& Definition, const SAccessLevel& DefinitionAccessLevel) const { const CScope& DefinitionScope = Definition._EnclosingScope; const CDefinition* EnclosingDefinition = Definition.GetEnclosingDefinition(); // Recursively check that the enclosing definition is accessible from this scope. This may be redundant, as // the caller will often have already checked accessibility of the enclosing scope. However, there are cases // where it isn't redundant: e.g. checking accessibility of an overriding definition, where the accessibility // of the scope containing the override will have already been checked by the caller, but not the scope // containing the overridden definition (Definition in this function). const uint32_t UploadedAtFNVersion = GetPackage()->_UploadedAtFNVersion; if (UploadedAtFNVersion >= 2810 && EnclosingDefinition && !EnclosingDefinition->IsAccessibleFrom(*this)) { return false; } auto GetScopeClassOrInterface = [](const CScope* Scope) -> const CLogicalScope* { const CLogicalScope* CallingScope = nullptr; const CScope* QueryingScope = Scope; while (QueryingScope) { if (QueryingScope->GetKind() == Cases) { CallingScope = static_cast(QueryingScope); break; } QueryingScope = QueryingScope->GetParentScope(); } return CallingScope; }; switch (DefinitionAccessLevel._Kind) { case SAccessLevel::EKind::Public: // Access is permitted anywhere // This case is present to be complete for switch return true; case SAccessLevel::EKind::Scoped: case SAccessLevel::EKind::Internal: { // for both internal and scoped, we may need to do some work to see if any parent scopes are scoped const CModule* ReferenceSiteModule = GetModule(); if (!ReferenceSiteModule) { return false; } const CModule* DefinitionModule = DefinitionScope.GetModule(); if (OrConstrained(*ReferenceSiteModule, [=](auto&& Module) { return Module.IsSameOrChildOf(DefinitionModule); })) { // ordinary internal rules are sufficient - reference is same or child of definition return true; } // we need to look at all parent scopes of the definition to see if any one granted access to the reference site return CheckScopedAccessLevelHelper(Definition, DefinitionAccessLevel, *ReferenceSiteModule); } case SAccessLevel::EKind::Protected: { const CLogicalScope* ReferencingScope = GetScopeClassOrInterface(this); if (ReferencingScope == nullptr) { return false; } if (ReferencingScope->GetKind() == CScope::EKind::Class) { if (DefinitionScope.GetKind() == CScope::EKind::Class) { return static_cast(ReferencingScope)->IsClass(*static_cast(&DefinitionScope)); } if (DefinitionScope.GetKind() == CScope::EKind::Interface) { return static_cast(ReferencingScope)->ImplementsInterface(*static_cast(&DefinitionScope)); } } else if (ReferencingScope->GetKind() == CScope::EKind::Interface) { if (DefinitionScope.GetKind() == CScope::EKind::Interface) { return static_cast(ReferencingScope)->IsInterface(*static_cast(&DefinitionScope)); } } return false; } case SAccessLevel::EKind::Private: { // Must be in same class or interface return GetScopeClassOrInterface(this) == &DefinitionScope; } case SAccessLevel::EKind::EpicInternal: { return CanAccessEpicInternal(); } default: ULANG_UNREACHABLE(); } } bool CScope::IsAuthoredByEpic() const { CUTF8StringBuilder ScopePathBuilder; ScopePathBuilder.Append(GetScopePath('/', EPathMode::PrefixSeparator)); ScopePathBuilder.Append('/'); return uLang::AnyOf(GetProgram()._EpicInternalModulePrefixes, [&](const CUTF8String& EpicInternalModulePrefix) -> bool { return ScopePathBuilder.ToStringView().StartsWith(EpicInternalModulePrefix); }); } bool CScope::CanAccessEpicInternal() const { CAstPackage* Package = GetPackage(); return (Package && Package->_VerseScope == EVerseScope::InternalUser) || IsAuthoredByEpic(); } const char* CScope::KindToCString(CScope::EKind Kind) { #define CSCOPE_KINDTOCSTRING_CASE(K, S) case EKind::K: return S; switch (Kind) { CSCOPE_KINDTOCSTRING_CASE(Program, "program") CSCOPE_KINDTOCSTRING_CASE(CompatConstraintRoot, "compatibility constraint root") CSCOPE_KINDTOCSTRING_CASE(Module, "module") CSCOPE_KINDTOCSTRING_CASE(ModulePart, "module part") CSCOPE_KINDTOCSTRING_CASE(Snippet, "file") CSCOPE_KINDTOCSTRING_CASE(Class, "class") CSCOPE_KINDTOCSTRING_CASE(Function, "function") CSCOPE_KINDTOCSTRING_CASE(ControlScope, "control scope") CSCOPE_KINDTOCSTRING_CASE(Interface, "interface") CSCOPE_KINDTOCSTRING_CASE(Type, "type") CSCOPE_KINDTOCSTRING_CASE(Enumeration, "enumeration") default: ULANG_ERRORF("Could not convert CScope::EKind %d to string", Kind); } #undef CSCOPE_KINDTOCSTRING_CASE return ""; } //======================================================================================= // CLogicalScope //======================================================================================= CLogicalScope::~CLogicalScope() { for (const TSRef& Definition : _Definitions) { ULANG_ASSERTF(Definition->GetRefCount() == 1, "Unexpectedly freeing %s scope while there's an external reference to its %s definition %s", CScope::KindToCString(GetKind()), DefinitionKindAsCString(Definition->GetKind()), Definition->AsNameCString()); } } EIterateResult CLogicalScope::IterateRecurseLogicalScopes(const TFunction& Functor) const { // Invoke on this EVisitResult Result = Functor(*this); if (Result != EVisitResult::Continue) { return Result == EVisitResult::Stop ? EIterateResult::Stopped : EIterateResult::Completed; } // Then on all definitions. for (const CDefinition* Definition : _Definitions) { if (const CLogicalScope* DefinitionLogicalScope = Definition->DefinitionAsLogicalScopeNullable()) { if (DefinitionLogicalScope->IterateRecurseLogicalScopes(Functor) == EIterateResult::Stopped) { return EIterateResult::Stopped; } } } return EIterateResult::Completed; } EIterateResult CLogicalScope::IterateRecurseLogicalScopes(TFunction&& Functor) const { return IterateRecurseLogicalScopes(Functor); } SmallDefinitionArray CLogicalScope::FindDefinitions(const CSymbol& Name, EMemberOrigin /*Origin*/, const SQualifier& Qualifier, const CAstPackage* ContextPackage, VisitStampType VisitStamp) const { SmallDefinitionArray Result; ULANG_ASSERTF(!Name.IsNull(), "Null names are reserved for anonymous variables"); for (CDefinition* Definition : _Definitions) { if (Definition->GetName() == Name) { if (Qualifier.IsUnspecified() || Qualifier == Definition->GetBaseOverriddenDefinition().GetPrototypeDefinition()->GetImplicitQualifier()) { if (ContextPackage && !IsDefinitionAvailableAtVersion(*Definition, ContextPackage->_UploadedAtFNVersion, _Program)) { continue; } if (Definition->TryMarkOverriddenAndConstrainedDefinitionsVisited(VisitStamp)) { Result.Add(Definition); } } } } return Result; } void CLogicalScope::SetRevision(SemanticRevision Revision) { ULANG_ENSUREF(Revision >= _CumulativeRevision, "Revision to be set must not be smaller than existing revisions."); _CumulativeRevision = Revision; if (_Parent) { const_cast(_Parent->GetLogicalScope()).SetRevision(Revision); } } const CDefinition* CLogicalScope::FindOverrideFor(const CDefinition& Definition) const { for (const uLang::CDefinition* LocalDefinition : GetDefinitions()) { if (LocalDefinition->GetOverriddenDefinition() == &Definition) { return LocalDefinition; } } return nullptr; } SQualifier CLogicalScope::AsQualifier() const { if (IsControlScope()) { return SQualifier::Local(); } else if (const CTypeBase* Scope = ScopeAsType()) { return SQualifier::NominalType(Scope->GetNormalType().AsNominalType()); } else { return SQualifier::Unknown(); } } } // namespace uLang