860 lines
29 KiB
C++
860 lines
29 KiB
C++
// 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 <malloc.h>
|
|
#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<const CModule*>(Scope);
|
|
}
|
|
|
|
if (Scope->_Kind == EKind::ModulePart)
|
|
{
|
|
return static_cast<const CModulePart*>(Scope)->CModulePart::GetModule();
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
CModule* CScope::GetModule()
|
|
{
|
|
return const_cast<CModule*>(const_cast<const CScope*>(this)->GetModule());
|
|
}
|
|
|
|
const CModulePart* CScope::GetModulePart() const
|
|
{
|
|
return static_cast<const CModulePart*>(GetScopeOfKind(EKind::ModulePart));
|
|
}
|
|
|
|
CModulePart* CScope::GetModulePart()
|
|
{
|
|
return const_cast<CModulePart*>(const_cast<const CScope*>(this)->GetModulePart());
|
|
}
|
|
|
|
CAstPackage* CScope::GetPackage() const
|
|
{
|
|
for (const CScope* Scope = this; Scope; Scope = Scope->_Parent)
|
|
{
|
|
if (Scope->_Kind == EKind::Module)
|
|
{
|
|
return static_cast<const CModule*>(Scope)->GetIrPackage();
|
|
}
|
|
if (Scope->_Kind == EKind::ModulePart)
|
|
{
|
|
return static_cast<const CModulePart*>(Scope)->GetIrPackage();
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
CAstCompilationUnit* CScope::GetCompilationUnit() const
|
|
{
|
|
CAstPackage* Package = GetPackage();
|
|
return Package ? Package->_CompilationUnit : nullptr;
|
|
}
|
|
|
|
const CSnippet* CScope::GetSnippet() const
|
|
{
|
|
return static_cast<const CSnippet*>(GetScopeOfKind(EKind::Snippet));
|
|
}
|
|
|
|
const TSPtr<CSymbolTable>& 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<CLogicalScope*>(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<CModule> NewModule = TSRef<CModule>::New(ModuleName, GetLogicalScope());
|
|
CModule& Module = *NewModule;
|
|
GetLogicalScope()._Definitions.Add(Move(NewModule));
|
|
|
|
return Module;
|
|
}
|
|
|
|
CClassDefinition& CScope::CreateClass(const CSymbol& ClassName, CClass* Superclass, TArray<CInterface*>&& SuperInterfaces, EStructOrClass StructOrClass)
|
|
{
|
|
TSRef<CClassDefinition> NewClass = TSRef<CClassDefinition>::New(ClassName, *this, Superclass, Move(SuperInterfaces), StructOrClass);
|
|
CClassDefinition& Class = *NewClass;
|
|
GetLogicalScope()._Definitions.Add(Move(NewClass));
|
|
return Class;
|
|
}
|
|
|
|
TSRef<CScopedAccessLevelDefinition> CScope::CreateScopedAccessLevelDefinition(TOptional<CSymbol> ClassName)
|
|
{
|
|
TSRef<CScopedAccessLevelDefinition> NewClass = TSRef<CScopedAccessLevelDefinition>::New(ClassName, *this);
|
|
if (!NewClass->_IsAnonymous)
|
|
{
|
|
GetLogicalScope()._Definitions.Add(NewClass);
|
|
}
|
|
return NewClass;
|
|
}
|
|
|
|
CInterface& CScope::CreateInterface(const CSymbol& InterfaceName, const TArray<CInterface*>& SuperInterfaces)
|
|
{
|
|
TSRef<CInterface> NewInterface = TSRef<CInterface>::New(InterfaceName, *this, SuperInterfaces);
|
|
CInterface& Interface = *NewInterface;
|
|
GetLogicalScope()._Definitions.Add(Move(NewInterface));
|
|
return Interface;
|
|
}
|
|
|
|
CEnumeration& CScope::CreateEnumeration(const CSymbol& EnumerationName)
|
|
{
|
|
TSRef<CEnumeration> NewEnumeration = TSRef<CEnumeration>::New(EnumerationName, *this);
|
|
CEnumeration& Enumeration = *NewEnumeration;
|
|
GetLogicalScope()._Definitions.Add(Move(NewEnumeration));
|
|
return Enumeration;
|
|
}
|
|
|
|
TSRef<CFunction> CScope::CreateFunction(const CSymbol FunctionName)
|
|
{
|
|
TSRef<CFunction> NewFunction = TSRef<CFunction>::New(
|
|
_Program.NextFunctionIndex(),
|
|
FunctionName,
|
|
*this);
|
|
|
|
GetLogicalScope()._Definitions.Add(NewFunction);
|
|
|
|
return NewFunction;
|
|
}
|
|
|
|
TSRef<CDataDefinition> CScope::CreateDataDefinition(const CSymbol VarName)
|
|
{
|
|
TSRef<CDataDefinition> NewDataDef = TSRef<CDataDefinition>::New(VarName, *this);
|
|
GetLogicalScope()._Definitions.Add(NewDataDef);
|
|
return NewDataDef;
|
|
}
|
|
|
|
TSRef<CDataDefinition> CScope::CreateDataDefinition(const CSymbol VarName, const CTypeBase* Type)
|
|
{
|
|
TSRef<CDataDefinition> NewDataDef = TSRef<CDataDefinition>::New(VarName, *this, Type);
|
|
GetLogicalScope()._Definitions.Add(NewDataDef);
|
|
return NewDataDef;
|
|
}
|
|
|
|
TSRef<CTypeAlias> CScope::CreateTypeAlias(const CSymbol Name)
|
|
{
|
|
TSRef<CTypeAlias> NewTypeAlias = TSRef<CTypeAlias>::New(Name, *this);
|
|
GetLogicalScope()._Definitions.Add(NewTypeAlias);
|
|
return NewTypeAlias;
|
|
}
|
|
|
|
TSRef<CTypeVariable> CScope::CreateTypeVariable(const CSymbol Name, const CTypeBase* NegativeType, const CTypeBase* PositiveType)
|
|
{
|
|
TSRef<CTypeVariable> NewTypeVariable = TSRef<CTypeVariable>::New(Name, NegativeType, PositiveType, *this);
|
|
GetLogicalScope()._Definitions.Add(NewTypeVariable);
|
|
return NewTypeVariable;
|
|
}
|
|
|
|
TSRef<CModuleAlias> CScope::CreateModuleAlias(const CSymbol Name)
|
|
{
|
|
TSRef<CModuleAlias> NewModuleAlias = TSRef<CModuleAlias>::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<CReferenceType>();
|
|
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<CControlScope> CScope::CreateNestedControlScope(CSymbol Name)
|
|
{
|
|
_NestedControlScopes.AddNew(this, _Program, Name);
|
|
return _NestedControlScopes.Last();
|
|
}
|
|
|
|
TSRef<CTypeScope> 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<const CModule*>(Parent);
|
|
}
|
|
Scope = Parent;
|
|
}
|
|
}
|
|
|
|
template <typename TPredicate>
|
|
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<CModule>());
|
|
}
|
|
|
|
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<CScope::EKind::Class, CScope::EKind::Interface>)
|
|
{
|
|
CallingScope = static_cast<const CLogicalScope*>(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<const CClass*>(ReferencingScope)->IsClass(*static_cast<const CClass*>(&DefinitionScope));
|
|
}
|
|
if (DefinitionScope.GetKind() == CScope::EKind::Interface)
|
|
{
|
|
return static_cast<const CClass*>(ReferencingScope)->ImplementsInterface(*static_cast<const CInterface*>(&DefinitionScope));
|
|
}
|
|
}
|
|
else if (ReferencingScope->GetKind() == CScope::EKind::Interface)
|
|
{
|
|
if (DefinitionScope.GetKind() == CScope::EKind::Interface)
|
|
{
|
|
return static_cast<const CInterface*>(ReferencingScope)->IsInterface(*static_cast<const CInterface*>(&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<CDefinition>& 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<EVisitResult(const CLogicalScope&)>& 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<EVisitResult(const CLogicalScope&)>&& 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<CLogicalScope&>(_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
|