diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 2b85f65577fb..81399f79ea57 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -81,6 +81,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..c74f8f096699 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 = { @@ -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/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..5bb0efd93139 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -50,15 +50,14 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { */ case class InterpolatedString(id: TermName, segments: List[Tree]) extends TermTree + /** A function type */ case class Function(args: List[Tree], body: Tree) extends Tree { override def isTerm = body.isTerm override def isType = body.isType } - /** An implicit function type */ - class ImplicitFunction(args: List[Tree], body: Tree) extends Function(args, body) { - override def toString = s"ImplicitFunction($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 @@ -119,6 +118,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case class Implicit() extends Mod(Flags.ImplicitCommon) + case class Unused() extends Mod(Flags.Unused) + 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..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 { @@ -117,18 +131,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 +756,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 +827,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 +874,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 +915,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 +932,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 } @@ -976,7 +1008,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 +1074,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..fd02bdac4eb4 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -367,6 +367,9 @@ object Flags { /** Symbol is a Java enum */ final val Enum = commonFlag(40, "") + /** Labeled with `unused` modifier (unused value) */ + final val Unused = termFlag(42, "unused") + // Flags following this one are not pickled /** Symbol is not a member of its owner */ @@ -438,7 +441,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 +515,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..6689a5371244 100644 --- a/compiler/src/dotty/tools/dotc/core/Mode.scala +++ b/compiler/src/dotty/tools/dotc/core/Mode.scala @@ -93,4 +93,5 @@ object Mode { /** We are in the IDE */ val Interactive = newMode(20, "Interactive") + } diff --git a/compiler/src/dotty/tools/dotc/core/NameOps.scala b/compiler/src/dotty/tools/dotc/core/NameOps.scala index 4a2d3092febc..def42bca9c88 100644 --- a/compiler/src/dotty/tools/dotc/core/NameOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NameOps.scala @@ -174,31 +174,53 @@ 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 || + isUnusedFunction } /** 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..c764ee73e59a 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -712,7 +712,7 @@ object Parsers { } } - /** Type ::= [`implicit'] FunArgTypes `=>' Type + /** Type ::= [FunArgMods] FunArgTypes `=>' Type * | HkTypeParamClause `->' Type * | InfixType * FunArgTypes ::= InfixType @@ -721,12 +721,12 @@ object Parsers { */ def typ(): Tree = { val start = in.offset - val isImplicit = in.token == IMPLICIT - if (isImplicit) in.nextToken() + val imods = modifiers(funArgMods) 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 NonEmptyFunction(params, t, imods) + else Function(params, t) } def funArgTypesRest(first: Tree, following: () => Tree) = { val buf = new ListBuffer[Tree] += first @@ -759,7 +759,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 +786,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[NonEmptyFunction]) syntaxError("Types with implicit keyword can only be function types", Position(start, start + nme.IMPLICITkw.asSimpleName.length)) t } @@ -1026,14 +1026,14 @@ object Parsers { } } - /** Expr ::= [`implicit'] FunParams `=>' Expr + /** Expr ::= [FunArgMods] FunParams =>' Expr * | Expr1 * 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 +1061,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 = modifiers(funArgMods) + implicitClosure(start, location, imods) + } else { val saved = placeholderParams placeholderParams = Nil @@ -1634,6 +1635,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 +1724,9 @@ object Parsers { normalize(loop(start)) } - def implicitMods(): Modifiers = - addMod(EmptyModifiers, atPos(accept(IMPLICIT)) { Mod.Implicit() }) + /** FunArgMods ::= { `implicit` | `unused` } + */ + def funArgMods = BitSet(IMPLICIT, UNUSED) /** Wrap annotation or constructor in New(...). */ def wrapNew(tpt: Tree) = Select(New(tpt), nme.CONSTRUCTOR) @@ -1811,12 +1814,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] `(' [FunArgMods] 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] `(' [FunArgMods] DefParams `)'] + * DefParamClause ::= [nl] `(' [`unused'] [DefParams] ')' * DefParams ::= DefParam {`,' DefParam} * DefParam ::= {Annotation} [`inline'] Param * Param ::= id `:' ParamType [`=' Expr] @@ -1869,21 +1872,30 @@ object Parsers { def paramClause(): List[ValDef] = inParens { if (in.token == RPAREN) Nil else { - if (in.token == IMPLICIT) { - implicitOffset = in.offset - imods = implicitMods() + 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()) } } 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 +1961,8 @@ object Parsers { } } } - /** ImportSelector ::= id [`=>' id | `=>' `_'] + + /** ImportSelector ::= id [`=>' id | `=>' `_'] */ def importSelector(): Tree = { val from = termIdentOrWildcard() @@ -2464,9 +2477,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 = modifiers(funArgMods) 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..13ec55f6d3b3 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -457,12 +457,15 @@ 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, 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 } @@ -496,15 +499,19 @@ 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) + 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)) .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 +571,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/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..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 (baseCls.is(NoInitsTrait) || defn.NoInitClasses.contains(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 diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index a5c32c568687..5c3a0a5aafdb 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 => + checkNotUnused(tree) handleMeta(tree.symbol) tree.tpe match { case tpe: ThisType => This(tpe.cls).withPos(tree.pos) case _ => tree } case tree @ Select(qual, name) => + checkNotUnused(tree) handleMeta(tree.symbol) if (name.isTypeName) { Checking.checkRealizable(qual.tpe, qual.pos.focus) - super.transform(tree) + super.transform(tree)(ctx.addMode(Mode.Type)) } else transformSelect(tree, Nil) @@ -310,5 +312,14 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase println(i"error while transforming $tree") throw ex } + + private def checkNotUnused(tree: RefTree)(implicit ctx: Context): Unit = { + 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"${tree.symbol} is declared as unused, but is in fact used" + ctx.error(msg, tree.pos) + } + } } } diff --git a/compiler/src/dotty/tools/dotc/transform/SyntheticMethods.scala b/compiler/src/dotty/tools/dotc/transform/SyntheticMethods.scala index 89c566036bdc..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] = @@ -191,7 +191,6 @@ class SyntheticMethods(thisPhase: DenotTransformer) { */ def valueHashCodeBody(implicit ctx: Context): Tree = { assert(accessors.nonEmpty) - assert(accessors.tail.forall(_.info.isPhantom)) 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..1a9eaf0019a2 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/UnusedDecls.scala @@ -0,0 +1,49 @@ +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 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 + + + /* 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/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..99c54b33b3b0 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -779,7 +779,11 @@ 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 => + tpd.cpy.Apply(app)(fun = fun, args = args.map(arg => normalizeUnusedExpr(arg, "This argument is given to an unused parameter. "))) + case _ => app + } } } @@ -1393,7 +1397,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 +1444,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 } @@ -1537,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/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 4dd960e4fae3..c0bd25c13440 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -383,6 +383,8 @@ object Checking { fail(CannotHaveSameNameAs(sym, cls, CannotHaveSameNameAs.CannotBeOverridden)) sym.setFlag(Private) // break the overriding relationship by making sym Private } + if (sym.is(Unused)) + checkApplicable(Unused, !sym.is(MutableOrLazy)) } /** Check the type signature of the symbol `M` defined by `tree` does not refer @@ -501,10 +503,12 @@ 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("First parameter of value class 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..720e4b214e50 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 + val (isImplicit, isUnused) = tree match { + case tree: untpd.NonEmptyFunction => + if (args.nonEmpty) (tree.mods.is(Implicit), tree.mods.is(Unused)) + else { + ctx.error(FunctionTypeNeedsNonEmptyParameterList(), tree.pos) + (false, false) } - else true - case _ => false + 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 EmptyTree } case tp => throw new java.lang.Error(i"internal error: closing over non-method $tp, pos = ${tree.pos}") @@ -1342,7 +1344,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)) @@ -1400,7 +1402,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) @@ -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) @@ -2128,7 +2130,18 @@ 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 tpe if tpe.isStable => arg + case _: AmbiguousImplicits => arg + case tpe => defaultValue(tpe) + } + } + } + val args = eraseUnusedArgs(implicitArgs(wtp.paramInfos)) + def propagatedFailure(args: List[Tree]): Type = args match { case arg :: args1 => 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/internals/syntax.md b/docs/docs/internals/syntax.md index 40825d221589..b2e13a66dd3a 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -117,9 +117,10 @@ 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` | `unused` } FunArgTypes ::= InfixType | ‘(’ [ FunArgType {‘,’ FunArgType } ] ‘)’ | '(' TypedFunParam {',' TypedFunParam } ')' @@ -153,9 +154,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 @@ -258,7 +259,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 @@ -266,7 +267,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. diff --git a/docs/docs/reference/unused-terms.md b/docs/docs/reference/unused-terms.md new file mode 100644 index 000000000000..324628758e9f --- /dev/null +++ b/docs/docs/reference/unused-terms.md @@ -0,0 +1,196 @@ +--- +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 +``` + +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 in some form in the generated code to be able to do separate compilation and retain binary compatiblity. + +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 +``` + +`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 = + 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 not evaluated and no value is passed to methodWithUnusedEv +methodWithUnusedEv(evidence1) +``` + +State machine with unused evidence example +------------------------------------------ +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]`. +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 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 { + // 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] +} + +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 + } +} +``` + + +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 + 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..49ebafb7ff16 --- /dev/null +++ b/tests/neg/unused-1.scala @@ -0,0 +1,34 @@ +object Test { + def foo0(a: Int): Int = a + def foo1(unused a: Int): Int = { + foo0( + a // error + ) + foo0({ + println() + a // error + }) + foo1(a) // OK + foo2( // error + a // error + ) + foo3( // error + a + ) + 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-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..eeb66055d1bf --- /dev/null +++ b/tests/neg/unused-6.scala @@ -0,0 +1,12 @@ +object Test { + 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 new file mode 100644 index 000000000000..a7ef65fbb6cb --- /dev/null +++ b/tests/neg/unused-args-lifted.scala @@ -0,0 +1,16 @@ +object Test { + 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 new file mode 100644 index 000000000000..6fa2cf257ef4 --- /dev/null +++ b/tests/neg/unused-assign.scala @@ -0,0 +1,11 @@ +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-case-class.scala b/tests/neg/unused-case-class.scala new file mode 100644 index 000000000000..dc1382fb3636 --- /dev/null +++ b/tests/neg/unused-case-class.scala @@ -0,0 +1 @@ +case class Foo1(unused x: Int) // error 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-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/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..ab2850296eda --- /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") + } + + unused def inky: Int = { + println("inky") // in erased function + 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..638d41045962 --- /dev/null +++ b/tests/run/unused-13.scala @@ -0,0 +1,15 @@ +object Test { + + def main(args: Array[String]): Unit = { + unused val x = { + println("x") + 42 + } + foo(x) + } + + def foo(unused a: Int) = { + println("foo") + } + +} 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-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..31b4ee9862cd --- /dev/null +++ b/tests/run/unused-2.scala @@ -0,0 +1,18 @@ +object Test { + + def main(args: Array[String]): Unit = { + + unused def !!! : Nothing = ??? + + try { + fun(!!!) + println("OK") + } catch { + case e: NotImplementedError => + } + } + + def fun(unused bottom: Nothing): 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-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 + +} 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 +}