From b1371e5c4f3eb9b4d2704828fefff6dce86b4d6c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 27 Mar 2021 15:59:19 +0100 Subject: [PATCH 1/2] Type ascribe arguments of bottom type to inline functions Fixes #11864 Fixes #8612 --- .../src/dotty/tools/dotc/typer/Inliner.scala | 76 +++++++++++-------- tests/pos/i11864.scala | 35 +++++++++ tests/pos/i8612.scala | 17 +++++ tests/run-macros/tasty-extractors-2.check | 4 +- 4 files changed, 98 insertions(+), 34 deletions(-) create mode 100644 tests/pos/i11864.scala create mode 100644 tests/pos/i8612.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 08ff58e918a2..2dc657c9abae 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -452,25 +452,32 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { */ private def paramBindingDef(name: Name, paramtp: Type, arg0: Tree, bindingsBuf: mutable.ListBuffer[ValOrDefDef])(using Context): ValOrDefDef = { + val isByName = paramtp.dealias.isInstanceOf[ExprType] val arg = arg0 match { case Typed(arg1, tpt) if tpt.tpe.isRepeatedParam && arg1.tpe.derivesFrom(defn.ArrayClass) => wrapArray(arg1, arg0.tpe.elemType) case _ => arg0 } val argtpe = arg.tpe.dealiasKeepAnnots.translateFromRepeated(toArray = false) - val isByName = paramtp.dealias.isInstanceOf[ExprType] - var inlineFlags: FlagSet = InlineProxy - if (paramtp.widenExpr.hasAnnotation(defn.InlineParamAnnot)) inlineFlags |= Inline - if (isByName) inlineFlags |= Method - val (bindingFlags, bindingType) = - if (isByName) (inlineFlags, ExprType(argtpe.widen)) - else (inlineFlags, argtpe.widen) + val argIsBottom = argtpe.isBottomTypeAfterErasure + val bindingType = + if argIsBottom then paramtp + else if isByName then ExprType(argtpe.widen) + else argtpe.widen + var bindingFlags: FlagSet = InlineProxy + if paramtp.widenExpr.hasAnnotation(defn.InlineParamAnnot) then + bindingFlags |= Inline + if isByName then + bindingFlags |= Method val boundSym = newSym(InlineBinderName.fresh(name.asTermName), bindingFlags, bindingType).asTerm val binding = { - val newArg = arg.changeOwner(ctx.owner, boundSym) - if (isByName) DefDef(boundSym, newArg) + var newArg = arg.changeOwner(ctx.owner, boundSym) + if bindingFlags.is(Inline) && argIsBottom then + newArg = Typed(newArg, TypeTree(paramtp)) // type ascribe RHS to avoid type errors in expansion. See i8612.scala + if isByName then DefDef(boundSym, newArg) else ValDef(boundSym, newArg) }.withSpan(boundSym.span) + inlining.println(i"parameter binding: $binding, $argIsBottom") bindingsBuf += binding binding } @@ -479,30 +486,35 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { * corresponding arguments. `bindingbuf` will be further extended later by * proxies to this-references. Issue an error if some arguments are missing. */ - private def computeParamBindings(tp: Type, targs: List[Tree], argss: List[List[Tree]]): Boolean = tp match - case tp: PolyType => - tp.paramNames.lazyZip(targs).foreach { (name, arg) => - paramSpan(name) = arg.span - paramBinding(name) = arg.tpe.stripTypeVar - } - computeParamBindings(tp.resultType, targs.drop(tp.paramNames.length), argss) - case tp: MethodType => - if argss.isEmpty then - report.error(i"missing arguments for inline method $inlinedMethod", call.srcPos) - false - else - tp.paramNames.lazyZip(tp.paramInfos).lazyZip(argss.head).foreach { (name, paramtp, arg) => + private def computeParamBindings( + tp: Type, targs: List[Tree], argss: List[List[Tree]], paramSubst: Type => Type): Boolean = + tp match + case tp: PolyType => + tp.paramNames.lazyZip(targs).foreach { (name, arg) => paramSpan(name) = arg.span - paramBinding(name) = arg.tpe.dealias match { - case _: SingletonType if isIdempotentPath(arg) => arg.tpe - case _ => paramBindingDef(name, paramtp, arg, bindingsBuf).symbol.termRef - } + paramBinding(name) = arg.tpe.stripTypeVar } - computeParamBindings(tp.resultType, targs, argss.tail) - case _ => - assert(targs.isEmpty) - assert(argss.isEmpty) - true + computeParamBindings( + tp.resultType, targs.drop(tp.paramNames.length), argss, + paramSubst.andThen(_.substParams(tp, targs.map(_.tpe.stripTypeVar)))) + case tp: MethodType => + if argss.isEmpty then + report.error(i"missing arguments for inline method $inlinedMethod", call.srcPos) + false + else + tp.paramNames.lazyZip(tp.paramInfos).lazyZip(argss.head).foreach { (name, paramtp, arg) => + paramSpan(name) = arg.span + paramBinding(name) = arg.tpe.dealias match + case _: SingletonType if isIdempotentPath(arg) => + arg.tpe + case _ => + paramBindingDef(name, paramSubst(paramtp), arg, bindingsBuf).symbol.termRef + } + computeParamBindings(tp.resultType, targs, argss.tail, paramSubst) + case _ => + assert(targs.isEmpty) + assert(argss.isEmpty) + true // Compute val-definitions for all this-proxies and append them to `bindingsBuf` private def computeThisBindings() = { @@ -687,7 +699,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { } // Compute bindings for all parameters, appending them to bindingsBuf - if !computeParamBindings(inlinedMethod.info, callTypeArgs, callValueArgss) then + if !computeParamBindings(inlinedMethod.info, callTypeArgs, callValueArgss, identity) then return call // make sure prefix is executed if it is impure diff --git a/tests/pos/i11864.scala b/tests/pos/i11864.scala new file mode 100644 index 000000000000..1d449e9f6122 --- /dev/null +++ b/tests/pos/i11864.scala @@ -0,0 +1,35 @@ +import language.experimental.erasedDefinitions + +type Ev = { type Out } +type Sub[X] = Ev { type Out = X } + +object test1: + transparent inline def foo(ev: Ev): Option[ev.Out] = ??? + transparent inline def bar[X](ev: Sub[X]): Option[ev.Out] = ??? + def test = + foo(???) + val y = bar[Int](???) + y: Option[Int] + +object test2: + inline def foo(ev: Ev): Option[ev.Out] = ??? + inline def bar[X](ev: Sub[X]): Option[ev.Out] = ??? + def test = + foo(???) + val y = bar[Int](???) + y: Option[Int] + +final class CallbackTo[+A] { + inline def map[B](f: A => B)(using erased ev: CallbackTo.MapGuard[B]): CallbackTo[ev.Out] = ??? +} + +object CallbackTo { + + type MapGuard[A] = { type Out = A } + erased given MapGuard[A]: MapGuard[A] = ??? + + def traverse[A, B](ta: List[A]): CallbackTo[List[B]] = + val x: CallbackTo[List[A] => List[B]] = ??? + x.map(_(ta)) +} + diff --git a/tests/pos/i8612.scala b/tests/pos/i8612.scala new file mode 100644 index 000000000000..294f7b58114b --- /dev/null +++ b/tests/pos/i8612.scala @@ -0,0 +1,17 @@ +object Foo1: + def assert1(x: Boolean) = if !x then ??? + inline def assert2(x: Boolean) = if !x then ??? + inline def assert3(inline x: Boolean) = if !x then ??? + + assert1(???) + assert2(???) + assert3(???) + +object Foo2: + def assert1(x: Boolean) = if !x then ??? + transparent inline def assert2(x: Boolean) = if !x then ??? + transparent inline def assert3(inline x: Boolean) = if !x then ??? + + assert1(???) + assert2(???) + assert3(???) diff --git a/tests/run-macros/tasty-extractors-2.check b/tests/run-macros/tasty-extractors-2.check index d7572ef7f5b2..1223a1fb71d9 100644 --- a/tests/run-macros/tasty-extractors-2.check +++ b/tests/run-macros/tasty-extractors-2.check @@ -4,8 +4,8 @@ TypeRef(ThisType(TypeRef(NoPrefix(), "scala")), "Unit") Inlined(None, Nil, Block(List(DefDef("$anonfun", List(TermParamClause(List(ValDef("x", TypeIdent("Int"), None)))), Inferred(), Some(Ident("x")))), Closure(Ident("$anonfun"), None))) AppliedType(TypeRef(ThisType(TypeRef(NoPrefix(), "scala")), "Function1"), List(TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "")), "scala"), "Int"), TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "")), "scala"), "Int"))) -Inlined(None, Nil, Ident("???")) -TermRef(TermRef(ThisType(TypeRef(NoPrefix(), "scala")), "Predef"), "???") +Inlined(None, Nil, Typed(Ident("???"), Inferred())) +AnnotatedType(TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "")), "scala"), "Any"), Apply(Select(New(Inferred()), ""), Nil)) Inlined(None, Nil, Literal(IntConstant(1))) ConstantType(IntConstant(1)) From a562c9b75516c514e7f6d12f78199331a181c930 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 28 Mar 2021 13:57:30 +0200 Subject: [PATCH 2/2] Handle prefixes and dependent parameter types when ascribing types --- .../src/dotty/tools/dotc/typer/Inliner.scala | 34 +++++++++++-------- tests/pos/i11864.scala | 14 ++++++++ 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 2dc657c9abae..ed8467f1ebb1 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -446,13 +446,13 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { * inline annotations from their parameters. The generated `def` is appended * to `bindingsBuf`. * @param name the name of the parameter - * @param paramtp the type of the parameter + * @param formal the type of the parameter * @param arg the argument corresponding to the parameter * @param bindingsBuf the buffer to which the definition should be appended */ - private def paramBindingDef(name: Name, paramtp: Type, arg0: Tree, + private def paramBindingDef(name: Name, formal: Type, arg0: Tree, bindingsBuf: mutable.ListBuffer[ValOrDefDef])(using Context): ValOrDefDef = { - val isByName = paramtp.dealias.isInstanceOf[ExprType] + val isByName = formal.dealias.isInstanceOf[ExprType] val arg = arg0 match { case Typed(arg1, tpt) if tpt.tpe.isRepeatedParam && arg1.tpe.derivesFrom(defn.ArrayClass) => wrapArray(arg1, arg0.tpe.elemType) @@ -461,11 +461,11 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { val argtpe = arg.tpe.dealiasKeepAnnots.translateFromRepeated(toArray = false) val argIsBottom = argtpe.isBottomTypeAfterErasure val bindingType = - if argIsBottom then paramtp + if argIsBottom then formal else if isByName then ExprType(argtpe.widen) else argtpe.widen var bindingFlags: FlagSet = InlineProxy - if paramtp.widenExpr.hasAnnotation(defn.InlineParamAnnot) then + if formal.widenExpr.hasAnnotation(defn.InlineParamAnnot) then bindingFlags |= Inline if isByName then bindingFlags |= Method @@ -473,7 +473,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { val binding = { var newArg = arg.changeOwner(ctx.owner, boundSym) if bindingFlags.is(Inline) && argIsBottom then - newArg = Typed(newArg, TypeTree(paramtp)) // type ascribe RHS to avoid type errors in expansion. See i8612.scala + newArg = Typed(newArg, TypeTree(formal)) // type ascribe RHS to avoid type errors in expansion. See i8612.scala if isByName then DefDef(boundSym, newArg) else ValDef(boundSym, newArg) }.withSpan(boundSym.span) @@ -487,30 +487,28 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { * proxies to this-references. Issue an error if some arguments are missing. */ private def computeParamBindings( - tp: Type, targs: List[Tree], argss: List[List[Tree]], paramSubst: Type => Type): Boolean = + tp: Type, targs: List[Tree], argss: List[List[Tree]], formalss: List[List[Type]]): Boolean = tp match case tp: PolyType => tp.paramNames.lazyZip(targs).foreach { (name, arg) => paramSpan(name) = arg.span paramBinding(name) = arg.tpe.stripTypeVar } - computeParamBindings( - tp.resultType, targs.drop(tp.paramNames.length), argss, - paramSubst.andThen(_.substParams(tp, targs.map(_.tpe.stripTypeVar)))) + computeParamBindings(tp.resultType, targs.drop(tp.paramNames.length), argss, formalss) case tp: MethodType => if argss.isEmpty then report.error(i"missing arguments for inline method $inlinedMethod", call.srcPos) false else - tp.paramNames.lazyZip(tp.paramInfos).lazyZip(argss.head).foreach { (name, paramtp, arg) => + tp.paramNames.lazyZip(formalss.head).lazyZip(argss.head).foreach { (name, formal, arg) => paramSpan(name) = arg.span paramBinding(name) = arg.tpe.dealias match case _: SingletonType if isIdempotentPath(arg) => arg.tpe case _ => - paramBindingDef(name, paramSubst(paramtp), arg, bindingsBuf).symbol.termRef + paramBindingDef(name, formal, arg, bindingsBuf).symbol.termRef } - computeParamBindings(tp.resultType, targs, argss.tail, paramSubst) + computeParamBindings(tp.resultType, targs, argss.tail, formalss.tail) case _ => assert(targs.isEmpty) assert(argss.isEmpty) @@ -698,8 +696,16 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { ) } + def paramTypess(call: Tree, acc: List[List[Type]]): List[List[Type]] = call match + case Apply(fn, args) => + fn.tpe.widen.match + case mt: MethodType => paramTypess(fn, mt.instantiateParamInfos(args.tpes) :: acc) + case _ => Nil + case TypeApply(fn, _) => paramTypess(fn, acc) + case _ => acc + // Compute bindings for all parameters, appending them to bindingsBuf - if !computeParamBindings(inlinedMethod.info, callTypeArgs, callValueArgss, identity) then + if !computeParamBindings(inlinedMethod.info, callTypeArgs, callValueArgss, paramTypess(call, Nil)) then return call // make sure prefix is executed if it is impure diff --git a/tests/pos/i11864.scala b/tests/pos/i11864.scala index 1d449e9f6122..da7140e57b8d 100644 --- a/tests/pos/i11864.scala +++ b/tests/pos/i11864.scala @@ -19,6 +19,20 @@ object test2: val y = bar[Int](???) y: Option[Int] +object test3: + inline def bar(ev: Ev)(x: ev.Out): Option[ev.Out] = Some(x) + val a: Ev { type Out = Int } = ??? + def test = + val y = bar(a)(???) + y: Option[Int] + +object test4: + inline def bar(ev: Ev, x: ev.Out): Option[ev.Out] = Some(x) + val a: Ev { type Out = Int } = ??? + def test = + val y = bar(a, ???) + y: Option[Int] + final class CallbackTo[+A] { inline def map[B](f: A => B)(using erased ev: CallbackTo.MapGuard[B]): CallbackTo[ev.Out] = ??? }