From 92ff00a87055e1b6d99f9393526d30e9ad2e9b66 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 4 Jan 2023 11:21:16 +0000 Subject: [PATCH 1/5] Use polyfunction for quote hole contents This avoids leaking references to types defined within the quotes when the contents of the holes are extracted from the quote. --- .../tools/dotc/transform/ExpandSAMs.scala | 2 +- .../tools/dotc/transform/PickleQuotes.scala | 40 ++++++++-- .../dotty/tools/dotc/transform/Splicing.scala | 80 +++++++++++++++---- .../tools/dotc/transform/TreeChecker.scala | 27 ++++++- tests/pos-macros/captured-type.scala | 6 ++ 5 files changed, 131 insertions(+), 24 deletions(-) create mode 100644 tests/pos-macros/captured-type.scala diff --git a/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala b/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala index 0bfc444e0997..b9abcb9cffdc 100644 --- a/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala +++ b/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala @@ -48,7 +48,7 @@ class ExpandSAMs extends MiniPhase: tpt.tpe match { case NoType => tree // it's a plain function - case tpe if defn.isContextFunctionType(tpe) => + case tpe if defn.isFunctionOrPolyType(tpe) => tree case tpe @ SAMType(_) if tpe.isRef(defn.PartialFunctionClass) => val tpe1 = checkRefinements(tpe, fn) diff --git a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala index 62174c806f09..16ae7a3afc77 100644 --- a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala @@ -319,17 +319,28 @@ object PickleQuotes { defn.QuotedExprClass.typeRef.appliedTo(defn.AnyType)), args => val cases = termSplices.map { case (splice, idx) => - val defn.FunctionOf(argTypes, defn.FunctionOf(quotesType :: _, _, _), _) = splice.tpe: @unchecked + val (typeParamCount, argTypes, quotesType) = splice.tpe match + case defn.FunctionOf(argTypes, defn.FunctionOf(quotesType :: _, _, _), _) => (0, argTypes, quotesType) + case RefinedType(polyFun, nme.apply, pt @ PolyType(tparams, _)) if polyFun.typeSymbol.derivesFrom(defn.PolyFunctionClass) => + pt.instantiate(pt.paramInfos.map(_.hi)) match + case MethodTpe(_, argTypes, defn.FunctionOf(quotesType :: _, _, _)) => + (tparams.size, argTypes, quotesType) + val rhs = { val spliceArgs = argTypes.zipWithIndex.map { (argType, i) => args(1).select(nme.apply).appliedTo(Literal(Constant(i))).asInstance(argType) } val Block(List(ddef: DefDef), _) = splice: @unchecked - // TODO: beta reduce inner closure? Or wait until BetaReduce phase? - BetaReduce( - splice - .select(nme.apply).appliedToArgs(spliceArgs)) - .select(nme.apply).appliedTo(args(2).asInstance(quotesType)) + + val typeArgs = ddef.symbol.info match + case pt: PolyType => pt.paramInfos + case _ => Nil + + val sel1 = splice.changeOwner(ddef.symbol.owner, ctx.owner).select(nme.apply) + val appTpe = if typeParamCount == 0 then sel1 else sel1.appliedToTypes(List.fill(typeParamCount)(defn.AnyType)) + val app1 = appTpe.appliedToArgs(spliceArgs) + val sel2 = app1.select(nme.apply) + sel2.appliedTo(args(2).asInstance(quotesType)) } CaseDef(Literal(Constant(idx)), EmptyTree, rhs) } @@ -338,8 +349,20 @@ object PickleQuotes { case _ => Match(args(0).annotated(New(ref(defn.UncheckedAnnot.typeRef))), cases) ) + def dealiasSplicedTypes(tp: Type) = new TypeMap { + def apply(tp: Type): Type = tp match + case tp: TypeRef if tp.typeSymbol.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot) => + val TypeAlias(alias) = tp.info: @unchecked + alias + case tp1 => mapOver(tp) + }.apply(tp) + + val adaptedType = + if isType then dealiasSplicedTypes(originalTp) + else originalTp + val quoteClass = if isType then defn.QuotedTypeClass else defn.QuotedExprClass - val quotedType = quoteClass.typeRef.appliedTo(originalTp) + val quotedType = quoteClass.typeRef.appliedTo(adaptedType) val lambdaTpe = MethodType(defn.QuotesClass.typeRef :: Nil, quotedType) val unpickleMeth = if isType then defn.QuoteUnpickler_unpickleTypeV2 @@ -347,9 +370,10 @@ object PickleQuotes { val unpickleArgs = if isType then List(pickledQuoteStrings, types) else List(pickledQuoteStrings, types, termHoles) + quotes .asInstance(defn.QuoteUnpicklerClass.typeRef) - .select(unpickleMeth).appliedToType(originalTp) + .select(unpickleMeth).appliedToType(adaptedType) .appliedToArgs(unpickleArgs).withSpan(body.span) } diff --git a/compiler/src/dotty/tools/dotc/transform/Splicing.scala b/compiler/src/dotty/tools/dotc/transform/Splicing.scala index bb82fba32a7c..8f344c458eaa 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicing.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicing.scala @@ -24,6 +24,7 @@ import dotty.tools.dotc.config.ScalaRelease.* import dotty.tools.dotc.staging.QuoteContext.* import dotty.tools.dotc.staging.StagingLevel.* import dotty.tools.dotc.staging.QuoteTypeTags +import dotty.tools.dotc.staging.DirectTypeOf import scala.annotation.constructorOnly @@ -44,12 +45,13 @@ object Splicing: * contains the quotes with references to all cross-quote references. There are some special rules * for references in the LHS of assignments and cross-quote method references. * - * In the following code example `x1` and `x2` are cross-quote references. + * In the following code example `x1`, `x2` and `U` are cross-quote references. * ``` * '{ ... - * val x1: T1 = ??? - * val x2: T2 = ??? - * ${ (q: Quotes) ?=> f('{ g(x1, x2) }) }: T3 + * type U + * val x1: T = ??? + * val x2: U = ??? + * ${ (q: Quotes) ?=> f('{ g[U](x1, x2) }) }: T3 * } * ``` * @@ -60,15 +62,15 @@ object Splicing: * '{ ... * val x1: T1 = ??? * val x2: T2 = ??? - * {{{ 0 | T3 | x1, x2 | - * (x1$: Expr[T1], x2$: Expr[T2]) => // body of this lambda does not contain references to x1 or x2 - * (q: Quotes) ?=> f('{ g(${x1$}, ${x2$}) }) + * {{{ 0 | T3 | U, x1, x2 | + * [U$1] => (U$2: Type[U$1], x1$: Expr[T], x2$: Expr[U$1]) => // body of this lambda does not contain references to U, x1 or x2 + * (q: Quotes) ?=> f('{ @SplicedType type U$3 = [[[ 0 | U$2 | | U$1 ]]]; g[U$3](${x1$}, ${x2$}) }) * * }}} * } * ``` * - * and then performs the same transformation on `'{ g(${x1$}, ${x2$}) }`. + * and then performs the same transformation on `'{ @SplicedType type U$3 = [[[ 0 | U$2 | | U$1 ]]]; g[U$3](${x1$}, ${x2$}) }`. * */ class Splicing extends MacroTransform: @@ -132,7 +134,7 @@ class Splicing extends MacroTransform: case None => val holeIdx = numHoles numHoles += 1 - val hole = tpd.Hole(false, holeIdx, Nil, ref(qual), TypeTree(tp)) + val hole = tpd.Hole(false, holeIdx, Nil, ref(qual), TypeTree(tp.dealias)) typeHoles.put(qual.symbol, hole) hole cpy.TypeDef(tree)(rhs = hole) @@ -154,7 +156,7 @@ class Splicing extends MacroTransform: private def transformAnnotations(tree: DefTree)(using Context): Unit = tree.symbol.annotations = tree.symbol.annotations.mapconserve { annot => - val newAnnotTree = transform(annot.tree)(using ctx.withOwner(tree.symbol)) + val newAnnotTree = transform(annot.tree) if (annot.tree == newAnnotTree) annot else ConcreteAnnotation(newAnnotTree) } @@ -198,10 +200,57 @@ class Splicing extends MacroTransform: val newTree = transform(tree) val (refs, bindings) = refBindingMap.values.toList.unzip val bindingsTypes = bindings.map(_.termRef.widenTermRefExpr) - val methType = MethodType(bindingsTypes, newTree.tpe) + val types = bindingsTypes.collect { + case AppliedType(tycon, List(arg: TypeRef)) if tycon.derivesFrom(defn.QuotedTypeClass) => arg + } + val newTypeParams = types.map { tpe => + newSymbol( + spliceOwner, + UniqueName.fresh(tpe.symbol.name.toTypeName), + Param, + TypeBounds.empty + ) + } + val methType = + if types.nonEmpty then + PolyType(types.map(tp => UniqueName.fresh(tp.symbol.name.toTypeName)))( + pt => types.map(_ => TypeBounds.empty), + pt => { + val tpParamMap = new TypeMap { + private val mapping = types.map(_.typeSymbol).zip(pt.paramRefs).toMap + def apply(tp: Type): Type = tp match + case tp: TypeRef => mapping.getOrElse(tp.typeSymbol, tp) + case tp => mapOver(tp) + } + MethodType(bindingsTypes.map(tpParamMap), tpParamMap(newTree.tpe)) + } + ) + else MethodType(bindingsTypes, newTree.tpe) val meth = newSymbol(spliceOwner, nme.ANON_FUN, Synthetic | Method, methType) - val ddef = DefDef(meth, List(bindings), newTree.tpe, newTree.changeOwner(ctx.owner, meth)) - val fnType = defn.FunctionType(bindings.size, isContextual = false).appliedTo(bindingsTypes :+ newTree.tpe) + + def substituteTypes(tree: Tree): Tree = + if types.nonEmpty then + val typeIndex = types.zipWithIndex.toMap + TreeTypeMap( + typeMap = new TypeMap { + def apply(tp: Type): Type = tp match + case tp @ TypeRef(x: TermRef, _) if tp.symbol == defn.QuotedType_splice => tp + case tp: TypeRef if tp.typeSymbol.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot) => tp + case tp: TypeRef => + typeIndex.get(tp) match + case Some(idx) => newTypeParams(idx).typeRef + case None => mapOver(tp) + case _ => mapOver(tp) + } + ).transform(tree) + else tree + val paramss = + if types.nonEmpty then List(newTypeParams, bindings) + else List(bindings) + val ddef = substituteTypes(DefDef(meth, paramss, newTree.tpe, newTree.changeOwner(ctx.owner, meth))) + val fnType = + if types.isEmpty then defn.FunctionType(bindings.size, isContextual = false).appliedTo(bindingsTypes :+ newTree.tpe) + else RefinedType(defn.PolyFunctionType, nme.apply, methType) val closure = Block(ddef :: Nil, Closure(Nil, ref(meth), TypeTree(fnType))) tpd.Hole(true, holeIdx, refs, closure, TypeTree(tpe)) @@ -255,6 +304,9 @@ class Splicing extends MacroTransform: if tree.symbol == defn.QuotedTypeModule_of && containsCapturedType(tpt.tpe) => val newContent = capturedPartTypes(tpt) newContent match + case DirectTypeOf.Healed(termRef) => + // Optimization: `quoted.Type.of[@SplicedType type T = x.Underlying; T](quotes)` --> `x` + tpd.ref(termRef).withSpan(tpt.span) case block: Block => inContext(ctx.withSource(tree.source)) { Apply(TypeApply(typeof, List(newContent)), List(quotes)).withSpan(tree.span) @@ -354,7 +406,7 @@ class Splicing extends MacroTransform: private def newQuotedTypeClassBinding(tpe: Type)(using Context) = newSymbol( spliceOwner, - UniqueName.fresh(nme.Type).toTermName, + UniqueName.fresh(tpe.typeSymbol.name.toTermName), Param, defn.QuotedTypeClass.typeRef.appliedTo(tpe), ) diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index f9240d6091c4..dabe16ca16e0 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -670,6 +670,9 @@ object TreeChecker { else assert(tpt.typeOpt =:= pt) // Check that the types of the args conform to the types of the contents of the hole + val typeArgsTypes = args.collect { case arg if arg.isType => + arg.typeOpt + } val argQuotedTypes = args.map { arg => if arg.isTerm then val tpe = arg.typeOpt.widenTermRefExpr match @@ -687,7 +690,29 @@ object TreeChecker { val contextualResult = defn.FunctionOf(List(defn.QuotesClass.typeRef), expectedResultType, isContextual = true) val expectedContentType = - defn.FunctionOf(argQuotedTypes, contextualResult) + if typeArgsTypes.isEmpty then defn.FunctionOf(argQuotedTypes, contextualResult) + else RefinedType(defn.PolyFunctionType, nme.apply, PolyType(typeArgsTypes.map(_ => TypeBounds.empty))(pt => + val tpParamMap = new TypeMap { + private val mapping = typeArgsTypes.map(_.typeSymbol).zip(pt.paramRefs).toMap + def apply(tp: Type): Type = tp match + case tp: TypeRef => mapping.getOrElse(tp.typeSymbol, tp) + case tp => mapOver(tp) + } + MethodType( + args.zipWithIndex.map { case (arg, idx) => + if arg.isTerm then + val tpe = arg.typeOpt.widenTermRefExpr match + case _: MethodicType => + // Special erasure for captured function references + // See `SpliceTransformer.transformCapturedApplication` + defn.AnyType + case tpe => tpe + defn.QuotedExprClass.typeRef.appliedTo(tpParamMap(tpe)) + else defn.QuotedTypeClass.typeRef.appliedTo(tpParamMap(arg.typeOpt)) + }, + tpParamMap(contextualResult)) + ) + ) assert(content.typeOpt =:= expectedContentType, i"unexpected content of hole\nexpected: ${expectedContentType}\nwas: ${content.typeOpt}") tree1 diff --git a/tests/pos-macros/captured-type.scala b/tests/pos-macros/captured-type.scala new file mode 100644 index 000000000000..54e59aec7a54 --- /dev/null +++ b/tests/pos-macros/captured-type.scala @@ -0,0 +1,6 @@ +import scala.quoted.* + +object Foo: + def baz(using Quotes): Unit = '{ + def f[T](x: T): T = ${ identity('{ x: T }) } + } From 134152aafb97c231a7b7d4cda4da6eb263f694b4 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 11 Apr 2023 16:28:06 +0200 Subject: [PATCH 2/5] Update documentation --- compiler/src/dotty/tools/dotc/transform/Splicing.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/Splicing.scala b/compiler/src/dotty/tools/dotc/transform/Splicing.scala index 8f344c458eaa..9df95fd560c3 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicing.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicing.scala @@ -39,7 +39,7 @@ object Splicing: * * After this phase we have the invariant where all splices have the following shape * ``` - * {{{ | | * | (*) => }}} + * {{{ | | * | [*] => (*) => }}} * ``` * where `` does not contain any free references to quoted definitions and `*` * contains the quotes with references to all cross-quote references. There are some special rules @@ -186,7 +186,7 @@ class Splicing extends MacroTransform: * ``` * is transformed into * ```scala - * {{{ | T2 | x, X | (x$1: Expr[T1], X$1: Type[X]) => (using Quotes) ?=> {... ${x$1} ... X$1.Underlying ...} }}} + * {{{ | T2 | x, X | [X$2] => (x$1: Expr[T1], X$1: Type[X$2]) => (using Quotes) ?=> {... ${x$1} ... X$1.Underlying ...} }}} * ``` */ private class SpliceTransformer(spliceOwner: Symbol, isCaptured: Symbol => Boolean) extends Transformer: From b931dd295b53f715270349496d902e904899ab5b Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 11 Apr 2023 16:28:28 +0200 Subject: [PATCH 3/5] Rename `types` to `capturedTypes` in Splicing --- .../dotty/tools/dotc/transform/Splicing.scala | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/Splicing.scala b/compiler/src/dotty/tools/dotc/transform/Splicing.scala index 9df95fd560c3..6f3a3828e01d 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicing.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicing.scala @@ -200,10 +200,10 @@ class Splicing extends MacroTransform: val newTree = transform(tree) val (refs, bindings) = refBindingMap.values.toList.unzip val bindingsTypes = bindings.map(_.termRef.widenTermRefExpr) - val types = bindingsTypes.collect { + val capturedTypes = bindingsTypes.collect { case AppliedType(tycon, List(arg: TypeRef)) if tycon.derivesFrom(defn.QuotedTypeClass) => arg } - val newTypeParams = types.map { tpe => + val newTypeParams = capturedTypes.map { tpe => newSymbol( spliceOwner, UniqueName.fresh(tpe.symbol.name.toTypeName), @@ -212,12 +212,12 @@ class Splicing extends MacroTransform: ) } val methType = - if types.nonEmpty then - PolyType(types.map(tp => UniqueName.fresh(tp.symbol.name.toTypeName)))( - pt => types.map(_ => TypeBounds.empty), + if capturedTypes.nonEmpty then + PolyType(capturedTypes.map(tp => UniqueName.fresh(tp.symbol.name.toTypeName)))( + pt => capturedTypes.map(_ => TypeBounds.empty), pt => { val tpParamMap = new TypeMap { - private val mapping = types.map(_.typeSymbol).zip(pt.paramRefs).toMap + private val mapping = capturedTypes.map(_.typeSymbol).zip(pt.paramRefs).toMap def apply(tp: Type): Type = tp match case tp: TypeRef => mapping.getOrElse(tp.typeSymbol, tp) case tp => mapOver(tp) @@ -229,8 +229,8 @@ class Splicing extends MacroTransform: val meth = newSymbol(spliceOwner, nme.ANON_FUN, Synthetic | Method, methType) def substituteTypes(tree: Tree): Tree = - if types.nonEmpty then - val typeIndex = types.zipWithIndex.toMap + if capturedTypes.nonEmpty then + val typeIndex = capturedTypes.zipWithIndex.toMap TreeTypeMap( typeMap = new TypeMap { def apply(tp: Type): Type = tp match @@ -245,11 +245,11 @@ class Splicing extends MacroTransform: ).transform(tree) else tree val paramss = - if types.nonEmpty then List(newTypeParams, bindings) + if capturedTypes.nonEmpty then List(newTypeParams, bindings) else List(bindings) val ddef = substituteTypes(DefDef(meth, paramss, newTree.tpe, newTree.changeOwner(ctx.owner, meth))) val fnType = - if types.isEmpty then defn.FunctionType(bindings.size, isContextual = false).appliedTo(bindingsTypes :+ newTree.tpe) + if capturedTypes.isEmpty then defn.FunctionType(bindings.size, isContextual = false).appliedTo(bindingsTypes :+ newTree.tpe) else RefinedType(defn.PolyFunctionType, nme.apply, methType) val closure = Block(ddef :: Nil, Closure(Nil, ref(meth), TypeTree(fnType))) tpd.Hole(true, holeIdx, refs, closure, TypeTree(tpe)) From 7c2d5e91ccf290804632a55ca292d74f75e09c8d Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 11 Apr 2023 16:57:52 +0200 Subject: [PATCH 4/5] Add documentation --- .../tools/dotc/transform/PickleQuotes.scala | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala index 16ae7a3afc77..722c685d4b96 100644 --- a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala @@ -331,16 +331,15 @@ object PickleQuotes { args(1).select(nme.apply).appliedTo(Literal(Constant(i))).asInstance(argType) } val Block(List(ddef: DefDef), _) = splice: @unchecked - - val typeArgs = ddef.symbol.info match - case pt: PolyType => pt.paramInfos - case _ => Nil - - val sel1 = splice.changeOwner(ddef.symbol.owner, ctx.owner).select(nme.apply) - val appTpe = if typeParamCount == 0 then sel1 else sel1.appliedToTypes(List.fill(typeParamCount)(defn.AnyType)) - val app1 = appTpe.appliedToArgs(spliceArgs) - val sel2 = app1.select(nme.apply) - sel2.appliedTo(args(2).asInstance(quotesType)) + val quotes = args(2).asInstance(quotesType) + val dummyTargs = List.fill(typeParamCount)(defn.AnyType) + // Generate: .apply[*](*).apply() + splice.changeOwner(ddef.symbol.owner, ctx.owner) + .select(nme.apply) + .appliedToTypes(dummyTargs) + .appliedToArgs(spliceArgs) + .select(nme.apply) + .appliedTo(quotes) } CaseDef(Literal(Constant(idx)), EmptyTree, rhs) } From cf868615248f87d3fa57063b7187e315f05db148 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 11 Apr 2023 17:05:36 +0200 Subject: [PATCH 5/5] Only check type of content if it exists --- .../tools/dotc/transform/TreeChecker.scala | 89 ++++++++++--------- 1 file changed, 45 insertions(+), 44 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index dabe16ca16e0..79ca6e6df2d8 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -669,51 +669,52 @@ object TreeChecker { if isTermHole then assert(tpt.typeOpt <:< pt) else assert(tpt.typeOpt =:= pt) - // Check that the types of the args conform to the types of the contents of the hole - val typeArgsTypes = args.collect { case arg if arg.isType => - arg.typeOpt - } - val argQuotedTypes = args.map { arg => - if arg.isTerm then - val tpe = arg.typeOpt.widenTermRefExpr match - case _: MethodicType => - // Special erasure for captured function references - // See `SpliceTransformer.transformCapturedApplication` - defn.AnyType - case tpe => tpe - defn.QuotedExprClass.typeRef.appliedTo(tpe) - else defn.QuotedTypeClass.typeRef.appliedTo(arg.typeOpt.widenTermRefExpr) - } - val expectedResultType = - if isTermHole then defn.QuotedExprClass.typeRef.appliedTo(tpt.typeOpt) - else defn.QuotedTypeClass.typeRef.appliedTo(tpt.typeOpt) - val contextualResult = - defn.FunctionOf(List(defn.QuotesClass.typeRef), expectedResultType, isContextual = true) - val expectedContentType = - if typeArgsTypes.isEmpty then defn.FunctionOf(argQuotedTypes, contextualResult) - else RefinedType(defn.PolyFunctionType, nme.apply, PolyType(typeArgsTypes.map(_ => TypeBounds.empty))(pt => - val tpParamMap = new TypeMap { - private val mapping = typeArgsTypes.map(_.typeSymbol).zip(pt.paramRefs).toMap - def apply(tp: Type): Type = tp match - case tp: TypeRef => mapping.getOrElse(tp.typeSymbol, tp) - case tp => mapOver(tp) - } - MethodType( - args.zipWithIndex.map { case (arg, idx) => - if arg.isTerm then - val tpe = arg.typeOpt.widenTermRefExpr match - case _: MethodicType => - // Special erasure for captured function references - // See `SpliceTransformer.transformCapturedApplication` - defn.AnyType - case tpe => tpe - defn.QuotedExprClass.typeRef.appliedTo(tpParamMap(tpe)) - else defn.QuotedTypeClass.typeRef.appliedTo(tpParamMap(arg.typeOpt)) - }, - tpParamMap(contextualResult)) + if content != EmptyTree then + // Check that the types of the args conform to the types of the contents of the hole + val typeArgsTypes = args.collect { case arg if arg.isType => + arg.typeOpt + } + val argQuotedTypes = args.map { arg => + if arg.isTerm then + val tpe = arg.typeOpt.widenTermRefExpr match + case _: MethodicType => + // Special erasure for captured function references + // See `SpliceTransformer.transformCapturedApplication` + defn.AnyType + case tpe => tpe + defn.QuotedExprClass.typeRef.appliedTo(tpe) + else defn.QuotedTypeClass.typeRef.appliedTo(arg.typeOpt.widenTermRefExpr) + } + val expectedResultType = + if isTermHole then defn.QuotedExprClass.typeRef.appliedTo(tpt.typeOpt) + else defn.QuotedTypeClass.typeRef.appliedTo(tpt.typeOpt) + val contextualResult = + defn.FunctionOf(List(defn.QuotesClass.typeRef), expectedResultType, isContextual = true) + val expectedContentType = + if typeArgsTypes.isEmpty then defn.FunctionOf(argQuotedTypes, contextualResult) + else RefinedType(defn.PolyFunctionType, nme.apply, PolyType(typeArgsTypes.map(_ => TypeBounds.empty))(pt => + val tpParamMap = new TypeMap { + private val mapping = typeArgsTypes.map(_.typeSymbol).zip(pt.paramRefs).toMap + def apply(tp: Type): Type = tp match + case tp: TypeRef => mapping.getOrElse(tp.typeSymbol, tp) + case tp => mapOver(tp) + } + MethodType( + args.zipWithIndex.map { case (arg, idx) => + if arg.isTerm then + val tpe = arg.typeOpt.widenTermRefExpr match + case _: MethodicType => + // Special erasure for captured function references + // See `SpliceTransformer.transformCapturedApplication` + defn.AnyType + case tpe => tpe + defn.QuotedExprClass.typeRef.appliedTo(tpParamMap(tpe)) + else defn.QuotedTypeClass.typeRef.appliedTo(tpParamMap(arg.typeOpt)) + }, + tpParamMap(contextualResult)) + ) ) - ) - assert(content.typeOpt =:= expectedContentType, i"unexpected content of hole\nexpected: ${expectedContentType}\nwas: ${content.typeOpt}") + assert(content.typeOpt =:= expectedContentType, i"unexpected content of hole\nexpected: ${expectedContentType}\nwas: ${content.typeOpt}") tree1 }