Skip to content

Commit 0f113da

Browse files
committed
Implement convertibleTo modifier on parameter types
Source input: From Scala 3: `(x: convertibleTo T)` From Scala 2: `(@allowConversions x: T)` Gets translated to a @annotation.internal.$convertible annotation on the parameter info in a method.
1 parent c0fdd62 commit 0f113da

24 files changed

+254
-48
lines changed

compiler/src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import util.Spans._, Types._, Contexts._, Constants._, Names._, NameOps._, Flags
77
import Symbols._, StdNames._, Trees._, Phases._, ContextOps._
88
import Decorators._, transform.SymUtils._
99
import NameKinds.{UniqueName, EvidenceParamName, DefaultGetterName}
10+
import Annotations.Annotation
1011
import typer.{TyperPhase, Namer, Checking}
1112
import util.{Property, SourceFile, SourcePosition}
1213
import config.Feature.{sourceVersion, migrateTo3, enabled}
@@ -161,14 +162,28 @@ object desugar {
161162
*
162163
* Generate setter where needed
163164
*/
164-
def valDef(vdef0: ValDef)(using Context): Tree = {
165+
def valDef(vdef0: ValDef)(using Context): Tree =
165166
val vdef @ ValDef(_, tpt, rhs) = vdef0
166-
val mods = vdef.mods
167-
168167
val valName = normalizeName(vdef, tpt).asTermName
169-
val vdef1 = cpy.ValDef(vdef)(name = valName)
170168

171-
if (isSetterNeeded(vdef)) {
169+
var mods1 = vdef.mods
170+
def dropConvertibleTo(tpt: Tree): Tree = tpt match
171+
case ConvertibleTo(tpt1) =>
172+
mods1 = vdef.mods.withAddedAnnotation(
173+
TypedSplice(
174+
Annotation(defn.AllowConversionsAnnot).tree.withSpan(tpt.span.startPos)))
175+
tpt1
176+
case ByNameTypeTree(tpt1) =>
177+
cpy.ByNameTypeTree(tpt)(dropConvertibleTo(tpt1))
178+
case PostfixOp(tpt1, op) if op.name == tpnme.raw.STAR =>
179+
cpy.PostfixOp(tpt)(dropConvertibleTo(tpt1), op)
180+
case _ =>
181+
tpt
182+
183+
val vdef1 = cpy.ValDef(vdef)(name = valName, tpt = dropConvertibleTo(tpt))
184+
.withMods(mods1)
185+
186+
if isSetterNeeded(vdef) then
172187
// TODO: copy of vdef as getter needed?
173188
// val getter = ValDef(mods, name, tpt, rhs) withPos vdef.pos?
174189
// right now vdef maps via expandedTree to a thicket which concerns itself.
@@ -177,16 +192,15 @@ object desugar {
177192
// The rhs gets filled in later, when field is generated and getter has parameters (see Memoize miniphase)
178193
val setterRhs = if (vdef.rhs.isEmpty) EmptyTree else unitLiteral
179194
val setter = cpy.DefDef(vdef)(
180-
name = valName.setterName,
181-
paramss = (setterParam :: Nil) :: Nil,
182-
tpt = TypeTree(defn.UnitType),
183-
rhs = setterRhs
184-
).withMods((mods | Accessor) &~ (CaseAccessor | GivenOrImplicit | Lazy))
185-
.dropEndMarker() // the end marker should only appear on the getter definition
195+
name = valName.setterName,
196+
paramss = (setterParam :: Nil) :: Nil,
197+
tpt = TypeTree(defn.UnitType),
198+
rhs = setterRhs
199+
).withMods((vdef.mods | Accessor) &~ (CaseAccessor | GivenOrImplicit | Lazy))
200+
.dropEndMarker() // the end marker should only appear on the getter definition
186201
Thicket(vdef1, setter)
187-
}
188202
else vdef1
189-
}
203+
end valDef
190204

191205
def makeImplicitParameters(tpts: List[Tree], implicitFlag: FlagSet, forPrimaryConstructor: Boolean = false)(using Context): List[ValDef] =
192206
for (tpt <- tpts) yield {
@@ -531,9 +545,9 @@ object desugar {
531545
val enumCompanionRef = TermRefTree()
532546
val enumImport =
533547
Import(enumCompanionRef, enumCases.flatMap(caseIds).map(
534-
enumCase =>
548+
enumCase =>
535549
ImportSelector(enumCase.withSpan(enumCase.span.startPos))
536-
)
550+
)
537551
)
538552
(enumImport :: enumStats, enumCases, enumCompanionRef)
539553
}

compiler/src/dotty/tools/dotc/ast/untpd.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
119119
case class ContextBounds(bounds: TypeBoundsTree, cxBounds: List[Tree])(implicit @constructorOnly src: SourceFile) extends TypTree
120120
case class PatDef(mods: Modifiers, pats: List[Tree], tpt: Tree, rhs: Tree)(implicit @constructorOnly src: SourceFile) extends DefTree
121121
case class ExtMethods(paramss: List[ParamClause], methods: List[Tree])(implicit @constructorOnly src: SourceFile) extends Tree
122+
case class ConvertibleTo(tpt: Tree)(implicit @constructorOnly src: SourceFile) extends Tree
122123
case class MacroTree(expr: Tree)(implicit @constructorOnly src: SourceFile) extends Tree
123124

124125
case class ImportSelector(imported: Ident, renamed: Tree = EmptyTree, bound: Tree = EmptyTree)(implicit @constructorOnly src: SourceFile) extends Tree {
@@ -642,6 +643,9 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
642643
def ExtMethods(tree: Tree)(paramss: List[ParamClause], methods: List[Tree])(using Context): Tree = tree match
643644
case tree: ExtMethods if (paramss eq tree.paramss) && (methods == tree.methods) => tree
644645
case _ => finalize(tree, untpd.ExtMethods(paramss, methods)(tree.source))
646+
def ConvertibleTo(tree: Tree)(tpt: Tree)(using Context): Tree = tree match
647+
case tree: ConvertibleTo if tpt eq tree.tpt => tree
648+
case _ => finalize(tree, untpd.ConvertibleTo(tpt)(tree.source))
645649
def ImportSelector(tree: Tree)(imported: Ident, renamed: Tree, bound: Tree)(using Context): Tree = tree match {
646650
case tree: ImportSelector if (imported eq tree.imported) && (renamed eq tree.renamed) && (bound eq tree.bound) => tree
647651
case _ => finalize(tree, untpd.ImportSelector(imported, renamed, bound)(tree.source))
@@ -709,6 +713,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
709713
cpy.PatDef(tree)(mods, transform(pats), transform(tpt), transform(rhs))
710714
case ExtMethods(paramss, methods) =>
711715
cpy.ExtMethods(tree)(transformParamss(paramss), transformSub(methods))
716+
case ConvertibleTo(tpt) =>
717+
cpy.ConvertibleTo(tree)(transform(tpt))
712718
case ImportSelector(imported, renamed, bound) =>
713719
cpy.ImportSelector(tree)(transformSub(imported), transform(renamed), transform(bound))
714720
case Number(_, _) | TypedSplice(_) =>
@@ -768,6 +774,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
768774
this(this(this(x, pats), tpt), rhs)
769775
case ExtMethods(paramss, methods) =>
770776
this(paramss.foldLeft(x)(apply), methods)
777+
case ConvertibleTo(tpt) =>
778+
this(x, tpt)
771779
case ImportSelector(imported, renamed, bound) =>
772780
this(this(this(x, imported), renamed), bound)
773781
case Number(_, _) =>

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -895,12 +895,14 @@ class Definitions {
895895
@tu lazy val RefiningAnnotationClass: ClassSymbol = requiredClass("scala.annotation.RefiningAnnotation")
896896

897897
// Annotation classes
898+
@tu lazy val AllowConversionsAnnot: ClassSymbol = requiredClass("scala.annotation.allowConversions")
898899
@tu lazy val AnnotationDefaultAnnot: ClassSymbol = requiredClass("scala.annotation.internal.AnnotationDefault")
899900
@tu lazy val BeanPropertyAnnot: ClassSymbol = requiredClass("scala.beans.BeanProperty")
900901
@tu lazy val BooleanBeanPropertyAnnot: ClassSymbol = requiredClass("scala.beans.BooleanBeanProperty")
901902
@tu lazy val BodyAnnot: ClassSymbol = requiredClass("scala.annotation.internal.Body")
902903
@tu lazy val ChildAnnot: ClassSymbol = requiredClass("scala.annotation.internal.Child")
903904
@tu lazy val ContextResultCountAnnot: ClassSymbol = requiredClass("scala.annotation.internal.ContextResultCount")
905+
@tu lazy val ConvertibleAnnot: ClassSymbol = requiredClass("scala.annotation.internal.$convertible")
904906
@tu lazy val ProvisionalSuperClassAnnot: ClassSymbol = requiredClass("scala.annotation.internal.ProvisionalSuperClass")
905907
@tu lazy val DeprecatedAnnot: ClassSymbol = requiredClass("scala.deprecated")
906908
@tu lazy val ImplicitAmbiguousAnnot: ClassSymbol = requiredClass("scala.annotation.implicitAmbiguous")

compiler/src/dotty/tools/dotc/core/StdNames.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,8 @@ object StdNames {
453453
val common: N = "common"
454454
val compiletime : N = "compiletime"
455455
val conforms_ : N = "$conforms"
456+
val convertible_ : N = "$convertible"
457+
val convertibleTo: N = "convertibleTo"
456458
val copy: N = "copy"
457459
val currentMirror: N = "currentMirror"
458460
val create: N = "create"

compiler/src/dotty/tools/dotc/core/Types.scala

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1840,6 +1840,11 @@ object Types {
18401840

18411841
def dropRepeatedAnnot(using Context): Type = dropAnnot(defn.RepeatedAnnot)
18421842

1843+
/** A translation from types of original parameter ValDefs to the types
1844+
* of parameters in MethodTypes.
1845+
* Translates `Seq[T] @repeated` or `Array[T] @repeated` to `<repeated>[T]`.
1846+
* That way, repeated arguments are made manifest without risk of dropped annotations.
1847+
*/
18431848
def annotatedToRepeated(using Context): Type = this match {
18441849
case tp @ ExprType(tp1) => tp.derivedExprType(tp1.annotatedToRepeated)
18451850
case AnnotatedType(tp, annot) if annot matches defn.RepeatedAnnot =>
@@ -3770,27 +3775,33 @@ object Types {
37703775
* and inline parameters:
37713776
* - replace @repeated annotations on Seq or Array types by <repeated> types
37723777
* - add @inlineParam to inline parameters
3778+
* - add @erasedParam to erased parameters
3779+
* - add @$convertibe to parameters that have an @allowConversions annotation
37733780
*/
3774-
def fromSymbols(params: List[Symbol], resultType: Type)(using Context): MethodType = {
3775-
def translateInline(tp: Type): Type = tp match {
3776-
case ExprType(resType) => ExprType(AnnotatedType(resType, Annotation(defn.InlineParamAnnot)))
3777-
case _ => AnnotatedType(tp, Annotation(defn.InlineParamAnnot))
3778-
}
3779-
def translateErased(tp: Type): Type = tp match {
3780-
case ExprType(resType) => ExprType(AnnotatedType(resType, Annotation(defn.ErasedParamAnnot)))
3781-
case _ => AnnotatedType(tp, Annotation(defn.ErasedParamAnnot))
3782-
}
3783-
def paramInfo(param: Symbol) = {
3781+
def fromSymbols(params: List[Symbol], resultType: Type)(using Context): MethodType =
3782+
def addAnnotation(tp: Type, cls: ClassSymbol, underStar: Boolean): Type = tp match
3783+
case ExprType(resType) =>
3784+
ExprType(addAnnotation(resType, cls, underStar))
3785+
case tp @ AppliedType(tycon, arg :: Nil)
3786+
if tycon.typeSymbol == defn.RepeatedParamClass && underStar =>
3787+
tp.derivedAppliedType(tycon, addAnnotation(arg, cls, underStar) :: Nil)
3788+
case _ =>
3789+
AnnotatedType(tp, Annotation(cls))
3790+
3791+
def paramInfo(param: Symbol) =
37843792
var paramType = param.info.annotatedToRepeated
3785-
if (param.is(Inline)) paramType = translateInline(paramType)
3786-
if (param.is(Erased)) paramType = translateErased(paramType)
3793+
if param.is(Inline) then
3794+
paramType = addAnnotation(paramType, defn.InlineParamAnnot, underStar = false)
3795+
if param.is(Erased) then
3796+
paramType = addAnnotation(paramType, defn.ErasedParamAnnot, underStar = false)
3797+
if param.hasAnnotation(defn.AllowConversionsAnnot) then
3798+
paramType = addAnnotation(paramType, defn.ConvertibleAnnot, underStar = true)
37873799
paramType
3788-
}
37893800

37903801
apply(params.map(_.name.asTermName))(
37913802
tl => params.map(p => tl.integrate(params, paramInfo(p))),
37923803
tl => tl.integrate(params, resultType))
3793-
}
3804+
end fromSymbols
37943805

37953806
final def apply(paramNames: List[TermName])(paramInfosExp: MethodType => List[Type], resultTypeExp: MethodType => Type)(using Context): MethodType =
37963807
checkValid(unique(new CachedMethodType(paramNames)(paramInfosExp, resultTypeExp, self)))

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1754,22 +1754,27 @@ object Parsers {
17541754
else commaSeparated(() => argType())
17551755
}
17561756

1757-
/** FunArgType ::= Type | `=>' Type
1757+
private def convertibleTo(tp: () => Tree) =
1758+
if in.isIdent(nme.convertibleTo) && canStartTypeTokens.contains(in.lookahead.token)
1759+
then atSpan(in.skipToken()) { ConvertibleTo(tp()) }
1760+
else tp()
1761+
1762+
/** FunArgType ::= [`=>'] [`convertibleTo`] Type
17581763
*/
17591764
val funArgType: () => Tree = () =>
1760-
if (in.token == ARROW) atSpan(in.skipToken()) { ByNameTypeTree(typ()) }
1761-
else typ()
1765+
if in.token == ARROW then atSpan(in.skipToken())(ByNameTypeTree(convertibleTo(typ)))
1766+
else convertibleTo(typ)
17621767

17631768
/** ParamType ::= [`=>'] ParamValueType
17641769
*/
17651770
def paramType(): Tree =
17661771
if (in.token == ARROW) atSpan(in.skipToken()) { ByNameTypeTree(paramValueType()) }
17671772
else paramValueType()
17681773

1769-
/** ParamValueType ::= Type [`*']
1774+
/** ParamValueType ::= [`convertibleTo`] Type [`*']
17701775
*/
17711776
def paramValueType(): Tree = {
1772-
val t = toplevelTyp()
1777+
val t = convertibleTo(toplevelTyp)
17731778
if (isIdent(nme.raw.STAR)) {
17741779
in.nextToken()
17751780
atSpan(startOffset(t)) { PostfixOp(t, Ident(tpnme.raw.STAR)) }

compiler/src/dotty/tools/dotc/parsing/Tokens.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,5 +287,5 @@ object Tokens extends TokensCommon {
287287

288288
final val skipStopTokens = BitSet(SEMI, NEWLINE, NEWLINES, RBRACE, RPAREN, RBRACKET, OUTDENT)
289289

290-
final val softModifierNames = Set(nme.inline, nme.opaque, nme.open, nme.transparent, nme.infix)
290+
final val softModifierNames = Set(nme.convertibleTo, nme.inline, nme.opaque, nme.open, nme.transparent, nme.infix)
291291
}

compiler/src/dotty/tools/dotc/typer/Checking.scala

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -922,14 +922,18 @@ trait Checking {
922922
sym.srcPos)
923923

924924
/** If `tree` is an application of a new-style implicit conversion (using the apply
925-
* method of a `scala.Conversion` instance), check that implicit conversions are
926-
* enabled.
925+
* method of a `scala.Conversion` instance), check that the expected type is
926+
* a convertible formal parameter type or that implicit conversions are enabled.
927927
*/
928-
def checkImplicitConversionUseOK(tree: Tree)(using Context): Unit =
928+
def checkImplicitConversionUseOK(tree: Tree, expected: Type)(using Context): Unit =
929929
val sym = tree.symbol
930+
def isConvertibleParam(tp: Type) = tp match
931+
case AnnotatedType(_, annot) => annot.symbol == defn.ConvertibleAnnot
932+
case _ => false
930933
if sym.name == nme.apply
931934
&& sym.owner.derivesFrom(defn.ConversionClass)
932935
&& !sym.info.isErroneous
936+
&& !isConvertibleParam(expected)
933937
then
934938
def conv = methPart(tree) match
935939
case Select(qual, _) => qual.symbol.orElse(sym.owner)
@@ -1420,7 +1424,7 @@ trait NoChecking extends ReChecking {
14201424
override def checkStable(tp: Type, pos: SrcPos, kind: String)(using Context): Unit = ()
14211425
override def checkClassType(tp: Type, pos: SrcPos, traitReq: Boolean, stablePrefixReq: Boolean)(using Context): Type = tp
14221426
override def checkImplicitConversionDefOK(sym: Symbol)(using Context): Unit = ()
1423-
override def checkImplicitConversionUseOK(tree: Tree)(using Context): Unit = ()
1427+
override def checkImplicitConversionUseOK(tree: Tree, expected: Type)(using Context): Unit = ()
14241428
override def checkFeasibleParent(tp: Type, pos: SrcPos, where: => String = "")(using Context): Type = tp
14251429
override def checkAnnotArgs(tree: Tree)(using Context): tree.type = tree
14261430
override def checkNoTargetNameConflict(stats: List[Tree])(using Context): Unit = ()

compiler/src/dotty/tools/dotc/typer/Namer.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1536,12 +1536,14 @@ class Namer { typer: Typer =>
15361536
def typedAheadAnnotation(tree: Tree)(using Context): tpd.Tree =
15371537
typedAheadExpr(tree, defn.AnnotationClass.typeRef)
15381538

1539-
def typedAheadAnnotationClass(tree: Tree)(using Context): Symbol = tree match {
1539+
def typedAheadAnnotationClass(tree: Tree)(using Context): Symbol = tree match
15401540
case Apply(fn, _) => typedAheadAnnotationClass(fn)
15411541
case TypeApply(fn, _) => typedAheadAnnotationClass(fn)
15421542
case Select(qual, nme.CONSTRUCTOR) => typedAheadAnnotationClass(qual)
15431543
case New(tpt) => typedAheadType(tpt).tpe.classSymbol
1544-
}
1544+
case TypedSplice(_) =>
1545+
val sym = tree.symbol
1546+
if sym.isConstructor then sym.owner else sym
15451547

15461548
/** Enter and typecheck parameter list */
15471549
def completeParams(params: List[MemberDef])(using Context): Unit = {

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3251,7 +3251,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
32513251
case SearchSuccess(found, _, _, isExtension) =>
32523252
if isExtension then return found
32533253
else
3254-
checkImplicitConversionUseOK(found)
3254+
checkImplicitConversionUseOK(found, selProto)
32553255
return typedSelect(tree, pt, found)
32563256
case failure: SearchFailure =>
32573257
if failure.isAmbiguous then
@@ -3879,7 +3879,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
38793879
case SearchSuccess(found, _, _, isExtension) =>
38803880
if isExtension then found
38813881
else
3882-
checkImplicitConversionUseOK(found)
3882+
checkImplicitConversionUseOK(found, pt)
38833883
withoutMode(Mode.ImplicitsEnabled)(readapt(found))
38843884
case failure: SearchFailure =>
38853885
if (pt.isInstanceOf[ProtoType] && !failure.isAmbiguous) then

compiler/test/dotty/tools/dotc/CompilationTests.scala

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -138,11 +138,8 @@ class CompilationTests {
138138
compileFilesInDir("tests/neg-custom-args/allow-double-bindings", allowDoubleBindings),
139139
compileFilesInDir("tests/neg-custom-args/allow-deep-subtypes", allowDeepSubtypes),
140140
compileFilesInDir("tests/neg-custom-args/explicit-nulls", defaultOptions.and("-Yexplicit-nulls")),
141+
compileFilesInDir("tests/neg-custom-args/feature", defaultOptions.and("-Xfatal-warnings", "-feature")),
141142
compileFilesInDir("tests/neg-custom-args/no-experimental", defaultOptions.and("-Yno-experimental")),
142-
compileDir("tests/neg-custom-args/impl-conv", defaultOptions.and("-Xfatal-warnings", "-feature")),
143-
compileDir("tests/neg-custom-args/i13946", defaultOptions.and("-Xfatal-warnings", "-feature")),
144-
compileFile("tests/neg-custom-args/implicit-conversions.scala", defaultOptions.and("-Xfatal-warnings", "-feature")),
145-
compileFile("tests/neg-custom-args/implicit-conversions-old.scala", defaultOptions.and("-Xfatal-warnings", "-feature")),
146143
compileFile("tests/neg-custom-args/i3246.scala", scala2CompatMode),
147144
compileFile("tests/neg-custom-args/overrideClass.scala", scala2CompatMode),
148145
compileFile("tests/neg-custom-args/ovlazy.scala", scala2CompatMode.and("-Xfatal-warnings")),
@@ -182,7 +179,6 @@ class CompilationTests {
182179
compileFile("tests/neg-custom-args/deptypes.scala", defaultOptions.and("-language:experimental.dependent")),
183180
compileFile("tests/neg-custom-args/matchable.scala", defaultOptions.and("-Xfatal-warnings", "-source", "future")),
184181
compileFile("tests/neg-custom-args/i7314.scala", defaultOptions.and("-Xfatal-warnings", "-source", "future")),
185-
compileFile("tests/neg-custom-args/feature-shadowing.scala", defaultOptions.and("-Xfatal-warnings", "-feature")),
186182
compileDir("tests/neg-custom-args/hidden-type-errors", defaultOptions.and("-explain")),
187183
compileFile("tests/neg-custom-args/i13026.scala", defaultOptions.and("-print-lines")),
188184
compileFile("tests/neg-custom-args/i13838.scala", defaultOptions.and("-Ximplicit-search-limit", "1000")),
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package scala.annotation
2+
3+
/** An annotation on a parameter type that allows implicit conversions
4+
* for its arguments. Intended for use by Scala 2, to annotate Scala 2
5+
* libraries. Scala 3 uses the `convertibleTo` modifier on the parameter
6+
* type instead.
7+
*/
8+
class allowConversions extends scala.annotation.StaticAnnotation
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package scala.annotation.internal
2+
3+
/** An annotation on a parameter type that allows implicit conversions
4+
* for its arguments. The scala.convertible annotation is
5+
* converted to this annotation for function parameter types.
6+
*/
7+
class $convertible extends scala.annotation.StaticAnnotation
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
class Text(val str: String)
2+
3+
object Test:
4+
5+
given Conversion[String, Text] = Text(_)
6+
7+
def f(x: Text, y: => Text, zs: Text*) =
8+
println(s"${x.str} ${y.str} ${zs.map(_.str).mkString(" ")}")
9+
10+
f("abc", "def") // error // error
11+
f("abc", "def", "xyz", "uvw") // error // error // error // error
12+
f("abc", "def", "xyz", Text("uvw")) // error // error // error
13+
14+
def g(x: convertibleTo Text) =
15+
println(x.str)
16+
17+
18+
g("abc") // OK
19+
val gg = g
20+
gg("abc") // straight eta expansion is also OK
21+
22+
def h1[X](x: X)(y: X): Unit = ()
23+
24+
def h(x: convertibleTo Text) =
25+
val y = h1(x)
26+
y("abc") // error, inference through type variable does not propagate
27+
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
abc def
2+
abc def xyz uvw
3+
abc def xyz uvw

0 commit comments

Comments
 (0)