Skip to content

Commit

Permalink
Implement into modifier on parameter types (scala#14514)
Browse files Browse the repository at this point in the history
in the thread
https://contributors.scala-lang.org/t/proposed-changes-and-restrictions-for-implicit-conversions/4923
we discussed two changes that would make implicit conversions largely
redundant. One proposed change was implemented in scala#14497. The other is
implemented here. It's based on scala#14497.

The idea of this PR is to have some way to specify that a method
parameter accepts implicit conversions on its arguments. Then the
feature warning on implicit conversion use would be suppressed in this
case. In the previous thread I proposed
`~` to mark convertible arguments but I now feel this is too cryptic.
Instead there is a `into` prefix in the
parameter type. E.g.
```scala
  def f(x: into T) = ...
```
For Scala 2, we introduce an annotation on the parameter that expresses
the same thing:
```scala
  // proposed Scala-2 syntax:
  def f(@allowConversions x: T) = 
```
A larger example:
```scala
given Conversion[String, Text] = Text(_)

@main def Test =

  def f(x: into Text, y: => into Text, zs: into Text*) =
    println(s"${x.str} ${y.str} ${zs.map(_.str).mkString(" ")}")

  f("abc", "def")  // ok, no feature warning
  f("abc", "def", "xyz", "uvw")  // also ok
  f("abc", "def", "xyz", Text("uvw"))  // also ok
```
A larger example is a parser combinator library that works without
unrestricted implicit conversions:

[tests/run/Parser.scala](https://github.com/lampepfl/dotty/blob/0f113da1bacc01603740cde4a5bafbfae39895f0/tests/run/Parser.scala)
  • Loading branch information
KacperFKorban authored Nov 21, 2022
2 parents 4af64cd + 015f5cb commit b4f8eef
Show file tree
Hide file tree
Showing 35 changed files with 390 additions and 59 deletions.
44 changes: 27 additions & 17 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import core._
import util.Spans._, Types._, Contexts._, Constants._, Names._, NameOps._, Flags._
import Symbols._, StdNames._, Trees._, ContextOps._
import Decorators._, transform.SymUtils._
import Annotations.Annotation
import NameKinds.{UniqueName, EvidenceParamName, DefaultGetterName, WildcardParamName}
import typer.{Namer, Checking}
import util.{Property, SourceFile, SourcePosition, Chars}
Expand Down Expand Up @@ -165,32 +166,41 @@ object desugar {
*
* Generate setter where needed
*/
def valDef(vdef0: ValDef)(using Context): Tree = {
def valDef(vdef0: ValDef)(using Context): Tree =
val vdef @ ValDef(_, tpt, rhs) = vdef0
val mods = vdef.mods

val valName = normalizeName(vdef, tpt).asTermName
val vdef1 = cpy.ValDef(vdef)(name = valName)
var mods1 = vdef.mods

def dropInto(tpt: Tree): Tree = tpt match
case Into(tpt1) =>
mods1 = vdef.mods.withAddedAnnotation(
TypedSplice(
Annotation(defn.AllowConversionsAnnot).tree.withSpan(tpt.span.startPos)))
tpt1
case ByNameTypeTree(tpt1) =>
cpy.ByNameTypeTree(tpt)(dropInto(tpt1))
case PostfixOp(tpt1, op) if op.name == tpnme.raw.STAR =>
cpy.PostfixOp(tpt)(dropInto(tpt1), op)
case _ =>
tpt

val vdef1 = cpy.ValDef(vdef)(name = valName, tpt = dropInto(tpt))
.withMods(mods1)

if (isSetterNeeded(vdef)) {
// TODO: copy of vdef as getter needed?
// val getter = ValDef(mods, name, tpt, rhs) withPos vdef.pos?
// right now vdef maps via expandedTree to a thicket which concerns itself.
// I don't see a problem with that but if there is one we can avoid it by making a copy here.
if isSetterNeeded(vdef) then
val setterParam = makeSyntheticParameter(tpt = SetterParamTree().watching(vdef))
// The rhs gets filled in later, when field is generated and getter has parameters (see Memoize miniphase)
val setterRhs = if (vdef.rhs.isEmpty) EmptyTree else unitLiteral
val setter = cpy.DefDef(vdef)(
name = valName.setterName,
paramss = (setterParam :: Nil) :: Nil,
tpt = TypeTree(defn.UnitType),
rhs = setterRhs
).withMods((mods | Accessor) &~ (CaseAccessor | GivenOrImplicit | Lazy))
.dropEndMarker() // the end marker should only appear on the getter definition
name = valName.setterName,
paramss = (setterParam :: Nil) :: Nil,
tpt = TypeTree(defn.UnitType),
rhs = setterRhs
).withMods((vdef.mods | Accessor) &~ (CaseAccessor | GivenOrImplicit | Lazy))
.dropEndMarker() // the end marker should only appear on the getter definition
Thicket(vdef1, setter)
}
else vdef1
}
end valDef

def makeImplicitParameters(tpts: List[Tree], implicitFlag: FlagSet, forPrimaryConstructor: Boolean = false)(using Context): List[ValDef] =
for (tpt <- tpts) yield {
Expand Down
8 changes: 8 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/untpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
case class ContextBounds(bounds: TypeBoundsTree, cxBounds: List[Tree])(implicit @constructorOnly src: SourceFile) extends TypTree
case class PatDef(mods: Modifiers, pats: List[Tree], tpt: Tree, rhs: Tree)(implicit @constructorOnly src: SourceFile) extends DefTree
case class ExtMethods(paramss: List[ParamClause], methods: List[Tree])(implicit @constructorOnly src: SourceFile) extends Tree
case class Into(tpt: Tree)(implicit @constructorOnly src: SourceFile) extends Tree
case class MacroTree(expr: Tree)(implicit @constructorOnly src: SourceFile) extends Tree

case class ImportSelector(imported: Ident, renamed: Tree = EmptyTree, bound: Tree = EmptyTree)(implicit @constructorOnly src: SourceFile) extends Tree {
Expand Down Expand Up @@ -649,6 +650,9 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
def ExtMethods(tree: Tree)(paramss: List[ParamClause], methods: List[Tree])(using Context): Tree = tree match
case tree: ExtMethods if (paramss eq tree.paramss) && (methods == tree.methods) => tree
case _ => finalize(tree, untpd.ExtMethods(paramss, methods)(tree.source))
def Into(tree: Tree)(tpt: Tree)(using Context): Tree = tree match
case tree: Into if tpt eq tree.tpt => tree
case _ => finalize(tree, untpd.Into(tpt)(tree.source))
def ImportSelector(tree: Tree)(imported: Ident, renamed: Tree, bound: Tree)(using Context): Tree = tree match {
case tree: ImportSelector if (imported eq tree.imported) && (renamed eq tree.renamed) && (bound eq tree.bound) => tree
case _ => finalize(tree, untpd.ImportSelector(imported, renamed, bound)(tree.source))
Expand Down Expand Up @@ -718,6 +722,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
cpy.PatDef(tree)(mods, transform(pats), transform(tpt), transform(rhs))
case ExtMethods(paramss, methods) =>
cpy.ExtMethods(tree)(transformParamss(paramss), transformSub(methods))
case Into(tpt) =>
cpy.Into(tree)(transform(tpt))
case ImportSelector(imported, renamed, bound) =>
cpy.ImportSelector(tree)(transformSub(imported), transform(renamed), transform(bound))
case Number(_, _) | TypedSplice(_) =>
Expand Down Expand Up @@ -777,6 +783,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
this(this(this(x, pats), tpt), rhs)
case ExtMethods(paramss, methods) =>
this(paramss.foldLeft(x)(apply), methods)
case Into(tpt) =>
this(x, tpt)
case ImportSelector(imported, renamed, bound) =>
this(this(this(x, imported), renamed), bound)
case Number(_, _) =>
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/config/Feature.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ object Feature:
val saferExceptions = experimental("saferExceptions")
val pureFunctions = experimental("pureFunctions")
val captureChecking = experimental("captureChecking")
val into = experimental("into")

val globalOnlyImports: Set[TermName] = Set(pureFunctions, captureChecking)

Expand Down
8 changes: 2 additions & 6 deletions compiler/src/dotty/tools/dotc/core/Decorators.scala
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ object Decorators {
/** Implements filterConserve, zipWithConserve methods
* on lists that avoid duplication of list nodes where feasible.
*/
implicit class ListDecorator[T](val xs: List[T]) extends AnyVal {
extension [T](xs: List[T])

final def mapconserve[U](f: T => U): List[U] = {
@tailrec
Expand Down Expand Up @@ -207,11 +207,7 @@ object Decorators {
}

/** Union on lists seen as sets */
def | (ys: List[T]): List[T] = xs ::: (ys filterNot (xs contains _))

/** Intersection on lists seen as sets */
def & (ys: List[T]): List[T] = xs filter (ys contains _)
}
def setUnion (ys: List[T]): List[T] = xs ::: ys.filterNot(xs contains _)

extension [T, U](xss: List[List[T]])
def nestedMap(f: T => U): List[List[U]] = xss match
Expand Down
4 changes: 4 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,8 @@ class Definitions {

@tu lazy val RepeatedParamClass: ClassSymbol = enterSpecialPolyClass(tpnme.REPEATED_PARAM_CLASS, Covariant, Seq(ObjectType, SeqType))

@tu lazy val IntoType: TypeSymbol = enterAliasType(tpnme.INTO, HKTypeLambda(TypeBounds.empty :: Nil)(_.paramRefs(0)))

// fundamental classes
@tu lazy val StringClass: ClassSymbol = requiredClass("java.lang.String")
def StringType: Type = StringClass.typeRef
Expand Down Expand Up @@ -973,6 +975,7 @@ class Definitions {
@tu lazy val RefiningAnnotationClass: ClassSymbol = requiredClass("scala.annotation.RefiningAnnotation")

// Annotation classes
@tu lazy val AllowConversionsAnnot: ClassSymbol = requiredClass("scala.annotation.allowConversions")
@tu lazy val AnnotationDefaultAnnot: ClassSymbol = requiredClass("scala.annotation.internal.AnnotationDefault")
@tu lazy val BeanPropertyAnnot: ClassSymbol = requiredClass("scala.beans.BeanProperty")
@tu lazy val BooleanBeanPropertyAnnot: ClassSymbol = requiredClass("scala.beans.BooleanBeanProperty")
Expand Down Expand Up @@ -2005,6 +2008,7 @@ class Definitions {
orType,
RepeatedParamClass,
ByNameParamClass2x,
IntoType,
AnyValClass,
NullClass,
NothingClass,
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/core/Names.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import scala.annotation.internal.sharable
object Names {
import NameKinds._

/** Things that can be turned into names with `totermName` and `toTypeName`
* Decorators defines implements these as extension methods for strings.
/** Things that can be turned into names with `toTermName` and `toTypeName`.
* Decorators implements these as extension methods for strings.
*/
type PreName = Name | String

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -634,7 +634,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
case param: TypeParamRef if contains(param) =>
param :: (if (isUpper) upper(param) else lower(param))
case tp: AndType if isUpper =>
dependentParams(tp.tp1, isUpper) | (dependentParams(tp.tp2, isUpper))
dependentParams(tp.tp1, isUpper).setUnion(dependentParams(tp.tp2, isUpper))
case tp: OrType if !isUpper =>
dependentParams(tp.tp1, isUpper).intersect(dependentParams(tp.tp2, isUpper))
case EtaExpansion(tycon) =>
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ object StdNames {
val EXCEPTION_RESULT_PREFIX: N = "exceptionResult"
val EXPAND_SEPARATOR: N = str.EXPAND_SEPARATOR
val IMPORT: N = "<import>"
val INTO: N = "<into>"
val MODULE_SUFFIX: N = str.MODULE_SUFFIX
val OPS_PACKAGE: N = "<special-ops>"
val OVERLOADED: N = "<overloaded>"
Expand Down Expand Up @@ -500,6 +501,7 @@ object StdNames {
val info: N = "info"
val inlinedEquals: N = "inlinedEquals"
val internal: N = "internal"
val into: N = "into"
val isArray: N = "isArray"
val isDefinedAt: N = "isDefinedAt"
val isDefinedAtImpl: N = "$isDefinedAt"
Expand Down
60 changes: 45 additions & 15 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,10 @@ object Types {
def isRepeatedParam(using Context): Boolean =
typeSymbol eq defn.RepeatedParamClass

/** Is this a parameter type that allows implicit argument converson? */
def isConvertibleParam(using Context): Boolean =
typeSymbol eq defn.IntoType

/** Is this the type of a method that has a repeated parameter type as
* last parameter type?
*/
Expand Down Expand Up @@ -536,7 +540,7 @@ object Types {
case tp: ClassInfo =>
tp.cls :: Nil
case AndType(l, r) =>
l.parentSymbols(include) | r.parentSymbols(include)
l.parentSymbols(include).setUnion(r.parentSymbols(include))
case OrType(l, r) =>
l.parentSymbols(include) intersect r.parentSymbols(include) // TODO does not conform to spec
case _ =>
Expand Down Expand Up @@ -1864,6 +1868,11 @@ object Types {

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

/** A translation from types of original parameter ValDefs to the types
* of parameters in MethodTypes.
* Translates `Seq[T] @repeated` or `Array[T] @repeated` to `<repeated>[T]`.
* That way, repeated arguments are made manifest without risk of dropped annotations.
*/
def annotatedToRepeated(using Context): Type = this match {
case tp @ ExprType(tp1) =>
tp.derivedExprType(tp1.annotatedToRepeated)
Expand Down Expand Up @@ -3948,27 +3957,48 @@ object Types {
* and inline parameters:
* - replace @repeated annotations on Seq or Array types by <repeated> types
* - add @inlineParam to inline parameters
* - add @erasedParam to erased parameters
* - wrap types of parameters that have an @allowConversions annotation with Into[_]
*/
def fromSymbols(params: List[Symbol], resultType: Type)(using Context): MethodType = {
def translateInline(tp: Type): Type = tp match {
case ExprType(resType) => ExprType(AnnotatedType(resType, Annotation(defn.InlineParamAnnot)))
case _ => AnnotatedType(tp, Annotation(defn.InlineParamAnnot))
}
def translateErased(tp: Type): Type = tp match {
case ExprType(resType) => ExprType(AnnotatedType(resType, Annotation(defn.ErasedParamAnnot)))
case _ => AnnotatedType(tp, Annotation(defn.ErasedParamAnnot))
}
def paramInfo(param: Symbol) = {
def fromSymbols(params: List[Symbol], resultType: Type)(using Context): MethodType =
def addAnnotation(tp: Type, cls: ClassSymbol): Type = tp match
case ExprType(resType) => ExprType(addAnnotation(resType, cls))
case _ => AnnotatedType(tp, Annotation(cls))

def wrapConvertible(tp: Type) =
AppliedType(defn.IntoType.typeRef, tp :: Nil)

/** Add `Into[..] to the type itself and if it is a function type, to all its
* curried result type(s) as well.
*/
def addInto(tp: Type): Type = tp match
case tp @ AppliedType(tycon, args) if tycon.typeSymbol == defn.RepeatedParamClass =>
tp.derivedAppliedType(tycon, addInto(args.head) :: Nil)
case tp @ AppliedType(tycon, args) if defn.isFunctionType(tp) =>
wrapConvertible(tp.derivedAppliedType(tycon, args.init :+ addInto(args.last)))
case tp @ RefinedType(parent, rname, rinfo) if defn.isFunctionOrPolyType(tp) =>
wrapConvertible(tp.derivedRefinedType(parent, rname, addInto(rinfo)))
case tp: MethodOrPoly =>
tp.derivedLambdaType(resType = addInto(tp.resType))
case ExprType(resType) =>
ExprType(addInto(resType))
case _ =>
wrapConvertible(tp)

def paramInfo(param: Symbol) =
var paramType = param.info.annotatedToRepeated
if (param.is(Inline)) paramType = translateInline(paramType)
if (param.is(Erased)) paramType = translateErased(paramType)
if param.is(Inline) then
paramType = addAnnotation(paramType, defn.InlineParamAnnot)
if param.is(Erased) then
paramType = addAnnotation(paramType, defn.ErasedParamAnnot)
if param.hasAnnotation(defn.AllowConversionsAnnot) then
paramType = addInto(paramType)
paramType
}

apply(params.map(_.name.asTermName))(
tl => params.map(p => tl.integrate(params, paramInfo(p))),
tl => tl.integrate(params, resultType))
}
end fromSymbols

final def apply(paramNames: List[TermName])(paramInfosExp: MethodType => List[Type], resultTypeExp: MethodType => Type)(using Context): MethodType =
checkValid(unique(new CachedMethodType(paramNames)(paramInfosExp, resultTypeExp, self)))
Expand Down
11 changes: 9 additions & 2 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1917,6 +1917,13 @@ object Parsers {
else
core()

private def maybeInto(tp: () => Tree) =
if in.isIdent(nme.into)
&& in.featureEnabled(Feature.into)
&& canStartTypeTokens.contains(in.lookahead.token)
then atSpan(in.skipToken()) { Into(tp()) }
else tp()

/** FunArgType ::= Type
* | `=>' Type
* | [CaptureSet] `->' Type
Expand All @@ -1929,10 +1936,10 @@ object Parsers {
*/
def paramType(): Tree = paramTypeOf(paramValueType)

/** ParamValueType ::= Type [`*']
/** ParamValueType ::= [`into`] Type [`*']
*/
def paramValueType(): Tree = {
val t = toplevelTyp()
val t = maybeInto(toplevelTyp)
if (isIdent(nme.raw.STAR)) {
in.nextToken()
atSpan(startOffset(t)) { PostfixOp(t, Ident(tpnme.raw.STAR)) }
Expand Down
4 changes: 3 additions & 1 deletion compiler/src/dotty/tools/dotc/parsing/Tokens.scala
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,8 @@ object Tokens extends TokensCommon {
final val canStartInfixTypeTokens: TokenSet = literalTokens | identifierTokens | BitSet(
THIS, SUPER, USCORE, LPAREN, LBRACE, AT)

final val canStartTypeTokens: TokenSet = canStartInfixTypeTokens | BitSet(LBRACE)

final val templateIntroTokens: TokenSet = BitSet(CLASS, TRAIT, OBJECT, ENUM, CASECLASS, CASEOBJECT)

final val dclIntroTokens: TokenSet = BitSet(DEF, VAL, VAR, TYPE, GIVEN)
Expand Down Expand Up @@ -287,7 +289,7 @@ object Tokens extends TokensCommon {

final val closingParens = BitSet(RPAREN, RBRACKET, RBRACE)

final val softModifierNames = Set(nme.inline, nme.opaque, nme.open, nme.transparent, nme.infix)
final val softModifierNames = Set(nme.inline, nme.into, nme.opaque, nme.open, nme.transparent, nme.infix)

def showTokenDetailed(token: Int): String = debugString(token)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
case _ =>
val tsym = tycon.typeSymbol
if tycon.isRepeatedParam then toTextLocal(args.head) ~ "*"
else if tp.isConvertibleParam then "into " ~ toText(args.head)
else if defn.isFunctionSymbol(tsym) then
toTextFunction(args, tsym.name.isContextFunction, tsym.name.isErasedFunction,
isPure = Feature.pureFunsEnabled && !tsym.name.isImpureFunction)
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/transform/TypeUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ object TypeUtils {
case AndType(tp1, tp2) =>
// We assume that we have the following property:
// (T1, T2, ..., Tn) & (U1, U2, ..., Un) = (T1 & U1, T2 & U2, ..., Tn & Un)
tp1.tupleElementTypes.zip(tp2.tupleElementTypes).map { case (t1, t2) => t1 & t2 }
tp1.tupleElementTypes.zip(tp2.tupleElementTypes).map { case (t1, t2) => t1.intersect(t2) }
case OrType(tp1, tp2) =>
None // We can't combine the type of two tuples
case _ =>
Expand Down
9 changes: 5 additions & 4 deletions compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -979,14 +979,15 @@ trait Checking {
sym.srcPos)

/** If `tree` is an application of a new-style implicit conversion (using the apply
* method of a `scala.Conversion` instance), check that implicit conversions are
* enabled.
* method of a `scala.Conversion` instance), check that the expected type is
* a convertible formal parameter type or that implicit conversions are enabled.
*/
def checkImplicitConversionUseOK(tree: Tree)(using Context): Unit =
def checkImplicitConversionUseOK(tree: Tree, expected: Type)(using Context): Unit =
val sym = tree.symbol
if sym.name == nme.apply
&& sym.owner.derivesFrom(defn.ConversionClass)
&& !sym.info.isErroneous
&& !expected.isConvertibleParam
then
def conv = methPart(tree) match
case Select(qual, _) => qual.symbol.orElse(sym.owner)
Expand Down Expand Up @@ -1536,7 +1537,7 @@ trait NoChecking extends ReChecking {
override def checkStable(tp: Type, pos: SrcPos, kind: String)(using Context): Unit = ()
override def checkClassType(tp: Type, pos: SrcPos, traitReq: Boolean, stablePrefixReq: Boolean)(using Context): Type = tp
override def checkImplicitConversionDefOK(sym: Symbol)(using Context): Unit = ()
override def checkImplicitConversionUseOK(tree: Tree)(using Context): Unit = ()
override def checkImplicitConversionUseOK(tree: Tree, expected: Type)(using Context): Unit = ()
override def checkFeasibleParent(tp: Type, pos: SrcPos, where: => String = "")(using Context): Type = tp
override def checkAnnotArgs(tree: Tree)(using Context): tree.type = tree
override def checkNoTargetNameConflict(stats: List[Tree])(using Context): Unit = ()
Expand Down
6 changes: 4 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/Namer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1618,12 +1618,14 @@ class Namer { typer: Typer =>
def typedAheadExpr(tree: Tree, pt: Type = WildcardType)(using Context): tpd.Tree =
typedAhead(tree, typer.typedExpr(_, pt))

def typedAheadAnnotationClass(tree: Tree)(using Context): Symbol = tree match {
def typedAheadAnnotationClass(tree: Tree)(using Context): Symbol = tree match
case Apply(fn, _) => typedAheadAnnotationClass(fn)
case TypeApply(fn, _) => typedAheadAnnotationClass(fn)
case Select(qual, nme.CONSTRUCTOR) => typedAheadAnnotationClass(qual)
case New(tpt) => typedAheadType(tpt).tpe.classSymbol
}
case TypedSplice(_) =>
val sym = tree.symbol
if sym.isConstructor then sym.owner else sym

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

0 comments on commit b4f8eef

Please sign in to comment.