// 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& 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& OutDigestPackageDependencies) const { using namespace Verse::Vst; TSRef DigestSnippet = TSRef::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 UsingMacro = TSRef::New( NullWhence(), GenerateUseOfIntrinsic("using"), ClauseArray{ TSRef::New( TSRef::New(UsingPath, NullWhence()).As(), 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& Parent) const { TGuardValue CurrentScopeGuard(_CurrentScope, &Scope); bool bGeneratedAnything = false; for (const TSRef& Definition : Scope.GetDefinitions()) { switch (Definition->GetKind()) { case CDefinition::EKind::Class: { const CClass* cls = static_cast(&Definition->AsChecked()); if (cls->IsSubclassOf(*_Program._scopedClass)) { bGeneratedAnything |= GenerateForScopedAccessLevel(Definition->AsChecked(), Parent); } else { bGeneratedAnything |= GenerateDefinitionForClass(Definition->AsChecked(), Parent); } } break; case CDefinition::EKind::Data: bGeneratedAnything |= GenerateForDataDefinition(Definition->AsChecked(), Parent); break; case CDefinition::EKind::Enumeration: bGeneratedAnything |= GenerateForEnumeration(Definition->AsChecked(), Parent); break; case CDefinition::EKind::Enumerator: bGeneratedAnything |= GenerateForEnumerator(Definition->AsChecked(), Parent); break; case CDefinition::EKind::Function: bGeneratedAnything |= GenerateForFunction(Definition->AsChecked(), Parent); break; case CDefinition::EKind::Interface: bGeneratedAnything |= GenerateDefinitionForInterface(Definition->AsChecked(), Parent); break; case CDefinition::EKind::Module: bGeneratedAnything |= GenerateForModule(Definition->AsChecked(), Parent); break; case CDefinition::EKind::ModuleAlias: bGeneratedAnything |= GenerateForModuleAlias(Definition->AsChecked(), Parent); break; case CDefinition::EKind::TypeAlias: bGeneratedAnything |= GenerateForTypeAlias(Definition->AsChecked(), 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& 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& Parent) const { using namespace Verse::Vst; if (!ShouldGenerate(Module, false)) { return false; } TGuardValue CurrentModuleGuard(_CurrentModule, &Module); TSRef InnerClause = TSRef::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 Name = GenerateDefinitionIdentifier(Module); TSRef DefinitionVst = TSRef::New( NullWhence(), Name, TSRef::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 ImportPathComment = TSRef::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& Parent) const { using namespace Verse::Vst; if (!ShouldGenerate(ModuleAlias, true)) { return false; } TGuardValue GlitchAstGuard(_CurrentGlitchAst, ModuleAlias.GetAstNode()); TSRef Name = GenerateDefinitionIdentifier(ModuleAlias); TSRef Call = TSRef::New(NullWhence()); TSRef ImportIdentifier = GenerateUseOfIntrinsic("import"); ImportIdentifier->SetTag(PrePostCall::Op::Expression); Call->AppendChild(ImportIdentifier); TSRef Arguments = TSRef::New((uint8_t)PrePostCall::Op::SureCall, NullWhence(), Clause::EForm::NoSemicolonOrNewline); Call->AppendChild(Arguments); Arguments->AppendChild(TSRef::New(ModuleAlias.Module()->GetScopePath('/', CScope::EPathMode::PrefixSeparator), NullWhence())); TSRef DefinitionVst = TSRef::New(NullWhence(), Name, Call); DefinitionVst->SetNumNewLinesAfter(NumNewLinesForSpacing); GenerateForAttributes(ModuleAlias, Name, DefinitionVst); Parent->AppendChild(DefinitionVst); return true; } bool GenerateForTypeAlias(const CTypeAlias& TypeAlias, const TSRef& Parent) const { using namespace Verse::Vst; if (!ShouldGenerate(TypeAlias, true)) { return false; } TGuardValue GlitchAstGuard(_CurrentGlitchAst, TypeAlias.GetAstNode()); TSRef Name = GenerateDefinitionIdentifier(TypeAlias); TSRef Type = GenerateForType(TypeAlias.GetPositiveAliasedType()); TSRef DefinitionVst = TSRef::New(NullWhence(), Name, Type); DefinitionVst->SetNumNewLinesAfter(NumNewLinesForSpacing); GenerateForAttributes(TypeAlias, Name, DefinitionVst); Parent->AppendChild(DefinitionVst); return true; } TArray> GenerateForSuperType(const CExpressionBase& SuperType, TArray& VisitedPublicSuperInterfaces) const { using namespace Verse::Vst; // Properly generate qualifiers for a subset of syntax. TArray> Ret; if (SuperType.GetNodeType() == EAstNodeType::Identifier_Class) { const CExprIdentifierClass& SuperClassIdentifier = static_cast(SuperType); const CClass* SuperClass = SuperClassIdentifier.GetClass(_Program); TArray 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 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(SuperType); const CInterface* SuperInterface = SuperInterfaceIdentifier.GetInterface(_Program); TArray 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 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(); const CNormalType& SuperNormalType = SuperTypeType.PositiveType()->GetNormalType(); if (SuperNormalType.AsNullable() || SuperNormalType.AsNullable()) { TArray PublicSuperTypes = PublifyType(&SuperNormalType, VisitedPublicSuperInterfaces); for (const CNominalType* PublicType : PublicSuperTypes) { Ret.Add(GenerateForType(PublicType)); } } else { return { GenerateForType(SuperTypeType.PositiveType()) }; } } return Ret; } TSRef GenerateMacroForClassOrInterface( const CUTF8StringView& MacroName, const CLogicalScope& MemberScope, const TArray>& SuperTypes, const CAttributable* EffectsAttributable, const TOptional& ConstructorAccessLevel) const { using namespace Verse::Vst; // Create the class/interface macro. TSRef InnerClause = TSRef::New(NullWhence(), Clause::EForm::HasSemicolonOrNewline); InnerClause->SetNewLineAfter(true); TSRef ClassId = GenerateUseOfIntrinsic(MacroName); TSRef ClassIdCopy = ClassId; TSRef ClassMacro = TSRef::New( NullWhence(), Move(ClassIdCopy), ClauseArray{InnerClause}); if (!SuperTypes.IsEmpty()) { // Create the super clause. TSRef SuperClause = TSRef::New(vsyntax::res_of, NullWhence(), Clause::EForm::NoSemicolonOrNewline); SuperClause->SetNewLineAfter(true); bool bEmptySuperClause = true; TArray VisitedSuperInterface; for (const TSRef& SuperType : SuperTypes) { for (TSPtr 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>& SuperTypes, const TSRef& Parent, const CAttributable* EffectsAttributable, const TOptional& ConstructorAccessLevel) const { using namespace Verse::Vst; TGuardValue GlitchAstGuard(_CurrentGlitchAst, DefinitionAst.GetAstNode()); // Create the class/interface definition. TSRef Name = GenerateDefinitionIdentifier(DefinitionAst); TSRef ClassMacro = GenerateMacroForClassOrInterface(MacroName, MemberScope, SuperTypes, EffectsAttributable, ConstructorAccessLevel); TSRef DefinitionVst = TSRef::New(NullWhence(), Name, ClassMacro); DefinitionVst->SetNumNewLinesAfter(NumNewLinesForSpacing); GenerateForAttributes(DefinitionAst, Name, DefinitionVst); Parent->AppendChild(DefinitionVst); return true; } bool GenerateDefinitionForClass(const CClassDefinition& Class, const TSRef& 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 GenerateMacroForClass(const CClassDefinition& Class) const { TGuardValue 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& Parent) const { // Cull inaccessible classes if (!ShouldGenerate(Interface)) { return false; } return GenerateDefinitionForClassOrInterface( "interface", Interface, Interface, Interface.GetIrNode()->SuperInterfaces(), Parent, &Interface._EffectAttributable, Interface._ConstructorAccessLevel); } TSRef GenerateMacroForInterface(const CInterface& Interface) const { TGuardValue GlitchAstGuard(_CurrentGlitchAst, Interface.GetAstNode()); return GenerateMacroForClassOrInterface( "interface", Interface, Interface.GetIrNode()->SuperInterfaces(), &Interface._EffectAttributable, Interface._ConstructorAccessLevel); } bool GenerateForEnumerator(const CEnumerator& Enumerator, const TSRef& 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 EnumIdentifier = GenerateDefinitionIdentifier(Enumerator); GenerateForAttributes(Enumerator, Enumerator.SelfAccessLevel(), EnumIdentifier); EnumIdentifier->SetNewLineAfter(true); Parent->AppendChild(EnumIdentifier); return true; } bool GenerateForEnumeration(const CEnumeration& Enumeration, const TSRef& Parent) const { using namespace Verse::Vst; // Cull inaccessible enumerations if (!ShouldGenerate(Enumeration)) { return false; } TGuardValue GlitchAstGuard(_CurrentGlitchAst, Enumeration.GetAstNode()); // Create enum definition TSRef InnerClause = TSRef::New(NullWhence(), Clause::EForm::HasSemicolonOrNewline); InnerClause->SetNewLineAfter(true); // If to use vertical format GenerateForScope(Enumeration, InnerClause); TSRef Name = GenerateDefinitionIdentifier(Enumeration); TSRef EnumIdentifierVst = GenerateUseOfIntrinsic("enum"); GenerateForAttributes( Enumeration._EffectAttributable, TOptional{}, EnumIdentifierVst); TSRef DefinitionVst = TSRef::New( NullWhence(), Name, TSRef::New( NullWhence(), EnumIdentifierVst, ClauseArray{ InnerClause })); DefinitionVst->SetNumNewLinesAfter(NumNewLinesForSpacing); GenerateForAttributes(Enumeration, Name, DefinitionVst); Parent->AppendChild(DefinitionVst); return true; } bool GenerateForScopedPaths(const CScopedAccessLevelDefinition& ScopedAccessLevel, const TSRef& 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(GetDependencyName(*Scope->ScopeAsDefinition())); TSRef NewPathLiteral = TSRef::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 GenerateForScopedMacro(const CScopedAccessLevelDefinition& ScopedAccessLevel) const { using namespace Verse::Vst; // Create access level definition TSRef InnerClause = TSRef::New(NullWhence(), Clause::EForm::HasSemicolonOrNewline); InnerClause->SetNewLineAfter(false); GenerateForScopedPaths(ScopedAccessLevel, InnerClause); return TSRef::New( NullWhence(), GenerateUseOfIntrinsic("scoped"), ClauseArray{ InnerClause }); } bool GenerateForScopedAccessLevel(const CScopedAccessLevelDefinition& ScopedAccessLevel, const TSRef& Parent) const { using namespace Verse::Vst; // Cull inaccessible access levels if (!ShouldGenerate(ScopedAccessLevel)) { return false; } TGuardValue GlitchAstGuard(_CurrentGlitchAst, ScopedAccessLevel.GetAstNode()); TSRef ScopedDefinitionName = GenerateDefinitionIdentifier(ScopedAccessLevel); TSRef DefinitionVst = TSRef::New( NullWhence(), ScopedDefinitionName, GenerateForScopedMacro(ScopedAccessLevel)); DefinitionVst->SetNewLineAfter(true); GenerateForAttributes(ScopedAccessLevel, ScopedDefinitionName, DefinitionVst); Parent->AppendChild(DefinitionVst); return true; } bool GenerateForFunction(const CFunction& Function, const TSRef& Parent) const { using namespace Verse::Vst; // Cull inaccessible functions if (!ShouldGenerate(Function) || Function.IsCoercion()) { return false; } TGuardValue GlitchAstGuard(_CurrentGlitchAst, Function.GetAstNode()); // Create function definition TSRef Call = TSRef::New(NullWhence()); const CUTF8StringView FunctionNameStringView = Function._ExtensionFieldAccessorKind == EExtensionFieldAccessorKind::ExtensionMethod ? _Program._IntrinsicSymbols.StripExtensionFieldOpName(Function.GetName()) : Function.AsNameStringView(); TSRef FunctionName = GenerateDefinitionIdentifier(FunctionNameStringView, Function); TSRef 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 LhsParameter = TSRef::New(NullWhence(), Clause::EForm::NoSemicolonOrNewline); LhsParameter->SetTag(PrePostCall::Op::SureCall); TSRef DotCall = TSRef::New(NullWhence()); DotCall->AppendChild(LhsParameter); DotCall->AppendChild(FunctionName); Call->AppendChild(DotCall); if (ParameterList->GetChildCount() == 1) { TSRef Child = ParameterList->TakeChildAt(0); ULANG_ASSERT(Child->IsA()); TSRef WhereNode = Child.As(); LhsParameter->AppendChild(WhereNode); TSRef Lhs = WhereNode->GetLhs(); ULANG_ASSERT(Lhs->IsA()); 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(*Body)._Class; TSRef DefinitionVst = TSRef::New( NullWhence(), Call, GenerateMacroForClass(*Class._Definition)); DefinitionVst->SetNumNewLinesAfter(NumNewLinesForSpacing); TArray Attributes = Function._Attributes; if (TOptional 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 DefinitionVst = TSRef::New( NullWhence(), Call, GenerateMacroForInterface(static_cast(*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(); ReturnTypeType && !Function.GetReturnTypeIr() && Function._Signature.GetEffects() == EffectSets::Computes) { TSRef DefinitionVst = TSRef::New( NullWhence(), Call, GenerateForType(ReturnTypeType->PositiveType())); DefinitionVst->SetNumNewLinesAfter(NumNewLinesForSpacing); GenerateForAttributes(Function, FunctionName, DefinitionVst); Parent->AppendChild(DefinitionVst); } else { TSRef TypedCall = TSRef::New( NullWhence(), Call, GenerateForType(Function._Signature.GetReturnType())); // Generate an assignment to the external{} macro TSRef DefinitionVst = TSRef::New( NullWhence(), TypedCall, GenerateExternalMacro()); DefinitionVst->SetNumNewLinesAfter(NumNewLinesForSpacing); GenerateForAttributes(Function, FunctionName, DefinitionVst); Parent->AppendChild(DefinitionVst); GenerateForEffectAttributes(Function._Signature.GetFunctionType()->GetEffects(), EffectSets::FunctionDefault, *Call); } } } else { TSRef TypedCall = TSRef::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& Parent) const { using namespace Verse::Vst; // Cull inaccessible data definitions if (!ShouldGenerate(DataDefinition)) { return false; } TGuardValue GlitchAstGuard(_CurrentGlitchAst, DataDefinition.GetAstNode()); // Create data definition CSymbol Name = DataDefinition.GetName(); TSRef NameNode = GenerateDefinitionIdentifier(DataDefinition); TSPtr DecoratedNode; const CTypeBase* Type = DataDefinition.GetType(); if (DataDefinition.IsVar()) { DecoratedNode = TSRef::New(NullWhence(), NameNode, Mutation::EKeyword::Var); GenerateForAttributesGeneric({ }, DataDefinition.SelfVarAccessLevel(), [&DecoratedNode] (const CClass*) { return DecoratedNode.AsRef(); }); Type = Type->GetNormalType().AsChecked().PositiveValueType(); } else { DecoratedNode = NameNode; } TSRef TypeSpecNode = TSRef::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 DefinitionVst = TSRef::New( NullWhence(), TypeSpecNode, TSRef::New(NullWhence(), GenerateUseOfIntrinsic("external"), ClauseArray{ TSRef::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 GenerateForParameters(const CFunction& Function) const { using namespace Verse::Vst; const CExprFunctionDefinition* FunctionDefinition = Function.GetIrNode(); const TSPtr& 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(*Element); auto ParamDefinitionIterator = Function._Signature.GetParams().begin(); TSRef Parameter = GenerateForParameter(*Invocation.GetArgument(), ParamDefinitionIterator); TArray> TypeVariableDefinition; for (const TSRef& TypeVariable : Function.GetDefinitionsOfKind()) { if (TypeVariable->_ExplicitParam) { continue; } TypeVariableDefinition.Add(TSRef::New( NullWhence(), GenerateDefinitionIdentifier(*TypeVariable->Definition()), GenerateForType(TypeVariable->GetPositiveType()))); } return AsClause(TypeVariableDefinition.IsEmpty()? Move(Parameter) : TSRef::New(NullWhence(), Move(Parameter), Move(TypeVariableDefinition))); } template TSRef GenerateForParameter( const CExpressionBase& Expression, TParamDefinitionIterator& ParamDefinitionIterator) const { using namespace Verse::Vst; if (Expression.GetNodeType() == EAstNodeType::Invoke_MakeTuple) { TSRef Result = TSRef::New(NullWhence(), Clause::EForm::NoSemicolonOrNewline); const CExprMakeTuple& ExprMakeTuple = static_cast(Expression); for (const TSPtr& SubExpr : ExprMakeTuple.GetSubExprs()) { Result->AppendChild(GenerateForParameter(*SubExpr, ParamDefinitionIterator)); } return Result; } else if (Expression.GetNodeType() == EAstNodeType::Definition_Where) { const CExprWhere& ExprWhere = static_cast(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(Expression); const CDataDefinition* ParamDefinition = *ParamDefinitionIterator; ++ParamDefinitionIterator; TSPtr 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 ElementNode; if (ParamDefinition->_bNamed) { TSRef QMarkNode = TSRef::New(IdentifierNode.AsRef(), NullWhence()); QMarkNode->PrependQMark(NullWhence()); ElementNode = Move(QMarkNode); } else { ElementNode = IdentifierNode; } TSRef Result = TSRef::New( NullWhence(), Move(ElementNode.AsRef()), GenerateForType(Type)); if (ExprDefinition.Value()) { Result = TSRef::New( NullWhence(), Move(Result), GenerateExternalMacro()); } return Result; } } static TSRef AsClause(TSRef Node) { namespace Vst = Verse::Vst; if (Node->IsA()) { return Node.As(); } if (const Vst::Parens* Parens = Node->AsNullable()) { TSRef Clause = TSRef::New(NullWhence(), Parens->GetForm()); Clause->SetTag(Vst::PrePostCall::Op::SureCall); Vst::Node::TransferChildren(Node, Clause); return Clause; } TSRef Clause = TSRef::New(NullWhence(), Vst::Clause::EForm::NoSemicolonOrNewline); Clause->SetTag(Vst::PrePostCall::Op::SureCall); Clause->AppendChild(Node); return Clause; } TSRef GenerateForParamTypes(CFunctionType::ParamTypes ParamTypes) const { using namespace Verse::Vst; TSRef ParamList = TSRef::New(NullWhence(), Clause::EForm::NoSemicolonOrNewline); for (const CTypeBase* ParamType : ParamTypes) { ParamList->AppendChild(TSRef::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 GenerateForTupleType(const CTupleType& Tuple) const { using namespace Verse::Vst; TSRef Call = TSRef::New(NullWhence()); // Add `tuple` identifier TSRef TupleIdent = GenerateUseOfIntrinsic("tuple"); TupleIdent->SetTag(PrePostCall::Op::Expression); Call->AppendChild(TupleIdent); // Add element types TSRef Elements = TSRef::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 GenerateForIntrinsicInvocation( const CUTF8StringView& CalleeName, const TSRef& Argument) const { using namespace Verse::Vst; TSRef CalleeIdentifier = GenerateUseOfIntrinsic(CalleeName); CalleeIdentifier->SetTag(PrePostCall::Op::Expression); Argument->SetTag(PrePostCall::Op::SureCall); TSRef Call = TSRef::New(NullWhence()); Call->AppendChild(CalleeIdentifier); Call->AppendChild(Argument); return Call; } TSRef GenerateForIntrinsicTypeInvocation( const CUTF8StringView& CalleeName, const CTypeBase* TypeArgment) const { using namespace Verse::Vst; return GenerateForIntrinsicInvocation( CalleeName, TSRef::New( GenerateForType(TypeArgment), NullWhence(), Clause::EForm::NoSemicolonOrNewline)); } TSRef GenerateForSubtypeType(const CTypeBase* NegativeType, bool bCastableSubtype) const { return GenerateForIntrinsicTypeInvocation(bCastableSubtype ? "castable_subtype" : "subtype", NegativeType); } TSRef GenerateForSupertypeType(const CTypeBase* PositiveType) const { return GenerateForIntrinsicTypeInvocation("supertype", PositiveType); } TSRef GenerateForWeakMapType(const CTypeBase* KeyType, const CTypeBase* ValueType) const { using namespace Verse::Vst; return GenerateForIntrinsicInvocation( "weak_map", TSRef::New( TSRefArray{GenerateForType(KeyType), GenerateForType(ValueType)}, NullWhence(), Clause::EForm::NoSemicolonOrNewline)); } TSRef GenerateForGeneratorType(const CTypeBase* ElementType) const { using namespace Verse::Vst; return GenerateForIntrinsicInvocation( "generator", TSRef::New( TSRefArray{GenerateForType(ElementType)}, NullWhence(), Clause::EForm::NoSemicolonOrNewline)); } TSRef 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(); if (Class.GetParentScope()->GetKind() != CScope::EKind::Function) { return GenerateUseOfDefinition(*Class.Definition()); } TSRef Name = GenerateUseOfDefinition(*static_cast(Class.GetParentScope())); Name->SetTag(PrePostCall::Expression); TSRef ArgumentsClause = TSRef::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 Invocation = TSRef::New(NullWhence()); Invocation->AppendChild(Move(Name)); Invocation->AppendChild(Move(ArgumentsClause)); return Move(Invocation); } case ETypeKind::Interface: { const CInterface& Interface = Type.AsChecked(); if (Interface.GetParentScope()->GetKind() != CScope::EKind::Function) { return GenerateUseOfDefinition(Interface); } TSRef Name = GenerateUseOfDefinition(*static_cast(Interface.GetParentScope())); Name->SetTag(PrePostCall::Expression); TSRef ArgumentsClause = TSRef::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 Invocation = TSRef::New(NullWhence()); Invocation->AppendChild(Move(Name)); Invocation->AppendChild(Move(ArgumentsClause)); return Move(Invocation); } case ETypeKind::Tuple: return GenerateForTupleType(Type.AsChecked()); case ETypeKind::Enumeration: { const CEnumeration& Enumeration = Type.AsChecked(); return GenerateUseOfDefinition(Enumeration); } case ETypeKind::Option: { TSRef Option = TSRef::New(NullWhence()); Option->AppendChild(TSRef::New((uint8_t)PrePostCall::Op::Option, NullWhence(), Clause::EForm::Synthetic)); Option->AppendChild(GenerateForType(Type.AsChecked().GetValueType())); return Option; } case ETypeKind::Type: { const CTypeType& TypeType = Type.AsChecked(); const CNormalType& NegativeType = TypeType.NegativeType()->GetNormalType(); const CNormalType& PositiveType = TypeType.PositiveType()->GetNormalType(); if (NegativeType.IsA()) { if (PositiveType.IsA()) { return GenerateUseOfIntrinsic("type"); } const CTypeBase* SuperType = &PositiveType; bool bCastableSubtype = false; if (const CCastableType* CastablePositiveType = PositiveType.AsNullable()) { SuperType = &CastablePositiveType->SuperType(); bCastableSubtype = true; } return GenerateForSubtypeType(SuperType, bCastableSubtype); } if (PositiveType.IsA()) { 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 Name = GenerateUnderscore(); TSRef PrePost = TSRef::New(NullWhence()); const CFunctionType* FunctionType = &Type.AsChecked(); PrePost->AppendChild(Name); PrePost->AppendChild(GenerateForParamTypes(FunctionType->GetParamTypes())); GenerateForEffectAttributes(FunctionType->GetEffects(), EffectSets::FunctionDefault, *PrePost); return TSRef::New( NullWhence(), GenerateUseOfIntrinsic("type"), ClauseArray{ TSRef::New(TSRef::New(NullWhence(), PrePost, GenerateForType(&FunctionType->GetReturnType())).As(), NullWhence(), Clause::EForm::NoSemicolonOrNewline) }); } case ETypeKind::Array: { TSRef ArrayTypeFormer = TSRef::New(NullWhence()); ArrayTypeFormer->AppendChild(TSRef::New((uint8_t)PrePostCall::Op::FailCall, NullWhence(), Clause::EForm::NoSemicolonOrNewline)); ArrayTypeFormer->AppendChild(GenerateForType(Type.AsChecked().GetElementType())); return ArrayTypeFormer; } case ETypeKind::Generator: { const CGeneratorType& GeneratorType = Type.AsChecked(); return GenerateForGeneratorType(GeneratorType.GetElementType()); } case ETypeKind::Map: { const CMapType& MapType = Type.AsChecked(); if (MapType.IsWeak()) { return GenerateForWeakMapType(MapType.GetKeyType(), MapType.GetValueType()); } TSRef MapTypeFormer = TSRef::New(NullWhence()); TSRef KeyTypeClause = TSRef::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().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(""); } TSRef 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> EffectClasses = _Program.ConvertEffectSetToEffectClasses(Effects, DefaultEffects)) { for (const CClass* EffectClass : EffectClasses.GetValue()) { CallAttributable.AppendAux(TSRef::New(GenerateUseOfDefinition(*EffectClass->Definition()).As(), 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 ReformatDocCommentAsComments(const CUTF8String& TextValue) const { TArray 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 void GenerateForScopedAttribute(const CScopedAccessLevelDefinition& AccessLevelDefinition, const Func& SelectAttributable) const { using namespace Verse::Vst; // Determine which attributable to put the attribute on TSRef Attributable = SelectAttributable(&AccessLevelDefinition); TSRef newScopedMacro = GenerateForScopedMacro(AccessLevelDefinition); // attribute nodes must be wrapped in a Clause node (elsewhere used to preserve source comments) TSRef WrapperClause = TSRef::New(newScopedMacro.As(), NullWhence(), Clause::EForm::IsAppendAttributeHolder); Attributable->AppendAux(WrapperClause); } const CClass* GetClassForExpression(TSPtr Expression) const { const CNormalType& ExpressionResultType = Expression->GetResultType(_Program)->GetNormalType(); if (const CClass* Class = ExpressionResultType.AsNullable()) { return Class; } else if (const CTypeType* TypeType = ExpressionResultType.AsNullable()) { return TypeType->PositiveType()->GetNormalType().AsNullable(); } return nullptr; } TSRef GenerateForExpression(TSPtr ExprValue) const { using namespace Verse::Vst; TGuardValue GlitchAstGuard(_CurrentGlitchAst, ExprValue); TSPtr 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 ArchInstValue = ExprValue.As(); const CClass* AttributeClass = GetClassForExpression(ExprValue); CUTF8String AttributeName; // special case for parametric types TSPtr AttribMacro; if (ArchInstValue->_ClassAst->GetNodeType() == EAstNodeType::Invoke_Invocation) { TSPtr ExprInvoke = ArchInstValue->_ClassAst.As(); const CFunctionType* FuncType = ExprInvoke->GetResolvedCalleeType(); if (const CTypeType* ReturnTypeType = FuncType->GetReturnType().GetNormalType().AsNullable()) { 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 ValueClause = TSRef::New(NullWhence(), Clause::EForm::Synthetic, Clause::EPunctuation::Braces); // iterate each argument clause and append for (const TSRef& AttribArg : ArchInstValue->Arguments()) { TSPtr ArgIdent; TSPtr ArgValueClause; if (AttribArg->GetNodeType() == EAstNodeType::Definition) { TSRef AttribArgDef = AttribArg.As(); { // IDENT TSPtr AttribArgIdentData = AttribArgDef->Element().As(); ArgIdent.SetNew(AttribArgIdentData->GetName().AsStringView(), NullWhence()); } { // Value TSPtr AttribArgValue = AttribArgDef->Value().As(); TSPtr ArgValue = GenerateForExpression(AttribArgValue->_Argument); ArgValueClause.SetNew(NullWhence(), Clause::EForm::NoSemicolonOrNewline, Clause::EPunctuation::Unknown); ArgValueClause->AppendChild(ArgValue.AsRef()); } } TSRef Arg = TSRef::New(NullWhence(), ArgIdent.AsRef(), ArgValueClause.AsRef()); ValueClause->AppendChild(Arg); } TArray> ValueClauses = { ValueClause }; ResultValue = TSRef::New(NullWhence(), AttribMacro.AsRef(), TArray>(ValueClauses)); } break; case EAstNodeType::Literal_Logic: { TSPtr LogicValue = ExprValue.As(); ResultValue = GenerateUseOfIntrinsic(LogicValue->_Value ? "true" : "false"); } break; case EAstNodeType::Literal_Number: { TSPtr Number = ExprValue.As(); if (Number->IsFloat()) { CUTF8String FloatStr = uLang::CUTF8String("%lf", Number->GetFloatValue()); ResultValue = TSRef::New(FloatStr, FloatLiteral::EFormat::F64, NullWhence()).As(); } else { CUTF8String IntStr = uLang::CUTF8String("%ld", Number->GetIntValue()); ResultValue = TSRef::New(IntStr, NullWhence()); } } break; case EAstNodeType::Literal_Char: { TSPtr CharValue = ExprValue.As(); switch (CharValue->_Type) { case CExprChar::EType::UTF8CodeUnit: ResultValue = TSRef::New(uLang::CUTF8String("0o%X", CharValue->_CodePoint), CharLiteral::EFormat::UTF8CodeUnit, NullWhence()).As(); break; case CExprChar::EType::UnicodeCodePoint: ResultValue = TSRef::New(uLang::CUTF8String("0u%X", CharValue->_CodePoint), CharLiteral::EFormat::UnicodeCodePoint, NullWhence()).As(); break; default: _Diagnostics->AppendGlitch( SGlitchResult(EDiagnostic::ErrDigest_Unimplemented, uLang::CUTF8String("Unknown character format type.")), _CurrentGlitchAst); break; } } break; case EAstNodeType::Literal_String: { TSPtr StringValue = ExprValue.As(); ResultValue = TSRef::New(NullWhence(), StringValue->_String).As(); } break; case EAstNodeType::Literal_Path: { TSPtr PathValue = ExprValue.As(); ResultValue = TSRef::New(PathValue->_Path, NullWhence()).As(); } break; case EAstNodeType::Literal_Enum: { TSPtr EnumValue = ExprValue.As(); ResultValue = GenerateUseOfDefinition(*EnumValue->_Enumerator).As(); } break; case EAstNodeType::Literal_Type: { TSPtr TypeValue = ExprValue.As(); ResultValue = GenerateForType(TypeBase); } break; case EAstNodeType::Identifier_Class: { TSPtr ClassIdent = ExprValue.As(); ResultValue = GenerateUseOfDefinition(*ClassIdent->GetClass(_Program)->Definition()).As(); } break; case EAstNodeType::Identifier_Data: { TSPtr DataIdent = ExprValue.As(); ResultValue = GenerateUseOfDefinition(DataIdent->_DataDefinition).As(); } break; case EAstNodeType::Invoke_MakeOption: { TSPtr MakeOptionExpr = ExprValue.As(); if (TSPtr SubExpr = MakeOptionExpr->Operand()) { TSPtr OptionElementClause = TSPtr::New(NullWhence(), Clause::EForm::NoSemicolonOrNewline, Clause::EPunctuation::Braces); OptionElementClause->AppendChild(GenerateForExpression(SubExpr)); TArray> MacroClauseArray; MacroClauseArray.Add(OptionElementClause.AsRef()); ResultValue = TSRef::New(NullWhence(), GenerateUseOfIntrinsic("option"), MacroClauseArray ).As(); } else { // unset option ResultValue = GenerateUseOfIntrinsic("false"); } } break; case EAstNodeType::Invoke_MakeArray: { const CArrayType& ArrayNormalType = TypeBase->GetNormalType().AsChecked(); const CTypeBase* InnerType = ArrayNormalType.GetInnerType(); if (InnerType == &_Program._char8Type) { // string TSPtr StringValue = ExprValue.As(); ResultValue = TSRef::New(NullWhence(), StringValue->_String).As(); } else { TSRef ArrayElementClause = TSRef::New(NullWhence(), Clause::EForm::NoSemicolonOrNewline, Clause::EPunctuation::Braces); TSPtr MakeArrayExpr = ExprValue.As(); for (TSPtr SubExpr : MakeArrayExpr->GetSubExprs()) { ArrayElementClause->AppendChild(GenerateForExpression(SubExpr)); } TArray> MacroClauseArray; MacroClauseArray.Add(ArrayElementClause); ResultValue = TSRef::New(NullWhence(), GenerateUseOfIntrinsic("array"), MacroClauseArray ).As(); } } 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 void GenerateForAttributeArchetype(TSPtr AttributeExpr, const CClass* AttributeClass, const Func& SelectAttributable) const { using namespace Verse::Vst; // Determine which attributable to put the attribute on TSRef Attributable = SelectAttributable(AttributeClass); if (!_bIncludeEpicInternalDefinitions && IsEpicInternalOnlyAttributeClass(*AttributeClass->_Definition)) { // Filter out Epic-internal attributes from public-only digests. return; } TSRef 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 WrapperClause = TSRef::New(MacroInst.As(), NullWhence(), Clause::EForm::IsAppendAttributeHolder); Attributable->AppendAux(WrapperClause); } template void GenerateForAttributeGeneric(const CClass* AttributeClass, const uLang::TOptional& TextValue, const Func& SelectAttributable) const { using namespace Verse::Vst; // Determine which attributable to put the attribute on TSRef 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 NewComment = TSRef::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(strlen("_attribute"))); const CSymbol ConstructorSymbol = _Program.GetSymbols()->AddChecked(ConstructorName); if (const CFunction* AttributeConstructor = AttributeDefinition->_EnclosingScope.GetLogicalScope().FindFirstDefinitionOfKind(ConstructorSymbol)) { AttributeDefinition = AttributeConstructor; } } if (TextValue.IsSet()) { TSRef Value = TSRef::New(NullWhence(), TextValue.GetValue()); TSRef ValueClause = TSRef::New(Value.As(), NullWhence(), Clause::EForm::Synthetic); TSRef Name = GenerateUseOfDefinition(*AttributeDefinition); Name->SetTag(PrePostCall::Op::Expression); ValueClause->SetTag(PrePostCall::Op::SureCall); TSRef Call = TSRef::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 WrapperClause = TSRef::New(Call.As(), NullWhence(), Clause::EForm::IsAppendAttributeHolder); Attributable->AppendAux(WrapperClause); } else { // attribute nodes must be wrapped in a Clause node (elsewhere used to preserve source comments) TSRef AttributeIdentifier = GenerateUseOfDefinition(*AttributeDefinition); // NOTE: (yiliang.siew) We do this so that `` 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() || Attributable->IsA()) { AttributeIdentifier->SetNewLineAfter(true); } TSRef WrapperClause = TSRef::New(AttributeIdentifier.As(), NullWhence(), Clause::EForm::IsAppendAttributeHolder); Attributable->AppendAux(WrapperClause); } } template void GenerateForAttributesGeneric(const TArray& Attributes, const TOptional& 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(AttributeClass), SelectAttributable); } else if (!bIsAccessLevelAttribute) { if (Attribute._Expression->GetNodeType() == EAstNodeType::Invoke_ArchetypeInstantiation) { GenerateForAttributeArchetype(Attribute._Expression.As(), AttributeClass, SelectAttributable); } else { const uLang::TOptional 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& AccessLevel, const TSRef& Attributable) const { GenerateForAttributesGeneric(Attributes._Attributes, AccessLevel, [&Attributable] (const CClass*) { return Attributable; }); } void GenerateForAttributes(const TArray& Attributes, const CDefinition& Definition, const TSRef& NameAttributable, const TSRef& DefAttributable) const { GenerateForAttributesGeneric( Attributes, Definition.SelfAccessLevel(), [this, &NameAttributable, &DefAttributable] (const CClass* AttributeClass) { TSPtr Attributable; if (AttributeClass->HasAttributeClass(_Program._attributeScopeName, _Program)) { Attributable = NameAttributable; } else { Attributable = DefAttributable; } return Attributable.AsRef(); }); } void GenerateForAttributes(const CDefinition& Definition, const TSRef& NameAttributable, const TSRef& DefAttributable) const { GenerateForAttributes(Definition._Attributes, Definition, NameAttributable, DefAttributable); } TSRef GenerateExternalMacro() const { using namespace Verse::Vst; return TSRef::New( NullWhence(), GenerateUseOfIntrinsic("external"), ClauseArray{TSRef::New(NullWhence(), Clause::EForm::NoSemicolonOrNewline)}); } bool IsUsable(const CDefinition& Definition) const { SAccessLevel AccessLevel; if (Definition._EnclosingScope.GetKind() == CScope::EKind::Function) { AccessLevel = static_cast(Definition._EnclosingScope).DerivedAccessLevel(); } else { AccessLevel = Definition.DerivedAccessLevel(); } if (AccessLevel._Kind == Cases) { 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 PublifySuperInterface(const CInterface* Interface) const { // Is it public? if (IsUsable(*Interface)) { // Yes, return as-is return {Interface}; } // No, find public super interfaces TArray Result; for (const CInterface* SuperInterface : Interface->_SuperInterfaces) { TArray 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 PublifySuperInterfaces(const TArray& Interfaces) const { TArray Result; for (const CInterface* Interface : Interfaces) { TArray PublicSuperInterfaces = PublifySuperInterface(Interface); for (const CInterface* PublicSuperInterface : PublicSuperInterfaces) { Result.Add(PublicSuperInterface); } } return Result; } TArray PublifyType(const CTypeBase* TypeToPublify, TArray& VisitedPublicSuperInterfaces) const { if (const CClass* Class = TypeToPublify->GetNormalType().AsNullable()) { if (IsUsable(*Class->_Definition)) { return { Class }; } else { TArray 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()) { if (IsUsable(*Interface)) { int32_t NumVisitedSuperInterfaces = VisitedPublicSuperInterfaces.Num(); if (VisitedPublicSuperInterfaces.AddUnique(Interface) == NumVisitedSuperInterfaces) { return { Interface }; } else { return {}; } } TArray 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 := interface: * bar():string * * baz := class(foothis): * bar():string= * return "test" * ``` * * We still want to generate the following in the digest: * * ``` * foothis := interface: * bar():[]char * baz := class: * bar():[]char = external {} * ``` * * Since `baz.bar` is public and marked as ``, digest compilation would fail otherwise if `foothis.bar` was not present. */ auto IsOrHasPubliclyOverriddenAbstractMethod = [](const CDefinition& Definition, auto&& IsPubliclyOverriddenAbstractMethod) -> bool { if (Definition.IsA()) { for (const auto& ChildDefinition : Definition.AsChecked().GetDefinitions()) { if (IsPubliclyOverriddenAbstractMethod(*ChildDefinition, IsPubliclyOverriddenAbstractMethod)) { return true; } } return false; } const CDefinition* EnclosingDefinition = Definition._EnclosingScope.ScopeAsDefinition(); if (EnclosingDefinition && EnclosingDefinition->IsA()) { 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(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 || (_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 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(); const CUTF8String UsingVersePath = Module.GetScopePath('/', CScope::EPathMode::PrefixSeparator); return TSRef::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::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(&Scope) : nullptr; } static const CInterface* ScopeAsInterface(const CScope& Scope) { return Scope.GetKind() == CScope::EKind::Interface ? static_cast(&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 GenerateIdentifierWithQualifierIfNeeded(CUTF8StringView IdentifierString, CSymbol SymbolToResolve, SQualifier ImplicitQualifier, const CScope* Scope, bool bNeverQualify = false) const { using namespace Verse::Vst; TSRef IdentifierNode = TSRef::New(IdentifierString, NullWhence()); TSPtr QualifierNode; if (!ImplicitQualifier.IsUnspecified()) { if (!bNeverQualify && NeedsQualification(SymbolToResolve, Scope)) { QualifierNode = GenerateForQualifier(ImplicitQualifier); } } if (QualifierNode) { IdentifierNode->AppendChild(QualifierNode.AsRef()); } return IdentifierNode; } TSRef GenerateUnderscore() const { return TSRef::New(_Underscore.AsStringView(), NullWhence()); } TSRef GenerateDefinitionIdentifier(CUTF8StringView IdentifierString, const CDefinition& Definition, bool bNeverQualify = false) const { return GenerateIdentifierWithQualifierIfNeeded(IdentifierString, Definition.GetName(), Definition.GetImplicitQualifier(), &Definition._EnclosingScope, bNeverQualify); } TSRef GenerateDefinitionIdentifier(const CDefinition& Definition, bool bNeverQualify = false) const { return GenerateIdentifierWithQualifierIfNeeded(Definition.AsNameStringView(), Definition.GetName(), Definition.GetImplicitQualifier(), &Definition._EnclosingScope, bNeverQualify); } TSRef GenerateUseOfDefinition(const CDefinition& Definition) const { DeclareDependencyOnScope(Definition._EnclosingScope); return GenerateIdentifierWithQualifierIfNeeded(Definition.AsNameStringView(), Definition.GetName(), Definition.GetImplicitQualifier(), _CurrentScope); } TSRef 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 _Diagnostics; bool _bIncludeInternalDefinitions; bool _bIncludeEpicInternalDefinitions; mutable TArray _Usings; mutable TSet _DependencyPackages; mutable const CModule* _CurrentModule; mutable const CScope* _CurrentScope; mutable const CAstNode* _CurrentGlitchAst; CSymbol _Underscore; const CUTF8String* _Notes; using SGlobalSymbolOccurrences = TArrayG>; TMap _SymbolMap; }; //==================================================================================== // Public API //==================================================================================== namespace DigestGenerator { bool Generate( const CSemanticProgram& Program, const CAstPackage& Package, bool bIncludeInternalDefinitions, bool bIncludeEpicInternalDefinitions, const TSRef& Diagnostics, const CUTF8String* Notes, CUTF8String& OutDigestCode, TArray& OutDigestPackageDependencies) { CDigestGeneratorImpl Generator(Program, Package, Diagnostics, Notes, bIncludeInternalDefinitions, bIncludeEpicInternalDefinitions); return Generator.Generate(OutDigestCode, OutDigestPackageDependencies); } } // namespace DigestGenerator } // namespace uLang