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

2349 lines
100 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "uLang/SemanticAnalyzer/DigestGenerator.h"
#include "uLang/Common/Algo/Cases.h"
#include "uLang/CompilerPasses/CompilerTypes.h"
#include "uLang/Semantics/Attributable.h"
#include "uLang/Semantics/Expression.h"
#include "uLang/Semantics/ModuleAlias.h"
#include "uLang/Semantics/ScopedAccessLevelType.h"
#include "uLang/Semantics/SemanticClass.h"
#include "uLang/Semantics/SemanticEnumeration.h"
#include "uLang/Semantics/SemanticFunction.h"
#include "uLang/Semantics/SemanticInterface.h"
#include "uLang/Semantics/SmallDefinitionArray.h"
#include "uLang/Semantics/TypeAlias.h"
#include "uLang/Semantics/TypeVariable.h"
#include "uLang/Syntax/VstNode.h"
#include "uLang/Syntax/vsyntax_types.h"
using Verse::NullWhence;
namespace uLang
{
/// Helper class that does the actual digest generation
class CDigestGeneratorImpl
{
static constexpr int32_t NumNewLinesForSpacing = 2;
public:
CDigestGeneratorImpl(
const CSemanticProgram& Program,
const CAstPackage& Package,
const TSRef<CDiagnostics>& Diagnostics,
const CUTF8String* Notes,
bool bIncludeInternalDefinitions,
bool bIncludeEpicInternalDefinitions)
: _Program(Program)
, _Package(Package)
, _Diagnostics(Diagnostics)
, _bIncludeInternalDefinitions(bIncludeInternalDefinitions)
, _bIncludeEpicInternalDefinitions(bIncludeEpicInternalDefinitions)
, _CurrentModule(Package._RootModule->GetModule())
, _CurrentScope(Package._RootModule)
, _CurrentGlitchAst(nullptr)
, _Underscore(Program.GetSymbols()->AddChecked("_"))
, _Notes(Notes)
{
BuildSymbolMap();
}
bool Generate(CUTF8String& OutDigestCode, TArray<const CAstPackage*>& OutDigestPackageDependencies) const
{
using namespace Verse::Vst;
TSRef<Snippet> DigestSnippet = TSRef<Snippet>::New(_Package._Name);
_Usings.Reset();
// Do the actual generation work
GenerateForScope(*_Package._RootModule->GetModule(), DigestSnippet);
// Prepend a list of required using declarations
for (const CUTF8String& UsingPath : _Usings)
{
TSRef<Macro> UsingMacro = TSRef<Macro>::New(
NullWhence(),
GenerateUseOfIntrinsic("using"),
ClauseArray{ TSRef<Clause>::New(
TSRef<PathLiteral>::New(UsingPath, NullWhence()).As<Node>(),
NullWhence(),
Clause::EForm::NoSemicolonOrNewline) });
UsingMacro->SetNewLineAfter(true);
DigestSnippet->AppendChildAt(UsingMacro, 0);
}
// Finally, generate the code
OutDigestCode = Verse::PrettyPrintVst(DigestSnippet);
// If digest is empty, make it clear there was no error but there was in fact nothing to export
if (OutDigestCode.IsEmpty())
{
OutDigestCode = "# This digest intentionally left blank.\n";
}
if (_Notes && !_Notes->IsEmpty())
{
OutDigestCode = *_Notes + "\n" + OutDigestCode;
}
for (const CAstPackage* DependencyPackage : _DependencyPackages)
{
OutDigestPackageDependencies.Add(DependencyPackage);
}
return !_Diagnostics->HasErrors();
}
private:
bool GenerateForScope(const CLogicalScope& Scope, const TSRef<Verse::Vst::Node>& Parent) const
{
TGuardValue<const CScope*> CurrentScopeGuard(_CurrentScope, &Scope);
bool bGeneratedAnything = false;
for (const TSRef<CDefinition>& Definition : Scope.GetDefinitions())
{
switch (Definition->GetKind())
{
case CDefinition::EKind::Class:
{
const CClass* cls = static_cast<const CClass*>(&Definition->AsChecked<CClassDefinition>());
if (cls->IsSubclassOf(*_Program._scopedClass))
{
bGeneratedAnything |= GenerateForScopedAccessLevel(Definition->AsChecked<CScopedAccessLevelDefinition>(), Parent);
}
else
{
bGeneratedAnything |= GenerateDefinitionForClass(Definition->AsChecked<CClassDefinition>(), Parent);
}
}
break;
case CDefinition::EKind::Data: bGeneratedAnything |= GenerateForDataDefinition(Definition->AsChecked<CDataDefinition>(), Parent); break;
case CDefinition::EKind::Enumeration: bGeneratedAnything |= GenerateForEnumeration(Definition->AsChecked<CEnumeration>(), Parent); break;
case CDefinition::EKind::Enumerator: bGeneratedAnything |= GenerateForEnumerator(Definition->AsChecked<CEnumerator>(), Parent); break;
case CDefinition::EKind::Function: bGeneratedAnything |= GenerateForFunction(Definition->AsChecked<CFunction>(), Parent); break;
case CDefinition::EKind::Interface: bGeneratedAnything |= GenerateDefinitionForInterface(Definition->AsChecked<CInterface>(), Parent); break;
case CDefinition::EKind::Module: bGeneratedAnything |= GenerateForModule(Definition->AsChecked<CModule>(), Parent); break;
case CDefinition::EKind::ModuleAlias: bGeneratedAnything |= GenerateForModuleAlias(Definition->AsChecked<CModuleAlias>(), Parent); break;
case CDefinition::EKind::TypeAlias: bGeneratedAnything |= GenerateForTypeAlias(Definition->AsChecked<CTypeAlias>(), Parent); break;
case CDefinition::EKind::TypeVariable: /* TODO */ break;
default: ULANG_UNREACHABLE();
}
}
// NOTE: (yiliang.siew) So that things appear nicely in digests, we do not set extra newlines
// on the last definition so that the parent definition can handle adding the newline instead
// of "doubling up" on newlines - since we want to have consistent spacing between definitions.
if (bGeneratedAnything)
{
const TSRef<Verse::Vst::Node>& GeneratedDefinition = Parent->AccessChildren().Last();
const int32_t NumNewLinesAfter = GeneratedDefinition->NumNewLinesAfter() - NumNewLinesForSpacing;
GeneratedDefinition->SetNumNewLinesAfter(NumNewLinesAfter < 0 ? 0 : NumNewLinesAfter);
}
return bGeneratedAnything;
}
bool GenerateForModule(const CModule& Module, const TSRef<Verse::Vst::Node>& Parent) const
{
using namespace Verse::Vst;
if (!ShouldGenerate(Module, false))
{
return false;
}
TGuardValue<const CModule*> CurrentModuleGuard(_CurrentModule, &Module);
TSRef<Clause> InnerClause = TSRef<Clause>::New(NullWhence(), Clause::EForm::HasSemicolonOrNewline);
if (!GenerateForScope(Module, InnerClause))
{
// TODO: (yiliang.siew) This hack is so that we don't produce module definitions for modules whose clauses
// are empty. We should probably just always prune modules that have no significant children (i.e. comments, empty modules, etc.).
// This flag is always set for the asset manifest.
if (_Package._bTreatModulesAsImplicit)
{
return false;
}
const bool bModuleHasPartInThisPackage = Module.GetParts().ContainsByPredicate([this](const CModulePart* ModulePart){return ModulePart->GetIrPackage() == &_Package;});
if (!bModuleHasPartInThisPackage)
{
return false;
}
}
// Generate definition for this module
InnerClause->SetNewLineAfter(true);
TSRef<Identifier> Name = GenerateDefinitionIdentifier(Module);
TSRef<Definition> DefinitionVst = TSRef<Definition>::New(
NullWhence(),
Name,
TSRef<Macro>::New(
NullWhence(),
GenerateUseOfIntrinsic("module"),
ClauseArray{ InnerClause }));
const CUTF8String ModuleImportPath = Module.GetScopePath('/', uLang::CScope::EPathMode::PrefixSeparator).AsCString();
const CUTF8StringView View(ModuleImportPath.ToStringView());
const CUTF8StringView LocalHost = "/localhost";
if (!View.StartsWith(LocalHost))
{
// Create a convenience full path comment for the module
TSRef<Comment> ImportPathComment = TSRef<Comment>::New(
Comment::EType::line,
CUTF8String("# Module import path: %s", *ModuleImportPath),
NullWhence());
ImportPathComment->SetNewLineAfter(true);
DefinitionVst->AppendPrefixComment(ImportPathComment);
}
DefinitionVst->SetNumNewLinesAfter(NumNewLinesForSpacing);
GenerateForAttributes(Module, Name, DefinitionVst);
Parent->AppendChild(DefinitionVst);
return true;
}
bool GenerateForModuleAlias(const CModuleAlias& ModuleAlias, const TSRef<Verse::Vst::Node>& Parent) const
{
using namespace Verse::Vst;
if (!ShouldGenerate(ModuleAlias, true))
{
return false;
}
TGuardValue<const CAstNode*> GlitchAstGuard(_CurrentGlitchAst, ModuleAlias.GetAstNode());
TSRef<Identifier> Name = GenerateDefinitionIdentifier(ModuleAlias);
TSRef<PrePostCall> Call = TSRef<PrePostCall>::New(NullWhence());
TSRef<Identifier> ImportIdentifier = GenerateUseOfIntrinsic("import");
ImportIdentifier->SetTag(PrePostCall::Op::Expression);
Call->AppendChild(ImportIdentifier);
TSRef<Clause> Arguments = TSRef<Clause>::New((uint8_t)PrePostCall::Op::SureCall, NullWhence(), Clause::EForm::NoSemicolonOrNewline);
Call->AppendChild(Arguments);
Arguments->AppendChild(TSRef<PathLiteral>::New(ModuleAlias.Module()->GetScopePath('/', CScope::EPathMode::PrefixSeparator), NullWhence()));
TSRef<Definition> DefinitionVst = TSRef<Definition>::New(NullWhence(), Name, Call);
DefinitionVst->SetNumNewLinesAfter(NumNewLinesForSpacing);
GenerateForAttributes(ModuleAlias, Name, DefinitionVst);
Parent->AppendChild(DefinitionVst);
return true;
}
bool GenerateForTypeAlias(const CTypeAlias& TypeAlias, const TSRef<Verse::Vst::Node>& Parent) const
{
using namespace Verse::Vst;
if (!ShouldGenerate(TypeAlias, true))
{
return false;
}
TGuardValue<const CAstNode*> GlitchAstGuard(_CurrentGlitchAst, TypeAlias.GetAstNode());
TSRef<Identifier> Name = GenerateDefinitionIdentifier(TypeAlias);
TSRef<Node> Type = GenerateForType(TypeAlias.GetPositiveAliasedType());
TSRef<Definition> DefinitionVst = TSRef<Definition>::New(NullWhence(), Name, Type);
DefinitionVst->SetNumNewLinesAfter(NumNewLinesForSpacing);
GenerateForAttributes(TypeAlias, Name, DefinitionVst);
Parent->AppendChild(DefinitionVst);
return true;
}
TArray<TSPtr<Verse::Vst::Node>> GenerateForSuperType(const CExpressionBase& SuperType, TArray<const CInterface*>& VisitedPublicSuperInterfaces) const
{
using namespace Verse::Vst;
// Properly generate qualifiers for a subset of syntax.
TArray<TSPtr<Verse::Vst::Node>> Ret;
if (SuperType.GetNodeType() == EAstNodeType::Identifier_Class)
{
const CExprIdentifierClass& SuperClassIdentifier = static_cast<const CExprIdentifierClass&>(SuperType);
const CClass* SuperClass = SuperClassIdentifier.GetClass(_Program);
TArray<const CNominalType*> PublicSuperTypes = PublifyType(SuperClass, VisitedPublicSuperInterfaces);
if (PublicSuperTypes.Num())
{
// If the type we are trying to publify is already public we just take it
if (SuperClass == PublicSuperTypes[0])
{
TSRef<Identifier> SuperClassId = GenerateUseOfDefinition(*SuperClass->Definition());
Ret.Add(Move(SuperClassId));
}
else
{
for (const CNominalType* PublicType : PublicSuperTypes)
{
Ret.Add(GenerateForType(PublicType));
}
}
}
}
else if (SuperType.GetNodeType() == EAstNodeType::Identifier_Interface)
{
const CExprInterfaceType& SuperInterfaceIdentifier = static_cast<const CExprInterfaceType&>(SuperType);
const CInterface* SuperInterface = SuperInterfaceIdentifier.GetInterface(_Program);
TArray<const CNominalType*> PublicSuperTypes = PublifyType(SuperInterface, VisitedPublicSuperInterfaces);
if (PublicSuperTypes.Num())
{
// If the type we are trying to publify is already public we just take it
if (SuperInterface == PublicSuperTypes[0])
{
TSRef<Identifier> SuperInterfaceId = GenerateUseOfDefinition(*SuperInterface);
Ret.Add(Move(SuperInterfaceId));
}
else
{
for (const CNominalType* PublicType : PublicSuperTypes)
{
Ret.Add(GenerateForType(PublicType));
}
}
}
}
// Fall back to generating unqualified types.
if (Ret.Num() == 0)
{
const CTypeBase* SuperResultType = SuperType.GetResultType(_Program);
const CTypeType& SuperTypeType = SuperResultType->GetNormalType().AsChecked<CTypeType>();
const CNormalType& SuperNormalType = SuperTypeType.PositiveType()->GetNormalType();
if (SuperNormalType.AsNullable<CClass>() || SuperNormalType.AsNullable<CInterface>())
{
TArray<const CNominalType*> PublicSuperTypes = PublifyType(&SuperNormalType, VisitedPublicSuperInterfaces);
for (const CNominalType* PublicType : PublicSuperTypes)
{
Ret.Add(GenerateForType(PublicType));
}
}
else
{
return { GenerateForType(SuperTypeType.PositiveType()) };
}
}
return Ret;
}
TSRef<Verse::Vst::Macro> GenerateMacroForClassOrInterface(
const CUTF8StringView& MacroName,
const CLogicalScope& MemberScope,
const TArray<TSRef<CExpressionBase>>& SuperTypes,
const CAttributable* EffectsAttributable,
const TOptional<SAccessLevel>& ConstructorAccessLevel) const
{
using namespace Verse::Vst;
// Create the class/interface macro.
TSRef<Clause> InnerClause = TSRef<Clause>::New(NullWhence(), Clause::EForm::HasSemicolonOrNewline);
InnerClause->SetNewLineAfter(true);
TSRef<Identifier> ClassId = GenerateUseOfIntrinsic(MacroName);
TSRef<Identifier> ClassIdCopy = ClassId;
TSRef<Macro> ClassMacro = TSRef<Macro>::New(
NullWhence(),
Move(ClassIdCopy),
ClauseArray{InnerClause});
if (!SuperTypes.IsEmpty())
{
// Create the super clause.
TSRef<Clause> SuperClause = TSRef<Clause>::New(vsyntax::res_of, NullWhence(), Clause::EForm::NoSemicolonOrNewline);
SuperClause->SetNewLineAfter(true);
bool bEmptySuperClause = true;
TArray<const CInterface*> VisitedSuperInterface;
for (const TSRef<CExpressionBase>& SuperType : SuperTypes)
{
for (TSPtr<Node> SuperNode : GenerateForSuperType(*SuperType, VisitedSuperInterface))
{
SuperClause->AppendChild(Move(SuperNode.AsRef()));
bEmptySuperClause = false;
}
}
// Append the super clause after the macro name.
if (!bEmptySuperClause)
{
ClassMacro->AppendChildAt(SuperClause, 1);
}
}
if (EffectsAttributable)
{
GenerateForAttributes(*EffectsAttributable, ConstructorAccessLevel, ClassId);
}
// And process its members
GenerateForScope(MemberScope, InnerClause);
return ClassMacro;
}
bool GenerateDefinitionForClassOrInterface(
const CUTF8StringView& MacroName,
const CLogicalScope& MemberScope,
const CDefinition& DefinitionAst,
const TArray<TSRef<CExpressionBase>>& SuperTypes,
const TSRef<Verse::Vst::Node>& Parent,
const CAttributable* EffectsAttributable,
const TOptional<SAccessLevel>& ConstructorAccessLevel) const
{
using namespace Verse::Vst;
TGuardValue<const CAstNode*> GlitchAstGuard(_CurrentGlitchAst, DefinitionAst.GetAstNode());
// Create the class/interface definition.
TSRef<Identifier> Name = GenerateDefinitionIdentifier(DefinitionAst);
TSRef<Macro> ClassMacro = GenerateMacroForClassOrInterface(MacroName, MemberScope, SuperTypes, EffectsAttributable, ConstructorAccessLevel);
TSRef<Definition> DefinitionVst = TSRef<Definition>::New(NullWhence(), Name, ClassMacro);
DefinitionVst->SetNumNewLinesAfter(NumNewLinesForSpacing);
GenerateForAttributes(DefinitionAst, Name, DefinitionVst);
Parent->AppendChild(DefinitionVst);
return true;
}
bool GenerateDefinitionForClass(const CClassDefinition& Class, const TSRef<Verse::Vst::Node>& Parent) const
{
// Cull inaccessible classes
if (!ShouldGenerate(Class))
{
return false;
}
return GenerateDefinitionForClassOrInterface(
Class.IsStruct()? "struct" : "class",
Class,
Class,
Class.GetIrNode()->SuperTypes(),
Parent,
&Class._EffectAttributable,
Class._ConstructorAccessLevel);
}
TSRef<Verse::Vst::Macro> GenerateMacroForClass(const CClassDefinition& Class) const
{
TGuardValue<const CAstNode*> GlitchAstGuard(_CurrentGlitchAst, Class.GetAstNode());
return GenerateMacroForClassOrInterface(
Class.IsStruct()? "struct" : "class",
Class,
Class.GetIrNode()->SuperTypes(),
&Class._EffectAttributable,
Class._ConstructorAccessLevel);
}
bool GenerateDefinitionForInterface(const CInterface& Interface, const TSRef<Verse::Vst::Node>& Parent) const
{
// Cull inaccessible classes
if (!ShouldGenerate(Interface))
{
return false;
}
return GenerateDefinitionForClassOrInterface(
"interface",
Interface,
Interface,
Interface.GetIrNode()->SuperInterfaces(),
Parent,
&Interface._EffectAttributable,
Interface._ConstructorAccessLevel);
}
TSRef<Verse::Vst::Macro> GenerateMacroForInterface(const CInterface& Interface) const
{
TGuardValue<const CAstNode*> GlitchAstGuard(_CurrentGlitchAst, Interface.GetAstNode());
return GenerateMacroForClassOrInterface(
"interface",
Interface,
Interface.GetIrNode()->SuperInterfaces(),
&Interface._EffectAttributable,
Interface._ConstructorAccessLevel);
}
bool GenerateForEnumerator(const CEnumerator& Enumerator, const TSRef<Verse::Vst::Node>& Parent) const
{
using namespace Verse::Vst;
// Cull inaccessible enumerations
if (!ShouldGenerate(Enumerator))
{
return false;
}
// We check here if there are any `@doc` attributes and convert them to comments as well.
const TSRef<Identifier> EnumIdentifier = GenerateDefinitionIdentifier(Enumerator);
GenerateForAttributes(Enumerator, Enumerator.SelfAccessLevel(), EnumIdentifier);
EnumIdentifier->SetNewLineAfter(true);
Parent->AppendChild(EnumIdentifier);
return true;
}
bool GenerateForEnumeration(const CEnumeration& Enumeration, const TSRef<Verse::Vst::Node>& Parent) const
{
using namespace Verse::Vst;
// Cull inaccessible enumerations
if (!ShouldGenerate(Enumeration))
{
return false;
}
TGuardValue<const CAstNode*> GlitchAstGuard(_CurrentGlitchAst, Enumeration.GetAstNode());
// Create enum definition
TSRef<Clause> InnerClause = TSRef<Clause>::New(NullWhence(), Clause::EForm::HasSemicolonOrNewline);
InnerClause->SetNewLineAfter(true); // If to use vertical format
GenerateForScope(Enumeration, InnerClause);
TSRef<Identifier> Name = GenerateDefinitionIdentifier(Enumeration);
TSRef<Identifier> EnumIdentifierVst = GenerateUseOfIntrinsic("enum");
GenerateForAttributes(
Enumeration._EffectAttributable,
TOptional<SAccessLevel>{},
EnumIdentifierVst);
TSRef<Definition> DefinitionVst = TSRef<Definition>::New(
NullWhence(),
Name,
TSRef<Macro>::New(
NullWhence(),
EnumIdentifierVst,
ClauseArray{ InnerClause }));
DefinitionVst->SetNumNewLinesAfter(NumNewLinesForSpacing);
GenerateForAttributes(Enumeration, Name, DefinitionVst);
Parent->AppendChild(DefinitionVst);
return true;
}
bool GenerateForScopedPaths(const CScopedAccessLevelDefinition& ScopedAccessLevel, const TSRef<Verse::Vst::Node>& Parent) const
{
using namespace Verse::Vst;
Verse::Vst::ClauseArray NewClauses;
for (const CScope* Scope : ScopedAccessLevel._Scopes)
{
const bool bEnclosingScopeIsProgram = Scope->ScopeAsDefinition()->_EnclosingScope.ScopeAsDefinition() == _Program.ScopeAsDefinition();
CUTF8String PathString = bEnclosingScopeIsProgram ? Scope->GetScopePath('/', CScope::EPathMode::PrefixSeparator)
: static_cast<CUTF8String>(GetDependencyName(*Scope->ScopeAsDefinition()));
TSRef<PathLiteral> NewPathLiteral = TSRef<PathLiteral>::New(Move(PathString), NullWhence());
// The syntax should be something like `scoped {/Verse.org`}, we don't want any newlines after the
// path literal.
NewPathLiteral->SetNumNewLinesAfter(0);
Parent->AppendChild(NewPathLiteral);
}
return true;
}
const TSRef<Verse::Vst::Macro> GenerateForScopedMacro(const CScopedAccessLevelDefinition& ScopedAccessLevel) const
{
using namespace Verse::Vst;
// Create access level definition
TSRef<Clause> InnerClause = TSRef<Clause>::New(NullWhence(), Clause::EForm::HasSemicolonOrNewline);
InnerClause->SetNewLineAfter(false);
GenerateForScopedPaths(ScopedAccessLevel, InnerClause);
return TSRef<Macro>::New(
NullWhence(),
GenerateUseOfIntrinsic("scoped"),
ClauseArray{ InnerClause });
}
bool GenerateForScopedAccessLevel(const CScopedAccessLevelDefinition& ScopedAccessLevel, const TSRef<Verse::Vst::Node>& Parent) const
{
using namespace Verse::Vst;
// Cull inaccessible access levels
if (!ShouldGenerate(ScopedAccessLevel))
{
return false;
}
TGuardValue<const CAstNode*> GlitchAstGuard(_CurrentGlitchAst, ScopedAccessLevel.GetAstNode());
TSRef<Identifier> ScopedDefinitionName = GenerateDefinitionIdentifier(ScopedAccessLevel);
TSRef<Definition> DefinitionVst = TSRef<Definition>::New(
NullWhence(),
ScopedDefinitionName,
GenerateForScopedMacro(ScopedAccessLevel));
DefinitionVst->SetNewLineAfter(true);
GenerateForAttributes(ScopedAccessLevel, ScopedDefinitionName, DefinitionVst);
Parent->AppendChild(DefinitionVst);
return true;
}
bool GenerateForFunction(const CFunction& Function, const TSRef<Verse::Vst::Node>& Parent) const
{
using namespace Verse::Vst;
// Cull inaccessible functions
if (!ShouldGenerate(Function) || Function.IsCoercion())
{
return false;
}
TGuardValue<const CAstNode*> GlitchAstGuard(_CurrentGlitchAst, Function.GetAstNode());
// Create function definition
TSRef<PrePostCall> Call = TSRef<PrePostCall>::New(NullWhence());
const CUTF8StringView FunctionNameStringView = Function._ExtensionFieldAccessorKind == EExtensionFieldAccessorKind::ExtensionMethod
? _Program._IntrinsicSymbols.StripExtensionFieldOpName(Function.GetName())
: Function.AsNameStringView();
TSRef<Identifier> FunctionName = GenerateDefinitionIdentifier(FunctionNameStringView, Function);
TSRef<Clause> ParameterList = GenerateForParameters(Function);
switch (Function._ExtensionFieldAccessorKind)
{
case EExtensionFieldAccessorKind::Function:
{
FunctionName->SetTag(PrePostCall::Op::Expression);
Call->AppendChild(FunctionName);
Call->AppendChild(ParameterList);
break;
}
case EExtensionFieldAccessorKind::ExtensionDataMember:
{
_Diagnostics->AppendGlitch(
SGlitchResult(EDiagnostic::ErrDigest_Unimplemented, uLang::CUTF8String("Extension data members are not implemented yet.")),
_CurrentGlitchAst);
break;
}
case EExtensionFieldAccessorKind::ExtensionMethod:
{
FunctionName->SetTag(PrePostCall::Op::DotIdentifier);
TSRef<Clause> LhsParameter = TSRef<Clause>::New(NullWhence(), Clause::EForm::NoSemicolonOrNewline);
LhsParameter->SetTag(PrePostCall::Op::SureCall);
TSRef<PrePostCall> DotCall = TSRef<PrePostCall>::New(NullWhence());
DotCall->AppendChild(LhsParameter);
DotCall->AppendChild(FunctionName);
Call->AppendChild(DotCall);
if (ParameterList->GetChildCount() == 1)
{
TSRef<Node> Child = ParameterList->TakeChildAt(0);
ULANG_ASSERT(Child->IsA<Where>());
TSRef<Where> WhereNode = Child.As<Where>();
LhsParameter->AppendChild(WhereNode);
TSRef<Node> Lhs = WhereNode->GetLhs();
ULANG_ASSERT(Lhs->IsA<Parens>());
ULANG_ASSERT(Lhs->GetChildCount() == 2);
WhereNode->SetLhs(Lhs->TakeChildAt(0));
Call->AppendChild(AsClause(Lhs->TakeChildAt(0)));
}
else
{
ULANG_ASSERT(ParameterList->GetChildCount() == 2);
LhsParameter->AppendChild(ParameterList->TakeChildAt(0));
Call->AppendChild(AsClause(ParameterList->TakeChildAt(0)));
}
break;
}
default:
ULANG_ERRORF("Missing an alternative in switch.");
}
// Is there an implementation?
if (const CExpressionBase* Body = Function.GetBodyIr())
{
if (Body->GetNodeType() == EAstNodeType::Definition_Class)
{
const CClass& Class = static_cast<const CExprClassDefinition&>(*Body)._Class;
TSRef<Definition> DefinitionVst = TSRef<Definition>::New(
NullWhence(),
Call,
GenerateMacroForClass(*Class._Definition));
DefinitionVst->SetNumNewLinesAfter(NumNewLinesForSpacing);
TArray<SAttribute> Attributes = Function._Attributes;
if (TOptional<SAttribute> NativeAttribute = Class.FindAttribute(_Program._nativeClass, _Program))
{
Attributes.Push(Move(*NativeAttribute));
}
GenerateForAttributes(Attributes, Function, FunctionName, DefinitionVst);
Parent->AppendChild(DefinitionVst);
}
else if (Body->GetNodeType() == EAstNodeType::Definition_Interface)
{
TSRef<Definition> DefinitionVst = TSRef<Definition>::New(
NullWhence(),
Call,
GenerateMacroForInterface(static_cast<const CExprInterfaceDefinition&>(*Body)._Interface));
DefinitionVst->SetNumNewLinesAfter(NumNewLinesForSpacing);
GenerateForAttributes(Function, FunctionName, DefinitionVst);
Parent->AppendChild(DefinitionVst);
}
else
{
const CTypeBase* ReturnType = Function._Signature.GetReturnType();
if (const CTypeType* ReturnTypeType = ReturnType->GetNormalType().AsNullable<CTypeType>();
ReturnTypeType && !Function.GetReturnTypeIr() && Function._Signature.GetEffects() == EffectSets::Computes)
{
TSRef<Definition> DefinitionVst = TSRef<Definition>::New(
NullWhence(),
Call,
GenerateForType(ReturnTypeType->PositiveType()));
DefinitionVst->SetNumNewLinesAfter(NumNewLinesForSpacing);
GenerateForAttributes(Function, FunctionName, DefinitionVst);
Parent->AppendChild(DefinitionVst);
}
else
{
TSRef<TypeSpec> TypedCall = TSRef<TypeSpec>::New(
NullWhence(),
Call,
GenerateForType(Function._Signature.GetReturnType()));
// Generate an assignment to the external{} macro
TSRef<Definition> DefinitionVst = TSRef<Definition>::New(
NullWhence(),
TypedCall,
GenerateExternalMacro());
DefinitionVst->SetNumNewLinesAfter(NumNewLinesForSpacing);
GenerateForAttributes(Function, FunctionName, DefinitionVst);
Parent->AppendChild(DefinitionVst);
GenerateForEffectAttributes(Function._Signature.GetFunctionType()->GetEffects(), EffectSets::FunctionDefault, *Call);
}
}
}
else
{
TSRef<TypeSpec> TypedCall = TSRef<TypeSpec>::New(
NullWhence(),
Call,
GenerateForType(Function._Signature.GetReturnType()));
// No, just generate the function declaration by itself
TypedCall->SetNumNewLinesAfter(NumNewLinesForSpacing);
GenerateForAttributes(Function, FunctionName, TypedCall);
Parent->AppendChild(TypedCall);
GenerateForEffectAttributes(Function._Signature.GetFunctionType()->GetEffects(), EffectSets::FunctionDefault, *Call);
}
return true;
}
bool GenerateForDataDefinition(const CDataDefinition& DataDefinition, const TSRef<Verse::Vst::Node>& Parent) const
{
using namespace Verse::Vst;
// Cull inaccessible data definitions
if (!ShouldGenerate(DataDefinition))
{
return false;
}
TGuardValue<const CAstNode*> GlitchAstGuard(_CurrentGlitchAst, DataDefinition.GetAstNode());
// Create data definition
CSymbol Name = DataDefinition.GetName();
TSRef<Identifier> NameNode = GenerateDefinitionIdentifier(DataDefinition);
TSPtr<Node> DecoratedNode;
const CTypeBase* Type = DataDefinition.GetType();
if (DataDefinition.IsVar())
{
DecoratedNode = TSRef<Mutation>::New(NullWhence(), NameNode, Mutation::EKeyword::Var);
GenerateForAttributesGeneric({ }, DataDefinition.SelfVarAccessLevel(), [&DecoratedNode] (const CClass*) { return DecoratedNode.AsRef(); });
Type = Type->GetNormalType().AsChecked<CPointerType>().PositiveValueType();
}
else
{
DecoratedNode = NameNode;
}
TSRef<TypeSpec> TypeSpecNode = TSRef<TypeSpec>::New(NullWhence(), Move(DecoratedNode.AsRef()), GenerateForType(Type));
// Is there a default value?
if (DataDefinition.GetIrNode()->Value().IsValid())
{
// Yes, generate an assignment to the external{} macro
TSRef<Definition> DefinitionVst = TSRef<Definition>::New(
NullWhence(),
TypeSpecNode,
TSRef<Macro>::New(NullWhence(), GenerateUseOfIntrinsic("external"), ClauseArray{ TSRef<Clause>::New(NullWhence(), Clause::EForm::NoSemicolonOrNewline) }));
DefinitionVst->SetNumNewLinesAfter(NumNewLinesForSpacing);
GenerateForAttributes(DataDefinition, NameNode, DefinitionVst);
Parent->AppendChild(DefinitionVst);
}
else
{
// No, just generate the type spec by itself
TypeSpecNode->SetNumNewLinesAfter(NumNewLinesForSpacing);
GenerateForAttributes(DataDefinition, NameNode, TypeSpecNode);
Parent->AppendChild(TypeSpecNode);
}
return true;
}
TSRef<Verse::Vst::Clause> GenerateForParameters(const CFunction& Function) const
{
using namespace Verse::Vst;
const CExprFunctionDefinition* FunctionDefinition = Function.GetIrNode();
const TSPtr<CExpressionBase>& Element = FunctionDefinition->Element();
ULANG_ASSERTF(Element, "Function definition IR node must have an element.");
ULANG_ASSERTF(Element->GetNodeType() == EAstNodeType::Invoke_Invocation, "Function definition element IR node must be an invocation.");
const CExprInvocation& Invocation = static_cast<const CExprInvocation&>(*Element);
auto ParamDefinitionIterator = Function._Signature.GetParams().begin();
TSRef<Node> Parameter = GenerateForParameter(*Invocation.GetArgument(), ParamDefinitionIterator);
TArray<TSRef<Node>> TypeVariableDefinition;
for (const TSRef<CTypeVariable>& TypeVariable : Function.GetDefinitionsOfKind<CTypeVariable>())
{
if (TypeVariable->_ExplicitParam)
{
continue;
}
TypeVariableDefinition.Add(TSRef<TypeSpec>::New(
NullWhence(),
GenerateDefinitionIdentifier(*TypeVariable->Definition()),
GenerateForType(TypeVariable->GetPositiveType())));
}
return AsClause(TypeVariableDefinition.IsEmpty()?
Move(Parameter) :
TSRef<Where>::New(NullWhence(), Move(Parameter), Move(TypeVariableDefinition)));
}
template <typename TParamDefinitionIterator>
TSRef<Verse::Vst::Node> GenerateForParameter(
const CExpressionBase& Expression,
TParamDefinitionIterator& ParamDefinitionIterator) const
{
using namespace Verse::Vst;
if (Expression.GetNodeType() == EAstNodeType::Invoke_MakeTuple)
{
TSRef<Parens> Result = TSRef<Parens>::New(NullWhence(), Clause::EForm::NoSemicolonOrNewline);
const CExprMakeTuple& ExprMakeTuple = static_cast<const CExprMakeTuple&>(Expression);
for (const TSPtr<CExpressionBase>& SubExpr : ExprMakeTuple.GetSubExprs())
{
Result->AppendChild(GenerateForParameter(*SubExpr, ParamDefinitionIterator));
}
return Result;
}
else if (Expression.GetNodeType() == EAstNodeType::Definition_Where)
{
const CExprWhere& ExprWhere = static_cast<const CExprWhere&>(Expression);
// The right-hand side of `where` is handled separately. All
// `where` clauses are collapsed into a single `where`. Given
// bindings introduced by `where` are scoped to the entire domain,
// this shouldn't introduce any ambiguity.
return GenerateForParameter(*ExprWhere.Lhs(), ParamDefinitionIterator);
}
else
{
ULANG_ASSERTF(
Expression.GetNodeType() == EAstNodeType::Definition,
"Digest generation for '%s' is unimplemented.", Expression.GetErrorDesc().AsCString());
const CExprDefinition& ExprDefinition = static_cast<const CExprDefinition&>(Expression);
const CDataDefinition* ParamDefinition = *ParamDefinitionIterator;
++ParamDefinitionIterator;
TSPtr<Identifier> IdentifierNode;
const CTypeBase* Type;
const bool bNeverQualify = ParamDefinition->_bNamed; // TODO: qualified named parameters aren't handled by the desugarer yet.
if (const CTypeVariable* ImplicitParam = ParamDefinition->_ImplicitParam)
{
IdentifierNode = GenerateDefinitionIdentifier(*ImplicitParam, bNeverQualify);
Type = ImplicitParam->GetPositiveType();
}
else
{
IdentifierNode = GenerateDefinitionIdentifier(*ParamDefinition, bNeverQualify);
Type = ParamDefinition->GetType();
}
TSPtr<Node> ElementNode;
if (ParamDefinition->_bNamed)
{
TSRef<PrePostCall> QMarkNode = TSRef<PrePostCall>::New(IdentifierNode.AsRef(), NullWhence());
QMarkNode->PrependQMark(NullWhence());
ElementNode = Move(QMarkNode);
}
else
{
ElementNode = IdentifierNode;
}
TSRef<Node> Result = TSRef<TypeSpec>::New(
NullWhence(),
Move(ElementNode.AsRef()),
GenerateForType(Type));
if (ExprDefinition.Value())
{
Result = TSRef<Definition>::New(
NullWhence(),
Move(Result),
GenerateExternalMacro());
}
return Result;
}
}
static TSRef<Verse::Vst::Clause> AsClause(TSRef<Verse::Vst::Node> Node)
{
namespace Vst = Verse::Vst;
if (Node->IsA<Vst::Clause>())
{
return Node.As<Vst::Clause>();
}
if (const Vst::Parens* Parens = Node->AsNullable<Vst::Parens>())
{
TSRef<Vst::Clause> Clause = TSRef<Vst::Clause>::New(NullWhence(), Parens->GetForm());
Clause->SetTag(Vst::PrePostCall::Op::SureCall);
Vst::Node::TransferChildren(Node, Clause);
return Clause;
}
TSRef<Vst::Clause> Clause = TSRef<Vst::Clause>::New(NullWhence(), Vst::Clause::EForm::NoSemicolonOrNewline);
Clause->SetTag(Vst::PrePostCall::Op::SureCall);
Clause->AppendChild(Node);
return Clause;
}
TSRef<Verse::Vst::Clause> GenerateForParamTypes(CFunctionType::ParamTypes ParamTypes) const
{
using namespace Verse::Vst;
TSRef<Clause> ParamList = TSRef<Clause>::New(NullWhence(), Clause::EForm::NoSemicolonOrNewline);
for (const CTypeBase* ParamType : ParamTypes)
{
ParamList->AppendChild(TSRef<TypeSpec>::New(NullWhence(), GenerateForType(&SemanticTypeUtils::AsPositive(*ParamType, {}))));
}
// We assume this node will be used in a PrePostCall so set it up properly for that
ParamList->SetTag(PrePostCall::Op::SureCall);
return ParamList;
}
// Create VST node representing supplied tuple type
TSRef<Verse::Vst::PrePostCall> GenerateForTupleType(const CTupleType& Tuple) const
{
using namespace Verse::Vst;
TSRef<PrePostCall> Call = TSRef<PrePostCall>::New(NullWhence());
// Add `tuple` identifier
TSRef<Identifier> TupleIdent = GenerateUseOfIntrinsic("tuple");
TupleIdent->SetTag(PrePostCall::Op::Expression);
Call->AppendChild(TupleIdent);
// Add element types
TSRef<Clause> Elements = TSRef<Clause>::New(NullWhence(), Clause::EForm::NoSemicolonOrNewline);
for (const CTypeBase* ElementType : Tuple.GetElements())
{
Elements->AppendChild(GenerateForType(ElementType));
}
Elements->SetTag(PrePostCall::Op::SureCall);
Call->AppendChild(Elements);
return Call;
}
TSRef<Verse::Vst::Node> GenerateForIntrinsicInvocation(
const CUTF8StringView& CalleeName,
const TSRef<Verse::Vst::Node>& Argument) const
{
using namespace Verse::Vst;
TSRef<Identifier> CalleeIdentifier = GenerateUseOfIntrinsic(CalleeName);
CalleeIdentifier->SetTag(PrePostCall::Op::Expression);
Argument->SetTag(PrePostCall::Op::SureCall);
TSRef<PrePostCall> Call = TSRef<PrePostCall>::New(NullWhence());
Call->AppendChild(CalleeIdentifier);
Call->AppendChild(Argument);
return Call;
}
TSRef<Verse::Vst::Node> GenerateForIntrinsicTypeInvocation(
const CUTF8StringView& CalleeName,
const CTypeBase* TypeArgment) const
{
using namespace Verse::Vst;
return GenerateForIntrinsicInvocation(
CalleeName,
TSRef<Clause>::New(
GenerateForType(TypeArgment),
NullWhence(),
Clause::EForm::NoSemicolonOrNewline));
}
TSRef<Verse::Vst::Node> GenerateForSubtypeType(const CTypeBase* NegativeType, bool bCastableSubtype) const
{
return GenerateForIntrinsicTypeInvocation(bCastableSubtype ? "castable_subtype" : "subtype", NegativeType);
}
TSRef<Verse::Vst::Node> GenerateForSupertypeType(const CTypeBase* PositiveType) const
{
return GenerateForIntrinsicTypeInvocation("supertype", PositiveType);
}
TSRef<Verse::Vst::Node> GenerateForWeakMapType(const CTypeBase* KeyType, const CTypeBase* ValueType) const
{
using namespace Verse::Vst;
return GenerateForIntrinsicInvocation(
"weak_map",
TSRef<Clause>::New(
TSRefArray<Node>{GenerateForType(KeyType), GenerateForType(ValueType)},
NullWhence(),
Clause::EForm::NoSemicolonOrNewline));
}
TSRef<Verse::Vst::Node> GenerateForGeneratorType(const CTypeBase* ElementType) const
{
using namespace Verse::Vst;
return GenerateForIntrinsicInvocation(
"generator",
TSRef<Clause>::New(
TSRefArray<Node>{GenerateForType(ElementType)},
NullWhence(),
Clause::EForm::NoSemicolonOrNewline));
}
TSRef<Verse::Vst::Node> GenerateForType(const CNormalType& Type) const
{
using namespace Verse::Vst;
switch (Type.GetKind())
{
case ETypeKind::False:
case ETypeKind::True:
case ETypeKind::Void:
case ETypeKind::Any:
case ETypeKind::Comparable:
case ETypeKind::Logic:
case ETypeKind::Int:
case ETypeKind::Rational:
case ETypeKind::Float:
case ETypeKind::Char8:
case ETypeKind::Char32:
case ETypeKind::Path:
case ETypeKind::Range:
return GenerateUseOfIntrinsic(Type.AsCode());
case ETypeKind::Class:
{
const CClass& Class = Type.AsChecked<CClass>();
if (Class.GetParentScope()->GetKind() != CScope::EKind::Function)
{
return GenerateUseOfDefinition(*Class.Definition());
}
TSRef<Identifier> Name = GenerateUseOfDefinition(*static_cast<const CFunction*>(Class.GetParentScope()));
Name->SetTag(PrePostCall::Expression);
TSRef<Clause> ArgumentsClause = TSRef<Clause>::New(NullWhence(), Clause::EForm::NoSemicolonOrNewline);
for (const STypeVariableSubstitution& InstTypeVariable : Class._TypeVariableSubstitutions)
{
if (InstTypeVariable._TypeVariable->_ExplicitParam && InstTypeVariable._TypeVariable->_NegativeTypeVariable)
{
ArgumentsClause->AppendChild(GenerateForType(InstTypeVariable._PositiveType));
}
}
ArgumentsClause->SetTag(PrePostCall::SureCall);
TSRef<PrePostCall> Invocation = TSRef<PrePostCall>::New(NullWhence());
Invocation->AppendChild(Move(Name));
Invocation->AppendChild(Move(ArgumentsClause));
return Move(Invocation);
}
case ETypeKind::Interface:
{
const CInterface& Interface = Type.AsChecked<CInterface>();
if (Interface.GetParentScope()->GetKind() != CScope::EKind::Function)
{
return GenerateUseOfDefinition(Interface);
}
TSRef<Identifier> Name = GenerateUseOfDefinition(*static_cast<const CFunction*>(Interface.GetParentScope()));
Name->SetTag(PrePostCall::Expression);
TSRef<Clause> ArgumentsClause = TSRef<Clause>::New(NullWhence(), Clause::EForm::NoSemicolonOrNewline);
for (const STypeVariableSubstitution& InstTypeVariable : Interface._TypeVariableSubstitutions)
{
if (InstTypeVariable._TypeVariable->_ExplicitParam && InstTypeVariable._TypeVariable->_NegativeTypeVariable)
{
ArgumentsClause->AppendChild(GenerateForType(InstTypeVariable._PositiveType));
}
}
ArgumentsClause->SetTag(PrePostCall::SureCall);
TSRef<PrePostCall> Invocation = TSRef<PrePostCall>::New(NullWhence());
Invocation->AppendChild(Move(Name));
Invocation->AppendChild(Move(ArgumentsClause));
return Move(Invocation);
}
case ETypeKind::Tuple:
return GenerateForTupleType(Type.AsChecked<CTupleType>());
case ETypeKind::Enumeration:
{
const CEnumeration& Enumeration = Type.AsChecked<CEnumeration>();
return GenerateUseOfDefinition(Enumeration);
}
case ETypeKind::Option:
{
TSRef<PrePostCall> Option = TSRef<PrePostCall>::New(NullWhence());
Option->AppendChild(TSRef<Clause>::New((uint8_t)PrePostCall::Op::Option, NullWhence(), Clause::EForm::Synthetic));
Option->AppendChild(GenerateForType(Type.AsChecked<COptionType>().GetValueType()));
return Option;
}
case ETypeKind::Type:
{
const CTypeType& TypeType = Type.AsChecked<CTypeType>();
const CNormalType& NegativeType = TypeType.NegativeType()->GetNormalType();
const CNormalType& PositiveType = TypeType.PositiveType()->GetNormalType();
if (NegativeType.IsA<CFalseType>())
{
if (PositiveType.IsA<CAnyType>())
{
return GenerateUseOfIntrinsic("type");
}
const CTypeBase* SuperType = &PositiveType;
bool bCastableSubtype = false;
if (const CCastableType* CastablePositiveType = PositiveType.AsNullable<CCastableType>())
{
SuperType = &CastablePositiveType->SuperType();
bCastableSubtype = true;
}
return GenerateForSubtypeType(SuperType, bCastableSubtype);
}
if (PositiveType.IsA<CAnyType>())
{
return GenerateForSupertypeType(TypeType.NegativeType());
}
if (&PositiveType != &NegativeType)
{
_Diagnostics->AppendGlitch(
SGlitchResult(
EDiagnostic::ErrSemantic_Unimplemented,
CUTF8String("Use of type `%s` in a digest is currently unsupported.", TypeType.AsCode().AsCString())),
_CurrentGlitchAst);
}
return GenerateForType(&SemanticTypeUtils::AsPositive(PositiveType, {}));
}
case ETypeKind::Function:
{
TSRef<Identifier> Name = GenerateUnderscore();
TSRef<PrePostCall> PrePost = TSRef<PrePostCall>::New(NullWhence());
const CFunctionType* FunctionType = &Type.AsChecked<CFunctionType>();
PrePost->AppendChild(Name);
PrePost->AppendChild(GenerateForParamTypes(FunctionType->GetParamTypes()));
GenerateForEffectAttributes(FunctionType->GetEffects(), EffectSets::FunctionDefault, *PrePost);
return TSRef<Macro>::New(
NullWhence(),
GenerateUseOfIntrinsic("type"),
ClauseArray{ TSRef<Clause>::New(TSRef<TypeSpec>::New(NullWhence(), PrePost, GenerateForType(&FunctionType->GetReturnType())).As<Node>(), NullWhence(), Clause::EForm::NoSemicolonOrNewline) });
}
case ETypeKind::Array:
{
TSRef<PrePostCall> ArrayTypeFormer = TSRef<PrePostCall>::New(NullWhence());
ArrayTypeFormer->AppendChild(TSRef<Clause>::New((uint8_t)PrePostCall::Op::FailCall, NullWhence(), Clause::EForm::NoSemicolonOrNewline));
ArrayTypeFormer->AppendChild(GenerateForType(Type.AsChecked<CArrayType>().GetElementType()));
return ArrayTypeFormer;
}
case ETypeKind::Generator:
{
const CGeneratorType& GeneratorType = Type.AsChecked<CGeneratorType>();
return GenerateForGeneratorType(GeneratorType.GetElementType());
}
case ETypeKind::Map:
{
const CMapType& MapType = Type.AsChecked<CMapType>();
if (MapType.IsWeak())
{
return GenerateForWeakMapType(MapType.GetKeyType(), MapType.GetValueType());
}
TSRef<PrePostCall> MapTypeFormer = TSRef<PrePostCall>::New(NullWhence());
TSRef<Clause> KeyTypeClause = TSRef<Clause>::New(GenerateForType(MapType.GetKeyType()), NullWhence(), Clause::EForm::NoSemicolonOrNewline);
KeyTypeClause->SetTag(PrePostCall::FailCall);
MapTypeFormer->AppendChild(Move(KeyTypeClause));
MapTypeFormer->AppendChild(GenerateForType(MapType.GetValueType()));
return MapTypeFormer;
}
case ETypeKind::Pointer:
ULANG_ERRORF("Pointers must be handled at the type spec level.");
break;
case ETypeKind::Variable:
{
return GenerateDefinitionIdentifier(*Type.AsChecked<CTypeVariable>().Definition());
}
case ETypeKind::Unknown:
case ETypeKind::Module:
case ETypeKind::Reference:
case ETypeKind::Named:
case ETypeKind::Persistable:
case ETypeKind::Castable:
default:
ULANG_ERRORF("Digest generation for %s '%s' is unimplemented.", TypeKindAsCString(Type.GetKind()), Type.AsCode().AsCString());
};
return GenerateUseOfIntrinsic("<unknown>");
}
TSRef<Verse::Vst::Node> GenerateForType(const CTypeBase* Type) const
{
// If the type is a usable type alias, generate a use of that type alias.
if (const CAliasType* AliasType = Type->AsAliasType())
{
if (IsUsable(AliasType->GetDefinition()))
{
return GenerateUseOfDefinition(AliasType->GetDefinition());
}
}
return GenerateForType(Type->GetNormalType());
}
void GenerateForEffectAttributes(const SEffectSet Effects, const SEffectSet DefaultEffects, Verse::Vst::Node& CallAttributable) const
{
using namespace Verse::Vst;
if (TOptional<TArray<const CClass*>> EffectClasses = _Program.ConvertEffectSetToEffectClasses(Effects, DefaultEffects))
{
for (const CClass* EffectClass : EffectClasses.GetValue())
{
CallAttributable.AppendAux(TSRef<Clause>::New(GenerateUseOfDefinition(*EffectClass->Definition()).As<Node>(), NullWhence(), Clause::EForm::IsAppendAttributeHolder));
}
}
}
bool IsEpicInternalOnlyAttributeClass(const CClassDefinition& AttributeClass) const
{
// Allow the built-in attributes so the attribute scope attributes used to define attributes
// that *aren't* epic_internal are preserved.
if (AttributeClass.IsBuiltIn())
{
return false;
}
// Also make an exception for the import_as attribute until we can link against a digest without it.
if (&AttributeClass == _Program._import_as_attribute.Get())
{
return false;
}
const CDefinition* WorkingDefinition = &AttributeClass;
while (WorkingDefinition)
{
if (WorkingDefinition->DerivedAccessLevel()._Kind == SAccessLevel::EKind::EpicInternal)
{
return true;
}
for (const CScope* Scope = &WorkingDefinition->_EnclosingScope; Scope; Scope = Scope->GetParentScope())
{
WorkingDefinition = Scope->ScopeAsDefinition();
if (WorkingDefinition)
{
break;
}
}
};
return false;
}
TArray<CUTF8String> ReformatDocCommentAsComments(const CUTF8String& TextValue) const
{
TArray<CUTF8String> Result;
Result.Reserve(8); // Completely arbitrary choice of how many comments we predict to have as multiline at a time.
const CUTF8StringView TextView = TextValue.ToStringView();
const UTF8Char* const ViewEnd = TextView._End;
const UTF8Char* CommentStart = TextView._Begin;
// Skip the leading newline that is often found in multiline doc strings.
if (*CommentStart == '\n')
{
++CommentStart;
}
for (const UTF8Char* Ptr = CommentStart; Ptr != ViewEnd; ++Ptr)
{
if (*Ptr == '\n')
{
const CUTF8StringView CurrentCommentView = CUTF8StringView(CommentStart, Ptr);
Result.Add(CUTF8String("# %s", CUTF8String(CurrentCommentView).AsCString()));
CommentStart = Ptr + 1; // Next comment starts after the new line character
}
}
// If the comment doesn't have any newlines at all, or doesn't end in a newline.
if (CommentStart != ViewEnd)
{
const CUTF8StringView CurrentCommentView = CUTF8StringView(CommentStart, ViewEnd);
Result.Add(CUTF8String("# %s", CUTF8String(CurrentCommentView).AsCString()));
}
return Result;
}
template<typename Func>
void GenerateForScopedAttribute(const CScopedAccessLevelDefinition& AccessLevelDefinition, const Func& SelectAttributable) const
{
using namespace Verse::Vst;
// Determine which attributable to put the attribute on
TSRef<Verse::Vst::Node> Attributable = SelectAttributable(&AccessLevelDefinition);
TSRef<Macro> newScopedMacro = GenerateForScopedMacro(AccessLevelDefinition);
// attribute nodes must be wrapped in a Clause node (elsewhere used to preserve source comments)
TSRef<Clause> WrapperClause = TSRef<Clause>::New(newScopedMacro.As<Node>(), NullWhence(), Clause::EForm::IsAppendAttributeHolder);
Attributable->AppendAux(WrapperClause);
}
const CClass* GetClassForExpression(TSPtr<CExpressionBase> Expression) const
{
const CNormalType& ExpressionResultType = Expression->GetResultType(_Program)->GetNormalType();
if (const CClass* Class = ExpressionResultType.AsNullable<CClass>())
{
return Class;
}
else if (const CTypeType* TypeType = ExpressionResultType.AsNullable<CTypeType>())
{
return TypeType->PositiveType()->GetNormalType().AsNullable<CClass>();
}
return nullptr;
}
TSRef<Verse::Vst::Node> GenerateForExpression(TSPtr<CExpressionBase> ExprValue) const
{
using namespace Verse::Vst;
TGuardValue<const CAstNode*> GlitchAstGuard(_CurrentGlitchAst, ExprValue);
TSPtr<Verse::Vst::Node> ResultValue;
const CTypeBase* TypeBase = ExprValue->GetResultType(_Program);
switch (ExprValue->GetNodeType())
{
case EAstNodeType::Invoke_ArchetypeInstantiation:
{
// Archetypes look like: @TypeName{ arg0 := value0, arg1 := value1 ... }
// The VST looks like this:
// Clause: @TypeName { A := 0 } breaks down into
// Macro:
// [0]Identifier: TypeName
// [1]Clause:
// [0]Definition: arg0 := value0
// [0]Identifier: arg0
// [1]Clause:
// [0]TYPE: value0
// [1]Definition: arg1 := value1
// [0]Identifier: arg1
// [1]Clause:
// [0]TYPE: value1
TSPtr<CExprArchetypeInstantiation> ArchInstValue = ExprValue.As<CExprArchetypeInstantiation>();
const CClass* AttributeClass = GetClassForExpression(ExprValue);
CUTF8String AttributeName;
// special case for parametric types
TSPtr<Node> AttribMacro;
if (ArchInstValue->_ClassAst->GetNodeType() == EAstNodeType::Invoke_Invocation)
{
TSPtr<CExprInvocation> ExprInvoke = ArchInstValue->_ClassAst.As<CExprInvocation>();
const CFunctionType* FuncType = ExprInvoke->GetResolvedCalleeType();
if (const CTypeType* ReturnTypeType = FuncType->GetReturnType().GetNormalType().AsNullable<CTypeType>())
{
AttribMacro = GenerateForType(ReturnTypeType->PositiveType());
}
else
{
_Diagnostics->AppendGlitch(
SGlitchResult(EDiagnostic::ErrDigest_Unimplemented, uLang::CUTF8String("Unsupported instance of type '%s' in attribute.", FuncType->GetReturnType().GetNormalType().AsCode().AsCString())),
_CurrentGlitchAst);
}
}
else
{
AttribMacro = GenerateForType(AttributeClass);
}
TSRef<Clause> ValueClause = TSRef<Clause>::New(NullWhence(), Clause::EForm::Synthetic, Clause::EPunctuation::Braces);
// iterate each argument clause and append
for (const TSRef<uLang::CExpressionBase>& AttribArg : ArchInstValue->Arguments())
{
TSPtr<Identifier> ArgIdent;
TSPtr<Clause> ArgValueClause;
if (AttribArg->GetNodeType() == EAstNodeType::Definition)
{
TSRef<CExprDefinition> AttribArgDef = AttribArg.As<CExprDefinition>();
{ // IDENT
TSPtr<CExprIdentifierData> AttribArgIdentData = AttribArgDef->Element().As<CExprIdentifierData>();
ArgIdent.SetNew(AttribArgIdentData->GetName().AsStringView(), NullWhence());
}
{ // Value
TSPtr<CExprInvokeType> AttribArgValue = AttribArgDef->Value().As<CExprInvokeType>();
TSPtr<Node> ArgValue = GenerateForExpression(AttribArgValue->_Argument);
ArgValueClause.SetNew(NullWhence(), Clause::EForm::NoSemicolonOrNewline, Clause::EPunctuation::Unknown);
ArgValueClause->AppendChild(ArgValue.AsRef());
}
}
TSRef<Definition> Arg = TSRef<Definition>::New(NullWhence(), ArgIdent.AsRef(), ArgValueClause.AsRef());
ValueClause->AppendChild(Arg);
}
TArray<TSRef<Clause>> ValueClauses = { ValueClause };
ResultValue = TSRef<Macro>::New(NullWhence(), AttribMacro.AsRef(), TArray<TSRef<Clause>>(ValueClauses));
}
break;
case EAstNodeType::Literal_Logic:
{
TSPtr<CExprLogic> LogicValue = ExprValue.As<CExprLogic>();
ResultValue = GenerateUseOfIntrinsic(LogicValue->_Value ? "true" : "false");
}
break;
case EAstNodeType::Literal_Number:
{
TSPtr<CExprNumber> Number = ExprValue.As<CExprNumber>();
if (Number->IsFloat())
{
CUTF8String FloatStr = uLang::CUTF8String("%lf", Number->GetFloatValue());
ResultValue = TSRef<FloatLiteral>::New(FloatStr, FloatLiteral::EFormat::F64, NullWhence()).As<Node>();
}
else
{
CUTF8String IntStr = uLang::CUTF8String("%ld", Number->GetIntValue());
ResultValue = TSRef<IntLiteral>::New(IntStr, NullWhence());
}
}
break;
case EAstNodeType::Literal_Char:
{
TSPtr<CExprChar> CharValue = ExprValue.As<CExprChar>();
switch (CharValue->_Type)
{
case CExprChar::EType::UTF8CodeUnit:
ResultValue = TSRef<CharLiteral>::New(uLang::CUTF8String("0o%X", CharValue->_CodePoint), CharLiteral::EFormat::UTF8CodeUnit, NullWhence()).As<Node>();
break;
case CExprChar::EType::UnicodeCodePoint:
ResultValue = TSRef<CharLiteral>::New(uLang::CUTF8String("0u%X", CharValue->_CodePoint), CharLiteral::EFormat::UnicodeCodePoint, NullWhence()).As<Node>();
break;
default:
_Diagnostics->AppendGlitch(
SGlitchResult(EDiagnostic::ErrDigest_Unimplemented, uLang::CUTF8String("Unknown character format type.")),
_CurrentGlitchAst);
break;
}
}
break;
case EAstNodeType::Literal_String:
{
TSPtr<CExprString> StringValue = ExprValue.As<CExprString>();
ResultValue = TSRef<StringLiteral>::New(NullWhence(), StringValue->_String).As<Node>();
}
break;
case EAstNodeType::Literal_Path:
{
TSPtr<CExprPath> PathValue = ExprValue.As<CExprPath>();
ResultValue = TSRef<PathLiteral>::New(PathValue->_Path, NullWhence()).As<Node>();
}
break;
case EAstNodeType::Literal_Enum:
{
TSPtr<CExprEnumLiteral> EnumValue = ExprValue.As<CExprEnumLiteral>();
ResultValue = GenerateUseOfDefinition(*EnumValue->_Enumerator).As<Node>();
}
break;
case EAstNodeType::Literal_Type:
{
TSPtr<CExprType> TypeValue = ExprValue.As<CExprType>();
ResultValue = GenerateForType(TypeBase);
}
break;
case EAstNodeType::Identifier_Class:
{
TSPtr<CExprIdentifierClass> ClassIdent = ExprValue.As<CExprIdentifierClass>();
ResultValue = GenerateUseOfDefinition(*ClassIdent->GetClass(_Program)->Definition()).As<Node>();
}
break;
case EAstNodeType::Identifier_Data:
{
TSPtr<CExprIdentifierData> DataIdent = ExprValue.As<CExprIdentifierData>();
ResultValue = GenerateUseOfDefinition(DataIdent->_DataDefinition).As<Node>();
}
break;
case EAstNodeType::Invoke_MakeOption:
{
TSPtr<CExprMakeOption> MakeOptionExpr = ExprValue.As<CExprMakeOption>();
if (TSPtr<CExpressionBase> SubExpr = MakeOptionExpr->Operand())
{
TSPtr<Clause> OptionElementClause = TSPtr<Clause>::New(NullWhence(), Clause::EForm::NoSemicolonOrNewline, Clause::EPunctuation::Braces);
OptionElementClause->AppendChild(GenerateForExpression(SubExpr));
TArray<TSRef<Clause>> MacroClauseArray;
MacroClauseArray.Add(OptionElementClause.AsRef());
ResultValue = TSRef<Macro>::New(NullWhence(),
GenerateUseOfIntrinsic("option"),
MacroClauseArray
).As<Node>();
}
else
{
// unset option
ResultValue = GenerateUseOfIntrinsic("false");
}
}
break;
case EAstNodeType::Invoke_MakeArray:
{
const CArrayType& ArrayNormalType = TypeBase->GetNormalType().AsChecked<CArrayType>();
const CTypeBase* InnerType = ArrayNormalType.GetInnerType();
if (InnerType == &_Program._char8Type)
{
// string
TSPtr<CExprString> StringValue = ExprValue.As<CExprString>();
ResultValue = TSRef<StringLiteral>::New(NullWhence(), StringValue->_String).As<Node>();
}
else
{
TSRef<Clause> ArrayElementClause = TSRef<Clause>::New(NullWhence(), Clause::EForm::NoSemicolonOrNewline, Clause::EPunctuation::Braces);
TSPtr<CExprMakeArray> MakeArrayExpr = ExprValue.As<CExprMakeArray>();
for (TSPtr<CExpressionBase> SubExpr : MakeArrayExpr->GetSubExprs())
{
ArrayElementClause->AppendChild(GenerateForExpression(SubExpr));
}
TArray<TSRef<Clause>> MacroClauseArray;
MacroClauseArray.Add(ArrayElementClause);
ResultValue = TSRef<Macro>::New(NullWhence(),
GenerateUseOfIntrinsic("array"),
MacroClauseArray
).As<Node>();
}
}
break;
// Currently Unsupported
case EAstNodeType::Error_:
case EAstNodeType::Placeholder_:
case EAstNodeType::External:
case EAstNodeType::PathPlusSymbol:
case EAstNodeType::Literal_Function:
case EAstNodeType::Identifier_Unresolved:
case EAstNodeType::Identifier_Module:
case EAstNodeType::Identifier_ModuleAlias:
case EAstNodeType::Identifier_Enum:
case EAstNodeType::Identifier_Interface:
case EAstNodeType::Identifier_TypeAlias:
case EAstNodeType::Identifier_TypeVariable:
case EAstNodeType::Identifier_Function:
case EAstNodeType::Identifier_OverloadedFunction:
case EAstNodeType::Identifier_Self:
case EAstNodeType::Identifier_Local:
case EAstNodeType::Identifier_BuiltInMacro:
case EAstNodeType::Definition:
case EAstNodeType::MacroCall:
case EAstNodeType::Invoke_Invocation:
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_TupleElement:
case EAstNodeType::Invoke_MakeMap:
case EAstNodeType::Invoke_MakeTuple:
case EAstNodeType::Invoke_MakeRange:
case EAstNodeType::Invoke_Type:
case EAstNodeType::Invoke_PointerToReference:
case EAstNodeType::Invoke_Set:
case EAstNodeType::Invoke_NewPointer:
case EAstNodeType::Invoke_ReferenceToValue:
case EAstNodeType::Assignment:
case EAstNodeType::Invoke_ArrayFormer:
case EAstNodeType::Invoke_GeneratorFormer:
case EAstNodeType::Invoke_MapFormer:
case EAstNodeType::Invoke_OptionFormer:
case EAstNodeType::Invoke_Subtype:
case EAstNodeType::Invoke_TupleType:
case EAstNodeType::Invoke_Arrow:
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:
default:
_Diagnostics->AppendGlitch(
SGlitchResult(EDiagnostic::ErrDigest_Unimplemented, uLang::CUTF8String("Unsupported expression type in digest generation.")),
_CurrentGlitchAst);
break;
}
return ResultValue.AsRef();
}
template<typename Func>
void GenerateForAttributeArchetype(TSPtr<uLang::CExprArchetypeInstantiation> AttributeExpr, const CClass* AttributeClass, const Func& SelectAttributable) const
{
using namespace Verse::Vst;
// Determine which attributable to put the attribute on
TSRef<Verse::Vst::Node> Attributable = SelectAttributable(AttributeClass);
if (!_bIncludeEpicInternalDefinitions && IsEpicInternalOnlyAttributeClass(*AttributeClass->_Definition))
{
// Filter out Epic-internal attributes from public-only digests.
return;
}
TSRef<Node> MacroInst = GenerateForExpression(AttributeExpr);
MacroInst->SetNewLineAfter(true); // For digest purposes, we force newlines after so that it looks neater.
// attribute nodes must be wrapped in a Clause node (elsewhere used to preserve source comments)
TSRef<Clause> WrapperClause = TSRef<Clause>::New(MacroInst.As<Node>(), NullWhence(), Clause::EForm::IsAppendAttributeHolder);
Attributable->AppendAux(WrapperClause);
}
template<typename Func>
void GenerateForAttributeGeneric(const CClass* AttributeClass, const uLang::TOptional<CUTF8String>& TextValue, const Func& SelectAttributable) const
{
using namespace Verse::Vst;
// Determine which attributable to put the attribute on
TSRef<Verse::Vst::Node> Attributable = SelectAttributable(AttributeClass);
if (AttributeClass == _Program._doc_attribute.Get() && TextValue.IsSet())
{
// Replace doc comments with line comments in the digest, regardless of whether it includes epic_internal definitions.
for (const CUTF8String& CommentString : ReformatDocCommentAsComments(TextValue.GetValue()))
{
TSRef<Comment> NewComment = TSRef<Comment>::New(Comment::EType::line, CommentString, NullWhence());
NewComment->SetNumNewLinesAfter(1);
Attributable->AppendPrefixComment(NewComment);
}
return;
}
else if (!_bIncludeEpicInternalDefinitions && IsEpicInternalOnlyAttributeClass(*AttributeClass->_Definition))
{
// Filter out Epic-internal attributes from public-only digests.
return;
}
else if (AttributeClass == _Program._getterClass || AttributeClass == _Program._setterClass)
{
// getters/setters are special; we don't want them to appear in any digests, regardless
// of access level
return;
}
const CDefinition* AttributeDefinition = AttributeClass->Definition();
// SOL-972 & SOL-2577: Some attributes are implemented as a function and a class, and they can't have the same name.
// The function has the same name as the attribute, and is what we need here.
// The class has "_attribute" appended, and is what we have.
// This is not done for all attributes, hence the if-statement.
if (AttributeDefinition->AsNameStringView().EndsWith("_attribute"))
{
CUTF8StringView ConstructorName = AttributeDefinition->AsNameStringView().SubViewTrimEnd(static_cast<int32_t>(strlen("_attribute")));
const CSymbol ConstructorSymbol = _Program.GetSymbols()->AddChecked(ConstructorName);
if (const CFunction* AttributeConstructor = AttributeDefinition->_EnclosingScope.GetLogicalScope().FindFirstDefinitionOfKind<CFunction>(ConstructorSymbol))
{
AttributeDefinition = AttributeConstructor;
}
}
if (TextValue.IsSet())
{
TSRef<StringLiteral> Value = TSRef<StringLiteral>::New(NullWhence(), TextValue.GetValue());
TSRef<Clause> ValueClause = TSRef<Clause>::New(Value.As<Node>(), NullWhence(), Clause::EForm::Synthetic);
TSRef<Identifier> Name = GenerateUseOfDefinition(*AttributeDefinition);
Name->SetTag(PrePostCall::Op::Expression);
ValueClause->SetTag(PrePostCall::Op::SureCall);
TSRef<PrePostCall> Call = TSRef<PrePostCall>::New(NullWhence());
Call->AppendChild(Name);
Call->AppendChild(ValueClause);
Call->SetNewLineAfter(true); // For digest purposes, we force newlines after so that it looks neater.
// attribute nodes must be wrapped in a Clause node (elsewhere used to preserve source comments)
TSRef<Clause> WrapperClause = TSRef<Clause>::New(Call.As<Node>(), NullWhence(), Clause::EForm::IsAppendAttributeHolder);
Attributable->AppendAux(WrapperClause);
}
else
{
// attribute nodes must be wrapped in a Clause node (elsewhere used to preserve source comments)
TSRef<Identifier> AttributeIdentifier = GenerateUseOfDefinition(*AttributeDefinition);
// NOTE: (yiliang.siew) We do this so that `<epic_internal>` and other attributes that are suffixed to the identifier
// do not get newlines after them, only the prefix attributes on the definition itself.
if (Attributable->IsA<Definition>() || Attributable->IsA<TypeSpec>())
{
AttributeIdentifier->SetNewLineAfter(true);
}
TSRef<Clause> WrapperClause = TSRef<Clause>::New(AttributeIdentifier.As<Node>(), NullWhence(), Clause::EForm::IsAppendAttributeHolder);
Attributable->AppendAux(WrapperClause);
}
}
template<typename Func>
void GenerateForAttributesGeneric(const TArray<SAttribute>& Attributes, const TOptional<SAccessLevel>& AccessLevel, const Func& SelectAttributable) const
{
for (const SAttribute& Attribute : Attributes)
{
// Determine the attribute class
const CClass* AttributeClass = GetClassForExpression(Attribute._Expression);
if (ULANG_ENSUREF(AttributeClass, "Unrecognized attribute type."))
{
const bool bIsAccessLevelAttribute =
AttributeClass == _Program._publicClass
|| AttributeClass == _Program._internalClass
|| AttributeClass == _Program._protectedClass
|| AttributeClass == _Program._privateClass
|| AttributeClass == _Program._epicInternalClass;
if (AttributeClass->IsSubclassOf(*_Program._scopedClass))
{
GenerateForScopedAttribute(*static_cast<const CScopedAccessLevelDefinition*>(AttributeClass), SelectAttributable);
}
else if (!bIsAccessLevelAttribute)
{
if (Attribute._Expression->GetNodeType() == EAstNodeType::Invoke_ArchetypeInstantiation)
{
GenerateForAttributeArchetype(Attribute._Expression.As<CExprArchetypeInstantiation>(), AttributeClass, SelectAttributable);
}
else
{
const uLang::TOptional<CUTF8String> TextValue = CAttributable::GetAttributeTextValue(Attributes, AttributeClass, _Program);
GenerateForAttributeGeneric(AttributeClass, TextValue, SelectAttributable);
}
}
}
}
if (AccessLevel.IsSet())
{
switch(AccessLevel.GetValue()._Kind)
{
case SAccessLevel::EKind::Public: GenerateForAttributeGeneric(_Program._publicClass, {}, SelectAttributable); break;
case SAccessLevel::EKind::Internal: GenerateForAttributeGeneric(_Program._internalClass, {}, SelectAttributable); break;
case SAccessLevel::EKind::Protected: GenerateForAttributeGeneric(_Program._protectedClass, {}, SelectAttributable); break;
case SAccessLevel::EKind::Private: GenerateForAttributeGeneric(_Program._privateClass, {}, SelectAttributable); break;
case SAccessLevel::EKind::Scoped: /* handled above */ break;
case SAccessLevel::EKind::EpicInternal: GenerateForAttributeGeneric(_Program._epicInternalClass, {}, SelectAttributable); break;
default: ULANG_UNREACHABLE();
}
}
}
void GenerateForAttributes(const CAttributable& Attributes, const TOptional<SAccessLevel>& AccessLevel, const TSRef<Verse::Vst::Node>& Attributable) const
{
GenerateForAttributesGeneric(Attributes._Attributes, AccessLevel, [&Attributable] (const CClass*) { return Attributable; });
}
void GenerateForAttributes(const TArray<SAttribute>& Attributes, const CDefinition& Definition, const TSRef<Verse::Vst::Identifier>& NameAttributable, const TSRef<Verse::Vst::Node>& DefAttributable) const
{
GenerateForAttributesGeneric(
Attributes,
Definition.SelfAccessLevel(),
[this, &NameAttributable, &DefAttributable] (const CClass* AttributeClass)
{
TSPtr<Verse::Vst::Node> Attributable;
if (AttributeClass->HasAttributeClass(_Program._attributeScopeName, _Program))
{
Attributable = NameAttributable;
}
else
{
Attributable = DefAttributable;
}
return Attributable.AsRef();
});
}
void GenerateForAttributes(const CDefinition& Definition, const TSRef<Verse::Vst::Identifier>& NameAttributable, const TSRef<Verse::Vst::Node>& DefAttributable) const
{
GenerateForAttributes(Definition._Attributes, Definition, NameAttributable, DefAttributable);
}
TSRef<Verse::Vst::Macro> GenerateExternalMacro() const
{
using namespace Verse::Vst;
return TSRef<Macro>::New(
NullWhence(),
GenerateUseOfIntrinsic("external"),
ClauseArray{TSRef<Clause>::New(NullWhence(), Clause::EForm::NoSemicolonOrNewline)});
}
bool IsUsable(const CDefinition& Definition) const
{
SAccessLevel AccessLevel;
if (Definition._EnclosingScope.GetKind() == CScope::EKind::Function)
{
AccessLevel = static_cast<const CFunction&>(Definition._EnclosingScope).DerivedAccessLevel();
}
else
{
AccessLevel = Definition.DerivedAccessLevel();
}
if (AccessLevel._Kind == Cases<SAccessLevel::EKind::Private, SAccessLevel::EKind::Internal>)
{
return false;
}
bool bSpecialException = false;
bSpecialException |= IsEpicInternalLocalizationDefinition(Definition);
if (AccessLevel._Kind == SAccessLevel::EKind::EpicInternal
&& !_bIncludeEpicInternalDefinitions
// Don't cull inheriting from epic_internal definitions in the intrinsically defined
// built-in snippet (e.g.attribute).
&& !Definition.IsBuiltIn()
&& !bSpecialException)
{
return false;
}
const CAstPackage* DefinitionPackage = Definition._EnclosingScope.GetPackage();
if (!_bIncludeEpicInternalDefinitions &&
DefinitionPackage && DefinitionPackage->_VerseScope == EVerseScope::InternalAPI)
{
return false;
}
return true;
}
// For a given class, find the nearest ancestor that is public
const CClass* PublifySuperclass(const CClass* Class) const
{
while (Class && !IsUsable(*Class->_Definition))
{
Class = Class->_Superclass;
}
return Class;
}
// For a given single interface, find the set of nearest ancestors that are all public
TArray<const CInterface*> PublifySuperInterface(const CInterface* Interface) const
{
// Is it public?
if (IsUsable(*Interface))
{
// Yes, return as-is
return {Interface};
}
// No, find public super interfaces
TArray<const CInterface*> Result;
for (const CInterface* SuperInterface : Interface->_SuperInterfaces)
{
TArray<const CInterface*> PublicSuperInterfaces = PublifySuperInterface(SuperInterface);
for (const CInterface* PublicSuperInterface : PublicSuperInterfaces)
{
Result.Add(PublicSuperInterface);
}
}
return Result;
}
// For a given set of interfaces, find the set of nearest ancestors that are all public
TArray<const CInterface*> PublifySuperInterfaces(const TArray<CInterface*>& Interfaces) const
{
TArray<const CInterface*> Result;
for (const CInterface* Interface : Interfaces)
{
TArray<const CInterface*> PublicSuperInterfaces = PublifySuperInterface(Interface);
for (const CInterface* PublicSuperInterface : PublicSuperInterfaces)
{
Result.Add(PublicSuperInterface);
}
}
return Result;
}
TArray<const CNominalType*> PublifyType(const CTypeBase* TypeToPublify, TArray<const CInterface*>& VisitedPublicSuperInterfaces) const
{
if (const CClass* Class = TypeToPublify->GetNormalType().AsNullable<CClass>())
{
if (IsUsable(*Class->_Definition))
{
return { Class };
}
else
{
TArray<const CNominalType*> Publified;
if (Class->_Superclass)
{
Publified.Append(PublifyType(Class->_Superclass, VisitedPublicSuperInterfaces));
}
for (const CInterface* SuperInterface : PublifySuperInterfaces(Class->_SuperInterfaces))
{
int32_t NumVisitedSuperInterfaces = VisitedPublicSuperInterfaces.Num();
if (VisitedPublicSuperInterfaces.AddUnique(SuperInterface) == NumVisitedSuperInterfaces)
{
Publified.Add(SuperInterface);
}
}
return Publified;
}
}
else if (const CInterface* Interface = TypeToPublify->GetNormalType().AsNullable<CInterface>())
{
if (IsUsable(*Interface))
{
int32_t NumVisitedSuperInterfaces = VisitedPublicSuperInterfaces.Num();
if (VisitedPublicSuperInterfaces.AddUnique(Interface) == NumVisitedSuperInterfaces)
{
return { Interface };
}
else
{
return {};
}
}
TArray<const CNominalType*> Publified;
for (const CInterface* SuperInterface : PublifySuperInterfaces(Interface->_SuperInterfaces))
{
int32_t NumVisitedSuperInterfaces = VisitedPublicSuperInterfaces.Num();
if (VisitedPublicSuperInterfaces.AddUnique(SuperInterface) == NumVisitedSuperInterfaces)
{
Publified.Add(SuperInterface);
}
}
return Publified;
}
return {};
}
bool DefinitionSubjectToScopedAccess(const CDefinition& Definition) const
{
if (Definition.DerivedAccessLevel()._Kind == SAccessLevel::EKind::Scoped)
{
return true;
}
for (const CScope* Scope = &Definition._EnclosingScope; Scope != nullptr; Scope = Scope->GetParentScope())
{
if (const CDefinition* ScopeDefinition = Scope->ScopeAsDefinition())
{
if (ScopeDefinition->DerivedAccessLevel()._Kind == SAccessLevel::EKind::Scoped)
{
return true;
}
}
}
return false;
}
bool ShouldGenerate(const CDefinition& Definition, bool bCheckPackage = true) const
{
/*
* We have this check here because if we have the code:
* ```
* foothis<epic_internal> := interface:
* bar<public>():string
*
* baz<public> := class(foothis):
* bar<override>():string=
* return "test"
* ```
*
* We still want to generate the following in the digest:
*
* ```
* foothis<epic_internal> := interface:
* bar<public>():[]char
* baz<public> := class:
* bar<override>():[]char = external {}
* ```
*
* Since `baz.bar` is public and marked as `<override>`, digest compilation would fail otherwise if `foothis.bar` was not present.
*/
auto IsOrHasPubliclyOverriddenAbstractMethod = [](const CDefinition& Definition, auto&& IsPubliclyOverriddenAbstractMethod) -> bool
{
if (Definition.IsA<CInterface>())
{
for (const auto& ChildDefinition : Definition.AsChecked<CInterface>().GetDefinitions())
{
if (IsPubliclyOverriddenAbstractMethod(*ChildDefinition, IsPubliclyOverriddenAbstractMethod))
{
return true;
}
}
return false;
}
const CDefinition* EnclosingDefinition = Definition._EnclosingScope.ScopeAsDefinition();
if (EnclosingDefinition && EnclosingDefinition->IsA<CInterface>())
{
const SQualifier Qualifier{Definition._Qualifier};
const SResolvedDefinitionArray OtherDefinitions = Definition._EnclosingScope.ResolveDefinition(Definition.GetName(), Qualifier);
// The other definitions being looked up would include the original one within the current enclosing scope in the first place.
for (const SResolvedDefinition& ResolvedDefn : OtherDefinitions)
{
if (ResolvedDefn._Definition != &Definition && ResolvedDefn._Definition->DerivedAccessLevel()._Kind == SAccessLevel::EKind::Public)
{
// Also walk up enclosing scopes to ensure that this is actually a public-ly accessible symbol.
const CScope* CurrentScope = &ResolvedDefn._Definition->_EnclosingScope;
while (CurrentScope)
{
if (CurrentScope->IsAuthoredByEpic())
{
return false;
}
CurrentScope = CurrentScope->GetParentScope();
}
return true;
}
}
}
return false;
};
if (Definition._EnclosingScope.GetKind() == CScope::EKind::Function)
{
return ShouldGenerate(static_cast<const CFunction&>(Definition._EnclosingScope), bCheckPackage);
}
// Don't generate digest definitions for the intrinsically defined built-in snippet, or definitions outside the current package.
const CAstPackage* Package = Definition._EnclosingScope.GetPackage();
if (Definition.IsBuiltIn()
|| (bCheckPackage && Package && Package != &_Package))
{
return false;
}
bool bSpecialException = false;
// Make an exception for the import_as attribute until we can link against a digest without it.
bSpecialException |= &Definition == _Program._import_as_attribute.Get();
bSpecialException |= &Definition == _Program._import_as.Get();
// Make a special exception for localization related functionality that
// is epic_internal and needs to be visible to user code
bSpecialException |= IsEpicInternalLocalizationDefinition(Definition);
// If this definition is living under a scoped access level, then we can't be sure it's not being accessed from elsewhere
bool bWithinAScopedScope = DefinitionSubjectToScopedAccess(Definition);
const SAccessLevel& AccessLevel = Definition.DerivedAccessLevel();
return AccessLevel._Kind == Cases<SAccessLevel::EKind::Public, SAccessLevel::EKind::Protected>
|| (_bIncludeInternalDefinitions && AccessLevel._Kind == SAccessLevel::EKind::Internal)
|| (_bIncludeEpicInternalDefinitions && AccessLevel._Kind == SAccessLevel::EKind::EpicInternal)
|| bSpecialException
|| (bWithinAScopedScope && AccessLevel._Kind != SAccessLevel::EKind::EpicInternal)
// If the definition is an abstract method that is being overriden by something that is public,
// the abstract method itself must be visible in the digest too regardless of its access level.
|| IsOrHasPubliclyOverriddenAbstractMethod(Definition, IsOrHasPubliclyOverriddenAbstractMethod);
}
CUTF8StringView GetDependencyName(const CDefinition& Definition) const
{
const CModule* UsingModule = Definition._EnclosingScope.GetModule();
ULANG_ASSERTF(UsingModule != nullptr, "Definition._EnclosingScope does not have a module.");
if (!_CurrentModule->IsSameOrChildOf(UsingModule)
&& UsingModule != _Program._VerseModule)
{
CUTF8String UsingVersePath = UsingModule->GetScopePath('/', CScope::EPathMode::PrefixSeparator);
if (_Usings.Find(UsingVersePath) == IndexNone)
{
const CAstPackage* UsingPackage = Definition._EnclosingScope.GetPackage();
if (!_bIncludeEpicInternalDefinitions && UsingPackage->_VerseScope == EVerseScope::InternalAPI)
{
_Diagnostics->AppendGlitch(
SGlitchResult(EDiagnostic::ErrDigest_DisallowedUsing, uLang::CUTF8String("Package `%s` is publicly visible but its public interface depends on `%s` from package `%s` which is not publicly visible.",
*_Package._Name, *GetQualifiedNameString(Definition), *UsingPackage->_Name)),
_CurrentGlitchAst);
}
else
{
_Usings.Add(UsingVersePath);
}
}
}
return Definition.AsNameStringView();
}
void DeclareDependencyOnScope(const CScope& Scope) const
{
const CModule* UsingModule = Scope.GetModule();
ULANG_ASSERTF(UsingModule != nullptr, "Definition._EnclosingScope does not have a module.");
if (!_CurrentModule->IsSameOrChildOf(UsingModule)
&& UsingModule != _Program._VerseModule)
{
CUTF8String UsingVersePath = UsingModule->GetScopePath('/', CScope::EPathMode::PrefixSeparator);
if (_Usings.Find(UsingVersePath) == IndexNone)
{
const CAstPackage* UsingPackage = Scope.GetPackage();
if (!_bIncludeEpicInternalDefinitions && UsingPackage->_VerseScope == EVerseScope::InternalAPI)
{
_Diagnostics->AppendGlitch(
SGlitchResult(EDiagnostic::ErrDigest_DisallowedUsing, uLang::CUTF8String("Package `%s` is publicly visible but its public interface depends on package `%s` which is not publicly visible.",
*_Package._Name, *UsingPackage->_Name)),
_CurrentGlitchAst);
}
else
{
_Usings.Add(UsingVersePath);
}
}
}
const CAstPackage* ScopePackage = Scope.GetPackage();
if (ScopePackage && ScopePackage != &_Package && ScopePackage != _Program._BuiltInPackage)
{
_DependencyPackages.Insert(ScopePackage);
}
}
TSRef<Verse::Vst::Node> GenerateForQualifier(const SQualifier& Qualifier) const
{
if (Qualifier._Type == SQualifier::EType::Local)
{
return GenerateUseOfIntrinsic("local");
}
// Use GenerateForType, but handle module "types" as a reference to the module value.
else if (Qualifier._Type == SQualifier::EType::NominalType && Qualifier.GetNominalType()->GetKind() == ETypeKind::Module)
{
const CModule& Module = Qualifier.GetNominalType()->AsChecked<CModule>();
const CUTF8String UsingVersePath = Module.GetScopePath('/', CScope::EPathMode::PrefixSeparator);
return TSRef<Verse::Vst::PathLiteral>::New(UsingVersePath, NullWhence());
}
else
{
ULANG_ASSERTF(Qualifier.GetNominalType(), "Invalid qualifier state encountered.");
// TODO: (yiliang.siew) For now we are always using the full path for the qualifier in this case until we implement
// logic to use the minimum qualification path possible.
const CDefinition* Definition = Qualifier.GetNominalType()->Definition();
if (ULANG_ENSUREF(Definition, "Invalid qualifier; no valid definition exists for it."))
{
const CLogicalScope* LogicalScope = Definition->DefinitionAsLogicalScopeNullable();
if (ULANG_ENSUREF(LogicalScope, "Invalid qualifier; definition for it is not a logical scope."))
{
const CUTF8String QualifierPath = LogicalScope->GetScopePath('/', CScope::EPathMode::PrefixSeparator);
return TSRef<Verse::Vst::PathLiteral>::New(QualifierPath, NullWhence());
}
}
ULANG_UNREACHABLE();
}
}
void BuildSymbolMap()
{
_Program.IterateRecurseLogicalScopes([this](const CLogicalScope& LogicalScope)
{
for (CDefinition* Definition : LogicalScope.GetDefinitions())
{
SGlobalSymbolOccurrences& Occurrences = _SymbolMap.FindOrInsert(CSymbol(Definition->GetName()))._Value;
Occurrences.Add(Definition);
}
return EVisitResult::Continue;
});
}
static const CClass* ScopeAsClass(const CScope& Scope)
{
return Scope.GetKind() == CScope::EKind::Class ? static_cast<const CClass*>(&Scope) : nullptr;
}
static const CInterface* ScopeAsInterface(const CScope& Scope)
{
return Scope.GetKind() == CScope::EKind::Interface ? static_cast<const CInterface*>(&Scope) : nullptr;
}
bool NeedsQualification(CSymbol Symbol, const CScope* Scope) const
{
if (const SGlobalSymbolOccurrences* Occurrences = _SymbolMap.Find(Symbol))
{
const CDefinition* UnambiguousResolution = nullptr;
for (const CDefinition* Definition : *Occurrences)
{
if (Definition->_EnclosingScope.IsModuleOrSnippet()
|| Scope->IsSameOrChildOf(&Definition->_EnclosingScope)
|| (ScopeAsClass(*Scope) && ScopeAsClass(Definition->_EnclosingScope) && ScopeAsClass(*Scope)->IsClass(*ScopeAsClass(Definition->_EnclosingScope)))
|| (ScopeAsClass(*Scope) && ScopeAsInterface(Definition->_EnclosingScope) && ScopeAsClass(*Scope)->ImplementsInterface(*ScopeAsInterface(Definition->_EnclosingScope)))
|| (ScopeAsInterface(*Scope) && ScopeAsInterface(Definition->_EnclosingScope) && ScopeAsInterface(*Scope)->IsInterface(*ScopeAsInterface(Definition->_EnclosingScope))))
{
if (!UnambiguousResolution)
{
UnambiguousResolution = Definition;
}
else if (Definition->GetImplicitQualifier() != UnambiguousResolution->GetImplicitQualifier())
{
return true;
}
}
}
}
return false;
}
TSRef<Verse::Vst::Identifier> GenerateIdentifierWithQualifierIfNeeded(CUTF8StringView IdentifierString, CSymbol SymbolToResolve, SQualifier ImplicitQualifier, const CScope* Scope, bool bNeverQualify = false) const
{
using namespace Verse::Vst;
TSRef<Identifier> IdentifierNode = TSRef<Identifier>::New(IdentifierString, NullWhence());
TSPtr<Node> QualifierNode;
if (!ImplicitQualifier.IsUnspecified())
{
if (!bNeverQualify && NeedsQualification(SymbolToResolve, Scope))
{
QualifierNode = GenerateForQualifier(ImplicitQualifier);
}
}
if (QualifierNode)
{
IdentifierNode->AppendChild(QualifierNode.AsRef());
}
return IdentifierNode;
}
TSRef<Verse::Vst::Identifier> GenerateUnderscore() const
{
return TSRef<Verse::Vst::Identifier>::New(_Underscore.AsStringView(), NullWhence());
}
TSRef<Verse::Vst::Identifier> GenerateDefinitionIdentifier(CUTF8StringView IdentifierString, const CDefinition& Definition, bool bNeverQualify = false) const
{
return GenerateIdentifierWithQualifierIfNeeded(IdentifierString, Definition.GetName(), Definition.GetImplicitQualifier(), &Definition._EnclosingScope, bNeverQualify);
}
TSRef<Verse::Vst::Identifier> GenerateDefinitionIdentifier(const CDefinition& Definition, bool bNeverQualify = false) const
{
return GenerateIdentifierWithQualifierIfNeeded(Definition.AsNameStringView(), Definition.GetName(), Definition.GetImplicitQualifier(), &Definition._EnclosingScope, bNeverQualify);
}
TSRef<Verse::Vst::Identifier> GenerateUseOfDefinition(const CDefinition& Definition) const
{
DeclareDependencyOnScope(Definition._EnclosingScope);
return GenerateIdentifierWithQualifierIfNeeded(Definition.AsNameStringView(), Definition.GetName(), Definition.GetImplicitQualifier(), _CurrentScope);
}
TSRef<Verse::Vst::Identifier> GenerateUseOfIntrinsic(const CUTF8StringView& IntrinsicName) const
{
return GenerateIdentifierWithQualifierIfNeeded(IntrinsicName, _Program.GetSymbols()->AddChecked(IntrinsicName), SQualifier::NominalType(_Program._VerseModule), _CurrentScope);
}
// Temporary helper function for identifying localization-related definitions
// that are EpicInternal but are required to appear in the digest for user code
// to see.
bool IsEpicInternalLocalizationDefinition(const CDefinition& Definition) const
{
bool Result = false;
Result |= (Definition.DerivedAccessLevel()._Kind == SAccessLevel::EKind::EpicInternal) && Definition.AsNameStringView() == "MakeMessageInternal";
Result |= (Definition.DerivedAccessLevel()._Kind == SAccessLevel::EKind::EpicInternal) && Definition.AsNameStringView() == "MakeLocalizableValue";
Result |= (Definition.DerivedAccessLevel()._Kind == SAccessLevel::EKind::EpicInternal) && Definition.AsNameStringView() == "LocalizeValue";
Result |= (Definition.DerivedAccessLevel()._Kind == SAccessLevel::EKind::EpicInternal) && Definition.AsNameStringView().StartsWith("localizable_");
return Result;
}
const CSemanticProgram& _Program;
const CAstPackage& _Package;
TSRef<CDiagnostics> _Diagnostics;
bool _bIncludeInternalDefinitions;
bool _bIncludeEpicInternalDefinitions;
mutable TArray<CUTF8String> _Usings;
mutable TSet<const CAstPackage*> _DependencyPackages;
mutable const CModule* _CurrentModule;
mutable const CScope* _CurrentScope;
mutable const CAstNode* _CurrentGlitchAst;
CSymbol _Underscore;
const CUTF8String* _Notes;
using SGlobalSymbolOccurrences = TArrayG<CDefinition*, TInlineElementAllocator<1>>;
TMap<CSymbol, SGlobalSymbolOccurrences> _SymbolMap;
};
//====================================================================================
// Public API
//====================================================================================
namespace DigestGenerator
{
bool Generate(
const CSemanticProgram& Program,
const CAstPackage& Package,
bool bIncludeInternalDefinitions,
bool bIncludeEpicInternalDefinitions,
const TSRef<CDiagnostics>& Diagnostics,
const CUTF8String* Notes,
CUTF8String& OutDigestCode,
TArray<const CAstPackage*>& OutDigestPackageDependencies)
{
CDigestGeneratorImpl Generator(Program, Package, Diagnostics, Notes, bIncludeInternalDefinitions, bIncludeEpicInternalDefinitions);
return Generator.Generate(OutDigestCode, OutDigestPackageDependencies);
}
} // namespace DigestGenerator
} // namespace uLang