From 28c382b2761ae75fb6a5626af20cabc72538a5b3 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 5 Feb 2019 11:45:05 +0100 Subject: [PATCH 01/17] Move Staging before PostTyper --- compiler/src/dotty/tools/dotc/Compiler.scala | 2 +- .../tools/dotc/transform/PostTyper.scala | 22 --------------- .../dotty/tools/dotc/transform/Staging.scala | 6 ++--- .../dotty/tools/dotc/typer/Applications.scala | 27 ++++++++++++++++--- .../src/dotty/tools/dotc/typer/Typer.scala | 8 ++++-- .../src/dotty/tools/repl/ReplCompiler.scala | 3 ++- .../tasty-extractors-owners.check | 10 +++---- tests/run/tasty-extractors-2.check | 2 +- 8 files changed, 41 insertions(+), 39 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index fca79f882644..0157a095cd0b 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -37,6 +37,7 @@ class Compiler { /** Phases dealing with the frontend up to trees ready for TASTY pickling */ protected def frontendPhases: List[List[Phase]] = List(new FrontEnd) :: // Compiler frontend: scanner, parser, namer, typer + List(new Staging) :: // Check PCP, heal quoted types and expand macros List(new sbt.ExtractDependencies) :: // Sends information on classes' dependencies to sbt via callbacks List(new PostTyper) :: // Additional checks and cleanups after type checking List(new sbt.ExtractAPI) :: // Sends a representation of the API of classes to sbt via callbacks @@ -45,7 +46,6 @@ class Compiler { /** Phases dealing with TASTY tree pickling and unpickling */ protected def picklerPhases: List[List[Phase]] = - List(new Staging) :: // Check PCP, heal quoted types and expand macros List(new Pickler) :: // Generate TASTY info List(new ReifyQuotes) :: // Turn quoted trees into explicit run-time data structures Nil diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 5a21cf20c59f..3749f73b6bbe 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -167,26 +167,6 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase } } - /** 1. If we are in an inline method but not in a nested quote, mark the inline method - * as a macro. - * - * 2. If selection is a quote or splice node, record that fact in the current compilation unit. - */ - private def handleMeta(sym: Symbol)(implicit ctx: Context): Unit = { - - def markAsMacro(c: Context): Unit = - if (c.owner eq c.outer.owner) markAsMacro(c.outer) - else if (c.owner.isInlineMethod) { - c.owner.setFlag(Macro) - } - else if (!c.outer.owner.is(Package)) markAsMacro(c.outer) - - if (sym.isSplice || sym.isQuote) { - markAsMacro(ctx) - ctx.compilationUnit.needsStaging = true - } - } - private object dropInlines extends TreeMap { override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match { case Inlined(call, _, _) => @@ -198,13 +178,11 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase override def transform(tree: Tree)(implicit ctx: Context): Tree = try tree match { case tree: Ident if !tree.isType => - handleMeta(tree.symbol) tree.tpe match { case tpe: ThisType => This(tpe.cls).withSpan(tree.span) case _ => tree } case tree @ Select(qual, name) => - handleMeta(tree.symbol) if (name.isTypeName) { Checking.checkRealizable(qual.tpe, qual.posd) super.transform(tree)(ctx.addMode(Mode.Type)) diff --git a/compiler/src/dotty/tools/dotc/transform/Staging.scala b/compiler/src/dotty/tools/dotc/transform/Staging.scala index 8f097da7e323..837993311818 100644 --- a/compiler/src/dotty/tools/dotc/transform/Staging.scala +++ b/compiler/src/dotty/tools/dotc/transform/Staging.scala @@ -81,7 +81,7 @@ class Staging extends MacroTransform { } override def run(implicit ctx: Context): Unit = - if (ctx.compilationUnit.needsStaging) super.run(freshStagingContext) + /*if (ctx.compilationUnit.needsStaging)*/ super.run(freshStagingContext) protected def newTransformer(implicit ctx: Context): Transformer = new Transformer { override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = @@ -194,10 +194,8 @@ class Staging extends MacroTransform { case Some(tpRef) => tpRef case _ => tree } - case _: TypeTree | _: AppliedTypeTree | _: Apply | _: TypeApply | _: UnApply => + case _: TypeTree | _: AppliedTypeTree | _: Apply | _: TypeApply | _: UnApply | Select(_, OuterSelectName(_, _)) => tree.withType(checkTp(tree.tpe)) - case Select(_, OuterSelectName(_, _)) => - tree.withType(checkTp(tree.tpe.widen)) case _: ValOrDefDef | _: Bind => tree.symbol.info = checkTp(tree.symbol.info) tree diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 6ae5ae5b04a7..428df85a53ff 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -185,6 +185,27 @@ object Applications { case _ => None } } + + /** 1. If we are in an inline method but not in a nested quote, mark the inline method + * as a macro. + * + * 2. If selection is a quote or splice node, record that fact in the current compilation unit. + */ + def handleMeta(tree: Tree)(implicit ctx: Context): tree.type = { + import transform.SymUtils._ + + def markAsMacro(c: Context): Unit = + if (c.owner eq c.outer.owner) markAsMacro(c.outer) + else if (c.owner.isInlineMethod) c.owner.setFlag(Macro) + else if (!c.outer.owner.is(Package)) markAsMacro(c.outer) + val sym = tree.symbol + if (sym.isSplice || sym.isQuote) { + markAsMacro(ctx) + ctx.compilationUnit.needsStaging = true + } + + tree + } } trait Applications extends Compatibility { self: Typer with Dynamic => @@ -754,7 +775,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => * or, if application is an operator assignment, also an `Assign` or * Block node. */ - def typedApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context): Tree = { + def typedApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context): Tree = handleMeta { def realApply(implicit ctx: Context): Tree = track("realApply") { val originalProto = new FunProto(tree.args, IgnoredProto(pt))(this, tree.isContextual)(argCtx(tree)) @@ -892,7 +913,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => def typedTypeApply(tree: untpd.TypeApply, pt: Type)(implicit ctx: Context): Tree = track("typedTypeApply") { val isNamed = hasNamedArg(tree.args) val typedArgs = if (isNamed) typedNamedArgs(tree.args) else tree.args.mapconserve(typedType(_)) - typedExpr(tree.fun, PolyProto(typedArgs, pt)) match { + handleMeta(typedExpr(tree.fun, PolyProto(typedArgs, pt)) match { case ExtMethodApply(app) => app case _: TypeApply if !ctx.isAfterTyper => @@ -914,7 +935,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => } if (typedFn.tpe eq TryDynamicCallType) tryDynamicTypeApply() else assignType(cpy.TypeApply(tree)(typedFn, typedArgs), typedFn, typedArgs) - } + }) } /** Rewrite `new Array[T](....)` if T is an unbounded generic to calls to newGenericArray. diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index c5c98812bf53..8cc4cb39b4b9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -440,7 +440,7 @@ class Typer extends Namer } private def typedSelect(tree: untpd.Select, pt: Type, qual: Tree)(implicit ctx: Context): Select = - checkValue(assignType(cpy.Select(tree)(qual, tree.name), qual), pt) + Applications.handleMeta(checkValue(assignType(cpy.Select(tree)(qual, tree.name), qual), pt)) def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = track("typedSelect") { @@ -1487,6 +1487,8 @@ class Typer extends Namer val vdef1 = assignType(cpy.ValDef(vdef)(name, tpt1, rhs1), sym) if (sym.is(Inline, butNot = DeferredOrTermParamOrAccessor)) checkInlineConformant(rhs1, isFinal = sym.is(Final), em"right-hand side of inline $sym") + if (sym.exists) + sym.defTree = vdef1 patchIfLazy(vdef1) patchFinalVals(vdef1) vdef1 @@ -1557,7 +1559,9 @@ class Typer extends Namer for (param <- tparams1 ::: vparamss1.flatten) checkRefsLegal(param, sym.owner, (name, sym) => sym.is(TypeParam), "secondary constructor") - assignType(cpy.DefDef(ddef)(name, tparams1, vparamss1, tpt1, rhs1), sym) + val ddef1 = assignType(cpy.DefDef(ddef)(name, tparams1, vparamss1, tpt1, rhs1), sym) + sym.defTree = ddef1 + ddef1 //todo: make sure dependent method types do not depend on implicits or by-name params } diff --git a/compiler/src/dotty/tools/repl/ReplCompiler.scala b/compiler/src/dotty/tools/repl/ReplCompiler.scala index f32d247c7635..3c4f38cc6170 100644 --- a/compiler/src/dotty/tools/repl/ReplCompiler.scala +++ b/compiler/src/dotty/tools/repl/ReplCompiler.scala @@ -12,7 +12,7 @@ import dotty.tools.dotc.core.Phases.Phase import dotty.tools.dotc.core.StdNames._ import dotty.tools.dotc.core.Symbols._ import dotty.tools.dotc.reporting.diagnostic.messages -import dotty.tools.dotc.transform.PostTyper +import dotty.tools.dotc.transform.{PostTyper, Staging} import dotty.tools.dotc.typer.ImportInfo import dotty.tools.dotc.util.Spans._ import dotty.tools.dotc.util.{ParsedComment, SourceFile} @@ -34,6 +34,7 @@ class ReplCompiler extends Compiler { override protected def frontendPhases: List[List[Phase]] = List( List(new REPLFrontEnd), List(new CollectTopLevelImports), + List(new Staging), List(new PostTyper) ) diff --git a/tests/run-custom-args/Yretain-trees/tasty-extractors-owners.check b/tests/run-custom-args/Yretain-trees/tasty-extractors-owners.check index ad142704cb0a..ab8f8ee11d2a 100644 --- a/tests/run-custom-args/Yretain-trees/tasty-extractors-owners.check +++ b/tests/run-custom-args/Yretain-trees/tasty-extractors-owners.check @@ -1,5 +1,5 @@ foo -DefDef("main", Nil, List(List(ValDef("args", TypeTree.Applied(TypeTree.Ident("Array"), List(TypeTree.Ident("String"))), None))), TypeTree.Ident("Unit"), Some(Term.Block(Nil, Term.Inlined(Some(TypeTree.Ident("Macros$")), Nil, Term.Typed(Term.Select(Term.Apply(Term.Apply(Term.TypeApply(Term.Ident("impl"), List(TypeTree.Inferred())), List(Term.Apply(Term.TypeApply(Term.Ident("apply"), List(TypeTree.Inferred())), List(Term.Inlined(None, Nil, Term.Block(List(DefDef("foo", Nil, Nil, TypeTree.Inferred(), Some(Term.Block(List(DefDef("bar", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(1)))), ValDef("bar2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(2))))), Term.Typed(Term.Ident("bar"), TypeTree.Inferred())))), ValDef("foo2", TypeTree.Inferred(), Some(Term.Block(List(DefDef("baz", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(3)))), ValDef("baz2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(4))))), Term.Typed(Term.Ident("baz"), TypeTree.Inferred())))), ClassDef("A", DefDef("", Nil, List(Nil), TypeTree.Inferred(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), ""), Nil)), Nil, None, List(TypeDef("B", TypeTree.Ident("Int")), DefDef("b", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(5)))), ValDef("b2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(6))))))), Term.Literal(Constant.Unit()))))))), List(Term.Ident("macroContext"))), "$splice"), TypeTree.Ident("Unit")))))) +DefDef("main", Nil, List(List(ValDef("args", TypeTree.Applied(TypeTree.Ident("Array"), List(TypeTree.Ident("String"))), None))), TypeTree.Ident("Unit"), Some(Term.Block(Nil, Term.Inlined(Some(Term.Apply(Term.TypeApply(Term.Ident("printOwners"), List(TypeTree.Inferred())), List(Term.Block(List(DefDef("foo", Nil, Nil, TypeTree.Inferred(), Some(Term.Block(List(DefDef("bar", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(1)))), ValDef("bar2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(2))))), Term.Typed(Term.Ident("bar"), TypeTree.Inferred())))), ValDef("foo2", TypeTree.Inferred(), Some(Term.Block(List(DefDef("baz", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(3)))), ValDef("baz2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(4))))), Term.Typed(Term.Ident("baz"), TypeTree.Inferred())))), ClassDef("A", DefDef("", Nil, List(Nil), TypeTree.Inferred(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), ""), Nil)), Nil, None, List(TypeDef("B", TypeTree.Ident("Int")), DefDef("b", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(5)))), ValDef("b2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(6))))))), Term.Literal(Constant.Unit()))))), Nil, Term.Typed(Term.Select(Term.Apply(Term.Apply(Term.TypeApply(Term.Ident("impl"), List(TypeTree.Inferred())), List(Term.Apply(Term.TypeApply(Term.Ident("apply"), List(TypeTree.Inferred())), List(Term.Inlined(None, Nil, Term.Block(List(DefDef("foo", Nil, Nil, TypeTree.Inferred(), Some(Term.Block(List(DefDef("bar", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(1)))), ValDef("bar2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(2))))), Term.Typed(Term.Ident("bar"), TypeTree.Inferred())))), ValDef("foo2", TypeTree.Inferred(), Some(Term.Block(List(DefDef("baz", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(3)))), ValDef("baz2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(4))))), Term.Typed(Term.Ident("baz"), TypeTree.Inferred())))), ClassDef("A", DefDef("", Nil, List(Nil), TypeTree.Inferred(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), ""), Nil)), Nil, None, List(TypeDef("B", TypeTree.Ident("Int")), DefDef("b", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(5)))), ValDef("b2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(6))))))), Term.Literal(Constant.Unit()))))))), List(Term.Ident("macroContext"))), "$splice"), TypeTree.Ident("Unit")))))) bar DefDef("foo", Nil, Nil, TypeTree.Inferred(), Some(Term.Block(List(DefDef("bar", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(1)))), ValDef("bar2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(2))))), Term.Typed(Term.Ident("bar"), TypeTree.Inferred())))) @@ -8,7 +8,7 @@ bar2 DefDef("foo", Nil, Nil, TypeTree.Inferred(), Some(Term.Block(List(DefDef("bar", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(1)))), ValDef("bar2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(2))))), Term.Typed(Term.Ident("bar"), TypeTree.Inferred())))) foo2 -DefDef("main", Nil, List(List(ValDef("args", TypeTree.Applied(TypeTree.Ident("Array"), List(TypeTree.Ident("String"))), None))), TypeTree.Ident("Unit"), Some(Term.Block(Nil, Term.Inlined(Some(TypeTree.Ident("Macros$")), Nil, Term.Typed(Term.Select(Term.Apply(Term.Apply(Term.TypeApply(Term.Ident("impl"), List(TypeTree.Inferred())), List(Term.Apply(Term.TypeApply(Term.Ident("apply"), List(TypeTree.Inferred())), List(Term.Inlined(None, Nil, Term.Block(List(DefDef("foo", Nil, Nil, TypeTree.Inferred(), Some(Term.Block(List(DefDef("bar", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(1)))), ValDef("bar2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(2))))), Term.Typed(Term.Ident("bar"), TypeTree.Inferred())))), ValDef("foo2", TypeTree.Inferred(), Some(Term.Block(List(DefDef("baz", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(3)))), ValDef("baz2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(4))))), Term.Typed(Term.Ident("baz"), TypeTree.Inferred())))), ClassDef("A", DefDef("", Nil, List(Nil), TypeTree.Inferred(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), ""), Nil)), Nil, None, List(TypeDef("B", TypeTree.Ident("Int")), DefDef("b", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(5)))), ValDef("b2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(6))))))), Term.Literal(Constant.Unit()))))))), List(Term.Ident("macroContext"))), "$splice"), TypeTree.Ident("Unit")))))) +DefDef("main", Nil, List(List(ValDef("args", TypeTree.Applied(TypeTree.Ident("Array"), List(TypeTree.Ident("String"))), None))), TypeTree.Ident("Unit"), Some(Term.Block(Nil, Term.Inlined(Some(Term.Apply(Term.TypeApply(Term.Ident("printOwners"), List(TypeTree.Inferred())), List(Term.Block(List(DefDef("foo", Nil, Nil, TypeTree.Inferred(), Some(Term.Block(List(DefDef("bar", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(1)))), ValDef("bar2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(2))))), Term.Typed(Term.Ident("bar"), TypeTree.Inferred())))), ValDef("foo2", TypeTree.Inferred(), Some(Term.Block(List(DefDef("baz", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(3)))), ValDef("baz2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(4))))), Term.Typed(Term.Ident("baz"), TypeTree.Inferred())))), ClassDef("A", DefDef("", Nil, List(Nil), TypeTree.Inferred(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), ""), Nil)), Nil, None, List(TypeDef("B", TypeTree.Ident("Int")), DefDef("b", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(5)))), ValDef("b2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(6))))))), Term.Literal(Constant.Unit()))))), Nil, Term.Typed(Term.Select(Term.Apply(Term.Apply(Term.TypeApply(Term.Ident("impl"), List(TypeTree.Inferred())), List(Term.Apply(Term.TypeApply(Term.Ident("apply"), List(TypeTree.Inferred())), List(Term.Inlined(None, Nil, Term.Block(List(DefDef("foo", Nil, Nil, TypeTree.Inferred(), Some(Term.Block(List(DefDef("bar", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(1)))), ValDef("bar2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(2))))), Term.Typed(Term.Ident("bar"), TypeTree.Inferred())))), ValDef("foo2", TypeTree.Inferred(), Some(Term.Block(List(DefDef("baz", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(3)))), ValDef("baz2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(4))))), Term.Typed(Term.Ident("baz"), TypeTree.Inferred())))), ClassDef("A", DefDef("", Nil, List(Nil), TypeTree.Inferred(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), ""), Nil)), Nil, None, List(TypeDef("B", TypeTree.Ident("Int")), DefDef("b", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(5)))), ValDef("b2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(6))))))), Term.Literal(Constant.Unit()))))))), List(Term.Ident("macroContext"))), "$splice"), TypeTree.Ident("Unit")))))) baz ValDef("foo2", TypeTree.Inferred(), Some(Term.Block(List(DefDef("baz", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(3)))), ValDef("baz2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(4))))), Term.Typed(Term.Ident("baz"), TypeTree.Inferred())))) @@ -17,11 +17,11 @@ baz2 ValDef("foo2", TypeTree.Inferred(), Some(Term.Block(List(DefDef("baz", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(3)))), ValDef("baz2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(4))))), Term.Typed(Term.Ident("baz"), TypeTree.Inferred())))) -ClassDef("A", DefDef("", Nil, List(Nil), TypeTree.Inferred(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), ""), Nil)), Nil, None, List(TypeDef("B", TypeTree.Ident("Int")), DefDef("b", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(5)))), ValDef("b2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(6)))))) +ClassDef("A", DefDef("", Nil, List(Nil), TypeTree.Inferred(), None), List(TypeTree.Inferred()), Nil, None, List(TypeDef("B", TypeBoundsTree(TypeTree.Inferred(), TypeTree.Inferred())), DefDef("b", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(5)))), ValDef("b2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(6)))))) b -ClassDef("A", DefDef("", Nil, List(Nil), TypeTree.Inferred(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), ""), Nil)), Nil, None, List(TypeDef("B", TypeTree.Ident("Int")), DefDef("b", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(5)))), ValDef("b2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(6)))))) +ClassDef("A", DefDef("", Nil, List(Nil), TypeTree.Inferred(), None), List(TypeTree.Inferred()), Nil, None, List(TypeDef("B", TypeBoundsTree(TypeTree.Inferred(), TypeTree.Inferred())), DefDef("b", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(5)))), ValDef("b2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(6)))))) b2 -ClassDef("A", DefDef("", Nil, List(Nil), TypeTree.Inferred(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), ""), Nil)), Nil, None, List(TypeDef("B", TypeTree.Ident("Int")), DefDef("b", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(5)))), ValDef("b2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(6)))))) +ClassDef("A", DefDef("", Nil, List(Nil), TypeTree.Inferred(), None), List(TypeTree.Inferred()), Nil, None, List(TypeDef("B", TypeBoundsTree(TypeTree.Inferred(), TypeTree.Inferred())), DefDef("b", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(5)))), ValDef("b2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(6)))))) diff --git a/tests/run/tasty-extractors-2.check b/tests/run/tasty-extractors-2.check index d6b33232dacf..5603ba2b5219 100644 --- a/tests/run/tasty-extractors-2.check +++ b/tests/run/tasty-extractors-2.check @@ -49,7 +49,7 @@ Type.SymRef(IsClassSymbol(), Type.ThisType(Type.SymRef(IsPackageSymb Term.Inlined(None, Nil, Term.Block(List(ClassDef("Foo", DefDef("", Nil, List(Nil), TypeTree.Inferred(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), ""), Nil)), Nil, None, List(DefDef("a", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(0))))))), Term.Literal(Constant.Unit()))) Type.SymRef(IsClassSymbol(), Type.ThisType(Type.SymRef(IsPackageSymbol(), NoPrefix()))) -Term.Inlined(None, Nil, Term.Block(List(ClassDef("Foo", DefDef("", Nil, List(Nil), TypeTree.Inferred(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), ""), Nil), TypeTree.Select(Term.Select(Term.Ident("_root_"), "scala"), "Product"), TypeTree.Select(Term.Select(Term.Ident("_root_"), "scala"), "Serializable")), Nil, None, List(DefDef("hashCode", Nil, List(Nil), TypeTree.Inferred(), Some(Term.Literal(Constant.Int(394005536)))), DefDef("equals", Nil, List(List(ValDef("x$0", TypeTree.Inferred(), None))), TypeTree.Inferred(), Some(Term.Apply(Term.Select(Term.Apply(Term.Select(Term.This(Some(Id("Foo"))), "eq"), List(Term.TypeApply(Term.Select(Term.Ident("x$0"), "$asInstanceOf$"), List(TypeTree.Inferred())))), "||"), List(Term.Match(Term.Ident("x$0"), List(CaseDef(Pattern.Bind("x$0", Pattern.TypeTest(TypeTree.Inferred())), None, Term.Literal(Constant.Boolean(true))), CaseDef(Pattern.Value(Term.Ident("_")), None, Term.Literal(Constant.Boolean(false))))))))), DefDef("toString", Nil, List(Nil), TypeTree.Inferred(), Some(Term.Apply(Term.Ident("_toString"), List(Term.This(Some(Id("Foo"))))))), DefDef("canEqual", Nil, List(List(ValDef("that", TypeTree.Inferred(), None))), TypeTree.Inferred(), Some(Term.TypeApply(Term.Select(Term.Ident("that"), "isInstanceOf"), List(TypeTree.Inferred())))), DefDef("productArity", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(0)))), DefDef("productPrefix", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.String("Foo")))), DefDef("productElement", Nil, List(List(ValDef("n", TypeTree.Inferred(), None))), TypeTree.Inferred(), Some(Term.Match(Term.Ident("n"), List(CaseDef(Pattern.Value(Term.Ident("_")), None, Term.Apply(Term.Ident("throw"), List(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), ""), List(Term.Apply(Term.Select(Term.Ident("n"), "toString"), Nil)))))))))), DefDef("productElementName", Nil, List(List(ValDef("x$1", TypeTree.Select(Term.Select(Term.Ident("_root_"), "scala"), "Int"), None))), TypeTree.Select(Term.Select(Term.Ident("java"), "lang"), "String"), Some(Term.Match(Term.Ident("x$1"), List(CaseDef(Pattern.Value(Term.Ident("_")), None, Term.Apply(Term.Ident("throw"), List(Term.Apply(Term.Select(Term.New(TypeTree.Select(Term.Select(Term.Ident("java"), "lang"), "IndexOutOfBoundsException")), ""), List(Term.Apply(Term.Select(Term.Select(Term.Select(Term.Ident("java"), "lang"), "String"), "valueOf"), List(Term.Ident("x$1")))))))))))), DefDef("copy", Nil, List(Nil), TypeTree.Inferred(), Some(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), ""), Nil))))), ValDef("Foo", TypeTree.Ident("Foo$"), Some(Term.Apply(Term.Select(Term.New(TypeTree.Ident("Foo$")), ""), Nil))), ClassDef("Foo$", DefDef("", Nil, List(Nil), TypeTree.Inferred(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), ""), Nil), TypeTree.Applied(TypeTree.Inferred(), List(TypeTree.Inferred())), TypeTree.Select(Term.Select(Term.Ident("_root_"), "scala"), "Serializable")), Nil, Some(ValDef("_", TypeTree.Singleton(Term.Ident("Foo")), None)), List(DefDef("apply", Nil, List(Nil), TypeTree.Inferred(), Some(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), ""), Nil))), DefDef("unapply", Nil, List(List(ValDef("x$1", TypeTree.Inferred(), None))), TypeTree.Inferred(), Some(Term.Literal(Constant.Boolean(true))))))), Term.Literal(Constant.Unit()))) +Term.Inlined(None, Nil, Term.Block(List(ClassDef("Foo", DefDef("", Nil, List(Nil), TypeTree.Inferred(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), ""), Nil), TypeTree.Select(Term.Select(Term.Ident("_root_"), "scala"), "Product"), TypeTree.Select(Term.Select(Term.Ident("_root_"), "scala"), "Serializable")), Nil, None, List(DefDef("productElementName", Nil, List(List(ValDef("x$1", TypeTree.Select(Term.Select(Term.Ident("_root_"), "scala"), "Int"), None))), TypeTree.Select(Term.Select(Term.Ident("java"), "lang"), "String"), Some(Term.Match(Term.Ident("x$1"), List(CaseDef(Pattern.Value(Term.Ident("_")), None, Term.Apply(Term.Ident("throw"), List(Term.Apply(Term.Select(Term.New(TypeTree.Select(Term.Select(Term.Ident("java"), "lang"), "IndexOutOfBoundsException")), ""), List(Term.Apply(Term.Select(Term.Select(Term.Select(Term.Ident("java"), "lang"), "String"), "valueOf"), List(Term.Ident("x$1")))))))))))), DefDef("copy", Nil, List(Nil), TypeTree.Inferred(), Some(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), ""), Nil))))), ValDef("Foo", TypeTree.Ident("Foo$"), Some(Term.Apply(Term.Select(Term.New(TypeTree.Ident("Foo$")), ""), Nil))), ClassDef("Foo$", DefDef("", Nil, List(Nil), TypeTree.Inferred(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), ""), Nil), TypeTree.Applied(TypeTree.Inferred(), List(TypeTree.Inferred())), TypeTree.Select(Term.Select(Term.Ident("_root_"), "scala"), "Serializable")), Nil, Some(ValDef("_", TypeTree.Singleton(Term.Ident("Foo")), None)), List(DefDef("apply", Nil, List(Nil), TypeTree.Inferred(), Some(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), ""), Nil))), DefDef("unapply", Nil, List(List(ValDef("x$1", TypeTree.Inferred(), None))), TypeTree.Inferred(), Some(Term.Literal(Constant.Boolean(true))))))), Term.Literal(Constant.Unit()))) Type.SymRef(IsClassSymbol(), Type.ThisType(Type.SymRef(IsPackageSymbol(), NoPrefix()))) Term.Inlined(None, Nil, Term.Block(List(ClassDef("Foo1", DefDef("", Nil, List(List(ValDef("a", TypeTree.Ident("Int"), None))), TypeTree.Inferred(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), ""), Nil)), Nil, None, List(ValDef("a", TypeTree.Inferred(), None)))), Term.Literal(Constant.Unit()))) From 5d21dd96fd779de003d6dbed0284142ea837c708 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 5 Feb 2019 16:22:46 +0100 Subject: [PATCH 02/17] Move Staging check into typer --- .../src/dotty/tools/dotc/ast/Desugar.scala | 9 +-- .../dotty/tools/dotc/transform/Staging.scala | 78 +++++++------------ .../src/dotty/tools/dotc/typer/Namer.scala | 1 - .../tools/dotc/typer/PrepareInlineable.scala | 2 +- .../src/dotty/tools/dotc/typer/Typer.scala | 48 +++++++++++- tests/neg/quote-this-b.scala | 10 +++ tests/neg/quote-this.scala | 3 - 7 files changed, 88 insertions(+), 63 deletions(-) create mode 100644 tests/neg/quote-this-b.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 1ada58556f6a..aad2dc8aa0f3 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -1325,14 +1325,9 @@ object desugar { val desugared = tree match { case SymbolLit(str) => Literal(Constant(scala.Symbol(str))) - case Quote(t) => - if (t.isType) - TypeApply(ref(defn.QuotedType_applyR), List(t)) - else - Apply(ref(defn.QuotedExpr_applyR), t) - case Splice(expr) => + case Splice(expr) => // TODO move to typer and track level Select(expr, nme.splice) - case TypSplice(expr) => + case TypSplice(expr) => // TODO move to typer and track level Select(expr, tpnme.splice) case InterpolatedString(id, segments) => val strs = segments map { diff --git a/compiler/src/dotty/tools/dotc/transform/Staging.scala b/compiler/src/dotty/tools/dotc/transform/Staging.scala index 837993311818..7edebace1f4d 100644 --- a/compiler/src/dotty/tools/dotc/transform/Staging.scala +++ b/compiler/src/dotty/tools/dotc/transform/Staging.scala @@ -37,16 +37,6 @@ class Staging extends MacroTransform { import tpd._ import Staging._ - /** Classloader used for loading macros */ - private[this] var myMacroClassLoader: java.lang.ClassLoader = _ - private def macroClassLoader(implicit ctx: Context): ClassLoader = { - if (myMacroClassLoader == null) { - val urls = ctx.settings.classpath.value.split(java.io.File.pathSeparatorChar).map(cp => java.nio.file.Paths.get(cp).toUri.toURL) - myMacroClassLoader = new java.net.URLClassLoader(urls, getClass.getClassLoader) - } - myMacroClassLoader - } - override def phaseName: String = Staging.name override def allowsImplicitSearch: Boolean = true @@ -88,39 +78,30 @@ class Staging extends MacroTransform { new PCPCheckAndHeal(ctx).transform(tree) } - private class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(ictx) { - override def transform(tree: Tree)(implicit ctx: Context): Tree = { - reporting.trace(i"PCPTransformer.transform $tree at $level", show = true) { - tree match { - case tree: DefDef if tree.symbol.is(Macro) => - if (level > 0) { - super.transform(tree) // Ignore output, only check PCP - EmptyTree // Already inlined - } - else if (enclosingInlineds.nonEmpty) { - EmptyTree // Already checked at definition site and already inlined - } - else tree.rhs match { - case InlineSplice(_) => - super.transform(tree) // Ignore output, only check PCP - tree - case _ => - ctx.error( - """Malformed macro. - | - |Expected the ~ to be at the top of the RHS: - | inline def foo(inline x: X, ..., y: Y): Int = ~impl(x, ... '(y)) - | - | * The contents of the splice must call a static method - | * All arguments must be quoted or inline - """.stripMargin, tree.rhs.sourcePos) - tree - } - case _ => - checkLevel(super.transform(tree)) - } +} + +object Staging { + val name: String = "staging" +} + + class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(ictx) { + import tpd._ + import PCPCheckAndHeal._ + + /** Classloader used for loading macros */ + private[this] var myMacroClassLoader: java.lang.ClassLoader = _ + private def macroClassLoader(implicit ctx: Context): ClassLoader = { + if (myMacroClassLoader == null) { + val urls = ctx.settings.classpath.value.split(java.io.File.pathSeparatorChar).map(cp => java.nio.file.Paths.get(cp).toUri.toURL) + myMacroClassLoader = new java.net.URLClassLoader(urls, getClass.getClassLoader) } + myMacroClassLoader + } + + override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match { + case tree: DefDef if tree.symbol.is(Inline) && level > 0 => EmptyTree + case _ => checkLevel(super.transform(tree)) } /** Transform quoted trees while maintaining phase correctness */ @@ -307,10 +288,16 @@ class Staging extends MacroTransform { } } + + } + + object PCPCheckAndHeal { + import tpd._ + /** InlineSplice is used to detect cases where the expansion * consists of a (possibly multiple & nested) block or a sole expression. */ - private object InlineSplice { + object InlineSplice { def unapply(tree: Tree)(implicit ctx: Context): Option[Tree] = tree match { case Spliced(code) if Splicer.canBeSpliced(code) => Some(code) case Block(List(stat), Literal(Constant(()))) => unapply(stat) @@ -319,11 +306,4 @@ class Staging extends MacroTransform { case _ => None } } - } - -} - -object Staging { - val name: String = "staging" -} diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 6744647d97f1..f1ba950f1cd2 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -815,7 +815,6 @@ class Namer { typer: Typer => case original: untpd.DefDef if sym.isInlineMethod => PrepareInlineable.registerInlineInfo( sym, - original.rhs, implicit ctx => typedAheadExpr(original).asInstanceOf[tpd.DefDef].rhs )(localContext(sym)) case _ => diff --git a/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala b/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala index f43120efc6c5..39353778a8e0 100644 --- a/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala +++ b/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala @@ -219,7 +219,7 @@ object PrepareInlineable { * to have the inline method as owner. */ def registerInlineInfo( - inlined: Symbol, originalBody: untpd.Tree, treeExpr: Context => Tree)(implicit ctx: Context): Unit = { + inlined: Symbol, treeExpr: Context => Tree)(implicit ctx: Context): Unit = { inlined.unforcedAnnotation(defn.BodyAnnot) match { case Some(ann: ConcreteBodyAnnotation) => case Some(ann: LazyBodyAnnotation) if ann.isEvaluated => diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 8cc4cb39b4b9..0c6d0d66b97c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -27,7 +27,7 @@ import EtaExpansion.etaExpand import util.Spans._ import util.common._ import util.Property -import Applications.{ExtMethodApply, wrapDefs, productSelectorTypes} +import Applications.{ExtMethodApply, productSelectorTypes, wrapDefs} import collection.mutable import annotation.tailrec @@ -36,6 +36,7 @@ import util.Stats.{record, track} import config.Printers.{gadts, typr} import rewrites.Rewrites.patch import NavigateAST._ +import dotty.tools.dotc.transform.{PCPCheckAndHeal, Staging, TreeMapWithStages} import transform.SymUtils._ import transform.TypeUtils._ import reporting.trace @@ -1553,7 +1554,39 @@ class Typer extends Namer if (sym.isInlineMethod) rhsCtx = rhsCtx.addMode(Mode.InlineableBody) val rhs1 = typedExpr(ddef.rhs, tpt1.tpe.widenExpr)(rhsCtx) - if (sym.isInlineMethod) PrepareInlineable.registerInlineInfo(sym, ddef.rhs, _ => rhs1) + if (sym.isInlineMethod) { + if (ctx.phase.isTyper) { + import PCPCheckAndHeal.InlineSplice + import TreeMapWithStages._ + var isMacro = false + new TreeMapWithStages(freshStagingContext) { + override protected def transformSplice(splice: tpd.Select)(implicit ctx: Context): tpd.Tree = { + isMacro = true + splice + } + }.transform(rhs1) + + if (isMacro) { + sym.setFlag(Macro) + if (TreeMapWithStages.level == 0) + rhs1 match { + case InlineSplice(_) => + new PCPCheckAndHeal(freshStagingContext).transform(rhs1) // Ignore output, only check PCP + case _ => + ctx.error( + """Malformed macro. + | + |Expected the ~ to be at the top of the RHS: + | inline def foo(inline x: X, ..., y: Y): Int = ~impl(x, ... '(y)) + | + | * The contents of the splice must call a static method + | * All arguments must be quoted or inline + """.stripMargin, ddef.sourcePos) + } + } + } + PrepareInlineable.registerInlineInfo(sym, _ => rhs1) + } if (sym.isConstructor && !sym.isPrimaryConstructor) for (param <- tparams1 ::: vparamss1.flatten) @@ -1928,6 +1961,16 @@ class Typer extends Namer } } + /** Translate `'(expr)`/`'{ expr* }` into `scala.quoted.Expr.apply(expr)` and `'[T]` into `scala.quoted.Type.apply[T]` + * while tracking the quotation level in the context. + */ + def typedQuote(tree: untpd.Quote, pt: Type)(implicit ctx: Context): Tree = track("typedQuote") { + if (tree.t.isType) + typedTypeApply(untpd.TypeApply(untpd.ref(defn.QuotedType_applyR), List(tree.t)), pt)(TreeMapWithStages.quoteContext).withSpan(tree.span) + else + typedApply(untpd.Apply(untpd.ref(defn.QuotedExpr_applyR), tree.t), pt)(TreeMapWithStages.quoteContext).withSpan(tree.span) + } + /** Retrieve symbol attached to given tree */ protected def retrieveSym(tree: untpd.Tree)(implicit ctx: Context): Symbol = tree.removeAttachment(SymOfTree) match { case Some(sym) => @@ -2018,6 +2061,7 @@ class Typer extends Namer case tree: untpd.InfixOp if ctx.mode.isExpr => typedInfixOp(tree, pt) case tree @ untpd.PostfixOp(qual, Ident(nme.WILDCARD)) => typedAsFunction(tree, pt) case untpd.EmptyTree => tpd.EmptyTree + case tree: untpd.Quote => typedQuote(tree, pt) case _ => typedUnadapted(desugar(tree), pt, locked) } diff --git a/tests/neg/quote-this-b.scala b/tests/neg/quote-this-b.scala new file mode 100644 index 000000000000..0e5078d786e1 --- /dev/null +++ b/tests/neg/quote-this-b.scala @@ -0,0 +1,10 @@ +import scala.quoted._ + +class Foo { + inline def k(): Unit = ${ Foo.impl[Any](this) } // error + inline def l(that: Foo): Unit = ${ Foo.impl[Any](that) } // error +} + +object Foo { + def impl[T](x: Any): Expr[Unit] = '{} +} diff --git a/tests/neg/quote-this.scala b/tests/neg/quote-this.scala index ddfc6961661c..733e9da9fab3 100644 --- a/tests/neg/quote-this.scala +++ b/tests/neg/quote-this.scala @@ -19,9 +19,6 @@ class Foo { 'that // error }) } - inline def k(): Unit = ${ Foo.impl[Any](this) } // error - inline def l(that: Foo): Unit = ${ Foo.impl[Any](that) } // error - } object Foo { From 987bfabe2f5bd0f7b049716abca4d3ccb7f657c4 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 6 Feb 2019 11:52:19 +0100 Subject: [PATCH 03/17] Expand macros in typer This adds support for whitebox macros --- .../src/dotty/tools/dotc/ast/Desugar.scala | 4 -- .../tools/dotc/core/StagingContext.scala | 25 +++++++ .../dotc/core/quoted/PickledQuotes.scala | 2 +- .../tools/dotc/transform/ReifyQuotes.scala | 2 +- .../dotty/tools/dotc/transform/Splicer.scala | 14 +++- .../dotty/tools/dotc/transform/Staging.scala | 43 ++++-------- .../dotc/transform/TreeMapWithStages.scala | 16 +---- .../dotty/tools/dotc/typer/EtaExpansion.scala | 4 +- .../src/dotty/tools/dotc/typer/Inliner.scala | 31 +++++++-- .../src/dotty/tools/dotc/typer/Typer.scala | 34 +++++++-- tests/neg/quote-whitebox/Macro_1.scala | 9 +++ tests/neg/quote-whitebox/Test_2.scala | 8 +++ .../quoted_1.scala | 0 tests/neg/tasty-macro-assert-1/quoted_2.scala | 10 +++ tests/neg/tasty-macro-assert-2/quoted_1.scala | 69 +++++++++++++++++++ .../quoted_2.scala | 2 - .../Yretain-trees/tasty-definitions-3.check | 4 +- .../tasty-extractors-owners.check | 18 ++--- .../Yretain-trees/tasty-load-tree-2.check | 4 +- .../quote-macro-in-splice.check | 4 ++ .../quote-macro-in-splice/quoted_1.scala | 5 ++ .../quote-macro-in-splice/quoted_2.scala | 19 +++++ tests/run/quote-whitebox.check | 2 + tests/run/quote-whitebox/Macro_1.scala | 9 +++ tests/run/quote-whitebox/Test_2.scala | 10 +++ tests/run/tasty-argument-tree-1.check | 22 +++--- tests/run/tasty-extractors-1.check | 36 +++++----- tests/run/tasty-extractors-2.check | 2 +- tests/run/tasty-macro-const/quoted_1.scala | 2 +- tests/run/tasty-positioned.check | 4 +- tests/run/tasty-positioned/quoted_2.scala | 4 +- 31 files changed, 303 insertions(+), 115 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/core/StagingContext.scala create mode 100644 tests/neg/quote-whitebox/Macro_1.scala create mode 100644 tests/neg/quote-whitebox/Test_2.scala rename tests/neg/{tasty-macro-assert => tasty-macro-assert-1}/quoted_1.scala (100%) create mode 100644 tests/neg/tasty-macro-assert-1/quoted_2.scala create mode 100644 tests/neg/tasty-macro-assert-2/quoted_1.scala rename tests/neg/{tasty-macro-assert => tasty-macro-assert-2}/quoted_2.scala (67%) create mode 100644 tests/run-with-compiler/quote-macro-in-splice.check create mode 100644 tests/run-with-compiler/quote-macro-in-splice/quoted_1.scala create mode 100644 tests/run-with-compiler/quote-macro-in-splice/quoted_2.scala create mode 100644 tests/run/quote-whitebox.check create mode 100644 tests/run/quote-whitebox/Macro_1.scala create mode 100644 tests/run/quote-whitebox/Test_2.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index aad2dc8aa0f3..942298094dd5 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -1325,10 +1325,6 @@ object desugar { val desugared = tree match { case SymbolLit(str) => Literal(Constant(scala.Symbol(str))) - case Splice(expr) => // TODO move to typer and track level - Select(expr, nme.splice) - case TypSplice(expr) => // TODO move to typer and track level - Select(expr, tpnme.splice) case InterpolatedString(id, segments) => val strs = segments map { case ts: Thicket => ts.trees.head diff --git a/compiler/src/dotty/tools/dotc/core/StagingContext.scala b/compiler/src/dotty/tools/dotc/core/StagingContext.scala new file mode 100644 index 000000000000..a6d0dd5dc47c --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/StagingContext.scala @@ -0,0 +1,25 @@ +package dotty.tools.dotc.core + +import dotty.tools.dotc.core.Contexts._ +import dotty.tools.dotc.util.Property + +import scala.collection.mutable + +object StagingContext { + + /** A key to be used in a context property that tracks the quoteation level */ + private val QuotationLevel = new Property.Key[Int] + + /** All enclosing calls that are currently inlined, from innermost to outermost. */ + def level(implicit ctx: Context): Int = + ctx.property(QuotationLevel).getOrElse(0) + + /** Context with an incremented quotation level. */ + def quoteContext(implicit ctx: Context): Context = + ctx.fresh.setProperty(QuotationLevel, level + 1) + + /** Context with a decremented quotation level. */ + def spliceContext(implicit ctx: Context): Context = + ctx.fresh.setProperty(QuotationLevel, level - 1) + +} diff --git a/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala b/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala index 3111ba3fadc4..a3e83220f91f 100644 --- a/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala +++ b/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala @@ -145,7 +145,7 @@ object PickledQuotes { case Block(stats, expr) => seq(stats, rec(expr)).withSpan(fn.span) case _ => - fn.select(nme.apply).appliedToArgs(argRefs()) + fn.select(nme.apply).appliedToArgs(argRefs()).withSpan(fn.span) } Block(argVals, rec(fn)) } diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index 7a456c2b9efb..b3ef77a3789f 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -105,7 +105,7 @@ class ReifyQuotes extends MacroTransform { private class QuoteReifier(outer: QuoteReifier, capturers: mutable.HashMap[Symbol, Tree => Tree], val embedded: Embedded, val owner: Symbol)(@constructorOnly ictx: Context) extends TreeMapWithStages(ictx) { self => - import TreeMapWithStages._ + import StagingContext._ /** A nested reifier for a quote (if `isQuote = true`) or a splice (if not) */ def nested(isQuote: Boolean)(implicit ctx: Context): QuoteReifier = { diff --git a/compiler/src/dotty/tools/dotc/transform/Splicer.scala b/compiler/src/dotty/tools/dotc/transform/Splicer.scala index 212eef754d1d..a47e5d5917ad 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicer.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicer.scala @@ -8,7 +8,7 @@ import dotty.tools.dotc.ast.tpd import dotty.tools.dotc.ast.Trees._ import dotty.tools.dotc.core.Contexts._ import dotty.tools.dotc.core.Decorators._ -import dotty.tools.dotc.core.Flags._ +import dotty.tools.dotc.core.Flags.{Method => MethodFlag, _} import dotty.tools.dotc.core.NameKinds.FlatName import dotty.tools.dotc.core.Names.{Name, TermName} import dotty.tools.dotc.core.StdNames._ @@ -95,7 +95,7 @@ object Splicer { } protected def interpretQuote(tree: Tree)(implicit env: Env): Object = - new scala.quoted.Exprs.TastyTreeExpr(tree) + new scala.quoted.Exprs.TastyTreeExpr(Inlined(EmptyTree, Nil, tree).withSpan(tree.span)) protected def interpretTypeQuote(tree: Tree)(implicit env: Env): Object = new scala.quoted.Types.TreeType(tree) @@ -312,7 +312,13 @@ object Splicer { protected final def interpretTree(tree: Tree)(implicit env: Env): Result = tree match { case Apply(TypeApply(fn, _), quoted :: Nil) if fn.symbol == defn.QuotedExpr_apply => - interpretQuote(quoted) + val quoted1 = quoted match { + case quoted: Ident if quoted.symbol.is(InlineProxy) && quoted.symbol.is(MethodFlag) => // inline proxy for by-name parameter + quoted.symbol.defTree.asInstanceOf[DefDef].rhs + case Inlined(EmptyTree, _, quoted) => quoted + case _ => quoted + } + interpretQuote(quoted1) case TypeApply(fn, quoted :: Nil) if fn.symbol == defn.QuotedType_apply => interpretTypeQuote(quoted) @@ -336,6 +342,8 @@ object Splicer { interpretStaticMethodCall(module, fn.symbol, args.map(arg => interpretTree(arg))) } else if (env.contains(fn.name)) { env(fn.name) + } else if (tree.symbol.is(InlineProxy)) { + interpretTree(tree.symbol.defTree.asInstanceOf[ValOrDefDef].rhs) } else { unexpectedTree(tree) } diff --git a/compiler/src/dotty/tools/dotc/transform/Staging.scala b/compiler/src/dotty/tools/dotc/transform/Staging.scala index 7edebace1f4d..4c5c90a085e2 100644 --- a/compiler/src/dotty/tools/dotc/transform/Staging.scala +++ b/compiler/src/dotty/tools/dotc/transform/Staging.scala @@ -9,6 +9,7 @@ import dotty.tools.dotc.core.Decorators._ import dotty.tools.dotc.core.Flags._ import dotty.tools.dotc.core.quoted._ import dotty.tools.dotc.core.NameKinds._ +import dotty.tools.dotc.core.StagingContext._ import dotty.tools.dotc.core.StdNames._ import dotty.tools.dotc.core.Symbols._ import dotty.tools.dotc.core.tasty.TreePickler.Hole @@ -89,16 +90,6 @@ object Staging { import tpd._ import PCPCheckAndHeal._ - /** Classloader used for loading macros */ - private[this] var myMacroClassLoader: java.lang.ClassLoader = _ - private def macroClassLoader(implicit ctx: Context): ClassLoader = { - if (myMacroClassLoader == null) { - val urls = ctx.settings.classpath.value.split(java.io.File.pathSeparatorChar).map(cp => java.nio.file.Paths.get(cp).toUri.toURL) - myMacroClassLoader = new java.net.URLClassLoader(urls, getClass.getClassLoader) - } - myMacroClassLoader - } - override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match { case tree: DefDef if tree.symbol.is(Inline) && level > 0 => EmptyTree case _ => checkLevel(super.transform(tree)) @@ -122,25 +113,19 @@ object Staging { if (splice1.isType) splice1 else addSpliceCast(splice1) } - else if (enclosingInlineds.nonEmpty) { // level 0 in an inlined call - val spliceCtx = ctx.outer // drop the last `inlineContext` - val pos: SourcePosition = spliceCtx.source.atSpan(enclosingInlineds.head.span) - val evaluatedSplice = Splicer.splice(splice.qualifier, pos, macroClassLoader)(spliceCtx) - if (ctx.reporter.hasErrors) splice else transform(evaluatedSplice.withSpan(splice.span)) - } - else if (!ctx.owner.isInlineMethod) { // level 0 outside an inline method - ctx.error(i"splice outside quotes or inline method", splice.sourcePos) - splice - } - else if (Splicer.canBeSpliced(splice.qualifier)) { // level 0 inside an inline definition - transform(splice.qualifier)(spliceContext) // Just check PCP - splice - } - else { // level 0 inside an inline definition - ctx.error( - "Malformed macro call. The contents of the ~ must call a static method and arguments must be quoted or inline.", - splice.sourcePos) - splice + else { + assert(!enclosingInlineds.nonEmpty, "unexpanded macro") + assert(ctx.owner.isInlineMethod) + if (Splicer.canBeSpliced(splice.qualifier)) { // level 0 inside an inline definition + transform(splice.qualifier)(spliceContext) // Just check PCP + splice + } + else { // level 0 inside an inline definition + ctx.error( + "Malformed macro call. The contents of the $ must call a static method and arguments must be quoted or inline.", + splice.sourcePos) + splice + } } } diff --git a/compiler/src/dotty/tools/dotc/transform/TreeMapWithStages.scala b/compiler/src/dotty/tools/dotc/transform/TreeMapWithStages.scala index 58896d9e4c4e..fcd651dea1e2 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeMapWithStages.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeMapWithStages.scala @@ -10,6 +10,7 @@ import dotty.tools.dotc.core.quoted._ import dotty.tools.dotc.core.NameKinds._ import dotty.tools.dotc.core.Types._ import dotty.tools.dotc.core.Contexts._ +import dotty.tools.dotc.core.StagingContext._ import dotty.tools.dotc.core.StdNames._ import dotty.tools.dotc.core.Symbols._ import dotty.tools.dotc.core.tasty.TreePickler.Hole @@ -129,21 +130,6 @@ object TreeMapWithStages { /** A key to be used in a context property that caches the `levelOf` mapping */ private val LevelOfKey = new Property.Key[mutable.HashMap[Symbol, Int]] - /** A key to be used in a context property that tracks the quoteation level */ - private val QuotationLevel = new Property.Key[Int] - - /** All enclosing calls that are currently inlined, from innermost to outermost. */ - def level(implicit ctx: Context): Int = - ctx.property(QuotationLevel).getOrElse(0) - - /** Context with an incremented quotation level. */ - def quoteContext(implicit ctx: Context): Context = - ctx.fresh.setProperty(QuotationLevel, level + 1) - - /** Context with a decremented quotation level. */ - def spliceContext(implicit ctx: Context): Context = - ctx.fresh.setProperty(QuotationLevel, level - 1) - /** Initial context for a StagingTransformer transformation. */ def freshStagingContext(implicit ctx: Context): Context = ctx.fresh.setProperty(LevelOfKey, new mutable.HashMap[Symbol, Int]) diff --git a/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala b/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala index 4525f0a1be65..0e5d0bf74e47 100644 --- a/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala +++ b/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala @@ -47,7 +47,9 @@ abstract class Lifter { var liftedType = expr.tpe.widen if (liftedFlags.is(Method)) liftedType = ExprType(liftedType) val lifted = ctx.newSymbol(ctx.owner, name, liftedFlags | Synthetic, liftedType, coord = spanCoord(expr.span)) - defs += liftedDef(lifted, expr).withSpan(expr.span) + val ddef = liftedDef(lifted, expr).withSpan(expr.span) + lifted.defTree = ddef + defs += ddef ref(lifted.termRef).withSpan(expr.span.focus) } diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 6105babde61c..8bc1ea9d96c6 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -2,7 +2,7 @@ package dotty.tools package dotc package typer -import ast._ +import ast.{TreeInfo, tpd, _} import Trees._ import core._ import Flags._ @@ -25,6 +25,7 @@ import dotty.tools.dotc.util.{SimpleIdentityMap, SimpleIdentitySet, SourceFile, import collection.mutable import reporting.trace import util.Spans.Span +import dotty.tools.dotc.transform.{Splicer, TreeMapWithStages} object Inliner { import tpd._ @@ -104,7 +105,7 @@ object Inliner { else if (enclosingInlineds.length < ctx.settings.XmaxInlines.value) { val body = bodyToInline(tree.symbol) // can typecheck the tree and thereby produce errors if (ctx.reporter.hasErrors) tree - else new Inliner(tree, body).inlined(pt) + else new Inliner(tree, body).inlined(pt, tree.sourcePos) } else errorTree( @@ -381,7 +382,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { } /** The Inlined node representing the inlined call */ - def inlined(pt: Type): Tree = { + def inlined(pt: Type, sourcePos: SourcePosition): Tree = { if (callTypeArgs.length == 1) if (inlinedMethod == defn.Compiletime_constValue) { @@ -487,7 +488,13 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { trace(i"inlining $call", inlining, show = true) { // The normalized bindings collected in `bindingsBuf` - bindingsBuf.transform(reducer.normalizeBinding(_)(inlineCtx)) + bindingsBuf.transform { binding => + val transformedBinding = reducer.normalizeBinding(binding)(inlineCtx) + // Set trees to symbols allow macros to see the definition tree. + // This is used by `underlyingArgument`. + transformedBinding.symbol.defTree = transformedBinding + transformedBinding + } // Run a typing pass over the inlined tree. See InlineTyper for details. val expansion1 = inlineTyper.typed(expansion, pt)(inlineCtx) @@ -949,11 +956,25 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = { assert(tree.hasType, tree) val qual1 = typed(tree.qualifier, selectionProto(tree.name, pt, this)) - val res = untpd.cpy.Select(tree)(qual1, tree.name).withType(tree.typeOpt) + val res = + if (tree.symbol == defn.QuotedExpr_splice && StagingContext.level == 0) expandMacro(qual1, tree.span) + else untpd.cpy.Select(tree)(qual1, tree.name).withType(tree.typeOpt) ensureAccessible(res.tpe, tree.qualifier.isInstanceOf[untpd.Super], tree.sourcePos) res } + private def expandMacro(body: Tree, span: Span)(implicit ctx: Context) = { + assert(StagingContext.level == 0) + // TODO cache macro classloader + val urls = ctx.settings.classpath.value.split(java.io.File.pathSeparatorChar).map(cp => java.nio.file.Paths.get(cp).toUri.toURL) + val macroClassLoader = new java.net.URLClassLoader(urls, getClass.getClassLoader) + + val inlinedFrom = enclosingInlineds.last + val evaluatedSplice = Splicer.splice(body, inlinedFrom.sourcePos, macroClassLoader)(ctx.withSource(inlinedFrom.source)) + if (ctx.reporter.hasErrors) EmptyTree + else evaluatedSplice.withSpan(span) + } + override def typedIf(tree: untpd.If, pt: Type)(implicit ctx: Context): Tree = typed(tree.cond, defn.BooleanType) match { case cond1 @ ConstantValue(b: Boolean) => diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 0c6d0d66b97c..b8cac56bdc88 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -37,6 +37,7 @@ import config.Printers.{gadts, typr} import rewrites.Rewrites.patch import NavigateAST._ import dotty.tools.dotc.transform.{PCPCheckAndHeal, Staging, TreeMapWithStages} +import dotty.tools.dotc.core.StagingContext._ import transform.SymUtils._ import transform.TypeUtils._ import reporting.trace @@ -1568,7 +1569,7 @@ class Typer extends Namer if (isMacro) { sym.setFlag(Macro) - if (TreeMapWithStages.level == 0) + if (level == 0) rhs1 match { case InlineSplice(_) => new PCPCheckAndHeal(freshStagingContext).transform(rhs1) // Ignore output, only check PCP @@ -1961,14 +1962,33 @@ class Typer extends Namer } } - /** Translate `'(expr)`/`'{ expr* }` into `scala.quoted.Expr.apply(expr)` and `'[T]` into `scala.quoted.Type.apply[T]` + /** Translate '{ t }` into `scala.quoted.Expr.apply(t)` and `'[T]` into `scala.quoted.Type.apply[T]` * while tracking the quotation level in the context. */ def typedQuote(tree: untpd.Quote, pt: Type)(implicit ctx: Context): Tree = track("typedQuote") { - if (tree.t.isType) - typedTypeApply(untpd.TypeApply(untpd.ref(defn.QuotedType_applyR), List(tree.t)), pt)(TreeMapWithStages.quoteContext).withSpan(tree.span) - else - typedApply(untpd.Apply(untpd.ref(defn.QuotedExpr_applyR), tree.t), pt)(TreeMapWithStages.quoteContext).withSpan(tree.span) + val tree1 = + if (tree.t.isType) + typedTypeApply(untpd.TypeApply(untpd.ref(defn.QuotedType_applyR), List(tree.t)), pt)(quoteContext) + else + typedApply(untpd.Apply(untpd.ref(defn.QuotedExpr_applyR), tree.t), pt)(quoteContext) + tree1.withSpan(tree.span) + } + + /** Translate `${ t: Expr[T] }` into expresiion `t.splice` while tracking the quotation level in the context */ + def typedSplice(tree: untpd.Splice, pt: Type)(implicit ctx: Context): Tree = track("typedSplice") { + checkSpliceOutsideQuote(tree) + typedSelect(untpd.Select(tree.expr, nme.splice), pt)(spliceContext).withSpan(tree.span) + } + + /** Translate ${ t: Type[T] }` into type `t.splice` while tracking the quotation level in the context */ + def typedTypSplice(tree: untpd.TypSplice, pt: Type)(implicit ctx: Context): Tree = track("typedTypSplice") { + checkSpliceOutsideQuote(tree) + typedSelect(untpd.Select(tree.expr, tpnme.splice), pt)(spliceContext).withSpan(tree.span) + } + + private def checkSpliceOutsideQuote(tree: untpd.Tree)(implicit ctx: Context): Unit = { + if (level == 0 && !ctx.owner.isInlineMethod) + ctx.error("splice outside quotes or inline method", tree.sourcePos) } /** Retrieve symbol attached to given tree */ @@ -2062,6 +2082,8 @@ class Typer extends Namer case tree @ untpd.PostfixOp(qual, Ident(nme.WILDCARD)) => typedAsFunction(tree, pt) case untpd.EmptyTree => tpd.EmptyTree case tree: untpd.Quote => typedQuote(tree, pt) + case tree: untpd.Splice => typedSplice(tree, pt) + case tree: untpd.TypSplice => typedTypSplice(tree, pt) case _ => typedUnadapted(desugar(tree), pt, locked) } diff --git a/tests/neg/quote-whitebox/Macro_1.scala b/tests/neg/quote-whitebox/Macro_1.scala new file mode 100644 index 000000000000..11eb26bf60df --- /dev/null +++ b/tests/neg/quote-whitebox/Macro_1.scala @@ -0,0 +1,9 @@ +import scala.quoted._ + +object Macros { + inline def defaultOf(inline str: String) <: Any = ${ defaultOfImpl(str) } + def defaultOfImpl(str: String): Expr[Any] = str match { + case "int" => '{1} + case "string" => '{"a"} + } +} diff --git a/tests/neg/quote-whitebox/Test_2.scala b/tests/neg/quote-whitebox/Test_2.scala new file mode 100644 index 000000000000..984e741af216 --- /dev/null +++ b/tests/neg/quote-whitebox/Test_2.scala @@ -0,0 +1,8 @@ +import Macros._ + +object Test { + def main(args: Array[String]): Unit = { + val a: String = defaultOf("int") // error + val b: Int = defaultOf("string") // error + } +} diff --git a/tests/neg/tasty-macro-assert/quoted_1.scala b/tests/neg/tasty-macro-assert-1/quoted_1.scala similarity index 100% rename from tests/neg/tasty-macro-assert/quoted_1.scala rename to tests/neg/tasty-macro-assert-1/quoted_1.scala diff --git a/tests/neg/tasty-macro-assert-1/quoted_2.scala b/tests/neg/tasty-macro-assert-1/quoted_2.scala new file mode 100644 index 000000000000..25ef0291b120 --- /dev/null +++ b/tests/neg/tasty-macro-assert-1/quoted_2.scala @@ -0,0 +1,10 @@ + +import Asserts._ + +object Test { + def main(args: Array[String]): Unit = { + macroAssert(true === "cde") + macroAssert("acb" === "cde") // error + } + +} diff --git a/tests/neg/tasty-macro-assert-2/quoted_1.scala b/tests/neg/tasty-macro-assert-2/quoted_1.scala new file mode 100644 index 000000000000..f3796a7a31f4 --- /dev/null +++ b/tests/neg/tasty-macro-assert-2/quoted_1.scala @@ -0,0 +1,69 @@ +import scala.quoted._ + +import scala.tasty._ + +object Asserts { + + implicit class Ops[T](left: T) { + def ===(right: T): Boolean = left == right + def !==(right: T): Boolean = left != right + } + + object Ops + + inline def macroAssert(cond: => Boolean): Unit = + ${ impl('cond) } + + def impl(cond: Expr[Boolean])(implicit reflect: Reflection): Expr[Unit] = { + import reflect._ + + val tree = cond.unseal + + def isOps(tpe: TypeOrBounds): Boolean = tpe match { + case Type.SymRef(IsDefSymbol(sym), _) => sym.name == "Ops" // TODO check that the parent is Asserts + case _ => false + } + + object OpsTree { + def unapply(arg: Term): Option[Term] = arg match { + case Term.Apply(Term.TypeApply(term, _), left :: Nil) if isOps(term.tpe) => + Some(left) + case _ => None + } + } + + tree match { + case Term.Inlined(_, Nil, Term.Apply(Term.Select(OpsTree(left), op), right :: Nil)) => + '{assertTrue(${left.seal[Boolean]})} // Buggy code. To generate the errors + case _ => + '{assertTrue($cond)} + } + + } + + def assertEquals[T](left: T, right: T): Unit = { + if (left != right) { + println( + s"""Error left did not equal right: + | left = $left + | right = $right""".stripMargin) + } + + } + + def assertNotEquals[T](left: T, right: T): Unit = { + if (left == right) { + println( + s"""Error left was equal to right: + | left = $left + | right = $right""".stripMargin) + } + + } + + def assertTrue(cond: Boolean): Unit = { + if (!cond) + println("Condition was false") + } + +} diff --git a/tests/neg/tasty-macro-assert/quoted_2.scala b/tests/neg/tasty-macro-assert-2/quoted_2.scala similarity index 67% rename from tests/neg/tasty-macro-assert/quoted_2.scala rename to tests/neg/tasty-macro-assert-2/quoted_2.scala index b94c98ab6b70..b43e3cc84dde 100644 --- a/tests/neg/tasty-macro-assert/quoted_2.scala +++ b/tests/neg/tasty-macro-assert-2/quoted_2.scala @@ -3,8 +3,6 @@ import Asserts._ object Test { def main(args: Array[String]): Unit = { - macroAssert(true === "cde") - macroAssert("acb" === "cde") // error macroAssert(false !== "acb") macroAssert("acb" !== "acb") // error } diff --git a/tests/run-custom-args/Yretain-trees/tasty-definitions-3.check b/tests/run-custom-args/Yretain-trees/tasty-definitions-3.check index a67242c349e8..466e73606bac 100644 --- a/tests/run-custom-args/Yretain-trees/tasty-definitions-3.check +++ b/tests/run-custom-args/Yretain-trees/tasty-definitions-3.check @@ -1,3 +1,3 @@ -DefDef("foo", Nil, Nil, TypeTree.Ident("Int"), Some(Term.Apply(Term.Select(Term.Literal(Constant.Int(1)), "+"), List(Term.Literal(Constant.Int(2)))))) -ValDef("bar", TypeTree.Ident("Int"), Some(Term.Apply(Term.Select(Term.Literal(Constant.Int(2)), "+"), List(Term.Literal(Constant.Int(3)))))) +DefDef("foo", Nil, Nil, TypeTree.Inferred(), None) +ValDef("bar", TypeTree.Inferred(), None) Pattern.Bind("x", Pattern.Value(Term.Ident("_"))) diff --git a/tests/run-custom-args/Yretain-trees/tasty-extractors-owners.check b/tests/run-custom-args/Yretain-trees/tasty-extractors-owners.check index ab8f8ee11d2a..e2d34376e9a5 100644 --- a/tests/run-custom-args/Yretain-trees/tasty-extractors-owners.check +++ b/tests/run-custom-args/Yretain-trees/tasty-extractors-owners.check @@ -1,27 +1,27 @@ foo -DefDef("main", Nil, List(List(ValDef("args", TypeTree.Applied(TypeTree.Ident("Array"), List(TypeTree.Ident("String"))), None))), TypeTree.Ident("Unit"), Some(Term.Block(Nil, Term.Inlined(Some(Term.Apply(Term.TypeApply(Term.Ident("printOwners"), List(TypeTree.Inferred())), List(Term.Block(List(DefDef("foo", Nil, Nil, TypeTree.Inferred(), Some(Term.Block(List(DefDef("bar", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(1)))), ValDef("bar2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(2))))), Term.Typed(Term.Ident("bar"), TypeTree.Inferred())))), ValDef("foo2", TypeTree.Inferred(), Some(Term.Block(List(DefDef("baz", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(3)))), ValDef("baz2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(4))))), Term.Typed(Term.Ident("baz"), TypeTree.Inferred())))), ClassDef("A", DefDef("", Nil, List(Nil), TypeTree.Inferred(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), ""), Nil)), Nil, None, List(TypeDef("B", TypeTree.Ident("Int")), DefDef("b", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(5)))), ValDef("b2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(6))))))), Term.Literal(Constant.Unit()))))), Nil, Term.Typed(Term.Select(Term.Apply(Term.Apply(Term.TypeApply(Term.Ident("impl"), List(TypeTree.Inferred())), List(Term.Apply(Term.TypeApply(Term.Ident("apply"), List(TypeTree.Inferred())), List(Term.Inlined(None, Nil, Term.Block(List(DefDef("foo", Nil, Nil, TypeTree.Inferred(), Some(Term.Block(List(DefDef("bar", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(1)))), ValDef("bar2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(2))))), Term.Typed(Term.Ident("bar"), TypeTree.Inferred())))), ValDef("foo2", TypeTree.Inferred(), Some(Term.Block(List(DefDef("baz", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(3)))), ValDef("baz2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(4))))), Term.Typed(Term.Ident("baz"), TypeTree.Inferred())))), ClassDef("A", DefDef("", Nil, List(Nil), TypeTree.Inferred(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), ""), Nil)), Nil, None, List(TypeDef("B", TypeTree.Ident("Int")), DefDef("b", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(5)))), ValDef("b2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(6))))))), Term.Literal(Constant.Unit()))))))), List(Term.Ident("macroContext"))), "$splice"), TypeTree.Ident("Unit")))))) +DefDef("main", Nil, List(List(ValDef("args", TypeTree.Inferred(), None))), TypeTree.Inferred(), None) bar -DefDef("foo", Nil, Nil, TypeTree.Inferred(), Some(Term.Block(List(DefDef("bar", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(1)))), ValDef("bar2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(2))))), Term.Typed(Term.Ident("bar"), TypeTree.Inferred())))) +DefDef("foo", Nil, Nil, TypeTree.Inferred(), None) bar2 -DefDef("foo", Nil, Nil, TypeTree.Inferred(), Some(Term.Block(List(DefDef("bar", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(1)))), ValDef("bar2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(2))))), Term.Typed(Term.Ident("bar"), TypeTree.Inferred())))) +DefDef("foo", Nil, Nil, TypeTree.Inferred(), None) foo2 -DefDef("main", Nil, List(List(ValDef("args", TypeTree.Applied(TypeTree.Ident("Array"), List(TypeTree.Ident("String"))), None))), TypeTree.Ident("Unit"), Some(Term.Block(Nil, Term.Inlined(Some(Term.Apply(Term.TypeApply(Term.Ident("printOwners"), List(TypeTree.Inferred())), List(Term.Block(List(DefDef("foo", Nil, Nil, TypeTree.Inferred(), Some(Term.Block(List(DefDef("bar", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(1)))), ValDef("bar2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(2))))), Term.Typed(Term.Ident("bar"), TypeTree.Inferred())))), ValDef("foo2", TypeTree.Inferred(), Some(Term.Block(List(DefDef("baz", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(3)))), ValDef("baz2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(4))))), Term.Typed(Term.Ident("baz"), TypeTree.Inferred())))), ClassDef("A", DefDef("", Nil, List(Nil), TypeTree.Inferred(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), ""), Nil)), Nil, None, List(TypeDef("B", TypeTree.Ident("Int")), DefDef("b", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(5)))), ValDef("b2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(6))))))), Term.Literal(Constant.Unit()))))), Nil, Term.Typed(Term.Select(Term.Apply(Term.Apply(Term.TypeApply(Term.Ident("impl"), List(TypeTree.Inferred())), List(Term.Apply(Term.TypeApply(Term.Ident("apply"), List(TypeTree.Inferred())), List(Term.Inlined(None, Nil, Term.Block(List(DefDef("foo", Nil, Nil, TypeTree.Inferred(), Some(Term.Block(List(DefDef("bar", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(1)))), ValDef("bar2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(2))))), Term.Typed(Term.Ident("bar"), TypeTree.Inferred())))), ValDef("foo2", TypeTree.Inferred(), Some(Term.Block(List(DefDef("baz", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(3)))), ValDef("baz2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(4))))), Term.Typed(Term.Ident("baz"), TypeTree.Inferred())))), ClassDef("A", DefDef("", Nil, List(Nil), TypeTree.Inferred(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Inferred()), ""), Nil)), Nil, None, List(TypeDef("B", TypeTree.Ident("Int")), DefDef("b", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(5)))), ValDef("b2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(6))))))), Term.Literal(Constant.Unit()))))))), List(Term.Ident("macroContext"))), "$splice"), TypeTree.Ident("Unit")))))) +DefDef("main", Nil, List(List(ValDef("args", TypeTree.Inferred(), None))), TypeTree.Inferred(), None) baz -ValDef("foo2", TypeTree.Inferred(), Some(Term.Block(List(DefDef("baz", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(3)))), ValDef("baz2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(4))))), Term.Typed(Term.Ident("baz"), TypeTree.Inferred())))) +ValDef("foo2", TypeTree.Inferred(), None) baz2 -ValDef("foo2", TypeTree.Inferred(), Some(Term.Block(List(DefDef("baz", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(3)))), ValDef("baz2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(4))))), Term.Typed(Term.Ident("baz"), TypeTree.Inferred())))) +ValDef("foo2", TypeTree.Inferred(), None) -ClassDef("A", DefDef("", Nil, List(Nil), TypeTree.Inferred(), None), List(TypeTree.Inferred()), Nil, None, List(TypeDef("B", TypeBoundsTree(TypeTree.Inferred(), TypeTree.Inferred())), DefDef("b", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(5)))), ValDef("b2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(6)))))) +ClassDef("A", DefDef("", Nil, List(Nil), TypeTree.Inferred(), None), List(TypeTree.Inferred()), Nil, None, List(TypeDef("B", TypeBoundsTree(TypeTree.Inferred(), TypeTree.Inferred())), DefDef("b", Nil, Nil, TypeTree.Inferred(), None), ValDef("b2", TypeTree.Inferred(), None))) b -ClassDef("A", DefDef("", Nil, List(Nil), TypeTree.Inferred(), None), List(TypeTree.Inferred()), Nil, None, List(TypeDef("B", TypeBoundsTree(TypeTree.Inferred(), TypeTree.Inferred())), DefDef("b", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(5)))), ValDef("b2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(6)))))) +ClassDef("A", DefDef("", Nil, List(Nil), TypeTree.Inferred(), None), List(TypeTree.Inferred()), Nil, None, List(TypeDef("B", TypeBoundsTree(TypeTree.Inferred(), TypeTree.Inferred())), DefDef("b", Nil, Nil, TypeTree.Inferred(), None), ValDef("b2", TypeTree.Inferred(), None))) b2 -ClassDef("A", DefDef("", Nil, List(Nil), TypeTree.Inferred(), None), List(TypeTree.Inferred()), Nil, None, List(TypeDef("B", TypeBoundsTree(TypeTree.Inferred(), TypeTree.Inferred())), DefDef("b", Nil, Nil, TypeTree.Inferred(), Some(Term.Literal(Constant.Int(5)))), ValDef("b2", TypeTree.Inferred(), Some(Term.Literal(Constant.Int(6)))))) +ClassDef("A", DefDef("", Nil, List(Nil), TypeTree.Inferred(), None), List(TypeTree.Inferred()), Nil, None, List(TypeDef("B", TypeBoundsTree(TypeTree.Inferred(), TypeTree.Inferred())), DefDef("b", Nil, Nil, TypeTree.Inferred(), None), ValDef("b2", TypeTree.Inferred(), None))) diff --git a/tests/run-custom-args/Yretain-trees/tasty-load-tree-2.check b/tests/run-custom-args/Yretain-trees/tasty-load-tree-2.check index fa4dada79aef..6438ecc51781 100644 --- a/tests/run-custom-args/Yretain-trees/tasty-load-tree-2.check +++ b/tests/run-custom-args/Yretain-trees/tasty-load-tree-2.check @@ -1,2 +1,2 @@ -DefDef("foo", Nil, Nil, TypeTree.Ident("Int"), Some(Term.Apply(Term.Select(Term.Literal(Constant.Int(1)), "+"), List(Term.Literal(Constant.Int(2)))))) -ValDef("bar", TypeTree.Ident("Int"), Some(Term.Apply(Term.Select(Term.Literal(Constant.Int(2)), "+"), List(Term.Literal(Constant.Int(3)))))) +DefDef("foo", Nil, Nil, TypeTree.Inferred(), None) +ValDef("bar", TypeTree.Inferred(), None) diff --git a/tests/run-with-compiler/quote-macro-in-splice.check b/tests/run-with-compiler/quote-macro-in-splice.check new file mode 100644 index 000000000000..b3982e366180 --- /dev/null +++ b/tests/run-with-compiler/quote-macro-in-splice.check @@ -0,0 +1,4 @@ +{ + val y: scala.Int = 1 + y.+(8) +} diff --git a/tests/run-with-compiler/quote-macro-in-splice/quoted_1.scala b/tests/run-with-compiler/quote-macro-in-splice/quoted_1.scala new file mode 100644 index 000000000000..762161a14d2d --- /dev/null +++ b/tests/run-with-compiler/quote-macro-in-splice/quoted_1.scala @@ -0,0 +1,5 @@ +import scala.quoted._ + +object Macros { + def impl(x: Expr[Int]): Expr[Int] = '{ $x + 1 } +} diff --git a/tests/run-with-compiler/quote-macro-in-splice/quoted_2.scala b/tests/run-with-compiler/quote-macro-in-splice/quoted_2.scala new file mode 100644 index 000000000000..9958f623c754 --- /dev/null +++ b/tests/run-with-compiler/quote-macro-in-splice/quoted_2.scala @@ -0,0 +1,19 @@ +import scala.quoted._ +import Macros._ + +import scala.quoted.Toolbox.Default._ + +object Test { + def main(args: Array[String]): Unit = { + val x = '{ + val y = 1 + ${ + inline def a(z: Int): Int = ${ impl('z) } + val b = a(7).toExpr + '{ y + $b } + } + } + println(x.show) + } + +} diff --git a/tests/run/quote-whitebox.check b/tests/run/quote-whitebox.check new file mode 100644 index 000000000000..e439850d6fa5 --- /dev/null +++ b/tests/run/quote-whitebox.check @@ -0,0 +1,2 @@ +1 +a diff --git a/tests/run/quote-whitebox/Macro_1.scala b/tests/run/quote-whitebox/Macro_1.scala new file mode 100644 index 000000000000..11eb26bf60df --- /dev/null +++ b/tests/run/quote-whitebox/Macro_1.scala @@ -0,0 +1,9 @@ +import scala.quoted._ + +object Macros { + inline def defaultOf(inline str: String) <: Any = ${ defaultOfImpl(str) } + def defaultOfImpl(str: String): Expr[Any] = str match { + case "int" => '{1} + case "string" => '{"a"} + } +} diff --git a/tests/run/quote-whitebox/Test_2.scala b/tests/run/quote-whitebox/Test_2.scala new file mode 100644 index 000000000000..105fcad244b7 --- /dev/null +++ b/tests/run/quote-whitebox/Test_2.scala @@ -0,0 +1,10 @@ +import Macros._ + +object Test { + def main(args: Array[String]): Unit = { + val a: Int = defaultOf("int") + val b: String = defaultOf("string") + println(a) + println(b) + } +} diff --git a/tests/run/tasty-argument-tree-1.check b/tests/run/tasty-argument-tree-1.check index b6b86afcffc1..8d50036bcadc 100644 --- a/tests/run/tasty-argument-tree-1.check +++ b/tests/run/tasty-argument-tree-1.check @@ -1,33 +1,33 @@ -tree: Term.Literal(Constant.Int(3)) +tree: Term.Inlined(None, Nil, Term.Literal(Constant.Int(3))) tree deref. vals: Term.Literal(Constant.Int(3)) -tree: Term.Ident("v") +tree: Term.Inlined(None, Nil, Term.Ident("v")) tree deref. vals: Term.Literal(Constant.Int(1)) -tree: Term.Ident("x") +tree: Term.Inlined(None, Nil, Term.Ident("x")) tree deref. vals: Term.Literal(Constant.Int(2)) -tree: Term.Ident("l") +tree: Term.Inlined(None, Nil, Term.Ident("l")) tree deref. vals: Term.Literal(Constant.Int(3)) -tree: Term.Ident("a") +tree: Term.Inlined(None, Nil, Term.Ident("a")) tree deref. vals: Term.Ident("a") -tree: Term.Ident("x") +tree: Term.Inlined(None, Nil, Term.Ident("x")) tree deref. vals: Term.Ident("b") -tree: Term.Ident("vv") +tree: Term.Inlined(None, Nil, Term.Ident("vv")) tree deref. vals: Term.Literal(Constant.Int(1)) -tree: Term.Ident("x") +tree: Term.Inlined(None, Nil, Term.Ident("x")) tree deref. vals: Term.Literal(Constant.Int(1)) -tree: Term.Ident("vd") +tree: Term.Inlined(None, Nil, Term.Ident("vd")) tree deref. vals: Term.Literal(Constant.Int(2)) -tree: Term.Ident("x") +tree: Term.Inlined(None, Nil, Term.Ident("x")) tree deref. vals: Term.Literal(Constant.Int(2)) -tree: Term.Ident("x") +tree: Term.Inlined(None, Nil, Term.Ident("x")) tree deref. vals: Term.Apply(Term.TypeApply(Term.Select(Term.Ident("Tuple2"), "apply"), List(TypeTree.Inferred(), TypeTree.Inferred())), List(Term.Literal(Constant.Int(1)), Term.Literal(Constant.Int(2)))) diff --git a/tests/run/tasty-extractors-1.check b/tests/run/tasty-extractors-1.check index b04fa977ed07..bc4db2f21ce5 100644 --- a/tests/run/tasty-extractors-1.check +++ b/tests/run/tasty-extractors-1.check @@ -1,19 +1,19 @@ -Term.Literal(Constant.Boolean(true)) +Term.Inlined(None, Nil, Term.Literal(Constant.Boolean(true))) Type.ConstantType(Constant.Boolean(true)) -Term.Literal(Constant.Int(1)) +Term.Inlined(None, Nil, Term.Literal(Constant.Int(1))) Type.ConstantType(Constant.Int(1)) -Term.Literal(Constant.Long(2)) +Term.Inlined(None, Nil, Term.Literal(Constant.Long(2))) Type.ConstantType(Constant.Long(2)) -Term.Literal(Constant.Float(2.1)) +Term.Inlined(None, Nil, Term.Literal(Constant.Float(2.1))) Type.ConstantType(Constant.Float(2.1)) -Term.Literal(Constant.Double(2.2)) +Term.Inlined(None, Nil, Term.Literal(Constant.Double(2.2))) Type.ConstantType(Constant.Double(2.2)) -Term.Literal(Constant.String("abc")) +Term.Inlined(None, Nil, Term.Literal(Constant.String("abc"))) Type.ConstantType(Constant.String("abc")) Term.Inlined(None, Nil, Term.Apply(Term.Ident("println"), List(Term.Literal(Constant.String("abc"))))) @@ -28,7 +28,7 @@ Type.SymRef(IsClassSymbol(), Type.SymRef(IsPackageSymbol(), T Term.Inlined(None, Nil, Term.Typed(Term.Literal(Constant.Short(8)), TypeTree.Ident("Short"))) Type.SymRef(IsClassSymbol(), Type.SymRef(IsPackageSymbol(), Type.ThisType(Type.SymRef(IsPackageSymbol(<>), NoPrefix())))) -Term.Literal(Constant.Char(a)) +Term.Inlined(None, Nil, Term.Literal(Constant.Char(a))) Type.ConstantType(Constant.Char(a)) Term.Inlined(None, Nil, Term.Block(List(Term.Literal(Constant.Int(1)), Term.Literal(Constant.Int(2))), Term.Literal(Constant.Int(3)))) @@ -37,40 +37,40 @@ Type.ConstantType(Constant.Int(3)) Term.Inlined(None, Nil, Term.If(Term.Typed(Term.Literal(Constant.Boolean(true)), TypeTree.Ident("Boolean")), Term.Literal(Constant.Int(1)), Term.Literal(Constant.Int(2)))) Type.SymRef(IsClassSymbol(), Type.ThisType(Type.SymRef(IsPackageSymbol(), NoPrefix()))) -Term.Inlined(None, Nil, Term.Match(Term.Literal(Constant.String("a")), List(CaseDef(Pattern.Value(Term.Literal(Constant.String("a"))), None, Term.Literal(Constant.Unit()))))) +Term.Inlined(None, Nil, Term.Match(Term.Literal(Constant.String("a")), List(CaseDef(Pattern.Value(Term.Literal(Constant.String("a"))), None, Term.Block(Nil, Term.Literal(Constant.Unit())))))) Type.SymRef(IsClassSymbol(), Type.ThisType(Type.SymRef(IsPackageSymbol(), NoPrefix()))) -Term.Inlined(None, Nil, Term.Match(Term.Literal(Constant.String("b")), List(CaseDef(Pattern.Bind("n", Pattern.Value(Term.Ident("_"))), None, Term.Literal(Constant.Unit()))))) +Term.Inlined(None, Nil, Term.Match(Term.Literal(Constant.String("b")), List(CaseDef(Pattern.Bind("n", Pattern.Value(Term.Ident("_"))), None, Term.Block(Nil, Term.Literal(Constant.Unit())))))) Type.SymRef(IsClassSymbol(), Type.ThisType(Type.SymRef(IsPackageSymbol(), NoPrefix()))) -Term.Inlined(None, Nil, Term.Match(Term.Literal(Constant.String("c")), List(CaseDef(Pattern.Bind("n", Pattern.TypeTest(TypeTree.Ident("String"))), None, Term.Literal(Constant.Unit()))))) +Term.Inlined(None, Nil, Term.Match(Term.Literal(Constant.String("c")), List(CaseDef(Pattern.Bind("n", Pattern.TypeTest(TypeTree.Ident("String"))), None, Term.Block(Nil, Term.Literal(Constant.Unit())))))) Type.SymRef(IsClassSymbol(), Type.ThisType(Type.SymRef(IsPackageSymbol(), NoPrefix()))) -Term.Inlined(None, Nil, Term.Match(Term.Literal(Constant.String("e")), List(CaseDef(Pattern.Value(Term.Ident("_")), None, Term.Literal(Constant.Unit()))))) +Term.Inlined(None, Nil, Term.Match(Term.Literal(Constant.String("e")), List(CaseDef(Pattern.Value(Term.Ident("_")), None, Term.Block(Nil, Term.Literal(Constant.Unit())))))) Type.SymRef(IsClassSymbol(), Type.ThisType(Type.SymRef(IsPackageSymbol(), NoPrefix()))) -Term.Inlined(None, Nil, Term.Match(Term.Literal(Constant.String("f")), List(CaseDef(Pattern.TypeTest(TypeTree.Ident("String")), None, Term.Literal(Constant.Unit()))))) +Term.Inlined(None, Nil, Term.Match(Term.Literal(Constant.String("f")), List(CaseDef(Pattern.TypeTest(TypeTree.Ident("String")), None, Term.Block(Nil, Term.Literal(Constant.Unit())))))) Type.SymRef(IsClassSymbol(), Type.ThisType(Type.SymRef(IsPackageSymbol(), NoPrefix()))) -Term.Inlined(None, Nil, Term.Match(Term.Typed(Term.Literal(Constant.String("g")), TypeTree.Ident("Any")), List(CaseDef(Pattern.Alternative(List(Pattern.TypeTest(TypeTree.Ident("String")), Pattern.TypeTest(TypeTree.Ident("Int")))), None, Term.Literal(Constant.Unit()))))) +Term.Inlined(None, Nil, Term.Match(Term.Typed(Term.Literal(Constant.String("g")), TypeTree.Ident("Any")), List(CaseDef(Pattern.Alternative(List(Pattern.TypeTest(TypeTree.Ident("String")), Pattern.TypeTest(TypeTree.Ident("Int")))), None, Term.Block(Nil, Term.Literal(Constant.Unit())))))) Type.SymRef(IsClassSymbol(), Type.ThisType(Type.SymRef(IsPackageSymbol(), NoPrefix()))) -Term.Inlined(None, Nil, Term.Match(Term.Literal(Constant.String("h")), List(CaseDef(Pattern.Value(Term.Ident("_")), Some(Term.Literal(Constant.Boolean(false))), Term.Literal(Constant.Unit()))))) +Term.Inlined(None, Nil, Term.Match(Term.Literal(Constant.String("h")), List(CaseDef(Pattern.Value(Term.Ident("_")), Some(Term.Literal(Constant.Boolean(false))), Term.Block(Nil, Term.Literal(Constant.Unit())))))) Type.SymRef(IsClassSymbol(), Type.ThisType(Type.SymRef(IsPackageSymbol(), NoPrefix()))) -Term.Inlined(None, Nil, Term.Block(List(ValDef("a", TypeTree.Inferred(), Some(Term.Literal(Constant.String("o"))))), Term.Match(Term.Literal(Constant.String("i")), List(CaseDef(Pattern.Bind("a", Pattern.Value(Term.Ident("_"))), None, Term.Literal(Constant.Unit())))))) +Term.Inlined(None, Nil, Term.Block(List(ValDef("a", TypeTree.Inferred(), Some(Term.Literal(Constant.String("o"))))), Term.Match(Term.Literal(Constant.String("i")), List(CaseDef(Pattern.Bind("a", Pattern.Value(Term.Ident("_"))), None, Term.Block(Nil, Term.Literal(Constant.Unit()))))))) Type.SymRef(IsClassSymbol(), Type.ThisType(Type.SymRef(IsPackageSymbol(), NoPrefix()))) -Term.Inlined(None, Nil, Term.Match(Term.Ident("Nil"), List(CaseDef(Pattern.Unapply(Term.TypeApply(Term.Select(Term.Ident("List"), "unapplySeq"), List(TypeTree.Inferred())), Nil, List(Pattern.Bind("a", Pattern.Value(Term.Ident("_"))), Pattern.Bind("b", Pattern.Value(Term.Ident("_"))), Pattern.Bind("c", Pattern.Value(Term.Ident("_"))))), None, Term.Literal(Constant.Unit()))))) +Term.Inlined(None, Nil, Term.Match(Term.Ident("Nil"), List(CaseDef(Pattern.Unapply(Term.TypeApply(Term.Select(Term.Ident("List"), "unapplySeq"), List(TypeTree.Inferred())), Nil, List(Pattern.Bind("a", Pattern.Value(Term.Ident("_"))), Pattern.Bind("b", Pattern.Value(Term.Ident("_"))), Pattern.Bind("c", Pattern.Value(Term.Ident("_"))))), None, Term.Block(Nil, Term.Literal(Constant.Unit())))))) Type.SymRef(IsClassSymbol(), Type.ThisType(Type.SymRef(IsPackageSymbol(), NoPrefix()))) -Term.Inlined(None, Nil, Term.Try(Term.Literal(Constant.Int(1)), List(CaseDef(Pattern.Value(Term.Ident("_")), None, Term.Literal(Constant.Unit()))), None)) +Term.Inlined(None, Nil, Term.Try(Term.Literal(Constant.Int(1)), List(CaseDef(Pattern.Value(Term.Ident("_")), None, Term.Block(Nil, Term.Literal(Constant.Unit())))), None)) Type.OrType(Type.SymRef(IsClassSymbol(), Type.ThisType(Type.SymRef(IsPackageSymbol(), NoPrefix()))), Type.SymRef(IsClassSymbol(), Type.ThisType(Type.SymRef(IsPackageSymbol(), NoPrefix())))) Term.Inlined(None, Nil, Term.Try(Term.Literal(Constant.Int(2)), Nil, Some(Term.Literal(Constant.Unit())))) Type.ConstantType(Constant.Int(2)) -Term.Inlined(None, Nil, Term.Try(Term.Literal(Constant.Int(3)), List(CaseDef(Pattern.Value(Term.Ident("_")), None, Term.Literal(Constant.Unit()))), Some(Term.Literal(Constant.Unit())))) +Term.Inlined(None, Nil, Term.Try(Term.Literal(Constant.Int(3)), List(CaseDef(Pattern.Value(Term.Ident("_")), None, Term.Block(Nil, Term.Literal(Constant.Unit())))), Some(Term.Literal(Constant.Unit())))) Type.OrType(Type.SymRef(IsClassSymbol(), Type.ThisType(Type.SymRef(IsPackageSymbol(), NoPrefix()))), Type.SymRef(IsClassSymbol(), Type.ThisType(Type.SymRef(IsPackageSymbol(), NoPrefix())))) Term.Inlined(None, Nil, Term.Apply(Term.Select(Term.Literal(Constant.String("a")), "=="), List(Term.Literal(Constant.String("b"))))) diff --git a/tests/run/tasty-extractors-2.check b/tests/run/tasty-extractors-2.check index 5603ba2b5219..9cfe4460e1ff 100644 --- a/tests/run/tasty-extractors-2.check +++ b/tests/run/tasty-extractors-2.check @@ -7,7 +7,7 @@ Type.AppliedType(Type.SymRef(IsClassSymbol(), Type.ThisType(Typ Term.Inlined(None, Nil, Term.Ident("???")) Type.SymRef(IsDefSymbol(), Type.SymRef(IsValSymbol(), Type.ThisType(Type.SymRef(IsPackageSymbol(), NoPrefix())))) -Term.Literal(Constant.Int(1)) +Term.Inlined(None, Nil, Term.Literal(Constant.Int(1))) Type.ConstantType(Constant.Int(1)) Term.Inlined(None, Nil, Term.Typed(Term.Literal(Constant.Int(1)), TypeTree.Ident("Int"))) diff --git a/tests/run/tasty-macro-const/quoted_1.scala b/tests/run/tasty-macro-const/quoted_1.scala index f48da1280d5f..20f419545bf0 100644 --- a/tests/run/tasty-macro-const/quoted_1.scala +++ b/tests/run/tasty-macro-const/quoted_1.scala @@ -9,7 +9,7 @@ object Macros { import reflection._ val xTree: Term = x.unseal xTree match { - case Term.Literal(Constant.Int(n)) => + case Term.Inlined(_, _, Term.Literal(Constant.Int(n))) => if (n <= 0) throw new QuoteError("Parameter must be natural number") xTree.seal[Int] diff --git a/tests/run/tasty-positioned.check b/tests/run/tasty-positioned.check index 3005121fb971..d007e4366bbe 100644 --- a/tests/run/tasty-positioned.check +++ b/tests/run/tasty-positioned.check @@ -4,5 +4,5 @@ acbvasdfa columns:13-36 lines:12-12 acbvasdfa columns:13-24 lines:13-13 a -b columns:6-25 lines:15-16 -Foo columns:12-19 lines:17-17 +b columns:6-33 lines:15-16 +Foo columns:13-20 lines:17-17 diff --git a/tests/run/tasty-positioned/quoted_2.scala b/tests/run/tasty-positioned/quoted_2.scala index 4026b53b3ff2..2ef194c72f63 100644 --- a/tests/run/tasty-positioned/quoted_2.scala +++ b/tests/run/tasty-positioned/quoted_2.scala @@ -14,8 +14,8 @@ object Test { printPos("acbvasdfa") printPos( """a - |b""".stripMargin) - printPos(new Foo) + |b""".stripMargin: String) + printPos(new Foo) } class Foo { override def toString: String = "Foo" From f8b9f3fc149d4c3762b6add5d2d6cbc91b1b24fa Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 25 Feb 2019 14:58:08 +0100 Subject: [PATCH 04/17] Only run staging when needed --- compiler/src/dotty/tools/dotc/transform/Staging.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/Staging.scala b/compiler/src/dotty/tools/dotc/transform/Staging.scala index 4c5c90a085e2..2053e3df904d 100644 --- a/compiler/src/dotty/tools/dotc/transform/Staging.scala +++ b/compiler/src/dotty/tools/dotc/transform/Staging.scala @@ -72,7 +72,7 @@ class Staging extends MacroTransform { } override def run(implicit ctx: Context): Unit = - /*if (ctx.compilationUnit.needsStaging)*/ super.run(freshStagingContext) + if (ctx.compilationUnit.needsStaging) super.run(freshStagingContext) protected def newTransformer(implicit ctx: Context): Transformer = new Transformer { override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = From b17deeae243a704bbcf79c6570fdbdc933ccaf38 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 26 Feb 2019 13:55:13 +0100 Subject: [PATCH 05/17] Cache macro classloader --- compiler/src/dotty/tools/dotc/Driver.scala | 3 ++- .../tools/dotc/core/MacroClassLoader.scala | 26 +++++++++++++++++++ .../src/dotty/tools/dotc/typer/Inliner.scala | 11 +++----- 3 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/core/MacroClassLoader.scala diff --git a/compiler/src/dotty/tools/dotc/Driver.scala b/compiler/src/dotty/tools/dotc/Driver.scala index 5fbb6853752d..a9313a409ab8 100644 --- a/compiler/src/dotty/tools/dotc/Driver.scala +++ b/compiler/src/dotty/tools/dotc/Driver.scala @@ -6,7 +6,7 @@ import dotty.tools.FatalError import config.CompilerCommand import core.Comments.{ContextDoc, ContextDocstrings} import core.Contexts.{Context, ContextBase} -import core.{Mode, TypeError} +import core.{MacroClassLoader, Mode, TypeError} import reporting._ import scala.util.control.NonFatal @@ -54,6 +54,7 @@ class Driver { val ctx = rootCtx.fresh val summary = CompilerCommand.distill(args)(ctx) ctx.setSettings(summary.sstate) + MacroClassLoader.init(ctx) if (!ctx.settings.YdropComments.value(ctx) || ctx.mode.is(Mode.ReadComments)) { ctx.setProperty(ContextDoc, new ContextDocstrings) diff --git a/compiler/src/dotty/tools/dotc/core/MacroClassLoader.scala b/compiler/src/dotty/tools/dotc/core/MacroClassLoader.scala new file mode 100644 index 000000000000..cbddcbaa6f05 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/MacroClassLoader.scala @@ -0,0 +1,26 @@ +package dotty.tools.dotc.core + +import dotty.tools.dotc.core.Contexts._ +import dotty.tools.dotc.util.Property +import dotty.tools.dotc.reporting.trace + +import scala.collection.mutable + +object MacroClassLoader { + + /** A key to be used in a context property that caches the class loader used for macro expansion */ + private val MacroClassLoaderKey = new Property.Key[ClassLoader] + + /** Get the macro class loader */ + def fromContext(implicit ctx: Context): ClassLoader = + ctx.property(MacroClassLoaderKey).getOrElse(makeMacroClassLoader) + + /** Context with a cached macro class loader that can be accessed with `macroClassLoader` */ + def init(ctx: FreshContext): ctx.type = + ctx.setProperty(MacroClassLoaderKey, makeMacroClassLoader(ctx)) + + private def makeMacroClassLoader(implicit ctx: Context): ClassLoader = trace("new macro class loader") { + val urls = ctx.settings.classpath.value.split(java.io.File.pathSeparatorChar).map(cp => java.nio.file.Paths.get(cp).toUri.toURL) + new java.net.URLClassLoader(urls, getClass.getClassLoader) + } +} diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 8bc1ea9d96c6..afe13a003972 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -10,6 +10,7 @@ import Symbols._ import Types._ import Decorators._ import Constants._ +import StagingContext._ import StdNames._ import transform.SymUtils._ import Contexts.Context @@ -957,20 +958,16 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { assert(tree.hasType, tree) val qual1 = typed(tree.qualifier, selectionProto(tree.name, pt, this)) val res = - if (tree.symbol == defn.QuotedExpr_splice && StagingContext.level == 0) expandMacro(qual1, tree.span) + if (tree.symbol == defn.QuotedExpr_splice && level == 0) expandMacro(qual1, tree.span) else untpd.cpy.Select(tree)(qual1, tree.name).withType(tree.typeOpt) ensureAccessible(res.tpe, tree.qualifier.isInstanceOf[untpd.Super], tree.sourcePos) res } private def expandMacro(body: Tree, span: Span)(implicit ctx: Context) = { - assert(StagingContext.level == 0) - // TODO cache macro classloader - val urls = ctx.settings.classpath.value.split(java.io.File.pathSeparatorChar).map(cp => java.nio.file.Paths.get(cp).toUri.toURL) - val macroClassLoader = new java.net.URLClassLoader(urls, getClass.getClassLoader) - + assert(level == 0) val inlinedFrom = enclosingInlineds.last - val evaluatedSplice = Splicer.splice(body, inlinedFrom.sourcePos, macroClassLoader)(ctx.withSource(inlinedFrom.source)) + val evaluatedSplice = Splicer.splice(body, inlinedFrom.sourcePos, MacroClassLoader.fromContext)(ctx.withSource(inlinedFrom.source)) if (ctx.reporter.hasErrors) EmptyTree else evaluatedSplice.withSpan(span) } From dd21107cd63563e751762b38930156cfec5cd30d Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 26 Feb 2019 14:58:14 +0100 Subject: [PATCH 06/17] Add regression test --- tests/pos/quote-whitebox/Macro_1.scala | 10 ++++++++++ tests/pos/quote-whitebox/Test_2.scala | 8 ++++++++ 2 files changed, 18 insertions(+) create mode 100644 tests/pos/quote-whitebox/Macro_1.scala create mode 100644 tests/pos/quote-whitebox/Test_2.scala diff --git a/tests/pos/quote-whitebox/Macro_1.scala b/tests/pos/quote-whitebox/Macro_1.scala new file mode 100644 index 000000000000..2f21ccdabc21 --- /dev/null +++ b/tests/pos/quote-whitebox/Macro_1.scala @@ -0,0 +1,10 @@ + +import scala.quoted._ + +object Macro { + + inline def charOrString(inline str: String) <: Char | String = ${ impl(str) } + + def impl(str: String) = if (str.length == 1) str.charAt(0).toExpr else str.toExpr + +} diff --git a/tests/pos/quote-whitebox/Test_2.scala b/tests/pos/quote-whitebox/Test_2.scala new file mode 100644 index 000000000000..da5757f22d79 --- /dev/null +++ b/tests/pos/quote-whitebox/Test_2.scala @@ -0,0 +1,8 @@ +import Macro._ + +object Test { + + val x: Char = charOrString("1") + val y: String = charOrString("123") + +} From 4fdc8444dd5b2787f6a32f29e544c219e2ab5af2 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 26 Feb 2019 15:17:18 +0100 Subject: [PATCH 07/17] Cleanup macro PCP checks --- .../dotty/tools/dotc/transform/Staging.scala | 19 ------- .../tools/dotc/typer/PrepareInlineable.scala | 56 ++++++++++++++++++- .../src/dotty/tools/dotc/typer/Typer.scala | 31 +--------- tests/neg/splice-in-top-level-splice-1.scala | 2 +- 4 files changed, 55 insertions(+), 53 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/Staging.scala b/compiler/src/dotty/tools/dotc/transform/Staging.scala index 2053e3df904d..26826c150f3f 100644 --- a/compiler/src/dotty/tools/dotc/transform/Staging.scala +++ b/compiler/src/dotty/tools/dotc/transform/Staging.scala @@ -88,7 +88,6 @@ object Staging { class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(ictx) { import tpd._ - import PCPCheckAndHeal._ override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match { case tree: DefDef if tree.symbol.is(Inline) && level > 0 => EmptyTree @@ -273,22 +272,4 @@ object Staging { } } - - } - - object PCPCheckAndHeal { - import tpd._ - - /** InlineSplice is used to detect cases where the expansion - * consists of a (possibly multiple & nested) block or a sole expression. - */ - object InlineSplice { - def unapply(tree: Tree)(implicit ctx: Context): Option[Tree] = tree match { - case Spliced(code) if Splicer.canBeSpliced(code) => Some(code) - case Block(List(stat), Literal(Constant(()))) => unapply(stat) - case Block(Nil, expr) => unapply(expr) - case Typed(expr, _) => unapply(expr) - case _ => None - } - } } diff --git a/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala b/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala index 39353778a8e0..6b07c75e4d44 100644 --- a/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala +++ b/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala @@ -2,7 +2,7 @@ package dotty.tools package dotc package typer -import dotty.tools.dotc.ast.{Trees, untpd, tpd} +import dotty.tools.dotc.ast.{Trees, tpd, untpd} import Trees._ import core._ import Flags._ @@ -15,9 +15,11 @@ import Contexts.Context import Names.Name import NameKinds.{InlineAccessorName, UniqueInlineName} import Annotations._ -import transform.AccessProxies +import transform.{AccessProxies, PCPCheckAndHeal, Splicer, TreeMapWithStages} import config.Printers.inlining -import util.Property +import util.{Property, SourcePosition} +import dotty.tools.dotc.core.StagingContext._ +import dotty.tools.dotc.transform.TreeMapWithStages._ object PrepareInlineable { import tpd._ @@ -249,4 +251,52 @@ object PrepareInlineable { em"inline unapply method can be rewritten only if its right hand side is a tuple (e1, ..., eN)", body.sourcePos) } + + def checkInlineMacro(sym: Symbol, rhs: Tree, pos: SourcePosition)(implicit ctx: Context) = { + if (ctx.phase.isTyper) { + + /** InlineSplice is used to detect cases where the expansion + * consists of a (possibly multiple & nested) block or a sole expression. + */ + object InlineSplice { + def unapply(tree: Tree)(implicit ctx: Context): Option[Tree] = tree match { + case Spliced(code) if Splicer.canBeSpliced(code) => Some(code) + case Block(List(stat), Literal(Constants.Constant(()))) => unapply(stat) + case Block(Nil, expr) => unapply(expr) + case Typed(expr, _) => unapply(expr) + case _ => None + } + } + + var isMacro = false + new TreeMapWithStages(freshStagingContext) { + override protected def transformSplice(splice: tpd.Select)(implicit ctx: Context): tpd.Tree = { + isMacro = true + splice + } + override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = + if (isMacro) tree else super.transform(tree) + }.transform(rhs) + + if (isMacro) { + sym.setFlag(Macro) + if (level == 0) + rhs match { + case InlineSplice(_) => + new PCPCheckAndHeal(freshStagingContext).transform(rhs) // Ignore output, only check PCP + case _ => + ctx.error( + """Malformed macro. + | + |Expected the ~ to be at the top of the RHS: + | inline def foo(inline x: X, ..., y: Y): Int = ~impl(x, ... '(y)) + | + | * The contents of the splice must call a static method + | * All arguments must be quoted or inline + """.stripMargin, pos) + } + } + } + } + } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index b8cac56bdc88..99136f1a01ee 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1556,36 +1556,7 @@ class Typer extends Namer val rhs1 = typedExpr(ddef.rhs, tpt1.tpe.widenExpr)(rhsCtx) if (sym.isInlineMethod) { - if (ctx.phase.isTyper) { - import PCPCheckAndHeal.InlineSplice - import TreeMapWithStages._ - var isMacro = false - new TreeMapWithStages(freshStagingContext) { - override protected def transformSplice(splice: tpd.Select)(implicit ctx: Context): tpd.Tree = { - isMacro = true - splice - } - }.transform(rhs1) - - if (isMacro) { - sym.setFlag(Macro) - if (level == 0) - rhs1 match { - case InlineSplice(_) => - new PCPCheckAndHeal(freshStagingContext).transform(rhs1) // Ignore output, only check PCP - case _ => - ctx.error( - """Malformed macro. - | - |Expected the ~ to be at the top of the RHS: - | inline def foo(inline x: X, ..., y: Y): Int = ~impl(x, ... '(y)) - | - | * The contents of the splice must call a static method - | * All arguments must be quoted or inline - """.stripMargin, ddef.sourcePos) - } - } - } + PrepareInlineable.checkInlineMacro(sym, rhs1, ddef.sourcePos) PrepareInlineable.registerInlineInfo(sym, _ => rhs1) } diff --git a/tests/neg/splice-in-top-level-splice-1.scala b/tests/neg/splice-in-top-level-splice-1.scala index 921007e2d292..815b764ba919 100644 --- a/tests/neg/splice-in-top-level-splice-1.scala +++ b/tests/neg/splice-in-top-level-splice-1.scala @@ -1,7 +1,7 @@ import scala.quoted._ object Foo { - inline def foo(): Int = ${bar($x)} // error + inline def foo(): Int = ${bar(${x})} // error def x: Expr[Int] = '{1} def bar(i: Int): Expr[Int] = i.toExpr } From 26e5e0f3e729518159695010dfe0ed6cf88c2129 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 26 Feb 2019 15:22:53 +0100 Subject: [PATCH 08/17] Move PCPCheckAndHeal into its own file --- .../dotc/transform/PCPCheckAndHeal.scala | 219 ++++++++++++++++++ .../dotty/tools/dotc/transform/Staging.scala | 195 +--------------- 2 files changed, 220 insertions(+), 194 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala diff --git a/compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala b/compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala new file mode 100644 index 000000000000..061f8aa4f2b1 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala @@ -0,0 +1,219 @@ +package dotty.tools.dotc +package transform + +import dotty.tools.dotc.ast.Trees._ +import dotty.tools.dotc.ast.{TreeTypeMap, tpd, untpd} +import dotty.tools.dotc.core.Constants._ +import dotty.tools.dotc.core.Contexts._ +import dotty.tools.dotc.core.Decorators._ +import dotty.tools.dotc.core.Flags._ +import dotty.tools.dotc.core.quoted._ +import dotty.tools.dotc.core.NameKinds._ +import dotty.tools.dotc.core.StagingContext._ +import dotty.tools.dotc.core.StdNames._ +import dotty.tools.dotc.core.Symbols._ +import dotty.tools.dotc.core.tasty.TreePickler.Hole +import dotty.tools.dotc.core.Types._ +import dotty.tools.dotc.util.SourcePosition +import dotty.tools.dotc.util.Spans._ +import dotty.tools.dotc.transform.SymUtils._ +import dotty.tools.dotc.transform.TreeMapWithStages._ +import dotty.tools.dotc.typer.Implicits.SearchFailureType +import dotty.tools.dotc.typer.Inliner + +import scala.collection.mutable +import dotty.tools.dotc.util.SourcePosition + +import scala.annotation.constructorOnly + +/** Checks that the Phase Consistency Principle (PCP) holds and heals types. + * + * Type healing consists in transforming a phase inconsistent type `T` into `implicitly[Type[T]].unary_~`. + */ +class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(ictx) { + import tpd._ + + override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match { + case tree: DefDef if tree.symbol.is(Inline) && level > 0 => EmptyTree + case _ => checkLevel(super.transform(tree)) + } + + /** Transform quoted trees while maintaining phase correctness */ + override protected def transformQuotation(body: Tree, quote: Tree)(implicit ctx: Context): Tree = { + val body1 = transform(body)(quoteContext) + super.transformQuotation(body1, quote) + } + + /** Transform splice + * - If inside a quote, transform the contents of the splice. + * - If inside inlined code, expand the macro code. + * - If inside of a macro definition, check the validity of the macro. + */ + protected def transformSplice(splice: Select)(implicit ctx: Context): Tree = { + if (level >= 1) { + val body1 = transform(splice.qualifier)(spliceContext) + val splice1 = cpy.Select(splice)(body1, splice.name) + if (splice1.isType) splice1 + else addSpliceCast(splice1) + } + else { + assert(!enclosingInlineds.nonEmpty, "unexpanded macro") + assert(ctx.owner.isInlineMethod) + if (Splicer.canBeSpliced(splice.qualifier)) { // level 0 inside an inline definition + transform(splice.qualifier)(spliceContext) // Just check PCP + splice + } + else { // level 0 inside an inline definition + ctx.error( + "Malformed macro call. The contents of the $ must call a static method and arguments must be quoted or inline.", + splice.sourcePos) + splice + } + } + } + + + /** Add cast to force boundaries where T and ~t (an alias of T) are used to ensure PCP. + * '{ ~(...: T) } --> '{ ~(...: T).asInstanceOf[T] } --> '{ ~(...: T).asInstanceOf[~t] } + */ + protected def addSpliceCast(tree: Tree)(implicit ctx: Context): Tree = { + val tp = checkType(tree.sourcePos).apply(tree.tpe.widenTermRefExpr) + tree.cast(tp).withSpan(tree.span) + } + + /** If `tree` refers to a locally defined symbol (either directly, or in a pickled type), + * check that its staging level matches the current level. References to types + * that are phase-incorrect can still be healed as follows: + * + * If `T` is a reference to a type at the wrong level, try to heal it by replacing it with + * `~implicitly[quoted.Type[T]]`. + */ + protected def checkLevel(tree: Tree)(implicit ctx: Context): Tree = { + def checkTp(tp: Type): Type = checkType(tree.sourcePos).apply(tp) + tree match { + case Quoted(_) | Spliced(_) => + tree + case tree: RefTree if tree.symbol.is(InlineParam) => + tree + case _: This => + assert(checkSymLevel(tree.symbol, tree.tpe, tree.sourcePos).isEmpty) + tree + case _: Ident => + checkSymLevel(tree.symbol, tree.tpe, tree.sourcePos) match { + case Some(tpRef) => tpRef + case _ => tree + } + case _: TypeTree | _: AppliedTypeTree | _: Apply | _: TypeApply | _: UnApply | Select(_, OuterSelectName(_, _)) => + tree.withType(checkTp(tree.tpe)) + case _: ValOrDefDef | _: Bind => + tree.symbol.info = checkTp(tree.symbol.info) + tree + case _: Template => + checkTp(tree.symbol.owner.asClass.givenSelfType) + tree + case _ => + tree + } + } + + /** Check and heal all named types and this-types in a given type for phase consistency. */ + private def checkType(pos: SourcePosition)(implicit ctx: Context): TypeMap = new TypeMap { + def apply(tp: Type): Type = reporting.trace(i"check type level $tp at $level") { + tp match { + case tp: TypeRef if tp.symbol.isSplice => + if (tp.isTerm) + ctx.error(i"splice outside quotes", pos) + tp + case tp: NamedType => + checkSymLevel(tp.symbol, tp, pos) match { + case Some(tpRef) => tpRef.tpe + case _ => + if (tp.symbol.is(Param)) tp + else mapOver(tp) + } + case tp: ThisType => + assert(checkSymLevel(tp.cls, tp, pos).isEmpty) + mapOver(tp) + case _ => + mapOver(tp) + } + } + } + + /** Check reference to `sym` for phase consistency, where `tp` is the underlying type + * by which we refer to `sym`. If it is an inconsistent type try construct a healed type for it. + * + * @return `None` if the phase is correct or cannot be healed + * `Some(tree)` with the `tree` of the healed type tree for `~implicitly[quoted.Type[T]]` + */ + private def checkSymLevel(sym: Symbol, tp: Type, pos: SourcePosition)(implicit ctx: Context): Option[Tree] = { + val isThis = tp.isInstanceOf[ThisType] + if (!isThis && !sym.is(Param) && sym.maybeOwner.isType) + None + else if (sym.exists && !sym.isStaticOwner && !levelOK(sym)) + tryHeal(sym, tp, pos) + else + None + } + + /** Does the level of `sym` match the current level? + * An exception is made for inline vals in macros. These are also OK if their level + * is one higher than the current level, because on execution such values + * are constant expression trees and we can pull out the constant from the tree. + */ + private def levelOK(sym: Symbol)(implicit ctx: Context): Boolean = levelOf(sym) match { + case Some(l) => + l == level || + level == -1 && ( + sym == defn.TastyReflection_macroContext || + // here we assume that Splicer.canBeSpliced was true before going to level -1, + // this implies that all non-inline arguments are quoted and that the following two cases are checked + // on inline parameters or type parameters. + sym.is(Param) || + sym.isClass // reference to this in inline methods + ) + case None => + !sym.is(Param) || levelOK(sym.owner) + } + + /** Try to heal phase-inconsistent reference to type `T` using a local type definition. + * @return None if successful + * @return Some(msg) if unsuccessful where `msg` is a potentially empty error message + * to be added to the "inconsistent phase" message. + */ + protected def tryHeal(sym: Symbol, tp: Type, pos: SourcePosition)(implicit ctx: Context): Option[Tree] = { + def levelError(errMsg: String) = { + def symStr = + if (!tp.isInstanceOf[ThisType]) sym.show + else if (sym.is(ModuleClass)) sym.sourceModule.show + else i"${sym.name}.this" + ctx.error( + em"""access to $symStr from wrong staging level: + | - the definition is at level ${levelOf(sym).getOrElse(0)}, + | - but the access is at level $level.$errMsg""", pos) + None + } + tp match { + case tp: TypeRef => + if (level == -1) { + assert(ctx.inInlineMethod) + None + } else { + val reqType = defn.QuotedTypeType.appliedTo(tp) + val tag = ctx.typer.inferImplicitArg(reqType, pos.span) + tag.tpe match { + case fail: SearchFailureType => + levelError(i""" + | + | The access would be accepted with the right type tag, but + | ${ctx.typer.missingArgMsg(tag, reqType, "")}""") + case _ => + Some(tag.select(tpnme.splice)) + } + } + case _ => + levelError("") + } + } + +} diff --git a/compiler/src/dotty/tools/dotc/transform/Staging.scala b/compiler/src/dotty/tools/dotc/transform/Staging.scala index 26826c150f3f..d8468ed16db3 100644 --- a/compiler/src/dotty/tools/dotc/transform/Staging.scala +++ b/compiler/src/dotty/tools/dotc/transform/Staging.scala @@ -26,13 +26,9 @@ import dotty.tools.dotc.util.SourcePosition import scala.annotation.constructorOnly - -/** Checks that the Phase Consistency Principle (PCP) holds, heals types and expand macros. +/** Checks that the Phase Consistency Principle (PCP) holds and heals types. * * Type healing consists in transforming a phase inconsistent type `T` into `implicitly[Type[T]].unary_~`. - * - * For macro definitions we assume that we have a single ${...} directly as the RHS. - * The Splicer is used to check that the RHS will be interpretable (with the `Splicer`) once inlined. */ class Staging extends MacroTransform { import tpd._ @@ -79,197 +75,8 @@ class Staging extends MacroTransform { new PCPCheckAndHeal(ctx).transform(tree) } - } object Staging { val name: String = "staging" } - - class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(ictx) { - import tpd._ - - override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match { - case tree: DefDef if tree.symbol.is(Inline) && level > 0 => EmptyTree - case _ => checkLevel(super.transform(tree)) - } - - /** Transform quoted trees while maintaining phase correctness */ - override protected def transformQuotation(body: Tree, quote: Tree)(implicit ctx: Context): Tree = { - val body1 = transform(body)(quoteContext) - super.transformQuotation(body1, quote) - } - - /** Transform splice - * - If inside a quote, transform the contents of the splice. - * - If inside inlined code, expand the macro code. - * - If inside of a macro definition, check the validity of the macro. - */ - protected def transformSplice(splice: Select)(implicit ctx: Context): Tree = { - if (level >= 1) { - val body1 = transform(splice.qualifier)(spliceContext) - val splice1 = cpy.Select(splice)(body1, splice.name) - if (splice1.isType) splice1 - else addSpliceCast(splice1) - } - else { - assert(!enclosingInlineds.nonEmpty, "unexpanded macro") - assert(ctx.owner.isInlineMethod) - if (Splicer.canBeSpliced(splice.qualifier)) { // level 0 inside an inline definition - transform(splice.qualifier)(spliceContext) // Just check PCP - splice - } - else { // level 0 inside an inline definition - ctx.error( - "Malformed macro call. The contents of the $ must call a static method and arguments must be quoted or inline.", - splice.sourcePos) - splice - } - } - } - - - /** Add cast to force boundaries where T and ~t (an alias of T) are used to ensure PCP. - * '{ ~(...: T) } --> '{ ~(...: T).asInstanceOf[T] } --> '{ ~(...: T).asInstanceOf[~t] } - */ - protected def addSpliceCast(tree: Tree)(implicit ctx: Context): Tree = { - val tp = checkType(tree.sourcePos).apply(tree.tpe.widenTermRefExpr) - tree.cast(tp).withSpan(tree.span) - } - - /** If `tree` refers to a locally defined symbol (either directly, or in a pickled type), - * check that its staging level matches the current level. References to types - * that are phase-incorrect can still be healed as follows: - * - * If `T` is a reference to a type at the wrong level, try to heal it by replacing it with - * `~implicitly[quoted.Type[T]]`. - */ - protected def checkLevel(tree: Tree)(implicit ctx: Context): Tree = { - def checkTp(tp: Type): Type = checkType(tree.sourcePos).apply(tp) - tree match { - case Quoted(_) | Spliced(_) => - tree - case tree: RefTree if tree.symbol.is(InlineParam) => - tree - case _: This => - assert(checkSymLevel(tree.symbol, tree.tpe, tree.sourcePos).isEmpty) - tree - case _: Ident => - checkSymLevel(tree.symbol, tree.tpe, tree.sourcePos) match { - case Some(tpRef) => tpRef - case _ => tree - } - case _: TypeTree | _: AppliedTypeTree | _: Apply | _: TypeApply | _: UnApply | Select(_, OuterSelectName(_, _)) => - tree.withType(checkTp(tree.tpe)) - case _: ValOrDefDef | _: Bind => - tree.symbol.info = checkTp(tree.symbol.info) - tree - case _: Template => - checkTp(tree.symbol.owner.asClass.givenSelfType) - tree - case _ => - tree - } - } - - /** Check and heal all named types and this-types in a given type for phase consistency. */ - private def checkType(pos: SourcePosition)(implicit ctx: Context): TypeMap = new TypeMap { - def apply(tp: Type): Type = reporting.trace(i"check type level $tp at $level") { - tp match { - case tp: TypeRef if tp.symbol.isSplice => - if (tp.isTerm) - ctx.error(i"splice outside quotes", pos) - tp - case tp: NamedType => - checkSymLevel(tp.symbol, tp, pos) match { - case Some(tpRef) => tpRef.tpe - case _ => - if (tp.symbol.is(Param)) tp - else mapOver(tp) - } - case tp: ThisType => - assert(checkSymLevel(tp.cls, tp, pos).isEmpty) - mapOver(tp) - case _ => - mapOver(tp) - } - } - } - - /** Check reference to `sym` for phase consistency, where `tp` is the underlying type - * by which we refer to `sym`. If it is an inconsistent type try construct a healed type for it. - * - * @return `None` if the phase is correct or cannot be healed - * `Some(tree)` with the `tree` of the healed type tree for `~implicitly[quoted.Type[T]]` - */ - private def checkSymLevel(sym: Symbol, tp: Type, pos: SourcePosition)(implicit ctx: Context): Option[Tree] = { - val isThis = tp.isInstanceOf[ThisType] - if (!isThis && !sym.is(Param) && sym.maybeOwner.isType) - None - else if (sym.exists && !sym.isStaticOwner && !levelOK(sym)) - tryHeal(sym, tp, pos) - else - None - } - - /** Does the level of `sym` match the current level? - * An exception is made for inline vals in macros. These are also OK if their level - * is one higher than the current level, because on execution such values - * are constant expression trees and we can pull out the constant from the tree. - */ - private def levelOK(sym: Symbol)(implicit ctx: Context): Boolean = levelOf(sym) match { - case Some(l) => - l == level || - level == -1 && ( - sym == defn.TastyReflection_macroContext || - // here we assume that Splicer.canBeSpliced was true before going to level -1, - // this implies that all non-inline arguments are quoted and that the following two cases are checked - // on inline parameters or type parameters. - sym.is(Param) || - sym.isClass // reference to this in inline methods - ) - case None => - !sym.is(Param) || levelOK(sym.owner) - } - - /** Try to heal phase-inconsistent reference to type `T` using a local type definition. - * @return None if successful - * @return Some(msg) if unsuccessful where `msg` is a potentially empty error message - * to be added to the "inconsistent phase" message. - */ - protected def tryHeal(sym: Symbol, tp: Type, pos: SourcePosition)(implicit ctx: Context): Option[Tree] = { - def levelError(errMsg: String) = { - def symStr = - if (!tp.isInstanceOf[ThisType]) sym.show - else if (sym.is(ModuleClass)) sym.sourceModule.show - else i"${sym.name}.this" - ctx.error( - em"""access to $symStr from wrong staging level: - | - the definition is at level ${levelOf(sym).getOrElse(0)}, - | - but the access is at level $level.$errMsg""", pos) - None - } - tp match { - case tp: TypeRef => - if (level == -1) { - assert(ctx.inInlineMethod) - None - } else { - val reqType = defn.QuotedTypeType.appliedTo(tp) - val tag = ctx.typer.inferImplicitArg(reqType, pos.span) - tag.tpe match { - case fail: SearchFailureType => - levelError(i""" - | - | The access would be accepted with the right type tag, but - | ${ctx.typer.missingArgMsg(tag, reqType, "")}""") - case _ => - Some(tag.select(tpnme.splice)) - } - } - case _ => - levelError("") - } - } - - } From e1d31eceeb1e8ffc21d3c2530c1128afca357a5b Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 6 Mar 2019 08:25:32 +0100 Subject: [PATCH 09/17] Fix outdated comments --- .../dotty/tools/dotc/transform/PCPCheckAndHeal.scala | 12 ++++++------ .../src/dotty/tools/dotc/transform/Splicer.scala | 3 ++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala b/compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala index 061f8aa4f2b1..18c4f4fe4042 100644 --- a/compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala +++ b/compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala @@ -28,7 +28,7 @@ import scala.annotation.constructorOnly /** Checks that the Phase Consistency Principle (PCP) holds and heals types. * - * Type healing consists in transforming a phase inconsistent type `T` into `implicitly[Type[T]].unary_~`. + * Type healing consists in transforming a phase inconsistent type `T` into a splice of `implicitly[Type[T]]`. */ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(ictx) { import tpd._ @@ -65,7 +65,7 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages( } else { // level 0 inside an inline definition ctx.error( - "Malformed macro call. The contents of the $ must call a static method and arguments must be quoted or inline.", + "Malformed macro call. The contents of the splice ${...} must call a static method and arguments must be quoted or inline.", splice.sourcePos) splice } @@ -73,8 +73,8 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages( } - /** Add cast to force boundaries where T and ~t (an alias of T) are used to ensure PCP. - * '{ ~(...: T) } --> '{ ~(...: T).asInstanceOf[T] } --> '{ ~(...: T).asInstanceOf[~t] } + /** Add cast to force boundaries where T and $t (an alias of T) are used to ensure PCP. + * '{ ${...: T} } --> '{ ${...: T}.asInstanceOf[T] } --> '{ ${...: T}.asInstanceOf[$t] } */ protected def addSpliceCast(tree: Tree)(implicit ctx: Context): Tree = { val tp = checkType(tree.sourcePos).apply(tree.tpe.widenTermRefExpr) @@ -86,7 +86,7 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages( * that are phase-incorrect can still be healed as follows: * * If `T` is a reference to a type at the wrong level, try to heal it by replacing it with - * `~implicitly[quoted.Type[T]]`. + * `${implicitly[quoted.Type[T]]}`. */ protected def checkLevel(tree: Tree)(implicit ctx: Context): Tree = { def checkTp(tp: Type): Type = checkType(tree.sourcePos).apply(tp) @@ -144,7 +144,7 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages( * by which we refer to `sym`. If it is an inconsistent type try construct a healed type for it. * * @return `None` if the phase is correct or cannot be healed - * `Some(tree)` with the `tree` of the healed type tree for `~implicitly[quoted.Type[T]]` + * `Some(tree)` with the `tree` of the healed type tree for `${implicitly[quoted.Type[T]]}` */ private def checkSymLevel(sym: Symbol, tp: Type, pos: SourcePosition)(implicit ctx: Context): Option[Tree] = { val isThis = tp.isInstanceOf[ThisType] diff --git a/compiler/src/dotty/tools/dotc/transform/Splicer.scala b/compiler/src/dotty/tools/dotc/transform/Splicer.scala index a47e5d5917ad..ee59d7b4f3ee 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicer.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicer.scala @@ -313,7 +313,8 @@ object Splicer { protected final def interpretTree(tree: Tree)(implicit env: Env): Result = tree match { case Apply(TypeApply(fn, _), quoted :: Nil) if fn.symbol == defn.QuotedExpr_apply => val quoted1 = quoted match { - case quoted: Ident if quoted.symbol.is(InlineProxy) && quoted.symbol.is(MethodFlag) => // inline proxy for by-name parameter + case quoted: Ident if quoted.symbol.is(InlineProxy) && quoted.symbol.is(MethodFlag) => + // inline proxy for by-name parameter quoted.symbol.defTree.asInstanceOf[DefDef].rhs case Inlined(EmptyTree, _, quoted) => quoted case _ => quoted From 6be023f5cfb391df9cf76bf2e170997cd36da694 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 6 Mar 2019 08:32:54 +0100 Subject: [PATCH 10/17] Add InlineByNameProxy flag conjunction --- compiler/src/dotty/tools/dotc/core/Flags.scala | 3 +++ compiler/src/dotty/tools/dotc/transform/Splicer.scala | 4 ++-- compiler/src/dotty/tools/dotc/typer/Inliner.scala | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index 31982388bcc7..dca30ce1f82c 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -608,6 +608,9 @@ object Flags { /** An inline method */ final val InlineMethod: FlagConjunction = allOf(Inline, Method) + /** An inline by-name parameter proxy */ + final val InlineByNameProxy: FlagConjunction = allOf(InlineProxy, Method) + /** An inline parameter */ final val InlineParam: FlagConjunction = allOf(Inline, Param) diff --git a/compiler/src/dotty/tools/dotc/transform/Splicer.scala b/compiler/src/dotty/tools/dotc/transform/Splicer.scala index ee59d7b4f3ee..f2af7906cff1 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicer.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicer.scala @@ -8,7 +8,7 @@ import dotty.tools.dotc.ast.tpd import dotty.tools.dotc.ast.Trees._ import dotty.tools.dotc.core.Contexts._ import dotty.tools.dotc.core.Decorators._ -import dotty.tools.dotc.core.Flags.{Method => MethodFlag, _} +import dotty.tools.dotc.core.Flags._ import dotty.tools.dotc.core.NameKinds.FlatName import dotty.tools.dotc.core.Names.{Name, TermName} import dotty.tools.dotc.core.StdNames._ @@ -313,7 +313,7 @@ object Splicer { protected final def interpretTree(tree: Tree)(implicit env: Env): Result = tree match { case Apply(TypeApply(fn, _), quoted :: Nil) if fn.symbol == defn.QuotedExpr_apply => val quoted1 = quoted match { - case quoted: Ident if quoted.symbol.is(InlineProxy) && quoted.symbol.is(MethodFlag) => + case quoted: Ident if quoted.symbol.is(InlineByNameProxy) => // inline proxy for by-name parameter quoted.symbol.defTree.asInstanceOf[DefDef].rhs case Inlined(EmptyTree, _, quoted) => quoted diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index afe13a003972..d4a1ced3c52f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -253,7 +253,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { var inlineFlag = InlineProxy if (paramtp.hasAnnotation(defn.InlineParamAnnot)) inlineFlag |= Inline val (bindingFlags, bindingType) = - if (isByName) (Method | InlineProxy, ExprType(argtpe.widen)) + if (isByName) (InlineByNameProxy.toTermFlags, ExprType(argtpe.widen)) else (inlineFlag, argtpe.widen) val boundSym = newSym(name, bindingFlags, bindingType).asTerm val binding = { From 1f78394ed13dd111b49b884ea345f6ea449e56e9 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 6 Mar 2019 08:36:07 +0100 Subject: [PATCH 11/17] Update comment --- .../tools/dotc/transform/ReifyQuotes.scala | 26 +++++++++---------- .../dotty/tools/dotc/transform/Staging.scala | 2 +- .../tools/dotc/typer/PrepareInlineable.scala | 4 +-- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index b3ef77a3789f..082292945856 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -36,7 +36,7 @@ import scala.annotation.constructorOnly * val x1 = ??? * val x2 = ??? * ... - * ~{ ... '{ ... x1 ... x2 ...} ... } + * ${ ... '{ ... x1 ... x2 ...} ... } * ... * } * ``` @@ -56,12 +56,12 @@ import scala.annotation.constructorOnly * val x1$1 = args(0).asInstanceOf[Expr[T]] * val x2$1 = args(1).asInstanceOf[Expr[T]] // can be asInstanceOf[Type[T]] * ... - * { ... '{ ... x1$1.unary_~ ... x2$1.unary_~ ...} ... } + * { ... '{ ... ${x1$1} ... ${x2$1} ...} ... } * } * ) * ) * ``` - * and then performs the same transformation on `'{ ... x1$1.unary_~ ... x2$1.unary_~ ...}`. + * and then performs the same transformation on `'{ ... ${x1$1} ... ${x2$1} ...}`. * */ class ReifyQuotes extends MacroTransform { @@ -95,9 +95,9 @@ class ReifyQuotes extends MacroTransform { * Returns a version of the reference that needs to be used in its place. * '{ * val x = ??? - * { ... '{ ... x ... } ... }.unary_~ + * ${ ... '{ ... x ... } ... } * } - * Eta expanding the `x` in `{ ... '{ ... x ... } ... }.unary_~` will return a `x$1.unary_~` for which the `x$1` + * Eta expanding the `x` in `${ ... '{ ... x ... } ... }` will return a `${x$1}` for which the `x$1` * be created by some outer reifier. * This transformation is only applied to definitions at staging level 1. * See `isCaptured`. @@ -113,11 +113,11 @@ class ReifyQuotes extends MacroTransform { new QuoteReifier(this, capturers, nestedEmbedded, ctx.owner)(ctx) } - /** Assuming contains types `.unary_~, ..., .unary_~`, the expression + /** Assuming contains types `${}, ..., ${}`, the expression * - * { type = .unary_~ + * { type = ${} * ... - * type = .unary_~ + * type = ${} * * } * @@ -133,7 +133,7 @@ class ReifyQuotes extends MacroTransform { val alias = ctx.typeAssigner.assignType(untpd.TypeBoundsTree(rhs, rhs), rhs, rhs) val local = ctx.newSymbol( owner = ctx.owner, - name = UniqueName.fresh((splicedTree.symbol.name.toString + "$_~").toTermName).toTypeName, + name = UniqueName.fresh((splicedTree.symbol.name.toString + "$_").toTermName).toTypeName, flags = Synthetic, info = TypeAlias(splicedTree.tpe.select(tpnme.splice)), coord = spliced.termSymbol.coord).asType @@ -178,7 +178,7 @@ class ReifyQuotes extends MacroTransform { else body match { case body: RefTree if isCaptured(body.symbol, level + 1) => // Optimization: avoid the full conversion when capturing `x` - // in '{ x } to '{ x$1.unary_~ } and go directly to `x$1` + // in '{ x } to '{ ${x$1} } and go directly to `x$1` capturers(body.symbol)(body) case _=> val (body1, splices) = nested(isQuote = true).splitQuote(body)(quoteContext) @@ -246,7 +246,7 @@ class ReifyQuotes extends MacroTransform { * '{ * val x = ??? * val y = ??? - * { ... '{ ... x .. y ... } ... }.unary_~ + * ${ ... '{ ... x .. y ... } ... } * } * then the spliced subexpression * { ... '{ ... x ... y ... } ... } @@ -254,7 +254,7 @@ class ReifyQuotes extends MacroTransform { * (args: Seq[Any]) => { * val x$1 = args(0).asInstanceOf[Expr[Any]] // or .asInstanceOf[Type[Any]] * val y$1 = args(1).asInstanceOf[Expr[Any]] // or .asInstanceOf[Type[Any]] - * { ... '{ ... x$1.unary_~ ... y$1.unary_~ ... } ... } + * { ... '{ ... ${x$1} ... ${y$1} ... } ... } * } * * See: `capture` @@ -347,7 +347,7 @@ class ReifyQuotes extends MacroTransform { reporting.trace(i"Reifier.transform $tree at $level", show = true) { tree match { case TypeApply(Select(spliceTree @ Spliced(_), _), tp) if tree.symbol.isTypeCast => - // Splice term which should be in the form `x.unary_~.asInstanceOf[T]` where T is an artefact of + // Splice term which should be in the form `${x}.asInstanceOf[T]` where T is an artifact of // typer to allow pickling/unpickling phase consistent types transformSplice(spliceTree) diff --git a/compiler/src/dotty/tools/dotc/transform/Staging.scala b/compiler/src/dotty/tools/dotc/transform/Staging.scala index d8468ed16db3..43eceac8b569 100644 --- a/compiler/src/dotty/tools/dotc/transform/Staging.scala +++ b/compiler/src/dotty/tools/dotc/transform/Staging.scala @@ -28,7 +28,7 @@ import scala.annotation.constructorOnly /** Checks that the Phase Consistency Principle (PCP) holds and heals types. * - * Type healing consists in transforming a phase inconsistent type `T` into `implicitly[Type[T]].unary_~`. + * Type healing consists in transforming a phase inconsistent type `T` into `${ implicitly[Type[T]] }`. */ class Staging extends MacroTransform { import tpd._ diff --git a/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala b/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala index 6b07c75e4d44..123f4efaaaf2 100644 --- a/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala +++ b/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala @@ -288,8 +288,8 @@ object PrepareInlineable { ctx.error( """Malformed macro. | - |Expected the ~ to be at the top of the RHS: - | inline def foo(inline x: X, ..., y: Y): Int = ~impl(x, ... '(y)) + |Expected the splice ${...} to be at the top of the RHS: + | inline def foo(inline x: X, ..., y: Y): Int = ${impl(x, ... '{y}}) | | * The contents of the splice must call a static method | * All arguments must be quoted or inline From 4ee9ebfd302ee7a33f87e9e335602a03c2abb683 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 6 Mar 2019 08:44:05 +0100 Subject: [PATCH 12/17] Use `ctx.isAfterTyper` --- compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala b/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala index 123f4efaaaf2..e361f2137f89 100644 --- a/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala +++ b/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala @@ -253,7 +253,7 @@ object PrepareInlineable { } def checkInlineMacro(sym: Symbol, rhs: Tree, pos: SourcePosition)(implicit ctx: Context) = { - if (ctx.phase.isTyper) { + if (!ctx.isAfterTyper) { /** InlineSplice is used to detect cases where the expansion * consists of a (possibly multiple & nested) block or a sole expression. From ec954c8da2f56d34edfe1b0f508434ed71b3fe77 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 6 Mar 2019 09:13:32 +0100 Subject: [PATCH 13/17] Only mark inline method with top level splices as macros --- compiler/src/dotty/tools/dotc/typer/Applications.scala | 7 +++++-- tests/pos/quoted-inline-quote.scala | 4 ++++ 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 tests/pos/quoted-inline-quote.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 428df85a53ff..984648f77410 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -199,8 +199,11 @@ object Applications { else if (c.owner.isInlineMethod) c.owner.setFlag(Macro) else if (!c.outer.owner.is(Package)) markAsMacro(c.outer) val sym = tree.symbol - if (sym.isSplice || sym.isQuote) { - markAsMacro(ctx) + if (sym.isSplice) { + if (StagingContext.level == 0) + markAsMacro(ctx) + ctx.compilationUnit.needsStaging = true + } else if (sym.isQuote) { ctx.compilationUnit.needsStaging = true } diff --git a/tests/pos/quoted-inline-quote.scala b/tests/pos/quoted-inline-quote.scala new file mode 100644 index 000000000000..70212ca52582 --- /dev/null +++ b/tests/pos/quoted-inline-quote.scala @@ -0,0 +1,4 @@ +class Foo { + inline def foo(x: quoted.Expr[String]) = '{ println(${x}) } + foo('{"abc"}) +} \ No newline at end of file From 00a40e2de02bf04729f4b2ba2b0cf2ce43fd5093 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 6 Mar 2019 09:42:39 +0100 Subject: [PATCH 14/17] Add TreeOps.setDefTree --- compiler/src/dotty/tools/dotc/ast/tpd.scala | 7 +++++++ .../dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 4 +--- compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala | 4 +--- compiler/src/dotty/tools/dotc/typer/Inliner.scala | 10 +++------- compiler/src/dotty/tools/dotc/typer/Typer.scala | 8 ++------ 5 files changed, 14 insertions(+), 19 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index 8b6fcfe03dad..bcf5b08ec2fd 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -1001,6 +1001,13 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { foreachSubTree { tree => if (f(tree)) buf += tree } buf.toList } + + /** Set this tree as the `defTree` of its symbol and return this tree */ + def setDefTree(implicit ctx: Context): ThisTree = { + val sym = tree.symbol + if (sym.exists) sym.defTree = tree + tree + } } /** Map Inlined nodes, NamedArgs, Blocks with no statements and local references to underlying arguments. diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 052497dad583..ef22e72bc82d 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -851,8 +851,6 @@ class TreeUnpickler(reader: TastyReader, sym.info = ta.avoidPrivateLeaks(sym, tree.sourcePos) } - sym.defTree = tree - if (ctx.mode.is(Mode.ReadComments)) { assert(ctx.docCtx.isDefined, "Mode is `ReadComments`, but no `docCtx` is set.") commentUnpicklerOpt.foreach { commentUnpickler => @@ -862,7 +860,7 @@ class TreeUnpickler(reader: TastyReader, } } - tree + tree.setDefTree } private def readTemplate(implicit ctx: Context): Template = { diff --git a/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala b/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala index 0e5d0bf74e47..0156e0c6f340 100644 --- a/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala +++ b/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala @@ -47,9 +47,7 @@ abstract class Lifter { var liftedType = expr.tpe.widen if (liftedFlags.is(Method)) liftedType = ExprType(liftedType) val lifted = ctx.newSymbol(ctx.owner, name, liftedFlags | Synthetic, liftedType, coord = spanCoord(expr.span)) - val ddef = liftedDef(lifted, expr).withSpan(expr.span) - lifted.defTree = ddef - defs += ddef + defs += liftedDef(lifted, expr).withSpan(expr.span).setDefTree ref(lifted.termRef).withSpan(expr.span.focus) } diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index d4a1ced3c52f..e47cd0f24ac2 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -260,8 +260,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { if (isByName) DefDef(boundSym, arg.changeOwner(ctx.owner, boundSym)) else ValDef(boundSym, arg) }.withSpan(boundSym.span) - boundSym.defTree = binding - bindingsBuf += binding + bindingsBuf += binding.setDefTree binding } @@ -313,9 +312,8 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { ref(rhsClsSym.sourceModule) else inlineCallPrefix - val binding = ValDef(selfSym.asTerm, rhs).withSpan(selfSym.span) + val binding = ValDef(selfSym.asTerm, rhs).withSpan(selfSym.span).setDefTree bindingsBuf += binding - selfSym.defTree = binding inlining.println(i"proxy at $level: $selfSym = ${bindingsBuf.last}") lastSelf = selfSym lastLevel = level @@ -490,11 +488,9 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { // The normalized bindings collected in `bindingsBuf` bindingsBuf.transform { binding => - val transformedBinding = reducer.normalizeBinding(binding)(inlineCtx) // Set trees to symbols allow macros to see the definition tree. // This is used by `underlyingArgument`. - transformedBinding.symbol.defTree = transformedBinding - transformedBinding + reducer.normalizeBinding(binding)(inlineCtx).setDefTree } // Run a typing pass over the inlined tree. See InlineTyper for details. diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 99136f1a01ee..da69b30f354e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1489,11 +1489,9 @@ class Typer extends Namer val vdef1 = assignType(cpy.ValDef(vdef)(name, tpt1, rhs1), sym) if (sym.is(Inline, butNot = DeferredOrTermParamOrAccessor)) checkInlineConformant(rhs1, isFinal = sym.is(Final), em"right-hand side of inline $sym") - if (sym.exists) - sym.defTree = vdef1 patchIfLazy(vdef1) patchFinalVals(vdef1) - vdef1 + vdef1.setDefTree } /** Add a @volitile to lazy vals when rewriting from Scala2 */ @@ -1564,9 +1562,7 @@ class Typer extends Namer for (param <- tparams1 ::: vparamss1.flatten) checkRefsLegal(param, sym.owner, (name, sym) => sym.is(TypeParam), "secondary constructor") - val ddef1 = assignType(cpy.DefDef(ddef)(name, tparams1, vparamss1, tpt1, rhs1), sym) - sym.defTree = ddef1 - ddef1 + assignType(cpy.DefDef(ddef)(name, tparams1, vparamss1, tpt1, rhs1), sym).setDefTree //todo: make sure dependent method types do not depend on implicits or by-name params } From 3071b31ad067fbc02378b9d560cbf6ffa25675e4 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 6 Mar 2019 10:22:45 +0100 Subject: [PATCH 15/17] Refactor type healing conditions --- .../dotty/tools/dotc/transform/PCPCheckAndHeal.scala | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala b/compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala index 18c4f4fe4042..be24641ba39c 100644 --- a/compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala +++ b/compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala @@ -148,9 +148,13 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages( */ private def checkSymLevel(sym: Symbol, tp: Type, pos: SourcePosition)(implicit ctx: Context): Option[Tree] = { val isThis = tp.isInstanceOf[ThisType] - if (!isThis && !sym.is(Param) && sym.maybeOwner.isType) - None - else if (sym.exists && !sym.isStaticOwner && !levelOK(sym)) + val shouldTryHealable = { + // TODO move logic to levelOK + tp.isInstanceOf[ThisType] || + sym.is(Param) || + !sym.maybeOwner.isType + } + if (sym.exists && !sym.isStaticOwner && !levelOK(sym) && shouldTryHealable) tryHeal(sym, tp, pos) else None From cb4497a25469e618bbf4519cb4da9187ce0a3ed0 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 7 Mar 2019 13:21:46 +0100 Subject: [PATCH 16/17] Rework healing condition --- .../dotty/tools/dotc/transform/PCPCheckAndHeal.scala | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala b/compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala index be24641ba39c..373c107109f7 100644 --- a/compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala +++ b/compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala @@ -147,14 +147,10 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages( * `Some(tree)` with the `tree` of the healed type tree for `${implicitly[quoted.Type[T]]}` */ private def checkSymLevel(sym: Symbol, tp: Type, pos: SourcePosition)(implicit ctx: Context): Option[Tree] = { - val isThis = tp.isInstanceOf[ThisType] - val shouldTryHealable = { - // TODO move logic to levelOK - tp.isInstanceOf[ThisType] || - sym.is(Param) || - !sym.maybeOwner.isType - } - if (sym.exists && !sym.isStaticOwner && !levelOK(sym) && shouldTryHealable) + /** Is a reference to a class but not `this.type` */ + def isClassRef = sym.isClass && !tp.isInstanceOf[ThisType] + + if (sym.exists && !sym.isStaticOwner && !isClassRef && !levelOK(sym)) tryHeal(sym, tp, pos) else None From cc3a8dd30cd322add83353b9988618e9e7e6ae3f Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 7 Mar 2019 13:34:56 +0100 Subject: [PATCH 17/17] Disable macro accessing comments Macros seem to be expanded before the comments are registered in the symbol. Comments where added to be used while loadind a TASTy file trough the tasty.Reflection API, hence this use case is not critical. See #6032 --- tests/{ => pending}/run/tasty-comments/quoted_1.scala | 0 tests/{ => pending}/run/tasty-comments/quoted_2.scala | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename tests/{ => pending}/run/tasty-comments/quoted_1.scala (100%) rename tests/{ => pending}/run/tasty-comments/quoted_2.scala (100%) diff --git a/tests/run/tasty-comments/quoted_1.scala b/tests/pending/run/tasty-comments/quoted_1.scala similarity index 100% rename from tests/run/tasty-comments/quoted_1.scala rename to tests/pending/run/tasty-comments/quoted_1.scala diff --git a/tests/run/tasty-comments/quoted_2.scala b/tests/pending/run/tasty-comments/quoted_2.scala similarity index 100% rename from tests/run/tasty-comments/quoted_2.scala rename to tests/pending/run/tasty-comments/quoted_2.scala