Is there a way to get the AST of Daml source code?

For Haskell, there are libraries, e.g. this one:haskell-tools-ast: Haskell AST for efficient tooling

Maybe I can use such a library with some conversions?

Interested in how you would plan to use this data if you could get it. I know that some tool vendors use this for SAST scanning.

I do Daml metaprogramming, meaning rewriting and generating code.

The ideal starting point for this is when all imports are qualified, because in this case we don’t have to rely on external data (implicit imports) to handle the code and don’t have to bother about different import syntaxes.

I wrote a script for converting all imports into qualified imports, but it’s not perfect. E.g. it cannot distinguish between an imported function name and a local variable, if the token is the same (see the “Qualified name in binding position” error message).

I can extract some information about the package using the DAML LF API Type Signature package, but that only contains information about the record and variant types, nothing about functions and classes.

So I want to refine my script and my guess is that the AST of the source code would help (by AST I mean not the parse tree, this is the source of my understanding about the difference between the AST and the parse tree: Abstract syntax tree - Wikipedia).

There isn’t any tooling for Daml that gives you an AST unfortunately.

1 Like

Like @cocreature says, there is no tooling for this at the moment. But, to throw in my two cents:

There are a few different “ASTs” for Daml - there is the AST which represents loosely the Haskell to which Daml code desugars, and then there is the AST representing the underlying executable lambda calculus that we use (called Daml-LF). Depending on what you want, one of these might be better. There is no AST for Daml code proper, all Daml code is pretty much parsed into a Haskell AST directly.

I assume for the qualified imports use case, the Haskell AST would be closest to what you needed.

As a pointer if you wanted to implement something like this yourself, there’s a good deal of prior work in GitHub - digital-asset/daml: The Daml smart contract language and GitHub - digital-asset/daml-ghcide: daml fork of haskell/ghcide, ultimately relying on GitHub - digital-asset/ghc: Fork of GHC (https://gitlab.haskell.org/ghc/ghc.git).

To understand the most basic form of parsing, inside the daml repo, in compiler/damlc/daml-desugar/src/DA/Daml/Desugar.hs there is a function desugar which takes source, parses it, and pretty prints the parsed result. Refer to execDesugar and cmdDesugar in compiler/damlc/lib/DA/Cli/Damlc.hs to see how it is executed.

To understand later phases (typechecking, LF generation) a decent entry point would be execBuild in compiler/damlc/lib/DA/Cli/Damlc.hs and buildDar in compiler/damlc/daml-compiler/src/DA/Daml/Compiler/Dar.hs. That’s how daml build does dar generation.

From this point on we use rules that we’ve defined in a build system called Shake. buildDar uses our GeneratePackage rule - this dispatches to GenerateDalf, when then goes to GenerateRawDalf, which finally calls generateCore (the GenerateCore rule) from the daml-ghcide repo. Staying within the daml-ghcide repo, generateCore uses the Typecheck rule (produces TypecheckedModule), which uses the GetParsedModule rule (produces ParsedModule). The TypecheckedModule contains a lot more information about names because it runs the renaming phase - depending on what you need, the TypecheckedModule or ParsedModule could be appropriate.

Keep in mind this is all internally-facing code - we don’t guarantee it will remain stable.

1 Like

Thank you, Dylan, I will check these out. I’ve just seen the Deep Dive into the Daml Compiler video, these links will lead me into an even deeper dive into the topic.

Hey Dylan,

I tried using the desugar function in order to get the Haskell AST and the Haskell code.

However, when trying to translate a simple whitelistedRegistry contract which has less than 40 lines in Daml and only 3 choices (changeOwner, SetWhitelisted and isWhitelisted) I got the following Haskell code:

module WhitelistedRegistry where

import (implicit) qualified DA.Internal.Record

import (implicit) qualified GHC.Types

import (implicit) qualified DA.Internal.Desugar

import (implicit) DA.Internal.RebindableSyntax

data GHC.Types.DamlTemplate => WhitelistedRegistry

= WhitelistedRegistry {owner : Party, whitelisted : [Party]}

deriving (DA.Internal.Desugar.Eq, DA.Internal.Desugar.Show)

instance DA.Internal.Record.GetField “owner” WhitelistedRegistry Party where

getField

= DA.Internal.Record.getFieldPrim

    @"owner" @WhitelistedRegistry @Party

instance DA.Internal.Record.SetField “owner” WhitelistedRegistry Party where

setField

= DA.Internal.Record.setFieldPrim

    @"owner" @WhitelistedRegistry @Party

instance DA.Internal.Record.GetField “whitelisted” WhitelistedRegistry [Party] where

getField

= DA.Internal.Record.getFieldPrim

    @"whitelisted" @WhitelistedRegistry @\[Party\]

instance DA.Internal.Record.SetField “whitelisted” WhitelistedRegistry [Party] where

setField

= DA.Internal.Record.setFieldPrim

    @"whitelisted" @WhitelistedRegistry @\[Party\]

data ChangeOwner

= ChangeOwner {newOwner : Party}

deriving (DA.Internal.Desugar.Eq, DA.Internal.Desugar.Show)

instance DA.Internal.Record.GetField “newOwner” ChangeOwner Party where

getField

= DA.Internal.Record.getFieldPrim @"newOwner" @ChangeOwner @Party

instance DA.Internal.Record.SetField “newOwner” ChangeOwner Party where

setField

= DA.Internal.Record.setFieldPrim @"newOwner" @ChangeOwner @Party

data SetWhitelisted

= SetWhitelisted {addr : Party, isWhitelisted : Bool}

deriving (DA.Internal.Desugar.Eq, DA.Internal.Desugar.Show)

instance DA.Internal.Record.GetField “addr” SetWhitelisted Party where

getField

= DA.Internal.Record.getFieldPrim @"addr" @SetWhitelisted @Party

instance DA.Internal.Record.SetField “addr” SetWhitelisted Party where

setField

= DA.Internal.Record.setFieldPrim @"addr" @SetWhitelisted @Party

instance DA.Internal.Record.GetField “isWhitelisted” SetWhitelisted Bool where

getField

= DA.Internal.Record.getFieldPrim

    @"isWhitelisted" @SetWhitelisted @Bool

instance DA.Internal.Record.SetField “isWhitelisted” SetWhitelisted Bool where

setField

= DA.Internal.Record.setFieldPrim

    @"isWhitelisted" @SetWhitelisted @Bool

data IsWhitelisted

= IsWhitelisted {addr : Party, caller : Party}

deriving (DA.Internal.Desugar.Eq, DA.Internal.Desugar.Show)

instance DA.Internal.Record.GetField “addr” IsWhitelisted Party where

getField

= DA.Internal.Record.getFieldPrim @"addr" @IsWhitelisted @Party

instance DA.Internal.Record.SetField “addr” IsWhitelisted Party where

setField

= DA.Internal.Record.setFieldPrim @"addr" @IsWhitelisted @Party

instance DA.Internal.Record.GetField “caller” IsWhitelisted Party where

getField

= DA.Internal.Record.getFieldPrim @"caller" @IsWhitelisted @Party

instance DA.Internal.Record.SetField “caller” IsWhitelisted Party where

setField

= DA.Internal.Record.setFieldPrim @"caller" @IsWhitelisted @Party

instance DA.Internal.Desugar.HasSignatory WhitelistedRegistry where

signatory this@WhitelistedRegistry {..}

= DA.Internal.Desugar.toParties (owner)

where

    \_ = this

instance DA.Internal.Desugar.HasObserver WhitelistedRegistry where

observer this@WhitelistedRegistry {..}

= DA.Internal.Desugar.toParties (whitelisted)

where

    \_ = this

instance DA.Internal.Desugar.HasEnsure WhitelistedRegistry where

ensure this@WhitelistedRegistry {..}

= DA.Internal.Desugar.True

where

    \_ = this

instance DA.Internal.Desugar.HasArchive WhitelistedRegistry where

archive cid

= DA.Internal.Desugar.exercise cid DA.Internal.Desugar.Archive

where

    \_ = cid

instance DA.Internal.Desugar.HasCreate WhitelistedRegistry where

create = GHC.Types.primitive @“UCreate”

instance DA.Internal.Desugar.HasFetch WhitelistedRegistry where

fetch = GHC.Types.primitive @“UFetch”

instance DA.Internal.Desugar.HasToAnyTemplate WhitelistedRegistry where

_toAnyTemplate = GHC.Types.primitive @“EToAnyTemplate”

instance DA.Internal.Desugar.HasFromAnyTemplate WhitelistedRegistry where

_fromAnyTemplate = GHC.Types.primitive @“EFromAnyTemplate”

instance DA.Internal.Desugar.HasTemplateTypeRep WhitelistedRegistry where

_templateTypeRep = GHC.Types.primitive @“ETemplateTypeRep”

instance DA.Internal.Desugar.HasIsInterfaceType WhitelistedRegistry where

_isInterfaceType _ = DA.Internal.Desugar.False

instance DA.Internal.Desugar.HasExercise WhitelistedRegistry DA.Internal.Desugar.Archive (()) where

exercise = GHC.Types.primitive @“UExercise”

instance DA.Internal.Desugar.HasToAnyChoice WhitelistedRegistry DA.Internal.Desugar.Archive (()) where

_toAnyChoice = GHC.Types.primitive @“EToAnyChoice”

instance DA.Internal.Desugar.HasFromAnyChoice WhitelistedRegistry DA.Internal.Desugar.Archive (()) where

_fromAnyChoice = GHC.Types.primitive @“EFromAnyChoice”

instance DA.Internal.Desugar.HasChoiceController WhitelistedRegistry DA.Internal.Desugar.Archive where

_choiceController = GHC.Types.primitive @“EChoiceController”

instance DA.Internal.Desugar.HasChoiceObserver WhitelistedRegistry DA.Internal.Desugar.Archive where

_choiceObserver = GHC.Types.primitive @“EChoiceObserver”

instance DA.Internal.Desugar.HasExercise WhitelistedRegistry ChangeOwner (ContractId WhitelistedRegistry) where

exercise = GHC.Types.primitive @“UExercise”

instance DA.Internal.Desugar.HasToAnyChoice WhitelistedRegistry ChangeOwner (ContractId WhitelistedRegistry) where

_toAnyChoice = GHC.Types.primitive @“EToAnyChoice”

instance DA.Internal.Desugar.HasFromAnyChoice WhitelistedRegistry ChangeOwner (ContractId WhitelistedRegistry) where

_fromAnyChoice = GHC.Types.primitive @“EFromAnyChoice”

instance DA.Internal.Desugar.HasChoiceController WhitelistedRegistry ChangeOwner where

_choiceController = GHC.Types.primitive @“EChoiceController”

instance DA.Internal.Desugar.HasChoiceObserver WhitelistedRegistry ChangeOwner where

_choiceObserver = GHC.Types.primitive @“EChoiceObserver”

instance DA.Internal.Desugar.HasExercise WhitelistedRegistry SetWhitelisted (ContractId WhitelistedRegistry) where

exercise = GHC.Types.primitive @“UExercise”

instance DA.Internal.Desugar.HasToAnyChoice WhitelistedRegistry SetWhitelisted (ContractId WhitelistedRegistry) where

_toAnyChoice = GHC.Types.primitive @“EToAnyChoice”

instance DA.Internal.Desugar.HasFromAnyChoice WhitelistedRegistry SetWhitelisted (ContractId WhitelistedRegistry) where

_fromAnyChoice = GHC.Types.primitive @“EFromAnyChoice”

instance DA.Internal.Desugar.HasChoiceController WhitelistedRegistry SetWhitelisted where

_choiceController = GHC.Types.primitive @“EChoiceController”

instance DA.Internal.Desugar.HasChoiceObserver WhitelistedRegistry SetWhitelisted where

_choiceObserver = GHC.Types.primitive @“EChoiceObserver”

instance DA.Internal.Desugar.HasExercise WhitelistedRegistry IsWhitelisted (Bool) where

exercise = GHC.Types.primitive @“UExercise”

instance DA.Internal.Desugar.HasToAnyChoice WhitelistedRegistry IsWhitelisted (Bool) where

_toAnyChoice = GHC.Types.primitive @“EToAnyChoice”

instance DA.Internal.Desugar.HasFromAnyChoice WhitelistedRegistry IsWhitelisted (Bool) where

_fromAnyChoice = GHC.Types.primitive @“EFromAnyChoice”

instance DA.Internal.Desugar.HasChoiceController WhitelistedRegistry IsWhitelisted where

_choiceController = GHC.Types.primitive @“EChoiceController”

instance DA.Internal.Desugar.HasChoiceObserver WhitelistedRegistry IsWhitelisted where

_choiceObserver = GHC.Types.primitive @“EChoiceObserver”

_choice$_WhitelistedRegistry$Archive :

(DA.Internal.Desugar.Consuming WhitelistedRegistry,

WhitelistedRegistry

→ DA.Internal.Desugar.Archive → [DA.Internal.Desugar.Party],

DA.Internal.Desugar.Optional (WhitelistedRegistry

                             -> DA.Internal.Desugar.Archive -> \[DA.Internal.Desugar.Party\]),

DA.Internal.Desugar.Optional (WhitelistedRegistry

                             -> DA.Internal.Desugar.Archive -> \[DA.Internal.Desugar.Party\]),

DA.Internal.Desugar.ContractId WhitelistedRegistry

→ WhitelistedRegistry

  -> DA.Internal.Desugar.Archive -> DA.Internal.Desugar.Update (()))

_choice$_WhitelistedRegistry$Archive

= (DA.Internal.Desugar.Consuming,

 \\ this \_ -> DA.Internal.Desugar.signatory this, 

 DA.Internal.Desugar.None, DA.Internal.Desugar.None, 

 \\ \_ \_ \_ -> pure ())

_choice$_WhitelistedRegistry$ChangeOwner :

(DA.Internal.Desugar.Consuming WhitelistedRegistry,

WhitelistedRegistry → ChangeOwner → [DA.Internal.Desugar.Party],

DA.Internal.Desugar.Optional (WhitelistedRegistry

                             -> ChangeOwner -> \[DA.Internal.Desugar.Party\]),

DA.Internal.Desugar.Optional (WhitelistedRegistry

                             -> ChangeOwner -> \[DA.Internal.Desugar.Party\]),

DA.Internal.Desugar.ContractId WhitelistedRegistry

→ WhitelistedRegistry

  -> ChangeOwner

     -> DA.Internal.Desugar.Update (ContractId WhitelistedRegistry))

_choice$_WhitelistedRegistry$ChangeOwner

= (DA.Internal.Desugar.Consuming,

 \\ this@WhitelistedRegistry {..}

   -> DA.Internal.Desugar.bypassReduceLambda

        \\ arg@ChangeOwner {..}

          -> let \_ = this in

             let \_ = arg in DA.Internal.Desugar.toParties (owner), 

 DA.Internal.Desugar.None, DA.Internal.Desugar.None, 

 \\ self this@WhitelistedRegistry {..}

   -> DA.Internal.Desugar.bypassReduceLambda

        \\ arg@ChangeOwner {..}

          -> let \_ = self in

             let \_ = this in let \_ = arg in do create this {owner = newOwner})

_choice$_WhitelistedRegistry$SetWhitelisted :

(DA.Internal.Desugar.Consuming WhitelistedRegistry,

WhitelistedRegistry

→ SetWhitelisted → [DA.Internal.Desugar.Party],

DA.Internal.Desugar.Optional (WhitelistedRegistry

                             -> SetWhitelisted -> \[DA.Internal.Desugar.Party\]),

DA.Internal.Desugar.Optional (WhitelistedRegistry

                             -> SetWhitelisted -> \[DA.Internal.Desugar.Party\]),

DA.Internal.Desugar.ContractId WhitelistedRegistry

→ WhitelistedRegistry

  -> SetWhitelisted

     -> DA.Internal.Desugar.Update (ContractId WhitelistedRegistry))

_choice$_WhitelistedRegistry$SetWhitelisted

= (DA.Internal.Desugar.Consuming,

 \\ this@WhitelistedRegistry {..}

   -> DA.Internal.Desugar.bypassReduceLambda

        \\ arg@SetWhitelisted {..}

          -> let \_ = this in

             let \_ = arg in DA.Internal.Desugar.toParties (owner), 

 DA.Internal.Desugar.None, DA.Internal.Desugar.None, 

 \\ self this@WhitelistedRegistry {..}

   -> DA.Internal.Desugar.bypassReduceLambda

        \\ arg@SetWhitelisted {..}

          -> let \_ = self in

             let \_ = this in

             let \_ = arg

             in

               do let newWhitelist

                        = if isWhitelisted then

                              addr :: filter (\\ p -> p /= addr) whitelisted

                          else

                              filter (\\ p -> p /= addr) whitelisted

                  create this {whitelisted = newWhitelist})

_choice$_WhitelistedRegistry$IsWhitelisted :

(DA.Internal.Desugar.NonConsuming WhitelistedRegistry,

WhitelistedRegistry

→ IsWhitelisted → [DA.Internal.Desugar.Party],

DA.Internal.Desugar.Optional (WhitelistedRegistry

                             -> IsWhitelisted -> \[DA.Internal.Desugar.Party\]),

DA.Internal.Desugar.Optional (WhitelistedRegistry

                             -> IsWhitelisted -> \[DA.Internal.Desugar.Party\]),

DA.Internal.Desugar.ContractId WhitelistedRegistry

→ WhitelistedRegistry

  -> IsWhitelisted -> DA.Internal.Desugar.Update (Bool))

_choice$_WhitelistedRegistry$IsWhitelisted

= (DA.Internal.Desugar.NonConsuming,

 \\ this@WhitelistedRegistry {..}

   -> DA.Internal.Desugar.bypassReduceLambda

        \\ arg@IsWhitelisted {..}

          -> let \_ = this in

             let \_ = arg in DA.Internal.Desugar.toParties (caller), 

 DA.Internal.Desugar.None, DA.Internal.Desugar.None, 

 \\ self this@WhitelistedRegistry {..}

   -> DA.Internal.Desugar.bypassReduceLambda

        \\ arg@IsWhitelisted {..}

          -> let \_ = self in

             let \_ = this in let \_ = arg in do return (elem addr whitelisted))

Do you know why I didn’t get proper Haskell code?

Thanks in advance