Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parents Manipulation Example #20

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft

Conversation

anatoliykmetyuk
Copy link
Collaborator

The idea is:

  • We have an expression new Base with Foo in a macro
  • We would like to make it into new Base with Foo with T, where T is passed as a type parameter to the macro by the user

An expression new Base with Foo is desugared to the following block:

{
  final class $anon() extends Base with Foo

  (new $anon(): Base & Foo)
}

If we want new Base with Foo with T, we aim to create the following block:

{
  final class $anon() extends Base with Foo with T

  (new $anon(): Base & Foo & T)
}

Parents, Foo and Bar, live in a Template node for which there is no reflect API.

AST of the $anon class
      TypeDef(
        $anon,
        Template(
          DefDef(
            <init>,
            List(
              List(
                
              )
            ),
            TypeTree[
              TypeRef(
                TermRef(
                  ThisType(
                    TypeRef(
                      NoPrefix,
                      module class <root>
                    )
                  ),
                  object scala
                ),
                Unit
              )
            ],
            EmptyTree
          ),
          List(
            Apply(
              Select(
                New(
                  Ident(
                    Base
                  )
                ),
                <init>
              ),
              List(
                
              )
            ),
             Ident(
              Foo
            ),
             TypeTree[
              TypeRef(
                ThisType(
                  TypeRef(
                    NoPrefix,
                    module class <empty>
                  )
                ),
                trait Bar
              )
            ]
          ),
          ValDef(
            _,
            EmptyTree,
            EmptyTree
          ),
          List(
            
          )
        )
      )

This PR adopts a hacky solution. To add T as a parent to new Base with Foo, we start with an expression '{new Base with Foo}. We then use TreeMap to detect the Template node, break the interface and use the compiler internals to copy it with T as an injected parent. We then use a similar approach to transform (new $anon(): Base & Foo), the last statement of the block showcased above, into (new $anon(): Base & Foo & T).

However, even though the ASTs produced appear to be correct, it appears that the expression is compiled as if it is still new Base with Foo. That is, in the test case from the PR, the override from Bar is not visible.

@nicolasstucki is there a cleaner approach to solve this problem? Why does the hacky AST not work? And why there's no API for template manipulation?

@anatoliykmetyuk anatoliykmetyuk marked this pull request as draft July 28, 2021 10:18
Copy link
Contributor

@nicolasstucki nicolasstucki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not use dotty.tools.dotc.ast.tpd in examples we give to users

@anatoliykmetyuk
Copy link
Collaborator Author

So how do we achieve the result in question? And why doesn't the hacky solution work, given that it constructed the valid asts?

@nicolasstucki
Copy link
Contributor

We should never teach users to use hacks. We show them the correct to do it. If something is missing, then we should add it to the API before the users can use it.

@anatoliykmetyuk
Copy link
Collaborator Author

This PR is a work in progress draft. It's propose is to showcase what we want to do, not to get merged as an example (at least not in a hacky form).

So I take it currently there's no good way to do it?

This example aims to inject a type passed to a macro as a
mixin parent for a given `new` expression.
@anatoliykmetyuk
Copy link
Collaborator Author

Ok, I refactored it so that not to use compiler internals, with the help of ClassDef. I am able to construct the following expr:

Expression
{
  final class $anon() extends Base with Foo with Bar

  (new $anon(): Base & Foo & Bar)
}

Inlined(Some(TypeIdent("macro$package$")), Nil, Block(List(ClassDef("$anon", DefDef("<init>", List(TermParamClause(Nil)), Inferred(), None), List(Apply(Select(New(TypeIdent("Base")), "<init>"), Nil), TypeIdent("Foo"), Inferred()), None, Nil)), Typed(Apply(Select(New(TypeIdent("$anon")), "<init>"), Nil), Inferred())))

However the output is still "Me" and not "Stuff" as expected – that is, the resulting class does not get the overrides from injected Bar. Any idea what's going on here?

@Ang9876
Copy link

Ang9876 commented Aug 3, 2021

@anatoliykmetyuk I tried something different using the same idea, but I noticed something strange. I want to get new Base with Bar from new Base with Foo, so I changed the code in class MyTreeMap:
from

case t@ClassDef(name, constr, parents, selfOpt, body) =>
          ClassDef.copy(t)(name, constr, parents.head :+ TypeTree.of[T], selfOpt, body)

case t@AndType(base, foo) => AndType(t, TypeTree.of[T].tpe)

to

case t@ClassDef(name, constr, parents, selfOpt, body) =>
          ClassDef.copy(t)(name, constr, List(parents.head, TypeTree.of[T]), selfOpt, body)

case t@AndType(base, foo) => AndType(base, TypeTree.of[T].tpe)

The result of newExpr.show and newExpr.asTerm seems to be correct (both include Bar and no Foo). But there are two things confusing:

  1. The result of newTree.show(using Printer.TreeStructure) does not include Bar either. (I am unclear how it works.)
  2. The result of the test is Me, which is of Foo. It seems that it is not sufficient to change some part of the tree to get a new one.
newExpr.show
{
  final class $anon() extends Base with Bar
  
  (new $anon(): Base & Bar)
}
newExpr.asTerm
Inlined(Ident(macro$package$),List(),Block(List(TypeDef($anon,Template(DefDef(<init>,List(List()),TypeTree[TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class <root>)),object scala),Unit)],EmptyTree),List(Apply(Select(New(Ident(Base)),<init>),List()), TypeTree[TypeRef(ThisType(TypeRef(NoPrefix,module class <empty>)),trait Bar)]),ValDef(_,EmptyTree,EmptyTree),List()))),Typed(Apply(Select(New(Ident($anon)),<init>),List()),TypeTree[AndType(TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class <root>)),module class <empty>)),Base),TypeRef(ThisType(TypeRef(NoPrefix,module class <empty>)),trait Bar))])))
newTree.show(using Printer.TreeStructure)
Inlined(Some(TypeIdent("macro$package$")), Nil, Block(List(ClassDef("$anon", DefDef("<init>", List(TermParamClause(Nil)), Inferred(), None), List(Apply(Select(New(TypeIdent("Base")), "<init>"), Nil), Inferred()), None, Nil)), Typed(Apply(Select(New(TypeIdent("$anon")), "<init>"), Nil), Inferred())))

@anatoliykmetyuk
Copy link
Collaborator Author

Yeah, the overrides don't seem to go through if you merely replace the ClassDef node with a copy with different parents. Something else seems to be needed...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants