{- Copyright (c) Meta Platforms, Inc. and affiliates. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. -} {-# LANGUAGE ApplicativeDo #-} {-# LANGUAGE ConstraintKinds #-} {-# OPTIONS_GHC -Wno-orphans #-} module Glean.Glass.Pretty.Cxx ( prettyCxxSignature, Qualified(..) ) where import qualified Glean import qualified Glean.Schema.CodeCxx.Types as Cxx import qualified Glean.Schema.Cxx1.Types as Cxx import Data.Text ( Text, intercalate ) import Compat.Prettyprinter (pretty, layoutSmart, LayoutOptions, SimpleDocStream, reAnnotateS) import Data.Maybe ( fromMaybe ) import qualified Data.Text as Text import Safe (atMay) import Util.Text (textShow) import Glean.Glass.Types ( SymbolId(..) ) (<+>) :: Text -> Text -> Text "" <+> b = b a <+> "" = a a <+> b = a <> " " <> b (<::>) :: Text -> Text -> Text a <::> b = a <> "::" <> b data Qualified = Qualified | Unqualified isQualified :: Qualified -> Bool isQualified Qualified = True isQualified Unqualified = False -- This behavior is intended to match clangd 'hover' feature -- to make sure, user would not notice the change from -- clangd language server to glean-glass language server. -- -- Some signatures doesn't match clangd -- * glean-glass signatures have more information than clangd (easy to fix) -- * glean-glass signatures have less information than clangd (hard to fix, -- as it may involve changing of the cxx schema and clang indexer changes) -- -- Signatures can be qualified or unqualified. This affects whether the -- scope of the symbol will be output. Other symbol signatures in -- the pretty signature will remain fully qualified even if this is -- false. prettyCxxSignature :: LayoutOptions -> Cxx.Entity -> Qualified -> Maybe (SimpleDocStream (Maybe SymbolId)) prettyCxxSignature opts e qualified = case text of "" -> Nothing _ -> Just $ reAnnotateS (const Nothing) $ layoutSmart opts $ pretty text where text = case e of Cxx.Entity_decl decl -> prettyDecl decl qualified Cxx.Entity_defn def -> prettyDefinition def qualified Cxx.Entity_enumerator x -> prettyEnumerator x -- clangd doesn't qualify enumerators Cxx.Entity_objcSelectorSlot slot -> prettyObjcSelectorSlot slot Cxx.Entity_EMPTY -> intentionallyEmpty intentionallyEmpty :: Text intentionallyEmpty = "" todoEmpty :: Text todoEmpty = "" -- The key should always be there, but it may not be fetched. -- It is a responsibility of the caller to make sure -- the Entity is fully fetched before -- generating Entity's signature missingKey :: Text missingKey = "" anonymousNamespace :: Text anonymousNamespace = "(anonymous)" implicitUnderlyingEnumType :: Text implicitUnderlyingEnumType = "int" prettyEnumerator :: Cxx.Enumerator -> Text prettyEnumerator (Cxx.Enumerator _ mkey) = case mkey of Nothing -> missingKey -- clangd just shows name and just it :( it is not useful actually -- but we want to stick to the clangd Just (Cxx.Enumerator_key name _ _) -> prettyName name -- Instead we could have had potentially: -- -> prettyEnum enum_decl <+> "{ " <+> prettyName name <+> " }" -- which would have produced something like -- "enum class biggrep::FetchResult { NoSpace }" -- * Which doesn't have all values of the enum -- * but it is easy to read -- * and it WILL be correctly cpp-highlighted prettyDecl :: Cxx.Declaration -> Qualified -> Text prettyDecl d qualified = case d of Cxx.Declaration_namespace_ x -> prettyNamespace x qualified Cxx.Declaration_usingDeclaration x -> prettyUsingDecl x Cxx.Declaration_usingDirective x -> prettyUsingDirective x Cxx.Declaration_enum_ x -> prettyEnum x qualified Cxx.Declaration_variable x -> prettyVariable x qualified Cxx.Declaration_objcContainer _ -> intentionallyEmpty Cxx.Declaration_objcMethod _ -> intentionallyEmpty Cxx.Declaration_objcProperty _ -> intentionallyEmpty Cxx.Declaration_typeAlias x -> prettyTypeAlias x qualified Cxx.Declaration_namespaceAlias x -> prettyNamespaceAlias x qualified Cxx.Declaration_record_ x -> prettyRecord x qualified Cxx.Declaration_function_ x -> prettyFunction x qualified Cxx.Declaration_EMPTY -> intentionallyEmpty prettyEnum :: Cxx.EnumDeclaration -> Qualified -> Text prettyEnum (Cxx.EnumDeclaration _ mkey) qualified = case mkey of Just (Cxx.EnumDeclaration_key qname is_scoped ttype _) -> foldr1 (<+>) [ "enum" , optional is_scoped "class" , prettyQName qname qualified , prettyEnumType ttype ] Nothing -> missingKey prettyEnumType :: Maybe Cxx.Type -> Text prettyEnumType maybeType = ":" <+> fillImplicitUnderlyingType maybeType where fillImplicitUnderlyingType Nothing = implicitUnderlyingEnumType fillImplicitUnderlyingType (Just ttype) = prettyType ttype prettyObjcSelectorSlot :: Cxx.ObjcSelectorSlotEntity -> Text prettyObjcSelectorSlot (Cxx.ObjcSelectorSlotEntity method idx) = fromMaybe missingKey $ do decl <- case method of Cxx.ObjcMethodEntity_decl decl -> Just decl Cxx.ObjcMethodEntity_defn Cxx.ObjcMethodDefinition{ objcMethodDefinition_key = Just decl} -> Just decl _ -> Nothing Cxx.ObjcMethodDeclaration_key{ objcMethodDeclaration_key_selector = Cxx.ObjcSelector{ objcSelector_key = Just selector } } <- Glean.getFactKey decl let index = fromIntegral $ Glean.fromNat idx atMay selector index -- Notes: -- * "kind = { global_ = {definition = true}" doesn't guarantee that this -- variable has an 'extern' key prettyVariable :: Cxx.VariableDeclaration -> Qualified -> Text prettyVariable (Cxx.VariableDeclaration _ mkey) qualified = case mkey of Nothing -> missingKey Just (Cxx.VariableDeclaration_key qname ttype kind _) -> foldr1 (<+>) [ prettyVarPrefix kind , prettyType ttype , prettyQName qname qualified ] prettyVarPrefix :: Cxx.VariableKind -> Text prettyVarPrefix kind = case kind of Cxx.VariableKind_field (Cxx.Field mutable _) -> optional mutable "mutable" Cxx.VariableKind_global_ (Cxx.GlobalVariable gv_kind attribute _) -> prefix1 <+> prefix2 where prefix1 = case gv_kind of Cxx.GlobalVariableKind_StaticVariable -> "static" Cxx.GlobalVariableKind_StaticMember -> "static" Cxx.GlobalVariableKind_SimpleVariable -> intentionallyEmpty Cxx.GlobalVariableKind__UNKNOWN {} -> intentionallyEmpty prefix2 = case attribute of Cxx.GlobalVariableAttribute_Plain -> intentionallyEmpty Cxx.GlobalVariableAttribute_Inline -> intentionallyEmpty -- TODO: there is a bug with const Cxx.GlobalVariableAttribute_Constexpr -> todoEmpty Cxx.GlobalVariableAttribute__UNKNOWN {} -> todoEmpty Cxx.VariableKind_local (Cxx.LocalVariable lv_kind attribute) -> prefix1 <+> prefix2 where prefix1 = case lv_kind of Cxx.LocalVariableKind_StaticVariable -> "static" Cxx.LocalVariableKind_SimpleVariable -> intentionallyEmpty Cxx.LocalVariableKind_Parameter -> intentionallyEmpty Cxx.LocalVariableKind__UNKNOWN {} -> intentionallyEmpty prefix2 = case attribute of Cxx.LocalVariableAttribute_Plain -> intentionallyEmpty -- TODO: there is a bug with const Cxx.LocalVariableAttribute_Constexpr -> todoEmpty Cxx.LocalVariableAttribute__UNKNOWN {} -> todoEmpty Cxx.VariableKind_ivar _ -> intentionallyEmpty Cxx.VariableKind_EMPTY -> todoEmpty prettyTypeAlias :: Cxx.TypeAliasDeclaration -> Qualified -> Text prettyTypeAlias (Cxx.TypeAliasDeclaration _ mkey) qualified = case mkey of Nothing -> missingKey Just (Cxx.TypeAliasDeclaration_key name ttype kind _) -> case kind of Cxx.TypeAliasKind_Typedef -> foldr1 (<+>) [ "typedef" , prettyType ttype , prettyQName name qualified ] Cxx.TypeAliasKind_Using -> foldr1 (<+>) [ "using" , prettyQName name qualified , "=" , prettyType ttype ] Cxx.TypeAliasKind__UNKNOWN {} -> todoEmpty prettyNamespaceAlias :: Cxx.NamespaceAliasDeclaration -> Qualified -> Text prettyNamespaceAlias (Cxx.NamespaceAliasDeclaration _ mkey) qualified = case mkey of Nothing -> missingKey Just (Cxx.NamespaceAliasDeclaration_key qname target _) -> foldr1 (<+>) [ "namespace", prettyNamespaceQName qname qualified, "=", targetName ] where targetName = case target of Cxx.NamespaceTarget_namespace_ (Cxx.NamespaceDeclaration _ mkey) -> case mkey of Just (Cxx.NamespaceDeclaration_key qname _) -> prettyNamespaceQName qname Qualified Nothing -> missingKey Cxx.NamespaceTarget_namespaceAlias (Cxx.NamespaceAliasDeclaration _ mkey) -> case mkey of Just (Cxx.NamespaceAliasDeclaration_key qname _ _) -> prettyNamespaceQName qname Qualified Nothing -> missingKey Cxx.NamespaceTarget_EMPTY -> todoEmpty prettyFunction :: Cxx.FunctionDeclaration -> Qualified -> Text prettyFunction (Cxx.FunctionDeclaration _ mkey) qualified = case mkey of Nothing -> missingKey Just (Cxx.FunctionDeclaration_key name sign mmethod _) -> foldr1 (<+>) [ prettyFuncPrefix mmethod , if hasReturn name then prettyReturnType sign else intentionallyEmpty , prettyFuncQName name qualified <> "(" <> prettyParams sign <> ")" , prettyFuncPostfix mmethod ] -- False means that we MUST not even try to print the return type -- For example in constructor we have it set to 'void' -- but we do not want to print it hasReturn :: Cxx.FunctionQName -> Bool hasReturn (Cxx.FunctionQName _ mkey) = case mkey of Nothing -> True Just (Cxx.FunctionQName_key (Cxx.FunctionName _ mmkey) _) -> case mmkey of Nothing -> True -- TODO Just key -> case key of Cxx.FunctionName_key_name _ -> True Cxx.FunctionName_key_literalOperator _ -> True -- TODO: check it Cxx.FunctionName_key_conversionOperator _ -> False Cxx.FunctionName_key_operator_ _ -> True Cxx.FunctionName_key_constructor _ -> False Cxx.FunctionName_key_destructor _ -> False Cxx.FunctionName_key_EMPTY -> True prettyFuncPostfix :: Maybe Cxx.MethodSignature -> Text prettyFuncPostfix Nothing = todoEmpty prettyFuncPostfix (Just (Cxx.MethodSignature _ is_const _ _)) = optional is_const "const" prettyFuncPrefix :: Maybe Cxx.MethodSignature -> Text prettyFuncPrefix Nothing = todoEmpty prettyFuncPrefix (Just (Cxx.MethodSignature is_virtual _ is_volatile _)) = optional is_virtual "virtual" <+> optional is_volatile "volatile" prettyReturnType :: Cxx.Signature -> Text prettyReturnType (Cxx.Signature _ mkey) = case mkey of Nothing -> missingKey Just (Cxx.Signature_key mtype _) -> prettyType mtype prettyParams :: Cxx.Signature -> Text prettyParams (Cxx.Signature _ mkey) = case mkey of Nothing -> todoEmpty Just (Cxx.Signature_key _ []) -> intentionallyEmpty Just (Cxx.Signature_key _ params) -> intercalate ", " (map prettyParameter params) prettyParameter :: Cxx.Parameter -> Text prettyParameter (Cxx.Parameter name mtype) = prettyType mtype <+> prettyName name prettyType :: Cxx.Type -> Text prettyType (Cxx.Type _ mkey) = alignRefOrPtrQualifier $ fromMaybe "" mkey alignRefOrPtrQualifier :: Text -> Text alignRefOrPtrQualifier ttype = Text.stripEnd (Text.dropWhileEnd isRefOrPtrQualifier ttype) <> Text.takeWhileEnd isRefOrPtrQualifier ttype where isRefOrPtrQualifier x = x == '&' || x == '*' prettyFuncQName :: Cxx.FunctionQName -> Qualified -> Text prettyFuncQName (Cxx.FunctionQName _ mkey) qualified = case mkey of Nothing -> missingKey Just (Cxx.FunctionQName_key func_name scope) | isQualified qualified -> prettyScope scope "::" <> prettyFunctionName func_name scope | otherwise -> prettyFunctionName func_name scope prettyFunctionName :: Cxx.FunctionName -> Cxx.Scope -> Text prettyFunctionName (Cxx.FunctionName _ mkey) scope = case mkey of Nothing -> missingKey Just key -> case key of Cxx.FunctionName_key_name nname -> prettyName nname Cxx.FunctionName_key_literalOperator loname -> loname Cxx.FunctionName_key_conversionOperator mtype -> prettyType mtype Cxx.FunctionName_key_operator_ oname | is_lambda -> "lambda_" <> getRecordId scope | otherwise -> oname Cxx.FunctionName_key_constructor _ -> owner_name Cxx.FunctionName_key_destructor _ -> "~" <> owner_name Cxx.FunctionName_key_EMPTY -> "" where owner_name = prettyScopeBasic scope is_lambda = case scope of Cxx.Scope_recordWithAccess (Cxx.Scope_recordWithAccess_ _ _) -> Text.null owner_name _ -> False getRecordId :: Cxx.Scope -> Text getRecordId scope = case scope of -- TODO: Make it better. Seems to be prone to errors Cxx.Scope_recordWithAccess ( Cxx.Scope_recordWithAccess_ (Cxx.QName _ (Just (Cxx.QName_key (Cxx.Name id _) _))) _ ) -> textShow id _ -> intentionallyEmpty prettyNamespace :: Cxx.NamespaceDeclaration -> Qualified -> Text prettyNamespace (Cxx.NamespaceDeclaration _ mkey) qualified = case mkey of Just (Cxx.NamespaceDeclaration_key qname _) -> "namespace" <+> prettyNamespaceQName qname qualified -- TODO: Decide anonymousNamespace | intentionallyEmpty | missingKey -- Check if there are "anonymous" things setting 'key' to Nothing. Nothing -> todoEmpty prettyNamespaceQName :: Cxx.NamespaceQName -> Qualified -> Text prettyNamespaceQName (Cxx.NamespaceQName _ mkey) qualified = case mkey of Just key -> case key of -- TODO: Decide anonymousNamespace | intentionallyEmpty | missingKey -- Check if there are "anonymous" namespaces setting 'name' to Nothing. Cxx.NamespaceQName_key name Nothing -> maybe todoEmpty prettyName name Cxx.NamespaceQName_key name (Just parent) | isQualified qualified -> prettyNamespaceQName parent Qualified <::> maybe todoEmpty prettyName name | otherwise -> maybe todoEmpty prettyName name -- Scope.namespace in VariableDeclaration hits it here Nothing -> anonymousNamespace prettyName :: Cxx.Name -> Text -- NamespaceDeclaration hits it here prettyName (Cxx.Name _ key) = fromMaybe anonymousNamespace key prettyRecord :: Cxx.RecordDeclaration -> Qualified -> Text prettyRecord (Cxx.RecordDeclaration _ mkey) qualified = case mkey of Nothing -> missingKey Just (Cxx.RecordDeclaration_key name kind _) -> case kind of Cxx.RecordKind_struct_ {} -> "struct" <+> prettyQName name qualified Cxx.RecordKind_class_ {} -> "class" <+> prettyQName name qualified Cxx.RecordKind_union_ {} -> "union" <+> prettyQName name qualified Cxx.RecordKind_EMPTY -> "" <+> prettyQName name qualified -- Produces X::Y::Z::T -- Uses "::" as separator everywhere prettyQName :: Cxx.QName -> Qualified -> Text prettyQName (Cxx.QName _ mkey) qualified = case mkey of Nothing -> missingKey Just (Cxx.QName_key name scope) | isQualified qualified -> prettyScope scope "::" <> prettyName name | otherwise -> prettyName name -- Intentionally scope has the <::> inside the function -- as we want to distinguish local and global as otherwise -- it is impossible to handle it different ways on the caller side prettyScope :: Cxx.Scope -> Text -> Text prettyScope k separator = case k of Cxx.Scope_global_ _ -> separator Cxx.Scope_namespace_ name -> prettyNamespaceQName name Qualified <> separator Cxx.Scope_recordWithAccess (Cxx.Scope_recordWithAccess_ record _) -> prettyQName record Qualified <> separator -- Current implementation explicitly ignores it -- but we can actually print a function name if needed Cxx.Scope_local _ -> intentionallyEmpty Cxx.Scope_EMPTY -> "" prettyScopeBasic :: Cxx.Scope -> Text prettyScopeBasic k = case k of Cxx.Scope_recordWithAccess (Cxx.Scope_recordWithAccess_ (Cxx.QName _ mkey) _) -> case mkey of Nothing -> missingKey Just (Cxx.QName_key name _) -> prettyName name _ -> intentionallyEmpty optional :: Bool -> Text -> Text optional True text = text optional False _ = intentionallyEmpty prettyUsingDecl :: Cxx.UsingDeclaration -> Text prettyUsingDecl (Cxx.UsingDeclaration _ mkey) = case mkey of Nothing -> missingKey Just (Cxx.UsingDeclaration_key name _) -> -- using declarations are always qualified as they don't -- have a declarator nested-name-specifier to omit in an -- unqualified-id -- for example `using namespace std::chrono;` declares no -- symbol name that could be an unqualified-id OR a qualified-id -- In this case, we qualify the symbol they refer to, which -- is always unscoped but fully qualified "using" <+> prettyFuncQName name Qualified prettyUsingDirective :: Cxx.UsingDirective -> Text prettyUsingDirective (Cxx.UsingDirective _ mkey) = case mkey of Nothing -> missingKey Just (Cxx.UsingDirective_key name _) -> -- using directives are always qualified as they don't -- have a declarator nested-name-specifier to omit in an -- unqualified-id -- for example `using namespace std::chrono;` declares no -- symbol name that could be an unqualified-id OR a qualified-id -- In this case, we qualify the symbol they refer to, which -- is always unscoped but fully qualified "using namespace" <+> prettyQName name Qualified prettyDefinition :: Cxx.Definition -> Qualified -> Text prettyDefinition def qualified = case def of Cxx.Definition_record_ x -> prettyRecordDef x qualified Cxx.Definition_function_ x -> prettyFuncDef x qualified Cxx.Definition_enum_ x -> prettyEnumDef x qualified Cxx.Definition_objcMethod _ -> intentionallyEmpty Cxx.Definition_objcContainer _ -> intentionallyEmpty Cxx.Definition_variable x -> prettyVariable x qualified Cxx.Definition_namespace_ x -> prettyNamespaceDef x qualified Cxx.Definition_EMPTY -> intentionallyEmpty prettyNamespaceDef :: Cxx.NamespaceDefinition -> Qualified -> Text prettyNamespaceDef (Cxx.NamespaceDefinition _ mkey) qualified = case mkey of Nothing -> missingKey Just (Cxx.NamespaceDefinition_key decl _) -> prettyNamespace decl qualified prettyEnumDef :: Cxx.EnumDefinition -> Qualified -> Text prettyEnumDef (Cxx.EnumDefinition _ mkey) qualified = case mkey of Nothing -> missingKey Just (Cxx.EnumDefinition_key decl _) -> prettyEnum decl qualified prettyFuncDef :: Cxx.FunctionDefinition -> Qualified -> Text prettyFuncDef (Cxx.FunctionDefinition _ mkey) qualified = case mkey of Nothing -> missingKey Just (Cxx.FunctionDefinition_key decl _) -> prettyFunction decl qualified prettyRecordDef :: Cxx.RecordDefinition -> Qualified -> Text prettyRecordDef (Cxx.RecordDefinition _ mkey) qualified = case mkey of Nothing -> missingKey Just (Cxx.RecordDefinition_key decl _ _) -> prettyRecord decl qualified