From 4b81aae294aa1eb810b3f744633c462aefb2a8a6 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 3 Oct 2017 13:57:06 +0200 Subject: [PATCH 01/27] Implement unused values --- compiler/src/dotty/tools/dotc/Compiler.scala | 2 + .../src/dotty/tools/dotc/ast/Desugar.scala | 2 +- .../src/dotty/tools/dotc/ast/TreeInfo.scala | 2 +- compiler/src/dotty/tools/dotc/ast/tpd.scala | 3 +- compiler/src/dotty/tools/dotc/ast/untpd.scala | 18 ++- .../dotty/tools/dotc/core/Definitions.scala | 74 ++++++--- .../src/dotty/tools/dotc/core/Flags.scala | 9 +- compiler/src/dotty/tools/dotc/core/Mode.scala | 5 + .../src/dotty/tools/dotc/core/NameOps.scala | 26 ++- .../src/dotty/tools/dotc/core/StdNames.scala | 2 + .../dotty/tools/dotc/core/TypeErasure.scala | 3 +- .../src/dotty/tools/dotc/core/Types.scala | 32 +++- .../core/unpickleScala2/Scala2Unpickler.scala | 2 +- .../dotty/tools/dotc/parsing/Parsers.scala | 68 ++++---- .../src/dotty/tools/dotc/parsing/Tokens.scala | 5 +- .../tools/dotc/printing/RefinedPrinter.scala | 7 +- .../reporting/diagnostic/ErrorMessageID.java | 2 +- .../dotc/reporting/diagnostic/messages.scala | 13 +- .../dotty/tools/dotc/transform/Erasure.scala | 19 ++- .../dotc/transform/FullParameterization.scala | 7 +- .../dotc/transform/GenericSignatures.scala | 9 +- .../dotty/tools/dotc/transform/Mixin.scala | 2 +- .../tools/dotc/transform/PostTyper.scala | 26 ++- .../dotc/transform/SyntheticMethods.scala | 10 +- .../tools/dotc/transform/UnusedDecls.scala | 46 ++++++ .../tools/dotc/transform/UnusedRefs.scala | 54 +++++++ .../dotc/transform/VCInlineMethods.scala | 2 +- .../dotty/tools/dotc/typer/Applications.scala | 4 +- .../src/dotty/tools/dotc/typer/Checking.scala | 12 +- .../src/dotty/tools/dotc/typer/Namer.scala | 8 +- .../src/dotty/tools/dotc/typer/Typer.scala | 28 ++-- .../dotty/tools/dotc/FromTastyTests.scala | 9 +- .../dotc/reporting/ErrorMessagesTests.scala | 2 +- docs/docs/reference/unused-terms.md | 150 ++++++++++++++++++ tests/generic-java-signatures/unused.check | 2 + tests/generic-java-signatures/unused.scala | 14 ++ tests/neg/unused-1.scala | 13 ++ tests/neg/unused-4.scala | 17 ++ tests/neg/unused-5.scala | 18 +++ tests/neg/unused-6.scala | 13 ++ tests/neg/unused-Nothing.scala | 6 + tests/neg/unused-args-lifted.scala | 16 ++ tests/neg/unused-assign.scala | 7 + tests/neg/unused-case-class.scala | 1 + tests/neg/unused-class.scala | 1 + tests/neg/unused-def-rhs.scala | 6 + tests/neg/unused-if-else.scala | 22 +++ tests/neg/unused-lazy-val.scala | 3 + tests/neg/unused-match.scala | 22 +++ tests/neg/unused-object.scala | 1 + tests/neg/unused-return.scala | 13 ++ tests/neg/unused-trait.scala | 1 + tests/neg/unused-try.scala | 16 ++ tests/neg/unused-type.scala | 3 + tests/neg/unused-val-rhs.scala | 6 + tests/neg/unused-value-class.scala | 4 + tests/neg/unused-var.scala | 3 + tests/pos/unused-args-lifted.scala | 12 ++ tests/pos/unused-asInstanceOf.scala | 18 +++ tests/pos/unused-deep-context.scala | 16 ++ tests/pos/unused-extension-method.scala | 3 + tests/pos/unused-pathdep-1.scala | 19 +++ tests/pos/unused-pathdep-2.scala | 19 +++ tests/run/unused-1.check | 1 + tests/run/unused-1.scala | 14 ++ tests/run/unused-10.check | 2 + tests/run/unused-10.scala | 20 +++ tests/run/unused-11.check | 4 + tests/run/unused-11.scala | 31 ++++ tests/run/unused-12.check | 4 + tests/run/unused-12.scala | 17 ++ tests/run/unused-13.check | 1 + tests/run/unused-13.scala | 15 ++ tests/run/unused-15.check | 1 + tests/run/unused-15.scala | 18 +++ tests/run/unused-16.check | 1 + tests/run/unused-16.scala | 22 +++ tests/run/unused-17.check | 1 + tests/run/unused-17.scala | 13 ++ tests/run/unused-18.check | 1 + tests/run/unused-18.scala | 16 ++ tests/run/unused-19.check | 1 + tests/run/unused-19.scala | 10 ++ tests/run/unused-2.check | 2 + tests/run/unused-2.scala | 18 +++ tests/run/unused-20.check | 1 + tests/run/unused-20.scala | 14 ++ tests/run/unused-21.check | 1 + tests/run/unused-21.scala | 16 ++ tests/run/unused-22.check | 1 + tests/run/unused-22.scala | 17 ++ tests/run/unused-23.check | 2 + tests/run/unused-23.scala | 22 +++ tests/run/unused-24.check | 1 + tests/run/unused-24.scala | 23 +++ tests/run/unused-25.check | 1 + tests/run/unused-25.scala | 10 ++ tests/run/unused-26.check | 1 + tests/run/unused-26.scala | 6 + tests/run/unused-27.check | 3 + tests/run/unused-27.scala | 15 ++ tests/run/unused-28.check | 4 + tests/run/unused-28.scala | 18 +++ tests/run/unused-3.check | 1 + tests/run/unused-3.scala | 21 +++ tests/run/unused-4.check | 4 + tests/run/unused-4.scala | 25 +++ tests/run/unused-5.check | 6 + tests/run/unused-5.scala | 20 +++ tests/run/unused-6.check | 1 + tests/run/unused-6.scala | 16 ++ tests/run/unused-7.check | 2 + tests/run/unused-7.scala | 19 +++ tests/run/unused-8.check | 2 + tests/run/unused-8.scala | 19 +++ tests/run/unused-9.check | 1 + tests/run/unused-9.scala | 15 ++ tests/run/unused-frameless.check | 2 + tests/run/unused-frameless.scala | 120 ++++++++++++++ tests/run/unused-machine-state.check | 4 + tests/run/unused-machine-state.scala | 57 +++++++ tests/run/unused-poly-ref.check | 1 + tests/run/unused-poly-ref.scala | 15 ++ tests/run/unused-value-class.check | 2 + tests/run/unused-value-class.scala | 16 ++ 125 files changed, 1572 insertions(+), 124 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/transform/UnusedDecls.scala create mode 100644 compiler/src/dotty/tools/dotc/transform/UnusedRefs.scala create mode 100644 docs/docs/reference/unused-terms.md create mode 100644 tests/generic-java-signatures/unused.check create mode 100644 tests/generic-java-signatures/unused.scala create mode 100644 tests/neg/unused-1.scala create mode 100644 tests/neg/unused-4.scala create mode 100644 tests/neg/unused-5.scala create mode 100644 tests/neg/unused-6.scala create mode 100644 tests/neg/unused-Nothing.scala create mode 100644 tests/neg/unused-args-lifted.scala create mode 100644 tests/neg/unused-assign.scala create mode 100644 tests/neg/unused-case-class.scala create mode 100644 tests/neg/unused-class.scala create mode 100644 tests/neg/unused-def-rhs.scala create mode 100644 tests/neg/unused-if-else.scala create mode 100644 tests/neg/unused-lazy-val.scala create mode 100644 tests/neg/unused-match.scala create mode 100644 tests/neg/unused-object.scala create mode 100644 tests/neg/unused-return.scala create mode 100644 tests/neg/unused-trait.scala create mode 100644 tests/neg/unused-try.scala create mode 100644 tests/neg/unused-type.scala create mode 100644 tests/neg/unused-val-rhs.scala create mode 100644 tests/neg/unused-value-class.scala create mode 100644 tests/neg/unused-var.scala create mode 100644 tests/pos/unused-args-lifted.scala create mode 100644 tests/pos/unused-asInstanceOf.scala create mode 100644 tests/pos/unused-deep-context.scala create mode 100644 tests/pos/unused-extension-method.scala create mode 100644 tests/pos/unused-pathdep-1.scala create mode 100644 tests/pos/unused-pathdep-2.scala create mode 100644 tests/run/unused-1.check create mode 100644 tests/run/unused-1.scala create mode 100644 tests/run/unused-10.check create mode 100644 tests/run/unused-10.scala create mode 100644 tests/run/unused-11.check create mode 100644 tests/run/unused-11.scala create mode 100644 tests/run/unused-12.check create mode 100644 tests/run/unused-12.scala create mode 100644 tests/run/unused-13.check create mode 100644 tests/run/unused-13.scala create mode 100644 tests/run/unused-15.check create mode 100644 tests/run/unused-15.scala create mode 100644 tests/run/unused-16.check create mode 100644 tests/run/unused-16.scala create mode 100644 tests/run/unused-17.check create mode 100644 tests/run/unused-17.scala create mode 100644 tests/run/unused-18.check create mode 100644 tests/run/unused-18.scala create mode 100644 tests/run/unused-19.check create mode 100644 tests/run/unused-19.scala create mode 100644 tests/run/unused-2.check create mode 100644 tests/run/unused-2.scala create mode 100644 tests/run/unused-20.check create mode 100644 tests/run/unused-20.scala create mode 100644 tests/run/unused-21.check create mode 100644 tests/run/unused-21.scala create mode 100644 tests/run/unused-22.check create mode 100644 tests/run/unused-22.scala create mode 100644 tests/run/unused-23.check create mode 100644 tests/run/unused-23.scala create mode 100644 tests/run/unused-24.check create mode 100644 tests/run/unused-24.scala create mode 100644 tests/run/unused-25.check create mode 100644 tests/run/unused-25.scala create mode 100644 tests/run/unused-26.check create mode 100644 tests/run/unused-26.scala create mode 100644 tests/run/unused-27.check create mode 100644 tests/run/unused-27.scala create mode 100644 tests/run/unused-28.check create mode 100644 tests/run/unused-28.scala create mode 100644 tests/run/unused-3.check create mode 100644 tests/run/unused-3.scala create mode 100644 tests/run/unused-4.check create mode 100644 tests/run/unused-4.scala create mode 100644 tests/run/unused-5.check create mode 100644 tests/run/unused-5.scala create mode 100644 tests/run/unused-6.check create mode 100644 tests/run/unused-6.scala create mode 100644 tests/run/unused-7.check create mode 100644 tests/run/unused-7.scala create mode 100644 tests/run/unused-8.check create mode 100644 tests/run/unused-8.scala create mode 100644 tests/run/unused-9.check create mode 100644 tests/run/unused-9.scala create mode 100644 tests/run/unused-frameless.check create mode 100644 tests/run/unused-frameless.scala create mode 100644 tests/run/unused-machine-state.check create mode 100644 tests/run/unused-machine-state.scala create mode 100644 tests/run/unused-poly-ref.check create mode 100644 tests/run/unused-poly-ref.scala create mode 100644 tests/run/unused-value-class.check create mode 100644 tests/run/unused-value-class.scala diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 2b85f65577fb..0ebd7d6409dd 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -63,6 +63,7 @@ class Compiler { new CheckReentrant, // Internal use only: Check that compiled program has no data races involving global vars new ElimPackagePrefixes) :: // Eliminate references to package prefixes in Select nodes List(new CheckStatic, // Check restrictions that apply to @static members + new UnusedRefs, // Removes all calls and references to unused values new ElimRepeated, // Rewrite vararg parameters and arguments new NormalizeFlags, // Rewrite some definition flags new ExtensionMethods, // Expand methods of value classes with extension methods @@ -81,6 +82,7 @@ class Compiler { new CrossCastAnd, // Normalize selections involving intersection types. new Splitter) :: // Expand selections involving union types into conditionals List(new PhantomArgLift, // Extracts the evaluation of phantom arguments placing them before the call. + new UnusedDecls, // Removes all unused defs and vals decls (except for parameters) new VCInlineMethods, // Inlines calls to value class methods new SeqLiterals, // Express vararg arguments as arrays new InterceptedMethods, // Special handling of `==`, `|=`, `getClass` methods diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index a55434c54bd0..6498568d23b9 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -258,7 +258,7 @@ object desugar { private def toDefParam(tparam: TypeDef): TypeDef = tparam.withMods(tparam.rawMods & EmptyFlags | Param) private def toDefParam(vparam: ValDef): ValDef = - vparam.withMods(vparam.rawMods & Implicit | Param) + vparam.withMods(vparam.rawMods & (Implicit | Unused) | Param) /** The expansion of a class definition. See inline comments for what is involved */ def classDef(cdef: TypeDef)(implicit ctx: Context): Tree = { diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index c3630d74ec3d..3b0d966751e4 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -396,7 +396,7 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => * flags set. */ private def refPurity(tree: Tree)(implicit ctx: Context): PurityLevel = - if (!tree.tpe.widen.isParameterless) SimplyPure + if (!tree.tpe.widen.isParameterless || tree.symbol.is(Unused)) SimplyPure else if (!tree.symbol.isStable) Impure else if (tree.symbol.is(Lazy)) Idempotent // TODO add Module flag, sinxce Module vals or not Lazy from the start. else SimplyPure diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index a0eb74be1422..57b4221d1bff 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -202,7 +202,8 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { case tp: MethodType => def valueParam(name: TermName, info: Type): TermSymbol = { val maybeImplicit = if (tp.isImplicitMethod) Implicit else EmptyFlags - ctx.newSymbol(sym, name, TermParam | maybeImplicit, info, coord = sym.coord) + val maybeUnused = if (tp.isUnusedMethod) Unused else EmptyFlags + ctx.newSymbol(sym, name, TermParam | maybeImplicit | maybeUnused, info, coord = sym.coord) } val params = (tp.paramNames, tp.paramInfos).zipped.map(valueParam) val (paramss, rtp) = valueParamss(tp.instantiate(params map (_.termRef))) diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 22fd83688eaf..7d82fea395d4 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -50,16 +50,30 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { */ case class InterpolatedString(id: TermName, segments: List[Tree]) extends TermTree + /** An function type */ case class Function(args: List[Tree], body: Tree) extends Tree { override def isTerm = body.isTerm override def isType = body.isType } + /** An function type that should have non empty args */ + abstract class NonEmptyFunction(args: List[Tree], body: Tree) extends Function(args, body) + /** An implicit function type */ - class ImplicitFunction(args: List[Tree], body: Tree) extends Function(args, body) { + class ImplicitFunction(args: List[Tree], body: Tree) extends NonEmptyFunction(args, body) { override def toString = s"ImplicitFunction($args, $body)" } + /** An function type with unused arguments */ + class UnusedFunction(args: List[Tree], body: Tree) extends NonEmptyFunction(args, body) { + override def toString = s"UnusedFunction($args, $body)" + } + + /** An implicit function type with unused arguments */ + class UnusedImplicitFunction(args: List[Tree], body: Tree) extends NonEmptyFunction(args, body) { + override def toString = s"UnusedImplicitFunction($args, $body)" + } + /** A function created from a wildcard expression * @param placeHolderParams a list of definitions of synthetic parameters * @param body the function body where wildcards are replaced by @@ -119,6 +133,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case class Implicit() extends Mod(Flags.ImplicitCommon) + case class Unused() extends Mod(Flags.UnusedCommon) + case class Final() extends Mod(Flags.Final) case class Sealed() extends Mod(Flags.Sealed) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 2f3580170455..64ef52bbfa62 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -117,18 +117,12 @@ class Definitions { val argParamRefs = List.tabulate(arity) { i => enterTypeParam(cls, paramNamePrefix ++ "T" ++ (i + 1).toString, Contravariant, decls).typeRef } - val resParam = enterTypeParam(cls, paramNamePrefix ++ "R", Covariant, decls) - val (methodType, parentTraits) = - if (name.firstPart.startsWith(str.ImplicitFunction)) { - val superTrait = - FunctionType(arity).appliedTo(argParamRefs ::: resParam.typeRef :: Nil) - (ImplicitMethodType, superTrait :: Nil) - } - else (MethodType, Nil) - val applyMeth = - decls.enter( - newMethod(cls, nme.apply, - methodType(argParamRefs, resParam.typeRef), Deferred)) + val resParamRef = enterTypeParam(cls, paramNamePrefix ++ "R", Covariant, decls).typeRef + val methodType = MethodType.maker(isJava = false, name.isImplicitFunction, name.isUnusedFunction) + val parentTraits = + if (!name.isImplicitFunction) Nil + else FunctionType(arity, isUnused = name.isUnusedFunction).appliedTo(argParamRefs ::: resParamRef :: Nil) :: Nil + decls.enter(newMethod(cls, nme.apply, methodType(argParamRefs, resParamRef), Deferred)) denot.info = ClassInfo(ScalaPackageClass.thisType, cls, ObjectType :: parentTraits, decls) } @@ -748,14 +742,14 @@ class Definitions { sym.owner.linkedClass.typeRef object FunctionOf { - def apply(args: List[Type], resultType: Type, isImplicit: Boolean = false)(implicit ctx: Context) = - FunctionType(args.length, isImplicit).appliedTo(args ::: resultType :: Nil) + def apply(args: List[Type], resultType: Type, isImplicit: Boolean = false, isUnused: Boolean = false)(implicit ctx: Context) = + FunctionType(args.length, isImplicit, isUnused).appliedTo(args ::: resultType :: Nil) def unapply(ft: Type)(implicit ctx: Context) = { val tsym = ft.typeSymbol if (isFunctionClass(tsym)) { val targs = ft.dealias.argInfos if (targs.isEmpty) None - else Some(targs.init, targs.last, tsym.name.isImplicitFunction) + else Some(targs.init, targs.last, tsym.name.isImplicitFunction, tsym.name.isUnusedFunction) } else None } @@ -819,20 +813,29 @@ class Definitions { lazy val TupleType = mkArityArray("scala.Tuple", MaxTupleArity, 2) - def FunctionClass(n: Int, isImplicit: Boolean = false)(implicit ctx: Context) = - if (isImplicit) { + def FunctionClass(n: Int, isImplicit: Boolean = false, isUnused: Boolean = false)(implicit ctx: Context) = { + if (isImplicit && isUnused) { + require(n > 0) + ctx.requiredClass("scala.UnusedImplicitFunction" + n.toString) + } + else if (isImplicit) { require(n > 0) ctx.requiredClass("scala.ImplicitFunction" + n.toString) } + else if (isUnused) { + require(n > 0) + ctx.requiredClass("scala.UnusedFunction" + n.toString) + } else if (n <= MaxImplementedFunctionArity) FunctionClassPerRun()(ctx)(n) else ctx.requiredClass("scala.Function" + n.toString) + } lazy val Function0_applyR = ImplementedFunctionType(0).symbol.requiredMethodRef(nme.apply) def Function0_apply(implicit ctx: Context) = Function0_applyR.symbol - def FunctionType(n: Int, isImplicit: Boolean = false)(implicit ctx: Context): TypeRef = - if (n <= MaxImplementedFunctionArity && (!isImplicit || ctx.erasedTypes)) ImplementedFunctionType(n) - else FunctionClass(n, isImplicit).typeRef + def FunctionType(n: Int, isImplicit: Boolean = false, isUnused: Boolean = false)(implicit ctx: Context): TypeRef = + if (n <= MaxImplementedFunctionArity && (!isImplicit || ctx.erasedTypes) && !isUnused) ImplementedFunctionType(n) + else FunctionClass(n, isImplicit, isUnused).typeRef private lazy val TupleTypes: Set[TypeRef] = TupleType.toSet @@ -857,14 +860,23 @@ class Definitions { /** Is a function class. * - FunctionN for N >= 0 * - ImplicitFunctionN for N > 0 + * - UnusedFunctionN for N > 0 + * - UnusedImplicitFunctionN for N > 0 */ def isFunctionClass(cls: Symbol) = scalaClassName(cls).isFunction /** Is an implicit function class. * - ImplicitFunctionN for N > 0 + * - UnusedImplicitFunctionN for N > 0 */ def isImplicitFunctionClass(cls: Symbol) = scalaClassName(cls).isImplicitFunction + /** Is an unused function class. + * - UnusedFunctionN for N > 0 + * - UnusedImplicitFunctionN for N > 0 + */ + def isUnusedFunctionClass(cls: Symbol) = scalaClassName(cls).isUnusedFunction + /** Is a class that will be erased to FunctionXXL * - FunctionN for N >= 22 * - ImplicitFunctionN for N >= 22 @@ -889,11 +901,14 @@ class Definitions { * - FunctionN for 22 > N >= 0 remains as FunctionN * - ImplicitFunctionN for N > 22 becomes FunctionXXL * - ImplicitFunctionN for 22 > N >= 0 becomes FunctionN + * - UnusedFunctionN becomes Function0 + * - ImplicitUnusedFunctionN becomes Function0 * - anything else becomes a NoSymbol */ def erasedFunctionClass(cls: Symbol): Symbol = { val arity = scalaClassName(cls).functionArity - if (arity > 22) FunctionXXLClass + if (cls.name.isUnusedFunction) FunctionClass(0) + else if (arity > 22) FunctionXXLClass else if (arity >= 0) FunctionClass(arity) else NoSymbol } @@ -903,12 +918,15 @@ class Definitions { * - FunctionN for 22 > N >= 0 remains as FunctionN * - ImplicitFunctionN for N > 22 becomes FunctionXXL * - ImplicitFunctionN for 22 > N >= 0 becomes FunctionN + * - UnusedFunctionN becomes Function0 + * - ImplicitUnusedFunctionN becomes Function0 * - anything else becomes a NoType */ def erasedFunctionType(cls: Symbol): Type = { val arity = scalaClassName(cls).functionArity - if (arity > 22) defn.FunctionXXLType - else if (arity >= 0) defn.FunctionType(arity) + if (cls.name.isUnusedFunction) FunctionType(0) + else if (arity > 22) FunctionXXLType + else if (arity >= 0) FunctionType(arity) else NoType } @@ -953,7 +971,10 @@ class Definitions { * trait gets screwed up. Therefore, it is mandatory that FunctionXXL * is treated as a NoInit trait. */ - lazy val NoInitClasses = NotRuntimeClasses + FunctionXXLClass + private lazy val NoInitClasses = NotRuntimeClasses + FunctionXXLClass + + def isNoInitClass(cls: Symbol): Boolean = + cls.is(NoInitsTrait) || NoInitClasses.contains(cls) || isFunctionClass(cls) def isPolymorphicAfterErasure(sym: Symbol) = (sym eq Any_isInstanceOf) || (sym eq Any_asInstanceOf) @@ -976,7 +997,7 @@ class Definitions { def isNonDepFunctionType(tp: Type)(implicit ctx: Context) = { val arity = functionArity(tp) val sym = tp.dealias.typeSymbol - arity >= 0 && isFunctionClass(sym) && tp.isRef(FunctionType(arity, sym.name.isImplicitFunction).typeSymbol) + arity >= 0 && isFunctionClass(sym) && tp.isRef(FunctionType(arity, sym.name.isImplicitFunction, sym.name.isUnusedFunction).typeSymbol) } /** Is `tp` a representation of a (possibly depenent) function type or an alias of such? */ @@ -1042,6 +1063,9 @@ class Definitions { def isImplicitFunctionType(tp: Type)(implicit ctx: Context): Boolean = asImplicitFunctionType(tp).exists + def isUnusedFunctionType(tp: Type)(implicit ctx: Context) = + isFunctionType(tp) && tp.dealias.typeSymbol.name.isUnusedFunction + // ----- primitive value class machinery ------------------------------------------ /** This class would also be obviated by the implicit function type design */ diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index 6847fd5ddb94..301831ef09d1 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -367,6 +367,11 @@ object Flags { /** Symbol is a Java enum */ final val Enum = commonFlag(40, "") + /** Labeled with `unused` modifier (unused value) */ + final val UnusedCommon = commonFlag(42, "unused") + final val Unused = UnusedCommon.toTermFlags + final val UnusedType = UnusedCommon.toTypeFlags + // Flags following this one are not pickled /** Symbol is not a member of its owner */ @@ -438,7 +443,7 @@ object Flags { /** Flags representing source modifiers */ final val SourceModifierFlags = commonFlags(Private, Protected, Abstract, Final, Inline, - Sealed, Case, Implicit, Override, AbsOverride, Lazy, JavaStatic) + Sealed, Case, Implicit, Override, AbsOverride, Lazy, JavaStatic, Unused) /** Flags representing modifiers that can appear in trees */ final val ModifierFlags = @@ -512,7 +517,7 @@ object Flags { /** Flags that can apply to a module val */ final val RetainedModuleValFlags: FlagSet = RetainedModuleValAndClassFlags | Override | Final | Method | Implicit | Lazy | - Accessor | AbsOverride | Stable | Captured | Synchronized | Inline + Accessor | AbsOverride | Stable | Captured | Synchronized | Inline | Unused /** Flags that can apply to a module class */ final val RetainedModuleClassFlags: FlagSet = RetainedModuleValAndClassFlags | ImplClass | Enum diff --git a/compiler/src/dotty/tools/dotc/core/Mode.scala b/compiler/src/dotty/tools/dotc/core/Mode.scala index c97e20a88d49..790f2a85270d 100644 --- a/compiler/src/dotty/tools/dotc/core/Mode.scala +++ b/compiler/src/dotty/tools/dotc/core/Mode.scala @@ -93,4 +93,9 @@ object Mode { /** We are in the IDE */ val Interactive = newMode(20, "Interactive") + + /** We are currently in code that will not be used at runtime. + * It can be in an argument to an unused parameter or a type selection. + */ + val Unused = newMode(21, "Unused") } diff --git a/compiler/src/dotty/tools/dotc/core/NameOps.scala b/compiler/src/dotty/tools/dotc/core/NameOps.scala index 4a2d3092febc..0756f36df7b9 100644 --- a/compiler/src/dotty/tools/dotc/core/NameOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NameOps.scala @@ -174,31 +174,51 @@ object NameOps { */ def functionArity: Int = functionArityFor(str.Function) max { - val n = functionArityFor(str.ImplicitFunction) + val n = functionArityFor(str.ImplicitFunction) max functionArityFor(str.UnusedFunction) max functionArityFor(str.UnusedImplicitFunction) if (n == 0) -1 else n } /** Is a function name * - FunctionN for N >= 0 * - ImplicitFunctionN for N >= 1 + * - UnusedFunctionN for N >= 1 + * - UnusedImplicitFunctionN for N >= 1 * - false otherwise */ def isFunction: Boolean = functionArity >= 0 /** Is a implicit function name * - ImplicitFunctionN for N >= 1 + * - UnusedImplicitFunctionN for N >= 1 * - false otherwise */ - def isImplicitFunction: Boolean = functionArityFor(str.ImplicitFunction) >= 1 + def isImplicitFunction: Boolean = { + functionArityFor(str.ImplicitFunction) >= 1 || + functionArityFor(str.UnusedImplicitFunction) >= 1 + } + + /** Is a implicit function name + * - UnusedFunctionN for N >= 1 + * - UnusedImplicitFunctionN for N >= 1 + * - false otherwise + */ + def isUnusedFunction: Boolean = { + functionArityFor(str.UnusedFunction) >= 1 || + functionArityFor(str.UnusedImplicitFunction) >= 1 + } /** Is a synthetic function name * - FunctionN for N > 22 * - ImplicitFunctionN for N >= 1 + * - UnusedFunctionN for N >= 1 + * - UnusedImplicitFunctionN for N >= 1 * - false otherwise */ def isSyntheticFunction: Boolean = { functionArityFor(str.Function) > MaxImplementedFunctionArity || - functionArityFor(str.ImplicitFunction) >= 1 + functionArityFor(str.ImplicitFunction) >= 1 || + functionArityFor(str.UnusedFunction) >= 1 || + functionArityFor(str.UnusedImplicitFunction) >= 1 } /** Parsed function arity for function with some specific prefix */ diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index fa6ff39b74aa..dd63cd5cf087 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -36,7 +36,9 @@ object StdNames { final val MODULE_INSTANCE_FIELD = "MODULE$" final val Function = "Function" + final val UnusedFunction = "UnusedFunction" final val ImplicitFunction = "ImplicitFunction" + final val UnusedImplicitFunction = "UnusedImplicitFunction" final val AbstractFunction = "AbstractFunction" final val Tuple = "Tuple" final val Product = "Product" diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index 05dc572dc51e..eef507ee32ab 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -402,7 +402,8 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean def paramErasure(tpToErase: Type) = erasureFn(tp.isJavaMethod, semiEraseVCs, isConstructor, wildcardOK)(tpToErase) val (names, formals0) = - if (tp.paramInfos.exists(_.isPhantom)) tp.paramNames.zip(tp.paramInfos).filterNot(_._2.isPhantom).unzip + if (tp.isUnusedMethod) (Nil, Nil) + else if (tp.paramInfos.exists(_.isPhantom)) tp.paramNames.zip(tp.paramInfos).filterNot(_._2.isPhantom).unzip else (tp.paramNames, tp.paramInfos) val formals = formals0.mapConserve(paramErasure) eraseResult(tp.resultType) match { diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 7cdcf767f8a2..04a4c98db77a 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -307,6 +307,9 @@ object Types { /** Is this a MethodType which has implicit parameters */ def isImplicitMethod: Boolean = false + /** Is this a MethodType for which the parameters will not be used */ + def isUnusedMethod: Boolean = false + // ----- Higher-order combinators ----------------------------------- /** Returns true if there is a part of this type that satisfies predicate `p`. @@ -1354,9 +1357,11 @@ object Types { def toFunctionType(dropLast: Int = 0)(implicit ctx: Context): Type = this match { case mt: MethodType if !mt.isParamDependent => val formals1 = if (dropLast == 0) mt.paramInfos else mt.paramInfos dropRight dropLast + val isImplicit = mt.isImplicitMethod && !ctx.erasedTypes + val isUnused = mt.isUnusedMethod && !ctx.erasedTypes val funType = defn.FunctionOf( formals1 mapConserve (_.underlyingIfRepeated(mt.isJavaMethod)), - mt.nonDependentResultApprox, mt.isImplicitMethod && !ctx.erasedTypes) + mt.nonDependentResultApprox, isImplicit, isUnused) if (mt.isDependent) RefinedType(funType, nme.apply, mt) else funType } @@ -2791,14 +2796,17 @@ object Types { def companion: MethodTypeCompanion final override def isJavaMethod: Boolean = companion eq JavaMethodType - final override def isImplicitMethod: Boolean = companion eq ImplicitMethodType + final override def isImplicitMethod: Boolean = companion.eq(ImplicitMethodType) || companion.eq(UnusedImplicitMethodType) + final override def isUnusedMethod: Boolean = companion.eq(UnusedMethodType) || companion.eq(UnusedImplicitMethodType) val paramInfos = paramInfosExp(this) val resType = resultTypeExp(this) assert(resType.exists) - def computeSignature(implicit ctx: Context): Signature = - resultSignature.prepend(paramInfos, isJavaMethod) + def computeSignature(implicit ctx: Context): Signature = { + val params = if (isUnusedMethod) Nil else paramInfos + resultSignature.prepend(params, isJavaMethod) + } final override def computeHash = doHash(paramNames, resType, paramInfos) @@ -2907,9 +2915,23 @@ object Types { } } - object MethodType extends MethodTypeCompanion + object MethodType extends MethodTypeCompanion { + def maker(isJava: Boolean = false, isImplicit: Boolean = false, isUnused: Boolean = false): MethodTypeCompanion = { + if (isJava) { + assert(!isImplicit) + assert(!isUnused) + JavaMethodType + } + else if (isImplicit && isUnused) UnusedImplicitMethodType + else if (isImplicit) ImplicitMethodType + else if (isUnused) UnusedMethodType + else MethodType + } + } object JavaMethodType extends MethodTypeCompanion object ImplicitMethodType extends MethodTypeCompanion + object UnusedMethodType extends MethodTypeCompanion + object UnusedImplicitMethodType extends MethodTypeCompanion /** A ternary extractor for MethodType */ object MethodTpe { diff --git a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala index e253c1b536fd..d1019095edeb 100644 --- a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala @@ -769,7 +769,7 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas def isImplicit = tag == IMPLICITMETHODtpe || params.nonEmpty && (params.head is Implicit) - val maker = if (isImplicit) ImplicitMethodType else MethodType + val maker = MethodType.maker(isImplicit = isImplicit) maker.fromSymbols(params, restpe) case POLYtpe => val restpe = readTypeRef() diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 1870777ef588..94d53c221555 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -712,21 +712,25 @@ object Parsers { } } - /** Type ::= [`implicit'] FunArgTypes `=>' Type + /** Type ::= [FunArgMods] FunArgTypes `=>' Type * | HkTypeParamClause `->' Type * | InfixType + * FunArgMods ::= `implicit' FunArgMods + * | `unused' FunArgMods * FunArgTypes ::= InfixType * | `(' [ FunArgType {`,' FunArgType } ] `)' * | '(' TypedFunParam {',' TypedFunParam } ')' */ def typ(): Tree = { val start = in.offset - val isImplicit = in.token == IMPLICIT - if (isImplicit) in.nextToken() + val imods = allFunctionMods(EmptyModifiers) def functionRest(params: List[Tree]): Tree = atPos(start, accept(ARROW)) { val t = typ() - if (isImplicit) new ImplicitFunction(params, t) else Function(params, t) + if (imods.is(Implicit) && imods.is(Unused)) new UnusedImplicitFunction(params, t) + else if (imods.is(Implicit)) new ImplicitFunction(params, t) + else if (imods.is(Unused)) new UnusedFunction(params, t) + else Function(params, t) } def funArgTypesRest(first: Tree, following: () => Tree) = { val buf = new ListBuffer[Tree] += first @@ -759,7 +763,7 @@ object Parsers { } openParens.change(LPAREN, -1) accept(RPAREN) - if (isImplicit || isValParamList || in.token == ARROW) functionRest(ts) + if (imods.is(Implicit) || isValParamList || in.token == ARROW) functionRest(ts) else { for (t <- ts) if (t.isInstanceOf[ByNameTypeTree]) @@ -786,7 +790,7 @@ object Parsers { case ARROW => functionRest(t :: Nil) case FORSOME => syntaxError(ExistentialTypesNoLongerSupported()); t case _ => - if (isImplicit && !t.isInstanceOf[ImplicitFunction]) + if (imods.is(Implicit) && !t.isInstanceOf[ImplicitFunction]) syntaxError("Types with implicit keyword can only be function types", Position(start, start + nme.IMPLICITkw.asSimpleName.length)) t } @@ -1026,14 +1030,16 @@ object Parsers { } } - /** Expr ::= [`implicit'] FunParams `=>' Expr + /** Expr ::= [FunArgMods] FunParams =>' Expr * | Expr1 + * FunArgMods ::= `implicit' FunArgMods + * | `unused' FunArgMods * FunParams ::= Bindings * | id * | `_' * ExprInParens ::= PostfixExpr `:' Type * | Expr - * BlockResult ::= [`implicit'] FunParams `=>' Block + * BlockResult ::= [FunArgMods] FunParams =>' Block * | Expr1 * Expr1 ::= `if' `(' Expr `)' {nl} Expr [[semi] else Expr] * | `if' Expr `then' Expr [[semi] else Expr] @@ -1061,9 +1067,10 @@ object Parsers { def expr(location: Location.Value): Tree = { val start = in.offset - if (in.token == IMPLICIT) - implicitClosure(start, location, implicitMods()) - else { + if (in.token == IMPLICIT || in.token == UNUSED) { + val imods = allFunctionMods(EmptyModifiers) + implicitClosure(start, location, imods) + } else { val saved = placeholderParams placeholderParams = Nil @@ -1634,6 +1641,7 @@ object Parsers { case ABSTRACT => Mod.Abstract() case FINAL => Mod.Final() case IMPLICIT => Mod.Implicit() + case UNUSED => Mod.Unused() case INLINE => Mod.Inline() case LAZY => Mod.Lazy() case OVERRIDE => Mod.Override() @@ -1722,8 +1730,14 @@ object Parsers { normalize(loop(start)) } - def implicitMods(): Modifiers = - addMod(EmptyModifiers, atPos(accept(IMPLICIT)) { Mod.Implicit() }) + def allFunctionMods(imods: Modifiers, doIfImplicit: () => Unit = () => ()): Modifiers = { + if (in.token == IMPLICIT) { + doIfImplicit() + allFunctionMods(addMod(imods, atPos(accept(IMPLICIT)) { Mod.Implicit() }), doIfImplicit) + } else if (in.token == UNUSED) + allFunctionMods(addMod(imods, atPos(accept(UNUSED)) { Mod.Unused() }), doIfImplicit) + else imods + } /** Wrap annotation or constructor in New(...). */ def wrapNew(tpt: Tree) = Select(New(tpt), nme.CONSTRUCTOR) @@ -1811,12 +1825,12 @@ object Parsers { def typeParamClauseOpt(ownerKind: ParamOwner.Value): List[TypeDef] = if (in.token == LBRACKET) typeParamClause(ownerKind) else Nil - /** ClsParamClauses ::= {ClsParamClause} [[nl] `(' `implicit' ClsParams `)'] - * ClsParamClause ::= [nl] `(' [ClsParams] ')' + /** ClsParamClauses ::= {ClsParamClause} [[nl] `(' [`unused'] `implicit' [`unused'] ClsParams `)'] + * ClsParamClause ::= [nl] `(' [`unused'] [ClsParams] ')' * ClsParams ::= ClsParam {`' ClsParam} * ClsParam ::= {Annotation} [{Modifier} (`val' | `var') | `inline'] Param - * DefParamClauses ::= {DefParamClause} [[nl] `(' `implicit' DefParams `)'] - * DefParamClause ::= [nl] `(' [DefParams] ')' + * DefParamClauses ::= {DefParamClause} [[nl] `(' [`unused'] `implicit' DefParams `)'] + * DefParamClause ::= [nl] `(' [`unused'] [DefParams] ')' * DefParams ::= DefParam {`,' DefParam} * DefParam ::= {Annotation} [`inline'] Param * Param ::= id `:' ParamType [`=' Expr] @@ -1869,21 +1883,20 @@ object Parsers { def paramClause(): List[ValDef] = inParens { if (in.token == RPAREN) Nil else { - if (in.token == IMPLICIT) { - implicitOffset = in.offset - imods = implicitMods() - } + if (in.token == IMPLICIT || in.token == UNUSED) + imods = allFunctionMods(imods, () => implicitOffset = in.offset) commaSeparated(() => param()) } } def clauses(): List[List[ValDef]] = { newLineOptWhenFollowedBy(LPAREN) - if (in.token == LPAREN) + if (in.token == LPAREN) { + imods = EmptyModifiers paramClause() :: { firstClauseOfCaseClass = false - if (imods.hasFlags) Nil else clauses() + if (imods is Implicit) Nil else clauses() } - else Nil + } else Nil } val start = in.offset val result = clauses() @@ -1949,7 +1962,8 @@ object Parsers { } } } - /** ImportSelector ::= id [`=>' id | `=>' `_'] + + /** ImportSelector ::= id [`=>' id | `=>' `_'] */ def importSelector(): Tree = { val from = termIdentOrWildcard() @@ -2464,9 +2478,9 @@ object Parsers { else if (isExprIntro) stats += expr(Location.InBlock) else if (isDefIntro(localModifierTokens)) - if (in.token == IMPLICIT) { + if (in.token == IMPLICIT || in.token == UNUSED) { val start = in.offset - val imods = implicitMods() + var imods = allFunctionMods(EmptyModifiers) if (isBindingIntro) stats += implicitClosure(start, Location.InBlock, imods) else stats +++= localDef(start, imods) } else { diff --git a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala index 3c1b3b679944..673c2d29cca2 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala @@ -177,6 +177,7 @@ object Tokens extends TokensCommon { final val FORSOME = 61; enter(FORSOME, "forSome") // TODO: deprecate final val INLINE = 62; enter(INLINE, "inline") final val ENUM = 63; enter(ENUM, "enum") + final val UNUSED = 64; enter(UNUSED, "unused") /** special symbols */ final val NEWLINE = 78; enter(NEWLINE, "end of statement", "new line") @@ -197,7 +198,7 @@ object Tokens extends TokensCommon { /** XML mode */ final val XMLSTART = 96; enter(XMLSTART, "$XMLSTART$<") // TODO: deprecate - final val alphaKeywords = tokenRange(IF, ENUM) + final val alphaKeywords = tokenRange(IF, UNUSED) final val symbolicKeywords = tokenRange(USCORE, VIEWBOUND) final val symbolicTokens = tokenRange(COMMA, VIEWBOUND) final val keywords = alphaKeywords | symbolicKeywords @@ -225,7 +226,7 @@ object Tokens extends TokensCommon { final val defIntroTokens = templateIntroTokens | dclIntroTokens final val localModifierTokens = BitSet( - ABSTRACT, FINAL, SEALED, IMPLICIT, INLINE, LAZY) + ABSTRACT, FINAL, SEALED, IMPLICIT, INLINE, LAZY, UNUSED) final val accessModifierTokens = BitSet( PRIVATE, PROTECTED) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 2133ea1dd455..e3bb7ce3ce2b 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -132,14 +132,14 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { def toTextTuple(args: List[Type]): Text = "(" ~ Text(args.map(argText), ", ") ~ ")" - def toTextFunction(args: List[Type], isImplicit: Boolean): Text = + def toTextFunction(args: List[Type], isImplicit: Boolean, isUnused: Boolean): Text = changePrec(GlobalPrec) { val argStr: Text = if (args.length == 2 && !defn.isTupleType(args.head)) atPrec(InfixPrec) { argText(args.head) } else toTextTuple(args.init) - (keywordText("implicit ") provided isImplicit) ~ argStr ~ " => " ~ argText(args.last) + (keywordText("unused ") provided isUnused) ~ (keywordText("implicit ") provided isImplicit) ~ argStr ~ " => " ~ argText(args.last) } def toTextDependentFunction(appType: MethodType): Text = { @@ -174,7 +174,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case AppliedType(tycon, args) => val cls = tycon.typeSymbol if (tycon.isRepeatedParam) return toTextLocal(args.head) ~ "*" - if (defn.isFunctionClass(cls)) return toTextFunction(args, cls.name.isImplicitFunction) + if (defn.isFunctionClass(cls)) return toTextFunction(args, cls.name.isImplicitFunction, cls.name.isUnusedFunction) if (defn.isTupleClass(cls)) return toTextTuple(args) if (isInfixType(tp)) return toTextInfixType(tycon, args) case EtaExpansion(tycon) => @@ -745,6 +745,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { else if (sym.isClass && flags.is(Case)) "case class" else if (flags is Module) "object" else if (sym.isTerm && !flags.is(Param) && flags.is(Implicit)) "implicit val" + else if (sym.isTerm && !flags.is(Param) && flags.is(Unused)) "unused val" else super.keyString(sym) } diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java b/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java index cf529724c028..a387877f563a 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java @@ -93,7 +93,7 @@ public enum ErrorMessageID { SuperCallsNotAllowedInlineID, ModifiersNotAllowedID, WildcardOnTypeArgumentNotAllowedOnNewID, - ImplicitFunctionTypeNeedsNonEmptyParameterListID, + FunctionTypeNeedsNonEmptyParameterListID, WrongNumberOfParametersID, DuplicatePrivateProtectedQualifierID, ExpectedStartOfTopLevelDefinitionID, diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index 3c7720b65aa9..7f01d220a274 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -1713,15 +1713,16 @@ object messages { } } - case class ImplicitFunctionTypeNeedsNonEmptyParameterList()(implicit ctx: Context) - extends Message(ImplicitFunctionTypeNeedsNonEmptyParameterListID) { + case class FunctionTypeNeedsNonEmptyParameterList(isImplicit: Boolean = true, isUnused: Boolean = true)(implicit ctx: Context) + extends Message(FunctionTypeNeedsNonEmptyParameterListID) { val kind = "Syntax" - val msg = "implicit function type needs non-empty parameter list" + val mods = ((isImplicit, "implicit") :: (isUnused, "unused") :: Nil).filter(_._1).mkString(" ") + val msg = mods + " function type needs non-empty parameter list" val explanation = { - val code1 = "type Transactional[T] = implicit Transaction => T" + val code1 = s"type Transactional[T] = $mods Transaction => T" val code2 = "val cl: implicit A => B" - hl"""It is not allowed to leave implicit function parameter list empty. - |Possible ways to define implicit function type: + hl"""It is not allowed to leave $mods function parameter list empty. + |Possible ways to define $mods function type: | |$code1 | diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index adb08d3e2e2a..88c26aa3d18f 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -457,12 +457,13 @@ object Erasure { private def runtimeCallWithProtoArgs(name: Name, pt: Type, args: Tree*)(implicit ctx: Context): Tree = { val meth = defn.runtimeMethodRef(name) val followingParams = meth.symbol.info.firstParamTypes.drop(args.length) - val followingArgs = protoArgs(pt).zipWithConserve(followingParams)(typedExpr).asInstanceOf[List[tpd.Tree]] + val followingArgs = protoArgs(pt, meth.widen).zipWithConserve(followingParams)(typedExpr).asInstanceOf[List[tpd.Tree]] ref(meth).appliedToArgs(args.toList ++ followingArgs) } - private def protoArgs(pt: Type): List[untpd.Tree] = pt match { - case pt: FunProto => pt.args ++ protoArgs(pt.resType) + private def protoArgs(pt: Type, tp: Type): List[untpd.Tree] = (pt, tp) match { + case (pt: FunProto, tp: MethodType) if tp.isUnusedMethod => protoArgs(pt.resType, tp.resType) + case (pt: FunProto, tp: MethodType) => pt.args ++ protoArgs(pt.resType, tp.resType) case _ => Nil } @@ -496,15 +497,20 @@ object Erasure { fun1.tpe.widen match { case mt: MethodType => val outers = outer.args(fun.asInstanceOf[tpd.Tree]) // can't use fun1 here because its type is already erased - var args0 = outers ::: args ++ protoArgs(pt) + var args0 = protoArgs(pt, tree.typeOpt) + if (mt.paramNames.nonEmpty && !mt.isUnusedMethod) args0 = args ::: args0 + args0 = outers ::: args0 + if (args0.length > MaxImplementedFunctionArity && mt.paramInfos.length == 1) { val bunchedArgs = untpd.JavaSeqLiteral(args0, TypeTree(defn.ObjectType)) .withType(defn.ArrayOf(defn.ObjectType)) args0 = bunchedArgs :: Nil } // Arguments are phantom if an only if the parameters are phantom, guaranteed by the separation of type lattices - val args1 = args0.filterConserve(arg => !wasPhantom(arg.typeOpt)).zipWithConserve(mt.paramInfos)(typedExpr) - untpd.cpy.Apply(tree)(fun1, args1) withType mt.resultType + val args1 = args0.filterConserve(arg => !wasPhantom(arg.typeOpt)) + assert(args1 hasSameLengthAs mt.paramInfos) + val args2 = args1.zipWithConserve(mt.paramInfos)(typedExpr) + untpd.cpy.Apply(tree)(fun1, args2) withType mt.resultType case _ => throw new MatchError(i"tree $tree has unexpected type of function ${fun1.tpe.widen}, was ${fun.typeOpt.widen}") } @@ -564,6 +570,7 @@ object Erasure { vparamss1 = (tpd.ValDef(bunchedParam) :: Nil) :: Nil rhs1 = untpd.Block(paramDefs, rhs1) } + vparamss1 = vparamss1.mapConserve(_.filterConserve(!_.symbol.is(Flags.Unused))) vparamss1 = vparamss1.mapConserve(_.filterConserve(vparam => !wasPhantom(vparam.tpe))) if (sym.is(Flags.ParamAccessor) && wasPhantom(ddef.tpt.tpe)) { sym.resetFlag(Flags.ParamAccessor) diff --git a/compiler/src/dotty/tools/dotc/transform/FullParameterization.scala b/compiler/src/dotty/tools/dotc/transform/FullParameterization.scala index a950bf7c4f49..3cde35a6cbec 100644 --- a/compiler/src/dotty/tools/dotc/transform/FullParameterization.scala +++ b/compiler/src/dotty/tools/dotc/transform/FullParameterization.scala @@ -229,14 +229,17 @@ trait FullParameterization { .appliedToTypes(allInstanceTypeParams(originalDef, abstractOverClass).map(_.typeRef)) .appliedTo(This(originalDef.symbol.enclosingClass.asClass)) + def refOrDefault(tree: Tree): Tree = // use deafult values for + if (tree.symbol is Flags.Unused) tpd.defaultValue(tree.tpe) else ref(tree.symbol) + (if (!liftThisType) - fun.appliedToArgss(originalDef.vparamss.nestedMap(vparam => ref(vparam.symbol))) + fun.appliedToArgss(originalDef.vparamss.nestedMap(vparam => refOrDefault(vparam))) else { // this type could have changed on forwarding. Need to insert a cast. val args = (originalDef.vparamss, fun.tpe.paramInfoss).zipped.map((vparams, paramTypes) => (vparams, paramTypes).zipped.map((vparam, paramType) => { assert(vparam.tpe <:< paramType.widen) // type should still conform to widened type - ref(vparam.symbol).ensureConforms(paramType) + refOrDefault(vparam).ensureConforms(paramType) }) ) fun.appliedToArgss(args) diff --git a/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala b/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala index 0086680d69e2..eea3c42aa9a3 100644 --- a/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala +++ b/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala @@ -239,8 +239,13 @@ object GenericSignatures { methodResultSig(restpe) case mtpe: MethodType => - // phantom method parameters do not make it to the bytecode. - val params = mtpe.paramInfoss.flatten.filterNot(_.isPhantom) + // unused method parameters do not make it to the bytecode. + def effectiveParamInfoss(t: Type)(implicit ctx: Context): List[List[Type]] = t match { + case t: MethodType if t.isUnusedMethod => effectiveParamInfoss(t.resType) + case t: MethodType => t.paramInfos.filterNot(_.isPhantom) :: effectiveParamInfoss(t.resType) + case _ => Nil + } + val params = effectiveParamInfoss(mtpe).flatten val restpe = mtpe.finalResultType builder.append('(') // TODO: Update once we support varargs diff --git a/compiler/src/dotty/tools/dotc/transform/Mixin.scala b/compiler/src/dotty/tools/dotc/transform/Mixin.scala index 712eafc6a3eb..0844b3f005e1 100644 --- a/compiler/src/dotty/tools/dotc/transform/Mixin.scala +++ b/compiler/src/dotty/tools/dotc/transform/Mixin.scala @@ -182,7 +182,7 @@ class Mixin extends MiniPhase with SymTransformer { thisPhase => if (defn.NotRuntimeClasses.contains(baseCls) || baseCls.is(NoInitsTrait)) Nil else call :: Nil case None => - if (baseCls.is(NoInitsTrait) || defn.NoInitClasses.contains(baseCls)) Nil + if (defn.isNoInitClass(baseCls)) Nil else { //println(i"synth super call ${baseCls.primaryConstructor}: ${baseCls.primaryConstructor.info}") transformFollowingDeep(superRef(baseCls.primaryConstructor).appliedToNone) :: Nil diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index a5c32c568687..db5bf4c9aac3 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -180,16 +180,18 @@ 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 => + chekedUnused(tree) handleMeta(tree.symbol) tree.tpe match { case tpe: ThisType => This(tpe.cls).withPos(tree.pos) case _ => tree } case tree @ Select(qual, name) => + chekedUnused(tree) handleMeta(tree.symbol) if (name.isTypeName) { Checking.checkRealizable(qual.tpe, qual.pos.focus) - super.transform(tree) + super.transform(tree)(ctx.addMode(Mode.Unused)) } else transformSelect(tree, Nil) @@ -198,14 +200,17 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase ctx.error(SuperCallsNotAllowedInline(ctx.owner), tree.pos) super.transform(tree) case tree: Apply => + def transformApply() = { + val ctx1 = if (tree.fun.tpe.widen.isUnusedMethod) ctx.addMode(Mode.Unused) else ctx + cpy.Apply(tree)(transform(tree.fun), transform(tree.args)(ctx1)) + } methPart(tree) match { case Select(nu: New, nme.CONSTRUCTOR) if isCheckable(nu) => // need to check instantiability here, because the type of the New itself // might be a type constructor. Checking.checkInstantiable(tree.tpe, nu.pos) - withNoCheckNews(nu :: Nil)(super.transform(tree)) - case _ => - super.transform(tree) + withNoCheckNews(nu :: Nil)(transformApply()) + case _ => transformApply() } case tree: TypeApply => val tree1 @ TypeApply(fn, args) = normalizeTypeArgs(tree) @@ -252,6 +257,14 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase ctx.compilationUnit.source.exists && sym != defn.SourceFileAnnot) sym.addAnnotation(Annotation.makeSourceFile(ctx.compilationUnit.source.file.path)) + if (sym.is(Case)) { + tree.rhs match { + case rhs: Template => + for (param <- rhs.constr.vparamss.head if param.symbol.is(Unused)) + ctx.error("First parameter list of case classes may not contain `unused` parameters", param.pos) + case _ => + } + } tree } super.transform(tree) @@ -310,5 +323,10 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase println(i"error while transforming $tree") throw ex } + + private def chekedUnused(tree: RefTree)(implicit ctx: Context): Unit = { + if (tree.symbol.is(Unused) && !ctx.mode.is(Mode.Unused)) + ctx.error(i"`unused` value $tree can only be used as unused arguments", tree.pos) + } } } diff --git a/compiler/src/dotty/tools/dotc/transform/SyntheticMethods.scala b/compiler/src/dotty/tools/dotc/transform/SyntheticMethods.scala index 89c566036bdc..077532141416 100644 --- a/compiler/src/dotty/tools/dotc/transform/SyntheticMethods.scala +++ b/compiler/src/dotty/tools/dotc/transform/SyntheticMethods.scala @@ -58,10 +58,10 @@ class SyntheticMethods(thisPhase: DenotTransformer) { /** The synthetic methods of the case or value class `clazz`. */ def syntheticMethods(clazz: ClassSymbol)(implicit ctx: Context): List[Tree] = { val clazzType = clazz.appliedRef - lazy val accessors = - if (isDerivedValueClass(clazz)) clazz.paramAccessors - else clazz.caseAccessors - + lazy val accessors = { + val allAccessors = if (isDerivedValueClass(clazz)) clazz.paramAccessors else clazz.caseAccessors + allAccessors.filterConserve(!_.is(Unused)) + } val symbolsToSynthesize: List[Symbol] = if (clazz.is(Case)) { if (clazz.is(Module)) caseModuleSymbols @@ -191,7 +191,7 @@ class SyntheticMethods(thisPhase: DenotTransformer) { */ def valueHashCodeBody(implicit ctx: Context): Tree = { assert(accessors.nonEmpty) - assert(accessors.tail.forall(_.info.isPhantom)) + assert(accessors.tail.forall(a => a.info.isPhantom || a.is(Unused))) ref(accessors.head).select(nme.hashCode_).ensureApplied } diff --git a/compiler/src/dotty/tools/dotc/transform/UnusedDecls.scala b/compiler/src/dotty/tools/dotc/transform/UnusedDecls.scala new file mode 100644 index 000000000000..d77904c6d908 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/UnusedDecls.scala @@ -0,0 +1,46 @@ +package dotty.tools.dotc.transform + +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.core.Contexts._ +import dotty.tools.dotc.core.DenotTransformers.InfoTransformer +import dotty.tools.dotc.core.Flags._ +import dotty.tools.dotc.core.Symbols._ +import dotty.tools.dotc.core.Phases.Phase +import dotty.tools.dotc.core.Types._ +import dotty.tools.dotc.transform.MegaPhase.MiniPhase + +/** This phase removes unused declarations of val`s (except for parameters). + * + * `unused val x = ...` are removed + */ +class UnusedDecls extends MiniPhase with InfoTransformer { + import tpd._ + + override def phaseName: String = "unusedDecls" + + override def runsAfterGroupsOf: Set[Class[_ <: Phase]] = Set( + classOf[PatternMatcher] // Make sure pattern match errors are emitted + ) + + /** Check what the phase achieves, to be called at any point after it is finished. */ + override def checkPostCondition(tree: Tree)(implicit ctx: Context): Unit = tree match { + case tree: ValOrDefDef if !tree.symbol.is(Param) => assert(!tree.symbol.is(Unused, butNot = Param)) + case _ => + } + + + /* Tree transform */ + + override def transformValDef(tree: ValDef)(implicit ctx: Context): Tree = + if (tree.symbol.is(Unused, butNot = Param)) EmptyTree else tree + + + /* Info transform */ + + override def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = tp match { + case tp: ClassInfo => + if (tp.classSymbol.is(JavaDefined) || !tp.decls.iterator.exists(_.is(Unused))) tp + else tp.derivedClassInfo(decls = tp.decls.filteredScope(!_.is(Unused))) + case _ => tp + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/UnusedRefs.scala b/compiler/src/dotty/tools/dotc/transform/UnusedRefs.scala new file mode 100644 index 000000000000..6eecbcb7d045 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/UnusedRefs.scala @@ -0,0 +1,54 @@ +package dotty.tools.dotc.transform + +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.ast.Trees._ +import dotty.tools.dotc.core.Contexts._ +import dotty.tools.dotc.core.Flags._ +import dotty.tools.dotc.core.Phases +import dotty.tools.dotc.core.Symbols._ +import dotty.tools.dotc.core.Types._ +import dotty.tools.dotc.transform.MegaPhase.MiniPhase + +/** This phase removes all references and calls to unused methods or vals + * + * if `unused def f(x1,...,xn): T = ...` + * then `f(y1,...,yn)` --> `y1; ...; yn; (default value for T)` + * + * if `unused val x: T = ...` including parameters + * then `x` --> `(default value for T)` + */ +class UnusedRefs extends MiniPhase { + import tpd._ + + override def phaseName: String = "unusedRefs" + + /** Check what the phase achieves, to be called at any point after it is finished. */ + override def checkPostCondition(tree: Tree)(implicit ctx: Context): Unit = tree match { + case _: Apply | _: TypeApply | _: RefTree => assert(!tree.symbol.is(Unused), tree) + case _ => + } + + /* Tree transform */ + + override def transformTypeApply(tree: TypeApply)(implicit ctx: Context): Tree = transformUnused(tree) + override def transformApply(tree: Apply)(implicit ctx: Context): Tree = transformUnused(tree) + override def transformIdent(tree: Ident)(implicit ctx: Context): Tree = transformUnused(tree) + override def transformSelect(tree: Select)(implicit ctx: Context): Tree = transformUnused(tree) + + private def transformUnused(tree: Tree)(implicit ctx: Context): Tree = { + if (!tree.symbol.is(Unused)) tree + else { + tree.tpe.widen match { + case _: MethodOrPoly => tree // Do the transformation higher in the tree if needed + case _ => + def qualAndArgs(t: Tree, acc: List[Tree]): List[Tree] = t match { + case TypeApply(fun, _) => qualAndArgs(fun, acc) + case Apply(fun, args) => qualAndArgs(fun, args ::: acc) + case Select(qual, _) => qual :: acc + case _ => acc + } + seq(qualAndArgs(tree, Nil), defaultValue(tree.tpe)) + } + } + } +} diff --git a/compiler/src/dotty/tools/dotc/transform/VCInlineMethods.scala b/compiler/src/dotty/tools/dotc/transform/VCInlineMethods.scala index 7daa374855a6..4d4e7eedeae3 100644 --- a/compiler/src/dotty/tools/dotc/transform/VCInlineMethods.scala +++ b/compiler/src/dotty/tools/dotc/transform/VCInlineMethods.scala @@ -45,7 +45,7 @@ class VCInlineMethods extends MiniPhase with IdentityDenotTransformer { override def phaseName: String = "vcInlineMethods" override def runsAfter: Set[Class[_ <: Phase]] = - Set(classOf[ExtensionMethods], classOf[PatternMatcher], classOf[PhantomArgLift]) + Set(classOf[ExtensionMethods], classOf[PatternMatcher]) /** Replace a value class method call by a call to the corresponding extension method. * diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index e269bcc99794..f91c25dd4f7b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1393,7 +1393,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => val alts1 = alts filter pt.isMatchedBy resolveOverloaded(alts1, pt1, targs1) - case defn.FunctionOf(args, resultType, _) => + case defn.FunctionOf(args, resultType, _, _) => narrowByTypes(alts, args, resultType) case pt => @@ -1440,7 +1440,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => val formalsForArg: List[Type] = altFormals.map(_.head) def argTypesOfFormal(formal: Type): List[Type] = formal match { - case defn.FunctionOf(args, result, isImplicit) => args + case defn.FunctionOf(args, result, isImplicit, isUnused) => args case defn.PartialFunctionOf(arg, result) => arg :: Nil case _ => Nil } diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 4dd960e4fae3..029945e50003 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -383,6 +383,12 @@ object Checking { fail(CannotHaveSameNameAs(sym, cls, CannotHaveSameNameAs.CannotBeOverridden)) sym.setFlag(Private) // break the overriding relationship by making sym Private } + checkApplicable(UnusedType, !sym.is(UnusedType)) + if (sym.is(Unused)) { + checkApplicable(Unused, sym.is(Param) || sym.is(ParamAccessor)) + if (sym.info.widen.finalResultType.isBottomType) + fail("unused " + sym.showKind + " cannot have type Nothing") + } } /** Check the type signature of the symbol `M` defined by `tree` does not refer @@ -502,9 +508,11 @@ object Checking { ctx.error(ValueClassParameterMayNotBeAVar(clazz, param), param.pos) if (param.info.isPhantom) ctx.error("value class first parameter must not be phantom", param.pos) + else if (param.is(Unused)) + ctx.error("value class first parameter cannot be `unused`", param.pos) else { - for (p <- params if !p.info.isPhantom) - ctx.error("value class can only have one non phantom parameter", p.pos) + for (p <- params if !(p.info.isPhantom || p.is(Unused))) + ctx.error("value class can only have one non `unused` parameter", p.pos) } case Nil => ctx.error(ValueClassNeedsOneValParam(clazz), clazz.pos) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 16554609744b..390ceb0ab0ca 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -134,10 +134,10 @@ trait NamerContextOps { this: Context => def methodType(typeParams: List[Symbol], valueParamss: List[List[Symbol]], resultType: Type, isJava: Boolean = false)(implicit ctx: Context): Type = { val monotpe = (valueParamss :\ resultType) { (params, resultType) => - val make = - if (params.nonEmpty && (params.head is Implicit)) ImplicitMethodType - else if (isJava) JavaMethodType - else MethodType + val (isImplicit, isUnused) = + if (params.isEmpty) (false, false) + else (params.head is Implicit, params.head is Unused) + val make = MethodType.maker(isJava, isImplicit, isUnused) if (isJava) for (param <- params) if (param.info.isDirectRef(defn.ObjectClass)) param.info = defn.AnyType diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index fc56b72eef12..cd88753cdef3 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -759,16 +759,16 @@ class Typer extends Namer def typedFunctionType(tree: untpd.Function, pt: Type)(implicit ctx: Context) = { val untpd.Function(args, body) = tree - val isImplicit = tree match { - case _: untpd.ImplicitFunction => - if (args.length == 0) { - ctx.error(ImplicitFunctionTypeNeedsNonEmptyParameterList(), tree.pos) - false - } - else true - case _ => false + val (isImplicit, isUnused) = tree match { + case _: untpd.NonEmptyFunction if args.isEmpty => + ctx.error(FunctionTypeNeedsNonEmptyParameterList(), tree.pos) + (false, false) + case _: untpd.UnusedImplicitFunction => (true, true) + case _: untpd.ImplicitFunction => (true, false) + case _: untpd.UnusedFunction => (false, true) + case _ => (false, false) } - val funCls = defn.FunctionClass(args.length, isImplicit) + val funCls = defn.FunctionClass(args.length, isImplicit, isUnused) /** Typechecks dependent function type with given parameters `params` */ def typedDependent(params: List[ValDef])(implicit ctx: Context): Tree = { @@ -940,11 +940,13 @@ class Typer extends Namer ctx.error(ex"result type of closure is an underspecified SAM type $pt", tree.pos) TypeTree(pt) case _ => - if (!mt.isParamDependent) EmptyTree - else throw new java.lang.Error( - i"""internal error: cannot turn method type $mt into closure + if (mt.isParamDependent) { + throw new java.lang.Error( + i"""internal error: cannot turn method type $mt into closure |because it has internal parameter dependencies, |position = ${tree.pos}, raw type = ${mt.toString}""") // !!! DEBUG. Eventually, convert to an error? + } else if (mt.isUnusedMethod) TypeTree(mt.toFunctionType(env1.length)) + else EmptyTree } case tp => throw new java.lang.Error(i"internal error: closing over non-method $tp, pos = ${tree.pos}") @@ -1791,7 +1793,7 @@ class Typer extends Namer } protected def makeImplicitFunction(tree: untpd.Tree, pt: Type)(implicit ctx: Context): Tree = { - val defn.FunctionOf(formals, _, true) = pt.dropDependentRefinement + val defn.FunctionOf(formals, _, true, _) = pt.dropDependentRefinement val ifun = desugar.makeImplicitFunction(formals, tree) typr.println(i"make implicit function $tree / $pt ---> $ifun") typed(ifun, pt) diff --git a/compiler/test/dotty/tools/dotc/FromTastyTests.scala b/compiler/test/dotty/tools/dotc/FromTastyTests.scala index f6bc751464df..e48fa73198eb 100644 --- a/compiler/test/dotty/tools/dotc/FromTastyTests.scala +++ b/compiler/test/dotty/tools/dotc/FromTastyTests.scala @@ -29,7 +29,7 @@ class FromTastyTests extends ParallelTesting { val (step1, step2, step3) = compileTastyInDir("tests/pos", defaultOptions, blacklist = Set( "macro-deprecate-dont-touch-backquotedidents.scala", - + // Compiles wrong class "simpleClass.scala", @@ -137,6 +137,13 @@ class FromTastyTests extends ParallelTesting { "phantom-poly-3.scala", "phantom-poly-4.scala", + // Issue with JFunction1$mcI$sp/T + "unused-15.scala", + "unused-17.scala", + "unused-20.scala", + "unused-21.scala", + "unused-23.scala", + "unused-value-class.scala", ) ) step1.checkCompile() // Compile all files to generate the class files with tasty diff --git a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala index 9d950953f550..f933b6faaf79 100644 --- a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala +++ b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala @@ -883,7 +883,7 @@ class ErrorMessagesTests extends ErrorMessagesTest { implicit val ctx: Context = ictx assertMessageCount(2, messages) - messages.foreach(assertEquals(_, ImplicitFunctionTypeNeedsNonEmptyParameterList())) + messages.foreach(assertEquals(_, FunctionTypeNeedsNonEmptyParameterList())) } @Test def wrongNumberOfParameters = diff --git a/docs/docs/reference/unused-terms.md b/docs/docs/reference/unused-terms.md new file mode 100644 index 000000000000..e5e528756af2 --- /dev/null +++ b/docs/docs/reference/unused-terms.md @@ -0,0 +1,150 @@ +--- +layout: doc-page +title: "Unused Parameters" +--- + +Why unused parameters? +---------------------- +The following examples shows an implementation of a simple state machine which can be in a state `On` or `Off`. +The machine can change state from `Off` to `On` with `turnedOn` only if it is currently `Off`. This last constraint is +captured with the `IsOff[S]` implicit evidence which only exists for `IsOff[Off]`. +For example, not allowing calling `turnedOn` on in an `On` state as we would require an evidence of type `IsOff[On]` that will not be found. + +```scala +sealed trait State +final class On extends State +final class Off extends State + +@implicitNotFound("State is must be Off") +class IsOff[S <: State] +object IsOff { + implicit def isOff: IsOff[Off] = new IsOff[Off] +} + +class Machine[S <: State] { + def turnedOn(implicit ev: IsOff[S]): Machine[On] = new Machine[On] +} + +val m = new Machine[Off] +m.turnedOn +m.turnedOn.turnedOn // ERROR +// ^ +// State is must be Off +``` + +These constraint that only depend on the types at the call site are completly resolved at compile time and never used at runtime. +As these parameters are never used at runtime there is not real need to have them around, but they still need to be +present at runtime to be able to do separate compilation and retain binary compatiblity. Unused parameters are contractually +obligated to not be used at runtime, enforcing the essence of evidences on types and allows them to always be optimized away. + + +How to define unused parameter? +------------------------------- +Parameters of methods and functions can be declared as unused, placing `unused` at the start of the parameter list (like `implicit`). + +```scala +def methodWithUnusedEv(unused ev: Ev): Int = 42 + +val lambdaWithUnusedEv: unused Ev => Int = + unused (ev: Ev) => 42 +``` + +Those parameters will not be usable for computations, thought they can be used as arguments to other `unused` parameters. + +```scala +def methodWithUnusedInt1(unused i: Int): Int = + i + 42 // ERROR: can not use i + +def methodWithUnusedInt2(unused i: Int): Int = + methodWithUnusedInt1(i) // OK +``` + +Not only parameters can be marked as unused, `val` and `def` can also be marked with `unused`. These will also only be usable as arguments to `unused` parameters. + +```scala +unused val unusedEvidence: Ev = ... +methodWithUnusedEv(unusedEvidence) +``` + + +What happens with unused values at runtime? +------------------------------------------- +As `unused` are guaranteed not to be used in computations, they can and will be erased. + +```scala +// becomes def methodWithUnusedEv(): Int at runtime +def methodWithUnusedEv(unused ev: Ev): Int = ... + +def evidence1: Ev = ... +unused def unusedEvidence2: Ev = ... // does not exist at runtime +unused val unusedEvidence3: Ev = ... // does not exist at runtime + +// evidence1 is evaluated but the result is not passed to methodWithUnusedEv +methodWithUnusedEv(evidence1) + +// unusedEvidence2 is not evaluated and its result is not passed to methodWithUnusedEv +methodWithUnusedEv(unusedEvidence2) +``` + +State machine with unused evidence example +------------------------------------------ +The following examples is an extended implementation of a simple state machine which can be in a state `On` or `Off`. +The machine can change state from `Off` to `On` with `turnedOn` only if it is currently `Off`, +conversely from `On` to `Off` with `turnedOff` only if it is currently `On`. These last constraint are +captured with the `IsOff[S]` and `IsOn[S]` implicit evidence only exist for `IsOff[Off]` and `InOn[On]`. +For example, not allowing calling `turnedOff` on in an `Off` state as we would require an evidence `IsOn[Off]` +that will not be found. + +As the implicit evidences of `turnedOn` and `turnedOff` are not used in the bodies of those functions +we can mark them as `unused`. This will remove the evidence parameters at runtime, but we would still +evaluate the `isOn` and `isOff` implicits that where found as arguments. +As `isOn` and `isOff` are not used except as as `unused` arguments, we can mark them as `unused`, hence +removing the evaluation of the `isOn` and `isOff` evidences. + +```scala +import scala.annotation.implicitNotFound + +sealed trait State +final class On extends State +final class Off extends State + +@implicitNotFound("State is must be Off") +class IsOff[S <: State] +object IsOff { + // def isOff will not exist at runtime + unused implicit def isOff: IsOff[Off] = new IsOff[Off] +} + +@implicitNotFound("State is must be On") +class IsOn[S <: State] +object IsOn { + // val isOn will not exist at runtime + unused implicit val isOn: IsOn[On] = new IsOn[On] +} + +class Machine[S <: State] private { + // ev will disapear from both functions + def turnedOn(implicit unused ev: IsOff[S]): Machine[On] = new Machine[On] + def turnedOff(implicit unused ev: IsOn[S]): Machine[Off] = new Machine[Off] +} + +object Machine { + def newMachine(): Machine[Off] = new Machine[Off] +} + +object Test { + def main(args: Array[String]): Unit = { + val m = Machine.newMachine() + m.turnedOn + m.turnedOn.turnedOff + + // m.turnedOff + // ^ + // State is must be On + + // m.turnedOn.turnedOn + // ^ + // State is must be Off + } +} +``` diff --git a/tests/generic-java-signatures/unused.check b/tests/generic-java-signatures/unused.check new file mode 100644 index 000000000000..4cf5a4d8612c --- /dev/null +++ b/tests/generic-java-signatures/unused.check @@ -0,0 +1,2 @@ +public int MyUnused$.f1() +U <: java.lang.Object diff --git a/tests/generic-java-signatures/unused.scala b/tests/generic-java-signatures/unused.scala new file mode 100644 index 000000000000..4ccf930a8500 --- /dev/null +++ b/tests/generic-java-signatures/unused.scala @@ -0,0 +1,14 @@ +object MyUnused { + def f1[U](unused a: Int): Int = 0 +} + +object Test { + def main(args: Array[String]): Unit = { + val f1 = MyUnused.getClass.getMethods.find(_.getName.endsWith("f1")).get + val tParams = f1.getTypeParameters + println(f1.toGenericString) + tParams.foreach { tp => + println(tp.getName + " <: " + tp.getBounds.map(_.getTypeName).mkString(", ")) + } + } +} diff --git a/tests/neg/unused-1.scala b/tests/neg/unused-1.scala new file mode 100644 index 000000000000..d1bc69e8ad1f --- /dev/null +++ b/tests/neg/unused-1.scala @@ -0,0 +1,13 @@ +object Test { + def foo0(a: Int): Int = a + def foo1(unused a: Int): Int = { + foo0(a) // error + foo0({ + println() + a // error + }) + foo1(a) // OK + a // error + a // error + } +} \ No newline at end of file diff --git a/tests/neg/unused-4.scala b/tests/neg/unused-4.scala new file mode 100644 index 000000000000..625f404c014a --- /dev/null +++ b/tests/neg/unused-4.scala @@ -0,0 +1,17 @@ +object Test { + + def main(args: Array[String]): Unit = { + val f: unused Int => Int = + unused (x: Int) => { + x // error + } + + val f2: unused Int => Int = + unused (x: Int) => { + foo(x) + } + + def foo (unused i: Int) = 0 + } + +} diff --git a/tests/neg/unused-5.scala b/tests/neg/unused-5.scala new file mode 100644 index 000000000000..3209a288f9a1 --- /dev/null +++ b/tests/neg/unused-5.scala @@ -0,0 +1,18 @@ +object Test { + + type UU[T] = unused T => Int + + def main(args: Array[String]): Unit = { + fun { x => + x // error: Cannot use `unused` value in a context that is not `unused` + } + + fun { + (x: Int) => x // error: `Int => Int` not compatible with `unused Int => Int` + } + } + + def fun(f: UU[Int]): Int = { + f(35) + } +} diff --git a/tests/neg/unused-6.scala b/tests/neg/unused-6.scala new file mode 100644 index 000000000000..87b6dda92806 --- /dev/null +++ b/tests/neg/unused-6.scala @@ -0,0 +1,13 @@ +object Test { + def f(unused foo: Foo) = { + foo.x() // error + foo.y // error + foo.z // error + } +} + +class Foo { + def x(): String = "abc" + def y: String = "abc" + val z: String = "abc" +} diff --git a/tests/neg/unused-Nothing.scala b/tests/neg/unused-Nothing.scala new file mode 100644 index 000000000000..1341c1132ee8 --- /dev/null +++ b/tests/neg/unused-Nothing.scala @@ -0,0 +1,6 @@ +object Test { + unused val foo1: Nothing = ??? // error + unused def foo2: Nothing = ??? // error + unused def foo3(): Nothing = ??? // error + def foo4(unused a: Nothing): Unit = () // error +} \ No newline at end of file diff --git a/tests/neg/unused-args-lifted.scala b/tests/neg/unused-args-lifted.scala new file mode 100644 index 000000000000..fd0bdc4caeb9 --- /dev/null +++ b/tests/neg/unused-args-lifted.scala @@ -0,0 +1,16 @@ +object Test { + def f(unused bar: Int => Int) = { + def foo(a: Int)(b: Int, c: Int) = 42 + + def baz: Int = { + println(1) + 2 + } + + foo( + bar(baz) // error + )( + c = baz, b = baz // force all args to be lifted in vals befor the call + ) + } +} diff --git a/tests/neg/unused-assign.scala b/tests/neg/unused-assign.scala new file mode 100644 index 000000000000..d59bf48b6e6f --- /dev/null +++ b/tests/neg/unused-assign.scala @@ -0,0 +1,7 @@ +object Test { + var i: Int = 1 + def foo(unused a: Int): Int = { + i = a // error + 42 + } +} diff --git a/tests/neg/unused-case-class.scala b/tests/neg/unused-case-class.scala new file mode 100644 index 000000000000..35c4a3397028 --- /dev/null +++ b/tests/neg/unused-case-class.scala @@ -0,0 +1 @@ +case class Foo1(unused x: Int) // error TODO add custom error message diff --git a/tests/neg/unused-class.scala b/tests/neg/unused-class.scala new file mode 100644 index 000000000000..69962b14522c --- /dev/null +++ b/tests/neg/unused-class.scala @@ -0,0 +1 @@ +unused class Test // error diff --git a/tests/neg/unused-def-rhs.scala b/tests/neg/unused-def-rhs.scala new file mode 100644 index 000000000000..58a51a03d050 --- /dev/null +++ b/tests/neg/unused-def-rhs.scala @@ -0,0 +1,6 @@ +object Test { + def f(unused i: Int) = { + def j: Int = i // error + j + } +} diff --git a/tests/neg/unused-if-else.scala b/tests/neg/unused-if-else.scala new file mode 100644 index 000000000000..f1d68ded03b6 --- /dev/null +++ b/tests/neg/unused-if-else.scala @@ -0,0 +1,22 @@ +object Test { + var b = true + def foo(unused a: Boolean): Boolean = { + if (a) // error + true + else + false + + if ({ + println() + a // error + }) + true + else + false + + if (b) + a // error + else + a // error + } +} diff --git a/tests/neg/unused-lazy-val.scala b/tests/neg/unused-lazy-val.scala new file mode 100644 index 000000000000..8d7f2212b0ed --- /dev/null +++ b/tests/neg/unused-lazy-val.scala @@ -0,0 +1,3 @@ +object Test { + unused lazy val i: Int = 1 // error +} diff --git a/tests/neg/unused-match.scala b/tests/neg/unused-match.scala new file mode 100644 index 000000000000..9de8ee1f2bd4 --- /dev/null +++ b/tests/neg/unused-match.scala @@ -0,0 +1,22 @@ +object Test { + var b = true + def foo(unused a: Int): Int = { + a match { // error + case _ => + } + + { + println() + a // error + } match { + case _ => + } + + b match { + case true => + a // error + case _ => + a // error + } + } +} diff --git a/tests/neg/unused-object.scala b/tests/neg/unused-object.scala new file mode 100644 index 000000000000..1eac930dc4e0 --- /dev/null +++ b/tests/neg/unused-object.scala @@ -0,0 +1 @@ +unused object Test // error diff --git a/tests/neg/unused-return.scala b/tests/neg/unused-return.scala new file mode 100644 index 000000000000..81eba6e0d9f7 --- /dev/null +++ b/tests/neg/unused-return.scala @@ -0,0 +1,13 @@ +object Test { + var b = true + def foo(unused a: Int): Int = { + if (b) + return a // error + else + return { + println() + a // error + } + 42 + } +} diff --git a/tests/neg/unused-trait.scala b/tests/neg/unused-trait.scala new file mode 100644 index 000000000000..f0f5d9026891 --- /dev/null +++ b/tests/neg/unused-trait.scala @@ -0,0 +1 @@ +unused trait Test // error diff --git a/tests/neg/unused-try.scala b/tests/neg/unused-try.scala new file mode 100644 index 000000000000..1d8ba2c417ca --- /dev/null +++ b/tests/neg/unused-try.scala @@ -0,0 +1,16 @@ +object Test { + def foo(unused a: Int): Int = { + try { + a // error + } catch { + case _ => 42 + } + } + def foo2(unused a: Int): Int = { + try { + 42 + } catch { + case _ => a // error + } + } +} diff --git a/tests/neg/unused-type.scala b/tests/neg/unused-type.scala new file mode 100644 index 000000000000..35a8bb034a6d --- /dev/null +++ b/tests/neg/unused-type.scala @@ -0,0 +1,3 @@ +class Test { + unused type T // error +} diff --git a/tests/neg/unused-val-rhs.scala b/tests/neg/unused-val-rhs.scala new file mode 100644 index 000000000000..ae10b3db9b98 --- /dev/null +++ b/tests/neg/unused-val-rhs.scala @@ -0,0 +1,6 @@ +object Test { + def f(unused i: Int) = { + val j: Int = i // error + () + } +} diff --git a/tests/neg/unused-value-class.scala b/tests/neg/unused-value-class.scala new file mode 100644 index 000000000000..8e1d2d9f4d88 --- /dev/null +++ b/tests/neg/unused-value-class.scala @@ -0,0 +1,4 @@ + +class Foo(unused x: Int) extends AnyVal // error + +class Bar(x: Int)(y: Int) extends AnyVal // error diff --git a/tests/neg/unused-var.scala b/tests/neg/unused-var.scala new file mode 100644 index 000000000000..6162b2b84e25 --- /dev/null +++ b/tests/neg/unused-var.scala @@ -0,0 +1,3 @@ +object Test { + unused var i: Int = 1 // error +} diff --git a/tests/pos/unused-args-lifted.scala b/tests/pos/unused-args-lifted.scala new file mode 100644 index 000000000000..1115cc9ce793 --- /dev/null +++ b/tests/pos/unused-args-lifted.scala @@ -0,0 +1,12 @@ +object Test { + def foo(unused a: Int)(b: Int, c: Int) = 42 + def bar(i: Int): Int = { + println(1) + 42 + } + def baz: Int = { + println(1) + 2 + } + foo(bar(baz))(c = baz, b = baz) // force all args to be lifted in vals befor the call +} diff --git a/tests/pos/unused-asInstanceOf.scala b/tests/pos/unused-asInstanceOf.scala new file mode 100644 index 000000000000..1a36d65c0a0c --- /dev/null +++ b/tests/pos/unused-asInstanceOf.scala @@ -0,0 +1,18 @@ + +trait Dataset { + def select(unused c: Column): Unit = () +} + +class Column + +object Test { + def main(args: Array[String]): Unit = { + + val ds: Dataset = ??? + + lazy val collD = new Column + + ds.select(collD) + + } +} diff --git a/tests/pos/unused-deep-context.scala b/tests/pos/unused-deep-context.scala new file mode 100644 index 000000000000..ff8f2b1e244c --- /dev/null +++ b/tests/pos/unused-deep-context.scala @@ -0,0 +1,16 @@ +object Test { + def outer1(): Int = { + def inner(unused a: Int): Int = 0 + inner(42) + } + + def outer2(): Int = { + def inner(unused b: Int): Int = { + def inner2(unused a: Int): Int = 0 + inner2(b) + } + inner(42) + 42 + } + +} \ No newline at end of file diff --git a/tests/pos/unused-extension-method.scala b/tests/pos/unused-extension-method.scala new file mode 100644 index 000000000000..b89e8bdac5ac --- /dev/null +++ b/tests/pos/unused-extension-method.scala @@ -0,0 +1,3 @@ +class IntDeco(x: Int) extends AnyVal { + def foo(unused y: Int) = x +} diff --git a/tests/pos/unused-pathdep-1.scala b/tests/pos/unused-pathdep-1.scala new file mode 100644 index 000000000000..b4b0d3db9802 --- /dev/null +++ b/tests/pos/unused-pathdep-1.scala @@ -0,0 +1,19 @@ +object Test { + + fun1(new Bar) + fun2(new Bar) + fun3(new Bar) + + def fun1[F >: Bar <: Foo](unused f: F): f.X = null.asInstanceOf[f.X] + def fun2[F >: Bar <: Foo](unused f: F)(unused bar: f.B): f.B = null.asInstanceOf[f.B] + def fun3[F >: Bar <: Foo](unused f: F)(unused b: f.B): b.X = null.asInstanceOf[b.X] +} + +class Foo { + type X + type B <: Bar +} + +class Bar extends Foo { + type X = String +} diff --git a/tests/pos/unused-pathdep-2.scala b/tests/pos/unused-pathdep-2.scala new file mode 100644 index 000000000000..adbcb5250821 --- /dev/null +++ b/tests/pos/unused-pathdep-2.scala @@ -0,0 +1,19 @@ +object Test { + + type F >: Bar <: Foo + + class A(unused val f: F) { + type F1 <: f.X + type F2[Z <: f.X] + } + +} + +class Foo { + type X + type B <: Bar +} + +class Bar extends Foo { + type X = String +} diff --git a/tests/run/unused-1.check b/tests/run/unused-1.check new file mode 100644 index 000000000000..3df46ad19028 --- /dev/null +++ b/tests/run/unused-1.check @@ -0,0 +1 @@ +fun diff --git a/tests/run/unused-1.scala b/tests/run/unused-1.scala new file mode 100644 index 000000000000..9e08936459f7 --- /dev/null +++ b/tests/run/unused-1.scala @@ -0,0 +1,14 @@ +object Test { + + def main(args: Array[String]): Unit = { + fun(foo) + } + + def foo = { + println("foo") + 42 + } + def fun(unused boo: Int): Unit = { + println("fun") + } +} diff --git a/tests/run/unused-10.check b/tests/run/unused-10.check new file mode 100644 index 000000000000..5353be80d1af --- /dev/null +++ b/tests/run/unused-10.check @@ -0,0 +1,2 @@ +fun +pacFun4 diff --git a/tests/run/unused-10.scala b/tests/run/unused-10.scala new file mode 100644 index 000000000000..1838546777b2 --- /dev/null +++ b/tests/run/unused-10.scala @@ -0,0 +1,20 @@ +object Test { + + def main(args: Array[String]): Unit = { + fun2.pacFun4(inky) + } + + def pacFun4(unused clyde: Int) = { + println("pacFun4") + } + + def inky: Int = { + println("inky") + 42 + } + + def fun2 = { + println("fun") + this + } +} diff --git a/tests/run/unused-11.check b/tests/run/unused-11.check new file mode 100644 index 000000000000..f252a04b6ab5 --- /dev/null +++ b/tests/run/unused-11.check @@ -0,0 +1,4 @@ +fun +Fun +Fun2 +Fun2fun diff --git a/tests/run/unused-11.scala b/tests/run/unused-11.scala new file mode 100644 index 000000000000..3248b59aa46d --- /dev/null +++ b/tests/run/unused-11.scala @@ -0,0 +1,31 @@ +object Test { + + def main(args: Array[String]): Unit = { + fun({ println("x1"); boo })({ println("x2"); boo }) + + new Fun({ println("y1"); boo })({ println("y2"); boo }) + + (new Fun2().fun)({ println("z1"); boo })({ println("z2"); boo }) + } + + def fun(unused x1: Int)(unused x2: Int) = { + println("fun") + } + + class Fun(unused y1: Int)(unused y2: Int) { + println("Fun") + } + + class Fun2 { + println("Fun2") + def fun(unused z1: Int)(unused z2: Int) = { + println("Fun2fun") + } + } + + def boo: Int = { + println("boo") + 42 + } + +} diff --git a/tests/run/unused-12.check b/tests/run/unused-12.check new file mode 100644 index 000000000000..7da1c2a62576 --- /dev/null +++ b/tests/run/unused-12.check @@ -0,0 +1,4 @@ +foo +Foo +foo +Foo diff --git a/tests/run/unused-12.scala b/tests/run/unused-12.scala new file mode 100644 index 000000000000..8bfd5dc030af --- /dev/null +++ b/tests/run/unused-12.scala @@ -0,0 +1,17 @@ +object Test { + + def main(args: Array[String]): Unit = { + new Foo(foo)(foo) + Foo(foo)(foo) + } + + def foo: Int = { + println("foo") + 42 + } + +} + +case class Foo(a: Int)(unused b: Int) { + println("Foo") +} diff --git a/tests/run/unused-13.check b/tests/run/unused-13.check new file mode 100644 index 000000000000..257cc5642cb1 --- /dev/null +++ b/tests/run/unused-13.check @@ -0,0 +1 @@ +foo diff --git a/tests/run/unused-13.scala b/tests/run/unused-13.scala new file mode 100644 index 000000000000..55fc9e37d3a3 --- /dev/null +++ b/tests/run/unused-13.scala @@ -0,0 +1,15 @@ +object Test { + + def main(args: Array[String]): Unit = { + lazy val x = { + println("x") + 42 + } + foo(x) + } + + def foo(unused a: Int) = { + println("foo") + } + +} diff --git a/tests/run/unused-15.check b/tests/run/unused-15.check new file mode 100644 index 000000000000..f1880f44381b --- /dev/null +++ b/tests/run/unused-15.check @@ -0,0 +1 @@ +Foo.apply diff --git a/tests/run/unused-15.scala b/tests/run/unused-15.scala new file mode 100644 index 000000000000..8b99563e8d97 --- /dev/null +++ b/tests/run/unused-15.scala @@ -0,0 +1,18 @@ +object Test { + + def main(args: Array[String]): Unit = { + new Foo().apply(foo) + } + + def foo = { + println("foo") + 42 + } +} + +class Foo extends UnusedFunction1[Int, Int] { + def apply(unused x: Int): Int = { + println("Foo.apply") + 42 + } +} diff --git a/tests/run/unused-16.check b/tests/run/unused-16.check new file mode 100644 index 000000000000..1a9d6a34050c --- /dev/null +++ b/tests/run/unused-16.check @@ -0,0 +1 @@ +Bar.foo diff --git a/tests/run/unused-16.scala b/tests/run/unused-16.scala new file mode 100644 index 000000000000..472455cca469 --- /dev/null +++ b/tests/run/unused-16.scala @@ -0,0 +1,22 @@ +object Test { + + def main(args: Array[String]): Unit = { + new Bar().foo(foo) + } + + def foo = { + println("foo") + 42 + } +} + +class Foo { + def foo(unused x: Int): Int = 42 +} + +class Bar extends Foo { + override def foo(unused x: Int): Int = { + println("Bar.foo") + 42 + } +} \ No newline at end of file diff --git a/tests/run/unused-17.check b/tests/run/unused-17.check new file mode 100644 index 000000000000..13f74ac1d904 --- /dev/null +++ b/tests/run/unused-17.check @@ -0,0 +1 @@ +lambda diff --git a/tests/run/unused-17.scala b/tests/run/unused-17.scala new file mode 100644 index 000000000000..cb1f340689b1 --- /dev/null +++ b/tests/run/unused-17.scala @@ -0,0 +1,13 @@ +object Test { + + def main(args: Array[String]): Unit = { + val f: unused Int => Int = + unused (x: Int) => { println("lambda"); 42 } + f(foo) + } + + def foo = { + println("foo") + 42 + } +} diff --git a/tests/run/unused-18.check b/tests/run/unused-18.check new file mode 100644 index 000000000000..13f74ac1d904 --- /dev/null +++ b/tests/run/unused-18.check @@ -0,0 +1 @@ +lambda diff --git a/tests/run/unused-18.scala b/tests/run/unused-18.scala new file mode 100644 index 000000000000..d1ae64829372 --- /dev/null +++ b/tests/run/unused-18.scala @@ -0,0 +1,16 @@ +object Test { + + def main(args: Array[String]): Unit = { + ( + unused (x: Int) => { + println("lambda") + 42 + } + )(foo) + } + + def foo = { + println("foo") + 42 + } +} diff --git a/tests/run/unused-19.check b/tests/run/unused-19.check new file mode 100644 index 000000000000..9766475a4185 --- /dev/null +++ b/tests/run/unused-19.check @@ -0,0 +1 @@ +ok diff --git a/tests/run/unused-19.scala b/tests/run/unused-19.scala new file mode 100644 index 000000000000..70cf1d355c13 --- /dev/null +++ b/tests/run/unused-19.scala @@ -0,0 +1,10 @@ +object Test { + + def main(args: Array[String]): Unit = { + { + unused (x: Int) => 42 + } + + println("ok") + } +} diff --git a/tests/run/unused-2.check b/tests/run/unused-2.check new file mode 100644 index 000000000000..1971d0bd23a4 --- /dev/null +++ b/tests/run/unused-2.check @@ -0,0 +1,2 @@ +fun +OK diff --git a/tests/run/unused-2.scala b/tests/run/unused-2.scala new file mode 100644 index 000000000000..1ca61c5da1f7 --- /dev/null +++ b/tests/run/unused-2.scala @@ -0,0 +1,18 @@ +object Test { + + def main(args: Array[String]): Unit = { + + def !!! : Null = ??? + + try { + fun(!!!) + println("OK") + } catch { + case e: NotImplementedError => + } + } + + def fun(unused bottom: Null): Unit = { + println("fun") + } +} diff --git a/tests/run/unused-20.check b/tests/run/unused-20.check new file mode 100644 index 000000000000..13f74ac1d904 --- /dev/null +++ b/tests/run/unused-20.check @@ -0,0 +1 @@ +lambda diff --git a/tests/run/unused-20.scala b/tests/run/unused-20.scala new file mode 100644 index 000000000000..3984873a5925 --- /dev/null +++ b/tests/run/unused-20.scala @@ -0,0 +1,14 @@ +object Test { + + def main(args: Array[String]): Unit = { + fun { unused (x: Int) => + println("lambda") + "abc" + } + + } + + def fun(f: unused Int => String): String = { + f(35) + } +} diff --git a/tests/run/unused-21.check b/tests/run/unused-21.check new file mode 100644 index 000000000000..13f74ac1d904 --- /dev/null +++ b/tests/run/unused-21.check @@ -0,0 +1 @@ +lambda diff --git a/tests/run/unused-21.scala b/tests/run/unused-21.scala new file mode 100644 index 000000000000..3c38615faf26 --- /dev/null +++ b/tests/run/unused-21.scala @@ -0,0 +1,16 @@ +object Test { + + type UU[T] = unused T => Int + + def main(args: Array[String]): Unit = { + fun { unused x => + println("lambda") + 42 + } + + } + + def fun(f: UU[Int]): Int = { + f(35) + } +} diff --git a/tests/run/unused-22.check b/tests/run/unused-22.check new file mode 100644 index 000000000000..3e2c972a8273 --- /dev/null +++ b/tests/run/unused-22.check @@ -0,0 +1 @@ +fun1 diff --git a/tests/run/unused-22.scala b/tests/run/unused-22.scala new file mode 100644 index 000000000000..ac5739c6a12a --- /dev/null +++ b/tests/run/unused-22.scala @@ -0,0 +1,17 @@ + +object Test { + + def main(args: Array[String]): Unit = { + val a: Int = fun1 + () + } + + implicit def foo: Int = { + println("foo") + 42 + } + def fun1(implicit unused boo: Int): Int = { + println("fun1") + 43 + } +} diff --git a/tests/run/unused-23.check b/tests/run/unused-23.check new file mode 100644 index 000000000000..efbbe4dddf06 --- /dev/null +++ b/tests/run/unused-23.check @@ -0,0 +1,2 @@ +lambda1 +lambda2 diff --git a/tests/run/unused-23.scala b/tests/run/unused-23.scala new file mode 100644 index 000000000000..e65e2f072a42 --- /dev/null +++ b/tests/run/unused-23.scala @@ -0,0 +1,22 @@ +object Test { + + def main(args: Array[String]): Unit = { + fun { implicit unused (x: Int) => + println("lambda1") + "abc" + } + + fun2 { unused implicit (x: Int) => + println("lambda2") + "abc" + } + } + + def fun(f: implicit unused Int => String): String = { + f(35) + } + + def fun2(f: unused implicit Int => String): String = { + f(36) + } +} diff --git a/tests/run/unused-24.check b/tests/run/unused-24.check new file mode 100644 index 000000000000..19765bd501b6 --- /dev/null +++ b/tests/run/unused-24.check @@ -0,0 +1 @@ +null diff --git a/tests/run/unused-24.scala b/tests/run/unused-24.scala new file mode 100644 index 000000000000..45d524d4c921 --- /dev/null +++ b/tests/run/unused-24.scala @@ -0,0 +1,23 @@ +object Test { + + def main(args: Array[String]): Unit = { + println(fun(new Bar)) + } + + def fun(unused foo: Foo): foo.X = { + null.asInstanceOf[foo.X] + } + + def fun2(unused foo: Foo)(unused bar: foo.B): bar.X = { + null.asInstanceOf[bar.X] + } +} + +class Foo { + type X + type B <: Bar +} + +class Bar extends Foo { + type X = String +} diff --git a/tests/run/unused-25.check b/tests/run/unused-25.check new file mode 100644 index 000000000000..8baef1b4abc4 --- /dev/null +++ b/tests/run/unused-25.check @@ -0,0 +1 @@ +abc diff --git a/tests/run/unused-25.scala b/tests/run/unused-25.scala new file mode 100644 index 000000000000..455dda180f62 --- /dev/null +++ b/tests/run/unused-25.scala @@ -0,0 +1,10 @@ +object Test { + def main(args: Array[String]): Unit = { + val ds: Dataset = new Dataset + println(ds.select(true).toString) + } +} + +class Dataset { + def select[A](unused c: Boolean): String = "abc" +} diff --git a/tests/run/unused-26.check b/tests/run/unused-26.check new file mode 100644 index 000000000000..8baef1b4abc4 --- /dev/null +++ b/tests/run/unused-26.check @@ -0,0 +1 @@ +abc diff --git a/tests/run/unused-26.scala b/tests/run/unused-26.scala new file mode 100644 index 000000000000..3c0b4d5a5807 --- /dev/null +++ b/tests/run/unused-26.scala @@ -0,0 +1,6 @@ +object Test { + def main(args: Array[String]): Unit = { + col("abc")(true) + } + def col[S](s: String)(unused ev: Boolean): Unit = println(s) +} diff --git a/tests/run/unused-27.check b/tests/run/unused-27.check new file mode 100644 index 000000000000..4413863feead --- /dev/null +++ b/tests/run/unused-27.check @@ -0,0 +1,3 @@ +block +x +foo diff --git a/tests/run/unused-27.scala b/tests/run/unused-27.scala new file mode 100644 index 000000000000..6fcc6d6acbba --- /dev/null +++ b/tests/run/unused-27.scala @@ -0,0 +1,15 @@ +object Test { + def main(args: Array[String]): Unit = { + ({ + println("block") + foo + })(x) + } + def foo(unused a: Int): Unit = { + println("foo") + } + def x: Int = { + println("x") + 42 + } +} diff --git a/tests/run/unused-28.check b/tests/run/unused-28.check new file mode 100644 index 000000000000..85733f6db2d7 --- /dev/null +++ b/tests/run/unused-28.check @@ -0,0 +1,4 @@ +x +foo +x +bar diff --git a/tests/run/unused-28.scala b/tests/run/unused-28.scala new file mode 100644 index 000000000000..bd567b4461fc --- /dev/null +++ b/tests/run/unused-28.scala @@ -0,0 +1,18 @@ +object Test { + var a = true + def main(args: Array[String]): Unit = { + (if (a) foo else bar)(x) + a = false + (if (a) foo else bar)(x) + } + def foo(unused a: Int): Unit = { + println("foo") + } + def bar(unused a: Int): Unit = { + println("bar") + } + def x: Int = { + println("x") + 42 + } +} diff --git a/tests/run/unused-3.check b/tests/run/unused-3.check new file mode 100644 index 000000000000..3df46ad19028 --- /dev/null +++ b/tests/run/unused-3.check @@ -0,0 +1 @@ +fun diff --git a/tests/run/unused-3.scala b/tests/run/unused-3.scala new file mode 100644 index 000000000000..57bb31237ad6 --- /dev/null +++ b/tests/run/unused-3.scala @@ -0,0 +1,21 @@ +object Test { + + def main(args: Array[String]): Unit = { + fun(foo1)(foo2) + } + + def foo1: Int = { + println("foo1") + 42 + } + + def foo2: String = { + println("foo2") + "abc" + } + + def fun(unused a: Int)(unused b: String): Unit = { + println("fun") + } + +} diff --git a/tests/run/unused-4.check b/tests/run/unused-4.check new file mode 100644 index 000000000000..8c49ff5454a0 --- /dev/null +++ b/tests/run/unused-4.check @@ -0,0 +1,4 @@ +foo1 +fun 42 +foo2 +fun2 abc diff --git a/tests/run/unused-4.scala b/tests/run/unused-4.scala new file mode 100644 index 000000000000..a2267bf491e9 --- /dev/null +++ b/tests/run/unused-4.scala @@ -0,0 +1,25 @@ +object Test { + + def main(args: Array[String]): Unit = { + fun(foo1)(foo2) + fun2(foo1)(foo2) + } + + def foo1: Int = { + println("foo1") + 42 + } + + def foo2: String = { + println("foo2") + "abc" + } + + def fun(a: Int)(unused b: String): Unit = { + println("fun " + a) + } + + def fun2(unused a: Int)(b: String): Unit = { + println("fun2 " + b) + } +} diff --git a/tests/run/unused-5.check b/tests/run/unused-5.check new file mode 100644 index 000000000000..fee5b9b3d6b5 --- /dev/null +++ b/tests/run/unused-5.check @@ -0,0 +1,6 @@ +foo +foo +fun 1 3 +foo +foo +fun2 2 4 diff --git a/tests/run/unused-5.scala b/tests/run/unused-5.scala new file mode 100644 index 000000000000..1f2d76cd35c7 --- /dev/null +++ b/tests/run/unused-5.scala @@ -0,0 +1,20 @@ +object Test { + + def main(args: Array[String]): Unit = { + fun(foo(1))(foo(2))(foo(3))(foo(4)) + fun2(foo(1))(foo(2))(foo(3))(foo(4)) + } + + def foo(i: Int): Int = { + println("foo") + i + } + + def fun(a: Int)(unused b: Int)(c: Int)(unused d: Int): Unit = { + println("fun " + a + " " + c) + } + + def fun2(unused a2: Int)(b2: Int)(unused c2: Int)(d2: Int): Unit = { + println("fun2 " + b2 + " " + d2) + } +} diff --git a/tests/run/unused-6.check b/tests/run/unused-6.check new file mode 100644 index 000000000000..bc56c4d89448 --- /dev/null +++ b/tests/run/unused-6.check @@ -0,0 +1 @@ +Foo diff --git a/tests/run/unused-6.scala b/tests/run/unused-6.scala new file mode 100644 index 000000000000..514d8f551ece --- /dev/null +++ b/tests/run/unused-6.scala @@ -0,0 +1,16 @@ +object Test { + + def main(args: Array[String]): Unit = { + new Foo(foo) + } + + def foo: Int = { + println("foo") + 42 + } + +} + +class Foo(unused a: Int) { + println("Foo") +} diff --git a/tests/run/unused-7.check b/tests/run/unused-7.check new file mode 100644 index 000000000000..9c6fd8b10c4d --- /dev/null +++ b/tests/run/unused-7.check @@ -0,0 +1,2 @@ +foo +Foo diff --git a/tests/run/unused-7.scala b/tests/run/unused-7.scala new file mode 100644 index 000000000000..cd4f39844d66 --- /dev/null +++ b/tests/run/unused-7.scala @@ -0,0 +1,19 @@ +object Test { + + def main(args: Array[String]): Unit = { + def f(unused i: Int) = { + new Foo(i)(foo) + } + f(5) + } + + def foo: Int = { + println("foo") + 42 + } + +} + +class Foo(unused a: Int)(b: Int) { + println("Foo") +} diff --git a/tests/run/unused-8.check b/tests/run/unused-8.check new file mode 100644 index 000000000000..9c6fd8b10c4d --- /dev/null +++ b/tests/run/unused-8.check @@ -0,0 +1,2 @@ +foo +Foo diff --git a/tests/run/unused-8.scala b/tests/run/unused-8.scala new file mode 100644 index 000000000000..52774d27728c --- /dev/null +++ b/tests/run/unused-8.scala @@ -0,0 +1,19 @@ +object Test { + + def main(args: Array[String]): Unit = { + def f(unused i: Int) = { + new Foo(foo)(i) + } + f(foo) + } + + def foo: Int = { + println("foo") + 42 + } + +} + +class Foo(a: Int)(unused b: Int) { + println("Foo") +} diff --git a/tests/run/unused-9.check b/tests/run/unused-9.check new file mode 100644 index 000000000000..3df46ad19028 --- /dev/null +++ b/tests/run/unused-9.check @@ -0,0 +1 @@ +fun diff --git a/tests/run/unused-9.scala b/tests/run/unused-9.scala new file mode 100644 index 000000000000..46202ee66287 --- /dev/null +++ b/tests/run/unused-9.scala @@ -0,0 +1,15 @@ +object Test { + + def main(args: Array[String]): Unit = { + fun(foo) + } + + def foo: Int = { + println("foo") + 42 + } + + def fun[T](unused x: T): Unit = { + println("fun") + } +} diff --git a/tests/run/unused-frameless.check b/tests/run/unused-frameless.check new file mode 100644 index 000000000000..41d0e66f879f --- /dev/null +++ b/tests/run/unused-frameless.check @@ -0,0 +1,2 @@ +selecting `d` from Vector(X4(1,s,1.1,true), X4(2,t,1.2,false)) +end diff --git a/tests/run/unused-frameless.scala b/tests/run/unused-frameless.scala new file mode 100644 index 000000000000..31aebaa786a4 --- /dev/null +++ b/tests/run/unused-frameless.scala @@ -0,0 +1,120 @@ +import scala.annotation.implicitNotFound + +// Subset of shapeless +// ---------------------------------------------------------------------------- + +sealed trait HList +sealed trait HNil extends HList +final case object HNil extends HNil +final case class ::[H, T <: HList](h: H, t: T) extends HList + +/** Generic representation os type T as a labelled sum of product. */ +trait LabelledGeneric[T] { + type Repr +} + +final case class R[K <: String, V](v: V) + +trait Selector[L <: HList, K, V] + +// Subset of Frameless +// ---------------------------------------------------------------------------- + +trait Dataset[T] { + + def select[A](c: Column[T, A]): Dataset[A] = new SelectedDataset[T, A](this, c) + // Use c.label to do an untyped select on actual Spark Dataset, and + // cast the result to TypedDataset[A] + + def col[S <: String, A](s: S)(implicit unused ev: Exists[T, s.type, A]) = + new Column[T, A](s) // ev is only here to check than this is safe, it's never used at runtime! + + def collect(): Vector[T] +} + +class SelectedDataset[T, A](ds: Dataset[T], val col: Column[T, A]) extends Dataset[A] { + def collect(): Vector[A] = { + // This would use collect of the underlying Spark structure plus a cast + ds match { // Dummy implementation + case SeqDataset(data) => + println(s"selecting `${col.label}` from $data") + col.label match { + case "a" => data.map(_.asInstanceOf[X4[A,_,_,_]].a).toVector + case "b" => data.map(_.asInstanceOf[X4[_,A,_,_]].b).toVector + case "c" => data.map(_.asInstanceOf[X4[_,_,A,_]].c).toVector + case "d" => data.map(_.asInstanceOf[X4[_,_,_,A]].d).toVector + } + } + } +} + +case class SeqDataset[T](data: Seq[T]) extends Dataset[T] { + override def collect(): Vector[T] = data.toVector +} + +object Dataset { + def create[T](values: Seq[T]): Dataset[T] = new SeqDataset[T](values) +} + +/** Expression used in `select`-like constructions. + * + * @tparam T type of dataset + * @tparam A type of column/expression + */ +case class Column[T, A](label: String) + +// Note: this type could be merged with Selector, but Selector comes from +// shapeless while this is frameless specific. +@implicitNotFound(msg = "No column ${K} in type ${T}") +trait Exists[T, K, V] + +object Exists { + implicit def derive[T, H <: HList, K, V](implicit g: LabelledGeneric[T] { type Repr = H }, s: Selector[H, K, V]): Exists[T, K, V] = { + println("Exists.derive") + null + } + + implicit def caseFound[T <: HList, K <: String, V]: Selector[R[K, V] :: T, K, V] = { + println("Selector.caseFound") + null + } + + implicit def caseRecur[H, T <: HList, K <: String, V](implicit i: Selector[T, K, V]): Selector[H :: T, K, V] = { + println("Selector.caseRecur") + null + } +} + +// X4 Example +// ---------------------------------------------------------------------------- + +case class X4[A, B, C, D](a: A, b: B, c: C, d: D) + +object X4 { + // Macro generated + implicit def x4Repr[A, B, C, D]: LabelledGeneric[X4[A, B, C, D]] { + type Repr = R["a", A] :: R["b", B] :: R["c", C] :: R["d", D] :: HNil + } = { + println("X4.x4Repr") + null + } +} + +object Test { + import Exists._ + + def main(args: Array[String]): Unit = { + val source: Vector[X4[Int, String, Double, Boolean]] = + Vector(X4(1, "s", 1.1, true), X4(2, "t", 1.2, false)) + val outColl : Vector[Boolean] = source.map(_.d) + + val ds: Dataset[X4[Int, String, Double, Boolean]] = + Dataset.create(source) + + val unusedD = ds.col("d") + val outSpark1: Vector[Boolean] = ds.select(unusedD).collect() + assert(outSpark1 == outColl) + + println("end") + } +} diff --git a/tests/run/unused-machine-state.check b/tests/run/unused-machine-state.check new file mode 100644 index 000000000000..f9d7929a8fc9 --- /dev/null +++ b/tests/run/unused-machine-state.check @@ -0,0 +1,4 @@ +newMachine +turnedOn +turnedOn +turnedOff diff --git a/tests/run/unused-machine-state.scala b/tests/run/unused-machine-state.scala new file mode 100644 index 000000000000..eba84ddeab38 --- /dev/null +++ b/tests/run/unused-machine-state.scala @@ -0,0 +1,57 @@ +import scala.annotation.implicitNotFound + +sealed trait State +final class On extends State +final class Off extends State + +@implicitNotFound("State is must be Off") +class IsOff[S <: State] +object IsOff { + implicit def isOff: IsOff[Off] = { + println("isOff") + new IsOff[Off] + } +} + +@implicitNotFound("State is must be On") +class IsOn[S <: State] +object IsOn { + implicit def isOn: IsOn[On] = { + println("isOn") + new IsOn[On] + } +} + +class Machine[S <: State] private { + def turnedOn(implicit unused s: IsOff[S]): Machine[On] = { + println("turnedOn") + new Machine[On] + } + def turnedOff(implicit unused s: IsOn[S]): Machine[Off] = { + println("turnedOff") + new Machine[Off] + } +} + +object Machine { + def newMachine(): Machine[Off] = { + println("newMachine") + new Machine[Off] + } +} + +object Test { + def main(args: Array[String]): Unit = { + val m = Machine.newMachine() + m.turnedOn + m.turnedOn.turnedOff + + // m.turnedOff + // ^ + // State is must be On + + // m.turnedOn.turnedOn + // ^ + // State is must be Off + } +} diff --git a/tests/run/unused-poly-ref.check b/tests/run/unused-poly-ref.check new file mode 100644 index 000000000000..3df46ad19028 --- /dev/null +++ b/tests/run/unused-poly-ref.check @@ -0,0 +1 @@ +fun diff --git a/tests/run/unused-poly-ref.scala b/tests/run/unused-poly-ref.scala new file mode 100644 index 000000000000..3598d5c9bca0 --- /dev/null +++ b/tests/run/unused-poly-ref.scala @@ -0,0 +1,15 @@ +object Test { + + def main(args: Array[String]): Unit = { + fun(foo(bar(5))(bar(6))) + } + + def fun(unused a: Int): Unit = println("fun") + + def foo[P](unused x: Int)(unused y: Int): Int = 0 + + def bar(x: Int) = { + println(x) + x + } +} diff --git a/tests/run/unused-value-class.check b/tests/run/unused-value-class.check new file mode 100644 index 000000000000..9eecade94af3 --- /dev/null +++ b/tests/run/unused-value-class.check @@ -0,0 +1,2 @@ +c +c diff --git a/tests/run/unused-value-class.scala b/tests/run/unused-value-class.scala new file mode 100644 index 000000000000..0bd3377194d0 --- /dev/null +++ b/tests/run/unused-value-class.scala @@ -0,0 +1,16 @@ +object Test { + + def main(args: Array[String]): Unit = { + new Bar(c)(c).foo() + identity(new Bar(c)(c)).foo() + } + + def c = { + println("c") + 3 + } +} + +class Bar(x: Int)(unused y: Int) extends AnyVal { + def foo() = x +} From ea4c48390cf3457038056d4c6e63a2aae7ba6844 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 16 Nov 2017 13:33:28 +0100 Subject: [PATCH 02/27] Transform unused arguments to dummy values in typer --- compiler/src/dotty/tools/dotc/Compiler.scala | 1 - compiler/src/dotty/tools/dotc/core/Mode.scala | 4 -- .../tools/dotc/transform/PostTyper.scala | 19 +++---- .../tools/dotc/transform/UnusedRefs.scala | 54 ------------------- .../dotty/tools/dotc/typer/Applications.scala | 11 +++- .../src/dotty/tools/dotc/typer/Typer.scala | 12 ++++- 6 files changed, 29 insertions(+), 72 deletions(-) delete mode 100644 compiler/src/dotty/tools/dotc/transform/UnusedRefs.scala diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 0ebd7d6409dd..81399f79ea57 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -63,7 +63,6 @@ class Compiler { new CheckReentrant, // Internal use only: Check that compiled program has no data races involving global vars new ElimPackagePrefixes) :: // Eliminate references to package prefixes in Select nodes List(new CheckStatic, // Check restrictions that apply to @static members - new UnusedRefs, // Removes all calls and references to unused values new ElimRepeated, // Rewrite vararg parameters and arguments new NormalizeFlags, // Rewrite some definition flags new ExtensionMethods, // Expand methods of value classes with extension methods diff --git a/compiler/src/dotty/tools/dotc/core/Mode.scala b/compiler/src/dotty/tools/dotc/core/Mode.scala index 790f2a85270d..6689a5371244 100644 --- a/compiler/src/dotty/tools/dotc/core/Mode.scala +++ b/compiler/src/dotty/tools/dotc/core/Mode.scala @@ -94,8 +94,4 @@ object Mode { /** We are in the IDE */ val Interactive = newMode(20, "Interactive") - /** We are currently in code that will not be used at runtime. - * It can be in an argument to an unused parameter or a type selection. - */ - val Unused = newMode(21, "Unused") } diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index db5bf4c9aac3..cd43758958fb 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -180,18 +180,18 @@ 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 => - chekedUnused(tree) + checkNotUnused(tree) handleMeta(tree.symbol) tree.tpe match { case tpe: ThisType => This(tpe.cls).withPos(tree.pos) case _ => tree } case tree @ Select(qual, name) => - chekedUnused(tree) + checkNotUnused(tree) handleMeta(tree.symbol) if (name.isTypeName) { Checking.checkRealizable(qual.tpe, qual.pos.focus) - super.transform(tree)(ctx.addMode(Mode.Unused)) + super.transform(tree)(ctx.addMode(Mode.Type)) } else transformSelect(tree, Nil) @@ -200,17 +200,14 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase ctx.error(SuperCallsNotAllowedInline(ctx.owner), tree.pos) super.transform(tree) case tree: Apply => - def transformApply() = { - val ctx1 = if (tree.fun.tpe.widen.isUnusedMethod) ctx.addMode(Mode.Unused) else ctx - cpy.Apply(tree)(transform(tree.fun), transform(tree.args)(ctx1)) - } methPart(tree) match { case Select(nu: New, nme.CONSTRUCTOR) if isCheckable(nu) => // need to check instantiability here, because the type of the New itself // might be a type constructor. Checking.checkInstantiable(tree.tpe, nu.pos) - withNoCheckNews(nu :: Nil)(transformApply()) - case _ => transformApply() + withNoCheckNews(nu :: Nil)(super.transform(tree)) + case _ => + super.transform(tree) } case tree: TypeApply => val tree1 @ TypeApply(fn, args) = normalizeTypeArgs(tree) @@ -324,8 +321,8 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase throw ex } - private def chekedUnused(tree: RefTree)(implicit ctx: Context): Unit = { - if (tree.symbol.is(Unused) && !ctx.mode.is(Mode.Unused)) + private def checkNotUnused(tree: RefTree)(implicit ctx: Context): Unit = { + if (tree.symbol.is(Unused) && !ctx.mode.is(Mode.Type)) ctx.error(i"`unused` value $tree can only be used as unused arguments", tree.pos) } } diff --git a/compiler/src/dotty/tools/dotc/transform/UnusedRefs.scala b/compiler/src/dotty/tools/dotc/transform/UnusedRefs.scala deleted file mode 100644 index 6eecbcb7d045..000000000000 --- a/compiler/src/dotty/tools/dotc/transform/UnusedRefs.scala +++ /dev/null @@ -1,54 +0,0 @@ -package dotty.tools.dotc.transform - -import dotty.tools.dotc.ast.tpd -import dotty.tools.dotc.ast.Trees._ -import dotty.tools.dotc.core.Contexts._ -import dotty.tools.dotc.core.Flags._ -import dotty.tools.dotc.core.Phases -import dotty.tools.dotc.core.Symbols._ -import dotty.tools.dotc.core.Types._ -import dotty.tools.dotc.transform.MegaPhase.MiniPhase - -/** This phase removes all references and calls to unused methods or vals - * - * if `unused def f(x1,...,xn): T = ...` - * then `f(y1,...,yn)` --> `y1; ...; yn; (default value for T)` - * - * if `unused val x: T = ...` including parameters - * then `x` --> `(default value for T)` - */ -class UnusedRefs extends MiniPhase { - import tpd._ - - override def phaseName: String = "unusedRefs" - - /** Check what the phase achieves, to be called at any point after it is finished. */ - override def checkPostCondition(tree: Tree)(implicit ctx: Context): Unit = tree match { - case _: Apply | _: TypeApply | _: RefTree => assert(!tree.symbol.is(Unused), tree) - case _ => - } - - /* Tree transform */ - - override def transformTypeApply(tree: TypeApply)(implicit ctx: Context): Tree = transformUnused(tree) - override def transformApply(tree: Apply)(implicit ctx: Context): Tree = transformUnused(tree) - override def transformIdent(tree: Ident)(implicit ctx: Context): Tree = transformUnused(tree) - override def transformSelect(tree: Select)(implicit ctx: Context): Tree = transformUnused(tree) - - private def transformUnused(tree: Tree)(implicit ctx: Context): Tree = { - if (!tree.symbol.is(Unused)) tree - else { - tree.tpe.widen match { - case _: MethodOrPoly => tree // Do the transformation higher in the tree if needed - case _ => - def qualAndArgs(t: Tree, acc: List[Tree]): List[Tree] = t match { - case TypeApply(fun, _) => qualAndArgs(fun, acc) - case Apply(fun, args) => qualAndArgs(fun, args ::: acc) - case Select(qual, _) => qual :: acc - case _ => acc - } - seq(qualAndArgs(tree, Nil), defaultValue(tree.tpe)) - } - } - } -} diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index f91c25dd4f7b..28d00abf6813 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -779,7 +779,16 @@ trait Applications extends Compatibility { self: Typer with Dynamic => checkCanEqual(left.tpe.widen, right.tpe.widen, app.pos) case _ => } - app + app match { + case Apply(fun, args) if fun.tpe.widen.isUnusedMethod => + val erasedArgs = args.map { arg => + if (!isPureExpr(arg)) + ctx.warning("This argument is given to an unused parameter. This expression will not be evaluated.", arg.pos) + defaultValue(arg.tpe) + } + tpd.cpy.Apply(app)(fun = fun, args = erasedArgs) + case _ => app + } } } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index cd88753cdef3..2996a00164ed 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2130,7 +2130,17 @@ class Typer extends Namer arg :: implicitArgs(formals1) } } - val args = implicitArgs(wtp.paramInfos) + def eraseUnusedArgs(args: List[Tree]): List[Tree] = { + if (!wtp.isUnusedMethod) args + else args.map { arg => + arg.tpe match { + case _: AmbiguousImplicits | _: SearchFailureType => arg + case tpe => defaultValue(tpe) + } + } + } + val args = eraseUnusedArgs(implicitArgs(wtp.paramInfos)) + def propagatedFailure(args: List[Tree]): Type = args match { case arg :: args1 => From cf52f934ffd62bf498f5b6fc187641c676838702 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 16 Nov 2017 14:35:03 +0100 Subject: [PATCH 03/27] Re-enable unused vals and defs --- .../tools/dotc/transform/UnusedDecls.scala | 5 ++- .../dotty/tools/dotc/typer/Applications.scala | 8 +--- .../src/dotty/tools/dotc/typer/Checking.scala | 2 +- .../src/dotty/tools/dotc/typer/Typer.scala | 5 ++- .../dotty/tools/dotc/typer/UnusedUtil.scala | 26 +++++++++++ docs/docs/reference/unused-terms.md | 12 ++--- tests/neg/unused-1.scala | 25 ++++++++++- tests/neg/unused-2.scala | 44 ++++++++++++++++++ tests/neg/unused-3.scala | 45 +++++++++++++++++++ tests/neg/unused-6.scala | 11 +++-- tests/neg/unused-args-lifted.scala | 26 +++++------ tests/neg/unused-assign.scala | 4 ++ tests/neg/unused-implicit.scala | 8 ++++ tests/run/unused-10.scala | 4 +- tests/run/unused-13.scala | 2 +- tests/run/unused-14.check | 1 + tests/run/unused-14.scala | 15 +++++++ tests/run/unused-2.scala | 2 +- tests/run/unused-select-prefix.check | 0 tests/run/unused-select-prefix.scala | 35 +++++++++++++++ 20 files changed, 237 insertions(+), 43 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/typer/UnusedUtil.scala create mode 100644 tests/neg/unused-2.scala create mode 100644 tests/neg/unused-3.scala create mode 100644 tests/neg/unused-implicit.scala create mode 100644 tests/run/unused-14.check create mode 100644 tests/run/unused-14.scala create mode 100644 tests/run/unused-select-prefix.check create mode 100644 tests/run/unused-select-prefix.scala diff --git a/compiler/src/dotty/tools/dotc/transform/UnusedDecls.scala b/compiler/src/dotty/tools/dotc/transform/UnusedDecls.scala index d77904c6d908..1a9eaf0019a2 100644 --- a/compiler/src/dotty/tools/dotc/transform/UnusedDecls.scala +++ b/compiler/src/dotty/tools/dotc/transform/UnusedDecls.scala @@ -31,7 +31,10 @@ class UnusedDecls extends MiniPhase with InfoTransformer { /* Tree transform */ - override def transformValDef(tree: ValDef)(implicit ctx: Context): Tree = + override def transformDefDef(tree: DefDef)(implicit ctx: Context): Tree = transformValOrDefDef(tree) + override def transformValDef(tree: ValDef)(implicit ctx: Context): Tree = transformValOrDefDef(tree) + + private def transformValOrDefDef(tree: ValOrDefDef)(implicit ctx: Context): Tree = if (tree.symbol.is(Unused, butNot = Param)) EmptyTree else tree diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 28d00abf6813..a8132748c86f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -23,6 +23,7 @@ import StdNames._ import NameKinds.DefaultGetterName import ProtoTypes._ import Inferencing._ +import UnusedUtil._ import collection.mutable import config.Printers.{overload, typr, unapp} @@ -781,12 +782,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => } app match { case Apply(fun, args) if fun.tpe.widen.isUnusedMethod => - val erasedArgs = args.map { arg => - if (!isPureExpr(arg)) - ctx.warning("This argument is given to an unused parameter. This expression will not be evaluated.", arg.pos) - defaultValue(arg.tpe) - } - tpd.cpy.Apply(app)(fun = fun, args = erasedArgs) + tpd.cpy.Apply(app)(fun = fun, args = args.map(arg => normalizeUnusedExpr(arg, "This argument is given to an unused parameter. "))) case _ => app } } diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 029945e50003..9da521e09552 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -385,7 +385,7 @@ object Checking { } checkApplicable(UnusedType, !sym.is(UnusedType)) if (sym.is(Unused)) { - checkApplicable(Unused, sym.is(Param) || sym.is(ParamAccessor)) + checkApplicable(Unused, !sym.is(MutableOrLazy)) if (sym.info.widen.finalResultType.isBottomType) fail("unused " + sym.showKind + " cannot have type Nothing") } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 2996a00164ed..9f0820b2797a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -29,6 +29,7 @@ import dotty.tools.dotc.transform.Erasure.Boxing import util.Positions._ import util.common._ import util.{Property, SourcePosition} +import UnusedUtil._ import collection.mutable import annotation.tailrec @@ -1344,7 +1345,7 @@ class Typer extends Namer val tpt1 = checkSimpleKinded(typedType(tpt)) val rhs1 = vdef.rhs match { case rhs @ Ident(nme.WILDCARD) => rhs withType tpt1.tpe - case rhs => typedExpr(rhs, tpt1.tpe) + case rhs => normalizeUnusedRhs(typedExpr(rhs, tpt1.tpe), sym) } val vdef1 = assignType(cpy.ValDef(vdef)(name, tpt1, rhs1), sym) if (sym.is(Inline, butNot = DeferredOrTermParamOrAccessor)) @@ -1402,7 +1403,7 @@ class Typer extends Namer (tparams1, sym.owner.typeParams).zipped.foreach ((tdef, tparam) => rhsCtx.gadt.setBounds(tdef.symbol, TypeAlias(tparam.typeRef))) } - val rhs1 = typedExpr(ddef.rhs, tpt1.tpe)(rhsCtx) + val rhs1 = normalizeUnusedRhs(typedExpr(ddef.rhs, tpt1.tpe)(rhsCtx), sym) // Overwrite inline body to make sure it is not evaluated twice if (sym.isInlineMethod) Inliner.registerInlineInfo(sym, _ => rhs1) diff --git a/compiler/src/dotty/tools/dotc/typer/UnusedUtil.scala b/compiler/src/dotty/tools/dotc/typer/UnusedUtil.scala new file mode 100644 index 000000000000..fcbfa45ba41a --- /dev/null +++ b/compiler/src/dotty/tools/dotc/typer/UnusedUtil.scala @@ -0,0 +1,26 @@ +package dotty.tools.dotc.typer + +import dotty.tools.dotc.ast.tpd._ +import dotty.tools.dotc.core.Contexts._ +import dotty.tools.dotc.core.Decorators._ +import dotty.tools.dotc.core.Symbols._ +import dotty.tools.dotc.core.Flags._ + +/** Util methods for transformation of `unused` expressions + * + * @author Nicolas Stucki + */ +object UnusedUtil { + + def normalizeUnusedExpr(tree: Tree, msg: String)(implicit ctx: Context): Tree = { + if (!isPureExpr(tree)) + ctx.warning(msg + "This expression will not be evaluated.", tree.pos) + defaultValue(tree.tpe) + } + + def normalizeUnusedRhs(tree: Tree, sym: Symbol)(implicit ctx: Context) = { + if (sym.is(Unused) && tree.tpe.exists) normalizeUnusedExpr(tree, "Expression is on the RHS of an `unused` " + sym.showKind + ". ") + else tree + } + +} diff --git a/docs/docs/reference/unused-terms.md b/docs/docs/reference/unused-terms.md index e5e528756af2..41d6d6729ad9 100644 --- a/docs/docs/reference/unused-terms.md +++ b/docs/docs/reference/unused-terms.md @@ -66,7 +66,6 @@ unused val unusedEvidence: Ev = ... methodWithUnusedEv(unusedEvidence) ``` - What happens with unused values at runtime? ------------------------------------------- As `unused` are guaranteed not to be used in computations, they can and will be erased. @@ -79,11 +78,8 @@ def evidence1: Ev = ... unused def unusedEvidence2: Ev = ... // does not exist at runtime unused val unusedEvidence3: Ev = ... // does not exist at runtime -// evidence1 is evaluated but the result is not passed to methodWithUnusedEv +// evidence1 is not evaluated and no value is passed to methodWithUnusedEv methodWithUnusedEv(evidence1) - -// unusedEvidence2 is not evaluated and its result is not passed to methodWithUnusedEv -methodWithUnusedEv(unusedEvidence2) ``` State machine with unused evidence example @@ -111,14 +107,14 @@ final class Off extends State @implicitNotFound("State is must be Off") class IsOff[S <: State] object IsOff { - // def isOff will not exist at runtime - unused implicit def isOff: IsOff[Off] = new IsOff[Off] + // def isOff will not be called at runtime for turnedOn, the compiler will only require that this evidence exists + implicit def isOff: IsOff[Off] = new IsOff[Off] } @implicitNotFound("State is must be On") class IsOn[S <: State] object IsOn { - // val isOn will not exist at runtime + // def isOn will not exist at runtime, the compiler will only require that this evidence exists at compile time unused implicit val isOn: IsOn[On] = new IsOn[On] } diff --git a/tests/neg/unused-1.scala b/tests/neg/unused-1.scala index d1bc69e8ad1f..49ebafb7ff16 100644 --- a/tests/neg/unused-1.scala +++ b/tests/neg/unused-1.scala @@ -1,13 +1,34 @@ object Test { def foo0(a: Int): Int = a def foo1(unused a: Int): Int = { - foo0(a) // error + foo0( + a // error + ) foo0({ println() a // error }) foo1(a) // OK + foo2( // error + a // error + ) + foo3( // error + a + ) a // error - a // error + } + unused def foo2(a: Int): Int = { + foo0(a) // OK + foo1(a) // OK + foo2(a) // OK + foo3(a) // OK + a // OK + } + unused def foo3(unused a: Int): Int = { + foo0(a) // OK + foo1(a) // OK + foo2(a) // OK + foo3(a) // OK + a // OK } } \ No newline at end of file diff --git a/tests/neg/unused-2.scala b/tests/neg/unused-2.scala new file mode 100644 index 000000000000..f071f5df063c --- /dev/null +++ b/tests/neg/unused-2.scala @@ -0,0 +1,44 @@ +object Test { + def foo0(a: Int): Int = a + def foo1(unused a: Int): Int = { + foo0( + u // error + ) + foo1(u) // OK + foo2( // error + u // error + ) + foo3( // error + u + ) + u // error + u // error + } + unused def foo2(a: Int): Int = { + foo0(u) // OK + foo1(u) // OK + foo2(u) // OK + foo3(u) // OK + u // warn + u // OK + } + unused def foo3(unused a: Int): Int = { + foo0(u) // OK + foo1(u) // OK + foo2(u) // OK + foo3(u) // OK + u // warn + u // OK + } + + unused val foo4: Int = { + foo0(u) // OK + foo1(u) // OK + foo2(u) // OK + foo3(u) // OK + u // warn + u // OK + } + + unused def u: Int = 42 +} \ No newline at end of file diff --git a/tests/neg/unused-3.scala b/tests/neg/unused-3.scala new file mode 100644 index 000000000000..db22487d0ae5 --- /dev/null +++ b/tests/neg/unused-3.scala @@ -0,0 +1,45 @@ +object Test { + def foo0(a: Int): Int = a + def foo1(unused a: Int): Int = { + foo0( + u() // error + ) + foo1(u()) // OK + foo2( // error + u() // error + ) + foo3( // error + u() + ) + u() // error + u() // error + } + unused def foo2(a: Int): Int = { + foo0(u()) // OK + foo1(u()) // OK + foo2(u()) // OK + foo3(u()) // OK + u() // warn + u() // OK + } + unused def foo3(unused a: Int): Int = { + foo0(u()) // OK + foo1(u()) // OK + foo2(u()) // OK + foo3(u()) // OK + u() // warn + u() // OK + } + + unused val foo4: Int = { + foo0(u()) // OK + foo1(u()) // OK + foo2(u()) // OK + foo3(u()) // OK + println() + u() // warn + u() // OK + } + + unused def u(): Int = 42 +} \ No newline at end of file diff --git a/tests/neg/unused-6.scala b/tests/neg/unused-6.scala index 87b6dda92806..eeb66055d1bf 100644 --- a/tests/neg/unused-6.scala +++ b/tests/neg/unused-6.scala @@ -1,13 +1,12 @@ object Test { - def f(unused foo: Foo) = { - foo.x() // error - foo.y // error - foo.z // error - } + unused def foo: Foo = new Foo + foo.x() // error + foo.y // error + foo.z // error } class Foo { def x(): String = "abc" def y: String = "abc" val z: String = "abc" -} +} \ No newline at end of file diff --git a/tests/neg/unused-args-lifted.scala b/tests/neg/unused-args-lifted.scala index fd0bdc4caeb9..a7ef65fbb6cb 100644 --- a/tests/neg/unused-args-lifted.scala +++ b/tests/neg/unused-args-lifted.scala @@ -1,16 +1,16 @@ object Test { - def f(unused bar: Int => Int) = { - def foo(a: Int)(b: Int, c: Int) = 42 - - def baz: Int = { - println(1) - 2 - } - - foo( - bar(baz) // error - )( - c = baz, b = baz // force all args to be lifted in vals befor the call - ) + def foo(a: Int)(b: Int, c: Int) = 42 + unused def bar(i: Int): Int = { + println(1) + 42 } + def baz: Int = { + println(1) + 2 + } + foo( + bar(baz) // error + )( + c = baz, b = baz // force all args to be lifted in vals befor the call + ) } diff --git a/tests/neg/unused-assign.scala b/tests/neg/unused-assign.scala index d59bf48b6e6f..6fa2cf257ef4 100644 --- a/tests/neg/unused-assign.scala +++ b/tests/neg/unused-assign.scala @@ -2,6 +2,10 @@ object Test { var i: Int = 1 def foo(unused a: Int): Int = { i = a // error + unused def r = { + i = a + () + } 42 } } diff --git a/tests/neg/unused-implicit.scala b/tests/neg/unused-implicit.scala new file mode 100644 index 000000000000..cfb244d2a04b --- /dev/null +++ b/tests/neg/unused-implicit.scala @@ -0,0 +1,8 @@ +object Test { + + fun // error + + def fun(implicit a: Double): Int = 42 + + unused implicit def doubleImplicit: Double = 42.0 +} diff --git a/tests/run/unused-10.scala b/tests/run/unused-10.scala index 1838546777b2..ab2850296eda 100644 --- a/tests/run/unused-10.scala +++ b/tests/run/unused-10.scala @@ -8,8 +8,8 @@ object Test { println("pacFun4") } - def inky: Int = { - println("inky") + unused def inky: Int = { + println("inky") // in erased function 42 } diff --git a/tests/run/unused-13.scala b/tests/run/unused-13.scala index 55fc9e37d3a3..638d41045962 100644 --- a/tests/run/unused-13.scala +++ b/tests/run/unused-13.scala @@ -1,7 +1,7 @@ object Test { def main(args: Array[String]): Unit = { - lazy val x = { + unused val x = { println("x") 42 } diff --git a/tests/run/unused-14.check b/tests/run/unused-14.check new file mode 100644 index 000000000000..bc56c4d89448 --- /dev/null +++ b/tests/run/unused-14.check @@ -0,0 +1 @@ +Foo diff --git a/tests/run/unused-14.scala b/tests/run/unused-14.scala new file mode 100644 index 000000000000..f891c155d04e --- /dev/null +++ b/tests/run/unused-14.scala @@ -0,0 +1,15 @@ +object Test { + + def main(args: Array[String]): Unit = { + new Foo + } + +} + +class Foo { + unused val x: Int = { + println("x") + 42 + } + println("Foo") +} diff --git a/tests/run/unused-2.scala b/tests/run/unused-2.scala index 1ca61c5da1f7..e90baa8c6f71 100644 --- a/tests/run/unused-2.scala +++ b/tests/run/unused-2.scala @@ -2,7 +2,7 @@ object Test { def main(args: Array[String]): Unit = { - def !!! : Null = ??? + unused def !!! : Null = ??? try { fun(!!!) diff --git a/tests/run/unused-select-prefix.check b/tests/run/unused-select-prefix.check new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/run/unused-select-prefix.scala b/tests/run/unused-select-prefix.scala new file mode 100644 index 000000000000..2729383f6b12 --- /dev/null +++ b/tests/run/unused-select-prefix.scala @@ -0,0 +1,35 @@ +object Test { + + def main(args: Array[String]): Unit = { + + bar({ + println("Test0") + Test + }.foo0) + + bar({ + println("Test1") + Test + }.foo1()) + + bar({ + println("Test2") + Test + }.foo2[Int]) + + bar({ + println("Test3") + Test + }.foo3[Int]()) + + () + } + + def bar(unused i: Int): Unit = () + + unused def foo0: Int = 0 + unused def foo1(): Int = 1 + unused def foo2[T]: Int = 2 + unused def foo3[T](): Int = 3 + +} From 85f9a7035d29a1fca06862ff78ff4399366043d8 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 11 Dec 2017 17:55:15 +0100 Subject: [PATCH 04/27] Move NoInit checks to Mixin --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 5 +---- compiler/src/dotty/tools/dotc/transform/Mixin.scala | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 64ef52bbfa62..10da942a8821 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -971,10 +971,7 @@ class Definitions { * trait gets screwed up. Therefore, it is mandatory that FunctionXXL * is treated as a NoInit trait. */ - private lazy val NoInitClasses = NotRuntimeClasses + FunctionXXLClass - - def isNoInitClass(cls: Symbol): Boolean = - cls.is(NoInitsTrait) || NoInitClasses.contains(cls) || isFunctionClass(cls) + lazy val NoInitClasses = NotRuntimeClasses + FunctionXXLClass def isPolymorphicAfterErasure(sym: Symbol) = (sym eq Any_isInstanceOf) || (sym eq Any_asInstanceOf) diff --git a/compiler/src/dotty/tools/dotc/transform/Mixin.scala b/compiler/src/dotty/tools/dotc/transform/Mixin.scala index 0844b3f005e1..c3236872d607 100644 --- a/compiler/src/dotty/tools/dotc/transform/Mixin.scala +++ b/compiler/src/dotty/tools/dotc/transform/Mixin.scala @@ -182,7 +182,7 @@ class Mixin extends MiniPhase with SymTransformer { thisPhase => if (defn.NotRuntimeClasses.contains(baseCls) || baseCls.is(NoInitsTrait)) Nil else call :: Nil case None => - if (defn.isNoInitClass(baseCls)) Nil + if (baseCls.is(NoInitsTrait) || defn.NoInitClasses.contains(baseCls) || defn.isFunctionClass(baseCls)) Nil else { //println(i"synth super call ${baseCls.primaryConstructor}: ${baseCls.primaryConstructor.info}") transformFollowingDeep(superRef(baseCls.primaryConstructor).appliedToNone) :: Nil From 5e774d7599340423e574f813bcc874b17effc903 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 11 Dec 2017 11:04:08 +0100 Subject: [PATCH 05/27] Add modifier to NonEmptyFunction and replace ImplicitFunction --- .../src/dotty/tools/dotc/ast/Desugar.scala | 2 +- compiler/src/dotty/tools/dotc/ast/untpd.scala | 21 +++---------------- .../dotty/tools/dotc/parsing/Parsers.scala | 6 ++---- .../src/dotty/tools/dotc/typer/Typer.scala | 12 +++++------ 4 files changed, 12 insertions(+), 29 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 6498568d23b9..c74f8f096699 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -825,7 +825,7 @@ object desugar { def makeImplicitFunction(formals: List[Type], body: Tree)(implicit ctx: Context): Tree = { val params = makeImplicitParameters(formals.map(TypeTree)) - new ImplicitFunction(params, body) + new NonEmptyFunction(params, body, Modifiers(Implicit)) } /** Add annotation to tree: diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 7d82fea395d4..a649b09c8fc5 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -50,29 +50,14 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { */ case class InterpolatedString(id: TermName, segments: List[Tree]) extends TermTree - /** An function type */ + /** A function type */ case class Function(args: List[Tree], body: Tree) extends Tree { override def isTerm = body.isTerm override def isType = body.isType } - /** An function type that should have non empty args */ - abstract class NonEmptyFunction(args: List[Tree], body: Tree) extends Function(args, body) - - /** An implicit function type */ - class ImplicitFunction(args: List[Tree], body: Tree) extends NonEmptyFunction(args, body) { - override def toString = s"ImplicitFunction($args, $body)" - } - - /** An function type with unused arguments */ - class UnusedFunction(args: List[Tree], body: Tree) extends NonEmptyFunction(args, body) { - override def toString = s"UnusedFunction($args, $body)" - } - - /** An implicit function type with unused arguments */ - class UnusedImplicitFunction(args: List[Tree], body: Tree) extends NonEmptyFunction(args, body) { - override def toString = s"UnusedImplicitFunction($args, $body)" - } + /** A function type that should have non empty args */ + class NonEmptyFunction(args: List[Tree], body: Tree, val mods: Modifiers) extends Function(args, body) /** A function created from a wildcard expression * @param placeHolderParams a list of definitions of synthetic parameters diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 94d53c221555..771ce7bc3031 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -727,9 +727,7 @@ object Parsers { def functionRest(params: List[Tree]): Tree = atPos(start, accept(ARROW)) { val t = typ() - if (imods.is(Implicit) && imods.is(Unused)) new UnusedImplicitFunction(params, t) - else if (imods.is(Implicit)) new ImplicitFunction(params, t) - else if (imods.is(Unused)) new UnusedFunction(params, t) + if (imods.is(Implicit) || imods.is(Unused)) new NonEmptyFunction(params, t, imods) else Function(params, t) } def funArgTypesRest(first: Tree, following: () => Tree) = { @@ -790,7 +788,7 @@ object Parsers { case ARROW => functionRest(t :: Nil) case FORSOME => syntaxError(ExistentialTypesNoLongerSupported()); t case _ => - if (imods.is(Implicit) && !t.isInstanceOf[ImplicitFunction]) + if (imods.is(Implicit) && !t.isInstanceOf[NonEmptyFunction]) syntaxError("Types with implicit keyword can only be function types", Position(start, start + nme.IMPLICITkw.asSimpleName.length)) t } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 9f0820b2797a..7ff2e5b810ce 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -761,12 +761,12 @@ class Typer extends Namer def typedFunctionType(tree: untpd.Function, pt: Type)(implicit ctx: Context) = { val untpd.Function(args, body) = tree val (isImplicit, isUnused) = tree match { - case _: untpd.NonEmptyFunction if args.isEmpty => - ctx.error(FunctionTypeNeedsNonEmptyParameterList(), tree.pos) - (false, false) - case _: untpd.UnusedImplicitFunction => (true, true) - case _: untpd.ImplicitFunction => (true, false) - case _: untpd.UnusedFunction => (false, true) + case tree: untpd.NonEmptyFunction => + if (args.nonEmpty) (tree.mods.is(Implicit), tree.mods.is(Unused)) + else { + ctx.error(FunctionTypeNeedsNonEmptyParameterList(), tree.pos) + (false, false) + } case _ => (false, false) } val funCls = defn.FunctionClass(args.length, isImplicit, isUnused) From 68ea63cc1ba594be3e942d238ea10d77d537e73a Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 11 Dec 2017 11:16:52 +0100 Subject: [PATCH 06/27] Document UnusedFunctionN and UnusedImplicitFunctionN --- .../src/dotty/tools/dotc/core/Definitions.scala | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 10da942a8821..72a69c85431d 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -88,7 +88,7 @@ class Definitions { newClassSymbol(ScalaPackageClass, name, EmptyFlags, completer).entered } - /** The trait FunctionN or ImplicitFunctionN, for some N + /** The trait FunctionN, ImplicitFunctionN, UnusedFunctionN or UnusedImplicitFunction, for some N * @param name The name of the trait to be created * * FunctionN traits follow this template: @@ -106,6 +106,20 @@ class Definitions { * trait ImplicitFunctionN[T0,...,T{N-1}, R] extends Object with FunctionN[T0,...,T{N-1}, R] { * def apply(implicit $x0: T0, ..., $x{N_1}: T{N-1}): R * } + * + * UnusedFunctionN traits follow this template: + * + * trait UnusedFunctionN[T0,...,T{N-1}, R] extends Object { + * def apply(unused $x0: T0, ..., $x{N_1}: T{N-1}): R + * } + * + * UnusedImplicitFunctionN traits follow this template: + * + * trait UnusedImplicitFunctionN[T0,...,T{N-1}, R] extends Object with UnusedFunctionN[T0,...,T{N-1}, R] { + * def apply(unused implicit $x0: T0, ..., $x{N_1}: T{N-1}): R + * } + * + * UnusedFunctionN and UnusedImplicitFunctionN erase to Function0. */ def newFunctionNTrait(name: TypeName): ClassSymbol = { val completer = new LazyType { From 7474ba3c22ad20f04b506673f57d99f38094f516 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 11 Dec 2017 11:23:55 +0100 Subject: [PATCH 07/27] Reformat code of UnusedFunctions in NameOpts --- compiler/src/dotty/tools/dotc/core/NameOps.scala | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/NameOps.scala b/compiler/src/dotty/tools/dotc/core/NameOps.scala index 0756f36df7b9..def42bca9c88 100644 --- a/compiler/src/dotty/tools/dotc/core/NameOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NameOps.scala @@ -174,7 +174,10 @@ object NameOps { */ def functionArity: Int = functionArityFor(str.Function) max { - val n = functionArityFor(str.ImplicitFunction) max functionArityFor(str.UnusedFunction) max functionArityFor(str.UnusedImplicitFunction) + val n = + functionArityFor(str.ImplicitFunction) max + functionArityFor(str.UnusedFunction) max + functionArityFor(str.UnusedImplicitFunction) if (n == 0) -1 else n } @@ -216,9 +219,8 @@ object NameOps { */ def isSyntheticFunction: Boolean = { functionArityFor(str.Function) > MaxImplementedFunctionArity || - functionArityFor(str.ImplicitFunction) >= 1 || - functionArityFor(str.UnusedFunction) >= 1 || - functionArityFor(str.UnusedImplicitFunction) >= 1 + functionArityFor(str.ImplicitFunction) >= 1 || + isUnusedFunction } /** Parsed function arity for function with some specific prefix */ From e755dd83f6fcc426016f9f3f45f9d366c10e41f1 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 12 Dec 2017 09:57:25 +0100 Subject: [PATCH 08/27] Remove unnecessary early removal of unused prameters --- .../dotty/tools/dotc/transform/FullParameterization.scala | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/FullParameterization.scala b/compiler/src/dotty/tools/dotc/transform/FullParameterization.scala index 3cde35a6cbec..a950bf7c4f49 100644 --- a/compiler/src/dotty/tools/dotc/transform/FullParameterization.scala +++ b/compiler/src/dotty/tools/dotc/transform/FullParameterization.scala @@ -229,17 +229,14 @@ trait FullParameterization { .appliedToTypes(allInstanceTypeParams(originalDef, abstractOverClass).map(_.typeRef)) .appliedTo(This(originalDef.symbol.enclosingClass.asClass)) - def refOrDefault(tree: Tree): Tree = // use deafult values for - if (tree.symbol is Flags.Unused) tpd.defaultValue(tree.tpe) else ref(tree.symbol) - (if (!liftThisType) - fun.appliedToArgss(originalDef.vparamss.nestedMap(vparam => refOrDefault(vparam))) + fun.appliedToArgss(originalDef.vparamss.nestedMap(vparam => ref(vparam.symbol))) else { // this type could have changed on forwarding. Need to insert a cast. val args = (originalDef.vparamss, fun.tpe.paramInfoss).zipped.map((vparams, paramTypes) => (vparams, paramTypes).zipped.map((vparam, paramType) => { assert(vparam.tpe <:< paramType.widen) // type should still conform to widened type - refOrDefault(vparam).ensureConforms(paramType) + ref(vparam.symbol).ensureConforms(paramType) }) ) fun.appliedToArgss(args) From 2177da5f72b9f2aa90d25f55f9b0a5941c10ea5f Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 12 Dec 2017 10:42:12 +0100 Subject: [PATCH 09/27] Move error for unused case accessors --- .../dotty/tools/dotc/transform/PostTyper.scala | 16 ++++++---------- tests/neg/unused-case-class.scala | 2 +- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index cd43758958fb..892439eabf59 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -254,14 +254,6 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase ctx.compilationUnit.source.exists && sym != defn.SourceFileAnnot) sym.addAnnotation(Annotation.makeSourceFile(ctx.compilationUnit.source.file.path)) - if (sym.is(Case)) { - tree.rhs match { - case rhs: Template => - for (param <- rhs.constr.vparamss.head if param.symbol.is(Unused)) - ctx.error("First parameter list of case classes may not contain `unused` parameters", param.pos) - case _ => - } - } tree } super.transform(tree) @@ -322,8 +314,12 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase } private def checkNotUnused(tree: RefTree)(implicit ctx: Context): Unit = { - if (tree.symbol.is(Unused) && !ctx.mode.is(Mode.Type)) - ctx.error(i"`unused` value $tree can only be used as unused arguments", tree.pos) + if (tree.symbol.is(Unused) && !ctx.mode.is(Mode.Type)) { + val msg = + if (tree.symbol.is(CaseAccessor)) "First parameter list of case class may not contain `unused` parameters" + else i"`unused` value $tree can only be used as unused arguments" + ctx.error(msg, tree.pos) + } } } } diff --git a/tests/neg/unused-case-class.scala b/tests/neg/unused-case-class.scala index 35c4a3397028..dc1382fb3636 100644 --- a/tests/neg/unused-case-class.scala +++ b/tests/neg/unused-case-class.scala @@ -1 +1 @@ -case class Foo1(unused x: Int) // error TODO add custom error message +case class Foo1(unused x: Int) // error From 724bf9c0ca79475246b7c56c94585e59273bfbb2 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 12 Dec 2017 10:48:57 +0100 Subject: [PATCH 10/27] Improve error message --- compiler/src/dotty/tools/dotc/transform/PostTyper.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 892439eabf59..5c3a0a5aafdb 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -317,7 +317,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase if (tree.symbol.is(Unused) && !ctx.mode.is(Mode.Type)) { val msg = if (tree.symbol.is(CaseAccessor)) "First parameter list of case class may not contain `unused` parameters" - else i"`unused` value $tree can only be used as unused arguments" + else i"${tree.symbol} is declared as unused, but is in fact used" ctx.error(msg, tree.pos) } } From f1aa9fc7578d3a975f720e836a2cbb48d2ac67a6 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 12 Dec 2017 10:49:30 +0100 Subject: [PATCH 11/27] Remove unnecessary removal of unused accessors --- .../dotty/tools/dotc/transform/SyntheticMethods.scala | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/SyntheticMethods.scala b/compiler/src/dotty/tools/dotc/transform/SyntheticMethods.scala index 077532141416..6eb3795f8371 100644 --- a/compiler/src/dotty/tools/dotc/transform/SyntheticMethods.scala +++ b/compiler/src/dotty/tools/dotc/transform/SyntheticMethods.scala @@ -58,10 +58,10 @@ class SyntheticMethods(thisPhase: DenotTransformer) { /** The synthetic methods of the case or value class `clazz`. */ def syntheticMethods(clazz: ClassSymbol)(implicit ctx: Context): List[Tree] = { val clazzType = clazz.appliedRef - lazy val accessors = { - val allAccessors = if (isDerivedValueClass(clazz)) clazz.paramAccessors else clazz.caseAccessors - allAccessors.filterConserve(!_.is(Unused)) - } + lazy val accessors = + if (isDerivedValueClass(clazz)) clazz.paramAccessors + else clazz.caseAccessors + val symbolsToSynthesize: List[Symbol] = if (clazz.is(Case)) { if (clazz.is(Module)) caseModuleSymbols @@ -191,7 +191,6 @@ class SyntheticMethods(thisPhase: DenotTransformer) { */ def valueHashCodeBody(implicit ctx: Context): Tree = { assert(accessors.nonEmpty) - assert(accessors.tail.forall(a => a.info.isPhantom || a.is(Unused))) ref(accessors.head).select(nme.hashCode_).ensureApplied } From ae8f80e56cbcf63492fac4060f3de26898bd075b Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 12 Dec 2017 11:03:29 +0100 Subject: [PATCH 12/27] Remove Nothing restriction on unused parameters --- compiler/src/dotty/tools/dotc/typer/Checking.scala | 5 +---- tests/neg/unused-Nothing.scala | 6 ------ tests/run/unused-2.scala | 4 ++-- 3 files changed, 3 insertions(+), 12 deletions(-) delete mode 100644 tests/neg/unused-Nothing.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 9da521e09552..252f06e7da76 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -384,11 +384,8 @@ object Checking { sym.setFlag(Private) // break the overriding relationship by making sym Private } checkApplicable(UnusedType, !sym.is(UnusedType)) - if (sym.is(Unused)) { + if (sym.is(Unused)) checkApplicable(Unused, !sym.is(MutableOrLazy)) - if (sym.info.widen.finalResultType.isBottomType) - fail("unused " + sym.showKind + " cannot have type Nothing") - } } /** Check the type signature of the symbol `M` defined by `tree` does not refer diff --git a/tests/neg/unused-Nothing.scala b/tests/neg/unused-Nothing.scala deleted file mode 100644 index 1341c1132ee8..000000000000 --- a/tests/neg/unused-Nothing.scala +++ /dev/null @@ -1,6 +0,0 @@ -object Test { - unused val foo1: Nothing = ??? // error - unused def foo2: Nothing = ??? // error - unused def foo3(): Nothing = ??? // error - def foo4(unused a: Nothing): Unit = () // error -} \ No newline at end of file diff --git a/tests/run/unused-2.scala b/tests/run/unused-2.scala index e90baa8c6f71..31b4ee9862cd 100644 --- a/tests/run/unused-2.scala +++ b/tests/run/unused-2.scala @@ -2,7 +2,7 @@ object Test { def main(args: Array[String]): Unit = { - unused def !!! : Null = ??? + unused def !!! : Nothing = ??? try { fun(!!!) @@ -12,7 +12,7 @@ object Test { } } - def fun(unused bottom: Null): Unit = { + def fun(unused bottom: Nothing): Unit = { println("fun") } } From 81ec5653b796d1f5753808e6010f9c3a435564fb Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 12 Dec 2017 11:09:28 +0100 Subject: [PATCH 13/27] Change error message --- compiler/src/dotty/tools/dotc/typer/Checking.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 252f06e7da76..4500539c852c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -504,9 +504,9 @@ object Checking { if (param.is(Mutable)) ctx.error(ValueClassParameterMayNotBeAVar(clazz, param), param.pos) if (param.info.isPhantom) - ctx.error("value class first parameter must not be phantom", param.pos) + ctx.error("First parameter of value class must not be phantom", param.pos) else if (param.is(Unused)) - ctx.error("value class first parameter cannot be `unused`", param.pos) + ctx.error("First parameter of value class cannot be `unused`", param.pos) else { for (p <- params if !(p.info.isPhantom || p.is(Unused))) ctx.error("value class can only have one non `unused` parameter", p.pos) From dd661ddad2f0eac6d734a03dc39ef12cfb8388d5 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 12 Dec 2017 11:10:21 +0100 Subject: [PATCH 14/27] Remove special case for unused closure --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 7ff2e5b810ce..c4303a73c626 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -946,7 +946,7 @@ class Typer extends Namer i"""internal error: cannot turn method type $mt into closure |because it has internal parameter dependencies, |position = ${tree.pos}, raw type = ${mt.toString}""") // !!! DEBUG. Eventually, convert to an error? - } else if (mt.isUnusedMethod) TypeTree(mt.toFunctionType(env1.length)) + } else EmptyTree } case tp => From 6a40b84e55e6605b7811b35e5a3fc823c05e6aa0 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 12 Dec 2017 11:14:06 +0100 Subject: [PATCH 15/27] Keep stable unused arguments --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index c4303a73c626..a056e6b62e8f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2135,7 +2135,8 @@ class Typer extends Namer if (!wtp.isUnusedMethod) args else args.map { arg => arg.tpe match { - case _: AmbiguousImplicits | _: SearchFailureType => arg + case tpe if tpe.isStable => arg + case _: AmbiguousImplicits => arg case tpe => defaultValue(tpe) } } From aa531823200727e0c885cb14d11a8be7332e832e Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 12 Dec 2017 11:20:34 +0100 Subject: [PATCH 16/27] Add documentation --- compiler/src/dotty/tools/dotc/typer/UnusedUtil.scala | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/UnusedUtil.scala b/compiler/src/dotty/tools/dotc/typer/UnusedUtil.scala index fcbfa45ba41a..4c09a9bbbb60 100644 --- a/compiler/src/dotty/tools/dotc/typer/UnusedUtil.scala +++ b/compiler/src/dotty/tools/dotc/typer/UnusedUtil.scala @@ -12,15 +12,21 @@ import dotty.tools.dotc.core.Flags._ */ object UnusedUtil { + /** Transforms the tree into a its default tree. + * Performed to shrink the tree that is known to be erased later. + */ def normalizeUnusedExpr(tree: Tree, msg: String)(implicit ctx: Context): Tree = { if (!isPureExpr(tree)) ctx.warning(msg + "This expression will not be evaluated.", tree.pos) defaultValue(tree.tpe) } - def normalizeUnusedRhs(tree: Tree, sym: Symbol)(implicit ctx: Context) = { - if (sym.is(Unused) && tree.tpe.exists) normalizeUnusedExpr(tree, "Expression is on the RHS of an `unused` " + sym.showKind + ". ") - else tree + /** Transforms the rhs tree into a its default tree if it is in an `unused` val/def. + * Performed to shrink the tree that is known to be erased later. + */ + def normalizeUnusedRhs(rhs: Tree, sym: Symbol)(implicit ctx: Context) = { + if (sym.is(Unused) && rhs.tpe.exists) normalizeUnusedExpr(rhs, "Expression is on the RHS of an `unused` " + sym.showKind + ". ") + else rhs } } From aba82ba52805b11b51544e1387c96d784113c6fd Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 13 Dec 2017 17:56:58 +0100 Subject: [PATCH 17/27] Complete doc of FunArgMods --- .../dotty/tools/dotc/parsing/Parsers.scala | 19 ++++++++++--------- docs/docs/internals/syntax.md | 4 +++- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 771ce7bc3031..9ea8ac827057 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -715,15 +715,13 @@ object Parsers { /** Type ::= [FunArgMods] FunArgTypes `=>' Type * | HkTypeParamClause `->' Type * | InfixType - * FunArgMods ::= `implicit' FunArgMods - * | `unused' FunArgMods * FunArgTypes ::= InfixType * | `(' [ FunArgType {`,' FunArgType } ] `)' * | '(' TypedFunParam {',' TypedFunParam } ')' */ def typ(): Tree = { val start = in.offset - val imods = allFunctionMods(EmptyModifiers) + val imods = funArgMods(EmptyModifiers) def functionRest(params: List[Tree]): Tree = atPos(start, accept(ARROW)) { val t = typ() @@ -1066,7 +1064,7 @@ object Parsers { def expr(location: Location.Value): Tree = { val start = in.offset if (in.token == IMPLICIT || in.token == UNUSED) { - val imods = allFunctionMods(EmptyModifiers) + val imods = funArgMods(EmptyModifiers) implicitClosure(start, location, imods) } else { val saved = placeholderParams @@ -1728,12 +1726,15 @@ object Parsers { normalize(loop(start)) } - def allFunctionMods(imods: Modifiers, doIfImplicit: () => Unit = () => ()): Modifiers = { + /** FunArgMods ::= `implicit' FunArgMods + * | `unused' FunArgMods + */ + def funArgMods(imods: Modifiers, doIfImplicit: () => Unit = () => ()): Modifiers = { if (in.token == IMPLICIT) { doIfImplicit() - allFunctionMods(addMod(imods, atPos(accept(IMPLICIT)) { Mod.Implicit() }), doIfImplicit) + funArgMods(addMod(imods, atPos(accept(IMPLICIT)) { Mod.Implicit() }), doIfImplicit) } else if (in.token == UNUSED) - allFunctionMods(addMod(imods, atPos(accept(UNUSED)) { Mod.Unused() }), doIfImplicit) + funArgMods(addMod(imods, atPos(accept(UNUSED)) { Mod.Unused() }), doIfImplicit) else imods } @@ -1882,7 +1883,7 @@ object Parsers { if (in.token == RPAREN) Nil else { if (in.token == IMPLICIT || in.token == UNUSED) - imods = allFunctionMods(imods, () => implicitOffset = in.offset) + imods = funArgMods(imods, () => implicitOffset = in.offset) commaSeparated(() => param()) } } @@ -2478,7 +2479,7 @@ object Parsers { else if (isDefIntro(localModifierTokens)) if (in.token == IMPLICIT || in.token == UNUSED) { val start = in.offset - var imods = allFunctionMods(EmptyModifiers) + var imods = funArgMods(EmptyModifiers) if (isBindingIntro) stats += implicitClosure(start, Location.InBlock, imods) else stats +++= localDef(start, imods) } else { diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 40825d221589..5bf96c02b837 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -117,9 +117,11 @@ ClassQualifier ::= ‘[’ id ‘]’ ### Types ```ebnf -Type ::= [‘implicit’] FunArgTypes ‘=>’ Type Function(ts, t) +Type ::= [FunArgMods] FunArgTypes ‘=>’ Type Function(ts, t) | HkTypeParamClause ‘=>’ Type TypeLambda(ps, t) | InfixType +FunArgMods ::= `implicit' FunArgMods + | `unused' FunArgMods FunArgTypes ::= InfixType | ‘(’ [ FunArgType {‘,’ FunArgType } ] ‘)’ | '(' TypedFunParam {',' TypedFunParam } ')' From 7c4662d435c756ab00916a06697289720fd93753 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 13 Dec 2017 18:15:07 +0100 Subject: [PATCH 18/27] Use set for funArgMods --- .../dotty/tools/dotc/parsing/Parsers.scala | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 9ea8ac827057..e784e1c2a2a8 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -721,7 +721,7 @@ object Parsers { */ def typ(): Tree = { val start = in.offset - val imods = funArgMods(EmptyModifiers) + val imods = modifiers(funArgMods) def functionRest(params: List[Tree]): Tree = atPos(start, accept(ARROW)) { val t = typ() @@ -1064,7 +1064,7 @@ object Parsers { def expr(location: Location.Value): Tree = { val start = in.offset if (in.token == IMPLICIT || in.token == UNUSED) { - val imods = funArgMods(EmptyModifiers) + val imods = modifiers(funArgMods) implicitClosure(start, location, imods) } else { val saved = placeholderParams @@ -1729,14 +1729,7 @@ object Parsers { /** FunArgMods ::= `implicit' FunArgMods * | `unused' FunArgMods */ - def funArgMods(imods: Modifiers, doIfImplicit: () => Unit = () => ()): Modifiers = { - if (in.token == IMPLICIT) { - doIfImplicit() - funArgMods(addMod(imods, atPos(accept(IMPLICIT)) { Mod.Implicit() }), doIfImplicit) - } else if (in.token == UNUSED) - funArgMods(addMod(imods, atPos(accept(UNUSED)) { Mod.Unused() }), doIfImplicit) - else imods - } + def funArgMods = BitSet(IMPLICIT, UNUSED) /** Wrap annotation or constructor in New(...). */ def wrapNew(tpt: Tree) = Select(New(tpt), nme.CONSTRUCTOR) @@ -1828,7 +1821,7 @@ object Parsers { * ClsParamClause ::= [nl] `(' [`unused'] [ClsParams] ')' * ClsParams ::= ClsParam {`' ClsParam} * ClsParam ::= {Annotation} [{Modifier} (`val' | `var') | `inline'] Param - * DefParamClauses ::= {DefParamClause} [[nl] `(' [`unused'] `implicit' DefParams `)'] + * DefParamClauses ::= {DefParamClause} [[nl] `(' [`FunArgMods'] DefParams `)'] * DefParamClause ::= [nl] `(' [`unused'] [DefParams] ')' * DefParams ::= DefParam {`,' DefParam} * DefParam ::= {Annotation} [`inline'] Param @@ -1882,8 +1875,18 @@ object Parsers { def paramClause(): List[ValDef] = inParens { if (in.token == RPAREN) Nil else { - if (in.token == IMPLICIT || in.token == UNUSED) - imods = funArgMods(imods, () => implicitOffset = in.offset) + def funArgMods(): Unit = { + if (in.token == IMPLICIT) { + implicitOffset = in.offset + imods = addMod(imods, atPos(accept(IMPLICIT)) { Mod.Implicit() }) + funArgMods() + } else if (in.token == UNUSED) { + imods = addMod(imods, atPos(accept(UNUSED)) { Mod.Unused() }) + funArgMods() + } + } + funArgMods() + commaSeparated(() => param()) } } @@ -2479,7 +2482,7 @@ object Parsers { else if (isDefIntro(localModifierTokens)) if (in.token == IMPLICIT || in.token == UNUSED) { val start = in.offset - var imods = funArgMods(EmptyModifiers) + var imods = modifiers(funArgMods) if (isBindingIntro) stats += implicitClosure(start, Location.InBlock, imods) else stats +++= localDef(start, imods) } else { From ce2d8057aae55e7c27225146245f7621e9a31091 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 13 Dec 2017 18:27:54 +0100 Subject: [PATCH 19/27] Add missing syntax for FunArgMods --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 6 ++---- docs/docs/internals/syntax.md | 8 ++++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index e784e1c2a2a8..b64c6b013fa3 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1028,8 +1028,6 @@ object Parsers { /** Expr ::= [FunArgMods] FunParams =>' Expr * | Expr1 - * FunArgMods ::= `implicit' FunArgMods - * | `unused' FunArgMods * FunParams ::= Bindings * | id * | `_' @@ -1817,11 +1815,11 @@ object Parsers { def typeParamClauseOpt(ownerKind: ParamOwner.Value): List[TypeDef] = if (in.token == LBRACKET) typeParamClause(ownerKind) else Nil - /** ClsParamClauses ::= {ClsParamClause} [[nl] `(' [`unused'] `implicit' [`unused'] ClsParams `)'] + /** ClsParamClauses ::= {ClsParamClause} [[nl] `(' [FunArgMods] ClsParams `)'] * ClsParamClause ::= [nl] `(' [`unused'] [ClsParams] ')' * ClsParams ::= ClsParam {`' ClsParam} * ClsParam ::= {Annotation} [{Modifier} (`val' | `var') | `inline'] Param - * DefParamClauses ::= {DefParamClause} [[nl] `(' [`FunArgMods'] DefParams `)'] + * DefParamClauses ::= {DefParamClause} [[nl] `(' [FunArgMods] DefParams `)'] * DefParamClause ::= [nl] `(' [`unused'] [DefParams] ')' * DefParams ::= DefParam {`,' DefParam} * DefParam ::= {Annotation} [`inline'] Param diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 5bf96c02b837..af20e53bd3e2 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -155,9 +155,9 @@ TypeParamBounds ::= TypeBounds {‘<%’ Type} {‘:’ Type} ### Expressions ```ebnf -Expr ::= [‘implicit’] FunParams ‘=>’ Expr Function(args, expr), Function(ValDef([implicit], id, TypeTree(), EmptyTree), expr) +Expr ::= [FunArgMods] FunParams ‘=>’ Expr Function(args, expr), Function(ValDef([implicit], id, TypeTree(), EmptyTree), expr) | Expr1 -BlockResult ::= [‘implicit’] FunParams ‘=>’ Block +BlockResult ::= [FunArgMods] FunParams ‘=>’ Block | Expr1 FunParams ::= Bindings | id @@ -260,7 +260,7 @@ HkTypeParamClause ::= ‘[’ HkTypeParam {‘,’ HkTypeParam} ‘]’ HkTypeParam ::= {Annotation} [‘+’ | ‘-’] (Id[HkTypeParamClause] | ‘_’) TypeBounds -ClsParamClauses ::= {ClsParamClause} [[nl] ‘(’ ‘implicit’ ClsParams ‘)’] +ClsParamClauses ::= {ClsParamClause} [[nl] ‘(’ [FunArgMods] ClsParams ‘)’] ClsParamClause ::= [nl] ‘(’ [ClsParams] ‘)’ ClsParams ::= ClsParam {‘,’ ClsParam} ClsParam ::= {Annotation} ValDef(mods, id, tpe, expr) -- point of mods on val/var @@ -268,7 +268,7 @@ ClsParam ::= {Annotation} Param ::= id ‘:’ ParamType [‘=’ Expr] | INT -DefParamClauses ::= {DefParamClause} [[nl] ‘(’ ‘implicit’ DefParams ‘)’] +DefParamClauses ::= {DefParamClause} [[nl] ‘(’ [FunArgMods] DefParams ‘)’] DefParamClause ::= [nl] ‘(’ [DefParams] ‘)’ DefParams ::= DefParam {‘,’ DefParam} DefParam ::= {Annotation} [‘inline’] Param ValDef(mods, id, tpe, expr) -- point of mods at id. From 544c8d5d70a77a890e0f99f01031185369f47069 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 13 Dec 2017 18:50:32 +0100 Subject: [PATCH 20/27] Improve variable names --- .../src/dotty/tools/dotc/transform/Erasure.scala | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 88c26aa3d18f..13ec55f6d3b3 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -461,9 +461,11 @@ object Erasure { ref(meth).appliedToArgs(args.toList ++ followingArgs) } - private def protoArgs(pt: Type, tp: Type): List[untpd.Tree] = (pt, tp) match { - case (pt: FunProto, tp: MethodType) if tp.isUnusedMethod => protoArgs(pt.resType, tp.resType) - case (pt: FunProto, tp: MethodType) => pt.args ++ protoArgs(pt.resType, tp.resType) + private def protoArgs(pt: Type, methTp: Type): List[untpd.Tree] = (pt, methTp) match { + case (pt: FunProto, methTp: MethodType) if methTp.isUnusedMethod => + protoArgs(pt.resType, methTp.resType) + case (pt: FunProto, methTp: MethodType) => + pt.args ++ protoArgs(pt.resType, methTp.resType) case _ => Nil } @@ -497,9 +499,8 @@ object Erasure { fun1.tpe.widen match { case mt: MethodType => val outers = outer.args(fun.asInstanceOf[tpd.Tree]) // can't use fun1 here because its type is already erased - var args0 = protoArgs(pt, tree.typeOpt) - if (mt.paramNames.nonEmpty && !mt.isUnusedMethod) args0 = args ::: args0 - args0 = outers ::: args0 + val ownArgs = if (mt.paramNames.nonEmpty && !mt.isUnusedMethod) args else Nil + var args0 = outers ::: ownArgs ::: protoArgs(pt, tree.typeOpt) if (args0.length > MaxImplementedFunctionArity && mt.paramInfos.length == 1) { val bunchedArgs = untpd.JavaSeqLiteral(args0, TypeTree(defn.ObjectType)) From f10f2d0a00418db5887a2db436ea817d6ad21db6 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 13 Dec 2017 18:54:47 +0100 Subject: [PATCH 21/27] Improve comment --- docs/docs/reference/unused-terms.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/reference/unused-terms.md b/docs/docs/reference/unused-terms.md index 41d6d6729ad9..e752363b09db 100644 --- a/docs/docs/reference/unused-terms.md +++ b/docs/docs/reference/unused-terms.md @@ -32,7 +32,7 @@ m.turnedOn.turnedOn // ERROR // State is must be Off ``` -These constraint that only depend on the types at the call site are completly resolved at compile time and never used at runtime. +Note that in the code above the actual implicit arguments for `IsOff` are never used at runtime; they serve only to establish the right constraints at compile time. As these parameters are never used at runtime there is not real need to have them around, but they still need to be present at runtime to be able to do separate compilation and retain binary compatiblity. Unused parameters are contractually obligated to not be used at runtime, enforcing the essence of evidences on types and allows them to always be optimized away. From ddf5b2c8651d18f0e3dc0300c027875e06a247ba Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 14 Dec 2017 11:05:58 +0100 Subject: [PATCH 22/27] Fix synthetic methods of value class with unused params --- compiler/src/dotty/tools/dotc/transform/SyntheticMethods.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/SyntheticMethods.scala b/compiler/src/dotty/tools/dotc/transform/SyntheticMethods.scala index 6eb3795f8371..8b9bd364e6d1 100644 --- a/compiler/src/dotty/tools/dotc/transform/SyntheticMethods.scala +++ b/compiler/src/dotty/tools/dotc/transform/SyntheticMethods.scala @@ -59,7 +59,7 @@ class SyntheticMethods(thisPhase: DenotTransformer) { def syntheticMethods(clazz: ClassSymbol)(implicit ctx: Context): List[Tree] = { val clazzType = clazz.appliedRef lazy val accessors = - if (isDerivedValueClass(clazz)) clazz.paramAccessors + if (isDerivedValueClass(clazz)) clazz.paramAccessors.take(1) // Tail parameters can only be `unused` else clazz.caseAccessors val symbolsToSynthesize: List[Symbol] = From 7f81bc3519cc8da7460c9a4531d971ea5c6cb674 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 14 Dec 2017 11:22:22 +0100 Subject: [PATCH 23/27] Make Unused flag a term flag only --- compiler/src/dotty/tools/dotc/ast/untpd.scala | 2 +- compiler/src/dotty/tools/dotc/core/Flags.scala | 4 +--- compiler/src/dotty/tools/dotc/typer/Checking.scala | 1 - 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index a649b09c8fc5..5bb0efd93139 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -118,7 +118,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case class Implicit() extends Mod(Flags.ImplicitCommon) - case class Unused() extends Mod(Flags.UnusedCommon) + case class Unused() extends Mod(Flags.Unused) case class Final() extends Mod(Flags.Final) diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index 301831ef09d1..fd02bdac4eb4 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -368,9 +368,7 @@ object Flags { final val Enum = commonFlag(40, "") /** Labeled with `unused` modifier (unused value) */ - final val UnusedCommon = commonFlag(42, "unused") - final val Unused = UnusedCommon.toTermFlags - final val UnusedType = UnusedCommon.toTypeFlags + final val Unused = termFlag(42, "unused") // Flags following this one are not pickled diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 4500539c852c..c0bd25c13440 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -383,7 +383,6 @@ object Checking { fail(CannotHaveSameNameAs(sym, cls, CannotHaveSameNameAs.CannotBeOverridden)) sym.setFlag(Private) // break the overriding relationship by making sym Private } - checkApplicable(UnusedType, !sym.is(UnusedType)) if (sym.is(Unused)) checkApplicable(Unused, !sym.is(MutableOrLazy)) } From c87747361a8b2a1f5a9543b40f4251cd4a464c26 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 12 Jan 2018 14:40:41 +0100 Subject: [PATCH 24/27] Add rules --- docs/docs/reference/unused-terms.md | 52 +++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/docs/docs/reference/unused-terms.md b/docs/docs/reference/unused-terms.md index e752363b09db..4a5ab7d692ca 100644 --- a/docs/docs/reference/unused-terms.md +++ b/docs/docs/reference/unused-terms.md @@ -144,3 +144,55 @@ object Test { } } ``` + + +Rules +----- + +1) The `unused` modifier can appear: +* At the start of a parameter block of a method, function or class +* In a method definition +* In a `val` definition (but not `lazy val` or `var`) + +```scala +unused val x = ... +unused def f = ... + +def g(unused x: Int) = ... + +(unused x: Int) => ... +def h(x: unused Int => Int) = ... + +class K(unused x: Int) { ... } +``` + +2) A reference to an `unused` definition can only be used +* Inside the expression of argument to an `unused` parameter +* Inside the body of an `unused` `val` or `def` + +3) Functions +* `(unused x1: T1, x2: T2, ..., xN: TN) => y : (unused T1, T2, ..., TN) => R` +* `(implicit unused x1: T1, x2: T2, ..., xN: TN) => y : (implicit unused T1, T2, ..., TN) => R` +* `implicit unused T1 => R <:< unused T1 => R` +* `(implicit unused T1, T2) => R <:< (unused T1, T2) => R` +* ... + +Note that there is no subtype relation between `unused T => R` and `T => R` (or `implicit unused T => R` and `implicit T => R`) + +4) Eta expansion +if `def f(unused x: T): U` then `f: (unused T) => U`. + + +5) Erasure Semantics +* All `unused` paramters are removed from the function +* All argument to `unused` paramters are not passed to the function +* All `unused` definitions are removed +* All `(unused T1, T2, ..., TN) => R` and `(implicit unused T1, T2, ..., TN) => R` become `() => R` + +6) Overloading +Method with `unused` parameters will follow the normal overloading constraints after erasure. + +7) Overriding +* Member definitions overidding each other must both be `unused` or not be `unused` +* `def foo(x: T): U` cannot be overriden by `def foo(unused x: T): U` an viceversa + From ed509581b8b3f49e5f9c6c10fd514293f12cfa00 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 23 Jan 2018 08:40:05 +0100 Subject: [PATCH 25/27] Fix documentation --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 3 +-- docs/docs/internals/syntax.md | 3 +-- docs/docs/reference/unused-terms.md | 8 +++----- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index b64c6b013fa3..c764ee73e59a 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1724,8 +1724,7 @@ object Parsers { normalize(loop(start)) } - /** FunArgMods ::= `implicit' FunArgMods - * | `unused' FunArgMods + /** FunArgMods ::= { `implicit` | `unused` } */ def funArgMods = BitSet(IMPLICIT, UNUSED) diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index af20e53bd3e2..b2e13a66dd3a 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -120,8 +120,7 @@ ClassQualifier ::= ‘[’ id ‘]’ Type ::= [FunArgMods] FunArgTypes ‘=>’ Type Function(ts, t) | HkTypeParamClause ‘=>’ Type TypeLambda(ps, t) | InfixType -FunArgMods ::= `implicit' FunArgMods - | `unused' FunArgMods +FunArgMods ::= { `implicit` | `unused` } FunArgTypes ::= InfixType | ‘(’ [ FunArgType {‘,’ FunArgType } ] ‘)’ | '(' TypedFunParam {',' TypedFunParam } ')' diff --git a/docs/docs/reference/unused-terms.md b/docs/docs/reference/unused-terms.md index 4a5ab7d692ca..12c0c597bd81 100644 --- a/docs/docs/reference/unused-terms.md +++ b/docs/docs/reference/unused-terms.md @@ -34,9 +34,7 @@ m.turnedOn.turnedOn // ERROR Note that in the code above the actual implicit arguments for `IsOff` are never used at runtime; they serve only to establish the right constraints at compile time. As these parameters are never used at runtime there is not real need to have them around, but they still need to be -present at runtime to be able to do separate compilation and retain binary compatiblity. Unused parameters are contractually -obligated to not be used at runtime, enforcing the essence of evidences on types and allows them to always be optimized away. - +present at runtime to be able to do separate compilation and retain binary compatiblity. How to define unused parameter? ------------------------------- @@ -49,7 +47,7 @@ val lambdaWithUnusedEv: unused Ev => Int = unused (ev: Ev) => 42 ``` -Those parameters will not be usable for computations, thought they can be used as arguments to other `unused` parameters. +`unused` parameters will not be usable for computations, though they can be used as arguments to other `unused` parameters. ```scala def methodWithUnusedInt1(unused i: Int): Int = @@ -84,7 +82,7 @@ methodWithUnusedEv(evidence1) State machine with unused evidence example ------------------------------------------ -The following examples is an extended implementation of a simple state machine which can be in a state `On` or `Off`. +The following example is an extended implementation of a simple state machine which can be in a state `On` or `Off`. The machine can change state from `Off` to `On` with `turnedOn` only if it is currently `Off`, conversely from `On` to `Off` with `turnedOff` only if it is currently `On`. These last constraint are captured with the `IsOff[S]` and `IsOn[S]` implicit evidence only exist for `IsOff[Off]` and `InOn[On]`. From c04079842e053b05a52c6a89047b92a20cbe80a5 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 23 Jan 2018 08:40:55 +0100 Subject: [PATCH 26/27] Move methods from UnusedUtil to Applications --- .../dotty/tools/dotc/typer/Applications.scala | 18 ++++++++++- .../src/dotty/tools/dotc/typer/Typer.scala | 1 - .../dotty/tools/dotc/typer/UnusedUtil.scala | 32 ------------------- 3 files changed, 17 insertions(+), 34 deletions(-) delete mode 100644 compiler/src/dotty/tools/dotc/typer/UnusedUtil.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index a8132748c86f..99c54b33b3b0 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -23,7 +23,6 @@ import StdNames._ import NameKinds.DefaultGetterName import ProtoTypes._ import Inferencing._ -import UnusedUtil._ import collection.mutable import config.Printers.{overload, typr, unapp} @@ -1542,6 +1541,23 @@ trait Applications extends Compatibility { self: Typer with Dynamic => harmonizedElems } + /** Transforms the tree into a its default tree. + * Performed to shrink the tree that is known to be erased later. + */ + protected def normalizeUnusedExpr(tree: Tree, msg: String)(implicit ctx: Context): Tree = { + if (!isPureExpr(tree)) + ctx.warning(msg + "This expression will not be evaluated.", tree.pos) + defaultValue(tree.tpe) + } + + /** Transforms the rhs tree into a its default tree if it is in an `unused` val/def. + * Performed to shrink the tree that is known to be erased later. + */ + protected def normalizeUnusedRhs(rhs: Tree, sym: Symbol)(implicit ctx: Context) = { + if (sym.is(Unused) && rhs.tpe.exists) normalizeUnusedExpr(rhs, "Expression is on the RHS of an `unused` " + sym.showKind + ". ") + else rhs + } + /** If all `types` are numeric value types, and they are not all the same type, * pick a common numeric supertype and widen any constant types in `tpes` to it. * If the resulting types are all the same, return them instead of the original ones. diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index a056e6b62e8f..720e4b214e50 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -29,7 +29,6 @@ import dotty.tools.dotc.transform.Erasure.Boxing import util.Positions._ import util.common._ import util.{Property, SourcePosition} -import UnusedUtil._ import collection.mutable import annotation.tailrec diff --git a/compiler/src/dotty/tools/dotc/typer/UnusedUtil.scala b/compiler/src/dotty/tools/dotc/typer/UnusedUtil.scala deleted file mode 100644 index 4c09a9bbbb60..000000000000 --- a/compiler/src/dotty/tools/dotc/typer/UnusedUtil.scala +++ /dev/null @@ -1,32 +0,0 @@ -package dotty.tools.dotc.typer - -import dotty.tools.dotc.ast.tpd._ -import dotty.tools.dotc.core.Contexts._ -import dotty.tools.dotc.core.Decorators._ -import dotty.tools.dotc.core.Symbols._ -import dotty.tools.dotc.core.Flags._ - -/** Util methods for transformation of `unused` expressions - * - * @author Nicolas Stucki - */ -object UnusedUtil { - - /** Transforms the tree into a its default tree. - * Performed to shrink the tree that is known to be erased later. - */ - def normalizeUnusedExpr(tree: Tree, msg: String)(implicit ctx: Context): Tree = { - if (!isPureExpr(tree)) - ctx.warning(msg + "This expression will not be evaluated.", tree.pos) - defaultValue(tree.tpe) - } - - /** Transforms the rhs tree into a its default tree if it is in an `unused` val/def. - * Performed to shrink the tree that is known to be erased later. - */ - def normalizeUnusedRhs(rhs: Tree, sym: Symbol)(implicit ctx: Context) = { - if (sym.is(Unused) && rhs.tpe.exists) normalizeUnusedExpr(rhs, "Expression is on the RHS of an `unused` " + sym.showKind + ". ") - else rhs - } - -} From 68afbcd915c5f6e17f4d1347c6a784c9b1639b13 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 21 Feb 2018 11:25:42 +0100 Subject: [PATCH 27/27] Update unused parameter doc --- docs/docs/reference/unused-terms.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/reference/unused-terms.md b/docs/docs/reference/unused-terms.md index 12c0c597bd81..324628758e9f 100644 --- a/docs/docs/reference/unused-terms.md +++ b/docs/docs/reference/unused-terms.md @@ -34,7 +34,7 @@ m.turnedOn.turnedOn // ERROR Note that in the code above the actual implicit arguments for `IsOff` are never used at runtime; they serve only to establish the right constraints at compile time. As these parameters are never used at runtime there is not real need to have them around, but they still need to be -present at runtime to be able to do separate compilation and retain binary compatiblity. +present in some form in the generated code to be able to do separate compilation and retain binary compatiblity. How to define unused parameter? -------------------------------