Files
UnrealEngine/Engine/Source/Runtime/VerseCompiler/Private/uLang/SemanticAnalyzer/QualifierUtils.cpp
2025-05-18 13:04:45 +08:00

562 lines
20 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "uLang/Common/Common.h"
#include "uLang/Semantics/QualifierUtils.h"
#include "uLang/Semantics/Expression.h"
#include "uLang/Semantics/ModuleAlias.h"
#include "uLang/Semantics/TypeAlias.h"
#include "uLang/Semantics/TypeVariable.h"
#include "uLang/Semantics/SemanticEnumeration.h"
#include "uLang/Semantics/SemanticInterface.h"
#include "uLang/Semantics/SemanticProgram.h"
#include "uLang/Semantics/ScopedAccessLevelType.h"
namespace uLang
{
using VstSharedRef = TSRef<Verse::Vst::Node>;
bool HasDefinition(const CExprIdentifierBase& IdentifierBase)
{
// local and unresolved are not supposed to have a definition
// TODO: local will be qualified in the future
return IdentifierBase.GetNodeType() == Cases<EAstNodeType::Identifier_Local, EAstNodeType::Identifier_Unresolved>
|| static_cast<const CExprIdentifierBase&>(IdentifierBase).IdentifierDefinition();
}
class CFindResolvedIdentifiersWithoutDefinitionsAstVisitor final : public SAstVisitor
{
public:
virtual void Visit(const char* /*FieldName*/, CAstNode& AstNode) override
{
if (const CExprIdentifierBase* IdentifierBase = AstNode.AsIdentifierBase(); IdentifierBase && !HasDefinition(*IdentifierBase))
{
_InvalidIdentifiers.Add(AstNode.AsExpression());
}
}
virtual void VisitElement(CAstNode& AstNode) override
{
Visit(AstNode);
}
void Visit(const CAstNode& AstNode)
{
AstNode.VisitImmediates(*this);
AstNode.VisitChildren(*this);
}
TArray<const CExpressionBase*> GetInvalidIdentifiers() const
{
return _InvalidIdentifiers;
}
private:
TArray<const CExpressionBase*> _InvalidIdentifiers;
};
TArray<const CExpressionBase*> FindResolvedIdentifiersWithoutDefinitions(const TSRef<CSemanticProgram>& /*Program*/, const CAstNode& RootNode)
{
CFindResolvedIdentifiersWithoutDefinitionsAstVisitor Visitor;
Visitor.Visit(RootNode);
return Visitor.GetInvalidIdentifiers();
}
class CFindUnresolvedIdentifiersAstVisitor final : public SAstVisitor
{
public:
virtual void Visit(const char* /*FieldName*/, CAstNode& AstNode) override
{
if (AstNode.GetNodeType() == EAstNodeType::Identifier_Unresolved)
{
_UnresolvedIdentifiers.Add(AstNode.AsExpression());
}
}
virtual void VisitElement(CAstNode& AstNode) override
{
Visit(AstNode);
}
void Visit(const CAstNode& AstNode)
{
AstNode.VisitImmediates(*this);
AstNode.VisitChildren(*this);
}
TArray<const CExpressionBase*> GetUnresolvedIdentifiers() const
{
return _UnresolvedIdentifiers;
}
private:
TArray<const CExpressionBase*> _UnresolvedIdentifiers;
};
TArray<const CExpressionBase*> FindUnresolvedIdentifiers(const TSRef<CSemanticProgram>& Program, const CAstNode& RootNode)
{
CFindUnresolvedIdentifiersAstVisitor Visitor;
Visitor.Visit(RootNode);
return Visitor.GetUnresolvedIdentifiers();
}
struct SQualifierResult
{
enum class EStatus
{
Undefined, Qualified, NotRequired, Failed
};
static SQualifierResult Qualified(const CUTF8StringView Str)
{
return { EStatus::Qualified, Str };
}
static SQualifierResult NotRequired()
{
return {EStatus::NotRequired, {}};
}
static SQualifierResult Failed()
{
return {EStatus::Failed, {}};
}
EStatus Status = EStatus::Failed;
CUTF8String String;
};
// Arguments can have a lot of different shapes
// X:int, Y:int CExprMakeTuple
// X:int CExprDefinition
// X():int CExprInvocation
enum class EContains { Local, Path, NotYet, No };
EContains ContainsExpr(const CExpressionBase* Argument, const CExprIdentifierUnresolved* Expr)
{
if (Argument == Expr)
{
return EContains::Local;
}
// (X:int, ...)
else if (const CExprMakeTuple* Args = AsNullable<CExprMakeTuple>(Argument))
{
for (const TSPtr<CExpressionBase>& Arg : Args->GetSubExprs())
{
EContains Contains = ContainsExpr(Arg, Expr);
if (Contains != EContains::No)
{
return Contains;
}
}
}
// X:int
else if (auto Definition = AsNullable<CExprDefinition>(Argument))
{
if (Definition->GetName() == Expr->_Symbol)
{
return EContains::NotYet; // Should be Path, but the compiler isn't ready
}
return ContainsExpr(Definition->Element(), Expr);
}
// X(...):int
else if (auto Invocation = AsNullable<CExprInvocation>(Argument))
{
if (Invocation->GetCallee().Get() == Expr)
{
return EContains::Local;
}
EContains Contains = ContainsExpr(Invocation->GetArgument(), Expr);
if (Contains != EContains::No)
{
return Contains;
}
}
return EContains::No;
}
static SQualifierResult QualifierOf(const CExpressionBase* Expr, Verse::Vst::Identifier* VstIdentifier, const CSemanticProgram& Program)
{
static auto GetScopePath = [](const CScope& Scope)
{
CUTF8String Result = Scope.GetScopePath('/', CScope::EPathMode::PrefixSeparator);
return Result;
};
static auto GetQualifierPath = [Expr, VstIdentifier](const SQualifier& Qualifier) -> SQualifierResult
{
switch (Qualifier._Type)
{
case SQualifier::EType::Local:
return SQualifierResult::Qualified("local");
case SQualifier::EType::LogicalScope:
{
return SQualifierResult::Qualified(GetScopePath(*Qualifier.GetLogicalScope()));
}
case SQualifier::EType::NominalType:
{
auto* Scope = Qualifier.GetNominalType()->Definition()->DefinitionAsLogicalScopeNullable();
ULANG_ASSERT(Scope);
return SQualifierResult::Qualified(GetScopePath(*Scope));
}
case SQualifier::EType::Unknown:
return SQualifierResult::Failed();
default:
ULANG_UNREACHABLE();
};
};
static auto GetDefinitionQualifier = [](const CDefinition& Def) -> SQualifierResult
{
const CDefinition* PrototypeDefinition = Def.GetPrototypeDefinition();
if (const CDataDefinition* DataDefinition = PrototypeDefinition->AsNullable<CDataDefinition>(); DataDefinition && DataDefinition->_bNamed)
{
return SQualifierResult::NotRequired(); // TODO: Should use the implicit qualifier, but the compiler can't handle that.
}
return GetQualifierPath(PrototypeDefinition->GetImplicitQualifier());
};
/* Global types are special treated at the moment, they aren't qualified. This will not be true forever. */
static auto IsGlobalType = [](const CExprIdentifierTypeAlias* IdentifierTypeAlias) -> bool
{
const CTypeType* TypeType = IdentifierTypeAlias->_TypeAlias.GetTypeType();
const CTypeBase* PositiveType = TypeType->PositiveType();
if (PositiveType == TypeType->NegativeType()) {
if (PositiveType->GetNormalType().GetKind() == Cases < ETypeKind::False, ETypeKind::True, ETypeKind::Void, ETypeKind::Any, ETypeKind::Comparable, ETypeKind::Logic, ETypeKind::Rational, ETypeKind::Char8, ETypeKind::Char32, ETypeKind::Path, ETypeKind::Range, ETypeKind::Persistable>)
{
return true;
}
}
return false;
};
switch (Expr->GetNodeType())
{
case EAstNodeType::Identifier_Function:
{
// TODO: should this be here, or in the implementation of "GetImplicitQualifier"?
auto Fun = AsNullable<CExprIdentifierFunction>(Expr);
if (Fun->_Function._ExtensionFieldAccessorKind == EExtensionFieldAccessorKind::ExtensionMethod)
{
return SQualifierResult::NotRequired();
}
return GetDefinitionQualifier(Fun->_Function);
}
case EAstNodeType::Identifier_Enum:
return GetDefinitionQualifier(*AsNullable<CExprEnumerationType>(Expr)->GetEnumeration(Program));
case EAstNodeType::Identifier_Module:
return GetDefinitionQualifier(*AsNullable<CExprIdentifierModule>(Expr)->GetModule(Program));
case EAstNodeType::Identifier_ModuleAlias:
return GetDefinitionQualifier(AsNullable<CExprIdentifierModuleAlias>(Expr)->_ModuleAlias);
case EAstNodeType::Identifier_Interface:
return GetDefinitionQualifier(*AsNullable<CExprInterfaceType>(Expr)->GetInterface(Program));
case EAstNodeType::Identifier_Class:
return GetDefinitionQualifier(*AsNullable<CExprIdentifierClass>(Expr)->GetClass(Program)->_Definition);
case EAstNodeType::Identifier_TypeAlias:
{
// TODO: Add qualification for hardcoded types also.
if (const CExprIdentifierTypeAlias* IdentifierTypeAlias = AsNullable<CExprIdentifierTypeAlias>(Expr)) {
if (IsGlobalType(IdentifierTypeAlias))
{
return SQualifierResult::NotRequired();
}
}
return GetDefinitionQualifier(AsNullable<CExprIdentifierTypeAlias>(Expr)->_TypeAlias);
}
case EAstNodeType::Identifier_Data:
return GetDefinitionQualifier(AsNullable<CExprIdentifierData>(Expr)->_DataDefinition);
case EAstNodeType::Identifier_Self:
case EAstNodeType::Identifier_BuiltInMacro:
case EAstNodeType::Literal_Logic:
case EAstNodeType::Invoke_MakeOption:
// TODO: sus; need to see exactly what MakeOption can do,
// and whether or not it's covered by "Unresolved" below.
// TODO: these types of identifiers can't be hand-qualified at the moment,
// and their use as the LHS of definitions is forbidden by the semantic analyzer.
return SQualifierResult::NotRequired();
case EAstNodeType::Identifier_TypeVariable:
return GetDefinitionQualifier(AsNullable<CExprIdentifierTypeVariable>(Expr)->_TypeVariable);
case EAstNodeType::Identifier_Unresolved:
if (VstIdentifier->GetSourceText() == "_")
{
return SQualifierResult::NotRequired();
}
for (const Verse::Vst::Node* It = VstIdentifier; It; It = It->GetParent())
{
auto ItAst = It->GetMappedAstNode();
if (!ItAst)
{
continue;
}
auto ItExpr = ItAst->AsExpression();
if (AsNullable<CExprIdentifierUnresolved>(ItExpr))
{
continue; // Still not enough information
}
if (auto MakeNamed = AsNullable<CExprMakeNamed>(ItExpr))
{
if (MakeNamed->GetNameIdentifier().Get() == Expr)
{
// TODO: This is not true, but how to qualify named parameters is not resolved yet.
return SQualifierResult::NotRequired();
}
continue;
}
if (AsNullable<CExprInvocation>(ItExpr))
{
continue; // TODO: Is this due to processing an extension method? Write test and verify.
}
if (AsNullable<CExprWhere>(ItExpr))
{
return SQualifierResult::Qualified("local");
}
if (auto Def = AsNullable<CExprDefinition>(ItExpr))
{
if (Def->ValueDomain())
{
continue;
}
ULANG_ASSERT(Def->Value());
if (auto Module = AsNullable<CExprModuleDefinition>(Def->Value()))
{
return GetDefinitionQualifier(*Module->_SemanticModule->GetModule());
}
else if (auto Class = AsNullable<CExprClassDefinition>(Def->Value()))
{
return GetDefinitionQualifier(*Class->_Class._Definition);
}
else if (auto Interface = AsNullable<CExprInterfaceDefinition>(Def->Value()))
{
return GetDefinitionQualifier(Interface->_Interface);
}
else if (auto Enum = AsNullable<CExprEnumDefinition>(Def->Value()))
{
return GetDefinitionQualifier(Enum->_Enum);
}
else if (auto Scoped = AsNullable<CExprScopedAccessLevelDefinition>(Def->Value()))
{
return GetDefinitionQualifier(*Scoped->_AccessLevelDefinition);
}
return SQualifierResult::Failed();
}
if (auto DataDef = AsNullable<CExprDataDefinition>(ItExpr))
{
if (DataDef->Element() == Expr)
{
return GetDefinitionQualifier(*DataDef->_DataMember);
}
else if (auto Var = AsNullable<CExprVar>(DataDef->Element()))
{
if (Var->Operand() == Expr)
{
return GetDefinitionQualifier(*DataDef->_DataMember);
}
}
return SQualifierResult::Failed();
}
if (auto FuncDef = AsNullable<CExprFunctionDefinition>(ItExpr))
{
auto Invocation = AsNullable<CExprInvocation>(FuncDef->Element());
if (!Invocation)
{
return SQualifierResult::Failed();
}
if (Invocation->GetCallee() == Expr)
{
return GetDefinitionQualifier(*FuncDef->_Function);
}
else if (EContains Contains = ContainsExpr(Invocation->GetArgument(), AsNullable<CExprIdentifierUnresolved>(Expr)); Contains != EContains::No)
{
switch (Contains)
{
case EContains::Local: return SQualifierResult::Qualified("local");
case EContains::Path: return SQualifierResult::Failed();// Need to implement for qualified named parameters
case EContains::NotYet: return SQualifierResult::NotRequired();
case EContains::No: return SQualifierResult::Failed();
}
}
return SQualifierResult::Failed();
}
if (auto ModuleDef = AsNullable<CExprModuleDefinition>(ItExpr))
{
return GetQualifierPath(ModuleDef->_SemanticModule->GetModule()->AsQualifier());
}
}
return SQualifierResult::Failed();
case EAstNodeType::Invoke_Invocation:
case EAstNodeType::Literal_Enum:
// nb: these AST nodes either contain identifiers, or always come with context (eg. MyEnum.SomeCase)
// and can be ignored
return SQualifierResult::NotRequired();
case EAstNodeType::Invoke_ReferenceToValue:
return QualifierOf(AsNullable<CExprReferenceToValue>(Expr)->Operand(), VstIdentifier, Program);
case EAstNodeType::Invoke_PointerToReference:
return QualifierOf(AsNullable<CExprPointerToReference>(Expr)->Operand(), VstIdentifier, Program);
case EAstNodeType::Error_:
case EAstNodeType::Placeholder_:
case EAstNodeType::External:
case EAstNodeType::PathPlusSymbol:
case EAstNodeType::Literal_Number:
case EAstNodeType::Literal_Char:
case EAstNodeType::Literal_String:
case EAstNodeType::Literal_Path:
case EAstNodeType::Literal_Type:
case EAstNodeType::Literal_Function:
case EAstNodeType::Identifier_OverloadedFunction:
case EAstNodeType::Identifier_Local:
case EAstNodeType::Definition:
case EAstNodeType::MacroCall:
case EAstNodeType::Invoke_UnaryArithmetic:
case EAstNodeType::Invoke_BinaryArithmetic:
case EAstNodeType::Invoke_ShortCircuitAnd:
case EAstNodeType::Invoke_ShortCircuitOr:
case EAstNodeType::Invoke_LogicalNot:
case EAstNodeType::Invoke_Comparison:
case EAstNodeType::Invoke_QueryValue:
case EAstNodeType::Invoke_MakeArray:
case EAstNodeType::Invoke_MakeMap:
case EAstNodeType::Invoke_MakeTuple:
case EAstNodeType::Invoke_TupleElement:
case EAstNodeType::Invoke_MakeRange:
case EAstNodeType::Invoke_Type:
case EAstNodeType::Invoke_Set:
case EAstNodeType::Invoke_NewPointer:
case EAstNodeType::Assignment:
case EAstNodeType::Invoke_ArrayFormer:
case EAstNodeType::Invoke_GeneratorFormer:
case EAstNodeType::Invoke_MapFormer:
case EAstNodeType::Invoke_Subtype:
case EAstNodeType::Invoke_TupleType:
case EAstNodeType::Invoke_Arrow:
case EAstNodeType::Invoke_ArchetypeInstantiation:
case EAstNodeType::Flow_CodeBlock:
case EAstNodeType::Flow_Let:
case EAstNodeType::Flow_Defer:
case EAstNodeType::Flow_If:
case EAstNodeType::Flow_Iteration:
case EAstNodeType::Flow_Loop:
case EAstNodeType::Flow_Break:
case EAstNodeType::Flow_Return:
case EAstNodeType::Flow_ProfileBlock:
case EAstNodeType::Ir_For:
case EAstNodeType::Ir_ForBody:
case EAstNodeType::Ir_ArrayAdd:
case EAstNodeType::Ir_MapAdd:
case EAstNodeType::Ir_ArrayUnsafeCall:
case EAstNodeType::Ir_ConvertToDynamic:
case EAstNodeType::Ir_ConvertFromDynamic:
case EAstNodeType::Concurrent_Sync:
case EAstNodeType::Concurrent_Rush:
case EAstNodeType::Concurrent_Race:
case EAstNodeType::Concurrent_SyncIterated:
case EAstNodeType::Concurrent_RushIterated:
case EAstNodeType::Concurrent_RaceIterated:
case EAstNodeType::Concurrent_Branch:
case EAstNodeType::Concurrent_Spawn:
case EAstNodeType::Definition_Module:
case EAstNodeType::Definition_Enum:
case EAstNodeType::Definition_Interface:
case EAstNodeType::Definition_Class:
case EAstNodeType::Definition_Data:
case EAstNodeType::Definition_IterationPair:
case EAstNodeType::Definition_Function:
case EAstNodeType::Definition_TypeAlias:
case EAstNodeType::Definition_Using:
case EAstNodeType::Definition_Import:
case EAstNodeType::Definition_Where:
case EAstNodeType::Definition_Var:
case EAstNodeType::Definition_ScopedAccessLevel:
case EAstNodeType::Invoke_MakeNamed:
case EAstNodeType::Context_Project:
case EAstNodeType::Context_CompilationUnit:
case EAstNodeType::Context_Package:
case EAstNodeType::Context_Snippet:
case EAstNodeType::Invoke_OptionFormer:
default:
return SQualifierResult::Failed();
}
}
TArray<VstSharedRef> QualifyAllAnalyzedIdentifiers(bool bVerbose, const TSRef<CSemanticProgram>& Program, VstSharedRef& Root)
{
TArray<VstSharedRef> IdentifiersFailed;
uLang::TArray<VstSharedRef> Stack;
Stack.Add(Root);
while (!Stack.IsEmpty())
{
VstSharedRef CurrentNode = Stack.Pop();
for (const VstSharedRef& Child : CurrentNode->GetChildren())
{
Stack.Add(Child);
}
auto VstIdentifier = CurrentNode->AsNullable<Verse::Vst::Identifier>();
if (!VstIdentifier || !VstIdentifier->CanBeQualified())
{
continue;
}
if (VstIdentifier->IsQualified())
{
continue;
}
const CAstNode* AstNode = VstIdentifier->GetMappedAstNode();
if (!AstNode)
{
continue;
}
const CExpressionBase* Expr = AstNode->AsExpression();
const SQualifierResult& Qualifier = QualifierOf(Expr, VstIdentifier, *Program);
switch (Qualifier.Status)
{
case SQualifierResult::EStatus::Qualified:
VstIdentifier->AddQualifier(Qualifier.String);
break;
case SQualifierResult::EStatus::NotRequired:
break;
case SQualifierResult::EStatus::Failed:
if (bVerbose)
{
const Verse::SLocus& Locus = VstIdentifier->Whence();
ULANG_LOGF(Display, "failed: %s %d:%d-%d:%d %s (%s)",
VstIdentifier->GetSnippetPath().AsCString(),
Locus.BeginRow(), Locus.BeginColumn(), Locus.EndRow(), Locus.EndColumn(),
VstIdentifier->GetSourceCStr(),
AsStringLiteral(Expr->GetNodeType()));
}
IdentifiersFailed.Add(CurrentNode);
break;
case SQualifierResult::EStatus::Undefined:
// TODO Do something here?
default:
ULANG_UNREACHABLE();
}
}
if (IdentifiersFailed.Num() && bVerbose)
{
ULANG_LOGF(Display, "# Identifiers left to qualify: %d", IdentifiersFailed.Num());
}
return IdentifiersFailed;
}
} // Namespace uLang